diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0138559..2d15da0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, "3.10"] + python-version: ["3.11", "3.12"] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index b657205..c0d2ca3 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Python Env uses: actions/setup-python@v3 with: - python-version: "3.9" + python-version: "3.11" - name: Install dependencies run: pip install poetry dunamai diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 081963c..f04bb60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,24 +1,12 @@ +exclude: "_version.py|versioneer.py" repos: - hooks: - - args: - - --remove-all-unused-imports - - --in-place - id: autoflake - repo: https://github.com/humitos/mirrors-autoflake - rev: v1.1 - - hooks: - - id: isort - repo: https://github.com/timothycrosley/isort - rev: 5.10.1 - - hooks: - - id: black - repo: https://github.com/psf/black - rev: 22.3.0 - - hooks: - - id: flake8 - exclude: (^docs/|^examples/|^notebooks/|^tests/) - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - id: ruff + args: ["--fix"] + - id: ruff-format + args: [--check] + repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.11 - hooks: - id: pyright name: pyright @@ -26,5 +14,5 @@ repos: language: node pass_filenames: false types: [python] - additional_dependencies: ["pyright@1.1.255"] + additional_dependencies: ["pyright@1.1.344"] repo: local diff --git a/.python-version b/.python-version index 30291cb..171a6a9 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.0 +3.12.1 diff --git a/aioreactive/__init__.py b/aioreactive/__init__.py index cdca1e8..75cf439 100644 --- a/aioreactive/__init__.py +++ b/aioreactive/__init__.py @@ -14,21 +14,12 @@ """ from __future__ import annotations -from typing import ( - Any, - AsyncIterable, - Awaitable, - Callable, - Iterable, - Optional, - Tuple, - TypeVar, - Union, - overload, -) +from collections.abc import AsyncIterable, Awaitable, Callable, Iterable +from typing import Any, TypeVar, TypeVarTuple -from expression import Option, curry_flipped, pipe +from expression import Option, curry_flip, pipe from expression.system.disposable import AsyncDisposable +from typing_extensions import Unpack from .observables import AsyncAnonymousObservable, AsyncIterableObservable from .observers import ( @@ -41,10 +32,12 @@ from .subscription import run from .types import AsyncObservable, AsyncObserver, CloseAsync, SendAsync, ThrowAsync + _A = TypeVar("_A") _B = TypeVar("_B") _C = TypeVar("_C") _D = TypeVar("_D") +_V = TypeVarTuple("_V") _TSource = TypeVar("_TSource") _TResult = TypeVar("_TResult") _TOther = TypeVar("_TOther") @@ -67,9 +60,9 @@ def __init__(self, source: AsyncObservable[_TSource]) -> None: async def subscribe_async( self, - send: Optional[Union[SendAsync[_TSource], AsyncObserver[_TSource]]] = None, - throw: Optional[ThrowAsync] = None, - close: Optional[CloseAsync] = None, + send: SendAsync[_TSource] | AsyncObserver[_TSource] | None = None, + throw: ThrowAsync | None = None, + close: CloseAsync | None = None, ) -> AsyncDisposable: """Subscribe to the async observable. @@ -77,21 +70,21 @@ async def subscribe_async( observable. Args: - observer: The async observer to subscribe. + send: The async observer or the send function to subscribe. + throw: The throw function to subscribe. + close: The close function to subscribe. Returns: An async disposable that can be used to dispose the subscription. """ - observer = ( - send - if isinstance(send, AsyncObserver) - else AsyncAnonymousObserver(send, throw, close) - ) + observer = send if isinstance(send, AsyncObserver) else AsyncAnonymousObserver(send, throw, close) return await self._source.subscribe_async(observer) - def __getitem__(self, key: Union[slice, int]) -> AsyncRx[_TSource]: - """Slices the given source stream using Python slice notation. + def __getitem__(self, key: slice | int) -> AsyncRx[_TSource]: + """Slice observable. + + Slices the given source stream using Python slice notation. The arguments to slice is start, stop and step given within brackets [] and separated with the ':' character. It is basically a wrapper around the operators skip(), skip_last(), @@ -118,7 +111,6 @@ def __getitem__(self, key: Union[slice, int]) -> AsyncRx[_TSource]: Returns: The sliced source stream. """ - from .filtering import slice as _slice if isinstance(key, slice): @@ -126,7 +118,7 @@ def __getitem__(self, key: Union[slice, int]) -> AsyncRx[_TSource]: else: start, stop, step = key, key + 1, 1 - return AsyncRx(pipe(self, _slice(start, stop, step))) + return AsyncRx(pipe(self, _slice(start, stop, step or 1))) @classmethod def create(cls, source: AsyncObservable[_TSource]) -> AsyncRx[_TSource]: @@ -145,9 +137,7 @@ def from_iterable(cls, iter: Iterable[_TSource]) -> AsyncRx[_TSource]: return AsyncRx(from_iterable(iter)) @classmethod - def from_async_iterable( - cls, iter: AsyncIterable[_TSource] - ) -> AsyncObservable[_TSource]: + def from_async_iterable(cls, iter: AsyncIterable[_TSource]) -> AsyncObservable[_TSource]: """Convert an async iterable to an async observable stream. Example: @@ -157,7 +147,6 @@ def from_async_iterable( The source stream whose elements are pulled from the given (async) iterable sequence. """ - return AsyncRx(from_async_iterable(iter)) @classmethod @@ -169,9 +158,7 @@ def single(cls, value: _TSource) -> AsyncRx[_TSource]: def as_async_observable(self) -> AsyncObservable[_TSource]: return AsyncAnonymousObservable(self.subscribe_async) - def choose( - self, chooser: Callable[[_TSource], Option[_TSource]] - ) -> AsyncObservable[_TSource]: + def choose(self, chooser: Callable[[_TSource], Option[_TSource]]) -> AsyncObservable[_TSource]: """Choose. Applies the given function to each element of the stream and returns @@ -187,9 +174,7 @@ def choose( """ return AsyncRx(pipe(self, choose(chooser))) - def choose_async( - self, chooser: Callable[[_TSource], Awaitable[Option[_TSource]]] - ) -> AsyncObservable[_TSource]: + def choose_async(self, chooser: Callable[[_TSource], Awaitable[Option[_TSource]]]) -> AsyncObservable[_TSource]: """Choose async. Applies the given async function to each element of the stream and @@ -205,9 +190,7 @@ def choose_async( """ return AsyncRx(pipe(self, choose_async(chooser))) - def combine_latest( - self, other: AsyncObservable[_TOther] - ) -> AsyncRx[Tuple[_TSource, _TOther]]: + def combine_latest(self, other: AsyncObservable[_TOther]) -> AsyncRx[tuple[_TSource, _TOther]]: from .combine import combine_latest xs = pipe( @@ -233,7 +216,6 @@ def debounce(self, seconds: float) -> AsyncRx[_TSource]: Returns: The debounced stream. """ - from .timeshift import debounce return AsyncRx(pipe(self, debounce(seconds))) @@ -263,14 +245,11 @@ def filter(self, predicate: Callable[[_TSource], bool]) -> AsyncRx[_TSource]: Returns: The filtered stream. """ - from .filtering import filter as _filter return AsyncRx(pipe(self, _filter(predicate))) - def filteri( - self, predicate: Callable[[_TSource, int], bool] - ) -> AsyncObservable[_TSource]: + def filteri(self, predicate: Callable[[_TSource, int], bool]) -> AsyncObservable[_TSource]: """Filter with index. Filters the elements of an observable sequence based on a predicate @@ -285,23 +264,17 @@ def filteri( """ return AsyncRx(pipe(self, filteri(predicate))) - def filter_async( - self, predicate: Callable[[_TSource], Awaitable[bool]] - ) -> AsyncRx[_TSource]: + def filter_async(self, predicate: Callable[[_TSource], Awaitable[bool]]) -> AsyncRx[_TSource]: from .filtering import filter_async return AsyncRx(pipe(self, filter_async(predicate))) - def flat_map( - self, selector: Callable[[_TSource], AsyncObservable[_TResult]] - ) -> AsyncRx[_TResult]: + def flat_map(self, selector: Callable[[_TSource], AsyncObservable[_TResult]]) -> AsyncRx[_TResult]: from .transform import flat_map return AsyncRx.create(pipe(self, flat_map(selector))) - def flat_map_async( - self, selector: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]] - ) -> AsyncRx[_TResult]: + def flat_map_async(self, selector: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]]) -> AsyncRx[_TResult]: from .transform import flat_map_async return AsyncRx.create(pipe(self, flat_map_async(selector))) @@ -361,7 +334,9 @@ def skip(self, count: int) -> AsyncObservable[_TSource]: ) def skip_last(self, count: int) -> AsyncRx[_TSource]: - """Bypasses a specified number of elements at the end of an + """Skip the last items of the observable sequencerm . + + Bypasses a specified number of elements at the end of an observable sequence. This operator accumulates a queue with a length enough to store @@ -380,11 +355,12 @@ def skip_last(self, count: int) -> AsyncRx[_TSource]: return AsyncRx(pipe(self, skip_last(count))) def starfilter( - self: AsyncObservable[Any], predicate: Callable[..., bool] - ) -> AsyncRx[Any]: + self: AsyncObservable[tuple[Unpack[_V]]], predicate: Callable[[Unpack[_V]], bool] + ) -> AsyncRx[_TSource]: """Filter and spread the arguments to the predicate. Filters the elements of an observable sequence based on a predicate. + Returns: An observable sequence that contains elements from the input sequence that satisfy the condition. @@ -392,16 +368,13 @@ def starfilter( xs = pipe(self, starfilter(predicate)) return AsyncRx.create(xs) - def starmap( - self: AsyncRx[Tuple[Any, ...]], mapper: Callable[..., _TResult] - ) -> AsyncRx[_TResult]: + def starmap(self: AsyncRx[tuple[Unpack[_V]]], mapper: Callable[[Unpack[_V]], _TResult]) -> AsyncRx[_TResult]: """Map and spread the arguments to the mapper. Returns: An observable sequence whose elements are the result of invoking the mapper function on each element of the source. """ - return AsyncRx(pipe(self, starmap(mapper))) def take(self, count: int) -> AsyncObservable[_TSource]: @@ -411,7 +384,7 @@ def take(self, count: int) -> AsyncObservable[_TSource]: an observable sequence. Args: - count Number of elements to take. + count: Number of elements to take. Returns: An observable sequence that contains the specified number of @@ -458,9 +431,7 @@ def to_async_iterable(self) -> AsyncIterable[_TSource]: return to_async_iterable(self) - def with_latest_from( - self, other: AsyncObservable[_TOther] - ) -> AsyncRx[Tuple[_TSource, _TOther]]: + def with_latest_from(self, other: AsyncObservable[_TOther]) -> AsyncRx[tuple[_TSource, _TOther]]: from .combine import with_latest_from return AsyncRx.create(pipe(self, with_latest_from(other))) @@ -475,7 +446,7 @@ def as_chained(source: AsyncObservable[_TSource]) -> AsyncRx[_TSource]: def choose( - chooser: Callable[[_TSource], Option[_TResult]] + chooser: Callable[[_TSource], Option[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Choose. @@ -490,14 +461,13 @@ def choose( Returns: The filtered and/or transformed stream. """ - from .filtering import choose return choose(chooser) def choose_async( - chooser: Callable[[_TSource], Awaitable[Option[_TResult]]] + chooser: Callable[[_TSource], Awaitable[Option[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Choose async. @@ -512,16 +482,15 @@ def choose_async( Returns: The filtered and/or transformed stream. """ - from .filtering import choose_async return choose_async(chooser) -@curry_flipped(1) +@curry_flip(1) def combine_latest( source: AsyncObservable[_TSource], other: AsyncObservable[_TOther] -) -> AsyncObservable[Tuple[_TSource, _TOther]]: +) -> AsyncObservable[tuple[_TSource, _TOther]]: from .combine import combine_latest return pipe(source, combine_latest(other)) @@ -545,14 +514,13 @@ def debounce( A partially applied debounce function that takes the source observable to debounce. """ - from .timeshift import debounce return debounce(seconds) def catch( - handler: Callable[[Exception], AsyncObservable[_TSource]] + handler: Callable[[Exception], AsyncObservable[_TSource]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: from .transform import catch @@ -562,8 +530,11 @@ def catch( def concat( other: AsyncObservable[_TSource], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: - """Concatenates an observable sequence with another observable - sequence.""" + """Concatenate observables. + + Concatenates an observable sequence with another observable + sequence. + """ def _concat(source: AsyncObservable[_TSource]) -> AsyncObservable[_TSource]: from .combine import concat_seq @@ -577,17 +548,17 @@ def concat_seq( sources: Iterable[AsyncObservable[_TSource]], ) -> AsyncObservable[_TSource]: """Concatenates an iterable of observable sequences.""" - from .combine import concat_seq return concat_seq(sources) -def defer( - factory: Callable[[], AsyncObservable[_TSource]] -) -> AsyncObservable[_TSource]: - """Returns an observable sequence that invokes the specified factory - function whenever a new observer subscribes.""" +def defer(factory: Callable[[], AsyncObservable[_TSource]]) -> AsyncObservable[_TSource]: + """Defer observable. + + Returns an observable sequence that invokes the specified factory + function whenever a new observer subscribes. + """ from .create import defer return defer(factory) @@ -609,15 +580,13 @@ def distinct_until_changed( return distinct_until_changed(source) -def empty() -> "AsyncObservable[Any]": +def empty() -> AsyncObservable[Any]: from .create import empty return empty() -def filter( - predicate: Callable[[_TSource], bool] -) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: +def filter(predicate: Callable[[_TSource], bool]) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: """Filter stream. Filters the elements of an observable sequence based on a predicate. @@ -638,7 +607,7 @@ def filter( def filteri( - predicate: Callable[[_TSource, int], bool] + predicate: Callable[[_TSource, int], bool], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: """Filter with index. @@ -658,7 +627,7 @@ def filteri( def filter_async( - predicate: Callable[[_TSource], Awaitable[bool]] + predicate: Callable[[_TSource], Awaitable[bool]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: from .filtering import filter_async @@ -687,7 +656,7 @@ def from_iterable(iterable: Iterable[_TSource]) -> AsyncObservable[_TSource]: def flat_map( - mapper: Callable[[_TSource], AsyncObservable[_TResult]] + mapper: Callable[[_TSource], AsyncObservable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: from .transform import flat_map @@ -695,7 +664,7 @@ def flat_map( def flat_mapi( - mapper: Callable[[_TSource, int], AsyncObservable[_TResult]] + mapper: Callable[[_TSource, int], AsyncObservable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: from .transform import flat_mapi @@ -703,7 +672,7 @@ def flat_mapi( def flat_map_async( - mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]] + mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flap map async. @@ -711,10 +680,8 @@ def flat_map_async( an observable sequence and merges the resulting observable sequences back into one observable sequence. - Args: - mapperCallable ([type]): [description] - Awaitable ([type]): [description] + mapper: A transform function to apply to each element or an Returns: Stream[TSource, TResult]: [description] @@ -725,7 +692,7 @@ def flat_map_async( def flat_map_latest_async( - mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]] + mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flat map latest async. @@ -761,52 +728,60 @@ def from_async_iterable(iter: AsyncIterable[_TSource]) -> AsyncObservable[_TSour def interval(seconds: float, period: int) -> AsyncObservable[int]: - """Returns an observable sequence that triggers the increasing + """Observable interval. + + Returns an observable sequence that triggers the increasing sequence starting with 0 after the given msecs, and the after each - period.""" + period. + """ from .create import interval return interval(seconds, period) -def map( - fn: Callable[[_TSource], _TResult] -) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: +def map(fn: Callable[[_TSource], _TResult]) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: from .transform import map as _map return _map(fn) def map_async( - mapper: Callable[[_TSource], Awaitable[_TResult]] + mapper: Callable[[_TSource], Awaitable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Map asynchrnously. Returns an observable sequence whose elements are the result of invoking the async mapper function on each element of the - source.""" + source. + """ from .transform import map_async as map_async_ return map_async_(mapper) def mapi_async( - mapper: Callable[[_TSource, int], Awaitable[_TResult]] + mapper: Callable[[_TSource, int], Awaitable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: - """Returns an observable sequence whose elements are the result of + """Map indexed asynchronously. + + Returns an observable sequence whose elements are the result of invoking the async mapper function by incorporating the element's - index on each element of the source.""" + index on each element of the source. + """ from .transform import mapi_async return mapi_async(mapper) def mapi( - mapper: Callable[[_TSource, int], _TResult] + mapper: Callable[[_TSource, int], _TResult], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: - """Returns an observable sequence whose elements are the result of + """Map indexed. + + Returns an observable sequence whose elements are the result of invoking the mapper function and incorporating the element's index - on each element of the source.""" + on each element of the source. + """ from .transform import mapi return mapi(mapper) @@ -851,7 +826,7 @@ def merge_seq( ) -def never() -> "AsyncObservable[Any]": +def never() -> AsyncObservable[Any]: from .create import never return never() @@ -875,7 +850,7 @@ def scan( accumulator: Callable[[_TResult, _TSource], _TResult], initial: _TResult, ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: - """The scan operator + """The scan operator. This operator runs the accumulator for every value from the source with the current state. After every run, the new computed value is returned. @@ -951,7 +926,9 @@ def skip( def skip_last( count: int, ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: - """Bypasses a specified number of elements at the end of an + """Skip the last items of the observable sequence. + + Bypasses a specified number of elements at the end of an observable sequence. This operator accumulates a queue with a length enough to store @@ -972,35 +949,13 @@ def skip_last( return skip_last(count) -@overload -def starfilter( - predicate: Callable[[Tuple[_A, _B]], bool] -) -> Callable[[AsyncObservable[Tuple[_A, _B]]], AsyncObservable[Tuple[_A, _B]]]: - ... - - -@overload def starfilter( - predicate: Callable[[Tuple[_A, _B, _C]], bool] -) -> Callable[[AsyncObservable[Tuple[_A, _B, _C]]], AsyncObservable[Tuple[_A, _B, _C]]]: - ... - - -@overload -def starfilter( - predicate: Callable[[Tuple[_A, _B, _C, _D]], bool] -) -> Callable[ - [AsyncObservable[Tuple[_A, _B, _C, _D]]], AsyncObservable[Tuple[_A, _B, _C, _D]] -]: - ... - - -def starfilter( - predicate: Callable[..., bool] -) -> Callable[[AsyncObservable[Any]], AsyncObservable[Any]]: + predicate: Callable[[Unpack[_V]], bool], +) -> Callable[[AsyncObservable[tuple[Unpack[_V]]]], AsyncObservable[Any]]: """Filter and spread the arguments to the predicate. Filters the elements of an observable sequence based on a predicate. + Returns: An observable sequence that contains elements from the input sequence that satisfy the condition. @@ -1010,43 +965,20 @@ def starfilter( return starfilter(predicate) -@overload -def starmap( - mapper: Callable[[_A, _B], _TResult] -) -> Callable[[AsyncObservable[Tuple[_A, _B]]], AsyncObservable[_TResult]]: - ... - - -@overload -def starmap( - mapper: Callable[[_A, _B, _C], _TResult] -) -> Callable[[AsyncObservable[Tuple[_A, _B, _C]]], AsyncObservable[_TResult]]: - ... - - -@overload -def starmap( - mapper: Callable[[_A, _B, _C, _D], _TResult] -) -> Callable[[AsyncObservable[Tuple[_A, _B, _C, _D]]], AsyncObservable[_TResult]]: - ... - - def starmap( - mapper: Callable[..., _TResult] -) -> Callable[[AsyncObservable[Any]], AsyncObservable[_TResult]]: + mapper: Callable[[Unpack[_V]], _TResult], +) -> Callable[[AsyncObservable[tuple[Unpack[_V]]]], AsyncObservable[_TResult]]: """Map and spread the arguments to the mapper. Returns an observable sequence whose elements are the result of - invoking the mapper function on each element of the source.""" - + invoking the mapper function on each element of the source. + """ from .transform import starmap return starmap(mapper) -def switch_latest() -> Callable[ - [AsyncObservable[AsyncObservable[_TSource]]], AsyncObservable[_TSource] -]: +def switch_latest() -> Callable[[AsyncObservable[AsyncObservable[_TSource]]], AsyncObservable[_TSource]]: from .transform import switch_latest as switch_latest_ return switch_latest_ @@ -1061,7 +993,7 @@ def take( an observable sequence. Args: - count Number of elements to take. + count: Number of elements to take. Returns: An observable sequence that contains the specified number of @@ -1112,7 +1044,9 @@ def take_until( def timer(due_time: float) -> AsyncObservable[int]: - """Returns an observable sequence that triggers the value 0 + """Observable timer. + + Returns an observable sequence that triggers the value 0 after the given duetime in milliseconds. """ from .create import timer @@ -1126,11 +1060,11 @@ def to_async_iterable(source: AsyncObservable[_TSource]) -> AsyncIterable[_TSour return to_async_iterable(source) -@curry_flipped(1) +@curry_flip(1) def with_latest_from( source: AsyncObservable[_TSource], other: AsyncObservable[_TOther], -) -> AsyncObservable[Tuple[_TSource, _TOther]]: +) -> AsyncObservable[tuple[_TSource, _TOther]]: from .combine import with_latest_from return pipe( diff --git a/aioreactive/combine.py b/aioreactive/combine.py index 2b8ff6d..5c2bf9b 100644 --- a/aioreactive/combine.py +++ b/aioreactive/combine.py @@ -1,9 +1,10 @@ import dataclasses import logging +from collections.abc import Callable, Iterable from dataclasses import dataclass -from typing import Any, Callable, Generic, Iterable, NoReturn, Tuple, TypeVar, cast +from typing import Any, Generic, NoReturn, TypeVar -from expression import curry_flipped +from expression import curry_flip from expression.collections import Block, Map, block, map from expression.core import ( MailboxProcessor, @@ -12,23 +13,13 @@ Some, TailCall, TailCallResult, - match, pipe, tailrec_async, ) from expression.system import AsyncDisposable from .create import of_seq -from .msg import ( - CompletedMsg, - DisposeMsg, - InnerCompletedMsg, - InnerObservableMsg, - Key, - Msg, - OtherMsg, - SourceMsg, -) +from .msg import Key, Msg from .notification import Notification, OnError, OnNext from .observables import AsyncAnonymousObservable from .observers import ( @@ -38,6 +29,7 @@ ) from .types import AsyncObservable, AsyncObserver + _TSource = TypeVar("_TSource") _TOther = TypeVar("_TOther") @@ -82,30 +74,23 @@ async def athrow(error: Exception) -> None: await safe_obv.athrow(error) async def aclose() -> None: - inbox.post(InnerCompletedMsg(key)) + inbox.post(Msg(inner_completed=key)) return AsyncAnonymousObserver(asend, athrow, aclose) - async def update( - msg: Msg[_TSource], model: Model[_TSource] - ) -> Model[_TSource]: + async def update(msg: Msg[_TSource], model: Model[_TSource]) -> Model[_TSource]: # log.debug("update: %s, model: %s", msg, model) - with match(msg) as case: - for xs in case(InnerObservableMsg[_TSource]): - if ( - max_concurrent == 0 - or len(model.subscriptions) < max_concurrent - ): + match msg: + case Msg(tag="inner_observable", inner_observable=xs): + if max_concurrent == 0 or len(model.subscriptions) < max_concurrent: inner = await xs.subscribe_async(obv(model.key)) return model.replace( - subscriptions=model.subscriptions.add( - model.key, inner - ), + subscriptions=model.subscriptions.add(model.key, inner), key=Key(model.key + 1), ) lst = Block.singleton(xs) return model.replace(queue=model.queue.append(lst)) - for key in case(InnerCompletedMsg[Key]): + case Msg(tag="inner_completed", inner_completed=key): subscriptions = model.subscriptions.remove(key) if len(model.queue): xs = model.queue[0] @@ -122,18 +107,18 @@ async def update( if model.is_stopped: await safe_obv.aclose() return model.replace(subscriptions=map.empty) - while case(CompletedMsg): + case Msg(tag="completed"): if not model.subscriptions: log.debug("merge_inner: closing!") await safe_obv.aclose() return model.replace(is_stopped=True) - while case.default(): + case _: for dispose in model.subscriptions.values(): await dispose.dispose_async() - return initial_model.replace(is_stopped=True) + return initial_model.replace(is_stopped=True) async def message_loop(model: Model[_TSource]) -> None: while True: @@ -149,21 +134,21 @@ async def message_loop(model: Model[_TSource]) -> None: async def asend(xs: AsyncObservable[_TSource]) -> None: log.debug("merge_inner:asend(%s)", xs) - agent.post(InnerObservableMsg(inner_observable=xs)) + agent.post(Msg(inner_observable=xs)) async def athrow(error: Exception) -> None: await safe_obv.athrow(error) - agent.post(DisposeMsg) + agent.post(Msg(dispose=True)) async def aclose() -> None: - agent.post(CompletedMsg) + agent.post(Msg(completed=True)) obv = AsyncAnonymousObserver(asend, athrow, aclose) dispose = await auto_detach(source.subscribe_async(obv)) async def cancel() -> None: await dispose.dispose_async() - agent.post(DisposeMsg) + agent.post(Msg(dispose=True)) return AsyncDisposable.create(cancel) @@ -175,19 +160,21 @@ async def cancel() -> None: def concat_seq( sources: Iterable[AsyncObservable[_TSource]], ) -> AsyncObservable[_TSource]: - """Returns an observable sequence that contains the elements of each - given sequences, in sequential order.""" + """Concatenate sequences. + Returns an observable sequence that contains the elements of each + given sequences, in sequential order. + """ return pipe( of_seq(sources), merge_inner(1), ) -@curry_flipped(1) +@curry_flip(1) def combine_latest( source: AsyncObservable[_TSource], other: AsyncObservable[_TOther] -) -> AsyncObservable[Tuple[_TSource, _TOther]]: +) -> AsyncObservable[tuple[_TSource, _TOther]]: """Combine latest values. Merges the specified observable sequences into one observable @@ -195,6 +182,7 @@ def combine_latest( an observable sequence containing the combined results. Args: + source: The first observable to combine. other: The other observable to combine with. Returns: @@ -202,9 +190,7 @@ def combine_latest( returns the combined observable. """ - async def subscribe_async( - aobv: AsyncObserver[Tuple[_TSource, _TOther]] - ) -> AsyncDisposable: + async def subscribe_async(aobv: AsyncObserver[tuple[_TSource, _TOther]]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) async def worker(inbox: MailboxProcessor[Msg[_TSource]]) -> None: @@ -215,28 +201,26 @@ async def message_loop( cn = await inbox.receive() async def get_value(n: Notification[Any]) -> Option[Any]: - with match(n) as m: - for value in case(OnNext[_TSource]): + match n: + case OnNext(value=value): return Some(value) - - for err in case(OnError): + case OnError(exception=err): await safe_obv.athrow(err) - - while m.default(): + case _: await safe_obv.aclose() return Nothing - with match(cn) as case: - for value in case(SourceMsg[_TSource]): + match cn: + case Msg(tag="source", source=value): source_value = await get_value(value) - break - for value in case(OtherMsg[_TOther]): + case Msg(tag="other", other=value): other_value = await get_value(value) - break + case _: + raise ValueError(f"Unexpected message: {cn}") - def binder(s: _TSource) -> Option[Tuple[_TSource, _TOther]]: - def mapper(o: _TOther) -> Tuple[_TSource, _TOther]: + def binder(s: _TSource) -> Option[tuple[_TSource, _TOther]]: + def mapper(o: _TOther) -> tuple[_TSource, _TOther]: return (s, o) return other_value.map(mapper) @@ -245,19 +229,17 @@ def mapper(o: _TOther) -> Tuple[_TSource, _TOther]: for x in combined.to_list(): await safe_obv.asend(x) - return TailCall[Option[_TSource], Option[_TOther]]( - source_value, other_value - ) + return TailCall[Option[_TSource], Option[_TOther]](source_value, other_value) await message_loop(Nothing, Nothing) agent = MailboxProcessor.start(worker) async def obv_fn1(n: Notification[_TSource]) -> None: - pipe(SourceMsg(n), agent.post) + pipe(Msg(source=n), agent.post) async def obv_fn2(n: Notification[_TOther]) -> None: - pipe(OtherMsg(n), agent.post) + pipe(Msg(other=n), agent.post) obv1: AsyncObserver[_TSource] = AsyncNotificationObserver(obv_fn1) obv2: AsyncObserver[_TOther] = AsyncNotificationObserver(obv_fn2) @@ -269,10 +251,10 @@ async def obv_fn2(n: Notification[_TOther]) -> None: return AsyncAnonymousObservable(subscribe_async) -@curry_flipped(1) +@curry_flip(1) def with_latest_from( source: AsyncObservable[_TSource], other: AsyncObservable[_TOther] -) -> AsyncObservable[Tuple[_TSource, _TOther]]: +) -> AsyncObservable[tuple[_TSource, _TOther]]: """With latest from. Merges the specified observable sequences into one observable @@ -281,6 +263,7 @@ def with_latest_from( observable sequence. Args: + source: The first observable to combine. other (AsyncObservable[TOther]): The other observable to merge with. @@ -288,9 +271,7 @@ def with_latest_from( The merged observable. """ - async def subscribe_async( - aobv: AsyncObserver[Tuple[_TSource, _TOther]] - ) -> AsyncDisposable: + async def subscribe_async(aobv: AsyncObserver[tuple[_TSource, _TOther]]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) async def worker(inbox: MailboxProcessor[Msg[_TSource]]) -> None: @@ -301,27 +282,28 @@ async def message_loop( cn = await inbox.receive() async def get_value(n: Notification[Any]) -> Option[Any]: - with match(n) as case: - for value in case(OnNext[_TSource]): + match n: + case OnNext(value=value): return Some(value) - for err in case(OnError[_TSource]): + case OnError(exception=err): await safe_obv.athrow(err) - if case.default(): + case _: await safe_obv.aclose() return Nothing source_value = Nothing - if isinstance(cn, SourceMsg): - cn = cast(SourceMsg[_TSource], cn) - source_value = await get_value(cn.value) - else: - cn = cast(OtherMsg[_TOther], cn) - latest = await get_value(cn.value) - - def binder(s: _TSource) -> Option[Tuple[_TSource, _TOther]]: - def mapper(o: _TOther) -> Tuple[_TSource, _TOther]: + match cn: + case Msg(tag="source", source=value): + source_value = await get_value(value) + case Msg(tag="other", other=value): + latest = await get_value(value) + case _: + raise ValueError(f"Unexpected message: {cn}") + + def binder(s: _TSource) -> Option[tuple[_TSource, _TOther]]: + def mapper(o: _TOther) -> tuple[_TSource, _TOther]: return (s, o) return latest.map(mapper) @@ -337,10 +319,10 @@ def mapper(o: _TOther) -> Tuple[_TSource, _TOther]: agent = MailboxProcessor.start(worker) async def obv_fn1(n: Notification[_TSource]) -> None: - pipe(SourceMsg(n), agent.post) + pipe(Msg(source=n), agent.post) async def obv_fn2(n: Notification[_TOther]) -> None: - pipe(OtherMsg(n), agent.post) + pipe(Msg(other=n), agent.post) obv1: AsyncObserver[_TSource] = AsyncNotificationObserver(obv_fn1) obv2: AsyncObserver[_TOther] = AsyncNotificationObserver(obv_fn2) @@ -351,13 +333,11 @@ async def obv_fn2(n: Notification[_TOther]) -> None: return AsyncAnonymousObservable(subscribe_async) -@curry_flipped(1) +@curry_flip(1) def zip_seq( source: AsyncObservable[_TSource], sequence: Iterable[_TOther] -) -> AsyncObservable[Tuple[_TSource, _TOther]]: - async def subscribe_async( - aobv: AsyncObserver[Tuple[_TSource, _TOther]] - ) -> AsyncDisposable: +) -> AsyncObservable[tuple[_TSource, _TOther]]: + async def subscribe_async(aobv: AsyncObserver[tuple[_TSource, _TOther]]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) enumerator = iter(sequence) diff --git a/aioreactive/create.py b/aioreactive/create.py index 891739f..4658a6c 100644 --- a/aioreactive/create.py +++ b/aioreactive/create.py @@ -1,19 +1,11 @@ import asyncio import logging -from asyncio import Future -from typing import ( - Any, - AsyncIterable, - Awaitable, - Callable, - Iterable, - Optional, - Tuple, - TypeVar, -) +from asyncio import Task +from collections.abc import AsyncIterable, Awaitable, Callable, Iterable +from typing import Any, TypeVar -from expression.core import TailCallResult, aiotools, tailrec_async -from expression.core.fn import TailCall +from expression import TailCall, TailCallResult, tailrec_async +from expression.core import aiotools from expression.system import ( AsyncDisposable, CancellationToken, @@ -24,12 +16,13 @@ from .observers import AsyncObserver, safe_observer from .types import AsyncObservable + TSource = TypeVar("TSource") log = logging.getLogger(__name__) -def canceller() -> Tuple[AsyncDisposable, CancellationToken]: +def canceller() -> tuple[AsyncDisposable, CancellationToken]: cts = CancellationTokenSource() async def cancel() -> None: @@ -39,9 +32,7 @@ async def cancel() -> None: return AsyncDisposable.create(cancel), cts.token -def create( - subscribe: Callable[[AsyncObserver[TSource]], Awaitable[AsyncDisposable]] -) -> AsyncObservable[TSource]: +def create(subscribe: Callable[[AsyncObserver[TSource]], Awaitable[AsyncDisposable]]) -> AsyncObservable[TSource]: """Create an async observable. Creates an `AsyncObservable[TSource]` from the given subscribe @@ -50,11 +41,8 @@ def create( return AsyncAnonymousObservable(subscribe) -def of_async_worker( - worker: Callable[[AsyncObserver[Any], CancellationToken], Awaitable[None]] -) -> AsyncObservable[Any]: - """Create async observable from async worker function""" - +def of_async_worker(worker: Callable[[AsyncObserver[Any], CancellationToken], Awaitable[None]]) -> AsyncObservable[Any]: + """Create async observable from async worker function.""" log.debug("of_async_worker()") async def subscribe_async(aobv: AsyncObserver[Any]) -> AsyncDisposable: @@ -69,8 +57,11 @@ async def subscribe_async(aobv: AsyncObserver[Any]) -> AsyncDisposable: def of_async(workflow: Awaitable[TSource]) -> AsyncObservable[TSource]: - """Returns the async observable sequence whose single element is the - result of the given async workflow.""" + """Create from async workflow. + + Returns the async observable sequence whose single element is the + result of the given async workflow. + """ async def worker(obv: AsyncObserver[TSource], _: CancellationToken) -> None: try: @@ -86,9 +77,9 @@ async def worker(obv: AsyncObserver[TSource], _: CancellationToken) -> None: def of_async_iterable(iterable: AsyncIterable[TSource]) -> AsyncObservable[TSource]: - async def subscribe_async(observer: AsyncObserver[TSource]) -> AsyncDisposable: - task: Optional[Future[None]] = None + tasks: set[Task[Any]] = set() + async def subscribe_async(observer: AsyncObserver[TSource]) -> AsyncDisposable: async def cancel() -> None: if task: task.cancel() @@ -104,20 +95,26 @@ async def worker() -> None: return await observer.aclose() + tasks.remove(task) try: task = asyncio.create_task(worker()) except Exception as ex: log.debug("FromIterable:worker(), Exception: %s" % ex) await observer.athrow(ex) + else: + tasks.add(task) return sub return AsyncAnonymousObservable(subscribe_async) def single(value: TSource) -> AsyncObservable[TSource]: - """Returns an observable sequence containing the single specified - element.""" + """Create from a single item. + + Returns an observable sequence containing the single specified + element. + """ async def subscribe_async(aobv: AsyncObserver[TSource]) -> AsyncDisposable: safe_obv = safe_observer(aobv, AsyncDisposable.empty()) @@ -149,8 +146,11 @@ async def subscribe_async(_: AsyncObserver[Any]) -> AsyncDisposable: def fail(error: Exception) -> AsyncObservable[Any]: - """Returns the observable sequence that terminates exceptionally - with the specified exception.""" + """Create failing observable. + + Returns the observable sequence that terminates exceptionally + with the specified exception. + """ async def worker(obv: AsyncObserver[Any], _: CancellationToken) -> None: await obv.athrow(error) @@ -162,7 +162,8 @@ def of_seq(xs: Iterable[TSource]) -> AsyncObservable[TSource]: """Create async observable from sequence. Returns the async observable sequence whose elements are pulled from - the given enumerable sequence.""" + the given enumerable sequence. + """ async def worker(obv: AsyncObserver[TSource], token: CancellationToken) -> None: log.debug("of_seq:worker()") @@ -181,8 +182,11 @@ async def worker(obv: AsyncObserver[TSource], token: CancellationToken) -> None: def defer(factory: Callable[[], AsyncObservable[TSource]]) -> AsyncObservable[TSource]: - """Returns an observable sequence that invokes the specified factory - function whenever a new observer subscribes.""" + """Defer observable. + + Returns an observable sequence that invokes the specified factory + function whenever a new observer subscribes. + """ async def subscribe_async(aobv: AsyncObserver[TSource]) -> AsyncDisposable: try: @@ -196,17 +200,18 @@ async def subscribe_async(aobv: AsyncObserver[TSource]) -> AsyncDisposable: def interval(seconds: float, period: float) -> AsyncObservable[int]: - """Returns an observable sequence that triggers the increasing + """Create observable interval. + + Returns an observable sequence that triggers the increasing sequence starting with 0 after the given msecs, and the after each - period.""" + period. + """ async def subscribe_async(aobv: AsyncObserver[int]) -> AsyncDisposable: cancel, token = canceller() @tailrec_async - async def handler( - seconds: float, next: int - ) -> "TailCallResult[None, [float, int]]": + async def handler(seconds: float, next: int) -> "TailCallResult[None, [float, int]]": await asyncio.sleep(seconds) await aobv.asend(next) @@ -223,7 +228,9 @@ async def handler( def timer(due_time: float) -> AsyncObservable[int]: - """Returns an observable sequence that triggers the value 0 - after the given duetime in milliseconds.""" + """Create observable timer. + Returns an observable sequence that triggers the value 0 + after the given duetime in milliseconds. + """ return interval(due_time, 0) diff --git a/aioreactive/filtering.py b/aioreactive/filtering.py index 608d6e8..846f3ee 100644 --- a/aioreactive/filtering.py +++ b/aioreactive/filtering.py @@ -1,4 +1,5 @@ -from typing import Any, Awaitable, Callable, Iterable, List, NoReturn, Optional, TypeVar +from collections.abc import Awaitable, Callable, Iterable +from typing import Any, NoReturn, TypeVar from expression.collections import seq from expression.core import ( @@ -8,7 +9,6 @@ TailCallResult, aiotools, compose, - match, pipe, tailrec_async, ) @@ -25,16 +25,15 @@ from .transform import map, transform from .types import AsyncObservable, AsyncObserver + _TSource = TypeVar("_TSource") _TResult = TypeVar("_TResult") def choose_async( - chooser: Callable[[_TSource], Awaitable[Option[_TResult]]] + chooser: Callable[[_TSource], Awaitable[Option[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: - async def handler( - next: Callable[[_TResult], Awaitable[None]], xs: _TSource - ) -> None: + async def handler(next: Callable[[_TResult], Awaitable[None]], xs: _TSource) -> None: result = await chooser(xs) for x in result.to_list(): await next(x) @@ -43,11 +42,9 @@ async def handler( def choose( - chooser: Callable[[_TSource], Option[_TResult]] + chooser: Callable[[_TSource], Option[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: - def handler( - next: Callable[[_TResult], Awaitable[None]], xs: _TSource - ) -> Awaitable[None]: + def handler(next: Callable[[_TResult], Awaitable[None]], xs: _TSource) -> Awaitable[None]: for x in chooser(xs).to_list(): return next(x) return aiotools.empty() @@ -56,7 +53,7 @@ def handler( def filter_async( - predicate: Callable[[_TSource], Awaitable[bool]] + predicate: Callable[[_TSource], Awaitable[bool]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: """Filter async. @@ -78,12 +75,8 @@ async def handler(next: Callable[[_TSource], Awaitable[None]], x: _TSource) -> N return transform(handler) -def filter( - predicate: Callable[[_TSource], bool] -) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: - def handler( - next: Callable[[_TSource], Awaitable[None]], x: _TSource - ) -> Awaitable[None]: +def filter(predicate: Callable[[_TSource], bool]) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: + def handler(next: Callable[[_TSource], Awaitable[None]], x: _TSource) -> Awaitable[None]: if predicate(x): return next(x) return aiotools.empty() @@ -91,20 +84,17 @@ def handler( return transform(handler) -def starfilter( - predicate: Callable[..., bool] -) -> Callable[[AsyncObservable[Any]], AsyncObservable[Any]]: +def starfilter(predicate: Callable[..., bool]) -> Callable[[AsyncObservable[Any]], AsyncObservable[Any]]: """Filter and spread the arguments to the predicate. Filters the elements of an observable sequence based on a predicate. + Returns: An observable sequence that contains elements from the input sequence that satisfy the condition. """ - def handler( - next: Callable[[Iterable[Any]], Awaitable[None]], args: Iterable[Any] - ) -> Awaitable[None]: + def handler(next: Callable[[Iterable[Any]], Awaitable[None]], args: Iterable[Any]) -> Awaitable[None]: if predicate(*args): return next(args) return aiotools.empty() @@ -113,7 +103,7 @@ def handler( def filteri( - predicate: Callable[[_TSource, int], bool] + predicate: Callable[[_TSource, int], bool], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: ret = compose( zip_seq(seq.infinite), @@ -145,34 +135,29 @@ async def worker(inbox: MailboxProcessor[Notification[_TSource]]) -> None: @tailrec_async async def message_loop( latest: Notification[_TSource], - ) -> "TailCallResult[NoReturn, [Notification[_TSource]]]": + ) -> TailCallResult[NoReturn, [Notification[_TSource]]]: n = await inbox.receive() async def get_latest() -> Notification[_TSource]: - with match(n) as case: - for x in case(OnNext[_TSource]): - if n == latest: - break - try: - await safe_obv.asend(x) - except Exception as ex: - await safe_obv.athrow(ex) - break - for err in case(OnError[_TSource]): + match n: + case OnNext(value=x): + if n != latest: + try: + await safe_obv.asend(x) + except Exception as ex: + await safe_obv.athrow(ex) + case OnError(exception=err): await safe_obv.athrow(err) - break - while case(OnCompleted): + + case _: await safe_obv.aclose() - break return n latest = await get_latest() return TailCall[Notification[_TSource]](latest) - await message_loop( - OnCompleted - ) # Use as sentinel value as it will not match any OnNext value + await message_loop(OnCompleted()) agent = MailboxProcessor.start(worker) @@ -188,7 +173,7 @@ async def notification(n: Notification[_TSource]) -> None: def skip( count: int, ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: - """[summary] + """Skip items from observable sequence. Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. @@ -228,10 +213,10 @@ def _skip_last(source: AsyncObservable[_TSource]) -> AsyncObservable[_TSource]: async def subscribe_async(observer: AsyncObserver[_TSource]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(observer) - q: List[_TSource] = [] + q: list[_TSource] = [] async def asend(value: _TSource) -> None: - front = None + front: _TSource | None = None q.append(value) if len(q) > count: front = q.pop(0) @@ -294,7 +279,7 @@ def take_last( def _take_last(source: AsyncObservable[_TSource]) -> AsyncObservable[_TSource]: async def subscribe_async(aobv: AsyncObserver[_TSource]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) - queue: List[_TSource] = [] + queue: list[_TSource] = [] async def asend(value: _TSource) -> None: queue.append(value) @@ -348,7 +333,7 @@ async def asend(value: _TSource) -> None: def slice( - start: Optional[int] = None, stop: Optional[int] = None, step: int = 1 + start: int | None = None, stop: int | None = None, step: int = 1 ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: """Slices the given source stream. @@ -395,14 +380,16 @@ def _slice(source: AsyncObservable[_TSource]) -> AsyncObservable[_TSource]: else: source = pipe(source, skip_last(abs(stop))) - if step is not None: - if step > 1: - mapper: Callable[[Any, int], bool] = lambda _, i: i % step == 0 - xs = pipe(source, filteri(mapper)) - source = xs - elif step < 0: - # Reversing streams is not supported - raise TypeError("Negative step not supported.") + if step > 1: + + def mapper(_: Any, i: int) -> bool: + return i % step == 0 + + xs = pipe(source, filteri(mapper)) + source = xs + elif step < 0: + # Reversing streams is not supported + raise TypeError("Negative step not supported.") return source diff --git a/aioreactive/iterable/__init__.py b/aioreactive/iterable/__init__.py index e69de29..0185c9b 100644 --- a/aioreactive/iterable/__init__.py +++ b/aioreactive/iterable/__init__.py @@ -0,0 +1,5 @@ +"""Iterable module. + +This module contains functions to create async observables from +iterables. +""" diff --git a/aioreactive/iterable/to_async_observable.py b/aioreactive/iterable/to_async_observable.py index 9216c72..6a2cd11 100644 --- a/aioreactive/iterable/to_async_observable.py +++ b/aioreactive/iterable/to_async_observable.py @@ -1,16 +1,18 @@ -from typing import AsyncIterable, TypeVar +from collections.abc import AsyncIterable +from typing import TypeVar from aioreactive import AsyncObservable, AsyncRx + TSource = TypeVar("TSource") def to_async_observable(source: AsyncIterable[TSource]) -> AsyncObservable[TSource]: """Convert to async observable. - Keyword arguments: - source -- Async iterable to convert to async observable. - - Returns async observable""" + Keyword Arguments: + source: Async iterable to convert to async observable. + Returns async observable + """ return AsyncRx.from_async_iterable(source) diff --git a/aioreactive/leave.py b/aioreactive/leave.py index 1a34bcd..58ca389 100644 --- a/aioreactive/leave.py +++ b/aioreactive/leave.py @@ -1,5 +1,6 @@ import asyncio -from typing import AsyncIterable, Optional, TypeVar +from collections.abc import AsyncIterable +from typing import Any, TypeVar import reactivex from expression.system.disposable import AsyncDisposable @@ -7,11 +8,8 @@ from reactivex.abc import DisposableBase, ObserverBase, SchedulerBase from reactivex.disposable import Disposable -from .observables import ( - AsyncAnonymousObserver, - AsyncIterableObservable, - AsyncObservable, -) +from .observables import AsyncAnonymousObserver, AsyncIterableObservable, AsyncObservable + _TSource = TypeVar("_TSource") @@ -20,6 +18,7 @@ def to_async_iterable(source: AsyncObservable[_TSource]) -> AsyncIterable[_TSour """Convert async observable to async iterable. Args: + source: The source observable. count: The number of elements to skip before returning the remaining values. @@ -27,15 +26,15 @@ def to_async_iterable(source: AsyncObservable[_TSource]) -> AsyncIterable[_TSour A source stream that contains the values that occur after the specified index in the input source stream. """ - return AsyncIterableObservable(source) def to_observable(source: AsyncObservable[_TSource]) -> Observable[_TSource]: - def subscribe( - obv: ObserverBase[_TSource], scheduler: Optional[SchedulerBase] = None - ) -> DisposableBase: - subscription: Optional[AsyncDisposable] = None + """Convert async observable to observable.""" + tasks: set[asyncio.Task[Any]] = set() + + def subscribe(obv: ObserverBase[_TSource], scheduler: SchedulerBase | None = None) -> DisposableBase: + subscription: AsyncDisposable | None = None async def start() -> None: nonlocal subscription @@ -49,16 +48,23 @@ async def athrow(error: Exception) -> None: async def aclose() -> None: obv.on_completed() - subscription = await source.subscribe_async( - AsyncAnonymousObserver(asend, athrow, aclose) - ) + subscription = await source.subscribe_async(AsyncAnonymousObserver(asend, athrow, aclose)) + tasks.remove(task) - asyncio.create_task(start()) + task = asyncio.create_task(start()) + tasks.add(task) + task.add_done_callback(lambda _: tasks.remove(task)) def dispose() -> None: if subscription: - asyncio.create_task(subscription.dispose_async()) + task = asyncio.create_task(subscription.dispose_async()) + tasks.add(task) return Disposable(dispose) return reactivex.create(subscribe) + + +# def to_iterable(source: AsyncObservable[_TSource]) -> Iterable[_TSource]: +# """Convert async observable to iterable.""" +# return to_observable(source).to_iterable() diff --git a/aioreactive/msg.py b/aioreactive/msg.py index 0575af7..dec4858 100644 --- a/aioreactive/msg.py +++ b/aioreactive/msg.py @@ -1,148 +1,39 @@ -"""Internal messages used by mailbox processors. Do not import or use. -""" -from abc import ABC -from dataclasses import dataclass -from typing import Any, Iterable, NewType, TypeVar, get_origin +"""Internal messages used by mailbox processors. Do not import or use.""" +from typing import Generic, Literal, NewType, TypeVar -from expression.core import SupportsMatch +from expression import case, tag, tagged_union from expression.system import AsyncDisposable from .notification import Notification from .types import AsyncObservable -TSource = TypeVar("TSource") -TOther = TypeVar("TOther") -Key = NewType("Key", int) - - -class Msg(SupportsMatch[TSource], ABC): - """Message base class.""" - - -@dataclass -class SourceMsg(Msg[Notification[TSource]], SupportsMatch[TSource]): - value: Notification[TSource] - - def __match__(self, pattern: Any) -> Iterable[Notification[TSource]]: - origin: Any = get_origin(pattern) - try: - if isinstance(self, origin or pattern): - return [self.value] - except TypeError: - pass - return [] - - -@dataclass -class OtherMsg(Msg[Notification[TOther]], SupportsMatch[TOther]): - value: Notification[TOther] - - def __match__(self, pattern: Any) -> Iterable[Notification[TOther]]: - origin: Any = get_origin(pattern) - try: - if isinstance(self, origin or pattern): - return [self.value] - except TypeError: - pass - return [] - - -@dataclass -class DisposableMsg(Msg[AsyncDisposable], SupportsMatch[AsyncDisposable]): - """Message containing a diposable.""" - - disposable: AsyncDisposable - - def __match__(self, pattern: Any) -> Iterable[AsyncDisposable]: - try: - if isinstance(self, pattern): - return [self.disposable] - except TypeError: - pass - return [] - - -@dataclass -class InnerObservableMsg( - Msg[AsyncObservable[TSource]], SupportsMatch[AsyncObservable[TSource]] -): - """Message containing an inner observable.""" - - inner_observable: AsyncObservable[TSource] +_TSource = TypeVar("_TSource") - def __match__(self, pattern: Any) -> Iterable[AsyncObservable[TSource]]: - origin: Any = get_origin(pattern) - try: - if isinstance(self, origin or pattern): - return [self.inner_observable] - except TypeError: - pass - return [] - - -@dataclass -class InnerCompletedMsg(Msg[TSource]): - """Message notifying that the inner observable completed.""" - - key: Key - - def __match__(self, pattern: Any) -> Iterable[Any]: - origin: Any = get_origin(pattern) - try: - if isinstance(self, origin or pattern): - return [self.key] - except TypeError: - pass - return [] - - -class CompletedMsg_(Msg[Any]): - """Message notifying that the observable sequence completed.""" - - def __match__(self, pattern: Any) -> Iterable[bool]: - if self is pattern: - return [True] - - origin: Any = get_origin(pattern) - try: - if isinstance(self, origin or pattern): - return [True] - except TypeError: - pass - - return [] - - -CompletedMsg = CompletedMsg_() # Singleton - - -class DisposeMsg_(Msg[None]): - """Message notifying that the operator got disposed.""" - - def __match__(self, pattern: Any) -> Iterable[Any]: - - if self is pattern: - return [True] +Key = NewType("Key", int) - origin: Any = get_origin(pattern) - try: - if isinstance(self, origin or pattern): - return [True] - except TypeError: - pass - return [] +@tagged_union(frozen=True) +class Msg(Generic[_TSource]): + """Message tagged union.""" + tag: Literal[ + "source", + "other", + "dispose", + "disposable", + "inner_observable", + "inner_completed", + "completed", + ] = tag() -DisposeMsg = DisposeMsg_() # Singleton + source: Notification[_TSource] = case() + other: Notification[_TSource] = case() + dispose: Literal[True] = case() + disposable: AsyncDisposable = case() + inner_observable: AsyncObservable[_TSource] = case() + inner_completed: Key = case() + completed: Literal[True] = case() -__all__ = [ - "Msg", - "DisposeMsg", - "CompletedMsg", - "InnerCompletedMsg", - "InnerObservableMsg", - "DisposableMsg", -] +__all__ = ["Msg"] diff --git a/aioreactive/notification.py b/aioreactive/notification.py index ea62fb5..16d3f04 100644 --- a/aioreactive/notification.py +++ b/aioreactive/notification.py @@ -1,11 +1,11 @@ from abc import ABC, abstractmethod +from collections.abc import Awaitable, Callable, Iterable from enum import Enum -from typing import Any, Awaitable, Callable, Generic, Iterable, TypeVar, get_origin - -from expression.core import SupportsMatch +from typing import Any, Generic, TypeVar, get_origin from .types import AsyncObserver + _TSource = TypeVar("_TSource") @@ -38,9 +38,11 @@ def __repr__(self) -> str: return str(self) -class OnNext(Notification[_TSource], SupportsMatch[_TSource]): +class OnNext(Notification[_TSource]): """Represents an OnNext notification to an observer.""" + __match_args__ = ("value",) + def __init__(self, value: _TSource) -> None: """Constructs a notification of a new value.""" super().__init__(MsgKind.ON_NEXT) @@ -75,9 +77,11 @@ def __str__(self) -> str: return f"OnNext({self.value})" -class OnError(Notification[_TSource], SupportsMatch[Exception]): +class OnError(Notification[_TSource]): """Represents an OnError notification to an observer.""" + __match_args__ = ("exception",) + def __init__(self, exception: Exception) -> None: """Constructs a notification of an exception.""" super().__init__(MsgKind.ON_ERROR) @@ -112,7 +116,7 @@ def __str__(self) -> str: return f"OnError({self.exception})" -class _OnCompleted(Notification[_TSource], SupportsMatch[bool]): +class OnCompleted(Notification[_TSource]): """Represents an OnCompleted notification to an observer. Note: Do not use. Use the singleton `OnCompleted` instance instead. @@ -120,7 +124,6 @@ class _OnCompleted(Notification[_TSource], SupportsMatch[bool]): def __init__(self) -> None: """Constructs a notification of the end of a sequence.""" - super().__init__(MsgKind.ON_COMPLETED) async def accept( @@ -147,13 +150,9 @@ def __match__(self, pattern: Any) -> Iterable[bool]: return [] def __eq__(self, other: Any) -> bool: - if isinstance(other, _OnCompleted): + if isinstance(other, OnCompleted): return True return False def __str__(self) -> str: return "OnCompleted" - - -OnCompleted: _OnCompleted[Any] = _OnCompleted() -"""OnCompleted singleton instance.""" diff --git a/aioreactive/observables.py b/aioreactive/observables.py index 5da9f85..c6a75d5 100644 --- a/aioreactive/observables.py +++ b/aioreactive/observables.py @@ -1,47 +1,34 @@ import logging -from typing import ( - AsyncIterable, - AsyncIterator, - Awaitable, - Callable, - Optional, - TypeVar, - Union, -) +from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable +from typing import TypeVar from expression.system import AsyncDisposable from .observers import AsyncAnonymousObserver, AsyncIteratorObserver from .types import AsyncObservable, AsyncObserver, CloseAsync, SendAsync, ThrowAsync + _TSource = TypeVar("_TSource") log = logging.getLogger(__name__) class AsyncAnonymousObservable(AsyncObservable[_TSource]): - """An anonymous AsyncObservable. Uses a custom subscribe method. """ - def __init__( - self, subscribe: Callable[[AsyncObserver[_TSource]], Awaitable[AsyncDisposable]] - ) -> None: + def __init__(self, subscribe: Callable[[AsyncObserver[_TSource]], Awaitable[AsyncDisposable]]) -> None: self._subscribe = subscribe async def subscribe_async( self, - send: Optional[Union[SendAsync[_TSource], AsyncObserver[_TSource]]] = None, - throw: Optional[ThrowAsync] = None, - close: Optional[CloseAsync] = None, + send: SendAsync[_TSource] | AsyncObserver[_TSource] | None = None, + throw: ThrowAsync | None = None, + close: CloseAsync | None = None, ) -> AsyncDisposable: - observer = ( - send - if isinstance(send, AsyncObserver) - else AsyncAnonymousObserver(send, throw, close) - ) + observer = send if isinstance(send, AsyncObserver) else AsyncAnonymousObserver(send, throw, close) log.debug("AsyncAnonymousObservable:subscribe_async(%s)", self._subscribe) return await self._subscribe(observer) @@ -52,15 +39,11 @@ def __init__(self, source: AsyncObservable[_TSource]) -> None: async def subscribe_async( self, - send: Optional[Union[SendAsync[_TSource], AsyncObserver[_TSource]]] = None, - throw: Optional[ThrowAsync] = None, - close: Optional[CloseAsync] = None, + send: SendAsync[_TSource] | AsyncObserver[_TSource] | None = None, + throw: ThrowAsync | None = None, + close: CloseAsync | None = None, ) -> AsyncDisposable: - observer = ( - send - if isinstance(send, AsyncObserver) - else AsyncAnonymousObserver(send, throw, close) - ) + observer = send if isinstance(send, AsyncObserver) else AsyncAnonymousObserver(send, throw, close) return await self._source.subscribe_async(observer) def __aiter__(self) -> AsyncIterator[_TSource]: @@ -73,7 +56,6 @@ def __aiter__(self) -> AsyncIterator[_TSource]: Returns: An async iterator. """ - return AsyncIteratorObserver(self) diff --git a/aioreactive/observers.py b/aioreactive/observers.py index 3af9dd8..d62ee76 100644 --- a/aioreactive/observers.py +++ b/aioreactive/observers.py @@ -1,35 +1,23 @@ import logging from asyncio import Future, iscoroutinefunction -from typing import ( - Any, - AsyncIterable, - AsyncIterator, - Awaitable, - Callable, - Coroutine, - List, - Optional, - Tuple, - TypeVar, - cast, -) +from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable, Coroutine +from typing import Any, TypeVar, cast from expression.core import MailboxProcessor, TailCall, tailrec_async from expression.system import AsyncDisposable, CancellationTokenSource, Disposable -from .msg import DisposableMsg, DisposeMsg, Msg +from .msg import Msg from .notification import MsgKind, Notification, OnCompleted, OnError, OnNext from .types import AsyncObservable, AsyncObserver from .utils import anoop + log = logging.getLogger(__name__) _TSource = TypeVar("_TSource") -class AsyncIteratorObserver( - AsyncObserver[_TSource], AsyncIterable[_TSource], AsyncDisposable -): +class AsyncIteratorObserver(AsyncObserver[_TSource], AsyncIterable[_TSource], AsyncDisposable): """An async observer that might be iterated asynchronously.""" def __init__(self, source: AsyncObservable[_TSource]) -> None: @@ -38,8 +26,8 @@ def __init__(self, source: AsyncObservable[_TSource]) -> None: self._push: Future[_TSource] = Future() self._pull: Future[bool] = Future() - self._awaiters: List[Future[bool]] = [] - self._subscription: Optional[AsyncDisposable] = None + self._awaiters: list[Future[bool]] = [] + self._subscription: AsyncDisposable | None = None self._source = source self._busy = False @@ -111,13 +99,14 @@ class AsyncAnonymousObserver(AsyncObserver[_TSource]): Creates as sink where the implementation is provided by three optional and anonymous functions, asend, athrow and aclose. Used for - listening to a source.""" + listening to a source. + """ def __init__( self, - asend: Optional[Callable[[_TSource], Awaitable[None]]] = None, - athrow: Optional[Callable[[Exception], Awaitable[None]]] = None, - aclose: Optional[Callable[[], Awaitable[None]]] = None, + asend: Callable[[_TSource], Awaitable[None]] | None = None, + athrow: Callable[[Exception], Awaitable[None]] | None = None, + aclose: Callable[[], Awaitable[None]] | None = None, ) -> None: super().__init__() self._asend = asend or anoop @@ -140,7 +129,7 @@ async def aclose(self) -> None: class AsyncNotificationObserver(AsyncObserver[_TSource]): - """Observer created from an async notification processing function""" + """Observer created from an async notification processing function.""" def __init__(self, fn: Callable[[Notification[_TSource]], Awaitable[None]]) -> None: self._fn = fn @@ -152,17 +141,17 @@ async def athrow(self, error: Exception) -> None: await self._fn(OnError(error)) async def aclose(self) -> None: - await self._fn(OnCompleted) + await self._fn(OnCompleted()) def noop() -> AsyncObserver[Any]: return AsyncAnonymousObserver(anoop, anoop, anoop) -def safe_observer( - obv: AsyncObserver[_TSource], disposable: AsyncDisposable -) -> AsyncObserver[_TSource]: - """Safe observer that wraps the given observer. Makes sure that +def safe_observer(obv: AsyncObserver[_TSource], disposable: AsyncDisposable) -> AsyncObserver[_TSource]: + """Safe observer that wraps the given observer. + + Makes sure that invocations are serialized and that the Rx grammar is not violated: `(OnNext*(OnError|OnCompleted)?)` @@ -205,18 +194,16 @@ async def athrow(ex: Exception) -> None: agent.post(OnError(ex)) async def aclose() -> None: - agent.post(OnCompleted) + agent.post(OnCompleted()) return AsyncAnonymousObserver(asend, athrow, aclose) def auto_detach_observer( obv: AsyncObserver[_TSource], -) -> Tuple[ +) -> tuple[ AsyncObserver[_TSource], - Callable[ - [Coroutine[None, None, AsyncDisposable]], Coroutine[None, None, AsyncDisposable] - ], + Callable[[Coroutine[None, None, AsyncDisposable]], Coroutine[None, None, AsyncDisposable]], ]: cts = CancellationTokenSource() token = cts.token @@ -224,19 +211,20 @@ def auto_detach_observer( async def worker(inbox: MailboxProcessor[Msg[_TSource]]) -> None: @tailrec_async async def message_loop( - disposables: List[AsyncDisposable], + disposables: list[AsyncDisposable], ) -> Any: if token.is_cancellation_requested: return cmd = await inbox.receive() - if isinstance(cmd, DisposableMsg): - disposables.append(cmd.disposable) - else: - for disp in disposables: - await disp.dispose_async() - return - return TailCall[List[AsyncDisposable]](disposables) + match cmd: + case Msg(tag="disposable", disposable=disposable): + disposables.append(disposable) + case _: + for disp in disposables: + await disp.dispose_async() + return + return TailCall[list[AsyncDisposable]](disposables) await message_loop([]) @@ -244,17 +232,15 @@ async def message_loop( async def cancel() -> None: cts.cancel() - agent.post(DisposeMsg) + agent.post(Msg(dispose=True)) canceller = AsyncDisposable.create(cancel) safe_obv = safe_observer(obv, canceller) # Auto-detaches (disposes) the disposable when the observer completes with success or error. - async def auto_detach( - async_disposable: Coroutine[None, None, AsyncDisposable] - ) -> AsyncDisposable: + async def auto_detach(async_disposable: Coroutine[None, None, AsyncDisposable]) -> AsyncDisposable: disposable = await async_disposable - agent.post(DisposableMsg(disposable)) + agent.post(Msg(disposable=disposable)) return disposable return safe_obv, auto_detach @@ -265,7 +251,8 @@ class AsyncAwaitableObserver(Future[_TSource], AsyncObserver[_TSource], Disposab Both a future and async observer. The future resolves with the last value before the observer is closed. A close without any values sent - is the same as cancelling the future.""" + is the same as cancelling the future. + """ def __init__( self, diff --git a/aioreactive/subject.py b/aioreactive/subject.py index 560ee05..d3d7eb1 100644 --- a/aioreactive/subject.py +++ b/aioreactive/subject.py @@ -1,21 +1,19 @@ import logging from asyncio import Future -from typing import List, Optional, TypeVar, Union +from typing import TypeVar from expression.system import AsyncDisposable, ObjectDisposedException from .observables import AsyncAnonymousObserver, AsyncObservable from .types import AsyncObserver, CloseAsync, SendAsync, ThrowAsync + log = logging.getLogger(__name__) _TSource = TypeVar("_TSource") -class AsyncSingleSubject( - AsyncObserver[_TSource], AsyncObservable[_TSource], AsyncDisposable -): - +class AsyncSingleSubject(AsyncObserver[_TSource], AsyncObservable[_TSource], AsyncDisposable): """An stream with a single sink. Both an async observable and async observer. @@ -28,7 +26,7 @@ def __init__(self) -> None: super().__init__() self._wait: Future[bool] = Future() - self._observer: Optional[AsyncObserver[_TSource]] = None + self._observer: AsyncObserver[_TSource] | None = None self._is_disposed = False self._is_stopped = False @@ -82,18 +80,13 @@ async def dispose_async(self) -> None: async def subscribe_async( self, - send: Optional[Union[SendAsync[_TSource], AsyncObserver[_TSource]]] = None, - throw: Optional[ThrowAsync] = None, - close: Optional[CloseAsync] = None, + send: SendAsync[_TSource] | AsyncObserver[_TSource] | None = None, + throw: ThrowAsync | None = None, + close: CloseAsync | None = None, ) -> AsyncDisposable: """Start streaming.""" - self.check_disposed() - self._observer = ( - send - if isinstance(send, AsyncObserver) - else AsyncAnonymousObserver(send, throw, close) - ) + self._observer = send if isinstance(send, AsyncObserver) else AsyncAnonymousObserver(send, throw, close) if not self._wait.done(): self._wait.set_result(True) @@ -101,9 +94,7 @@ async def subscribe_async( return AsyncDisposable.create(self.dispose_async) -class AsyncMultiSubject( - AsyncObserver[_TSource], AsyncObservable[_TSource], AsyncDisposable -): +class AsyncMultiSubject(AsyncObserver[_TSource], AsyncObservable[_TSource], AsyncDisposable): """An stream with a multiple observers. Both an async observable and async observer. @@ -114,7 +105,7 @@ class AsyncMultiSubject( def __init__(self) -> None: super().__init__() - self._observers: List[AsyncObserver[_TSource]] = [] + self._observers: list[AsyncObserver[_TSource]] = [] self._is_disposed = False self._is_stopped = False @@ -153,20 +144,15 @@ async def aclose(self) -> None: async def subscribe_async( self, - send: Optional[Union[SendAsync[_TSource], AsyncObserver[_TSource]]] = None, - throw: Optional[ThrowAsync] = None, - close: Optional[CloseAsync] = None, + send: SendAsync[_TSource] | AsyncObserver[_TSource] | None = None, + throw: ThrowAsync | None = None, + close: CloseAsync | None = None, ) -> AsyncDisposable: """Subscribe.""" - log.debug("AsyncMultiStream:subscribe_async()") self.check_disposed() - observer = ( - send - if isinstance(send, AsyncObserver) - else AsyncAnonymousObserver(send, throw, close) - ) + observer = send if isinstance(send, AsyncObserver) else AsyncAnonymousObserver(send, throw, close) self._observers.append(observer) async def dispose() -> None: diff --git a/aioreactive/subscription.py b/aioreactive/subscription.py index 8c1a86b..904a086 100644 --- a/aioreactive/subscription.py +++ b/aioreactive/subscription.py @@ -1,12 +1,14 @@ import asyncio import logging -from typing import Awaitable, Callable, Optional, TypeVar +from collections.abc import Awaitable, Callable +from typing import TypeVar from expression.system.disposable import AsyncDisposable from .observers import AsyncAwaitableObserver from .types import AsyncObservable, AsyncObserver + log = logging.getLogger(__name__) _TSource = TypeVar("_TSource") @@ -14,7 +16,7 @@ async def run( source: AsyncObservable[_TSource], - observer: Optional[AsyncAwaitableObserver[_TSource]] = None, + observer: AsyncAwaitableObserver[_TSource] | None = None, timeout: int = 2, ) -> _TSource: """Run the source with the given observer. @@ -23,7 +25,9 @@ async def run( closes and returns the final value received. Args: - timeout -- Seconds before timing out in case source never closes. + source: The source observable. + observer: The observer to subscribe to the source. + timeout: Seconds before timing out in case source never closes. Returns: The last event sent through the stream. If any values have been @@ -32,7 +36,6 @@ async def run( `StopAsyncIteration`. For any other errors it will throw the exception. """ - # For run we need a noopobserver if no observer is specified to avoid # blocking the last single stream in the chain. obv: AsyncAwaitableObserver[_TSource] = observer or AsyncAwaitableObserver() diff --git a/aioreactive/testing/__init__.py b/aioreactive/testing/__init__.py index 938ff43..abda9d2 100644 --- a/aioreactive/testing/__init__.py +++ b/aioreactive/testing/__init__.py @@ -7,6 +7,7 @@ from .utils import ca from .virtual_events import VirtualTimeEventLoop + __all__ = [ "ca", "VirtualTimeEventLoop", diff --git a/aioreactive/testing/observer.py b/aioreactive/testing/observer.py index dc3eed0..43ac07a 100644 --- a/aioreactive/testing/observer.py +++ b/aioreactive/testing/observer.py @@ -1,10 +1,12 @@ import logging -from typing import Awaitable, Callable, List, Tuple, TypeVar +from collections.abc import Awaitable, Callable +from typing import TypeVar from aioreactive import AsyncAwaitableObserver from aioreactive.notification import Notification, OnCompleted, OnError, OnNext from aioreactive.utils import anoop + log = logging.getLogger(__name__) @@ -33,7 +35,7 @@ def __init__( ) -> None: super().__init__(asend, athrow, aclose) - self._values: List[Tuple[float, Notification[TSource]]] = [] + self._values: list[tuple[float, Notification[TSource]]] = [] self._send = asend self._throw = athrow @@ -62,11 +64,11 @@ async def aclose(self) -> None: log.debug("AsyncAnonymousObserver:aclose()") time = self.time() - self._values.append((time, OnCompleted)) + self._values.append((time, OnCompleted())) await self._close() await super().aclose() @property - def values(self) -> List[Tuple[float, Notification[TSource]]]: + def values(self) -> list[tuple[float, Notification[TSource]]]: return self._values diff --git a/aioreactive/testing/subject.py b/aioreactive/testing/subject.py index 49a53e8..b5d8d3b 100644 --- a/aioreactive/testing/subject.py +++ b/aioreactive/testing/subject.py @@ -1,9 +1,11 @@ import asyncio -from typing import TypeVar +from asyncio import Task +from typing import Any, TypeVar from aioreactive import AsyncObserver from aioreactive.subject import AsyncSingleSubject, AsyncSubject + TSource = TypeVar("TSource") @@ -16,13 +18,20 @@ class AsyncSubjectBase(AsyncObserver[TSource]): def __init__(self) -> None: self._loop = asyncio.get_event_loop() + self._running_tasks: set[Task[Any]] = set() async def asend_at(self, when: float, value: TSource) -> None: - async def task() -> None: + task: Task[Any] | None = None + + async def worker() -> None: await self.asend(value) + if task: + self._running_tasks.remove(task) def callback() -> None: - asyncio.ensure_future(task()) + nonlocal task + task = asyncio.ensure_future(worker()) + self._running_tasks.add(task) self._loop.call_at(when, callback) @@ -31,18 +40,29 @@ async def asend_later(self, delay: float, value: TSource) -> None: await self.asend(value) async def asend_later_scheduled(self, delay: float, value: TSource) -> None: - async def task() -> None: + task: Task[Any] | None = None + + async def worker() -> None: await asyncio.sleep(delay) await self.asend(value) + if task: + self._running_tasks.remove(task) - asyncio.ensure_future(task()) + task = asyncio.ensure_future(worker()) + self._running_tasks.add(task) async def athrow_at(self, when: float, err: Exception) -> None: - async def task() -> None: + task: Task[Any] | None = None + + async def worker() -> None: await self.athrow(err) + if task: + self._running_tasks.remove(task) def callback() -> None: - asyncio.ensure_future(task()) + nonlocal task + task = asyncio.ensure_future(worker()) + self._running_tasks.add(task) self._loop.call_at(when, callback) @@ -51,18 +71,29 @@ async def athrow_later(self, delay: float, err: Exception) -> None: await self.athrow(err) async def athrow_later_scheduled(self, delay: float, err: Exception) -> None: - async def task() -> None: + task: Task[Any] | None = None + + async def worker() -> None: + nonlocal task await asyncio.sleep(delay) await self.athrow(err) + if task: + self._running_tasks.remove(task) - asyncio.ensure_future(task()) + task = asyncio.ensure_future(worker()) + self._running_tasks.add(task) async def aclose_at(self, when: float) -> None: - async def task() -> None: + task: Task[Any] | None = None + + async def worker() -> None: await self.aclose() + if task: + self._running_tasks.remove(task) def callback() -> None: - asyncio.ensure_future(task()) + task = asyncio.ensure_future(worker()) + self._running_tasks.add(task) self._loop.call_at(when, callback) @@ -71,11 +102,16 @@ async def aclose_later(self, delay: float) -> None: await self.aclose() async def close_later_scheduled(self, delay: float) -> None: - async def task() -> None: + task: Task[Any] | None = None + + async def worker() -> None: await asyncio.sleep(delay) await self.aclose() + if task: + self._running_tasks.remove(task) - asyncio.ensure_future(task()) + task = asyncio.ensure_future(worker()) + self._running_tasks.add(task) class AsyncTestSubject(AsyncSubject[TSource], AsyncSubjectBase[TSource]): diff --git a/aioreactive/testing/virtual_events.py b/aioreactive/testing/virtual_events.py index a9e350f..4aeccc2 100644 --- a/aioreactive/testing/virtual_events.py +++ b/aioreactive/testing/virtual_events.py @@ -6,6 +6,7 @@ from asyncio import tasks from asyncio.log import logger + log = logging.getLogger(__name__) __all__ = "VirtualTimeEventLoop" @@ -52,18 +53,18 @@ def time(self): return self._time def _run_once(self): - """Run one full iteration of the event loop. This calls all - currently ready callbacks, polls for I/O, schedules the - resulting callbacks, and finally schedules 'call_later' - callbacks. + """Run one full iteration of the event loop. + + This calls all currently ready callbacks, polls for I/O, + schedules the resulting callbacks, and finally schedules + 'call_later' callbacks. """ # log.debug("run_once()") sched_count = len(self._scheduled) if ( sched_count > _MIN_SCHEDULED_TIMER_HANDLES - and self._timer_cancelled_count / sched_count - > _MIN_CANCELLED_TIMER_HANDLES_FRACTION + and self._timer_cancelled_count / sched_count > _MIN_CANCELLED_TIMER_HANDLES_FRACTION ): # Remove delayed calls that were cancelled if their number # is too high @@ -114,9 +115,7 @@ def _run_once(self): handle._run() dt = self.time() - t0 if dt >= self.slow_callback_duration: - logger.warning( - "Executing %s took %.3f seconds", _format_handle(handle), dt - ) + logger.warning("Executing %s took %.3f seconds", _format_handle(handle), dt) finally: self._current_handle = None else: diff --git a/aioreactive/timeshift.py b/aioreactive/timeshift.py index 5cc5ad8..b5b1f94 100644 --- a/aioreactive/timeshift.py +++ b/aioreactive/timeshift.py @@ -1,7 +1,8 @@ import asyncio import logging -from datetime import datetime, timedelta -from typing import Callable, Iterable, NoReturn, Tuple, TypeVar +from collections.abc import Awaitable, Callable, Iterable +from datetime import UTC, datetime, timedelta +from typing import NoReturn, TypeVar from expression import curry_flipped from expression.collections import seq @@ -11,7 +12,6 @@ TailCallResult, aiotools, fst, - match, pipe, tailrec_async, ) @@ -25,6 +25,7 @@ from .transform import map from .types import AsyncDisposable, AsyncObservable, AsyncObserver + _TSource = TypeVar("_TSource") log = logging.getLogger(__name__) @@ -40,52 +41,50 @@ def delay( relative time intervals between the values are preserved. Args: + source: The source observable. seconds (float): Number of seconds to delay. Returns: Delayed stream. """ - cts = CancellationTokenSource() token = cts.token async def subscribe_async(aobv: AsyncObserver[_TSource]) -> AsyncDisposable: - async def worker( - inbox: MailboxProcessor[Tuple[Notification[_TSource], datetime]] - ) -> None: + def worker(inbox: MailboxProcessor[tuple[Notification[_TSource], datetime]]) -> Awaitable[None]: @tailrec_async - async def loop() -> "TailCallResult[None, ...]": + async def loop() -> TailCallResult[None, ...]: if token.is_cancellation_requested: return ns, due_time = await inbox.receive() - diff = due_time - datetime.utcnow() + diff = due_time - datetime.now(UTC) seconds = diff.total_seconds() if seconds > 0: await asyncio.sleep(seconds) async def matcher() -> None: - with match(ns) as case: - for x in case(OnNext[_TSource]): + match ns: + case OnNext(value=x): await aobv.asend(x) return - for err in case(OnError[_TSource]): + case OnError(exception=err): await aobv.athrow(err) return - for x in case(OnCompleted): + case _: await aobv.aclose() return await matcher() return TailCall["..."]() - asyncio.ensure_future(loop()) + return asyncio.ensure_future(loop()) agent = MailboxProcessor.start(worker, token) async def fn(ns: Notification[_TSource]) -> None: - due_time = datetime.utcnow() + timedelta(seconds=seconds) + due_time = datetime.now(UTC) + timedelta(seconds=seconds) agent.post((ns, due_time)) obv: AsyncNotificationObserver[_TSource] = AsyncNotificationObserver(fn) @@ -109,28 +108,26 @@ async def subscribe_async(aobv: AsyncObserver[_TSource]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) infinite: Iterable[int] = seq.infinite - async def worker( - inbox: MailboxProcessor[Tuple[Notification[_TSource], int]] - ) -> None: + async def worker(inbox: MailboxProcessor[tuple[Notification[_TSource], int]]) -> None: @tailrec_async async def message_loop( current_index: int, ) -> "TailCallResult[NoReturn, [int]]": n, index = await inbox.receive() - with match(n) as case: - log.debug("debounce: %s, %d, %d", n, index, current_index) - for x in case(OnNext[_TSource]): + match n: + case OnNext(value=x): + log.debug("debounce: %s, %d, %d", n, index, current_index) if index == current_index: await safe_obv.asend(x) current_index = index elif index > current_index: current_index = index - for err in case(OnError[_TSource]): + case OnError(exception=err): await safe_obv.athrow(err) - while case(OnCompleted): + case _: await safe_obv.aclose() return TailCall[int](current_index) diff --git a/aioreactive/transform.py b/aioreactive/transform.py index c563277..87f60a3 100644 --- a/aioreactive/transform.py +++ b/aioreactive/transform.py @@ -1,4 +1,7 @@ -from typing import Any, Awaitable, Callable, Iterable, TypeVar +from __future__ import annotations + +from collections.abc import Awaitable, Callable, Iterable +from typing import Any, TypeVar from expression.collections import seq from expression.core import ( @@ -9,7 +12,6 @@ TailCall, TailCallResult, compose, - match, pipe, tailrec_async, ) @@ -18,10 +20,6 @@ from .combine import merge_inner, zip_seq from .create import fail from .msg import ( - CompletedMsg, - DisposeMsg, - InnerCompletedMsg, - InnerObservableMsg, Key, Msg, ) @@ -29,6 +27,7 @@ from .observers import AsyncAnonymousObserver, auto_detach_observer from .types import AsyncObserver + _TSource = TypeVar("_TSource") _TResult = TypeVar("_TResult") @@ -43,16 +42,14 @@ def transform( _TSource, ], Awaitable[None], - ] + ], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: def _(source: AsyncObservable[_TSource]) -> AsyncObservable[_TResult]: async def subscribe_async(aobv: AsyncObserver[_TResult]) -> AsyncDisposable: async def asend(value: _TSource) -> None: return await anext(aobv.asend, value) - obv: AsyncObserver[_TSource] = AsyncAnonymousObserver( - asend, aobv.athrow, aobv.aclose - ) + obv: AsyncObserver[_TSource] = AsyncAnonymousObserver(asend, aobv.athrow, aobv.aclose) sub = await source.subscribe_async(obv) return sub @@ -62,11 +59,14 @@ async def asend(value: _TSource) -> None: def map_async( - amapper: Callable[[_TSource], Awaitable[_TResult]] + amapper: Callable[[_TSource], Awaitable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: - """Returns an observable sequence whose elements are the result of + """Map async. + + Returns an observable sequence whose elements are the result of invoking the async mapper function on each element of the - source.""" + source. + """ async def handler(next: Callable[[_TResult], Awaitable[None]], x: _TSource) -> None: b = await amapper(x) @@ -76,57 +76,50 @@ async def handler(next: Callable[[_TResult], Awaitable[None]], x: _TSource) -> N def starmap_async( - amapper: Callable[..., Awaitable[_TResult]] + amapper: Callable[..., Awaitable[_TResult]], ) -> Callable[[AsyncObservable[Any]], AsyncObservable[_TResult]]: """Map async spreading arguments to the async mapper. Returns an observable sequence whose elements are the result of invoking the async mapper function on each element of the - source.""" + source. + """ - async def handler( - next: Callable[[_TResult], Awaitable[None]], args: Iterable[Any] - ) -> None: + async def handler(next: Callable[[_TResult], Awaitable[None]], args: Iterable[Any]) -> None: b = await amapper(*args) await next(b) return transform(handler) -def map( - mapper: Callable[[_TSource], _TResult] -) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: +def map(mapper: Callable[[_TSource], _TResult]) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Map each element in the stream. Returns an observable sequence whose elements are the result of - invoking the mapper function on each element of the source.""" + invoking the mapper function on each element of the source. + """ - def handler( - next: Callable[[_TResult], Awaitable[None]], x: _TSource - ) -> Awaitable[None]: + def handler(next: Callable[[_TResult], Awaitable[None]], x: _TSource) -> Awaitable[None]: return next(mapper(x)) return transform(handler) -def starmap( - mapper: Callable[..., _TResult] -) -> Callable[[AsyncObservable[Any]], AsyncObservable[_TResult]]: +def starmap(mapper: Callable[..., _TResult]) -> Callable[[AsyncObservable[Any]], AsyncObservable[_TResult]]: """Map and spread the arguments to the mapper. Returns an observable sequence whose elements are the result of - invoking the mapper function on each element of the source.""" + invoking the mapper function on each element of the source. + """ - def handler( - next: Callable[[_TResult], Awaitable[None]], args: Iterable[Any] - ) -> Awaitable[None]: + def handler(next: Callable[[_TResult], Awaitable[None]], args: Iterable[Any]) -> Awaitable[None]: return next(mapper(*args)) return transform(handler) def mapi_async( - mapper: Callable[[_TSource, int], Awaitable[_TResult]] + mapper: Callable[[_TSource, int], Awaitable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Map with index async. @@ -134,7 +127,6 @@ def mapi_async( invoking the async mapper function by incorporating the element's index on each element of the source. """ - return compose( zip_seq(seq.infinite), starmap_async(mapper), @@ -142,7 +134,7 @@ def mapi_async( def mapi( - mapper: Callable[[_TSource, int], _TResult] + mapper: Callable[[_TSource, int], _TResult], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Map with index. @@ -157,7 +149,7 @@ def mapi( def flat_map( - mapper: Callable[[_TSource], AsyncObservable[_TResult]] + mapper: Callable[[_TSource], AsyncObservable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flap map the observable sequence. @@ -171,7 +163,6 @@ def flat_map( Returns: The result stream. """ - return compose( map(mapper), merge_inner(0), @@ -179,7 +170,7 @@ def flat_map( def flat_mapi( - mapper: Callable[[_TSource, int], AsyncObservable[_TResult]] + mapper: Callable[[_TSource, int], AsyncObservable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flat map with index. @@ -195,7 +186,6 @@ def flat_mapi( Returns: Stream[TSource, TResult]: [description] """ - return compose( mapi(mapper), merge_inner(0), @@ -203,7 +193,7 @@ def flat_mapi( def flat_map_async( - mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]] + mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flap map async. @@ -213,8 +203,7 @@ def flat_map_async( Args: - mapperCallable ([type]): [description] - Awaitable ([type]): [description] + mapper: Function to transform each item in the stream. Returns: Stream[TSource, TResult]: [description] @@ -226,7 +215,7 @@ def flat_map_async( def flat_mapi_async( - mapper: Callable[[_TSource, int], Awaitable[AsyncObservable[_TResult]]] + mapper: Callable[[_TSource, int], Awaitable[AsyncObservable[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flat map async with index. @@ -236,8 +225,7 @@ def flat_mapi_async( back into one observable sequence. Args: - mapperAsync ([type]): [description] - Awaitable ([type]): [description] + mapper: Function to transform each item in the stream. Returns: Stream[TSource, TResult]: [description] @@ -249,7 +237,7 @@ def flat_mapi_async( def concat_map( - mapper: Callable[[_TSource], AsyncObservable[_TResult]] + mapper: Callable[[_TSource], AsyncObservable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: return compose( map(mapper), @@ -276,9 +264,7 @@ def switch_latest( async def subscribe_async(aobv: AsyncObserver[_TSource]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) - def obv( - mb: MailboxProcessor[Msg[_TSource]], id: int - ) -> AsyncObserver[_TSource]: + def obv(mb: MailboxProcessor[Msg[_TSource]], id: int) -> AsyncObserver[_TSource]: async def asend(value: _TSource) -> None: await safe_obv.asend(value) @@ -287,8 +273,7 @@ async def athrow(error: Exception) -> None: async def aclose() -> None: pipe( - Key(id), - InnerCompletedMsg, + Msg[Any](inner_completed=Key(id)), mb.post, ) @@ -298,35 +283,34 @@ async def worker(inbox: MailboxProcessor[Msg[_TSource]]) -> None: @tailrec_async async def message_loop( current: Option[AsyncDisposable], is_stopped: bool, current_id: int - ) -> "TailCallResult[None, [Option[AsyncDisposable], bool, int]]": + ) -> TailCallResult[None, [Option[AsyncDisposable], bool, int]]: cmd = await inbox.receive() - with match(cmd) as case: - for xs in case(InnerObservableMsg[_TSource]): + match cmd: + case Msg(tag="inner_observable", inner_observable=xs): next_id = current_id + 1 for disp in current.to_list(): await disp.dispose_async() inner = await xs.subscribe_async(obv(inbox, next_id)) current, current_id = Some(inner), next_id - break - for idx in case(InnerCompletedMsg[Key]): + + case Msg(tag="inner_completed", inner_completed=idx): if is_stopped and idx == current_id: await safe_obv.aclose() current, is_stopped = Nothing, True - break - while case(CompletedMsg): + + case Msg(tag="completed"): if current.is_none(): await safe_obv.aclose() - break - while case(DisposeMsg): + + case Msg(tag="dispose"): if current.is_some(): await current.value.dispose_async() current, is_stopped = Nothing, True - break + case _: + raise ValueError(f"Unknown message: {cmd}") - return TailCall[Option[AsyncDisposable], bool, int]( - current, is_stopped, current_id - ) + return TailCall[Option[AsyncDisposable], bool, int](current, is_stopped, current_id) await message_loop(Nothing, False, 0) @@ -334,7 +318,7 @@ async def message_loop( async def asend(xs: AsyncObservable[_TSource]) -> None: pipe( - InnerObservableMsg(xs), + Msg(inner_observable=xs), inner_agent.post, ) @@ -342,19 +326,18 @@ async def athrow(error: Exception) -> None: await safe_obv.athrow(error) async def aclose() -> None: - inner_agent.post(CompletedMsg) + inner_agent.post(Msg(completed=True)) _obv = AsyncAnonymousObserver(asend, athrow, aclose) dispose = await pipe( _obv, - AsyncObserver, source.subscribe_async, auto_detach, ) async def cancel() -> None: await dispose.dispose_async() - inner_agent.post(DisposeMsg) + inner_agent.post(Msg(dispose=True)) return AsyncDisposable.create(cancel) @@ -362,7 +345,7 @@ async def cancel() -> None: def flat_map_latest_async( - mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]] + mapper: Callable[[_TSource], Awaitable[AsyncObservable[_TResult]]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flat map latest async. @@ -381,11 +364,10 @@ def flat_map_latest_async( def flat_map_latest( - mapper: Callable[[_TSource], AsyncObservable[_TResult]] + mapper: Callable[[_TSource], AsyncObservable[_TResult]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TResult]]: """Flat map latest. - Transforms the items emitted by an source sequence into observable streams, and mirror those items emitted by the most-recently transformed observable sequence. @@ -400,7 +382,7 @@ def flat_map_latest( def catch( - handler: Callable[[Exception], AsyncObservable[_TSource]] + handler: Callable[[Exception], AsyncObservable[_TSource]], ) -> Callable[[AsyncObservable[_TSource]], AsyncObservable[_TSource]]: """Catch Exception. diff --git a/aioreactive/types.py b/aioreactive/types.py index 417f4b8..c8d41e2 100644 --- a/aioreactive/types.py +++ b/aioreactive/types.py @@ -1,8 +1,10 @@ from abc import abstractmethod -from typing import Awaitable, Callable, Generic, Optional, Protocol, TypeVar, Union +from collections.abc import Awaitable, Callable +from typing import Generic, Protocol, TypeVar from expression.system import AsyncDisposable + _T = TypeVar("_T") _TSource = TypeVar("_TSource") _T_out = TypeVar("_T_out", covariant=True) # Any type covariant containers. @@ -37,22 +39,22 @@ class AsyncObservable(Generic[_T_out]): @abstractmethod async def subscribe_async( self, - send: Optional[Union[SendAsync[_T_out], AsyncObserver[_T_out]]] = None, - throw: Optional[ThrowAsync] = None, - close: Optional[CloseAsync] = None, + send: SendAsync[_T_out] | AsyncObserver[_T_out] | None = None, + throw: ThrowAsync | None = None, + close: CloseAsync | None = None, ) -> AsyncDisposable: raise NotImplementedError class Flatten(Protocol): - """A zipping projetion is a function that projects from one observable to a zipped, i.e: + """Flatten protocol. + + A zipping projetion is a function that projects from one observable to a zipped, i.e: `AsyncObservable[AsyncObservable[TSource]]) -> AsyncObservable[Tuple[TSource, TResult]]` """ - def __call__( - self, __source: AsyncObservable[AsyncObservable[_TSource]] - ) -> AsyncObservable[_TSource]: + def __call__(self, __source: AsyncObservable[AsyncObservable[_TSource]]) -> AsyncObservable[_TSource]: raise NotImplementedError diff --git a/aioreactive/utils.py b/aioreactive/utils.py index 2700182..d49fd67 100644 --- a/aioreactive/utils.py +++ b/aioreactive/utils.py @@ -1,19 +1,20 @@ import logging -from typing import Any, Optional, TypeVar +from typing import Any, TypeVar from .types import AsyncObserver + _TSource = TypeVar("_TSource") log = logging.getLogger(__name__) def noop(*args: Any, **kw: Any) -> None: - """No operation. Returns nothing""" + """No operation. Returns nothing.""" -async def anoop(value: Optional[Any] = None) -> None: - """Async no operation. Returns nothing""" +async def anoop(value: Any | None = None) -> None: + """Async no operation. Returns nothing.""" class NoopObserver(AsyncObserver[_TSource]): diff --git a/examples/autocomplete/autocomplete.py b/examples/autocomplete/autocomplete.py index a2beb9a..5e031bd 100644 --- a/examples/autocomplete/autocomplete.py +++ b/examples/autocomplete/autocomplete.py @@ -1,8 +1,7 @@ -""" -Example running an aiohttp server doing search queries against +"""Example running an aiohttp server doing search queries against Wikipedia to populate the autocomplete dropdown in the web UI. Start using `python autocomplete.py` and navigate your web browser to -http://localhost:8080 +http://localhost:8080. Requirements: > pip3 install aiohttp @@ -12,7 +11,7 @@ import json import os from asyncio.events import AbstractEventLoop -from typing import Any, Callable, Dict +from typing import Any import aiohttp import aiohttp_jinja2 @@ -25,7 +24,8 @@ import aioreactive as rx -Msg = Dict[str, str] + +Msg = dict[str, str] async def search_wikipedia(term: str) -> rx.AsyncObservable[str]: @@ -45,7 +45,9 @@ async def websocket_handler(request: Request) -> WebSocketResponse: stream: rx.AsyncObservable[Msg] = rx.AsyncSubject() - mapper: Callable[[Msg], str] = lambda x: x["term"] + def mapper(x: Msg) -> str: + return x["term"] + xs = pipe( stream, rx.map(mapper), @@ -81,7 +83,7 @@ async def athrow(ex: Exception) -> None: @aiohttp_jinja2.template("index.html") -async def index(request: Request) -> Dict[str, Any]: +async def index(request: Request) -> dict[str, Any]: return dict() diff --git a/examples/parallel/parallel.py b/examples/parallel/parallel.py index df4e82e..ca3bc59 100644 --- a/examples/parallel/parallel.py +++ b/examples/parallel/parallel.py @@ -9,6 +9,7 @@ import aioreactive as rx + log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -16,9 +17,9 @@ def long_running(value: int) -> int: - print("Long running ({0}) on thread {1}".format(value, current_thread().name)) + print(f"Long running ({value}) on thread {current_thread().name}") time.sleep(3) - print("Long running, done ({0}) on thread {1}".format(value, current_thread().name)) + print(f"Long running, done ({value}) on thread {current_thread().name}") return value diff --git a/examples/timeflies/timeflies.py b/examples/timeflies/timeflies.py index d7e166c..c31bfc6 100644 --- a/examples/timeflies/timeflies.py +++ b/examples/timeflies/timeflies.py @@ -3,15 +3,16 @@ import sys from tkinter import Event, Frame, Label, Misc, Tk from types import FrameType -from typing import Optional, Tuple +from typing import Any -from expression.core import MailboxProcessor, pipe +from expression import MailboxProcessor, pipe from expression.system import AsyncDisposable import aioreactive as rx from aioreactive import AsyncAnonymousObserver, AsyncSubject from aioreactive.types import AsyncObservable + # logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) @@ -19,7 +20,7 @@ async def main() -> None: root = Tk() root.title("aioreactive") - mousemoves: AsyncSubject[Tuple[int, int]] = AsyncSubject() + mousemoves: AsyncSubject[tuple[int, int]] = AsyncSubject() frame = Frame(root, width=800, height=600) # , bg="white") @@ -34,10 +35,10 @@ async def worker(mb: MailboxProcessor["Event[Misc]"]) -> None: text = "TIME FLIES LIKE AN ARROW" labels = [Label(frame, text=c, borderwidth=0, padx=0, pady=0) for c in text] - def handle_label(label: Label, i: int) -> AsyncObservable[Tuple[Label, int, int]]: + def handle_label(label: Label, i: int) -> AsyncObservable[tuple[Label, int, int]]: label.config(dict(borderwidth=0, padx=0, pady=0)) - def mapper(x: int, y: int) -> Tuple[Label, int, int]: + def mapper(x: int, y: int) -> tuple[Label, int, int]: """Map mouse-move pos to label and new pos for label.""" return label, x + i * 12 + 15, y @@ -55,15 +56,16 @@ def mapper(x: int, y: int) -> Tuple[Label, int, int]: subscription: AsyncDisposable running = True + tasks: list[asyncio.Task[Any]] = [] - async def asend(value: Tuple[Label, int, int]) -> None: + async def asend(value: tuple[Label, int, int]) -> None: """Perform side effect.""" label, x, y = value label.place(x=x, y=y) async def athrow(ex: Exception): - nonlocal running - print("Exception: ", ex) + nonlocal running, subscription + print("Exception: ", ex) # noqa await subscription.dispose_async() running = False @@ -80,17 +82,21 @@ async def stop(): await subscription.dispose_async() def handle_focus_in(event: "Event[Misc]"): - asyncio.ensure_future(start()) + task = asyncio.ensure_future(start()) + tasks.append(task) + task.add_done_callback(lambda _: tasks.remove(task)) def handle_focus_out(event: "Event[Misc]"): - asyncio.ensure_future(stop()) + task = asyncio.ensure_future(stop()) + tasks.append(task) + task.add_done_callback(lambda _: tasks.remove(task)) root.bind("", handle_focus_in) root.bind("", handle_focus_out) frame.pack() - def signal_handler(signal: int, frame: Optional[FrameType] = None) -> None: + def signal_handler(signal: int, frame: FrameType | None = None) -> None: nonlocal running running = False sys.stderr.write("Exiting...\n") diff --git a/poetry.lock b/poetry.lock index 7119ecb..71c6cea 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,111 +1,209 @@ -[[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "21.4.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "autoflake" -version = "1.4" +version = "1.7.8" description = "Removes unused imports and unused variables" -category = "dev" optional = false -python-versions = "*" - -[package.dependencies] -pyflakes = ">=1.1.0" - -[[package]] -name = "black" -version = "22.3.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" +files = [ + {file = "autoflake-1.7.8-py3-none-any.whl", hash = "sha256:46373ef69b6714f5064c923bb28bd797c4f8a9497f557d87fc36665c6d956b39"}, + {file = "autoflake-1.7.8.tar.gz", hash = "sha256:e7e46372dee46fa1c97acf310d99d922b63d369718a270809d7c278d34a194cf"}, +] [package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +pyflakes = ">=1.1.0,<3" [[package]] name = "certifi" -version = "2022.6.15" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] [[package]] name = "cfgv" -version = "3.3.1" +version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode_backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coverage" -version = "6.4.1" +version = "6.5.0" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] [package.extras] toml = ["tomli"] @@ -114,12 +212,15 @@ toml = ["tomli"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "dev" optional = false python-versions = ">= 3.5" +files = [ + {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, + {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, +] [package.dependencies] -coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -128,198 +229,159 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "distlib" -version = "0.3.4" +version = "0.3.8" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] [[package]] name = "dunamai" -version = "1.12.0" +version = "1.19.0" description = "Dynamic version generation" -category = "dev" optional = false python-versions = ">=3.5,<4.0" +files = [ + {file = "dunamai-1.19.0-py3-none-any.whl", hash = "sha256:1ed948676bbf0812bfaafe315a134634f8d6eb67138513c75aa66e747404b9c6"}, + {file = "dunamai-1.19.0.tar.gz", hash = "sha256:6ad99ae34f7cd290550a2ef1305d2e0292e6e6b5b1b830dfc07ceb7fd35fec09"}, +] [package.dependencies] packaging = ">=20.9" -[[package]] -name = "execnet" -version = "1.9.0" -description = "execnet: rapid multi-Python deployment" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -testing = ["pre-commit"] - [[package]] name = "expression" -version = "3.3.1" +version = "5.0.1" description = "Practical functional programming for Python 3.9+" -category = "main" optional = false -python-versions = ">=3.9,<3.11" +python-versions = ">=3.11,<4" +files = [ + {file = "expression-5.0.1-py3-none-any.whl", hash = "sha256:b5fe712a2e8c7515e4a688cff885f2e24b597f522099b3256e83e1edd6c99965"}, + {file = "expression-5.0.1.tar.gz", hash = "sha256:3755d9115242d1b6c51f2fc6facc769b9b5ce30077abe861e836639c1e935906"}, +] [package.dependencies] typing-extensions = ">=4.1.1,<5.0.0" +[package.extras] +all = ["pydantic (>=2.0.0,<3.0.0)"] +pydantic = ["pydantic (>=2.0.0,<3.0.0)"] + [[package]] name = "filelock" -version = "3.7.1" +version = "3.13.1" description = "A platform independent file lock." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] [package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] - -[[package]] -name = "flake8" -version = "4.0.1" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "identify" -version = "2.5.1" +version = "2.5.33" description = "File identification library for Python" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, +] [package.extras] license = ["ukkonen"] [[package]] name = "idna" -version = "3.3" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] -plugins = ["setuptools"] - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = "*" - -[[package]] -name = "mypy" -version = "0.931" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = ">=1.1.0" -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] [[package]] name = "nodeenv" -version = "1.6.0" +version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false -python-versions = "*" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] [package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +setuptools = "*" [[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] [[package]] name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" +version = "4.1.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] [package.extras] dev = ["pre-commit", "tox"] @@ -327,542 +389,229 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.19.0" +version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycodestyle" -version = "2.8.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +virtualenv = ">=20.10.0" [[package]] name = "pyflakes" -version = "2.4.0" +version = "2.5.0" description = "passive checker of Python programs" -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["railroad-diagrams", "jinja2"] - -[[package]] -name = "pyright" -version = "0.0.13.post0" -description = "Command line wrapper for pyright" -category = "dev" -optional = false -python-versions = ">=3" - -[package.dependencies] -nodeenv = ">=1.6.0" - -[package.extras] -all = ["twine (>=3.4.1)"] -dev = ["twine (>=3.4.1)"] +python-versions = ">=3.6" +files = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] [[package]] name = "pytest" -version = "7.1.2" +version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.18.3" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"}, + {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, + {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, +] [package.dependencies] pytest = ">=6.1.0" [package.extras] -testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-forked" -version = "1.4.0" -description = "run tests in isolated forked subprocesses" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -py = "*" -pytest = ">=3.10" - -[[package]] -name = "pytest-xdist" -version = "2.5.0" -description = "pytest xdist plugin for distributed testing and loop-on-failing modes" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" -pytest-forked = "*" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] +testing = ["coverage (==6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] [[package]] name = "reactivex" -version = "4.0.2" +version = "4.0.4" description = "ReactiveX (Rx) for Python" -category = "main" optional = false -python-versions = ">=3.7,<3.11" +python-versions = ">=3.7,<4.0" +files = [ + {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"}, + {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"}, +] [package.dependencies] typing-extensions = ">=4.1.1,<5.0.0" [[package]] name = "requests" -version = "2.28.0" +version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2.0.0,<2.1.0" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +name = "setuptools" +version = "69.0.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, +] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "typing-extensions" -version = "4.2.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] [[package]] name = "urllib3" -version = "1.26.9" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, +] [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.14.1" +version = "20.25.0" description = "Virtual Python Environment builder" -category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, +] [package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -platformdirs = ">=2,<3" -six = ">=1.9.0,<2" +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] -lock-version = "1.1" -python-versions = ">= 3.9, < 3.11" -content-hash = "31e22b0a63cc844a2381c28d28d973861e54423864c0c0bd2efd2022500a46fa" - -[metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, -] -autoflake = [ - {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, -] -black = [ - {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, - {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, - {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, - {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, - {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, - {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, - {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, - {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, - {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, - {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, - {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, - {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, - {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, - {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, - {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, - {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, - {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, - {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, - {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, - {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, - {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, - {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, - {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -coverage = [ - {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, - {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, - {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, - {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, - {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, - {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, - {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, - {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, - {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, - {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, - {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, - {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, - {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, -] -coveralls = [ - {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, - {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, -] -distlib = [ - {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, - {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, -] -docopt = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] -dunamai = [ - {file = "dunamai-1.12.0-py3-none-any.whl", hash = "sha256:00b9c1ef58d4950204f76c20f84afe7a28d095f77feaa8512dbb172035415e61"}, - {file = "dunamai-1.12.0.tar.gz", hash = "sha256:fac4f09e2b8a105bd01f8c50450fea5aa489a6c439c949950a65f0dd388b0d20"}, -] -execnet = [ - {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, - {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, -] -expression = [ - {file = "Expression-3.3.1-py3-none-any.whl", hash = "sha256:bdc4dbb8bd5927ba8795aabe8749ba04c17d45bd2e811034d4859236214fede5"}, - {file = "Expression-3.3.1.tar.gz", hash = "sha256:dcb36ba1090e4c8711ba5eb60c5800dc15145c1b6a64e71aebfd9b13db7d1be1"}, -] -filelock = [ - {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, - {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, -] -flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, -] -identify = [ - {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"}, - {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -mypy = [ - {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, - {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, - {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, - {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, - {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, - {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, - {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, - {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, - {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, - {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, - {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, - {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, - {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, - {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, - {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, - {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, - {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, - {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, - {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, - {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nodeenv = [ - {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, - {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pre-commit = [ - {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, - {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, -] -pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pyright = [ - {file = "pyright-0.0.13.post0-py3-none-any.whl", hash = "sha256:47c29623f208226f4437bf02276475b816beff6f539ea166e3cade57d6717598"}, - {file = "pyright-0.0.13.post0.tar.gz", hash = "sha256:dda7602f3692f07fdff8c6d30d704f1b1ed20b5b3bb55c069599988d1ec9bd5a"}, -] -pytest = [ - {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, - {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"}, - {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, - {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, -] -pytest-forked = [ - {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, - {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, -] -pytest-xdist = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -reactivex = [ - {file = "reactivex-4.0.2-py3-none-any.whl", hash = "sha256:04d17b55652caf8b6c911f7588b4fe8fa69b6fc48e312e0e3462597fb93bb588"}, - {file = "reactivex-4.0.2.tar.gz", hash = "sha256:e4db0f7b1646c2198fb7cceade05be7d2e1bd8c0284ae5dffdae449055400310"}, -] -requests = [ - {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"}, - {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, -] -urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, -] -virtualenv = [ - {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, - {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, -] +lock-version = "2.0" +python-versions = ">= 3.11, < 4" +content-hash = "f58815388ad47a96fe03eb2d6a1fc23f057dd008ec0bade544bee51ac50ce224" diff --git a/pyproject.toml b/pyproject.toml index 4db11c0..89893ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,67 +1,61 @@ [tool.poetry] name = "aioreactive" version = "0.0.0" # NOTE: will be updated by publish script -description = "sync/await Reactive Tools for Python 3.9+" +description = "sync/await Reactive Tools for Python 3.11+" readme = "README.md" authors = ["Dag Brattli ", "Børge Lanes"] license = "MIT License" repository = "https://github.com/dbrattli/aioreactive" classifiers = [ - "Development Status :: 3 - Alpha", - "Environment :: Other Environment", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Software Development :: Libraries :: Python Modules", -] -packages = [ - { include = "aioreactive" }, - { include = "aioreactive/py.typed" } + "Development Status :: 3 - Alpha", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: Libraries :: Python Modules", ] +packages = [{ include = "aioreactive" }, { include = "aioreactive/py.typed" }] [tool.poetry.dependencies] -python = ">= 3.9, < 3.11" +python = ">= 3.11, < 4" typing-extensions = "^4.1.1" -Expression = "^3.0.0" +Expression = "^5.0.1" reactivex = "^4.0.0" [tool.poetry.dev-dependencies] pytest-asyncio = "^0.18.1" -pytest = "^7.0.1" +pytest = "^7.4.4" coverage = "^6.3.2" -pytest-xdist = "^2.5.0" -black = "^22.1.0" -isort = "^5.10.1" -pyright = "^0.0.13" -mypy = "^0.931" -flake8 = "^4.0.1" coveralls = "^3.3.1" pre-commit = "^2.17.0" autoflake = "^1.4" dunamai = "^1.9.0" -[tool.black] -line-length = 88 -target_version = ['py39'] -include = '\.py$' +[tool.ruff] +# Keep in sync with .pre-commit-config.yaml +line-length = 120 +# D100: Missing docstring in public module +# D104: Missing docstring in public package +# D105: Missing docstring in magic method +ignore = ["D100", "D101", "D102", "D103", "D105", "D107"] +target-version = "py310" +select = ["D", "E", "W", "F", "I", "T", "RUF", "TID", "UP"] +exclude = ["tests", "docs", "examples"] + +[tool.ruff.pydocstyle] +convention = "google" -[tool.isort] -profile = "black" -line_length=88 # corresponds to -w flag -multi_line_output=3 # corresponds to -m flag -include_trailing_comma=true # corresponds to -tc flag -skip_glob = '^((?!py$).)*$' # isort all Python files -float_to_top=true +[tool.ruff.isort] +lines-after-imports = 2 +known-third-party = ["pytest"] [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "strict" -#addopts = "-n auto" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - diff --git a/pyrightconfig.json b/pyrightconfig.json index 7d1cd3c..8a07b5b 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -7,6 +7,6 @@ ], "reportImportCycles": false, "reportMissingImports": false, - "pythonVersion": "3.9", + "pythonVersion": "3.11", "typeCheckingMode": "strict" } \ No newline at end of file diff --git a/tests/test_async_iteration.py b/tests/test_async_iteration.py index ee1a526..b351d95 100644 --- a/tests/test_async_iteration.py +++ b/tests/test_async_iteration.py @@ -19,7 +19,7 @@ def event_loop(): @pytest.mark.asyncio async def test_async_iteration() -> None: xs = rx.from_iterable([1, 2, 3]) - result = [] + result: list[int] = [] async for x in rx.to_async_iterable(xs): result.append(x) @@ -39,7 +39,7 @@ async def test_async_comprehension() -> None: @pytest.mark.asyncio async def test_async_iteration_aync_with() -> None: xs = rx.from_iterable([1, 2, 3]) - result = [] + result: list[int] = [] obv = rx.AsyncIteratorObserver(xs) async for x in obv: @@ -55,7 +55,7 @@ async def test_async_iteration_inception() -> None: obv = rx.AsyncIteratorObserver(xs) ys = rx.from_async_iterable(obv) - result = [] + result: list[int] = [] async for y in rx.to_async_iterable(ys): result.append(y) diff --git a/tests/test_chain.py b/tests/test_chain.py index 61cc4db..1451cf5 100644 --- a/tests/test_chain.py +++ b/tests/test_chain.py @@ -34,7 +34,7 @@ def mapper(value: int) -> int: (0, OnNext(10)), (0, OnNext(20)), (0, OnNext(30)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -58,7 +58,7 @@ async def predicate(value: int) -> bool: assert obv.values == [ (ca(0.2), OnNext(20)), (ca(0.3), OnNext(30)), - (ca(0.3), OnCompleted), + (ca(0.3), OnCompleted()), ] @@ -84,5 +84,5 @@ async def long_running(value: int) -> AsyncObservable[int]: assert obv.values == [ (ca(0.2), OnNext(20)), (ca(0.3), OnNext(30)), - (ca(0.3), OnCompleted), + (ca(0.3), OnCompleted()), ] diff --git a/tests/test_debounce.py b/tests/test_debounce.py index f4344c9..b75a51b 100644 --- a/tests/test_debounce.py +++ b/tests/test_debounce.py @@ -44,7 +44,7 @@ async def test_debounce(): assert obv.values == [ (ca(0.5), OnNext(1)), (ca(1.1), OnNext(2)), - (ca(1.2), OnCompleted), + (ca(1.2), OnCompleted()), ] await subscription.dispose_async() @@ -68,7 +68,7 @@ async def test_debounce_filter(): assert obv.values == [ (ca(0.8), OnNext(2)), - (ca(0.9), OnCompleted), + (ca(0.9), OnCompleted()), ] await subscription.dispose_async() diff --git a/tests/test_delay.py b/tests/test_delay.py index 5dd4738..63ab45d 100644 --- a/tests/test_delay.py +++ b/tests/test_delay.py @@ -39,7 +39,7 @@ async def test_delay_done(): assert obv.values == [ (ca(1), OnNext(10)), (ca(2), OnNext(20)), - (ca(3), OnCompleted), + (ca(3), OnCompleted()), ] diff --git a/tests/test_distinct_until_changed.py b/tests/test_distinct_until_changed.py index b89404a..85eaa86 100644 --- a/tests/test_distinct_until_changed.py +++ b/tests/test_distinct_until_changed.py @@ -30,7 +30,7 @@ async def test_distinct_until_changed_different(): (0, OnNext(1)), (0, OnNext(2)), (0, OnNext(3)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -49,5 +49,5 @@ async def test_distinct_until_changed_changed(): (0, OnNext(3)), (0, OnNext(1)), (0, OnNext(2)), - (0, OnCompleted), + (0, OnCompleted()), ] diff --git a/tests/test_flat_map.py b/tests/test_flat_map.py index 1f93cfd..1251bef 100644 --- a/tests/test_flat_map.py +++ b/tests/test_flat_map.py @@ -31,7 +31,7 @@ def mapper(value: int) -> rx.AsyncObservable[int]: await xs.aclose() await obv - assert obv.values == [(0, OnNext(10)), (0, OnNext(20)), (0, OnCompleted)] + assert obv.values == [(0, OnNext(10)), (0, OnNext(20)), (0, OnCompleted())] @pytest.mark.asyncio diff --git a/tests/test_forward_pipe.py b/tests/test_forward_pipe.py index 6869a15..e82fe58 100644 --- a/tests/test_forward_pipe.py +++ b/tests/test_forward_pipe.py @@ -34,7 +34,7 @@ def mapper(value: int) -> int: (0, OnNext(10)), (0, OnNext(20)), (0, OnNext(30)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -57,7 +57,7 @@ async def predicate(value: int) -> bool: obv: AsyncTestObserver[int] = AsyncTestObserver() await rx.run(ys, obv) - assert obv.values == [(0, OnNext(20)), (0, OnNext(30)), (0, OnCompleted)] + assert obv.values == [(0, OnNext(20)), (0, OnNext(30)), (0, OnCompleted())] @pytest.mark.asyncio diff --git a/tests/test_from_iterable.py b/tests/test_from_iterable.py index 86d6450..cd32ae2 100644 --- a/tests/test_from_iterable.py +++ b/tests/test_from_iterable.py @@ -22,7 +22,7 @@ async def test_from_iterable_happy(): (0, OnNext(1)), (0, OnNext(2)), (0, OnNext(3)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -62,4 +62,4 @@ async def asend(value: int) -> None: # # with pytest.raises(asyncio.CancelledError): # await obv -# assert obv.values == [(0, OnNext(0)), (0, OnCompleted)] +# assert obv.values == [(0, OnNext(0)), (0, OnCompleted())] diff --git a/tests/test_map.py b/tests/test_map.py index 90aaa92..d5cf1d0 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -20,7 +20,7 @@ def event_loop(): @pytest.mark.asyncio async def test_map_works(): xs: AsyncObservable[int] = rx.from_iterable([1, 2, 3]) - values = [] + values: list[int] = [] async def asend(value: int) -> None: values.append(value) @@ -69,7 +69,7 @@ def mapper(x: int): async def test_map_subscription_cancel(): xs: rx.AsyncSubject[int] = rx.AsyncSubject() sub: Optional[AsyncDisposable] = None - result = [] + result: list[int] = [] def mapper(value: int) -> int: return value * 10 @@ -93,7 +93,7 @@ async def asend(value: int) -> None: @pytest.mark.asyncio async def test_mapi_works(): xs: AsyncObservable[int] = rx.from_iterable([1, 2, 3]) - values = [] + values: list[int] = [] async def asend(value: int) -> None: values.append(value) diff --git a/tests/test_merge.py b/tests/test_merge.py index 6dbb994..e782ec1 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -35,7 +35,7 @@ async def test_merge_done(): await xs.aclose() await obv - assert obv.values == [(0, OnNext(10)), (0, OnNext(20)), (0, OnCompleted)] + assert obv.values == [(0, OnNext(10)), (0, OnNext(20)), (0, OnCompleted())] @pytest.mark.asyncio @@ -71,7 +71,7 @@ async def test_merge_streams(): (3, OnNext(50)), (4, OnNext(30)), (5, OnNext(60)), - (6, OnCompleted), + (6, OnCompleted()), ] @@ -101,5 +101,5 @@ async def test_merge_streams_concat(): (6, OnNext(1)), (6, OnNext(2)), (6, OnNext(3)), - (6, OnCompleted), + (6, OnCompleted()), ] diff --git a/tests/test_pipe.py b/tests/test_pipe.py index 209b5e0..5c953c9 100644 --- a/tests/test_pipe.py +++ b/tests/test_pipe.py @@ -37,7 +37,7 @@ def mapper(value: int) -> int: (0, OnNext(10)), (0, OnNext(20)), (0, OnNext(30)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -59,7 +59,7 @@ async def predicate(value: int) -> bool: assert obv.values == [ (approx(0.2), OnNext(20)), (approx(0.3), OnNext(30)), - (approx(0.3), OnCompleted), + (approx(0.3), OnCompleted()), ] diff --git a/tests/test_scan.py b/tests/test_scan.py index 888b062..0ed251f 100644 --- a/tests/test_scan.py +++ b/tests/test_scan.py @@ -50,7 +50,7 @@ async def test_scan(): OnNext(3), OnNext(6), OnNext(10), - OnCompleted, + OnCompleted(), ] @@ -72,5 +72,5 @@ async def test_scan_async(): OnNext(3), OnNext(6), OnNext(10), - OnCompleted, + OnCompleted(), ] diff --git a/tests/test_single.py b/tests/test_single.py index a65c96c..e387a60 100644 --- a/tests/test_single.py +++ b/tests/test_single.py @@ -27,7 +27,7 @@ async def test_unit_happy(): obv: AsyncTestObserver[int] = AsyncTestObserver() await rx.run(xs, obv) - assert obv.values == [(0, OnNext(42)), (0, OnCompleted)] + assert obv.values == [(0, OnNext(42)), (0, OnCompleted())] @pytest.mark.asyncio @@ -65,7 +65,7 @@ async def asend(value: int) -> None: assert obv.values == [ (0, OnNext(42)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -79,7 +79,7 @@ async def test_unit_happy_resolved_future(): await rx.run(xs, obv) assert obv.values == [ (0, OnNext(42)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -95,7 +95,7 @@ async def test_unit_happy_future_resolve(): assert obv.values == [ (0, OnNext(42)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -125,4 +125,4 @@ async def test_unit_future_cancel(): with pytest.raises(asyncio.CancelledError): await obv - assert obv.values == [(approx(1), OnCompleted)] + assert obv.values == [(approx(1), OnCompleted())] diff --git a/tests/test_single_stream.py b/tests/test_single_stream.py index 77b9e17..3c71a7b 100644 --- a/tests/test_single_stream.py +++ b/tests/test_single_stream.py @@ -82,7 +82,7 @@ async def test_stream_send_after_close(): (1, OnNext(10)), (2, OnNext(20)), (3, OnNext(30)), - (5, OnCompleted), + (5, OnCompleted()), ] @@ -222,4 +222,4 @@ async def aclose(): async with await xs.subscribe_async(sink): await xs.asend_later(1, 20) - assert sink.values == [(10, OnCompleted)] + assert sink.values == [(10, OnCompleted())] diff --git a/tests/test_slice.py b/tests/test_slice.py index f5ce4da..c19ee66 100644 --- a/tests/test_slice.py +++ b/tests/test_slice.py @@ -32,7 +32,7 @@ async def test_slice_special(): (0, OnNext(2)), (0, OnNext(3)), (0, OnNext(4)), - (0, OnCompleted), + (0, OnCompleted()), ] @@ -50,5 +50,5 @@ async def test_slice_step(): (0, OnNext(1)), (0, OnNext(3)), (0, OnNext(5)), - (0, OnCompleted), + (0, OnCompleted()), ] diff --git a/tests/test_stream.py b/tests/test_stream.py index b1e15ce..778ddc3 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -84,7 +84,7 @@ async def test_stream_send_after_close() -> None: (1, OnNext(10)), (2, OnNext(20)), (3, OnNext(30)), - (5, OnCompleted), + (5, OnCompleted()), ] diff --git a/tests/test_take.py b/tests/test_take.py index 5b3258a..dc2b6a5 100644 --- a/tests/test_take.py +++ b/tests/test_take.py @@ -30,7 +30,7 @@ async def test_take_zero() -> None: with pytest.raises(CancelledError): await rx.run(ys, obv) - assert obv.values == [(0, OnCompleted)] + assert obv.values == [(0, OnCompleted())] @pytest.mark.asyncio @@ -43,7 +43,7 @@ async def test_take_empty() -> None: with pytest.raises(CancelledError): await rx.run(ys, obv) - assert obv.values == [(0, OnCompleted)] + assert obv.values == [(0, OnCompleted())] @pytest.mark.asyncio @@ -64,4 +64,4 @@ async def test_take_normal() -> None: result = await rx.run(ys, obv) assert result == 2 - assert obv.values == [(0, OnNext(1)), (0, OnNext(2)), (0, OnCompleted)] + assert obv.values == [(0, OnNext(1)), (0, OnNext(2)), (0, OnCompleted())] diff --git a/tests/test_with_latest_from.py b/tests/test_with_latest_from.py index e687e44..3e39850 100644 --- a/tests/test_with_latest_from.py +++ b/tests/test_with_latest_from.py @@ -52,7 +52,7 @@ async def test_withlatestfrom_never_never(): # with pytest.raises(CancelledError): # await rx.run(zs, obv) -# assert obv.values == [(0, OnCompleted)] +# assert obv.values == [(0, OnCompleted())] # @pytest.mark.asyncio @@ -70,4 +70,4 @@ async def test_withlatestfrom_never_never(): # await xs.aclose() # await obv -# assert obv.values == [(0, OnNext(5)), (0, OnCompleted)] +# assert obv.values == [(0, OnNext(5)), (0, OnCompleted())]