Skip to content

Commit

Permalink
Drop Python 3.6 (#307)
Browse files Browse the repository at this point in the history
Python 3.6 is now EOL, and we had to do a lot of work to support it, so this cleans a lot up.
  • Loading branch information
Andrew-Chen-Wang committed Dec 27, 2021
1 parent 6689c0a commit 02fecb6
Show file tree
Hide file tree
Showing 12 changed files with 55 additions and 189 deletions.
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Expand Up @@ -14,7 +14,6 @@ jobs:
fail-fast: false
matrix:
python-version:
- 3.6
- 3.7
- 3.8
- 3.9
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Expand Up @@ -3,13 +3,13 @@ repos:
rev: v2.9.0
hooks:
- id: pyupgrade
args: ["--py36-plus"]
args: ["--py37-plus"]

- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
args: ["--target-version=py36"]
args: ["--target-version=py37"]

- repo: https://github.com/pycqa/isort
rev: 5.7.0
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Expand Up @@ -96,7 +96,7 @@ file handles for incoming POST bodies).
Dependencies
------------

``asgiref`` requires Python 3.6 or higher.
``asgiref`` requires Python 3.7 or higher.


Contributing
Expand Down
61 changes: 0 additions & 61 deletions asgiref/_pep562.py

This file was deleted.

16 changes: 0 additions & 16 deletions asgiref/compatibility.py
@@ -1,6 +1,5 @@
import asyncio
import inspect
import sys


def is_double_callable(application):
Expand Down Expand Up @@ -46,18 +45,3 @@ def guarantee_single_callable(application):
if is_double_callable(application):
application = double_to_single_callable(application)
return application


if sys.version_info >= (3, 7):
# these were introduced in 3.7
get_running_loop = asyncio.get_running_loop
run_future = asyncio.run
create_task = asyncio.create_task
current_task = asyncio.current_task
else:
# marked as deprecated in 3.10, did not exist before 3.7
get_running_loop = asyncio.get_event_loop
run_future = asyncio.ensure_future
# does nothing, this is fine for <3.7
create_task = lambda task: task
current_task = asyncio.Task.current_task
112 changes: 38 additions & 74 deletions asgiref/sync.py
@@ -1,4 +1,5 @@
import asyncio.coroutines
import contextvars
import functools
import inspect
import os
Expand All @@ -9,15 +10,9 @@
from concurrent.futures import Future, ThreadPoolExecutor
from typing import Any, Callable, Dict, Optional, overload

from .compatibility import current_task, get_running_loop
from .current_thread_executor import CurrentThreadExecutor
from .local import Local

if sys.version_info >= (3, 7):
import contextvars
else:
contextvars = None


def _restore_context(context):
# Check for changes in contextvars, and set them to the current
Expand Down Expand Up @@ -55,8 +50,6 @@ class ThreadSensitiveContext:
In Python 3.7+, the ThreadSensitiveContext() context manager may be used to
specify a thread pool per context.
In Python 3.6, usage of this context manager has no effect.
This context manager is re-entrant, so only the outer-most call to
ThreadSensitiveContext will set the context.
Expand All @@ -70,32 +63,22 @@ class ThreadSensitiveContext:
def __init__(self):
self.token = None

if contextvars:

async def __aenter__(self):
try:
SyncToAsync.thread_sensitive_context.get()
except LookupError:
self.token = SyncToAsync.thread_sensitive_context.set(self)

return self

async def __aexit__(self, exc, value, tb):
if not self.token:
return
async def __aenter__(self):
try:
SyncToAsync.thread_sensitive_context.get()
except LookupError:
self.token = SyncToAsync.thread_sensitive_context.set(self)

executor = SyncToAsync.context_to_thread_executor.pop(self, None)
if executor:
executor.shutdown()
SyncToAsync.thread_sensitive_context.reset(self.token)
return self

else:
async def __aexit__(self, exc, value, tb):
if not self.token:
return

async def __aenter__(self):
return self

async def __aexit__(self, exc, value, tb):
pass
executor = SyncToAsync.context_to_thread_executor.pop(self, None)
if executor:
executor.shutdown()
SyncToAsync.thread_sensitive_context.reset(self.token)


class AsyncToSync:
Expand Down Expand Up @@ -135,7 +118,7 @@ def __init__(self, awaitable, force_new_loop=False):
self.main_event_loop = None
else:
try:
self.main_event_loop = get_running_loop()
self.main_event_loop = asyncio.get_running_loop()
except RuntimeError:
# There's no event loop in this thread. Look for the threadlocal if
# we're inside SyncToAsync
Expand All @@ -154,7 +137,7 @@ def __init__(self, awaitable, force_new_loop=False):
def __call__(self, *args, **kwargs):
# You can't call AsyncToSync from a thread with a running event loop
try:
event_loop = get_running_loop()
event_loop = asyncio.get_running_loop()
except RuntimeError:
pass
else:
Expand All @@ -164,12 +147,9 @@ def __call__(self, *args, **kwargs):
"just await the async function directly."
)

if contextvars is not None:
# Wrapping context in list so it can be reassigned from within
# `main_wrap`.
context = [contextvars.copy_context()]
else:
context = None
# Wrapping context in list so it can be reassigned from within
# `main_wrap`.
context = [contextvars.copy_context()]

