dirigent-client 0.23.4__tar.gz → 0.25.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.
Files changed (42) hide show
  1. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/PKG-INFO +2 -2
  2. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/pyproject.toml +2 -2
  3. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/pyproject.toml.orig +2 -2
  4. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/__init__.py +24 -0
  5. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/client.py +3 -0
  6. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/enums.py +52 -1
  7. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/pipelines.py +8 -0
  8. dirigent_client-0.25.0/src/dirigent_client/resources/sources.py +42 -0
  9. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/__init__.py +22 -5
  10. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/catalog.py +31 -0
  11. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/pipelines.py +19 -1
  12. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/runs.py +16 -0
  13. dirigent_client-0.25.0/src/dirigent_client/schemas/sources.py +164 -0
  14. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/LICENSE +0 -0
  15. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/README.md +0 -0
  16. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/errors.py +0 -0
  17. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/messages.py +0 -0
  18. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/py.typed +0 -0
  19. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/__init__.py +0 -0
  20. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/alerts.py +0 -0
  21. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/auth.py +0 -0
  22. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/base.py +0 -0
  23. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/blocks.py +0 -0
  24. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/connections.py +0 -0
  25. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/examples.py +0 -0
  26. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/runs.py +0 -0
  27. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/schedules.py +0 -0
  28. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/schemas.py +0 -0
  29. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/system.py +0 -0
  30. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/trigger_documents.py +0 -0
  31. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/watches.py +0 -0
  32. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/resources/webhooks.py +0 -0
  33. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/alerts.py +0 -0
  34. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/auth.py +0 -0
  35. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/common.py +0 -0
  36. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/connections.py +0 -0
  37. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/examples.py +0 -0
  38. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/labels.py +0 -0
  39. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/schemas.py +0 -0
  40. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/system.py +0 -0
  41. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/schemas/triggers.py +0 -0
  42. {dirigent_client-0.23.4 → dirigent_client-0.25.0}/src/dirigent_client/transport.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirigent-client
3
- Version: 0.23.4
3
+ Version: 0.25.0
4
4
  Summary: The dirigent API contract: typed wire schemas and an async Python client.
5
5
  License-Expression: LicenseRef-Proprietary
6
6
  License-File: LICENSE
7
7
  Classifier: Programming Language :: Python :: 3
8
8
  Classifier: Programming Language :: Python :: 3.13
9
- Requires-Dist: dirigent-common==0.23.4
9
+ Requires-Dist: dirigent-common==0.25.0
10
10
  Requires-Dist: httpx2>=2.12.0
11
11
  Requires-Dist: pydantic>=2.13.5
12
12
  Requires-Dist: pyyaml>=6.0.3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dirigent-client"
3
- version = "0.23.4"
3
+ version = "0.25.0"
4
4
  description = "The dirigent API contract: typed wire schemas and an async Python client."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -11,7 +11,7 @@ classifiers = [
11
11
  "Programming Language :: Python :: 3.13",
12
12
  ]
