dirigent-plugin 0.18.1__tar.gz → 0.18.3__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.18.1 → dirigent_plugin-0.18.3}/PKG-INFO +2 -2
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/pyproject.toml +2 -2
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/pyproject.toml.orig +2 -2
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/src/dirigent_plugin/__init__.py +6 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/src/dirigent_plugin/blocks.py +70 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/src/dirigent_plugin/messages.py +5 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/LICENSE +0 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/README.md +0 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/src/dirigent_plugin/markers.py +0 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/src/dirigent_plugin/py.typed +0 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/src/dirigent_plugin/runners.py +0 -0
- {dirigent_plugin-0.18.1 → dirigent_plugin-0.18.3}/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.18.
|
|
3
|
+
Version: 0.18.3
|
|
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.18.
|
|
9
|
+
Requires-Dist: dirigent-common==0.18.3
|
|
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.18.
|
|
3
|
+
version = "0.18.3"
|
|
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.18.
|
|
14
|
+
"dirigent-common==0.18.3",
|
|
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.18.
|
|
3
|
+
version = "0.18.3"
|
|
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.18.
|
|
14
|
+
"dirigent-common==0.18.3",
|
|
15
15
|
"httpx2>=2.12.0",
|
|
16
16
|
"pluginkit>=0.5.0",
|
|
17
17
|
"pydantic>=2.13.5",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from dirigent_plugin.blocks import (
|
|
4
4
|
BLOCK_ID_PATTERN,
|
|
5
|
+
MARK_LIMIT,
|
|
5
6
|
SHELL_VARIABLE_PREFIX,
|
|
6
7
|
SHELL_VARIABLES_FIELD,
|
|
7
8
|
SURFACE_ID_PATTERN,
|
|
@@ -13,6 +14,7 @@ from dirigent_plugin.blocks import (
|
|
|
13
14
|
Capture,
|
|
14
15
|
ConnectionKind,
|
|
15
16
|
ConnectionRef,
|
|
17
|
+
ContainerResult,
|
|
16
18
|
Contribution,
|
|
17
19
|
ErrorClass,
|
|
18
20
|
FormatCheck,
|
|
@@ -41,6 +43,7 @@ from dirigent_plugin.blocks import (
|
|
|
41
43
|
Storage,
|
|
42
44
|
StorageBackend,
|
|
43
45
|
classify_default,
|
|
46
|
+
mark_refusal,
|
|
44
47
|
merge_contributions,
|
|
45
48
|
shell_string_fields,
|
|
46
49
|
)
|
|
@@ -78,6 +81,7 @@ __all__ = [
|
|
|
78
81
|
"Capture",
|
|
79
82
|
"ConnectionKind",
|
|
80
83
|
"ConnectionRef",
|
|
84
|
+
"ContainerResult",
|
|
81
85
|
"Contribution",
|
|
82
86
|
"ConvertConfig",
|
|
83
87
|
"ConvertOutput",
|
|
@@ -88,6 +92,7 @@ __all__ = [
|
|
|
88
92
|
"Filterer",
|
|
89
93
|
"FormatCheck",
|
|
90
94
|
"Logger",
|
|
95
|
+
"MARK_LIMIT",
|
|
91
96
|
"Mapper",
|
|
92
97
|
"NotYet",
|
|
93
98
|
"Notifier",
|
|
@@ -129,6 +134,7 @@ __all__ = [
|
|
|
129
134
|
"extension",
|
|
130
135
|
"extension_point",
|
|
131
136
|
"formatters",
|
|
137
|
+
"mark_refusal",
|
|
132
138
|
"merge_contributions",
|
|
133
139
|
"shell_string_fields",
|
|
134
140
|
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""The dirigent block contract: the only module a third-party plugin package needs to import."""
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from collections.abc import AsyncGenerator, AsyncIterator, Callable, Mapping
|
|
5
6
|
from contextlib import AbstractAsyncContextManager
|
|
@@ -18,6 +19,7 @@ from pydantic_core import CoreSchema
|
|
|
18
19
|
from dirigent_common import API_VERSION, SHELL_MEDIA_TYPE, BlockModel, HealthReport, Issue, JsonMap, Message
|
|
19
20
|
from dirigent_plugin.messages import (
|
|
20
21
|
DUPLICATE_ID,
|
|
22
|
+
INVALID_MARK,
|
|
21
23
|
UNSUPPORTED_API_VERSION,
|
|
22
24
|
)
|
|
23
25
|
|
|
@@ -347,6 +349,19 @@ class StatResult(BaseModel):
|
|
|
347
349
|
content_type: str | None = None
|
|
348
350
|
|
|
349
351
|
|
|
352
|
+
class ContainerResult(BaseModel):
|
|
353
|
+
"""What a backend did when asked to make sure a URI's container is there."""
|
|
354
|
+
|
|
355
|
+
container: str | None = None
|
|
356
|
+
"""What the container is called, or None for a store that has none to make."""
|
|
357
|
+
|
|
358
|
+
created: bool = False
|
|
359
|
+
"""Whether this call made it, as opposed to finding it already there."""
|
|
360
|
+
|
|
361
|
+
endpoint: str | None = None
|
|
362
|
+
"""Where the store answers, carrying no credential; None for one with no endpoint."""
|
|
363
|
+
|
|
364
|
+
|
|
350
365
|
class AlertMessage(BaseModel):
|
|
351
366
|
"""What an alert rule hands a notifier: the event, a rendered summary, and links back."""
|
|
352
367
|
|
|
@@ -729,6 +744,14 @@ class StorageBackend(ABC):
|
|
|
729
744
|
"""Remove the object at a URI."""
|
|
730
745
|
...
|
|
731
746
|
|
|
747
|
+
async def ensure_container(self, uri: str) -> ContainerResult:
|
|
748
|
+
"""Create the container a URI's objects live in, unless it is already there.
|
|
749
|
+
|
|
750
|
+
A store whose containers a write makes for itself -- a filesystem and its
|
|
751
|
+
directories -- has nothing to create and says so by leaving the result empty.
|
|
752
|
+
"""
|
|
753
|
+
return ContainerResult()
|
|
754
|
+
|
|
732
755
|
|
|
733
756
|
class Notifier(ABC):
|
|
734
757
|
"""A pluggable message sender that alert rules deliver through."""
|
|
@@ -742,12 +765,50 @@ class Notifier(ABC):
|
|
|
742
765
|
...
|
|
743
766
|
|
|
744
767
|
|
|
768
|
+
#: How much path data one mark may carry.
|
|
769
|
+
MARK_LIMIT: Final = 4096
|
|
770
|
+
|
|
771
|
+
#: Everything an SVG path's ``d`` is written out of: the commands, numbers, and separators.
|
|
772
|
+
_PATH_DATA: Final = re.compile(r"^[MmZzLlHhVvCcSsQqTtAaEe0-9,.+\-\s]+$")
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def mark_refusal(kind: str, mark: str | None) -> str | None:
|
|
776
|
+
"""Say why a connection kind may not wear the mark it declares, or ``None`` when it may.
|
|
777
|
+
|
|
778
|
+
A mark reaches a browser as the ``d`` of one path, so anything that is not path data is
|
|
779
|
+
refused here rather than drawn: non-empty, ASCII, path-data characters only, and at most
|
|
780
|
+
``MARK_LIMIT`` of them.
|
|
781
|
+
"""
|
|
782
|
+
if mark is None:
|
|
783
|
+
return None
|
|
784
|
+
detail: str | None = None
|
|
785
|
+
if not mark.strip():
|
|
786
|
+
detail = "it is empty"
|
|
787
|
+
elif not mark.isascii():
|
|
788
|
+
detail = "it is not ASCII"
|
|
789
|
+
elif len(mark) > MARK_LIMIT:
|
|
790
|
+
detail = f"it is {len(mark)} characters, and a mark holds at most {MARK_LIMIT}"
|
|
791
|
+
elif not _PATH_DATA.match(mark):
|
|
792
|
+
stray = sorted({character for character in mark if not _PATH_DATA.match(character)})
|
|
793
|
+
detail = f"it carries {''.join(stray)!r}, which path data does not"
|
|
794
|
+
if detail is None:
|
|
795
|
+
return None
|
|
796
|
+
return INVALID_MARK.render(kind=repr(kind), detail=detail)
|
|
797
|
+
|
|
798
|
+
|
|
745
799
|
class ConnectionKind(ABC):
|
|
746
800
|
"""A named credential record of a contributed kind whose secret fields the server redacts."""
|
|
747
801
|
|
|
748
802
|
id: ClassVar[str]
|
|
749
803
|
config_model: ClassVar[type[BaseModel]]
|
|
750
804
|
|
|
805
|
+
mark: ClassVar[str | None] = None
|
|
806
|
+
"""The ``d`` of one SVG path on a 24-unit grid, which is how a screen draws this kind.
|
|
807
|
+
|
|
808
|
+
Monochrome and filled in ``currentColor``, at the size every other kind's mark is drawn at;
|
|
809
|
+
a kind that declares none is drawn by the neutral mark.
|
|
810
|
+
"""
|
|
811
|
+
|
|
751
812
|
@abstractmethod
|
|
752
813
|
async def check(self, config: BaseModel) -> HealthReport:
|
|
753
814
|
"""Verify that the configured connection can reach its external system.
|
|
@@ -796,6 +857,15 @@ class Contribution(BaseModel):
|
|
|
796
857
|
_require_unique("format", list(self.formats))
|
|
797
858
|
return self
|
|
798
859
|
|
|
860
|
+
@model_validator(mode="after")
|
|
861
|
+
def _check_marks(self) -> "Contribution":
|
|
862
|
+
"""Reject a connection kind whose declared mark is not path data a browser may draw."""
|
|
863
|
+
for connection in self.connection_kinds:
|
|
864
|
+
refusal = mark_refusal(connection.id, connection.mark)
|
|
865
|
+
if refusal is not None:
|
|
866
|
+
raise ValueError(refusal)
|
|
867
|
+
return self
|
|
868
|
+
|
|
799
869
|
def block_ids(self) -> list[str]:
|
|
800
870
|
"""List every operator and sensor id this contribution registers."""
|
|
801
871
|
return [*(operator.spec.id for operator in self.operators), *(sensor.spec.id for sensor in self.sensors)]
|
|
@@ -37,3 +37,8 @@ UNSUPPORTED_API_VERSION = PLUGIN.define(
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
DUPLICATE_ID = PLUGIN.define("duplicate_id", "duplicate {label} {value} in contribution")
|
|
40
|
+
|
|
41
|
+
INVALID_MARK = PLUGIN.define(
|
|
42
|
+
"invalid_mark",
|
|
43
|
+
"the mark connection kind {kind} declares is not SVG path data: {detail}",
|
|
44
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|