# Make a future for the return information
call_result = Future()
Expand Down Expand Up @@ -218,8 +198,7 @@ def __call__(self, *args, **kwargs):
del self.executors.current
if old_current_executor:
self.executors.current = old_current_executor
if contextvars is not None:
_restore_context(context[0])
_restore_context(context[0])

# Wait for results from the future.
return call_result.result()
Expand All @@ -235,10 +214,7 @@ def _run_event_loop(self, loop, coro):
try:
# mimic asyncio.run() behavior
# cancel unexhausted async generators
if sys.version_info >= (3, 7, 0):
tasks = asyncio.all_tasks(loop)
else:
tasks = asyncio.Task.all_tasks(loop)
tasks = asyncio.all_tasks(loop)
for task in tasks:
task.cancel()

Expand Down Expand Up @@ -299,8 +275,7 @@ async def main_wrap(
finally:
del self.launch_map[current_task]

if context is not None:
context[0] = contextvars.copy_context()
context[0] = contextvars.copy_context()


class SyncToAsync:
Expand Down Expand Up @@ -345,21 +320,15 @@ class SyncToAsync:

# Maintain a contextvar for the current execution context. Optionally used
# for thread sensitive mode.
if sys.version_info >= (3, 7):
thread_sensitive_context: "contextvars.ContextVar[str]" = (
contextvars.ContextVar("thread_sensitive_context")
)
else:
thread_sensitive_context: None = None
thread_sensitive_context: "contextvars.ContextVar[str]" = contextvars.ContextVar(
"thread_sensitive_context"
)

# Contextvar that is used to detect if the single thread executor
# would be awaited on while already being used in the same context
if sys.version_info >= (3, 7):
deadlock_context: "contextvars.ContextVar[bool]" = contextvars.ContextVar(
"deadlock_context"
)
else:
deadlock_context: None = None
deadlock_context: "contextvars.ContextVar[bool]" = contextvars.ContextVar(
"deadlock_context"
)

# Maintaining a weak reference to the context ensures that thread pools are
# erased once the context goes out of scope. This terminates the thread pool.
Expand Down Expand Up @@ -388,7 +357,7 @@ def __init__(
pass

async def __call__(self, *args, **kwargs):
loop = get_running_loop()
loop = asyncio.get_running_loop()

# Work out what thread to run the code in
if self._thread_sensitive:
Expand Down Expand Up @@ -422,14 +391,11 @@ async def __call__(self, *args, **kwargs):
# Use the passed in executor, or the loop's default if it is None
executor = self._executor

if contextvars is not None:
context = contextvars.copy_context()
child = functools.partial(self.func, *args, **kwargs)
func = context.run
args = (child,)
kwargs = {}
else:
func = self.func
context = contextvars.copy_context()
child = functools.partial(self.func, *args, **kwargs)
func = context.run
args = (child,)
kwargs = {}

try:
# Run the code in the right thread
Expand All @@ -448,8 +414,7 @@ async def __call__(self, *args, **kwargs):
ret = await asyncio.wait_for(future, timeout=None)

finally:
if contextvars is not None:
_restore_context(context)
_restore_context(context)
if self.deadlock_context:
self.deadlock_context.set(False)

Expand Down Expand Up @@ -497,12 +462,11 @@ def thread_handler(self, loop, source_task, exc_info, func, *args, **kwargs):
@staticmethod
def get_current_task():
"""
Cross-version implementation of asyncio.current_task()
Returns None if there is no task.
Implementation of asyncio.current_task()
that returns None if there is no task.
"""
try:
return current_task()
return asyncio.current_task()
except RuntimeError:
return None

Expand Down
15 changes: 1 addition & 14 deletions asgiref/timeout.py
Expand Up @@ -10,8 +10,6 @@
from types import TracebackType
from typing import Any, Optional, Type

from .compatibility import current_task as asyncio_current_task


class timeout:
"""timeout context manager.
Expand Down Expand Up @@ -83,7 +81,7 @@ def _do_enter(self) -> "timeout":
if self._timeout is None:
return self

self._task = current_task(self._loop)
self._task = asyncio.current_task(self._loop)
if self._task is None:
raise RuntimeError(
"Timeout context manager should be used " "inside a task"
Expand Down Expand Up @@ -112,14 +110,3 @@ def _cancel_task(self) -> None:
if self._task is not None:
self._task.cancel()
self._cancelled = True


def current_task(loop: asyncio.AbstractEventLoop) -> "Optional[asyncio.Task[Any]]":
task = asyncio_current_task(loop=loop)
if task is None:
# this should be removed, tokio must use register_task and family API
fn = getattr(loop, "current_task", None)
if fn is not None:
task = fn()

return task
5 changes: 0 additions & 5 deletions asgiref/typing.py
Expand Up @@ -13,8 +13,6 @@
Union,
)

from asgiref._pep562 import pep562

if sys.version_info >= (3, 8):
from typing import Literal, Protocol, TypedDict
else:
Expand Down Expand Up @@ -282,6 +280,3 @@ def __getattr__(name: str) -> Any:

def __dir__() -> List[str]:
return sorted(list(__all__) + list(__deprecated__.keys()))


pep562(__name__)

0 comments on commit 02fecb6

Please sign in to comment.