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.
Files changed (44) hide show
  1. encore/__init__.py +43 -0
  2. encore/cron/__init__.py +50 -0
  3. encore/internal/__init__.py +1 -0
  4. encore/internal/_loader.py +79 -0
  5. encore/internal/_native.pyi +734 -0
  6. encore/internal/lazy.py +35 -0
  7. encore/internal/runtime.py +133 -0
  8. encore/internal/startup.py +24 -0
  9. encore/logs/__init__.py +19 -0
  10. encore/logs/_console.py +155 -0
  11. encore/logs/_runtime.py +56 -0
  12. encore/meta/__init__.py +228 -0
  13. encore/metrics/__init__.py +390 -0
  14. encore/pubsub/__init__.py +425 -0
  15. encore/py.typed +0 -0
  16. encore/secrets/__init__.py +139 -0
  17. encore/shutdown/__init__.py +164 -0
  18. encore/storage/__init__.py +5 -0
  19. encore/storage/objects/__init__.py +396 -0
  20. encore/storage/postgres/__init__.py +737 -0
  21. encore/storage/redis/__init__.py +200 -0
  22. encore/storage/redis/_base.py +42 -0
  23. encore/storage/redis/_responses.py +150 -0
  24. encore/storage/redis/_values.py +189 -0
  25. encore/storage/redis/bitmap.py +142 -0
  26. encore/storage/redis/generic.py +367 -0
  27. encore/storage/redis/geo.py +508 -0
  28. encore/storage/redis/hash.py +504 -0
  29. encore/storage/redis/json.py +614 -0
  30. encore/storage/redis/list.py +511 -0
  31. encore/storage/redis/pubsub.py +276 -0
  32. encore/storage/redis/set.py +570 -0
  33. encore/storage/redis/sorted_set.py +753 -0
  34. encore/storage/redis/stream.py +773 -0
  35. encore/storage/redis/string.py +654 -0
  36. encore/tracing/__init__.py +19 -0
  37. encore/tracing/_context.py +27 -0
  38. encore/tracing/_encode.py +351 -0
  39. encore/tracing/_provider.py +62 -0
  40. encore/tracing/_span.py +226 -0
  41. encore/tracing/_tracer.py +93 -0
  42. encore_dev-2.0.0a7.dist-info/METADATA +791 -0
  43. encore_dev-2.0.0a7.dist-info/RECORD +44 -0
  44. 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
+ )