dirigent-client 0.9.0__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.
- dirigent_client-0.9.0/LICENSE +18 -0
- dirigent_client-0.9.0/PKG-INFO +36 -0
- dirigent_client-0.9.0/README.md +23 -0
- dirigent_client-0.9.0/pyproject.toml +20 -0
- dirigent_client-0.9.0/pyproject.toml.orig +20 -0
- dirigent_client-0.9.0/src/dirigent_client/__init__.py +219 -0
- dirigent_client-0.9.0/src/dirigent_client/client.py +143 -0
- dirigent_client-0.9.0/src/dirigent_client/enums.py +215 -0
- dirigent_client-0.9.0/src/dirigent_client/errors.py +140 -0
- dirigent_client-0.9.0/src/dirigent_client/py.typed +0 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/__init__.py +1 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/alerts.py +97 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/auth.py +155 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/base.py +52 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/blocks.py +16 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/connections.py +50 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/pipelines.py +209 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/runs.py +257 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/schedules.py +130 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/schemas.py +45 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/system.py +36 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/trigger_documents.py +20 -0
- dirigent_client-0.9.0/src/dirigent_client/resources/webhooks.py +80 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/__init__.py +164 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/alerts.py +90 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/auth.py +115 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/catalog.py +66 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/common.py +38 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/connections.py +56 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/pipelines.py +326 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/runs.py +203 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/schemas.py +54 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/system.py +100 -0
- dirigent_client-0.9.0/src/dirigent_client/schemas/triggers.py +208 -0
- dirigent_client-0.9.0/src/dirigent_client/transport.py +191 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Copyright (c) 2026 Morten Olav Hansen <morten@winterop.com>. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This source code and accompanying documentation are the property of
|
|
4
|
+
Morten Olav Hansen. No license, express or implied, is granted to use, copy,
|
|
5
|
+
modify, merge, publish, distribute, sublicense, or sell copies of this
|
|
6
|
+
software or its derivatives.
|
|
7
|
+
|
|
8
|
+
The source is published for reference only. Any use beyond reading
|
|
9
|
+
requires written permission from the copyright holder.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
12
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
13
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
14
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES,
|
|
15
|
+
OR OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
|
|
16
|
+
|
|
17
|
+
Third-party components redistributed with this software, and the licences they
|
|
18
|
+
carry, are listed in THIRD_PARTY_NOTICES.md.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dirigent-client
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: The dirigent API contract: typed wire schemas and an async Python client.
|
|
5
|
+
License-Expression: LicenseRef-Proprietary
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: dirigent-common
|
|
8
|
+
Requires-Dist: httpx2>=2.12.0
|
|
9
|
+
Requires-Dist: pydantic>=2.13.5
|
|
10
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# dirigent-client
|
|
15
|
+
|
|
16
|
+
The dirigent API contract: typed wire schemas and an async Python client.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from datetime import timedelta
|
|
20
|
+
|
|
21
|
+
from dirigent_client import Dirigent
|
|
22
|
+
|
|
23
|
+
async with Dirigent(url="https://dirigent.example.org", token=token) as dg:
|
|
24
|
+
plan = await dg.pipelines.apply("pipelines/daily-climate-load.yaml", dry_run=True)
|
|
25
|
+
run = await dg.pipelines.run("daily-climate-load", params={"day": "2026-08-29"})
|
|
26
|
+
final = await dg.runs.wait(run.run_id, timeout=timedelta(hours=2))
|
|
27
|
+
async for entry in dg.runs.follow_logs(run.run_id):
|
|
28
|
+
print(entry.message)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The package holds the pydantic schemas for every request and response the REST API speaks,
|
|
32
|
+
and `dirigent-server` imports them, so the client and the server can never disagree about a
|
|
33
|
+
shape. It depends on `dirigent-common`, `httpx2`, `pydantic`, and `pyyaml`, and on nothing else in the
|
|
34
|
+
workspace.
|
|
35
|
+
|
|
36
|
+
`docs/python.md` has the worked examples, and `examples/python/` has six runnable scripts.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# dirigent-client
|
|
2
|
+
|
|
3
|
+
The dirigent API contract: typed wire schemas and an async Python client.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
|
|
8
|
+
from dirigent_client import Dirigent
|
|
9
|
+
|
|
10
|
+
async with Dirigent(url="https://dirigent.example.org", token=token) as dg:
|
|
11
|
+
plan = await dg.pipelines.apply("pipelines/daily-climate-load.yaml", dry_run=True)
|
|
12
|
+
run = await dg.pipelines.run("daily-climate-load", params={"day": "2026-08-29"})
|
|
13
|
+
final = await dg.runs.wait(run.run_id, timeout=timedelta(hours=2))
|
|
14
|
+
async for entry in dg.runs.follow_logs(run.run_id):
|
|
15
|
+
print(entry.message)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The package holds the pydantic schemas for every request and response the REST API speaks,
|
|
19
|
+
and `dirigent-server` imports them, so the client and the server can never disagree about a
|
|
20
|
+
shape. It depends on `dirigent-common`, `httpx2`, `pydantic`, and `pyyaml`, and on nothing else in the
|
|
21
|
+
workspace.
|
|
22
|
+
|
|
23
|
+
`docs/python.md` has the worked examples, and `examples/python/` has six runnable scripts.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dirigent-client"
|
|
3
|
+
version = "0.9.0"
|
|
4
|
+
description = "The dirigent API contract: typed wire schemas and an async Python client."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
license = "LicenseRef-Proprietary"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"dirigent-common",
|
|
11
|
+
"httpx2>=2.12.0",
|
|
12
|
+
"pydantic>=2.13.5",
|
|
13
|
+
"pyyaml>=6.0.3",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.12.0,<0.13.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[tool.uv.sources]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dirigent-client"
|
|
3
|
+
version = "0.9.0"
|
|
4
|
+
description = "The dirigent API contract: typed wire schemas and an async Python client."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
license = "LicenseRef-Proprietary"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"dirigent-common",
|
|
11
|
+
"httpx2>=2.12.0",
|
|
12
|
+
"pydantic>=2.13.5",
|
|
13
|
+
"pyyaml>=6.0.3",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.12.0,<0.13.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[tool.uv.sources]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""The dirigent API contract: typed wire schemas, and an async client over them."""
|
|
2
|
+
|
|
3
|
+
from dirigent_client.client import BlockingDirigent, Dirigent
|
|
4
|
+
from dirigent_client.enums import (
|
|
5
|
+
AlertEvent,
|
|
6
|
+
AlertScope,
|
|
7
|
+
AttemptKind,
|
|
8
|
+
AttemptStatus,
|
|
9
|
+
DocumentKind,
|
|
10
|
+
FiringOutcome,
|
|
11
|
+
LogLevel,
|
|
12
|
+
NotificationStatus,
|
|
13
|
+
ProvenanceSource,
|
|
14
|
+
RunItemStatus,
|
|
15
|
+
RunPriority,
|
|
16
|
+
RunStatus,
|
|
17
|
+
ScheduleKind,
|
|
18
|
+
TokenKind,
|
|
19
|
+
TriggerKind,
|
|
20
|
+
UserRole,
|
|
21
|
+
WebhookOutcome,
|
|
22
|
+
WorkerStatus,
|
|
23
|
+
)
|
|
24
|
+
from dirigent_client.errors import (
|
|
25
|
+
VERSION_HEADER,
|
|
26
|
+
Conflict,
|
|
27
|
+
DirigentError,
|
|
28
|
+
Forbidden,
|
|
29
|
+
NotDirigent,
|
|
30
|
+
NotFound,
|
|
31
|
+
RateLimited,
|
|
32
|
+
ServerError,
|
|
33
|
+
TransportError,
|
|
34
|
+
Unauthorized,
|
|
35
|
+
ValidationFailed,
|
|
36
|
+
WaitTimeout,
|
|
37
|
+
)
|
|
38
|
+
from dirigent_client.schemas import (
|
|
39
|
+
TERMINAL_RUN_STATUSES,
|
|
40
|
+
AlertRuleIn,
|
|
41
|
+
AlertRuleOut,
|
|
42
|
+
ApplyRequest,
|
|
43
|
+
ApplyResult,
|
|
44
|
+
AttemptEvent,
|
|
45
|
+
AttemptOut,
|
|
46
|
+
BackfillAccepted,
|
|
47
|
+
BackfilledRun,
|
|
48
|
+
BackfillRequest,
|
|
49
|
+
BlockEntry,
|
|
50
|
+
BlockKind,
|
|
51
|
+
Catalog,
|
|
52
|
+
CheckResult,
|
|
53
|
+
CheckStatus,
|
|
54
|
+
ConnectionHealth,
|
|
55
|
+
ConnectionIn,
|
|
56
|
+
ConnectionOut,
|
|
57
|
+
ConnectionUpdate,
|
|
58
|
+
DagNode,
|
|
59
|
+
DagView,
|
|
60
|
+
DeliveryOut,
|
|
61
|
+
DiffSummary,
|
|
62
|
+
FiringOut,
|
|
63
|
+
Health,
|
|
64
|
+
HookAccepted,
|
|
65
|
+
Identity,
|
|
66
|
+
IssuedTokenOut,
|
|
67
|
+
ItemOut,
|
|
68
|
+
JsonList,
|
|
69
|
+
JsonMap,
|
|
70
|
+
LogEntryOut,
|
|
71
|
+
LoginRequest,
|
|
72
|
+
Materialized,
|
|
73
|
+
NotificationOut,
|
|
74
|
+
Page,
|
|
75
|
+
PipelineDetail,
|
|
76
|
+
PipelineOut,
|
|
77
|
+
PipelinePlan,
|
|
78
|
+
PipelineVersionOut,
|
|
79
|
+
PlanAction,
|
|
80
|
+
Problem,
|
|
81
|
+
Readiness,
|
|
82
|
+
RunAccepted,
|
|
83
|
+
RunDetail,
|
|
84
|
+
RunOut,
|
|
85
|
+
RunReport,
|
|
86
|
+
RunRequest,
|
|
87
|
+
ScheduleIn,
|
|
88
|
+
ScheduleOut,
|
|
89
|
+
SchedulePreview,
|
|
90
|
+
SchedulePreviewRequest,
|
|
91
|
+
SchemaIn,
|
|
92
|
+
SchemaOut,
|
|
93
|
+
SchemaUpdate,
|
|
94
|
+
StepReport,
|
|
95
|
+
SurfaceEntry,
|
|
96
|
+
SystemInfo,
|
|
97
|
+
TestQueued,
|
|
98
|
+
TestRequest,
|
|
99
|
+
TokenOut,
|
|
100
|
+
TokenRequest,
|
|
101
|
+
TriggerDocumentDetail,
|
|
102
|
+
TriggerDocumentOut,
|
|
103
|
+
UserIn,
|
|
104
|
+
UserOut,
|
|
105
|
+
ValidationIssue,
|
|
106
|
+
WebhookIn,
|
|
107
|
+
WebhookOut,
|
|
108
|
+
WebhookTokenOut,
|
|
109
|
+
WorkerOut,
|
|
110
|
+
)
|
|
111
|
+
from dirigent_client.transport import API_PREFIX, Transport
|
|
112
|
+
|
|
113
|
+
__all__ = [
|
|
114
|
+
"API_PREFIX",
|
|
115
|
+
"TERMINAL_RUN_STATUSES",
|
|
116
|
+
"VERSION_HEADER",
|
|
117
|
+
"AlertEvent",
|
|
118
|
+
"AlertRuleIn",
|
|
119
|
+
"AlertRuleOut",
|
|
120
|
+
"AlertScope",
|
|
121
|
+
"ApplyRequest",
|
|
122
|
+
"ApplyResult",
|
|
123
|
+
"BackfillAccepted",
|
|
124
|
+
"BackfillRequest",
|
|
125
|
+
"BackfilledRun",
|
|
126
|
+
"AttemptKind",
|
|
127
|
+
"AttemptEvent",
|
|
128
|
+
"AttemptOut",
|
|
129
|
+
"AttemptStatus",
|
|
130
|
+
"BlockEntry",
|
|
131
|
+
"BlockKind",
|
|
132
|
+
"BlockingDirigent",
|
|
133
|
+
"Catalog",
|
|
134
|
+
"CheckResult",
|
|
135
|
+
"CheckStatus",
|
|
136
|
+
"Conflict",
|
|
137
|
+
"ConnectionHealth",
|
|
138
|
+
"ConnectionIn",
|
|
139
|
+
"ConnectionOut",
|
|
140
|
+
"ConnectionUpdate",
|
|
141
|
+
"SchemaIn",
|
|
142
|
+
"SchemaOut",
|
|
143
|
+
"SchemaUpdate",
|
|
144
|
+
"DagNode",
|
|
145
|
+
"DagView",
|
|
146
|
+
"DocumentKind",
|
|
147
|
+
"DeliveryOut",
|
|
148
|
+
"DiffSummary",
|
|
149
|
+
"Dirigent",
|
|
150
|
+
"DirigentError",
|
|
151
|
+
"FiringOut",
|
|
152
|
+
"FiringOutcome",
|
|
153
|
+
"Forbidden",
|
|
154
|
+
"Health",
|
|
155
|
+
"HookAccepted",
|
|
156
|
+
"Identity",
|
|
157
|
+
"IssuedTokenOut",
|
|
158
|
+
"ItemOut",
|
|
159
|
+
"JsonList",
|
|
160
|
+
"JsonMap",
|
|
161
|
+
"LogEntryOut",
|
|
162
|
+
"LogLevel",
|
|
163
|
+
"LoginRequest",
|
|
164
|
+
"Materialized",
|
|
165
|
+
"NotDirigent",
|
|
166
|
+
"NotFound",
|
|
167
|
+
"NotificationOut",
|
|
168
|
+
"NotificationStatus",
|
|
169
|
+
"Page",
|
|
170
|
+
"PipelineDetail",
|
|
171
|
+
"PipelineOut",
|
|
172
|
+
"PipelinePlan",
|
|
173
|
+
"PipelineVersionOut",
|
|
174
|
+
"PlanAction",
|
|
175
|
+
"Problem",
|
|
176
|
+
"ProvenanceSource",
|
|
177
|
+
"RateLimited",
|
|
178
|
+
"Readiness",
|
|
179
|
+
"RunAccepted",
|
|
180
|
+
"RunDetail",
|
|
181
|
+
"RunItemStatus",
|
|
182
|
+
"RunOut",
|
|
183
|
+
"RunPriority",
|
|
184
|
+
"RunReport",
|
|
185
|
+
"RunRequest",
|
|
186
|
+
"RunStatus",
|
|
187
|
+
"ScheduleIn",
|
|
188
|
+
"ScheduleKind",
|
|
189
|
+
"ScheduleOut",
|
|
190
|
+
"SchedulePreview",
|
|
191
|
+
"SchedulePreviewRequest",
|
|
192
|
+
"ServerError",
|
|
193
|
+
"StepReport",
|
|
194
|
+
"SurfaceEntry",
|
|
195
|
+
"SystemInfo",
|
|
196
|
+
"TestQueued",
|
|
197
|
+
"TestRequest",
|
|
198
|
+
"TokenKind",
|
|
199
|
+
"TokenOut",
|
|
200
|
+
"TokenRequest",
|
|
201
|
+
"TriggerDocumentDetail",
|
|
202
|
+
"TriggerDocumentOut",
|
|
203
|
+
"Transport",
|
|
204
|
+
"TransportError",
|
|
205
|
+
"TriggerKind",
|
|
206
|
+
"Unauthorized",
|
|
207
|
+
"UserIn",
|
|
208
|
+
"UserOut",
|
|
209
|
+
"UserRole",
|
|
210
|
+
"ValidationFailed",
|
|
211
|
+
"ValidationIssue",
|
|
212
|
+
"WaitTimeout",
|
|
213
|
+
"WebhookIn",
|
|
214
|
+
"WebhookOut",
|
|
215
|
+
"WebhookOutcome",
|
|
216
|
+
"WebhookTokenOut",
|
|
217
|
+
"WorkerOut",
|
|
218
|
+
"WorkerStatus",
|
|
219
|
+
]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""The client itself: one connection, and one namespaced accessor per API resource."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import threading
|
|
5
|
+
from collections.abc import AsyncIterator, Awaitable, Iterator, Mapping
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
import httpx2
|
|
9
|
+
|
|
10
|
+
from dirigent_client.resources.alerts import Alerts
|
|
11
|
+
from dirigent_client.resources.auth import Admin, Auth
|
|
12
|
+
from dirigent_client.resources.blocks import Blocks
|
|
13
|
+
from dirigent_client.resources.connections import Connections
|
|
14
|
+
from dirigent_client.resources.pipelines import Pipelines
|
|
15
|
+
from dirigent_client.resources.runs import Runs
|
|
16
|
+
from dirigent_client.resources.schedules import Schedules
|
|
17
|
+
from dirigent_client.resources.schemas import Schemas
|
|
18
|
+
from dirigent_client.resources.system import System, Workers
|
|
19
|
+
from dirigent_client.resources.trigger_documents import TriggerDocuments
|
|
20
|
+
from dirigent_client.resources.webhooks import Webhooks
|
|
21
|
+
from dirigent_client.transport import API_PREFIX, DEFAULT_RETRIES, DEFAULT_TIMEOUT, Transport
|
|
22
|
+
|
|
23
|
+
SHUTDOWN_SECONDS = 5.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Dirigent:
|
|
27
|
+
"""An authenticated connection to one dirigent instance."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
url: str,
|
|
33
|
+
token: str | None = None,
|
|
34
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
35
|
+
retries: int = DEFAULT_RETRIES,
|
|
36
|
+
headers: Mapping[str, str] | None = None,
|
|
37
|
+
http_transport: httpx2.AsyncBaseTransport | None = None,
|
|
38
|
+
api_prefix: str = API_PREFIX,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Open a connection to an instance, with a bearer token when one is needed."""
|
|
41
|
+
self.transport = Transport(
|
|
42
|
+
url=url,
|
|
43
|
+
api_prefix=api_prefix,
|
|
44
|
+
token=token,
|
|
45
|
+
timeout=timeout,
|
|
46
|
+
retries=retries,
|
|
47
|
+
headers=headers,
|
|
48
|
+
http_transport=http_transport,
|
|
49
|
+
)
|
|
50
|
+
self.pipelines = Pipelines(self.transport)
|
|
51
|
+
self.runs = Runs(self.transport)
|
|
52
|
+
self.connections = Connections(self.transport)
|
|
53
|
+
self.schemas = Schemas(self.transport)
|
|
54
|
+
self.blocks = Blocks(self.transport)
|
|
55
|
+
self.schedules = Schedules(self.transport)
|
|
56
|
+
self.webhooks = Webhooks(self.transport)
|
|
57
|
+
self.trigger_documents = TriggerDocuments(self.transport)
|
|
58
|
+
self.alerts = Alerts(self.transport)
|
|
59
|
+
self.system = System(self.transport)
|
|
60
|
+
self.workers = Workers(self.transport)
|
|
61
|
+
self.auth = Auth(self.transport)
|
|
62
|
+
self.admin = Admin(self.transport)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def url(self) -> str:
|
|
66
|
+
"""Name the instance this client is bound to."""
|
|
67
|
+
return self.transport.url
|
|
68
|
+
|
|
69
|
+
async def __aenter__(self) -> Self:
|
|
70
|
+
"""Enter the client's context."""
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
async def __aexit__(self, *_: object) -> None:
|
|
74
|
+
"""Close the underlying connection pool."""
|
|
75
|
+
await self.aclose()
|
|
76
|
+
|
|
77
|
+
async def aclose(self) -> None:
|
|
78
|
+
"""Close the underlying connection pool."""
|
|
79
|
+
await self.transport.aclose()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BlockingDirigent:
|
|
83
|
+
"""The same client, driven from synchronous code."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
86
|
+
"""Open a connection and the loop its calls run on; the arguments are Dirigent's."""
|
|
87
|
+
self._loop = asyncio.new_event_loop()
|
|
88
|
+
self._thread = threading.Thread(target=self._loop.run_forever, name="dirigent-client", daemon=True)
|
|
89
|
+
self._thread.start()
|
|
90
|
+
self.client = Dirigent(**kwargs)
|
|
91
|
+
self.url = self.client.url
|
|
92
|
+
self.pipelines = self.client.pipelines
|
|
93
|
+
self.runs = self.client.runs
|
|
94
|
+
self.connections = self.client.connections
|
|
95
|
+
self.schemas = self.client.schemas
|
|
96
|
+
self.blocks = self.client.blocks
|
|
97
|
+
self.schedules = self.client.schedules
|
|
98
|
+
self.webhooks = self.client.webhooks
|
|
99
|
+
self.trigger_documents = self.client.trigger_documents
|
|
100
|
+
self.alerts = self.client.alerts
|
|
101
|
+
self.system = self.client.system
|
|
102
|
+
self.workers = self.client.workers
|
|
103
|
+
self.auth = self.client.auth
|
|
104
|
+
self.admin = self.client.admin
|
|
105
|
+
|
|
106
|
+
def __enter__(self) -> Self:
|
|
107
|
+
"""Enter the client's context."""
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def __exit__(self, *_: object) -> None:
|
|
111
|
+
"""Close the connection pool and stop the loop."""
|
|
112
|
+
self.close()
|
|
113
|
+
|
|
114
|
+
def call[T](self, awaitable: Awaitable[T]) -> T:
|
|
115
|
+
"""Run one of the client's coroutines to completion and return what it produced."""
|
|
116
|
+
return asyncio.run_coroutine_threadsafe(_awaited(awaitable), self._loop).result()
|
|
117
|
+
|
|
118
|
+
def iterate[T](self, source: AsyncIterator[T]) -> Iterator[T]:
|
|
119
|
+
"""Read an async iterator, such as a log tail, as an ordinary one."""
|
|
120
|
+
while True:
|
|
121
|
+
try:
|
|
122
|
+
yield self.call(anext(source))
|
|
123
|
+
except StopAsyncIteration:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
def close(self) -> None:
|
|
127
|
+
"""Close the connection pool and stop the loop it was running on.
|
|
128
|
+
|
|
129
|
+
The asynchronous generators must be shut down before the loop is: a generator
|
|
130
|
+
finalised after its loop has closed raises where nobody is listening.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
self.call(self.client.aclose())
|
|
134
|
+
self.call(self._loop.shutdown_asyncgens())
|
|
135
|
+
finally:
|
|
136
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
137
|
+
self._thread.join(timeout=SHUTDOWN_SECONDS)
|
|
138
|
+
self._loop.close()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def _awaited[T](awaitable: Awaitable[T]) -> T:
|
|
142
|
+
"""Wrap an awaitable as the coroutine ``run_coroutine_threadsafe`` requires."""
|
|
143
|
+
return await awaitable
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""The closed vocabularies the engine's state machines are built from."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RunStatus(StrEnum):
|
|
7
|
+
"""Where a run is."""
|
|
8
|
+
|
|
9
|
+
QUEUED = "queued"
|
|
10
|
+
RUNNING = "running"
|
|
11
|
+
SUCCEEDED = "succeeded"
|
|
12
|
+
COMPLETED_WITH_ERRORS = "completed_with_errors"
|
|
13
|
+
FAILED = "failed"
|
|
14
|
+
CANCELLED = "cancelled"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AttemptStatus(StrEnum):
|
|
18
|
+
"""The step-attempt state machine."""
|
|
19
|
+
|
|
20
|
+
PENDING = "pending"
|
|
21
|
+
"""Waiting on its depends_on edges; not yet claimable."""
|
|
22
|
+
|
|
23
|
+
QUEUED = "queued"
|
|
24
|
+
"""Claimable once available_at has passed."""
|
|
25
|
+
|
|
26
|
+
RUNNING = "running"
|
|
27
|
+
"""Claimed by a worker, holding a lease."""
|
|
28
|
+
|
|
29
|
+
WAITING = "waiting"
|
|
30
|
+
"""Not on a worker: parked with a remote handle or a pending poke, re-examined when
|
|
31
|
+
next_poll_at is due."""
|
|
32
|
+
|
|
33
|
+
SUCCEEDED = "succeeded"
|
|
34
|
+
FAILED = "failed"
|
|
35
|
+
SKIPPED = "skipped"
|
|
36
|
+
CANCELLED = "cancelled"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AttemptKind(StrEnum):
|
|
40
|
+
"""Whether the engine created an attempt or an operator asked for it."""
|
|
41
|
+
|
|
42
|
+
AUTOMATIC = "automatic"
|
|
43
|
+
MANUAL = "manual"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RunItemStatus(StrEnum):
|
|
47
|
+
"""Per-item status of a fan-out step."""
|
|
48
|
+
|
|
49
|
+
PENDING = "pending"
|
|
50
|
+
RUNNING = "running"
|
|
51
|
+
SUCCEEDED = "succeeded"
|
|
52
|
+
FAILED = "failed"
|
|
53
|
+
SKIPPED = "skipped"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RunPriority(StrEnum):
|
|
57
|
+
"""How far ahead of the other runs a run's attempts are claimed."""
|
|
58
|
+
|
|
59
|
+
LOW = "low"
|
|
60
|
+
"""Claimed after everything else, which is where a bulk job belongs."""
|
|
61
|
+
|
|
62
|
+
NORMAL = "normal"
|
|
63
|
+
|
|
64
|
+
HIGH = "high"
|
|
65
|
+
"""Claimed before any other run's attempts, the moment a slot frees."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
#: What each priority is worth to the claim's sort, highest first.
|
|
69
|
+
PRIORITY_RANK: dict[RunPriority, int] = {RunPriority.LOW: 0, RunPriority.NORMAL: 1, RunPriority.HIGH: 2}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TriggerKind(StrEnum):
|
|
73
|
+
"""What started a run."""
|
|
74
|
+
|
|
75
|
+
ADHOC = "adhoc"
|
|
76
|
+
SCHEDULE = "schedule"
|
|
77
|
+
WEBHOOK = "webhook"
|
|
78
|
+
API_TOKEN = "api_token"
|
|
79
|
+
USER = "user"
|
|
80
|
+
PIPELINE = "pipeline"
|
|
81
|
+
"""A step of another run started this one, and ``triggered_by_id`` names that run."""
|
|
82
|
+
|
|
83
|
+
BACKFILL = "backfill"
|
|
84
|
+
"""A backfill over past windows created this one, and ``triggered_by_id`` names the schedule."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ScheduleKind(StrEnum):
|
|
88
|
+
"""How a schedule computes its next firing."""
|
|
89
|
+
|
|
90
|
+
CRON = "cron"
|
|
91
|
+
INTERVAL = "interval"
|
|
92
|
+
ONE_TIME = "one_time"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class AlertEvent(StrEnum):
|
|
96
|
+
"""The run-level events an alert rule may bind to."""
|
|
97
|
+
|
|
98
|
+
RUN_FAILED = "run_failed"
|
|
99
|
+
RUN_COMPLETED_WITH_ERRORS = "run_completed_with_errors"
|
|
100
|
+
RUN_SUCCEEDED = "run_succeeded"
|
|
101
|
+
RUN_STUCK = "run_stuck"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class AlertScope(StrEnum):
|
|
105
|
+
"""Whether an alert rule watches everything or one pipeline."""
|
|
106
|
+
|
|
107
|
+
GLOBAL = "global"
|
|
108
|
+
PIPELINE = "pipeline"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class NotificationStatus(StrEnum):
|
|
112
|
+
"""Where one queued alert delivery is."""
|
|
113
|
+
|
|
114
|
+
PENDING = "pending"
|
|
115
|
+
"""Queued and claimable once available_at has passed."""
|
|
116
|
+
|
|
117
|
+
SENDING = "sending"
|
|
118
|
+
"""Claimed by a worker, holding a lease."""
|
|
119
|
+
|
|
120
|
+
SENT = "sent"
|
|
121
|
+
"""The notifier accepted it."""
|
|
122
|
+
|
|
123
|
+
FAILED = "failed"
|
|
124
|
+
"""Terminally undeliverable: the retry budget is gone, and the error is on the row."""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class FiringOutcome(StrEnum):
|
|
128
|
+
"""What one scheduler tick decided about one due schedule."""
|
|
129
|
+
|
|
130
|
+
FIRED = "fired"
|
|
131
|
+
"""A run was created and is executing."""
|
|
132
|
+
|
|
133
|
+
QUEUED = "queued"
|
|
134
|
+
"""A run was created but is held behind the one already in flight."""
|
|
135
|
+
|
|
136
|
+
REPLACED = "replaced"
|
|
137
|
+
"""The run in flight was cancelled and a new one took its place."""
|
|
138
|
+
|
|
139
|
+
SKIPPED = "skipped"
|
|
140
|
+
"""The concurrency policy dropped this firing; the run in flight is enough."""
|
|
141
|
+
|
|
142
|
+
FAILED = "failed"
|
|
143
|
+
"""The run could not be created at all, and the reason is on the row."""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class WebhookOutcome(StrEnum):
|
|
147
|
+
"""What the intake endpoint did with one delivery."""
|
|
148
|
+
|
|
149
|
+
ACCEPTED = "accepted"
|
|
150
|
+
"""The payload mapped, validated, and started a run."""
|
|
151
|
+
|
|
152
|
+
SKIPPED = "skipped"
|
|
153
|
+
"""The payload was good, but the pipeline's concurrency policy dropped the run."""
|
|
154
|
+
|
|
155
|
+
REJECTED = "rejected"
|
|
156
|
+
"""The delivery was refused, and the reason is on the row."""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class LogLevel(StrEnum):
|
|
160
|
+
"""Severity of a product log entry, which is separate from process logging."""
|
|
161
|
+
|
|
162
|
+
DEBUG = "debug"
|
|
163
|
+
INFO = "info"
|
|
164
|
+
WARNING = "warning"
|
|
165
|
+
ERROR = "error"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class DocumentKind(StrEnum):
|
|
169
|
+
"""Which kind of ``dirigent/v1`` document an apply carried."""
|
|
170
|
+
|
|
171
|
+
PIPELINE = "pipeline"
|
|
172
|
+
TRIGGERS = "triggers"
|
|
173
|
+
"""Clocks and webhooks for a pipeline defined somewhere else."""
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class ProvenanceSource(StrEnum):
|
|
177
|
+
"""Where a pipeline version came from, recorded when it is applied."""
|
|
178
|
+
|
|
179
|
+
UI = "ui"
|
|
180
|
+
FILE = "file"
|
|
181
|
+
URL = "url"
|
|
182
|
+
API = "api"
|
|
183
|
+
DIRECTORY = "directory"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class UserRole(StrEnum):
|
|
187
|
+
"""What an account may do."""
|
|
188
|
+
|
|
189
|
+
ADMIN = "admin"
|
|
190
|
+
"""Full access: definitions, connections, runs, users, and tokens."""
|
|
191
|
+
|
|
192
|
+
OPERATOR = "operator"
|
|
193
|
+
"""Define, apply, run, cancel and schedule, but not manage accounts, tokens or connections."""
|
|
194
|
+
|
|
195
|
+
VIEWER = "viewer"
|
|
196
|
+
"""Read-only: every listing, run, log and report, and nothing that writes."""
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TokenKind(StrEnum):
|
|
200
|
+
"""Whether a stored credential is an automation token or a browser session."""
|
|
201
|
+
|
|
202
|
+
API = "api"
|
|
203
|
+
"""A bearer token an operator created for a script or a CI job."""
|
|
204
|
+
|
|
205
|
+
SESSION = "session"
|
|
206
|
+
"""A cookie-borne session minted by a login, expiring on its own."""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class WorkerStatus(StrEnum):
|
|
210
|
+
"""What the worker registry last heard from a node."""
|
|
211
|
+
|
|
212
|
+
STARTING = "starting"
|
|
213
|
+
RUNNING = "running"
|
|
214
|
+
DRAINING = "draining"
|
|
215
|
+
STOPPED = "stopped"
|