13
13
  dependencies = [
14
- "dirigent-common==0.23.4",
14
+ "dirigent-common==0.25.0",
15
15
  "httpx2>=2.12.0",
16
16
  "pydantic>=2.13.5",
17
17
  "pyyaml>=6.0.3",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dirigent-client"
3
- version = "0.23.4"
3
+ version = "0.25.0"
4
4
  description = "The dirigent API contract: typed wire schemas and an async Python client."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -11,7 +11,7 @@ classifiers = [
11
11
  "Programming Language :: Python :: 3.13",
12
12
  ]
13
13
  dependencies = [
14
- "dirigent-common==0.23.4",
14
+ "dirigent-common==0.25.0",
15
15
  "httpx2>=2.12.0",
16
16
  "pydantic>=2.13.5",
17
17
  "pyyaml>=6.0.3",
@@ -6,9 +6,11 @@ from dirigent_client.enums import (
6
6
  AlertScope,
7
7
  AttemptKind,
8
8
  AttemptStatus,
9
+ BodyKind,
9
10
  DocumentKind,
10
11
  FiringOutcome,
11
12
  Importance,
13
+ InputOutcome,
12
14
  LogLevel,
13
15
  NotificationStatus,
14
16
  ProvenanceSource,
@@ -16,6 +18,8 @@ from dirigent_client.enums import (
16
18
  RunPriority,
17
19
  RunStatus,
18
20
  ScheduleKind,
21
+ SourceState,
22
+ StepKind,
19
23
  TokenKind,
20
24
  TriggerKind,
21
25
  UserRole,
@@ -66,12 +70,14 @@ from dirigent_client.schemas import (
66
70
  DagView,
67
71
  DeliveryOut,
68
72
  DiffSummary,
73
+ EndpointInput,
69
74
  ExampleDetail,
70
75
  ExampleOut,
71
76
  FiringOut,
72
77
  Health,
73
78
  HookAccepted,
74
79
  Identity,
80
+ InputAccepted,
75
81
  IssuedTokenOut,
76
82
  ItemOut,
77
83
  JsonList,
@@ -88,6 +94,7 @@ from dirigent_client.schemas import (
88
94
  PlanAction,
89
95
  Problem,
90
96
  Readiness,
97
+ RequestFacts,
91
98
  Requirements,
92
99
  RunAccepted,
93
100
  RunDetail,
@@ -101,6 +108,11 @@ from dirigent_client.schemas import (
101
108
  SchemaIn,
102
109
  SchemaOut,
103
110
  SchemaUpdate,
111
+ SourceChanges,
112
+ SourceEntry,
113
+ SourceInputOut,
114
+ SourceOut,
115
+ SourceTokenOut,
104
116
  StepReport,
105
117
  SurfaceEntry,
106
118
  SystemInfo,
@@ -165,6 +177,17 @@ __all__ = [
165
177
  "ConnectionOut",
166
178
  "ConnectionUpdate",
167
179
  "SchemaIn",
180
+ "RequestFacts",
181
+ "EndpointInput",
182
+ "InputAccepted",
183
+ "InputOutcome",
184
+ "SourceChanges",
185
+ "SourceInputOut",
186
+ "SourceOut",
187
+ "SourceState",
188
+ "SourceTokenOut",
189
+ "StepKind",
190
+ "BodyKind",
168
191
  "SchemaOut",
169
192
  "SchemaUpdate",
170
193
  "DagNode",
@@ -221,6 +244,7 @@ __all__ = [
221
244
  "SchedulePreviewRequest",
222
245
  "ServerError",
223
246
  "StepReport",
247
+ "SourceEntry",
224
248
  "SurfaceEntry",
225
249
  "SystemInfo",
226
250
  "TestQueued",
@@ -16,6 +16,7 @@ from dirigent_client.resources.pipelines import Pipelines
16
16
  from dirigent_client.resources.runs import Runs
17
17
  from dirigent_client.resources.schedules import Schedules
18
18
  from dirigent_client.resources.schemas import Schemas
19
+ from dirigent_client.resources.sources import Sources
19
20
  from dirigent_client.resources.system import System, Workers
20
21
  from dirigent_client.resources.trigger_documents import TriggerDocuments
21
22
  from dirigent_client.resources.watches import Watches
@@ -66,6 +67,7 @@ class Dirigent:
66
67
  self.schedules = Schedules(self.transport)
67
68
  self.webhooks = Webhooks(self.transport)
68
69
  self.watches = Watches(self.transport)
70
+ self.sources = Sources(self.transport)
69
71
  self.trigger_documents = TriggerDocuments(self.transport)
70
72
  self.alerts = Alerts(self.transport)
71
73
  self.system = System(self.transport)
@@ -110,6 +112,7 @@ class BlockingDirigent:
110
112
  self.schedules = self.client.schedules
111
113
  self.webhooks = self.client.webhooks
112
114
  self.watches = self.client.watches
115
+ self.sources = self.client.sources
113
116
  self.trigger_documents = self.client.trigger_documents
114
117
  self.alerts = self.client.alerts
115
118
  self.system = self.client.system
@@ -37,10 +37,23 @@ class AttemptStatus(StrEnum):
37
37
 
38
38
 
39
39
  class AttemptKind(StrEnum):
40
- """Whether the engine created an attempt or an operator asked for it."""
40
+ """Whether the engine created an attempt, an operator asked for it, or an input arrived as it."""
41
41
 
42
42
  AUTOMATIC = "automatic"
43
43
  MANUAL = "manual"
44
+ RECEIVED = "received"
45
+ """A source node's attempt: written succeeded when the run is created, its output the input."""
46
+
47
+
48
+ class StepKind(StrEnum):
49
+ """Which of the three forms a step takes: work on a worker, an entry, or an answer."""
50
+
51
+ BLOCK = "block"
52
+ SOURCE = "source"
53
+ """Where an input enters the pipeline; settled by the server when the run is created."""
54
+
55
+ REPLY = "reply"
56
+ """A leaf that answers the caller an input arrived from; settled by the server, never claimed."""
44
57
 
45
58
 
46
59
  class RunItemStatus(StrEnum):
@@ -102,6 +115,9 @@ class TriggerKind(StrEnum):
102
115
  WATCH = "watch"
103
116
  """A watch armed this one to wait on its sensor, and ``triggered_by_id`` names the watch."""
104
117
 
118
+ SOURCE = "source"
119
+ """An input arrived at a source node, and ``triggered_by_id`` names the source."""
120
+
105
121
 
106
122
  class ScheduleKind(StrEnum):
107
123
  """How a schedule computes its next firing."""
@@ -175,6 +191,41 @@ class WebhookOutcome(StrEnum):
175
191
  """The delivery was refused, and the reason is on the row."""
176
192
 
177
193
 
194
+ class SourceState(StrEnum):
195
+ """Whether a source takes inputs now, read from its row and the runs it has unsettled."""
196
+
197
+ LISTENING = "listening"
198
+ """It takes inputs."""
199
+
200
+ BACKLOGGED = "backlogged"
201
+ """As many runs from it are unsettled as ``max_in_flight`` allows; a new input is refused 503."""
202
+
203
+ DRAINING = "draining"
204
+ """It or its pipeline is paused, and runs it started are still unsettled."""
205
+
206
+ PAUSED = "paused"
207
+ """It or its pipeline is paused, and nothing it started is unsettled."""
208
+
209
+
210
+ class InputOutcome(StrEnum):
211
+ """What came of one input that reached a source."""
212
+
213
+ ACCEPTED = "accepted"
214
+ """It became a run, and the caller was answered with where to read it."""
215
+
216
+ REFUSED = "refused"
217
+ """It was refused before any run existed, and the reason is on the row."""
218
+
219
+
220
+ class BodyKind(StrEnum):
221
+ """How an inbound request's body was read."""
222
+
223
+ NONE = "none"
224
+ JSON = "json"
225
+ FORM = "form"
226
+ TEXT = "text"
227
+
228
+
178
229
  class LogLevel(StrEnum):
179
230
  """Severity of a product log entry, which is separate from process logging."""
180
231
 
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from typing import cast
8
8
 
9
9
  import yaml
10
+ from pydantic import JsonValue
10
11
 
11
12
  from dirigent_client.enums import LogLevel, ProvenanceSource, RunPriority
12
13
  from dirigent_client.errors import DirigentError
@@ -137,6 +138,8 @@ class Pipelines(Resource):
137
138
  window: tuple[datetime, datetime] | None = None,
138
139
  log_levels: dict[str, LogLevel] | None = None,
139
140
  priority: RunPriority | None = None,
141
+ source: str | None = None,
142
+ input: JsonValue = None,
140
143
  ) -> RunAccepted:
141
144
  """Start an ad hoc run, with parameters validated against the pipeline's own schema.
142
145
 
@@ -151,9 +154,14 @@ class Pipelines(Resource):
151
154
 
152
155
  ``priority`` is how far ahead of other runs this one's attempts are claimed; omitted
153
156
  takes the pipeline's own.
157
+
158
+ ``source`` is the source a run of a pipeline with sources enters at, omitted for its only
159
+ one, and ``input`` is what arrives there, omitted for the source's sample.
154
160
  """
155
161
  payload = RunRequest(
156
162
  params=params or {},
163
+ source=source,
164
+ input=input,
157
165
  window_start=window[0] if window else None,
158
166
  window_end=window[1] if window else None,
159
167
  log_levels=log_levels,
@@ -0,0 +1,42 @@
1
+ """A pipeline's sources: what each listens to, its state, what has arrived, and its token."""
2
+
3
+ from dirigent_client.resources.base import Resource, query
4
+ from dirigent_client.schemas import Page, SourceInputOut, SourceOut, SourceTokenOut
5
+
6
+
7
+ class Sources(Resource):
8
+ """Read, pause, resume and rotate the sources a pipeline's document declares."""
9
+
10
+ async def list(
11
+ self, pipeline: str | None = None, *, after: str | None = None, limit: int | None = None
12
+ ) -> Page[SourceOut]:
13
+ """List one pipeline's sources, or every pipeline's, never their tokens."""
14
+ path = f"/pipelines/{pipeline}/sources" if pipeline is not None else "/sources"
15
+ return await self._many(SourceOut, "GET", path, params=query(after=after, limit=limit))
16
+
17
+ async def get(self, pipeline: str, code: str) -> SourceOut:
18
+ """Read one source by its step code, never its token."""
19
+ return await self._one(SourceOut, "GET", f"/pipelines/{pipeline}/sources/{code}")
20
+
21
+ async def pause(self, pipeline: str, code: str) -> SourceOut:
22
+ """Refuse new inputs with 503, keeping the token and the counts."""
23
+ return await self._one(SourceOut, "POST", f"/pipelines/{pipeline}/sources/{code}/$pause")
24
+
25
+ async def resume(self, pipeline: str, code: str) -> SourceOut:
26
+ """Take inputs again on the token that was already issued."""
27
+ return await self._one(SourceOut, "POST", f"/pipelines/{pipeline}/sources/{code}/$resume")
28
+
29
+ async def rotate_token(self, pipeline: str, code: str) -> SourceTokenOut:
30
+ """Mint a new token and forget the old one immediately; every caller has to be given it."""
31
+ return await self._one(SourceTokenOut, "POST", f"/pipelines/{pipeline}/sources/{code}/$rotate-token")
32
+
33
+ async def inputs(
34
+ self, pipeline: str, code: str, *, after: str | None = None, limit: int | None = None
35
+ ) -> Page[SourceInputOut]:
36
+ """Read what has arrived, newest first, refusals included."""
37
+ return await self._many(
38
+ SourceInputOut,
39
+ "GET",
40
+ f"/pipelines/{pipeline}/sources/{code}/inputs",
41
+ params=query(after=after, limit=limit),
42
+ )
@@ -23,7 +23,7 @@ from dirigent_client.schemas.auth import (
23
23
  UserOut,
24
24
  UserUpdate,
25
25
  )
26
- from dirigent_client.schemas.catalog import BlockEntry, BlockKind, Catalog, SurfaceEntry
26
+ from dirigent_client.schemas.catalog import BlockEntry, BlockKind, Catalog, SourceEntry, SurfaceEntry
27
27
  from dirigent_client.schemas.common import Page, Problem
28
28
  from dirigent_client.schemas.connections import ConnectionIn, ConnectionOut, ConnectionUpdate
29
29
  from dirigent_client.schemas.examples import ExampleDetail, ExampleOut
@@ -64,6 +64,15 @@ from dirigent_client.schemas.runs import (
64
64
  StepReport,
65
65
  )
66
66
  from dirigent_client.schemas.schemas import SchemaIn, SchemaOut, SchemaUpdate
67
+ from dirigent_client.schemas.sources import (
68
+ EndpointInput,
69
+ InputAccepted,
70
+ RequestFacts,
71
+ SourceChanges,
72
+ SourceInputOut,
73
+ SourceOut,
74
+ SourceTokenOut,
75
+ )
67
76
  from dirigent_client.schemas.system import (
68
77
  CheckResult,
69
78
  CheckStatus,
@@ -100,8 +109,8 @@ __all__ = [
100
109
  "AttemptEvent",
101
110
  "AttemptOut",
102
111
  "BackfillAccepted",
103
- "BackfilledRun",
104
112
  "BackfillRequest",
113
+ "BackfilledRun",
105
114
  "BlockEntry",
106
115
  "BlockKind",
107
116
  "Catalog",
@@ -115,6 +124,8 @@ __all__ = [
115
124
  "DagView",
116
125
  "DeliveryOut",
117
126
  "DiffSummary",
127
+ "EndpointInput",
128
+ "InputAccepted",
118
129
  "ExampleDetail",
119
130
  "ExampleOut",
120
131
  "FiringOut",
@@ -125,11 +136,9 @@ __all__ = [
125
136
  "ItemOut",
126
137
  "JsonList",
127
138
  "JsonMap",
139
+ "LOG_NOTIFIER",
128
140
  "Labels",
129
141
  "LastRun",
130
- "LOG_NOTIFIER",
131
- "TEST_BODY",
132
- "TEST_SUBJECT",
133
142
  "LogEntryOut",
134
143
  "LoginRequest",
135
144
  "Materialized",
@@ -146,6 +155,11 @@ __all__ = [
146
155
  "PruneRequest",
147
156
  "PruneResult",
148
157
  "Readiness",
158
+ "RequestFacts",
159
+ "SourceChanges",
160
+ "SourceInputOut",
161
+ "SourceOut",
162
+ "SourceTokenOut",
149
163
  "Requirements",
150
164
  "RunAccepted",
151
165
  "RunDetail",
@@ -159,10 +173,13 @@ __all__ = [
159
173
  "SchemaIn",
160
174
  "SchemaOut",
161
175
  "SchemaUpdate",
176
+ "SourceEntry",
162
177
  "StepReport",
163
178
  "SurfaceEntry",
164
179
  "SystemInfo",
165
180
  "TERMINAL_RUN_STATUSES",
181
+ "TEST_BODY",
182
+ "TEST_SUBJECT",
166
183
  "TestQueued",
167
184
  "TestRequest",
168
185
  "TokenOut",
@@ -49,6 +49,31 @@ class SurfaceEntry(BaseModel):
49
49
  declares one. Monochrome: a screen fills it in the ink it draws every other mark in."""
50
50
 
51
51
 
52
+ class SourceEntry(BaseModel):
53
+ """One source kind in the catalog: where an input enters a pipeline, and what it hands on."""
54
+
55
+ kind: str
56
+ """What a document writes as ``source.kind``."""
57
+
58
+ plugin: str
59
+ summary: str
60
+ hosting: str
61
+ """Who takes the kind's inputs: ``received`` by the server, ``polled`` or ``held`` by a worker."""
62
+
63
+ replies: bool = False
64
+ """Whether a reply node may answer an input of this kind."""
65
+
66
+ config_schema: JsonMap = Field(default_factory=dict)
67
+ """The fields a source node of this kind writes beside ``kind``."""
68
+
69
+ output_schema: JsonMap = Field(default_factory=dict)
70
+ """The input a source node of this kind hands the step after it."""
71
+
72
+ secret_fields: list[str] = Field(default_factory=list[str])
73
+ mark: str | None = None
74
+ """The ``d`` of one SVG path on a 24-unit grid this kind is drawn by, where it declares one."""
75
+
76
+
52
77
  class Catalog(BaseModel):
53
78
  """The host's merged view of every contribution."""
54
79
 
@@ -58,11 +83,17 @@ class Catalog(BaseModel):
58
83
  storage_schemes: list[SurfaceEntry] = Field(default_factory=list[SurfaceEntry])
59
84
  notifiers: list[SurfaceEntry] = Field(default_factory=list[SurfaceEntry])
60
85
  connection_kinds: list[SurfaceEntry] = Field(default_factory=list[SurfaceEntry])
86
+ sources: list[SourceEntry] = Field(default_factory=list[SourceEntry])
87
+ """The source kinds a source node may write."""
61
88
 
62
89
  def block(self, block_id: str) -> BlockEntry | None:
63
90
  """Find one catalog entry by block id."""
64
91
  return next((entry for entry in self.blocks if entry.id == block_id), None)
65
92
 
93
+ def source(self, kind: str) -> SourceEntry | None:
94
+ """Find one source kind by the ``kind`` a document writes."""
95
+ return next((entry for entry in self.sources if entry.kind == kind), None)
96
+
66
97
  @property
67
98
  def digest(self) -> str:
68
99
  """Hash the catalog, so a worker registry row can prove code parity across nodes."""
@@ -5,7 +5,7 @@ from enum import StrEnum
5
5
  from typing import Any
6
6
  from uuid import UUID
7
7
 
8
- from pydantic import AliasChoices, BaseModel, ConfigDict, Field, field_validator, model_validator
8
+ from pydantic import AliasChoices, BaseModel, ConfigDict, Field, JsonValue, field_validator, model_validator
9
9
 
10
10
  from dirigent_client.enums import DocumentKind, Importance, LogLevel, ProvenanceSource, RunPriority, RunStatus
11
11
  from dirigent_client.messages import (
@@ -15,6 +15,7 @@ from dirigent_client.messages import (
15
15
  WINDOW_RUNS_FORWARDS,
16
16
  )
17
17
  from dirigent_client.schemas.common import WireModel
18
+ from dirigent_client.schemas.sources import SourceChanges
18
19
  from dirigent_common import EntityName, Issue, JsonMap, Message
19
20
 
20
21
 
@@ -141,6 +142,20 @@ class RunRequest(BaseModel):
141
142
  """Ad hoc run parameters, validated against the pipeline's own schema."""
142
143
 
143
144
  params: JsonMap = Field(default_factory=dict)
145
+ source: str | None = Field(
146
+ default=None,
147
+ description=(
148
+ "The source node a run of a pipeline with sources enters at. Omitted names the "
149
+ "pipeline's only source, and a pipeline with several refuses the run."
150
+ ),
151
+ )
152
+ input: JsonValue = Field(
153
+ default=None,
154
+ description=(
155
+ "What arrives at that source, in the shape of its output; what it omits the kind "
156
+ "fills in. Omitted takes the source's sample."
157
+ ),
158
+ )
144
159
  priority: RunPriority | None = Field(
145
160
  default=None,
146
161
  description=(
@@ -381,3 +396,6 @@ class ApplyResult(WireModel):
381
396
  dry_run: bool = False
382
397
  triggers: Materialized = Field(default_factory=Materialized)
383
398
  """Which schedules, webhooks and watches the document's ``triggers:`` section created, changed, or retired."""
399
+
400
+ sources: SourceChanges = Field(default_factory=SourceChanges)
401
+ """Which source rows the apply created, changed, or retired, and the tokens it minted."""
@@ -12,6 +12,7 @@ from dirigent_client.enums import (
12
12
  RunItemStatus,
13
13
  RunPriority,
14
14
  RunStatus,
15
+ StepKind,
15
16
  TriggerKind,
16
17
  )
17
18
  from dirigent_client.schemas.common import WireModel
@@ -42,7 +43,13 @@ class RunOut(WireModel):
42
43
  triggered_by_kind: TriggerKind
43
44
  parent_run_id: UUID | None = None
44
45
  """The run whose step started this one, when a step of another run did."""
46
+ triggered_by_id: UUID | None = None
47
+ """What started the run: the source, schedule, webhook, watch, token or user, as its id."""
48
+
45
49
  triggered_by_label: str | None = None
50
+ source: str | None = None
51
+ """The source node a run of a pipeline with sources entered at, by its step code."""
52
+
46
53
  trace_id: str | None = None
47
54
  error: str | None = None
48
55
  """What went wrong: the reason a cancel gave, else what the first failed attempt said."""
@@ -158,7 +165,12 @@ class DagNode(WireModel):
158
165
  name: str | None = None
159
166
  """The step's human title, when the document gave it one."""
160
167
 
168
+ kind: StepKind = StepKind.BLOCK
169
+ """Which form the step takes: a block, a source an input enters at, or a reply."""
170
+
161
171
  block: str
172
+ """The block a block step runs, the kind of a source, or ``reply``."""
173
+
162
174
  outcome: str
163
175
  depends_on: list[str] = Field(default_factory=list[str])
164
176
  rule: str
@@ -166,6 +178,10 @@ class DagNode(WireModel):
166
178
  grid_source: str | None = None
167
179
  """The step a late grid waits for before it expands, or null for a grid fixed at creation."""
168
180
 
181
+ item_wise_parent: str | None = None
182
+ """The fan-out whose matching item each of this step's items waits for, one by one, or null
183
+ when the step waits for its prerequisites whole."""
184
+
169
185
  items_total: int = 0
170
186
  items_failed: int = 0
171
187
  attempts: int = 0
@@ -0,0 +1,164 @@
1
+ """Sources: what an input arrives as, the source rows that take inputs, and their journal."""
2
+
3
+ from datetime import datetime
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field, JsonValue
7
+
8
+ from dirigent_client.enums import BodyKind, InputOutcome, SourceState
9
+ from dirigent_client.schemas.common import WireModel
10
+ from dirigent_common import JsonMap
11
+
12
+
13
+ class RequestFacts(BaseModel):
14
+ """What an HTTP request arrived as, read once: the shape the playground and an endpoint share."""
15
+
16
+ model_config = ConfigDict(use_attribute_docstrings=True)
17
+
18
+ method: str
19
+ """The HTTP method the call was made with."""
20
+
21
+ path: str
22
+ """The concrete path, without the query."""
23
+
24
+ query: dict[str, JsonValue] = Field(default_factory=dict[str, JsonValue])
25
+ """The query arguments as strings, with a repeated name arriving as a list."""
26
+
27
+ headers: dict[str, str] = Field(default_factory=dict[str, str])
28
+ """The request headers, lowercased."""
29
+
30
+ content_type: str | None = None
31
+ """The content type the body arrived under, as the header spelled it."""
32
+
33
+ body_kind: BodyKind = BodyKind.NONE
34
+ """How the body was read. A body appears once, in ``body``."""
35
+
36
+ body: JsonValue = None
37
+ """The body, decoded the way ``body_kind`` says. Null when there was none."""
38
+
39
+ body_bytes: int = 0
40
+ """How large the body was, in bytes."""
41
+
42
+
43
+ class EndpointInput(RequestFacts):
44
+ """The output of an endpoint source, which is the input its pipeline's next steps read.
45
+
46
+ ``headers`` never carries the credential the caller presented: ``authorization``, the
47
+ cookie and the signature header are removed before the input is written.
48
+ """
49
+
50
+ route: str
51
+ """The template the source declares, which the concrete path matched."""
52
+
53
+ path_params: dict[str, str] = Field(default_factory=dict[str, str])
54
+ """What each ``{name}`` in the template matched, as a string."""
55
+
56
+ received_at: datetime
57
+ """When the input arrived, or when a hand run supplied it."""
58
+
59
+
60
+ class SourceOut(WireModel):
61
+ """A source as a listing shows it: what it listens to, its state, and what it has taken, never its token."""
62
+
63
+ model_config = ConfigDict(use_attribute_docstrings=True)
64
+
65
+ id: UUID
66
+ pipeline: str
67
+ """The code of the pipeline the source belongs to."""
68
+
69
+ code: str
70
+ """The source's step key."""
71
+
72
+ name: str | None = None
73
+ description: str | None = None
74
+ kind: str
75
+ state: SourceState
76
+ methods: list[str] = Field(default_factory=list[str])
77
+ """The methods a received source answers; empty for a kind the server does not receive."""
78
+
79
+ route: str | None = None
80
+ """The template a received source answers, under the instance's endpoint prefix."""
81
+
82
+ url_path: str | None = None
83
+ """Where a received source is called: the endpoint prefix and the template."""
84
+
85
+ auth: str | None = None
86
+ """How a caller of a received source authenticates: ``token`` or ``public``."""
87
+
88
+ token_prefix: str | None = None
89
+ """The leading characters of the token, so a caller's copy can be told apart from another."""
90
+
91
+ in_flight: int = 0
92
+ """How many runs it started are unsettled."""
93
+
94
+ max_in_flight: int | None = None
95
+ """How many unsettled runs it allows before refusing an input, or null for no bound."""
96
+
97
+ accepted: int = 0
98
+ """How many inputs became runs."""
99
+
100
+ refused: int = 0
101
+ """How many inputs were refused once their caller was let in."""
102
+
103
+ last_input_at: datetime | None = None
104
+ created_at: datetime
105
+
106
+
107
+ class SourceTokenOut(WireModel):
108
+ """A token freshly minted for a source, returned once and never readable again."""
109
+
110
+ source_id: UUID
111
+ pipeline: str
112
+ code: str
113
+ token: str
114
+ prefix: str
115
+ url_path: str
116
+ """Where the source is called, so a caller can be configured without assembling the path by hand."""
117
+
118
+
119
+ class SourceChanges(WireModel):
120
+ """What applying a document did to the source rows its pipeline holds."""
121
+
122
+ created: list[str] = Field(default_factory=list[str])
123
+ updated: list[str] = Field(default_factory=list[str])
124
+ removed: list[str] = Field(default_factory=list[str])
125
+ tokens: list[SourceTokenOut] = Field(default_factory=list[SourceTokenOut])
126
+ """The tokens minted for the sources this apply created, each shown here and nowhere else again."""
127
+
128
+ @property
129
+ def empty(self) -> bool:
130
+ """Report whether the apply left every source row exactly as it was."""
131
+ return not (self.created or self.updated or self.removed)
132
+
133
+
134
+ class SourceInputOut(WireModel):
135
+ """One input that reached a source, and the run or the refusal it became."""
136
+
137
+ id: int
138
+ created_at: datetime
139
+ outcome: InputOutcome
140
+ status: int
141
+ """The HTTP status the caller was answered with."""
142
+
143
+ run_id: UUID | None = None
144
+ code: str | None = None
145
+ """The refusal's message code, or null for an accepted input."""
146
+
147
+ reason: str | None = None
148
+ params: JsonMap | None = None
149
+ method: str
150
+ path: str
151
+ body_bytes: int = 0
152
+ duration_ms: int = 0
153
+ caller: str | None = None
154
+
155
+
156
+ class InputAccepted(WireModel):
157
+ """What a caller of a source is answered with when its input became a run."""
158
+
159
+ run_id: UUID
160
+ status: str
161
+ """The status the run was created in."""
162
+
163
+ run_url: str
164
+ """Where the run is read, under the versioned API."""