encore-dev 2.0.0b6__tar.gz → 2.0.0b7__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.
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/PKG-INFO +1 -1
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/_run.py +9 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/_native.pyi +15 -1
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/runtime.py +24 -22
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/startup.py +27 -1
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/pubsub/__init__.py +10 -3
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/pyproject.toml +1 -1
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/pyproject.toml.orig +1 -1
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/README.md +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/config/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/config/_build.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/config/_markers.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/cron/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/env/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/errs/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/gateway/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/gateway/_serve.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/gateway/match.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/_loader.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/lazy.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/internal/reqfields.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/logs/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/logs/_console.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/logs/_runtime.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/metrics/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/metrics/_instrument.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/metrics/_metrics.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/metrics/_provider.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/py.typed +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/reqctx/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/reqctx/_json.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/secrets/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/server/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/server/_serve.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/server/_span.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/server/_tracing.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/server/asgi.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/server/wsgi.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/services/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/services/_propagate.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/services/aiohttp.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/services/httpx.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/services/niquests.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/services/requests.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/shutdown/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/objects/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/postgres/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/_base.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/_responses.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/_values.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/bitmap.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/generic.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/geo.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/hash.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/json.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/list.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/pubsub.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/set.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/sorted_set.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/stream.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/storage/redis/string.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/tracing/__init__.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/tracing/_context.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/tracing/_encode.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/tracing/_provider.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/tracing/_span.py +0 -0
- {encore_dev-2.0.0b6 → encore_dev-2.0.0b7}/encore/tracing/_tracer.py +0 -0
|
@@ -153,6 +153,7 @@ async def _lifecycle(http: Any) -> int:
|
|
|
153
153
|
|
|
154
154
|
async def _shutdown(http: Any, force: asyncio.Event) -> int:
|
|
155
155
|
from . import shutdown
|
|
156
|
+
from .internal.startup import stopped as subscriptions_stopped
|
|
156
157
|
|
|
157
158
|
# Cooperative: Encore cleans up the resources the app uses once the app
|
|
158
159
|
# says it is done (its requests answered, its lifespan shut down), or at
|
|
@@ -195,9 +196,17 @@ async def _shutdown(http: Any, force: asyncio.Event) -> int:
|
|
|
195
196
|
await http.join(1.0)
|
|
196
197
|
|
|
197
198
|
result = await shutdown.completed()
|
|
199
|
+
# Before the loop closes: the subscriptions' consumers ended as the
|
|
200
|
+
# shutdown began.
|
|
201
|
+
await subscriptions_stopped(_CONSUMERS_STOP)
|
|
198
202
|
return result.code()
|
|
199
203
|
|
|
200
204
|
|
|
205
|
+
#: How long the subscriptions' consumers may take to end once shutdown has
|
|
206
|
+
#: completed.
|
|
207
|
+
_CONSUMERS_STOP = 1.0
|
|
208
|
+
|
|
209
|
+
|
|
201
210
|
#: How long after the first signal the same one again is a new one, not the
|
|
202
211
|
#: first delivered twice.
|
|
203
212
|
_REPEAT_SECS = 1.0
|
|
@@ -734,7 +734,8 @@ class PubSubSubscription:
|
|
|
734
734
|
"""
|
|
735
735
|
Consume messages on the calling asyncio loop until subscription shutdown.
|
|
736
736
|
Handler completion acknowledges a message; exceptions propagate to the core
|
|
737
|
-
so the provider can apply its retry policy.
|
|
737
|
+
so the provider can apply its retry policy. Subscribing again moves the
|
|
738
|
+
handler to the calling loop: the subscription has one handler.
|
|
738
739
|
"""
|
|
739
740
|
|
|
740
741
|
@final
|
|
@@ -1030,6 +1031,12 @@ class Runtime:
|
|
|
1030
1031
|
"""
|
|
1031
1032
|
The version of the Encore runtime in use.
|
|
1032
1033
|
"""
|
|
1034
|
+
def wait_completed(self, /) -> "ShutdownResult":
|
|
1035
|
+
"""
|
|
1036
|
+
Wait for cleanup to finish, as `completed` does, blocking this thread
|
|
1037
|
+
rather than awaiting: for the exit, which runs no event loop and
|
|
1038
|
+
leaves the runtime nothing to complete. Call begin_shutdown first.
|
|
1039
|
+
"""
|
|
1033
1040
|
|
|
1034
1041
|
@final
|
|
1035
1042
|
class Secret:
|
|
@@ -1108,6 +1115,13 @@ def encode_log(level: int, time_unix_nanos: int, message: str, *, logger: str |N
|
|
|
1108
1115
|
runtime.
|
|
1109
1116
|
"""
|
|
1110
1117
|
|
|
1118
|
+
def exiting(timeout: float) -> "None":
|
|
1119
|
+
"""
|
|
1120
|
+
Says the interpreter is exiting: from now on the runtime's threads don't
|
|
1121
|
+
enter Python. Waits up to `timeout` seconds for those in it to leave.
|
|
1122
|
+
The last thing the SDK does at exit.
|
|
1123
|
+
"""
|
|
1124
|
+
|
|
1111
1125
|
def flush_runtime_log(timeout: float) -> "int":
|
|
1112
1126
|
"""
|
|
1113
1127
|
Waits, up to `timeout` seconds, for the handler to take the lines queued
|
|
@@ -30,16 +30,24 @@ _native.set_runtime_log_handler(_handle_runtime_line)
|
|
|
30
30
|
#: How long the runtime's queued lines may take to reach ``logging`` at exit.
|
|
31
31
|
_RUNTIME_LOG_FLUSH = 1.0
|
|
32
32
|
|
|
33
|
+
#: How long the runtime's threads in Python may take to leave it at exit.
|
|
34
|
+
_LEAVE_PYTHON = 1.0
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
|
|
37
|
+
def _exit() -> None:
|
|
38
|
+
"""Hand the runtime's queued lines to ``logging``, then leave Python be.
|
|
39
|
+
|
|
40
|
+
After this, the runtime's threads don't enter Python: one that did as
|
|
41
|
+
the interpreter finalizes could crash it.
|
|
42
|
+
"""
|
|
36
43
|
lost = _native.flush_runtime_log(_RUNTIME_LOG_FLUSH)
|
|
37
44
|
if lost:
|
|
38
45
|
print(f"encore: lost {lost} of the runtime's log lines", file=sys.stderr)
|
|
46
|
+
_native.exiting(_LEAVE_PYTHON)
|
|
39
47
|
|
|
40
48
|
|
|
41
49
|
# Registered first, so it runs after the exit handlers registered later.
|
|
42
|
-
atexit.register(
|
|
50
|
+
atexit.register(_exit)
|
|
43
51
|
|
|
44
52
|
# Instrumentations read this at import; Encore records stable semconv (ADR 0004).
|
|
45
53
|
os.environ.setdefault("OTEL_SEMCONV_STABILITY_OPT_IN", "http")
|
|
@@ -62,28 +70,22 @@ def _shutdown() -> bool:
|
|
|
62
70
|
global _shutdown_ok
|
|
63
71
|
if _shutdown_ok is not None:
|
|
64
72
|
return _shutdown_ok
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
from .. import shutdown
|
|
68
|
-
|
|
73
|
+
# Imports nothing, and runs no event loop: it runs at exit, and in the
|
|
74
|
+
# SIGTERM handler, which may interrupt an import.
|
|
69
75
|
if threading.current_thread() is threading.main_thread():
|
|
70
76
|
# A repeated SIGTERM must not interrupt the flush.
|
|
71
77
|
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
waiter = threading.Thread(target=wait, name="encore-shutdown")
|
|
84
|
-
waiter.start()
|
|
85
|
-
waiter.join()
|
|
86
|
-
_shutdown_ok = bool(result and result[0])
|
|
78
|
+
_runtime.begin_shutdown()
|
|
79
|
+
try:
|
|
80
|
+
result = _runtime.wait_completed()
|
|
81
|
+
except EncoreError as exc:
|
|
82
|
+
print(f"encore: shutdown failed: {exc}", file=sys.stderr)
|
|
83
|
+
result = None
|
|
84
|
+
if result == _native.ShutdownResult.Failed:
|
|
85
|
+
print(
|
|
86
|
+
"encore: shutdown failed: Encore resource cleanup failed", file=sys.stderr
|
|
87
|
+
)
|
|
88
|
+
_shutdown_ok = result == _native.ShutdownResult.Success
|
|
87
89
|
return _shutdown_ok
|
|
88
90
|
|
|
89
91
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Application-loop startup for registered Encore resources."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
3
5
|
from .. import pubsub
|
|
4
6
|
from .runtime import runtime
|
|
5
7
|
|
|
@@ -10,7 +12,9 @@ async def start() -> None:
|
|
|
10
12
|
Start all registered subscriptions and return. Import resource declarations
|
|
11
13
|
before calling this from your async main or framework startup hook.
|
|
12
14
|
Repeated calls on the same loop reuse consumers; later calls also start newly
|
|
13
|
-
imported subscriptions.
|
|
15
|
+
imported subscriptions. Once the loop they were started on has closed, as
|
|
16
|
+
a test client's does after each lifespan, a call on another moves them
|
|
17
|
+
there. This does not own the loop or install signal handlers.
|
|
14
18
|
|
|
15
19
|
Shortly after, the runtime warns of each subscription the service hosts
|
|
16
20
|
that has no handler, such as one declared in a module never imported.
|
|
@@ -28,3 +32,25 @@ async def start() -> None:
|
|
|
28
32
|
subscription._start()
|
|
29
33
|
|
|
30
34
|
runtime().subscriptions_started()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def stopped(timeout: float) -> None:
|
|
38
|
+
"""Wait for this loop's subscription consumers to end, up to ``timeout``.
|
|
39
|
+
|
|
40
|
+
They end once Encore's shutdown begins, each resolved from a runtime
|
|
41
|
+
thread. Awaited before the loop closes, so their completions run on it,
|
|
42
|
+
and those still pending at ``timeout`` are cancelled.
|
|
43
|
+
"""
|
|
44
|
+
loop = asyncio.get_running_loop()
|
|
45
|
+
consumers = {
|
|
46
|
+
consumer
|
|
47
|
+
for subscription in pubsub._subscriptions
|
|
48
|
+
if (consumer := subscription._consumer) is not None
|
|
49
|
+
and consumer.get_loop() is loop
|
|
50
|
+
and not consumer.done()
|
|
51
|
+
}
|
|
52
|
+
if not consumers:
|
|
53
|
+
return
|
|
54
|
+
_, pending = await asyncio.wait(consumers, timeout=timeout)
|
|
55
|
+
for consumer in pending:
|
|
56
|
+
consumer.cancel()
|
|
@@ -366,7 +366,7 @@ def _handler_context(message: _native.PubSubMessage) -> context_api.Context:
|
|
|
366
366
|
|
|
367
367
|
|
|
368
368
|
class SubscriptionLoopError(RuntimeError):
|
|
369
|
-
"""The subscription
|
|
369
|
+
"""The subscription runs on another asyncio loop, which is still open."""
|
|
370
370
|
|
|
371
371
|
|
|
372
372
|
class Subscription(Generic[_T]):
|
|
@@ -375,6 +375,10 @@ class Subscription(Generic[_T]):
|
|
|
375
375
|
Await encore.start() to begin consumption on the application's asyncio
|
|
376
376
|
loop. The explicit type argument selects payload decoding; omitting it
|
|
377
377
|
uses ordinary JSON decoding.
|
|
378
|
+
|
|
379
|
+
It runs on one loop at a time. Once that loop has closed, as a test
|
|
380
|
+
client's closes after its lifespan, encore.start() on another moves it
|
|
381
|
+
there.
|
|
378
382
|
"""
|
|
379
383
|
|
|
380
384
|
def __init__(
|
|
@@ -446,14 +450,17 @@ class Subscription(Generic[_T]):
|
|
|
446
450
|
|
|
447
451
|
def _check_loop(self) -> None:
|
|
448
452
|
if self._consumer is not None:
|
|
449
|
-
|
|
453
|
+
loop = self._consumer.get_loop()
|
|
454
|
+
if loop is not asyncio.get_running_loop() and not loop.is_closed():
|
|
450
455
|
raise SubscriptionLoopError(
|
|
451
456
|
"subscription already started on a different asyncio loop"
|
|
452
457
|
)
|
|
453
458
|
|
|
454
459
|
def _start(self) -> asyncio.Future[None]:
|
|
455
460
|
self._check_loop()
|
|
456
|
-
|
|
461
|
+
# Started anew where the loop it ran on has closed: the native
|
|
462
|
+
# subscription moves its handler to this one.
|
|
463
|
+
if self._consumer is None or self._consumer.get_loop().is_closed():
|
|
457
464
|
self._consumer = asyncio.ensure_future(self._handle.get().subscribe())
|
|
458
465
|
self._consumer.add_done_callback(self._consumer_done)
|
|
459
466
|
return self._consumer
|
|
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
|