mode-streaming 0.5.1__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.github/workflows/tests.yml +1 -3
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/PKG-INFO +2 -1
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/loops.py +28 -2
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/objects.py +37 -6
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/PKG-INFO +2 -1
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/scm_version.json +2 -2
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/pyproject.toml +1 -0
- mode_streaming-0.5.3/tests/unit/utils/test_loops.py +94 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/utils/test_objects.py +32 -0
- mode_streaming-0.5.1/tests/unit/utils/test_loops.py +0 -48
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.editorconfig +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.github/ISSUE_TEMPLATE.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.github/workflows/deploy-docs.yml +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.github/workflows/publish.yml +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.gitignore +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/.pre-commit-config.yaml +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/AUTHORS +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/CHANGELOG.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/CODE_OF_CONDUCT.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/CONTRIBUTING.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/LICENSE +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/README.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/changelog.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/contributing.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/creating-service.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/example-service.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/example-webapp.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/index.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.debug.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.exceptions.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.locals.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.loop.eventlet.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.loop.gevent.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.loop.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.loop.uvloop.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.proxy.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.services.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.signals.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.supervisors.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.threads.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.timers.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.types.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.types.services.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.types.signals.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.types.supervisors.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.aiter.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.collections.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.compat.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.contexts.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.cron.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.futures.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.graphs.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.imports.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.locals.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.locks.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.logging.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.loops.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.mocks.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.objects.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.queues.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.text.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.times.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.tracebacks.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.trees.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.types.graphs.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.utils.types.trees.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/docs/references/mode.worker.md +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/examples/service.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/examples/webapp.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mkdocs.yml +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/debug.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/exceptions.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/locals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/loop/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/loop/_gevent_loop.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/loop/eventlet.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/loop/gevent.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/loop/uvloop.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/proxy.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/py.typed +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/services.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/signals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/supervisors.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/threads.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/timers.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/types/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/types/services.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/types/signals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/types/supervisors.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/aiter.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/collections.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/compat.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/contexts.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/cron.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/futures.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/graphs/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/graphs/formatter.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/graphs/graph.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/imports.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/locals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/locks.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/logging.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/mocks.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/queues.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/text.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/times.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/tracebacks.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/trees.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/types/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/types/graphs.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/utils/types/trees.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode/worker.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/SOURCES.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/dependency_links.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/requires.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/scm_file_list.json +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/mode_streaming.egg-info/top_level.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/requirements-docs.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/requirements-tests.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/requirements.txt +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/build-docs.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/build.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/bump.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/clean.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/format.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/lint.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/scripts/tests.sh +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/setup.cfg +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/conftest.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/test_mode.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/test_proxy.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/test_service.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/test_signals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/test_supervisors.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/test_timers.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_aiter.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_collections.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_compat.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_contexts.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_futures.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_locals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_queues.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_text.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_times.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_tracebacks.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/functional/utils/test_trees.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/test_debug.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/test_locals.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/test_services.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/test_supervisors.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/test_threads.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/test_worker.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/utils/__init__.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/utils/test_cron.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/utils/test_imports.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/utils/test_locks.py +0 -0
- {mode_streaming-0.5.1 → mode_streaming-0.5.3}/tests/unit/utils/test_logging.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mode-streaming
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: AsyncIO Service-based programming
|
|
5
5
|
Author-email: Ask Solem Hoel <ask@robinhood.com>
|
|
6
6
|
Maintainer-email: William Barnhart <williambbarnhart@gmail.com>
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
23
|
Classifier: Operating System :: POSIX
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"""Event loop utilities."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import threading
|
|
4
5
|
from typing import Any, Callable, Optional
|
|
5
6
|
|
|
6
7
|
__all__ = ["call_asap", "clone_loop", "get_event_loop"]
|
|
7
8
|
|
|
9
|
+
#: Per-thread cache of the loop resolved by :func:`get_event_loop` for when
|
|
10
|
+
#: there is no *running* loop. ``threading.local`` gives every thread its
|
|
11
|
+
#: own attribute storage for free (no cross-thread leakage, no explicit
|
|
12
|
+
#: cleanup required when a thread exits) -- required for correctness, not
|
|
13
|
+
#: just performance, since Mode creates and runs a dedicated event loop per
|
|
14
|
+
#: :class:`~mode.threads.ServiceThread` worker thread.
|
|
15
|
+
_current_loop = threading.local()
|
|
16
|
+
|
|
8
17
|
|
|
9
18
|
def get_event_loop() -> asyncio.AbstractEventLoop:
|
|
10
19
|
"""Return the current event loop, creating one if necessary.
|
|
@@ -20,17 +29,34 @@ def get_event_loop() -> asyncio.AbstractEventLoop:
|
|
|
20
29
|
loop -- e.g. at import time, when agents/services are declared at module
|
|
21
30
|
level -- so it needs the historical "get or create" semantics. This
|
|
22
31
|
restores them in a way that works across Python 3.9-3.14.
|
|
32
|
+
|
|
33
|
+
Whether a loop is currently *running* can change on every call (that's
|
|
34
|
+
the whole point of an event loop), so :func:`asyncio.get_running_loop`
|
|
35
|
+
must always be re-checked and can't be cached. When nothing is running,
|
|
36
|
+
though, the resolved loop is stable for the lifetime of the thread that
|
|
37
|
+
resolved it, so it's cached in thread-local storage: once a thread has
|
|
38
|
+
created (or found) its loop, later calls on that thread return the
|
|
39
|
+
cached reference directly instead of re-running the
|
|
40
|
+
``asyncio.get_event_loop()``-raises-then-create dance every time.
|
|
23
41
|
"""
|
|
24
42
|
try:
|
|
25
43
|
return asyncio.get_running_loop()
|
|
26
44
|
except RuntimeError:
|
|
27
45
|
pass
|
|
46
|
+
|
|
47
|
+
loop: Optional[asyncio.AbstractEventLoop] = getattr(
|
|
48
|
+
_current_loop, "loop", None
|
|
49
|
+
)
|
|
50
|
+
if loop is not None and not loop.is_closed():
|
|
51
|
+
return loop
|
|
52
|
+
|
|
28
53
|
try:
|
|
29
|
-
|
|
54
|
+
loop = asyncio.get_event_loop()
|
|
30
55
|
except RuntimeError:
|
|
31
56
|
loop = asyncio.new_event_loop()
|
|
32
57
|
asyncio.set_event_loop(loop)
|
|
33
|
-
|
|
58
|
+
_current_loop.loop = loop
|
|
59
|
+
return loop
|
|
34
60
|
|
|
35
61
|
|
|
36
62
|
def _is_unix_loop(loop: asyncio.AbstractEventLoop) -> bool:
|
|
@@ -366,11 +366,18 @@ def annotations(
|
|
|
366
366
|
)
|
|
367
367
|
)
|
|
368
368
|
|
|
369
|
-
#
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
# NOTE: Return the resolved field types as-is. `_resolve_refs`/`eval_type`
|
|
370
|
+
# above already turned string/ForwardRef annotations into real types, so
|
|
371
|
+
# nothing further is needed here. Do NOT run `_normalize_forwardref` over
|
|
372
|
+
# the result: it rewrites any type whose ``__qualname__`` contains
|
|
373
|
+
# "<locals>" (i.e. every class defined inside a function) down to a bare
|
|
374
|
+
# ``__name__`` *string*, destroying the actual class object. Callers such
|
|
375
|
+
# as faust.Record rely on identity of the returned types (e.g. matching a
|
|
376
|
+
# field type against a custom coercion mapping), which silently breaks
|
|
377
|
+
# when the type is replaced by its name string. `_normalize_forwardref`
|
|
378
|
+
# remains available as a comparison helper for callers/tests that want a
|
|
379
|
+
# canonical, hashable-by-name form.
|
|
380
|
+
return fields, defaults
|
|
374
381
|
|
|
375
382
|
|
|
376
383
|
def local_annotations(
|
|
@@ -453,6 +460,7 @@ def eval_type(
|
|
|
453
460
|
"""
|
|
454
461
|
invalid_types = invalid_types or set()
|
|
455
462
|
alias_types = alias_types or {}
|
|
463
|
+
original_str = typ if isinstance(typ, str) else None
|
|
456
464
|
if isinstance(typ, str):
|
|
457
465
|
typ = ForwardRef(typ)
|
|
458
466
|
# `typing._eval_type` (imported above as `_eval_type`) already resolves
|
|
@@ -465,7 +473,30 @@ def eval_type(
|
|
|
465
473
|
# attributes are not part of any stable API and are no longer present at
|
|
466
474
|
# all on Python 3.14's ForwardRef, which raised AttributeError. mode's
|
|
467
475
|
# floor is Python 3.9, well past the versions that workaround targeted).
|
|
468
|
-
|
|
476
|
+
if isinstance(typ, ForwardRef):
|
|
477
|
+
try:
|
|
478
|
+
typ = _eval_type(typ, globalns, localns)
|
|
479
|
+
except TypeError:
|
|
480
|
+
# `_eval_type` runs the forward ref through `typing._type_check`,
|
|
481
|
+
# which rejects `ClassVar[...]` with "is not valid as type
|
|
482
|
+
# argument". But a *string* ClassVar annotation is legitimate --
|
|
483
|
+
# `from __future__ import annotations` stringizes every
|
|
484
|
+
# annotation, ClassVars included -- and callers (see
|
|
485
|
+
# `_resolve_refs` + `skip_classvar`) still want to see it so they
|
|
486
|
+
# can drop it. Evaluate the forward arg directly and accept it
|
|
487
|
+
# when it is a ClassVar; otherwise re-raise the real error.
|
|
488
|
+
src = (
|
|
489
|
+
original_str
|
|
490
|
+
if original_str is not None
|
|
491
|
+
else typ.__forward_arg__
|
|
492
|
+
)
|
|
493
|
+
evaluated = eval(src, globalns, localns) # noqa: S307
|
|
494
|
+
if _is_class_var(evaluated):
|
|
495
|
+
typ = evaluated
|
|
496
|
+
else:
|
|
497
|
+
raise
|
|
498
|
+
else:
|
|
499
|
+
typ = _eval_type(typ, globalns, localns)
|
|
469
500
|
if typ in invalid_types:
|
|
470
501
|
raise InvalidAnnotation(typ)
|
|
471
502
|
return alias_types.get(typ, typ)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mode-streaming
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: AsyncIO Service-based programming
|
|
5
5
|
Author-email: Ask Solem Hoel <ask@robinhood.com>
|
|
6
6
|
Maintainer-email: William Barnhart <williambbarnhart@gmail.com>
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
23
|
Classifier: Operating System :: POSIX
|
|
@@ -40,6 +40,7 @@ classifiers = [
|
|
|
40
40
|
"Programming Language :: Python :: 3.11",
|
|
41
41
|
"Programming Language :: Python :: 3.12",
|
|
42
42
|
"Programming Language :: Python :: 3.13",
|
|
43
|
+
"Programming Language :: Python :: 3.14",
|
|
43
44
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
44
45
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
45
46
|
"Operating System :: POSIX",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import threading
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from mode.utils.loops import get_event_loop
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_get_event_loop__returns_running_loop_when_running():
|
|
9
|
+
async def main():
|
|
10
|
+
return get_event_loop()
|
|
11
|
+
|
|
12
|
+
running = asyncio.new_event_loop()
|
|
13
|
+
try:
|
|
14
|
+
got = running.run_until_complete(main())
|
|
15
|
+
assert got is running
|
|
16
|
+
finally:
|
|
17
|
+
running.close()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_get_event_loop__reuses_current_loop_when_set():
|
|
21
|
+
loop = asyncio.new_event_loop()
|
|
22
|
+
asyncio.set_event_loop(loop)
|
|
23
|
+
try:
|
|
24
|
+
assert get_event_loop() is loop
|
|
25
|
+
finally:
|
|
26
|
+
asyncio.set_event_loop(None)
|
|
27
|
+
loop.close()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_get_event_loop__creates_loop_when_none_set():
|
|
31
|
+
# Emulates Python 3.14, where asyncio.get_event_loop() no longer creates
|
|
32
|
+
# a loop implicitly and raises RuntimeError instead.
|
|
33
|
+
asyncio.set_event_loop(None)
|
|
34
|
+
loop = get_event_loop()
|
|
35
|
+
try:
|
|
36
|
+
assert isinstance(loop, asyncio.AbstractEventLoop)
|
|
37
|
+
assert asyncio.get_event_loop() is loop
|
|
38
|
+
finally:
|
|
39
|
+
asyncio.set_event_loop(None)
|
|
40
|
+
loop.close()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_get_event_loop__is_idempotent_without_running_loop():
|
|
44
|
+
asyncio.set_event_loop(None)
|
|
45
|
+
first = get_event_loop()
|
|
46
|
+
try:
|
|
47
|
+
assert get_event_loop() is first
|
|
48
|
+
finally:
|
|
49
|
+
asyncio.set_event_loop(None)
|
|
50
|
+
first.close()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_get_event_loop__caches_loop_without_repeated_lookup():
|
|
54
|
+
# The whole point of the thread-local cache: once a loop has been
|
|
55
|
+
# resolved for a thread, later calls on that thread must not re-invoke
|
|
56
|
+
# (and potentially re-raise/re-catch RuntimeError from)
|
|
57
|
+
# asyncio.get_event_loop() again.
|
|
58
|
+
asyncio.set_event_loop(None)
|
|
59
|
+
first = get_event_loop()
|
|
60
|
+
try:
|
|
61
|
+
with patch(
|
|
62
|
+
"mode.utils.loops.asyncio.get_event_loop"
|
|
63
|
+
) as get_event_loop_mock:
|
|
64
|
+
second = get_event_loop()
|
|
65
|
+
assert second is first
|
|
66
|
+
get_event_loop_mock.assert_not_called()
|
|
67
|
+
finally:
|
|
68
|
+
asyncio.set_event_loop(None)
|
|
69
|
+
first.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_get_event_loop__caches_per_thread_not_globally():
|
|
73
|
+
# Mode runs a dedicated event loop per ServiceThread worker thread
|
|
74
|
+
# (see mode.threads.ServiceThread), so the cache must be thread-local:
|
|
75
|
+
# one thread's cached loop must never leak into another thread's call.
|
|
76
|
+
asyncio.set_event_loop(None)
|
|
77
|
+
main_loop = get_event_loop()
|
|
78
|
+
other_loop_holder: dict = {}
|
|
79
|
+
|
|
80
|
+
def other_thread() -> None:
|
|
81
|
+
other_loop_holder["loop"] = get_event_loop()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
thread = threading.Thread(target=other_thread)
|
|
85
|
+
thread.start()
|
|
86
|
+
thread.join()
|
|
87
|
+
|
|
88
|
+
other_loop = other_loop_holder["loop"]
|
|
89
|
+
assert other_loop is not main_loop
|
|
90
|
+
assert get_event_loop() is main_loop
|
|
91
|
+
finally:
|
|
92
|
+
asyncio.set_event_loop(None)
|
|
93
|
+
main_loop.close()
|
|
94
|
+
other_loop_holder["loop"].close()
|
|
@@ -275,6 +275,38 @@ def test_annotations__stop_excludes_base_above_stop():
|
|
|
275
275
|
assert fields == {"foo": int}
|
|
276
276
|
|
|
277
277
|
|
|
278
|
+
def test_annotations__preserves_function_local_class_identity():
|
|
279
|
+
# Regression test: a field annotated with a class defined inside a
|
|
280
|
+
# function (``__qualname__`` contains "<locals>") must be returned as the
|
|
281
|
+
# actual class object, not rewritten to its bare name string. Callers
|
|
282
|
+
# such as faust.Record match a field's resolved type by identity against
|
|
283
|
+
# a coercion mapping, which silently breaks if the type is replaced by a
|
|
284
|
+
# string.
|
|
285
|
+
class Local:
|
|
286
|
+
value: int
|
|
287
|
+
|
|
288
|
+
class Holder:
|
|
289
|
+
item: Local
|
|
290
|
+
|
|
291
|
+
fields, _ = annotations(Holder, globalns=globals(), localns=locals())
|
|
292
|
+
|
|
293
|
+
assert fields["item"] is Local
|
|
294
|
+
assert not isinstance(fields["item"], str)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_eval_type__string_classvar_is_not_rejected():
|
|
298
|
+
# Regression test: a *string* ClassVar annotation (as produced by
|
|
299
|
+
# `from __future__ import annotations`) must not raise. Passing the
|
|
300
|
+
# forward ref straight to typing._eval_type runs typing._type_check,
|
|
301
|
+
# which rejects ClassVar[...] with "is not valid as type argument".
|
|
302
|
+
from typing import ClassVar as _ClassVar
|
|
303
|
+
|
|
304
|
+
result = eval_type(
|
|
305
|
+
"ClassVar[int]", globalns={"ClassVar": _ClassVar}, localns={}
|
|
306
|
+
)
|
|
307
|
+
assert result == _ClassVar[int]
|
|
308
|
+
|
|
309
|
+
|
|
278
310
|
@pytest.mark.parametrize(
|
|
279
311
|
"input,expected",
|
|
280
312
|
[
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
from mode.utils.loops import get_event_loop
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_get_event_loop__returns_running_loop_when_running():
|
|
7
|
-
async def main():
|
|
8
|
-
return get_event_loop()
|
|
9
|
-
|
|
10
|
-
running = asyncio.new_event_loop()
|
|
11
|
-
try:
|
|
12
|
-
got = running.run_until_complete(main())
|
|
13
|
-
assert got is running
|
|
14
|
-
finally:
|
|
15
|
-
running.close()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_get_event_loop__reuses_current_loop_when_set():
|
|
19
|
-
loop = asyncio.new_event_loop()
|
|
20
|
-
asyncio.set_event_loop(loop)
|
|
21
|
-
try:
|
|
22
|
-
assert get_event_loop() is loop
|
|
23
|
-
finally:
|
|
24
|
-
asyncio.set_event_loop(None)
|
|
25
|
-
loop.close()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def test_get_event_loop__creates_loop_when_none_set():
|
|
29
|
-
# Emulates Python 3.14, where asyncio.get_event_loop() no longer creates
|
|
30
|
-
# a loop implicitly and raises RuntimeError instead.
|
|
31
|
-
asyncio.set_event_loop(None)
|
|
32
|
-
loop = get_event_loop()
|
|
33
|
-
try:
|
|
34
|
-
assert isinstance(loop, asyncio.AbstractEventLoop)
|
|
35
|
-
assert asyncio.get_event_loop() is loop
|
|
36
|
-
finally:
|
|
37
|
-
asyncio.set_event_loop(None)
|
|
38
|
-
loop.close()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_get_event_loop__is_idempotent_without_running_loop():
|
|
42
|
-
asyncio.set_event_loop(None)
|
|
43
|
-
first = get_event_loop()
|
|
44
|
-
try:
|
|
45
|
-
assert get_event_loop() is first
|
|
46
|
-
finally:
|
|
47
|
-
asyncio.set_event_loop(None)
|
|
48
|
-
first.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|