Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate Py3.6 and Py3.7 #119

Merged
merged 36 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bc605cd
remove tests for outdated versions
maxfischer2781 Jan 5, 2024
389c64a
update GH actions
maxfischer2781 Jan 5, 2024
7261949
remove old versions from metadata
maxfischer2781 Jan 5, 2024
b7da83d
knifes and lint
maxfischer2781 Jan 5, 2024
c8298a3
remove typing backports
maxfischer2781 Jan 5, 2024
b39e7dd
lint
maxfischer2781 Jan 5, 2024
78e4812
simplify ScopedIter
maxfischer2781 Jan 5, 2024
91b28e6
async generator
maxfischer2781 Jan 5, 2024
20776b1
walrus fold
maxfischer2781 Jan 5, 2024
b12a142
usage note
maxfischer2781 Jan 5, 2024
63dbfec
type stubs
maxfischer2781 Jan 5, 2024
543110a
clearer control flow
maxfischer2781 Jan 5, 2024
67fcdcf
pylance typing
maxfischer2781 Jan 5, 2024
15517f4
inline generator
maxfischer2781 Jan 5, 2024
dcf2edb
remove redundant special case
maxfischer2781 Jan 5, 2024
91dbce3
walrus fold
maxfischer2781 Jan 5, 2024
24e1dbe
typing and usage
maxfischer2781 Jan 5, 2024
730c1b3
enforce positional parameters
maxfischer2781 Jan 5, 2024
4a0659c
remove micro-optimisation
maxfischer2781 Jan 6, 2024
e721911
pyright typing
maxfischer2781 Jan 6, 2024
433897e
line length is enforced by black
maxfischer2781 Jan 6, 2024
1e83afa
docs typo
maxfischer2781 Jan 6, 2024
350d694
remove slot_get helper (closes #78)
maxfischer2781 Jan 6, 2024
b7d6d9e
cleanup
maxfischer2781 Jan 6, 2024
1ffdace
remove deprecated test
maxfischer2781 Jan 6, 2024
ca008ef
improved typing for test helpers
maxfischer2781 Jan 7, 2024
46d6471
ignore typing ellipsis
maxfischer2781 Jan 7, 2024
390e091
expanded test case
maxfischer2781 Jan 7, 2024
d591e41
improved type hinting for reduce
maxfischer2781 Jan 7, 2024
57656b9
type hinting
maxfischer2781 Jan 7, 2024
ce1eb68
cleanup
maxfischer2781 Jan 7, 2024
62e40cc
typing cleanup
maxfischer2781 Jan 7, 2024
f3e2295
simplify batched
maxfischer2781 Jan 7, 2024
5d32b7c
aclose type check
maxfischer2781 Jan 7, 2024
82e2d0a
slots
maxfischer2781 Jan 7, 2024
6c9cd8f
ignore inference blurb
maxfischer2781 Jan 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ on:

jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [
'3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12',
'pypy-3.6', 'pypy-3.8', 'pypy-3.10'
'3.8', '3.9', '3.10', '3.11', '3.12',
'pypy-3.8', 'pypy-3.10'
]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
37 changes: 21 additions & 16 deletions asyncstdlib/_core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
"""
Internal helpers to safely build async abstractions

While some of these helpers have public siblings
(e.g. :py:class:`~.ScopedIter` and :py:func:`~.asynctools.scoped_iter`)
they are purposely kept separate.
Any helpers in this module are *not* bound to maintaining a public interface,
and offer less convenience to save on overhead.
"""
from inspect import iscoroutinefunction
from typing import (
Any,
Expand Down Expand Up @@ -55,41 +64,38 @@ async def _aiter_sync(iterable: Iterable[T]) -> AsyncIterator[T]:


class ScopedIter(Generic[T]):
"""Context manager that provides and cleans up an iterator for an iterable"""
"""
Context manager that provides and cleans up an iterator for an iterable

Note that unlike :py:func:`~.asynctools.scoped_iter`, this helper does
*not* borrow the iterator automatically. Use :py:func:`~.borrow` if needed.
"""

__slots__ = ("_iterable", "_iterator")
__slots__ = ("_iterator",)

def __init__(self, iterable: AnyIterable[T]):
self._iterable: Optional[AnyIterable[T]] = iterable
self._iterator: Optional[AsyncIterator[T]] = None
self._iterator: AsyncIterator[T] = aiter(iterable)

async def __aenter__(self) -> AsyncIterator[T]:
assert (
self._iterable is not None
), f"{self.__class__.__name__} is not re-entrant"
self._iterator = aiter(self._iterable)
self._iterable = None
return self._iterator

async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> bool:
) -> None:
try:
aclose = self._iterator.aclose() # type: ignore
except AttributeError:
pass
else:
await aclose
return False


async def borrow(iterator: AsyncIterator[T]) -> AsyncGenerator[T, None]:
def borrow(iterator: AsyncIterator[T]) -> AsyncGenerator[T, None]:
"""Borrow an async iterator for iteration, preventing it from being closed"""
async for item in iterator:
yield item
return (item async for item in iterator)


def awaitify(
Expand All @@ -112,8 +118,7 @@ def __init__(self, function: Union[Callable[..., T], Callable[..., Awaitable[T]]
self._async_call: Optional[Callable[..., Awaitable[T]]] = None

def __call__(self, *args: Any, **kwargs: Any) -> Awaitable[T]:
async_call = self._async_call
if async_call is None:
if (async_call := self._async_call) is None:
value = self.__wrapped__(*args, **kwargs)
if isinstance(value, Awaitable):
self._async_call = self.__wrapped__ # type: ignore
Expand Down
23 changes: 15 additions & 8 deletions asyncstdlib/_lrucache.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,18 @@

def cache_parameters(self) -> CacheParameters:
"""Get the parameters of the cache"""
...

Check warning on line 79 in asyncstdlib/_lrucache.py

View check run for this annotation

Codecov / codecov/patch

asyncstdlib/_lrucache.py#L79

Added line #L79 was not covered by tests
Dismissed Show dismissed Hide dismissed

def cache_info(self) -> CacheInfo:
"""
Get the current performance and boundary of the cache
as a :py:class:`~typing.NamedTuple`
"""
...

Check warning on line 86 in asyncstdlib/_lrucache.py

View check run for this annotation

Codecov / codecov/patch

asyncstdlib/_lrucache.py#L86

Added line #L86 was not covered by tests
Dismissed Show dismissed Hide dismissed

def cache_clear(self) -> None:
"""Evict all call argument patterns and their results from the cache"""
...

Check warning on line 90 in asyncstdlib/_lrucache.py

View check run for this annotation

Codecov / codecov/patch

asyncstdlib/_lrucache.py#L90

Added line #L90 was not covered by tests
Dismissed Show dismissed Hide dismissed

def cache_discard(self, *args: Any, **kwargs: Any) -> None:
"""
Expand All @@ -95,9 +98,11 @@
the descriptor must support wrapping descriptors for this method
to detect implicit arguments such as ``self``.
"""
# Maintainers note:
# "support wrapping descriptors" means that the wrapping descriptor has to use
# the cache as a descriptor as well, i.e. invoke its ``__get__`` method instead
# of just passing in `self`/`cls`/... directly.
...

Check warning on line 105 in asyncstdlib/_lrucache.py

View check run for this annotation

Codecov / codecov/patch

asyncstdlib/_lrucache.py#L105

Added line #L105 was not covered by tests
Dismissed Show dismissed Hide dismissed


class LRUAsyncBoundCallable(LRUAsyncCallable[AC]):
Expand Down Expand Up @@ -212,7 +217,8 @@
elif callable(maxsize):
# used as function decorator, first arg is the function to be wrapped
fast_wrapper = CachedLRUAsyncCallable(cast(AC, maxsize), typed, 128)
return update_wrapper(fast_wrapper, maxsize)
update_wrapper(fast_wrapper, maxsize)
return fast_wrapper
elif maxsize is not None:
raise TypeError(
"first argument to 'lru_cache' must be an int, a callable or None"
Expand All @@ -227,7 +233,8 @@
wrapper = UncachedLRUAsyncCallable(function, typed)
else:
wrapper = CachedLRUAsyncCallable(function, typed, maxsize)
return update_wrapper(wrapper, function)
update_wrapper(wrapper, function)
return wrapper

return lru_decorator

Expand Down Expand Up @@ -301,11 +308,11 @@
__get__ = cache__get

def __init__(self, call: AC, typed: bool):
self.__wrapped__ = call
self.__wrapped__ = call # type: ignore[reportIncompatibleMethodOverride]
self.__misses = 0
self.__typed = typed

async def __call__(self, *args, **kwargs): # type: ignore
async def __call__(self, *args: Any, **kwargs: Any) -> Any: # type: ignore[reportIncompatibleVariableOverride]
self.__misses += 1
return await self.__wrapped__(*args, **kwargs)

Expand Down Expand Up @@ -339,13 +346,13 @@
__get__ = cache__get

def __init__(self, call: AC, typed: bool):
self.__wrapped__ = call
self.__wrapped__ = call # type: ignore[reportIncompatibleMethodOverride]
self.__hits = 0
self.__misses = 0
self.__typed = typed
self.__cache: Dict[Union[CallKey, int, str], Any] = {}

async def __call__(self, *args, **kwargs): # type: ignore
async def __call__(self, *args: Any, **kwargs: Any) -> Any: # type: ignore[reportIncompatibleVariableOverride]
key = CallKey.from_call(args, kwargs, typed=self.__typed)
try:
result = self.__cache[key]
Expand Down Expand Up @@ -394,14 +401,14 @@
__get__ = cache__get

def __init__(self, call: AC, typed: bool, maxsize: int):
self.__wrapped__ = call
self.__wrapped__ = call # type: ignore[reportIncompatibleMethodOverride]
self.__hits = 0
self.__misses = 0
self.__typed = typed
self.__maxsize = maxsize
self.__cache: OrderedDict[Union[int, str, CallKey], Any] = OrderedDict()

async def __call__(self, *args, **kwargs): # type: ignore
async def __call__(self, *args: Any, **kwargs: Any) -> Any: # type: ignore[reportIncompatibleVariableOverride]
key = CallKey.from_call(args, kwargs, typed=self.__typed)
try:
result = self.__cache[key]
Expand Down
11 changes: 1 addition & 10 deletions asyncstdlib/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
This module is for internal use only. Do *not* put any new
"async typing" definitions here.
"""
import sys
from typing import (
TypeVar,
Hashable,
Expand All @@ -16,15 +15,7 @@
Awaitable,
)

if sys.version_info >= (3, 8):
from typing import Protocol, AsyncContextManager, ContextManager, TypedDict
else:
from typing_extensions import (
Protocol,
AsyncContextManager,
ContextManager,
TypedDict,
)
from typing import Protocol, AsyncContextManager, ContextManager, TypedDict

__all__ = [
"Protocol",
Expand Down
8 changes: 4 additions & 4 deletions asyncstdlib/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ async def _zip_inner_strict(
tried = 0
try:
while True:
items = []
items: _sync_builtins.list[T] = []
for tried, _aiter in _sync_builtins.enumerate(aiters): # noqa: B007
items.append(await anext(_aiter))
yield (*items,)
Expand All @@ -280,15 +280,15 @@ async def _zip_inner_strict(
if tried > 0:
plural = " " if tried == 1 else "s 1-"
raise ValueError(
f"zip() argument {tried+1} is shorter than argument{plural}{tried}"
f"zip() argument {tried + 1} is shorter than argument{plural}{tried}"
) from None
# after the first iterable was empty, some later iterable may be not
sentinel = object()
for tried, _aiter in _sync_builtins.enumerate(aiters):
if await anext(_aiter, sentinel) is not sentinel:
plural = " " if tried == 1 else "s 1-"
raise ValueError(
f"zip() argument {tried+1} is longer than argument{plural}{tried}"
f"zip() argument {tried + 1} is longer than argument{plural}{tried}"
) from None
return

Expand Down Expand Up @@ -756,4 +756,4 @@ async def sorted(
async_key = _awaitify(key)
keyed_items = [(await async_key(item), item) async for item in aiter(iterable)]
keyed_items.sort(key=lambda ki: ki[0], reverse=reverse)
return [item for key, item in keyed_items]
return [item for _, item in keyed_items]
6 changes: 2 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand All @@ -25,8 +23,8 @@ classifiers = [
]
license = {"file" = "LICENSE"}
keywords = ["async", "enumerate", "itertools", "builtins", "functools", "contextlib"]
requires-python = "~=3.6"
dependencies = ["typing_extensions; python_version<'3.8'"]
requires-python = "~=3.8"
dependencies = []

[project.optional-dependencies]
test = [
Expand Down
Loading