Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-duet for openSUSE:Factory checked in at 2022-09-29 18:12:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-duet (Old) and /work/SRC/openSUSE:Factory/.python-duet.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-duet" Thu Sep 29 18:12:07 2022 rev:2 rq:1006513 version:0.2.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python-duet/python-duet.changes 2022-02-21 17:47:00.627601356 +0100 +++ /work/SRC/openSUSE:Factory/.python-duet.new.2275/python-duet.changes 2022-09-29 18:12:18.291131900 +0200 @@ -1,0 +2,12 @@ +Tue Sep 27 19:11:09 UTC 2022 - Yogalakshmi Arunachalam <[email protected]> + +- Update to 0.2.7 + * Fix timeout clipping in Scheduler.tick (#67) + * Add support for overloaded sync callbacks (#66) + * Add support for wrapping abstract methods with duet.sync (#63) + * Drop support for python 3.6 (#62) + * Update ci.yml (#61) + * Ensure that duet.sleep raises an exception if outer timeout elapses first (#58) + * Update to latest version of black (#57) + +------------------------------------------------------------------- Old: ---- duet-0.2.3.tar.gz New: ---- duet-0.2.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-duet.spec ++++++ --- /var/tmp/diff_new_pack.Zyxm6R/_old 2022-09-29 18:12:18.907133103 +0200 +++ /var/tmp/diff_new_pack.Zyxm6R/_new 2022-09-29 18:12:18.911133111 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python3-%{**}} %define skip_python2 1 Name: python-duet -Version: 0.2.3 +Version: 0.2.7 Release: 0 Summary: A simple future-based async library for python License: Apache-2.0 @@ -32,8 +32,8 @@ BuildRequires: %{python_module asyncio-contextmanager} BuildRequires: %{python_module contextvars} %endif -BuildRequires: python-rpm-macros BuildRequires: fdupes +BuildRequires: python-rpm-macros Requires: python-typing_extensions %if %python_version_nodots <= 36 Requires: python-asyncio-contextmanager ++++++ duet-0.2.3.tar.gz -> duet-0.2.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/PKG-INFO new/duet-0.2.7/PKG-INFO --- old/duet-0.2.3/PKG-INFO 2021-11-02 00:29:39.942416400 +0100 +++ new/duet-0.2.7/PKG-INFO 2022-06-18 03:52:56.991586000 +0200 @@ -1,49 +1,52 @@ Metadata-Version: 2.1 Name: duet -Version: 0.2.3 +Version: 0.2.7 Summary: A simple future-based async library for python. Home-page: http://github.com/google/duet Author: The Duet Authors Author-email: [email protected] License: Apache 2 -Description: # duet - - A simple future-based async library for python - - Duet takes inspiration from the amazing [trio](https://trio.readthedocs.io/en/stable/) - library and the [structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) - approach to async programming that it uses. - However, duet differs from trio in two major ways: - - - Instead of a full-blown implementation of asynchronous IO, duet relies on the - `Future` interface for parallelism, and provides a way to run async/await - coroutines around those `Future`s. This is useful if you are using an API that - returns futures, such as RPC libraries like gRPC. The standard `Future` - interface does not implement `__await__` directly, so `Future` instances must - be wrapped in `duet.AwaitableFuture`. - - - duet is re-entrant. At the top level, you run async code by calling - `duet.run(foo)`. Inside `foo` suppose you call a function that has not yet - been fully refactored to be asynchronous, but itself calls `duet.run(bar)`. - Most async libraries, including `trio` and `asyncio`, will raise an exception - if you try to "re-enter" the event loop in this way, but duet allows it. We - have found that this can simplify the process of refactoring code to be - asynchronous because you don't have to completely separate the sync and async - parts of your codebase all at once. - - ## Installation - - Install from pypi: - - ``` - pip install duet - ``` - - ## Note - - duet is not an official Google project. - Platform: UNKNOWN -Requires-Python: >=3.6.0 +Requires-Python: >=3.7.0 Description-Content-Type: text/markdown Provides-Extra: dev_env +License-File: LICENSE + +# duet + +A simple future-based async library for python + +Duet takes inspiration from the amazing [trio](https://trio.readthedocs.io/en/stable/) +library and the [structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) +approach to async programming that it uses. +However, duet differs from trio in two major ways: + +- Instead of a full-blown implementation of asynchronous IO, duet relies on the + `Future` interface for parallelism, and provides a way to run async/await + coroutines around those `Future`s. This is useful if you are using an API that + returns futures, such as RPC libraries like gRPC. The standard `Future` + interface does not implement `__await__` directly, so `Future` instances must + be wrapped in `duet.AwaitableFuture`. + +- duet is re-entrant. At the top level, you run async code by calling + `duet.run(foo)`. Inside `foo` suppose you call a function that has not yet + been fully refactored to be asynchronous, but itself calls `duet.run(bar)`. + Most async libraries, including `trio` and `asyncio`, will raise an exception + if you try to "re-enter" the event loop in this way, but duet allows it. We + have found that this can simplify the process of refactoring code to be + asynchronous because you don't have to completely separate the sync and async + parts of your codebase all at once. + +## Installation + +Install from pypi: + +``` +pip install duet +``` + +## Note + +duet is not an official Google project. + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/dev/requirements.txt new/duet-0.2.7/dev/requirements.txt --- old/duet-0.2.3/dev/requirements.txt 2021-09-17 00:41:00.000000000 +0200 +++ new/duet-0.2.7/dev/requirements.txt 2022-05-04 09:11:02.000000000 +0200 @@ -1,6 +1,6 @@ -black == 20.8b1 +black == 22.3.0 isort == 5.7.* -mypy == 0.782.* +mypy == 0.931.* pylint == 2.10.* -pytest == 5.4.* +pytest == 6.2.* twine == 3.3.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet/_version.py new/duet-0.2.7/duet/_version.py --- old/duet-0.2.3/duet/_version.py 2021-11-02 00:29:05.000000000 +0100 +++ new/duet-0.2.7/duet/_version.py 2022-06-18 03:35:26.000000000 +0200 @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.2.3" +__version__ = "0.2.7" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet/api.py new/duet-0.2.7/duet/api.py --- old/duet-0.2.3/duet/api.py 2021-11-02 00:03:18.000000000 +0100 +++ new/duet-0.2.7/duet/api.py 2022-06-18 03:23:31.000000000 +0200 @@ -39,15 +39,6 @@ T = TypeVar("T") U = TypeVar("U") -try: - asynccontextmanager = contextlib.asynccontextmanager -except AttributeError: - # In python 3.6 asynccontextmanager isn't available from the standard library, so we are using - # equivalent third-party implementation. - from aiocontext import async_contextmanager - - asynccontextmanager = async_contextmanager - def run(func: Callable[..., Awaitable[T]], *args, **kwds) -> T: """Run an async function to completion. @@ -67,10 +58,12 @@ def sync(f: Callable[..., Awaitable[T]]) -> Callable[..., T]: """Decorator that adds a sync version of async function or method.""" + if isinstance(f, classmethod): + raise TypeError(f"duet.sync cannot be applied to classmethod {f.__func__}") sig = inspect.signature(f) first_arg = next(iter(sig.parameters), None) - if first_arg == "self" or first_arg == "cls": + if first_arg == "self": # For class or instance methods, look up the method to call on the given # class or instance. This ensures that we call the right method even it # has been overridden in a subclass. To illustrate, consider: @@ -89,13 +82,15 @@ # function by name at runtime, using getattr. @functools.wraps(f) - def wrapped(self_or_cls, *args, **kw): - method = getattr(self_or_cls, f.__name__, None) + def wrapped(self, *args, **kw): + method = getattr(self, f.__name__) if inspect.ismethod(method) and id(method.__func__) == wrapped_id: - return run(f, self_or_cls, *args, **kw) + return run(f, self, *args, **kw) return run(method, *args, **kw) wrapped_id = id(wrapped) + if getattr(wrapped, "__isabstractmethod__", False): + wrapped.__isabstractmethod__ = False # type: ignore[attr-defined] else: @functools.wraps(f) @@ -138,9 +133,7 @@ async def pmap_async( - func: Callable[[T], Awaitable[U]], - iterable: AnyIterable[T], - limit: Optional[int] = None, + func: Callable[[T], Awaitable[U]], iterable: AnyIterable[T], limit: Optional[int] = None ) -> List[U]: """Apply an async function to every item in iterable. @@ -160,9 +153,7 @@ async def pstarmap_async( - func: Callable[..., Awaitable[U]], - iterable: AnyIterable[Any], - limit: Optional[int] = None, + func: Callable[..., Awaitable[U]], iterable: AnyIterable[Any], limit: Optional[int] = None ) -> List[U]: """Apply an async function to every tuple of args in iterable. @@ -256,14 +247,15 @@ async def sleep(time: float) -> None: """Sleeps for the given length of time in seconds.""" - try: - async with timeout_scope(time): + async with new_scope(timeout=time) as scope: + try: await AwaitableFuture() - except TimeoutError: - pass + except TimeoutError as e: + if e is not scope._timeout_error: + raise -@asynccontextmanager [email protected] async def deadline_scope(deadline: float) -> AsyncIterator[None]: """Enter a scope that will exit when the deadline elapses. @@ -274,7 +266,7 @@ yield -@asynccontextmanager [email protected] async def timeout_scope(timeout: float) -> AsyncIterator[None]: """Enter a scope that will exit when the timeout elapses. @@ -285,7 +277,7 @@ yield -@asynccontextmanager [email protected] async def new_scope( *, deadline: Optional[float] = None, timeout: Optional[float] = None ) -> AsyncIterator["Scope"]: @@ -324,10 +316,11 @@ deadline = scheduler.time() + timeout else: deadline = min(deadline, scheduler.time() + timeout) + scope = Scope(main_task, scheduler, tasks) if deadline is not None: - main_task.push_deadline(deadline) + main_task.push_deadline(deadline, scope._timeout_error) try: - yield Scope(main_task, scheduler, tasks) + yield scope await finish_tasks() except (impl.Interrupt, Exception) as exc: # Interrupt remaining tasks. @@ -358,6 +351,7 @@ self._main_task = main_task self._scheduler = scheduler self._tasks = tasks + self._timeout_error = TimeoutError() def cancel(self) -> None: self._main_task.interrupt(self._main_task, CancelledError()) @@ -494,29 +488,21 @@ self.scope.spawn(func, *args, **kwds) async def pmap_async( - self, - func: Callable[[T], Awaitable[U]], - iterable: AnyIterable[T], + self, func: Callable[[T], Awaitable[U]], iterable: AnyIterable[T] ) -> List[U]: return [x async for x in self.pmap_aiter(func, iterable)] def pmap_aiter( - self, - func: Callable[[T], Awaitable[U]], - iterable: AnyIterable[T], + self, func: Callable[[T], Awaitable[U]], iterable: AnyIterable[T] ) -> AsyncIterator[U]: return pmap_aiter(self.scope, func, self.limiter.throttle(iterable)) async def pstarmap_async( - self, - func: Callable[..., Awaitable[U]], - iterable: AnyIterable[Any], + self, func: Callable[..., Awaitable[U]], iterable: AnyIterable[Any] ) -> List[U]: return [x async for x in self.pstarmap_aiter(func, iterable)] def pstarmap_aiter( - self, - func: Callable[..., Awaitable[U]], - iterable: AnyIterable[Any], + self, func: Callable[..., Awaitable[U]], iterable: AnyIterable[Any] ) -> AsyncIterator[U]: return pstarmap_aiter(self.scope, func, self.limiter.throttle(iterable)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet/api_test.py new/duet-0.2.7/duet/api_test.py --- old/duet-0.2.3/duet/api_test.py 2021-11-02 00:03:18.000000000 +0100 +++ new/duet-0.2.7/duet/api_test.py 2022-06-18 03:22:49.000000000 +0200 @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc import concurrent.futures import inspect import sys @@ -346,6 +347,15 @@ @duet.sync +async def test_sleep_with_timeout(): + start = time.time() + with pytest.raises(TimeoutError): + async with duet.timeout_scope(0.5): + await duet.sleep(10) + assert abs((time.time() - start) - 0.5) < 0.2 + + [email protected] async def test_repeated_sleep(): start = time.time() for _ in range(5): @@ -353,6 +363,16 @@ assert abs((time.time() - start) - 0.5) < 0.2 [email protected] +async def test_repeated_sleep_with_timeout(): + start = time.time() + with pytest.raises(TimeoutError): + async with duet.timeout_scope(0.5): + for _ in range(5): + await duet.sleep(0.2) + assert abs((time.time() - start) - 0.5) < 0.2 + + class TestScope: @duet.sync async def test_run_all(self): @@ -522,3 +542,45 @@ scope.spawn(set_results, f0, f1) await f1 + + +class TestSync: + def test_sync_on_overridden_method(self): + class Foo: + async def foo_async(self, a: int) -> int: + return a * 2 + + foo = duet.sync(foo_async) + + class Bar(Foo): + async def foo_async(self, a: int) -> int: + return a * 3 + + assert Foo().foo(5) == 10 + assert Bar().foo(5) == 15 + + def test_sync_on_abstract_method(self): + class Foo(abc.ABC): + @abc.abstractmethod + async def foo_async(self, a: int) -> int: + pass + + foo = duet.sync(foo_async) + + class Bar(Foo): + async def foo_async(self, a: int) -> int: + return a * 3 + + with pytest.raises(TypeError, match="Can't instantiate abstract class Foo.*foo_async"): + _ = Foo() + assert Bar().foo(5) == 15 + + def test_sync_on_classmethod(self): + with pytest.raises(TypeError, match="duet.sync cannot be applied to classmethod"): + + class _Foo: + @classmethod + async def foo_async(cls, a: int) -> int: + return a * 2 + + foo = duet.sync(foo_async) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet/futuretools.py new/duet-0.2.7/duet/futuretools.py --- old/duet-0.2.3/duet/futuretools.py 2021-08-25 23:23:22.000000000 +0200 +++ new/duet-0.2.7/duet/futuretools.py 2022-05-04 09:11:02.000000000 +0200 @@ -16,7 +16,10 @@ from concurrent.futures import Future from typing import Any, Callable, Generator, Generic, Optional, Tuple, Type, TypeVar -from typing_extensions import Protocol +try: + from typing import Protocol +except ImportError: + from typing_extensions import Protocol # type: ignore[misc] try: import grpc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet/impl.py new/duet-0.2.7/duet/impl.py --- old/duet-0.2.3/duet/impl.py 2021-09-18 01:29:58.000000000 +0200 +++ new/duet-0.2.7/duet/impl.py 2022-06-18 03:23:31.000000000 +0200 @@ -68,10 +68,7 @@ class Task(Generic[T]): def __init__( - self, - awaitable: Awaitable[T], - scheduler: "Scheduler", - main_task: Optional["Task"], + self, awaitable: Awaitable[T], scheduler: "Scheduler", main_task: Optional["Task"] ) -> None: self.scheduler = scheduler self.main_task = main_task @@ -84,8 +81,9 @@ self._result: Optional[T] = None self._error: Optional[Exception] = None self._deadlines: List[DeadlineEntry] = [] - if main_task and main_task.deadline is not None: - self.push_deadline(main_task.deadline) + if main_task and main_task.deadline_entry is not None: + entry = main_task.deadline_entry + self.push_deadline(deadline=entry.deadline, timeout_error=entry.timeout_error) self._generator = awaitable.__await__() # Returns coroutine generator. if isinstance(awaitable, Coroutine): awaitable.cr_frame.f_locals.setdefault(LOCALS_TASK_SCHEDULER, scheduler) @@ -152,10 +150,14 @@ finally: _current_task.reset(token) - def push_deadline(self, deadline: float) -> None: + def push_deadline(self, deadline: float, timeout_error: TimeoutError) -> None: if self._deadlines: - deadline = min(self._deadlines[-1].deadline, deadline) - entry = self.scheduler.add_deadline(self, deadline) + entry = self._deadlines[-1] + if entry.deadline < deadline: + deadline = entry.deadline + timeout_error = entry.timeout_error + entry = DeadlineEntry(self, deadline, timeout_error) + self.scheduler.add_deadline(entry) self._deadlines.append(entry) def pop_deadline(self) -> None: @@ -163,8 +165,8 @@ entry.valid = False @property - def deadline(self) -> Optional[float]: - return self._deadlines[-1].deadline if self._deadlines else None + def deadline_entry(self) -> Optional["DeadlineEntry"]: + return self._deadlines[-1] if self._deadlines else None def interrupt(self, task, error): if self.done or not self.interruptible or self._interrupt: @@ -293,9 +295,10 @@ _counter = itertools.count() - def __init__(self, task: Task, deadline: float): + def __init__(self, task: Task, deadline: float, timeout_error: TimeoutError): self.task = task self.deadline = deadline + self.timeout_error = timeout_error self.count = next(self._counter) self._cmp_val = (deadline, self.count) self.valid = True @@ -345,10 +348,8 @@ def time(self) -> float: return time.time() - def add_deadline(self, task: Task, deadline: float) -> DeadlineEntry: - entry = DeadlineEntry(task, deadline=deadline) + def add_deadline(self, entry: DeadlineEntry) -> None: heapq.heappush(self._deadlines, entry) - return entry def get_next_deadline(self) -> Optional[float]: while self._deadlines: @@ -358,11 +359,11 @@ return self._deadlines[0].deadline return None - def get_deadline_tasks(self, deadline: float) -> Iterator[Task]: + def get_deadline_entries(self, deadline: float) -> Iterator[DeadlineEntry]: while self._deadlines and self._deadlines[0].deadline <= deadline: entry = heapq.heappop(self._deadlines) if entry.valid: - yield entry.task + yield entry def tick(self): """Runs the scheduler ahead by one tick. @@ -397,8 +398,8 @@ except TimeoutError: pass if not ready_tasks: - for task in self.get_deadline_tasks(deadline): - task.interrupt(task, TimeoutError()) + for entry in self.get_deadline_entries(deadline): + entry.task.interrupt(entry.task, entry.timeout_error) ready_tasks = self._ready_tasks.get_all(None) for task in ready_tasks: try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet.egg-info/PKG-INFO new/duet-0.2.7/duet.egg-info/PKG-INFO --- old/duet-0.2.3/duet.egg-info/PKG-INFO 2021-11-02 00:29:39.000000000 +0100 +++ new/duet-0.2.7/duet.egg-info/PKG-INFO 2022-06-18 03:52:56.000000000 +0200 @@ -1,49 +1,52 @@ Metadata-Version: 2.1 Name: duet -Version: 0.2.3 +Version: 0.2.7 Summary: A simple future-based async library for python. Home-page: http://github.com/google/duet Author: The Duet Authors Author-email: [email protected] License: Apache 2 -Description: # duet - - A simple future-based async library for python - - Duet takes inspiration from the amazing [trio](https://trio.readthedocs.io/en/stable/) - library and the [structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) - approach to async programming that it uses. - However, duet differs from trio in two major ways: - - - Instead of a full-blown implementation of asynchronous IO, duet relies on the - `Future` interface for parallelism, and provides a way to run async/await - coroutines around those `Future`s. This is useful if you are using an API that - returns futures, such as RPC libraries like gRPC. The standard `Future` - interface does not implement `__await__` directly, so `Future` instances must - be wrapped in `duet.AwaitableFuture`. - - - duet is re-entrant. At the top level, you run async code by calling - `duet.run(foo)`. Inside `foo` suppose you call a function that has not yet - been fully refactored to be asynchronous, but itself calls `duet.run(bar)`. - Most async libraries, including `trio` and `asyncio`, will raise an exception - if you try to "re-enter" the event loop in this way, but duet allows it. We - have found that this can simplify the process of refactoring code to be - asynchronous because you don't have to completely separate the sync and async - parts of your codebase all at once. - - ## Installation - - Install from pypi: - - ``` - pip install duet - ``` - - ## Note - - duet is not an official Google project. - Platform: UNKNOWN -Requires-Python: >=3.6.0 +Requires-Python: >=3.7.0 Description-Content-Type: text/markdown Provides-Extra: dev_env +License-File: LICENSE + +# duet + +A simple future-based async library for python + +Duet takes inspiration from the amazing [trio](https://trio.readthedocs.io/en/stable/) +library and the [structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) +approach to async programming that it uses. +However, duet differs from trio in two major ways: + +- Instead of a full-blown implementation of asynchronous IO, duet relies on the + `Future` interface for parallelism, and provides a way to run async/await + coroutines around those `Future`s. This is useful if you are using an API that + returns futures, such as RPC libraries like gRPC. The standard `Future` + interface does not implement `__await__` directly, so `Future` instances must + be wrapped in `duet.AwaitableFuture`. + +- duet is re-entrant. At the top level, you run async code by calling + `duet.run(foo)`. Inside `foo` suppose you call a function that has not yet + been fully refactored to be asynchronous, but itself calls `duet.run(bar)`. + Most async libraries, including `trio` and `asyncio`, will raise an exception + if you try to "re-enter" the event loop in this way, but duet allows it. We + have found that this can simplify the process of refactoring code to be + asynchronous because you don't have to completely separate the sync and async + parts of your codebase all at once. + +## Installation + +Install from pypi: + +``` +pip install duet +``` + +## Note + +duet is not an official Google project. + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/duet.egg-info/requires.txt new/duet-0.2.7/duet.egg-info/requires.txt --- old/duet-0.2.3/duet.egg-info/requires.txt 2021-11-02 00:29:39.000000000 +0100 +++ new/duet-0.2.7/duet.egg-info/requires.txt 2022-06-18 03:52:56.000000000 +0200 @@ -1,12 +1,11 @@ -[:python_version <= "3.6"] -asyncio-contextmanager==1.0.1 -contextvars==2.4 +[:python_version <= "3.7"] +typing-extensions==3.10.0 [dev_env] -black==20.8b1 +black==22.3.0 isort==5.7.* -mypy==0.782.* +mypy==0.931.* pylint==2.10.* -pytest==5.4.* +pytest==6.2.* twine==3.3.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/pyproject.toml new/duet-0.2.7/pyproject.toml --- old/duet-0.2.3/pyproject.toml 2021-02-09 01:16:58.000000000 +0100 +++ new/duet-0.2.7/pyproject.toml 2022-05-04 09:11:02.000000000 +0200 @@ -1,6 +1,7 @@ [tool.black] line-length = 100 target_version = ['py37', 'py38'] +skip-magic-trailing-comma = true [tool.isort] profile = 'black' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/requirements.txt new/duet-0.2.7/requirements.txt --- old/duet-0.2.3/requirements.txt 2021-08-25 23:23:22.000000000 +0200 +++ new/duet-0.2.7/requirements.txt 2022-06-18 03:22:49.000000000 +0200 @@ -1,2 +1 @@ -asyncio-contextmanager == 1.0.1; python_version <= '3.6' -contextvars == 2.4; python_version <= '3.6' \ No newline at end of file +typing-extensions == 3.10.0; python_version <= '3.7' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/duet-0.2.3/setup.py new/duet-0.2.7/setup.py --- old/duet-0.2.3/setup.py 2021-08-25 23:23:22.000000000 +0200 +++ new/duet-0.2.7/setup.py 2022-06-18 03:22:49.000000000 +0200 @@ -56,7 +56,7 @@ url="http://github.com/google/duet", author="The Duet Authors", author_email="[email protected]", - python_requires=">=3.6.0", + python_requires=">=3.7.0", install_requires=requirements, extras_require={ "dev_env": dev_requirements,
