Skip to content

Commit

Permalink
[WIP] Python 3.12 (#27)
Browse files Browse the repository at this point in the history
* some initial attempts to be able to run tests on 3.12
* trying to workaround runtime issue
* added 3.12 to supported versions
* changelog, warning that Tornado support is going to be removed in future
  • Loading branch information
zmumi authored May 4, 2024
1 parent cf0cc0f commit f26c868
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tox-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
python: [ '3.7', '3.8', '3.9', '3.10', '3.11', '3.12' ]

steps:
- uses: actions/checkout@v2
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
1.2.0
-----

* Added support for Python 3.12
* Added warning that support for Tornado is deprecated and will be removed in future
(it causes more and more hacks/workarounds while Tornado importance is diminishing).

1.1.5
-----

Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ For configuration options see `Configurability`_.
You can use ``memoize`` with both `asyncio <https://docs.python.org/3/library/asyncio.html>`_
and `Tornado <https://github.com/tornadoweb/tornado>`_ - please see the appropriate example:

.. warning::
Support for `Tornado <https://github.com/tornadoweb/tornado>`_ is planned to be removed in the future.

asyncio
~~~~~~~

Expand Down
83 changes: 82 additions & 1 deletion memoize/coerced.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datetime
import importlib.util
import logging
import sys

from memoize.memoize_configuration import force_asyncio

Expand Down Expand Up @@ -47,9 +48,89 @@ def _timeout_error_type():

logger.info('Using asyncio instead of torando')

# this backported version of `wait_for` is taken from Python 3.11
# and allows to continue having these `coerced` functions working (they are at least partially based on hacks)
# in general we need to drop tornado support either way (so this temporary solution would be gone either way)
async def wait_for(fut, timeout):
"""Wait for the single Future or coroutine to complete, with timeout.
Coroutine will be wrapped in Task.
Returns result of the Future or coroutine. When a timeout occurs,
it cancels the task and raises TimeoutError. To avoid the task
cancellation, wrap it in shield().
If the wait is cancelled, the task is also cancelled.
This function is a coroutine.
"""

from asyncio import events, ensure_future, exceptions
from asyncio.tasks import _cancel_and_wait, _release_waiter
import functools
loop = events.get_running_loop()

if timeout is None:
return await fut

if timeout <= 0:
fut = ensure_future(fut, loop=loop)

if fut.done():
return fut.result()

await _cancel_and_wait(fut)
try:
return fut.result()
except exceptions.CancelledError as exc:
raise exceptions.TimeoutError() from exc

waiter = loop.create_future()
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
cb = functools.partial(_release_waiter, waiter)

fut = ensure_future(fut, loop=loop)
fut.add_done_callback(cb)

try:
# wait until the future completes or the timeout
try:
await waiter
except exceptions.CancelledError:
if fut.done():
return fut.result()
else:
fut.remove_done_callback(cb)
# We must ensure that the task is not running
# after wait_for() returns.
# See https://bugs.python.org/issue32751
await _cancel_and_wait(fut)
raise

if fut.done():
return fut.result()
else:
fut.remove_done_callback(cb)
# We must ensure that the task is not running
# after wait_for() returns.
# See https://bugs.python.org/issue32751
await _cancel_and_wait(fut)
# In case task cancellation failed with some
# exception, we should re-raise it
# See https://bugs.python.org/issue40607
try:
return fut.result()
except exceptions.CancelledError as exc:
raise exceptions.TimeoutError() from exc
finally:
timeout_handle.cancel()

# ignore for mypy as types are resolved in runtime
def _apply_timeout(method_timeout: datetime.timedelta, future: asyncio.Future) -> asyncio.Future: # type: ignore
return asyncio.wait_for(future, method_timeout.total_seconds())
if sys.version_info >= (3, 12, 0):
return wait_for(future, method_timeout.total_seconds())
else:
return asyncio.wait_for(future, method_timeout.total_seconds())


def _call_later(delay: datetime.timedelta, callback):
Expand Down
8 changes: 3 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
from setuptools import setup


def prepare_description():
Expand All @@ -14,7 +11,7 @@ def prepare_description():

setup(
name='py-memoize',
version='1.1.5',
version='1.2.0',
author='Michal Zmuda',
author_email='[email protected]',
url='https://github.com/DreamLab/memoize',
Expand Down Expand Up @@ -42,6 +39,7 @@ def prepare_description():
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Framework :: AsyncIO',
'Intended Audience :: Developers'
]
Expand Down
5 changes: 4 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ skipdist = True

# py-{asyncio,tornado} are added for GitHub Actions (where we have only one interpreter at the time)
# py{35,36,37,38,39}-{asyncio,tornado} are added for development purposes (where one has multiple interpreters)
envlist = py-{asyncio,tornado},py{37,38,39,310,311}-{asyncio,tornado},coverage-py310,mypy-py310
envlist = py-{asyncio,tornado},py{37,38,39,310,311,312}-{asyncio,tornado},coverage-py310,mypy-py310

[testenv]
setenv =
Expand All @@ -19,6 +19,9 @@ commands =
coverage-py37: coverage report
mypy-py37: mypy memoize
deps =
setuptools # for setup.py to work (distutils is removed from Python 3.12)
tornado: backports.ssl-match-hostname # for tornado-based tests to run with Python 3.12
asyncio: backports.ssl-match-hostname # unit tests that run for asyncio are still based on tornado.testing
asyncio: tornado>4,<5
tornado: tornado>4,<5
coverage: coverage
Expand Down

0 comments on commit f26c868

Please sign in to comment.