pgtask 0.1.3__cp310-abi3-win_amd64.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.
- pgtask/__init__.py +31 -0
- pgtask/_native.pyd +0 -0
- pgtask/_native.pyi +48 -0
- pgtask/client.py +367 -0
- pgtask/py.typed +0 -0
- pgtask-0.1.3.dist-info/METADATA +42 -0
- pgtask-0.1.3.dist-info/RECORD +8 -0
- pgtask-0.1.3.dist-info/WHEEL +4 -0
pgtask/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pgtask.client import (
|
|
4
|
+
Client,
|
|
5
|
+
EnqueueRequest,
|
|
6
|
+
JSONValue,
|
|
7
|
+
Task,
|
|
8
|
+
TaskDefinition,
|
|
9
|
+
TaskHandle,
|
|
10
|
+
TaskHandler,
|
|
11
|
+
TaskRegistry,
|
|
12
|
+
TaskResult,
|
|
13
|
+
TaskState,
|
|
14
|
+
TransactionConnection,
|
|
15
|
+
Worker,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Client",
|
|
20
|
+
"EnqueueRequest",
|
|
21
|
+
"JSONValue",
|
|
22
|
+
"Task",
|
|
23
|
+
"TaskDefinition",
|
|
24
|
+
"TaskHandle",
|
|
25
|
+
"TaskHandler",
|
|
26
|
+
"TaskRegistry",
|
|
27
|
+
"TaskResult",
|
|
28
|
+
"TaskState",
|
|
29
|
+
"TransactionConnection",
|
|
30
|
+
"Worker",
|
|
31
|
+
]
|
pgtask/_native.pyd
ADDED
|
Binary file
|
pgtask/_native.pyi
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
class Client:
|
|
6
|
+
@staticmethod
|
|
7
|
+
async def connect(
|
|
8
|
+
database_url: str,
|
|
9
|
+
*,
|
|
10
|
+
listener_url: str | None = None,
|
|
11
|
+
max_query_connections: int = 10,
|
|
12
|
+
max_listener_connections: int = 1,
|
|
13
|
+
) -> Client: ...
|
|
14
|
+
async def migrate(self) -> None: ...
|
|
15
|
+
async def enqueue(self, request: dict[str, Any]) -> tuple[str, bool]: ...
|
|
16
|
+
async def task_result(self, task_id: str) -> dict[str, Any] | None: ...
|
|
17
|
+
async def wait_result(self, task_id: str, timeout: float | None) -> dict[str, Any] | None: ...
|
|
18
|
+
async def emit_signal(self, task_id: str, name: str, occurrence: int, value: Any) -> Any: ...
|
|
19
|
+
async def cancel(self, task_id: str) -> bool: ...
|
|
20
|
+
|
|
21
|
+
class TaskContext:
|
|
22
|
+
async def step(self, name: str, occurrence: int, operation: Any) -> Any: ...
|
|
23
|
+
async def sleep_for(self, name: str, occurrence: int, seconds: float) -> None: ...
|
|
24
|
+
async def sleep_until(self, name: str, occurrence: int, wake_at: str) -> None: ...
|
|
25
|
+
async def wait_for_signal(
|
|
26
|
+
self,
|
|
27
|
+
step_name: str,
|
|
28
|
+
occurrence: int,
|
|
29
|
+
signal_name: str,
|
|
30
|
+
signal_occurrence: int = 0,
|
|
31
|
+
timeout: float | None = None,
|
|
32
|
+
) -> Any: ...
|
|
33
|
+
async def spawn(self, step_name: str, occurrence: int, request: dict[str, Any]) -> str: ...
|
|
34
|
+
async def wait_for_result(
|
|
35
|
+
self, step_name: str, occurrence: int, task_id: str, timeout: float | None = None
|
|
36
|
+
) -> Any: ...
|
|
37
|
+
|
|
38
|
+
class Worker:
|
|
39
|
+
def __init__(self, database_url: str, queue_names: list[str], options: dict[str, Any]) -> None: ...
|
|
40
|
+
def register(
|
|
41
|
+
self,
|
|
42
|
+
name: str,
|
|
43
|
+
function: Any,
|
|
44
|
+
handler_version: int = 1,
|
|
45
|
+
retry_delay: float | None = 1.0,
|
|
46
|
+
) -> None: ...
|
|
47
|
+
async def run(self) -> None: ...
|
|
48
|
+
def shutdown(self) -> None: ...
|
pgtask/client.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Generic, TypeVar, cast
|
|
7
|
+
|
|
8
|
+
from opentelemetry.context import attach, detach
|
|
9
|
+
from opentelemetry.propagate import extract, inject
|
|
10
|
+
from typing_extensions import Literal, Protocol, TypeAlias # noqa: UP035
|
|
11
|
+
|
|
12
|
+
from pgtask import _native
|
|
13
|
+
|
|
14
|
+
JSONValue: TypeAlias = None | bool | int | float | str | list["JSONValue"] | dict[str, "JSONValue"]
|
|
15
|
+
TaskState: TypeAlias = Literal["pending", "running", "waiting", "succeeded", "failed", "cancelled"]
|
|
16
|
+
PayloadT = TypeVar("PayloadT")
|
|
17
|
+
ResultT = TypeVar("ResultT")
|
|
18
|
+
StepT = TypeVar("StepT")
|
|
19
|
+
TaskHandler: TypeAlias = Callable[["Task", PayloadT], Awaitable[ResultT]]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TransactionCursor(Protocol):
|
|
23
|
+
async def fetchone(self) -> tuple[str, bool] | None: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TransactionConnection(Protocol):
|
|
27
|
+
async def execute(self, query: str, params: tuple[Any, ...]) -> TransactionCursor: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class EnqueueRequest(Generic[ResultT]):
|
|
32
|
+
task_name: str
|
|
33
|
+
payload: JSONValue
|
|
34
|
+
queue_name: str = "default"
|
|
35
|
+
handler_version: int = 1
|
|
36
|
+
run_at: datetime | None = None
|
|
37
|
+
priority: int = 0
|
|
38
|
+
max_attempts: int = 5
|
|
39
|
+
idempotency_key: str | None = None
|
|
40
|
+
headers: dict[str, JSONValue] = field(default_factory=dict)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _request_value(request: EnqueueRequest[Any]) -> dict[str, Any]:
|
|
44
|
+
headers = dict(request.headers)
|
|
45
|
+
inject(cast(dict[str, str], headers))
|
|
46
|
+
return {
|
|
47
|
+
"task_name": request.task_name,
|
|
48
|
+
"payload": request.payload,
|
|
49
|
+
"queue_name": request.queue_name,
|
|
50
|
+
"handler_version": request.handler_version,
|
|
51
|
+
"run_at": request.run_at.isoformat() if request.run_at is not None else None,
|
|
52
|
+
"priority": request.priority,
|
|
53
|
+
"max_attempts": request.max_attempts,
|
|
54
|
+
"idempotency_key": request.idempotency_key,
|
|
55
|
+
"headers": headers,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class TaskDefinition(Generic[PayloadT, ResultT]):
|
|
61
|
+
name: str
|
|
62
|
+
queue_name: str
|
|
63
|
+
handler: TaskHandler[PayloadT, ResultT] = field(repr=False)
|
|
64
|
+
handler_version: int = 1
|
|
65
|
+
retry_delay: float | None = 1.0
|
|
66
|
+
|
|
67
|
+
def request(
|
|
68
|
+
self,
|
|
69
|
+
payload: PayloadT,
|
|
70
|
+
*,
|
|
71
|
+
run_at: datetime | None = None,
|
|
72
|
+
priority: int = 0,
|
|
73
|
+
max_attempts: int = 5,
|
|
74
|
+
idempotency_key: str | None = None,
|
|
75
|
+
headers: dict[str, JSONValue] | None = None,
|
|
76
|
+
) -> EnqueueRequest[ResultT]:
|
|
77
|
+
return EnqueueRequest(
|
|
78
|
+
task_name=self.name,
|
|
79
|
+
payload=cast(JSONValue, payload),
|
|
80
|
+
queue_name=self.queue_name,
|
|
81
|
+
handler_version=self.handler_version,
|
|
82
|
+
run_at=run_at,
|
|
83
|
+
priority=priority,
|
|
84
|
+
max_attempts=max_attempts,
|
|
85
|
+
idempotency_key=idempotency_key,
|
|
86
|
+
headers={} if headers is None else headers,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TaskRegistry:
|
|
91
|
+
def __init__(self, queue_name: str = "default") -> None:
|
|
92
|
+
self.queue_name = queue_name
|
|
93
|
+
self._definitions: dict[tuple[str, int], TaskDefinition[Any, Any]] = {}
|
|
94
|
+
|
|
95
|
+
def task(
|
|
96
|
+
self,
|
|
97
|
+
name: str,
|
|
98
|
+
*,
|
|
99
|
+
handler_version: int = 1,
|
|
100
|
+
retry_delay: float | None = 1.0,
|
|
101
|
+
) -> Callable[[TaskHandler[PayloadT, ResultT]], TaskDefinition[PayloadT, ResultT]]:
|
|
102
|
+
def decorator(handler: TaskHandler[PayloadT, ResultT]) -> TaskDefinition[PayloadT, ResultT]:
|
|
103
|
+
definition = TaskDefinition(name, self.queue_name, handler, handler_version, retry_delay)
|
|
104
|
+
key = (definition.name, definition.handler_version)
|
|
105
|
+
if key in self._definitions:
|
|
106
|
+
raise ValueError(f"task {definition.name!r} version {definition.handler_version} is already registered")
|
|
107
|
+
self._definitions[key] = definition
|
|
108
|
+
return definition
|
|
109
|
+
|
|
110
|
+
return decorator
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def definitions(self) -> tuple[TaskDefinition[Any, Any], ...]:
|
|
114
|
+
return tuple(self._definitions.values())
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class TaskResult(Generic[ResultT]):
|
|
119
|
+
state: TaskState
|
|
120
|
+
result: ResultT | None
|
|
121
|
+
error: JSONValue
|
|
122
|
+
completed_at: datetime | None
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_native(cls, value: dict[str, Any]) -> TaskResult[ResultT]:
|
|
126
|
+
completed_at = value["completed_at"]
|
|
127
|
+
return cls(
|
|
128
|
+
state=cast(TaskState, value["state"]),
|
|
129
|
+
result=cast(ResultT | None, value["result"]),
|
|
130
|
+
error=value["error"],
|
|
131
|
+
completed_at=datetime.fromisoformat(completed_at.replace("Z", "+00:00"))
|
|
132
|
+
if completed_at is not None
|
|
133
|
+
else None,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(frozen=True)
|
|
138
|
+
class Task:
|
|
139
|
+
id: str
|
|
140
|
+
parent_task_id: str | None
|
|
141
|
+
queue_name: str
|
|
142
|
+
task_name: str
|
|
143
|
+
handler_version: int
|
|
144
|
+
payload: JSONValue
|
|
145
|
+
headers: dict[str, JSONValue]
|
|
146
|
+
state: TaskState
|
|
147
|
+
attempt: int
|
|
148
|
+
max_attempts: int
|
|
149
|
+
run_at: datetime
|
|
150
|
+
created_at: datetime
|
|
151
|
+
_context: _native.TaskContext = field(repr=False, compare=False)
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_native(cls, value: dict[str, Any], context: _native.TaskContext) -> Task:
|
|
155
|
+
return cls(
|
|
156
|
+
id=str(value["id"]),
|
|
157
|
+
parent_task_id=str(value["parent_task_id"]) if value["parent_task_id"] is not None else None,
|
|
158
|
+
queue_name=str(value["queue_name"]),
|
|
159
|
+
task_name=str(value["task_name"]),
|
|
160
|
+
handler_version=int(value["handler_version"]),
|
|
161
|
+
payload=cast(JSONValue, value["payload"]),
|
|
162
|
+
headers=cast(dict[str, JSONValue], value["headers"]),
|
|
163
|
+
state=cast(TaskState, value["state"]),
|
|
164
|
+
attempt=int(value["attempt"]),
|
|
165
|
+
max_attempts=int(value["max_attempts"]),
|
|
166
|
+
run_at=datetime.fromisoformat(value["run_at"].replace("Z", "+00:00")),
|
|
167
|
+
created_at=datetime.fromisoformat(value["created_at"].replace("Z", "+00:00")),
|
|
168
|
+
_context=context,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
async def step(self, name: str, operation: Callable[[], Awaitable[StepT]], occurrence: int = 0) -> StepT:
|
|
172
|
+
return cast(StepT, await self._context.step(name, occurrence, operation))
|
|
173
|
+
|
|
174
|
+
async def sleep_for(self, name: str, seconds: float, occurrence: int = 0) -> None:
|
|
175
|
+
await self._context.sleep_for(name, occurrence, seconds)
|
|
176
|
+
|
|
177
|
+
async def sleep_until(self, name: str, wake_at: datetime, occurrence: int = 0) -> None:
|
|
178
|
+
await self._context.sleep_until(name, occurrence, wake_at.isoformat())
|
|
179
|
+
|
|
180
|
+
async def wait_for_signal(
|
|
181
|
+
self,
|
|
182
|
+
step_name: str,
|
|
183
|
+
signal_name: str,
|
|
184
|
+
*,
|
|
185
|
+
occurrence: int = 0,
|
|
186
|
+
signal_occurrence: int = 0,
|
|
187
|
+
timeout: float | None = None,
|
|
188
|
+
) -> JSONValue:
|
|
189
|
+
return cast(
|
|
190
|
+
JSONValue,
|
|
191
|
+
await self._context.wait_for_signal(
|
|
192
|
+
step_name,
|
|
193
|
+
occurrence,
|
|
194
|
+
signal_name,
|
|
195
|
+
signal_occurrence,
|
|
196
|
+
timeout,
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def spawn(self, step_name: str, request: EnqueueRequest[Any], occurrence: int = 0) -> str:
|
|
201
|
+
return await self._context.spawn(step_name, occurrence, _request_value(request))
|
|
202
|
+
|
|
203
|
+
async def wait_for_result(
|
|
204
|
+
self,
|
|
205
|
+
step_name: str,
|
|
206
|
+
task_id: str,
|
|
207
|
+
*,
|
|
208
|
+
occurrence: int = 0,
|
|
209
|
+
timeout: float | None = None,
|
|
210
|
+
) -> JSONValue:
|
|
211
|
+
return cast(JSONValue, await self._context.wait_for_result(step_name, occurrence, task_id, timeout))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class TaskHandle(Generic[ResultT]):
|
|
216
|
+
id: str
|
|
217
|
+
_client: Client = field(repr=False, compare=False)
|
|
218
|
+
|
|
219
|
+
async def inspect(self) -> TaskResult[ResultT] | None:
|
|
220
|
+
return await self._client.task_result(self.id)
|
|
221
|
+
|
|
222
|
+
async def result(self, timeout: float | None = None) -> TaskResult[ResultT] | None:
|
|
223
|
+
return await self._client.wait_result(self.id, timeout)
|
|
224
|
+
|
|
225
|
+
async def signal(self, name: str, value: JSONValue, occurrence: int = 0) -> JSONValue:
|
|
226
|
+
return await self._client.emit_signal(self.id, name, value, occurrence)
|
|
227
|
+
|
|
228
|
+
async def cancel(self) -> bool:
|
|
229
|
+
return await self._client.cancel(self.id)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Client:
|
|
233
|
+
def __init__(self, native: _native.Client) -> None:
|
|
234
|
+
self._native = native
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
async def connect(
|
|
238
|
+
cls,
|
|
239
|
+
database_url: str,
|
|
240
|
+
*,
|
|
241
|
+
listener_url: str | None = None,
|
|
242
|
+
max_query_connections: int = 10,
|
|
243
|
+
max_listener_connections: int = 1,
|
|
244
|
+
) -> Client:
|
|
245
|
+
return cls(
|
|
246
|
+
await _native.Client.connect(
|
|
247
|
+
database_url,
|
|
248
|
+
listener_url=listener_url,
|
|
249
|
+
max_query_connections=max_query_connections,
|
|
250
|
+
max_listener_connections=max_listener_connections,
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
async def migrate(self) -> None:
|
|
255
|
+
await self._native.migrate()
|
|
256
|
+
|
|
257
|
+
async def enqueue(self, request: EnqueueRequest[ResultT]) -> TaskHandle[ResultT]:
|
|
258
|
+
task_id, _ = await self._enqueue(request)
|
|
259
|
+
return TaskHandle(task_id, self)
|
|
260
|
+
|
|
261
|
+
def task(self, task_id: str) -> TaskHandle[JSONValue]:
|
|
262
|
+
return TaskHandle(task_id, self)
|
|
263
|
+
|
|
264
|
+
async def _enqueue(self, request: EnqueueRequest[Any]) -> tuple[str, bool]:
|
|
265
|
+
return await self._native.enqueue(_request_value(request))
|
|
266
|
+
|
|
267
|
+
async def task_result(self, task_id: str) -> TaskResult[Any] | None:
|
|
268
|
+
result = await self._native.task_result(task_id)
|
|
269
|
+
return TaskResult.from_native(result) if result is not None else None
|
|
270
|
+
|
|
271
|
+
async def wait_result(self, task_id: str, timeout: float | None = None) -> TaskResult[Any] | None:
|
|
272
|
+
result = await self._native.wait_result(task_id, timeout)
|
|
273
|
+
return TaskResult.from_native(result) if result is not None else None
|
|
274
|
+
|
|
275
|
+
async def emit_signal(self, task_id: str, name: str, value: JSONValue, occurrence: int = 0) -> JSONValue:
|
|
276
|
+
return cast(JSONValue, await self._native.emit_signal(task_id, name, occurrence, value))
|
|
277
|
+
|
|
278
|
+
async def cancel(self, task_id: str) -> bool:
|
|
279
|
+
return await self._native.cancel(task_id)
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
async def enqueue_on(connection: TransactionConnection, request: EnqueueRequest[Any]) -> tuple[str, bool]:
|
|
283
|
+
from psycopg.types.json import Jsonb
|
|
284
|
+
|
|
285
|
+
cursor = await connection.execute(
|
|
286
|
+
"""
|
|
287
|
+
SELECT task_id::text, created
|
|
288
|
+
FROM pgtask.enqueue(%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
289
|
+
""",
|
|
290
|
+
(
|
|
291
|
+
request.task_name,
|
|
292
|
+
Jsonb(request.payload),
|
|
293
|
+
request.queue_name,
|
|
294
|
+
request.handler_version,
|
|
295
|
+
request.run_at,
|
|
296
|
+
request.priority,
|
|
297
|
+
request.max_attempts,
|
|
298
|
+
request.idempotency_key,
|
|
299
|
+
Jsonb(request.headers),
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
row = await cursor.fetchone()
|
|
303
|
+
if row is None:
|
|
304
|
+
raise RuntimeError("pgtask.enqueue returned no result")
|
|
305
|
+
return row
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class Worker:
|
|
309
|
+
def __init__(
|
|
310
|
+
self,
|
|
311
|
+
database_url: str,
|
|
312
|
+
registry: TaskRegistry | Sequence[TaskRegistry],
|
|
313
|
+
*,
|
|
314
|
+
concurrency: int = 10,
|
|
315
|
+
poll_interval: float = 30.0,
|
|
316
|
+
lease_duration: float = 30.0,
|
|
317
|
+
health_address: str | None = None,
|
|
318
|
+
listener_url: str | None = None,
|
|
319
|
+
max_query_connections: int = 10,
|
|
320
|
+
max_listener_connections: int = 1,
|
|
321
|
+
) -> None:
|
|
322
|
+
registries = [registry] if isinstance(registry, TaskRegistry) else list(registry)
|
|
323
|
+
if not registries:
|
|
324
|
+
raise ValueError("at least one registry is required")
|
|
325
|
+
queue_names = [entry.queue_name for entry in registries]
|
|
326
|
+
if len(set(queue_names)) != len(queue_names):
|
|
327
|
+
raise ValueError("registries must target distinct queues")
|
|
328
|
+
self._native = _native.Worker(
|
|
329
|
+
database_url,
|
|
330
|
+
queue_names,
|
|
331
|
+
{
|
|
332
|
+
"concurrency": concurrency,
|
|
333
|
+
"poll_interval": poll_interval,
|
|
334
|
+
"lease_duration": lease_duration,
|
|
335
|
+
"health_address": health_address,
|
|
336
|
+
"listener_url": listener_url,
|
|
337
|
+
"max_query_connections": max_query_connections,
|
|
338
|
+
"max_listener_connections": max_listener_connections,
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
definitions = [definition for entry in registries for definition in entry.definitions]
|
|
342
|
+
for definition in definitions:
|
|
343
|
+
|
|
344
|
+
async def adapter(
|
|
345
|
+
value: dict[str, Any],
|
|
346
|
+
context: _native.TaskContext,
|
|
347
|
+
registered: TaskDefinition[Any, Any] = definition,
|
|
348
|
+
) -> JSONValue:
|
|
349
|
+
task = Task.from_native(value, context)
|
|
350
|
+
token = attach(extract(cast(dict[str, str], task.headers)))
|
|
351
|
+
try:
|
|
352
|
+
return cast(JSONValue, await registered.handler(task, task.payload))
|
|
353
|
+
finally:
|
|
354
|
+
detach(token)
|
|
355
|
+
|
|
356
|
+
self._native.register(
|
|
357
|
+
definition.name,
|
|
358
|
+
adapter,
|
|
359
|
+
definition.handler_version,
|
|
360
|
+
definition.retry_delay,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
async def run(self) -> None:
|
|
364
|
+
await self._native.run()
|
|
365
|
+
|
|
366
|
+
def shutdown(self) -> None:
|
|
367
|
+
self._native.shutdown()
|
pgtask/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgtask
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
5
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Classifier: Programming Language :: Rust
|
|
12
|
+
Requires-Dist: opentelemetry-api>=1.30
|
|
13
|
+
Requires-Dist: psycopg>=3.2
|
|
14
|
+
Summary: PostgreSQL-native durable tasks and workflows
|
|
15
|
+
License-Expression: MIT
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
18
|
+
|
|
19
|
+
# `pgtask`
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
from pgtask import Client, EnqueueRequest
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def main() -> None:
|
|
31
|
+
client = await Client.connect(os.environ["PGTASK_DATABASE_URL"])
|
|
32
|
+
task = await client.enqueue(EnqueueRequest("reports.render", {"report_id": "report-123"}))
|
|
33
|
+
print(await task.result(timeout=30.0))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
asyncio.run(main())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This package provides the Python producer client and worker runtime for `pgtask`. See the
|
|
40
|
+
[Python SDK documentation](https://github.com/Kludex/pgtask/blob/main/docs/sdk/python.md) for workers, transactions,
|
|
41
|
+
durable execution, signals, cancellation, and OpenTelemetry propagation.
|
|
42
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pgtask\__init__.py,sha256=A2W2jVkCoQQURuItH-QA7BN37mS_OIzrZFPX5GWmHYg,532
|
|
2
|
+
pgtask\_native.pyd,sha256=eZuS1ODh-b_EQTgy9Xoasi0RT7RPi6SGCHwGSNFkvGE,9856512
|
|
3
|
+
pgtask\_native.pyi,sha256=vS90dUh2gybk3Fs-29ZEug0JBru1K4iJkDBB9qoXS-M,1875
|
|
4
|
+
pgtask\client.py,sha256=r_9faRvXN39H8losqpfrYFxE3mHmYu80Bj6ZK0MIAGk,13457
|
|
5
|
+
pgtask\py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pgtask-0.1.3.dist-info\METADATA,sha256=VKaFqCTF_6b3OeRpHa4PtcBnkFMMY_CQCo6IU3NKZ44,1381
|
|
7
|
+
pgtask-0.1.3.dist-info\WHEEL,sha256=ZMDDxh9OPoaLQ4P2dJmgI1XsENYSzjzq8fErKKVw5iE,96
|
|
8
|
+
pgtask-0.1.3.dist-info\RECORD,,
|