encore-dev 2.0.0a7__py3-none-any.whl
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/__init__.py +43 -0
- encore/cron/__init__.py +50 -0
- encore/internal/__init__.py +1 -0
- encore/internal/_loader.py +79 -0
- encore/internal/_native.pyi +734 -0
- encore/internal/lazy.py +35 -0
- encore/internal/runtime.py +133 -0
- encore/internal/startup.py +24 -0
- encore/logs/__init__.py +19 -0
- encore/logs/_console.py +155 -0
- encore/logs/_runtime.py +56 -0
- encore/meta/__init__.py +228 -0
- encore/metrics/__init__.py +390 -0
- encore/pubsub/__init__.py +425 -0
- encore/py.typed +0 -0
- encore/secrets/__init__.py +139 -0
- encore/shutdown/__init__.py +164 -0
- encore/storage/__init__.py +5 -0
- encore/storage/objects/__init__.py +396 -0
- encore/storage/postgres/__init__.py +737 -0
- encore/storage/redis/__init__.py +200 -0
- encore/storage/redis/_base.py +42 -0
- encore/storage/redis/_responses.py +150 -0
- encore/storage/redis/_values.py +189 -0
- encore/storage/redis/bitmap.py +142 -0
- encore/storage/redis/generic.py +367 -0
- encore/storage/redis/geo.py +508 -0
- encore/storage/redis/hash.py +504 -0
- encore/storage/redis/json.py +614 -0
- encore/storage/redis/list.py +511 -0
- encore/storage/redis/pubsub.py +276 -0
- encore/storage/redis/set.py +570 -0
- encore/storage/redis/sorted_set.py +753 -0
- encore/storage/redis/stream.py +773 -0
- encore/storage/redis/string.py +654 -0
- encore/tracing/__init__.py +19 -0
- encore/tracing/_context.py +27 -0
- encore/tracing/_encode.py +351 -0
- encore/tracing/_provider.py +62 -0
- encore/tracing/_span.py +226 -0
- encore/tracing/_tracer.py +93 -0
- encore_dev-2.0.0a7.dist-info/METADATA +791 -0
- encore_dev-2.0.0a7.dist-info/RECORD +44 -0
- encore_dev-2.0.0a7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Redis channel subscriptions and publishing.
|
|
2
|
+
|
|
3
|
+
These channels are separate from Encore's pubsub.Topic API. Publish bytes, UTF-8
|
|
4
|
+
text, integers, floats, or RedisValue implementations. Select a payload decoder
|
|
5
|
+
with db.pubsub.subscribe[T](channel, *channels) or
|
|
6
|
+
psubscribe[T](pattern, *patterns). Channel names remain bytes. Use an async
|
|
7
|
+
context manager to close each subscription when iteration ends early or raises
|
|
8
|
+
an exception.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from types import TracebackType
|
|
16
|
+
from typing import Generic, cast, overload
|
|
17
|
+
|
|
18
|
+
from ...internal.runtime import _native
|
|
19
|
+
from ...tracing._context import active_ctx_bytes
|
|
20
|
+
from ._base import CommandInterface, TypedCommand
|
|
21
|
+
from ._values import T, Value, decode, encode, validate_decoder, U
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class Message(Generic[T]):
|
|
26
|
+
"""A message received from a Redis channel.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
channel: The channel name as bytes, without the Encore key prefix.
|
|
30
|
+
payload: The message body decoded using the subscription's type
|
|
31
|
+
argument.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
channel: bytes
|
|
35
|
+
payload: T
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Subscription(Generic[T]):
|
|
39
|
+
"""An async iterator owning a dedicated Redis connection.
|
|
40
|
+
|
|
41
|
+
Use ``async with await db.pubsub.subscribe("events") as messages`` to
|
|
42
|
+
close the connection when iteration ends early or the body raises.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def __init__(
|
|
47
|
+
self: Subscription[bytes],
|
|
48
|
+
handle: _native.RedisSubscription,
|
|
49
|
+
*,
|
|
50
|
+
value_type: type[bytes] = bytes,
|
|
51
|
+
) -> None: ...
|
|
52
|
+
|
|
53
|
+
@overload
|
|
54
|
+
def __init__(
|
|
55
|
+
self, handle: _native.RedisSubscription, *, value_type: type[T]
|
|
56
|
+
) -> None: ...
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self, handle: _native.RedisSubscription, *, value_type: type[Value] = bytes
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Wrap an active native subscription.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
handle: The native subscription. Application code should obtain
|
|
65
|
+
subscriptions with subscribe() or psubscribe().
|
|
66
|
+
value_type: Decode payloads as this type; defaults to bytes.
|
|
67
|
+
"""
|
|
68
|
+
validate_decoder(value_type)
|
|
69
|
+
self._handle = handle
|
|
70
|
+
self._value_type = cast("type[T]", value_type)
|
|
71
|
+
|
|
72
|
+
async def receive(self) -> Message[T] | None:
|
|
73
|
+
"""Wait for the next message.
|
|
74
|
+
|
|
75
|
+
Custom decoding errors propagate to the caller. The message is consumed
|
|
76
|
+
before decoding; a decoding error does not put it back on the
|
|
77
|
+
subscription. Closing the subscription interrupts a pending receive.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The received message, or None if the subscription is closed or its
|
|
81
|
+
message stream ends.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
UnicodeDecodeError: The payload is not UTF-8 when T is str.
|
|
85
|
+
ValueError: The payload cannot be parsed when T is int or float.
|
|
86
|
+
EncoreError: Receiving the message fails.
|
|
87
|
+
"""
|
|
88
|
+
message = await self._handle.receive()
|
|
89
|
+
return (
|
|
90
|
+
None
|
|
91
|
+
if message is None
|
|
92
|
+
else Message(message[0], decode(message[1], self._value_type))
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def __aiter__(self) -> Subscription[T]:
|
|
96
|
+
"""Return this subscription as an asynchronous message iterator."""
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
async def __anext__(self) -> Message[T]:
|
|
100
|
+
"""Wait for the next message during asynchronous iteration.
|
|
101
|
+
|
|
102
|
+
Custom decoding errors propagate to the caller. The message is consumed
|
|
103
|
+
before decoding; a decoding error does not put it back on the
|
|
104
|
+
subscription. Closing the subscription interrupts a pending receive.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The received message.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
StopAsyncIteration: The subscription is closed or its stream ends.
|
|
111
|
+
UnicodeDecodeError: The payload is not UTF-8 when T is str.
|
|
112
|
+
ValueError: The payload cannot be parsed when T is int or float.
|
|
113
|
+
EncoreError: Receiving the message fails.
|
|
114
|
+
"""
|
|
115
|
+
message = await self.receive()
|
|
116
|
+
if message is None:
|
|
117
|
+
raise StopAsyncIteration
|
|
118
|
+
return message
|
|
119
|
+
|
|
120
|
+
async def close(self) -> None:
|
|
121
|
+
"""Close the connection and interrupt pending receives.
|
|
122
|
+
|
|
123
|
+
Calling this method again after closure has no effect.
|
|
124
|
+
"""
|
|
125
|
+
await self._handle.close()
|
|
126
|
+
|
|
127
|
+
async def __aenter__(self) -> Subscription[T]:
|
|
128
|
+
"""Return this subscription for use in an async context manager."""
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
async def __aexit__(
|
|
132
|
+
self,
|
|
133
|
+
exc_type: type[BaseException] | None,
|
|
134
|
+
exc: BaseException | None,
|
|
135
|
+
traceback: TracebackType | None,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Close the subscription when leaving its async context.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
exc_type: The exception type raised in the context, if any.
|
|
141
|
+
exc: The exception raised in the context, if any.
|
|
142
|
+
traceback: The exception traceback, if any.
|
|
143
|
+
"""
|
|
144
|
+
await asyncio.shield(self.close())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _SubscribeCommand(TypedCommand[T]):
|
|
148
|
+
"""A callable SUBSCRIBE command with selectable value decoding."""
|
|
149
|
+
|
|
150
|
+
def __getitem__(self, value_type: type[U]) -> _SubscribeCommand[U]:
|
|
151
|
+
"""Select the value type for this command.
|
|
152
|
+
|
|
153
|
+
Use subscribe[T](...) to decode message payloads as T. Selecting a type
|
|
154
|
+
does not open a connection or change the decoder of an existing command.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
value_type: bytes, str, int, float, or a class implementing
|
|
158
|
+
RedisValue.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
An independent command with the same arguments and the selected
|
|
162
|
+
decoder. Keys and other command metadata retain their existing
|
|
163
|
+
types.
|
|
164
|
+
"""
|
|
165
|
+
return _SubscribeCommand(self._get_handle, value_type)
|
|
166
|
+
|
|
167
|
+
async def __call__(self, channel: str, *channels: str) -> Subscription[T]:
|
|
168
|
+
"""Subscribe to the named channels.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
channel: The first channel name, without an Encore prefix.
|
|
172
|
+
*channels: Additional channel names, without Encore prefixes.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A subscription owning a dedicated connection. Use it with ``async
|
|
176
|
+
with await db.pubsub.subscribe("events") as messages`` or call
|
|
177
|
+
close() when finished.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
EncoreError: The subscription cannot be established.
|
|
181
|
+
"""
|
|
182
|
+
channels = (channel, *channels)
|
|
183
|
+
return Subscription(
|
|
184
|
+
await self._get_handle().pubsub_subscribe(
|
|
185
|
+
channels, source=active_ctx_bytes()
|
|
186
|
+
),
|
|
187
|
+
value_type=self._value_type,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class _PsubscribeCommand(TypedCommand[T]):
|
|
192
|
+
"""A callable PSUBSCRIBE command with selectable value decoding."""
|
|
193
|
+
|
|
194
|
+
def __getitem__(self, value_type: type[U]) -> _PsubscribeCommand[U]:
|
|
195
|
+
"""Select the value type for this command.
|
|
196
|
+
|
|
197
|
+
Use psubscribe[T](...) to decode message payloads as T. Selecting a type
|
|
198
|
+
does not open a connection or change the decoder of an existing command.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
value_type: bytes, str, int, float, or a class implementing
|
|
202
|
+
RedisValue.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
An independent command with the same arguments and the selected
|
|
206
|
+
decoder. Keys and other command metadata retain their existing
|
|
207
|
+
types.
|
|
208
|
+
"""
|
|
209
|
+
return _PsubscribeCommand(self._get_handle, value_type)
|
|
210
|
+
|
|
211
|
+
async def __call__(self, pattern: str, *patterns: str) -> Subscription[T]:
|
|
212
|
+
"""Subscribe to channels matching Redis glob patterns.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
pattern: The first channel pattern, without an Encore prefix.
|
|
216
|
+
*patterns: Additional channel patterns, without Encore prefixes.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
A subscription owning a dedicated connection. Each message contains
|
|
220
|
+
the matched channel name. Use an async context manager or call
|
|
221
|
+
close() when finished.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
EncoreError: The subscription cannot be established.
|
|
225
|
+
"""
|
|
226
|
+
patterns = (pattern, *patterns)
|
|
227
|
+
return Subscription(
|
|
228
|
+
await self._get_handle().pubsub_psubscribe(
|
|
229
|
+
patterns, source=active_ctx_bytes()
|
|
230
|
+
),
|
|
231
|
+
value_type=self._value_type,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class ReadOnlyInterface(CommandInterface):
|
|
236
|
+
"""Redis channel subscriptions available through a read-only handle."""
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def subscribe(self) -> _SubscribeCommand[bytes]:
|
|
240
|
+
"""The callable SUBSCRIBE command.
|
|
241
|
+
|
|
242
|
+
Use subscribe[T](...) to decode message payloads as T. Plain calls keep
|
|
243
|
+
payloads as bytes; channel names are always bytes.
|
|
244
|
+
"""
|
|
245
|
+
return _SubscribeCommand(self._get_handle, bytes)
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def psubscribe(self) -> _PsubscribeCommand[bytes]:
|
|
249
|
+
"""The callable PSUBSCRIBE command.
|
|
250
|
+
|
|
251
|
+
Use psubscribe[T](...) to decode message payloads as T. Plain calls keep
|
|
252
|
+
payloads as bytes; channel names are always bytes.
|
|
253
|
+
"""
|
|
254
|
+
return _PsubscribeCommand(self._get_handle, bytes)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class Interface(ReadOnlyInterface):
|
|
258
|
+
"""Redis publishing and subscriptions exposed by a writable handle."""
|
|
259
|
+
|
|
260
|
+
async def publish(self, channel: str, message: Value) -> int:
|
|
261
|
+
"""Publish a message to a Redis channel.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
channel: The channel name without an Encore prefix.
|
|
265
|
+
message: The value to encode as the message body.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
The number of receiving subscriptions reported by Redis. This does
|
|
269
|
+
not confirm that subscribers processed the message.
|
|
270
|
+
|
|
271
|
+
Raises:
|
|
272
|
+
EncoreError: Redis rejects the command or the request fails.
|
|
273
|
+
"""
|
|
274
|
+
return await self._get_handle().pubsub_publish(
|
|
275
|
+
channel, encode(message), source=active_ctx_bytes()
|
|
276
|
+
)
|