dirigent-plugin 0.21.0__tar.gz → 0.23.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_plugin-0.21.0 → dirigent_plugin-0.23.0}/PKG-INFO +2 -2
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/pyproject.toml +2 -2
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/pyproject.toml.orig +2 -2
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/__init__.py +4 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/blocks.py +106 -7
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/LICENSE +0 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/README.md +0 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/markers.py +0 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/messages.py +0 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/py.typed +0 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/runners.py +0 -0
- {dirigent_plugin-0.21.0 → dirigent_plugin-0.23.0}/src/dirigent_plugin/transforms.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirigent-plugin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: The dirigent plugin contract: block specs, protocols, and markers.
|
|
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.
|
|
9
|
+
Requires-Dist: dirigent-common==0.23.0
|
|
10
10
|
Requires-Dist: httpx2>=2.12.0
|
|
11
11
|
Requires-Dist: pluginkit>=0.5.0
|
|
12
12
|
Requires-Dist: pydantic>=2.13.5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-plugin"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.23.0"
|
|
4
4
|
description = "The dirigent plugin contract: block specs, protocols, and markers."
|
|
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.
|
|
14
|
+
"dirigent-common==0.23.0",
|
|
15
15
|
"httpx2>=2.12.0",
|
|
16
16
|
"pluginkit>=0.5.0",
|
|
17
17
|
"pydantic>=2.13.5",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-plugin"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.23.0"
|
|
4
4
|
description = "The dirigent plugin contract: block specs, protocols, and markers."
|
|
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.
|
|
14
|
+
"dirigent-common==0.23.0",
|
|
15
15
|
"httpx2>=2.12.0",
|
|
16
16
|
"pluginkit>=0.5.0",
|
|
17
17
|
"pydantic>=2.13.5",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from dirigent_plugin.blocks import (
|
|
4
4
|
BLOCK_ID_PATTERN,
|
|
5
5
|
MARK_LIMIT,
|
|
6
|
+
REFERENCE_KEYWORD,
|
|
6
7
|
SHELL_VARIABLE_PREFIX,
|
|
7
8
|
SHELL_VARIABLES_FIELD,
|
|
8
9
|
SURFACE_ID_PATTERN,
|
|
@@ -45,6 +46,7 @@ from dirigent_plugin.blocks import (
|
|
|
45
46
|
classify_default,
|
|
46
47
|
mark_refusal,
|
|
47
48
|
merge_contributions,
|
|
49
|
+
reference_fields,
|
|
48
50
|
shell_string_fields,
|
|
49
51
|
)
|
|
50
52
|
from dirigent_plugin.markers import (
|
|
@@ -112,6 +114,7 @@ __all__ = [
|
|
|
112
114
|
"RunnerEngine",
|
|
113
115
|
"Runs",
|
|
114
116
|
"SURFACE_ID_PATTERN",
|
|
117
|
+
"REFERENCE_KEYWORD",
|
|
115
118
|
"SchemaRef",
|
|
116
119
|
"Sensor",
|
|
117
120
|
"SensorSpec",
|
|
@@ -136,5 +139,6 @@ __all__ = [
|
|
|
136
139
|
"formatters",
|
|
137
140
|
"mark_refusal",
|
|
138
141
|
"merge_contributions",
|
|
142
|
+
"reference_fields",
|
|
139
143
|
"shell_string_fields",
|
|
140
144
|
]
|
|
@@ -16,7 +16,16 @@ from pydantic import BaseModel, ConfigDict, Field, GetJsonSchemaHandler, JsonVal
|
|
|
16
16
|
from pydantic.json_schema import JsonSchemaValue, SkipJsonSchema
|
|
17
17
|
from pydantic_core import CoreSchema
|
|
18
18
|
|
|
19
|
-
from dirigent_common import
|
|
19
|
+
from dirigent_common import (
|
|
20
|
+
API_VERSION,
|
|
21
|
+
SHELL_MEDIA_TYPE,
|
|
22
|
+
BlockModel,
|
|
23
|
+
Catalogue,
|
|
24
|
+
HealthReport,
|
|
25
|
+
Issue,
|
|
26
|
+
JsonMap,
|
|
27
|
+
Message,
|
|
28
|
+
)
|
|
20
29
|
from dirigent_plugin.messages import (
|
|
21
30
|
DUPLICATE_ID,
|
|
22
31
|
INVALID_MARK,
|
|
@@ -138,6 +147,14 @@ class ShellString:
|
|
|
138
147
|
return published
|
|
139
148
|
|
|
140
149
|
|
|
150
|
+
#: What :class:`Reference` publishes, and what reading one back looks for.
|
|
151
|
+
REFERENCE_KEYWORD: Final = "x-dirigent-ref"
|
|
152
|
+
|
|
153
|
+
#: The only pointer shape a block's own config schema writes, which pydantic emits for a
|
|
154
|
+
#: field whose type is a named alias.
|
|
155
|
+
_LOCAL_DEFS: Final = "#/$defs/"
|
|
156
|
+
|
|
157
|
+
|
|
141
158
|
class Reference:
|
|
142
159
|
"""Marks a config field whose value is the code of another thing this instance holds.
|
|
143
160
|
|
|
@@ -164,7 +181,7 @@ class Reference:
|
|
|
164
181
|
) -> JsonSchemaValue:
|
|
165
182
|
"""Publish what the marked field's code names, leaving what it validates untouched."""
|
|
166
183
|
published = handler(schema)
|
|
167
|
-
published[
|
|
184
|
+
published[REFERENCE_KEYWORD] = self.kind
|
|
168
185
|
return published
|
|
169
186
|
|
|
170
187
|
|
|
@@ -175,6 +192,55 @@ type ConnectionRef = Annotated[str, Reference("connection")]
|
|
|
175
192
|
type SchemaRef = Annotated[str, Reference("schema")]
|
|
176
193
|
|
|
177
194
|
|
|
195
|
+
def _pointed_at(shape: dict[str, Any], pool: dict[str, Any]) -> dict[str, Any] | None:
|
|
196
|
+
"""The definition a local ``$ref`` names, or nothing where it names none."""
|
|
197
|
+
pointer = shape.get("$ref")
|
|
198
|
+
if not isinstance(pointer, str) or not pointer.startswith(_LOCAL_DEFS):
|
|
199
|
+
return None
|
|
200
|
+
target = pool.get(pointer.removeprefix(_LOCAL_DEFS))
|
|
201
|
+
return cast("dict[str, Any]", target) if isinstance(target, dict) else None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _shapes(shape: dict[str, Any], pool: dict[str, Any]) -> list[dict[str, Any]]:
|
|
205
|
+
"""One field's own shape, the definition it points at, and each branch of a union."""
|
|
206
|
+
found: list[dict[str, Any]] = [shape]
|
|
207
|
+
target = _pointed_at(shape, pool)
|
|
208
|
+
if target is not None:
|
|
209
|
+
found.append(target)
|
|
210
|
+
for option in cast("list[Any]", shape.get("anyOf") or []):
|
|
211
|
+
if not isinstance(option, dict):
|
|
212
|
+
continue
|
|
213
|
+
branch = cast("dict[str, Any]", option)
|
|
214
|
+
found.append(branch)
|
|
215
|
+
pointed = _pointed_at(branch, pool)
|
|
216
|
+
if pointed is not None:
|
|
217
|
+
found.append(pointed)
|
|
218
|
+
return found
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def reference_fields(schema: JsonMap, kind: str) -> set[str]:
|
|
222
|
+
"""Name the config fields a block publishes as references to one kind of thing.
|
|
223
|
+
|
|
224
|
+
A block says which of its strings hold a code, so nothing here guesses from a value: a
|
|
225
|
+
code and a value of the same shape are not told apart by looking. The marker rides on the
|
|
226
|
+
field's own shape or on the definition that shape points at, because a field typed as a
|
|
227
|
+
named alias is published as a ``$ref``.
|
|
228
|
+
"""
|
|
229
|
+
properties = schema.get("properties")
|
|
230
|
+
if not isinstance(properties, dict):
|
|
231
|
+
return set()
|
|
232
|
+
held = schema.get("$defs")
|
|
233
|
+
pool = cast("dict[str, Any]", held) if isinstance(held, dict) else {}
|
|
234
|
+
named: set[str] = set()
|
|
235
|
+
for field, declared in cast("dict[str, Any]", properties).items():
|
|
236
|
+
if not isinstance(declared, dict):
|
|
237
|
+
continue
|
|
238
|
+
shapes = _shapes(cast("dict[str, Any]", declared), pool)
|
|
239
|
+
if any(one.get(REFERENCE_KEYWORD) == kind for one in shapes):
|
|
240
|
+
named.add(str(field))
|
|
241
|
+
return named
|
|
242
|
+
|
|
243
|
+
|
|
178
244
|
#: What the engine names the variables it substitutes a shell string's references out into.
|
|
179
245
|
SHELL_VARIABLE_PREFIX: Final = "DIRIGENT_V"
|
|
180
246
|
|
|
@@ -364,7 +430,13 @@ class ContainerResult(BaseModel):
|
|
|
364
430
|
|
|
365
431
|
|
|
366
432
|
class AlertMessage(BaseModel):
|
|
367
|
-
"""What an alert rule hands a notifier: the event, a rendered summary, and links back.
|
|
433
|
+
"""What an alert rule hands a notifier: the event, a rendered summary, and links back.
|
|
434
|
+
|
|
435
|
+
The subject and the body are the rule author's own template rendered over the run, so they
|
|
436
|
+
are that author's words in that author's language and carry no code. What dirigent itself
|
|
437
|
+
said is ``error_code`` and ``error_params``: the refusal the run ended with, which a
|
|
438
|
+
notifier with a catalogue can render in the language of wherever it delivers.
|
|
439
|
+
"""
|
|
368
440
|
|
|
369
441
|
event: str = Field(min_length=1)
|
|
370
442
|
subject: str = Field(min_length=1)
|
|
@@ -372,6 +444,10 @@ class AlertMessage(BaseModel):
|
|
|
372
444
|
run_id: RunId | None = None
|
|
373
445
|
pipeline: str | None = None
|
|
374
446
|
url: str | None = None
|
|
447
|
+
error_code: str | None = None
|
|
448
|
+
"""The dotted code of the refusal the alerted run ended with, when it ended with one."""
|
|
449
|
+
error_params: dict[str, JsonValue] = Field(default_factory=dict)
|
|
450
|
+
"""The specifics that refusal rendered, for a re-render in another language."""
|
|
375
451
|
context: dict[str, JsonValue] = Field(default_factory=dict)
|
|
376
452
|
|
|
377
453
|
|
|
@@ -493,6 +569,10 @@ class RunSnapshot(BaseModel):
|
|
|
493
569
|
total_steps: int = Field(default=0, ge=0)
|
|
494
570
|
finished_steps: int = Field(default=0, ge=0)
|
|
495
571
|
error: str | None = None
|
|
572
|
+
error_code: str | None = None
|
|
573
|
+
"""The dotted code of the refusal this run ended with, when it ended with one."""
|
|
574
|
+
error_params: JsonMap = Field(default_factory=dict)
|
|
575
|
+
"""The specifics that refusal rendered, for a re-render in another language."""
|
|
496
576
|
|
|
497
577
|
@property
|
|
498
578
|
def progress(self) -> float | None:
|
|
@@ -524,8 +604,12 @@ class Runs(Protocol):
|
|
|
524
604
|
"""Describe a run this instance holds, or return None when it holds no such run."""
|
|
525
605
|
...
|
|
526
606
|
|
|
527
|
-
async def cancel(self, run_id: RunId,
|
|
528
|
-
"""Cancel a run; False means it had already settled and there was nothing to stop.
|
|
607
|
+
async def cancel(self, run_id: RunId, reason: Message, /, **params: Any) -> bool:
|
|
608
|
+
"""Cancel a run; False means it had already settled and there was nothing to stop.
|
|
609
|
+
|
|
610
|
+
The reason is a catalogued message, so the cancelled run carries a code and whoever
|
|
611
|
+
reads it renders their own sentence rather than this block's English.
|
|
612
|
+
"""
|
|
529
613
|
...
|
|
530
614
|
|
|
531
615
|
|
|
@@ -575,7 +659,12 @@ class StepContext(Protocol):
|
|
|
575
659
|
...
|
|
576
660
|
|
|
577
661
|
def schema(self, code: str) -> JsonMap:
|
|
578
|
-
"""Resolve
|
|
662
|
+
"""Resolve one of the JSON Schemas this run holds by code; any other code fails the step.
|
|
663
|
+
|
|
664
|
+
A run holds the body of every schema its document named when it was created, so a
|
|
665
|
+
stored schema edited mid-run does not change what the rest of that run checks
|
|
666
|
+
against. A code the document does not name is not one the run holds.
|
|
667
|
+
"""
|
|
579
668
|
...
|
|
580
669
|
|
|
581
670
|
def format_checker(self) -> FormatChecker:
|
|
@@ -837,7 +926,7 @@ type AnySensor = Sensor[Any, Any]
|
|
|
837
926
|
|
|
838
927
|
|
|
839
928
|
class Contribution(BaseModel):
|
|
840
|
-
"""Everything one plugin adds, across all
|
|
929
|
+
"""Everything one plugin adds, across all seven surfaces, gathered by the host at startup."""
|
|
841
930
|
|
|
842
931
|
model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True)
|
|
843
932
|
|
|
@@ -852,6 +941,14 @@ class Contribution(BaseModel):
|
|
|
852
941
|
``format: <name>`` then asserts wherever the contributing pack is installed, and stays a
|
|
853
942
|
passing annotation on an instance without it."""
|
|
854
943
|
|
|
944
|
+
labels: list[Catalogue] = Field(default_factory=list[Catalogue])
|
|
945
|
+
"""The message catalogues this plugin refuses out of, so a surface can render its codes.
|
|
946
|
+
|
|
947
|
+
A pack's refusal reaches a browser under the pack's own code, and the browser holds no table
|
|
948
|
+
for it. Contributing the catalogue is what puts one there: the host serves every code and its
|
|
949
|
+
template, and a renderer that knows the code says its own sentence rather than repeating the
|
|
950
|
+
English this process happened to mint."""
|
|
951
|
+
|
|
855
952
|
@field_validator("api_version")
|
|
856
953
|
@classmethod
|
|
857
954
|
def _check_api_version(cls, value: int) -> int:
|
|
@@ -868,6 +965,7 @@ class Contribution(BaseModel):
|
|
|
868
965
|
_require_unique("notifier id", [notifier.id for notifier in self.notifiers])
|
|
869
966
|
_require_unique("connection kind id", [connection.id for connection in self.connection_kinds])
|
|
870
967
|
_require_unique("format", list(self.formats))
|
|
968
|
+
_require_unique("label prefix", [catalogue.prefix for catalogue in self.labels])
|
|
871
969
|
return self
|
|
872
970
|
|
|
873
971
|
@model_validator(mode="after")
|
|
@@ -901,6 +999,7 @@ def merge_contributions(contributions: list[Contribution]) -> Contribution:
|
|
|
901
999
|
"storage_backends": [],
|
|
902
1000
|
"notifiers": [],
|
|
903
1001
|
"connection_kinds": [],
|
|
1002
|
+
"labels": [],
|
|
904
1003
|
}
|
|
905
1004
|
for contribution in contributions:
|
|
906
1005
|
for surface, collected in merged.items():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|