Skip to content

Commit

Permalink
Python 3.13 compatibility (#184)
Browse files Browse the repository at this point in the history
* * Python 3.13 compat (new context manager generic argument)
* hopefully fix windows timing test flake by using time.monotonic() instead of time.time()
* remove uv.lock

* skip gevent test on python 3.13

* Some type ignores

* don't build gevent for python 3.13 since we skip that test anyways
  • Loading branch information
freider authored Oct 31, 2024
1 parent fcc10ae commit 75128fe
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 668 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
strategy:
fail-fast: false # run all variants across python versions/os to completion
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
os: ["ubuntu-latest"]
include:
- os: "macos-12" # x86-64
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ venv*
build
dist
*.iml
uv.lock
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ lint = [
]
test = [
"console-ctrl>=0.1.0",
"gevent>=24.2.1",
"gevent>=24.2.1; python_version < '3.13'",
"pytest>=8.3.3",
"pytest-asyncio>=0.24.0",
]
10 changes: 6 additions & 4 deletions src/synchronicity/type_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,16 +578,18 @@ def _translate_annotation_map_types(
if interface == Interface.BLOCKING:
# blocking interface special generic translations:
if origin == collections.abc.AsyncGenerator:
return typing.Generator[mapped_args + (None,)] # type: ignore
return typing.Generator[mapped_args + (None,)] # type: ignore[valid-type,misc]

if origin == contextlib.AbstractAsyncContextManager:
return combined_types.AsyncAndBlockingContextManager[mapped_args] # type: ignore
# TODO: in Python 3.13 mapped_args has a second argument for the exit type of the context
# manager, but we ignore that for now
return combined_types.AsyncAndBlockingContextManager[mapped_args[0]] # type: ignore[valid-type]

if origin == collections.abc.AsyncIterable:
return typing.Iterable[mapped_args] # type: ignore
return typing.Iterable[mapped_args] # type: ignore[valid-type]

if origin == collections.abc.AsyncIterator:
return typing.Iterator[mapped_args] # type: ignore
return typing.Iterator[mapped_args] # type: ignore[valid-type]

if origin == collections.abc.Awaitable:
return mapped_args[0]
Expand Down
48 changes: 24 additions & 24 deletions test/exception_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,70 +51,70 @@ async def f_raises_with_cause():


def test_function_raises_sync(synchronizer):
t0 = time.time()
t0 = time.monotonic()
with pytest.raises(CustomException) as exc:
f_raises_s = synchronizer.create_blocking(f_raises)
f_raises_s()
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None


def test_function_raises_with_cause_sync(synchronizer):
t0 = time.time()
t0 = time.monotonic()
with pytest.raises(CustomException) as exc:
f_raises_s = synchronizer.create_blocking(f_raises_with_cause)
f_raises_s()
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)


def test_function_raises_sync_futures(synchronizer):
t0 = time.time()
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises)
fut = f_raises_s(_future=True)
assert isinstance(fut, concurrent.futures.Future)
assert time.time() - t0 < SLEEP_DELAY
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
fut.result()
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None


def test_function_raises_with_cause_sync_futures(synchronizer):
t0 = time.time()
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises_with_cause)
fut = f_raises_s(_future=True)
assert isinstance(fut, concurrent.futures.Future)
assert time.time() - t0 < SLEEP_DELAY
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
fut.result()
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)


@pytest.mark.asyncio
async def test_function_raises_async(synchronizer):
t0 = time.time()
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises)
coro = f_raises_s.aio()
assert inspect.iscoroutine(coro)
assert time.time() - t0 < SLEEP_DELAY
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None


@pytest.mark.asyncio
async def test_function_raises_with_cause_async(synchronizer):
t0 = time.time()
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises_with_cause)
coro = f_raises_s.aio()
assert inspect.iscoroutine(coro)
assert time.time() - t0 < SLEEP_DELAY
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)


Expand All @@ -124,11 +124,11 @@ async def f_raises_baseexc():


def test_function_raises_baseexc_sync(synchronizer):
t0 = time.time()
t0 = time.monotonic()
with pytest.raises(BaseException) as exc:
f_raises_baseexc_s = synchronizer.create_blocking(f_raises_baseexc)
f_raises_baseexc_s()
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None


Expand All @@ -138,14 +138,14 @@ def f_raises_syncwrap() -> typing.Coroutine[typing.Any, typing.Any, None]:

@pytest.mark.asyncio
async def test_function_raises_async_syncwrap(synchronizer):
t0 = time.time()
t0 = time.monotonic()
f_raises_syncwrap_s = synchronizer.create_blocking(f_raises_syncwrap)
coro = f_raises_syncwrap_s.aio()
assert inspect.iscoroutine(coro)
assert time.time() - t0 < SLEEP_DELAY
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None


Expand All @@ -155,12 +155,12 @@ def f_raises_with_cause_syncwrap() -> typing.Coroutine[typing.Any, typing.Any, N

@pytest.mark.asyncio
async def test_function_raises_with_cause_async_syncwrap(synchronizer):
t0 = time.time()
t0 = time.monotonic()
f_raises_syncwrap_s = synchronizer.create_blocking(f_raises_with_cause_syncwrap)
coro = f_raises_syncwrap_s.aio()
assert inspect.iscoroutine(coro)
assert time.time() - t0 < SLEEP_DELAY
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.time() - t0 < 2 * SLEEP_DELAY
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)
2 changes: 2 additions & 0 deletions test/gevent_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import pytest
import subprocess
import sys
from pathlib import Path


@pytest.mark.skipif(sys.version_info >= (3, 13), reason="gevent seems broken on Python 3.13")
def test_gevent():
# Run it in a separate process because gevent modifies a lot of modules
fn = Path(__file__).parent / "support" / "_gevent.py"
Expand Down
7 changes: 6 additions & 1 deletion test/type_stub_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,12 @@ async def _get_foo(foo: _Foo) -> typing.AsyncContextManager[_Foo]:
print(src)
assert "class __get_foo_spec(typing_extensions.Protocol):" in src
assert " def __call__(self, foo: Foo) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Foo]" in src
assert " async def aio(self, foo: Foo) -> typing.AsyncContextManager[Foo]" in src
if sys.version_info < (3, 13):
expected_original_repr = "typing.AsyncContextManager[Foo]"
else:
# python 3.13 has an exit type generic argument to context managers
expected_original_repr = "typing.AsyncContextManager[Foo, bool | None]"
assert f" async def aio(self, foo: Foo) -> {expected_original_repr}" in src
assert "get_foo: __get_foo_spec"


Expand Down
Loading

0 comments on commit 75128fe

Please sign in to comment.