Skip to content

Commit

Permalink
Remove compatibility codes for Python 3.10 or lower
Browse files Browse the repository at this point in the history
  • Loading branch information
achimnol committed May 3, 2023
1 parent d01ec48 commit d0a213b
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 826 deletions.
19 changes: 3 additions & 16 deletions src/aiotools/compat.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
import asyncio


if hasattr(asyncio, 'get_running_loop'):
get_running_loop = asyncio.get_running_loop
else:
get_running_loop = asyncio.get_event_loop


if hasattr(asyncio, 'all_tasks'):
all_tasks = asyncio.all_tasks
else:
all_tasks = asyncio.Task.all_tasks # type: ignore


if hasattr(asyncio, 'current_task'):
current_task = asyncio.current_task
else:
current_task = asyncio.Task.current_task # type: ignore
get_running_loop = asyncio.get_running_loop
all_tasks = asyncio.all_tasks
current_task = asyncio.current_task
159 changes: 7 additions & 152 deletions src/aiotools/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,168 +8,23 @@
and later.
"""

import abc
import contextlib
import asyncio
import functools
import inspect
from typing import Any, Callable, Iterable, Optional, List
from typing import Iterable, Optional, List

__all__ = [
'AsyncContextManager', 'async_ctx_manager', 'actxmgr',
'aclosing', 'closing_async',
'AsyncContextGroup', 'actxgroup',
'AsyncExitStack',
]


if hasattr(contextlib, 'asynccontextmanager'):
__all__ += ['AsyncExitStack']

AbstractAsyncContextManager = \
contextlib.AbstractAsyncContextManager
AsyncContextManager = \
contextlib._AsyncGeneratorContextManager # type: ignore
AsyncExitStack = contextlib.AsyncExitStack
async_ctx_manager = contextlib.asynccontextmanager
else:
__all__ += ['AsyncContextDecorator', 'actxdecorator']

class AbstractAsyncContextManager(abc.ABC): # type: ignore
"""
The base abstract interface for asynchronous context manager.
"""

async def __aenter__(self):
return self # pragma: no cover

@abc.abstractmethod
async def __aexit__(self, exc_type, exc_value, tb):
return None # pragma: no cover

@classmethod
def __subclasshook__(cls, C):
if cls is AbstractAsyncContextManager:
if (any('__aenter__' in B.__dict__ for B in C.__mro__) and
any('__aexit__' in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented

class AsyncContextDecorator:
"""
Make an asynchronous context manager be used as a decorator function.
"""

def _recreate_cm(self):
return self

def __call__(self, func):
@functools.wraps(func)
async def inner(*args, **kwargs):
async with self._recreate_cm():
return (await func(*args, **kwargs))
return inner

actxdecorator = AsyncContextDecorator

class AsyncContextManager(AsyncContextDecorator, # type: ignore
AbstractAsyncContextManager):
"""
Converts an async-generator function into asynchronous context manager.
"""

def __init__(self, func: Callable[..., Any], args, kwargs):
if not inspect.isasyncgenfunction(func):
raise RuntimeError('Context manager function must be '
'an async-generator')
self._agen = func(*args, **kwargs)
self.func = func
self.args = args
self.kwargs = kwargs

def _recreate_cm(self):
return self.__class__(self.func, self.args, self.kwargs)

async def __aenter__(self):
try:
return (await self._agen.__anext__())
except StopAsyncIteration:
# The generator should yield at least once.
raise RuntimeError("async-generator didn't yield") from None

async def __aexit__(self, exc_type, exc_value, tb):
if exc_type is None:
# This is the normal path when the context body
# did not raise any exception.
try:
await self._agen.__anext__()
except StopAsyncIteration:
return
else:
# The generator has already yielded,
# no more yields are allowed.
raise RuntimeError("async-generator didn't stop") from None
else:
# The context body has raised an exception.
if exc_value is None:
# Ensure exc_value is a valid Exception.
exc_value = exc_type()
try:
# Throw the catched exception into the generator,
# so that it can handle as it wants.
await self._agen.athrow(exc_type, exc_value, tb)
# Here the generator should have finished!
# (i.e., it should not yield again in except/finally blocks!)
raise RuntimeError("async-generator didn't stop after athrow()")
# NOTE for PEP-479
# StopAsyncIteration raised inside the context body
# is converted to RuntimeError.
# In the standard library's contextlib.py, there is
# an extra except clause to catch StopIteration here,
# but this is unnecessary now.
except StopAsyncIteration as exc_new_value:
return exc_new_value is not exc_value
except RuntimeError as exc_new_value:
# When the context body did not catch the exception, re-raise.
if exc_new_value is exc_value:
return False
# When the context body's exception handler raises
# another chained exception, re-raise.
if isinstance(exc_value, (StopIteration, StopAsyncIteration)):
if exc_new_value.__cause__ is exc_value:
return False
# If this is a purely new exception, raise the new one.
raise
except (BaseException, asyncio.CancelledError) as exc:
if exc is not exc_value:
raise

def async_ctx_manager(func):
@functools.wraps(func)
def helper(*args, **kwargs):
return AsyncContextManager(func, args, kwargs)
return helper


class aclosing:
"""
An analogy to :func:`contextlib.closing` for async generators.
The motivation has been proposed by:
* https://github.com/njsmith/async_generator
* https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-\
in-a-post-asyncawait-world/#cleanup-in-generators-and-async-generators
* https://www.python.org/dev/peps/pep-0533/
"""

def __init__(self, thing):
self.thing = thing

async def __aenter__(self):
return self.thing

async def __aexit__(self, *args):
await self.thing.aclose()
AbstractAsyncContextManager = contextlib.AbstractAsyncContextManager
AsyncContextManager = contextlib._AsyncGeneratorContextManager
AsyncExitStack = contextlib.AsyncExitStack
async_ctx_manager = contextlib.asynccontextmanager
aclosing = contextlib.aclosing


class closing_async:
Expand Down
30 changes: 7 additions & 23 deletions src/aiotools/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ async def myserver(loop, pidx, args):
"""

import asyncio
from contextlib import (
AbstractContextManager, ContextDecorator,
)
try:
import contextvars
_cv_available = True
except ImportError:
_cv_available = False
import functools
import inspect
import logging
Expand All @@ -40,6 +32,10 @@ async def myserver(loop, pidx, args):
import struct
import sys
import threading
from contextlib import (
AbstractContextManager, ContextDecorator,
)
from contextvars import ContextVar
from typing import (
Any,
Callable,
Expand All @@ -55,11 +51,6 @@ async def myserver(loop, pidx, args):
from .context import AbstractAsyncContextManager
from .fork import AbstractChildProcess, afork, _has_pidfd

try:
from typing import Literal
except ImportError:
from typing_extensions import Literal # type: ignore # noqa

__all__ = (
'main',
'start_server',
Expand All @@ -71,12 +62,7 @@ async def myserver(loop, pidx, args):

log = logging.getLogger(__name__)

if _cv_available:
process_index: 'contextvars.ContextVar[int]'
process_index = contextvars.ContextVar('process_index')
else:
# Unsupported in Python 3.6
process_index = None # type: ignore # noqa
process_index: ContextVar[int] = ContextVar('process_index')


class InterruptedBySignal(BaseException):
Expand Down Expand Up @@ -298,8 +284,7 @@ def _worker_main(
setup_child_watcher(loop)
interrupted = asyncio.Event()
ctx = worker_actxmgr(loop, proc_idx, args)
if _cv_available:
process_index.set(proc_idx)
process_index.set(proc_idx)
forever_future = loop.create_future()

def handle_stop_signal(signum):
Expand Down Expand Up @@ -360,8 +345,7 @@ def _extra_main(
args: Sequence[Any],
) -> int:
interrupted = threading.Event()
if _cv_available:
process_index.set(proc_idx)
process_index.set(proc_idx)

# Since signals only work for the main thread in Python,
# extra processes in use_threading=True mode should check
Expand Down
38 changes: 11 additions & 27 deletions src/aiotools/taskgroup/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
import asyncio
from .base import TaskGroup, TaskGroupError, current_taskgroup
from .persistent import PersistentTaskGroup, current_ptaskgroup
from .types import MultiError

from .types import MultiError, TaskGroupError

if hasattr(asyncio, 'TaskGroup'):
from . import persistent
from .base import * # noqa
from .persistent import * # noqa
__all__ = [
"MultiError",
"TaskGroup",
"TaskGroupError",
"current_taskgroup",
*persistent.__all__,
]
else:
from . import persistent_compat
from .base_compat import * # type: ignore # noqa
from .persistent_compat import * # type: ignore # noqa
from .base_compat import has_contextvars
__all__ = [ # type: ignore # noqa
"MultiError",
"TaskGroup",
"TaskGroupError",
*persistent_compat.__all__,
]
if has_contextvars:
__all__.append("current_taskgroup")
__all__ = (
"MultiError",
"TaskGroup",
"TaskGroupError",
"current_taskgroup",
"PersistentTaskGroup",
"current_ptaskgroup",
)

0 comments on commit d0a213b

Please sign in to comment.