dirigent-testing 0.17.2__tar.gz → 0.18.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_testing-0.17.2 → dirigent_testing-0.18.0}/PKG-INFO +5 -4
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/README.md +2 -1
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/pyproject.toml +3 -3
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/pyproject.toml.orig +3 -3
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/__init__.py +2 -1
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/conformance.py +25 -9
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/doubles.py +30 -0
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/LICENSE +0 -0
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/environment.py +0 -0
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/fixtures.py +0 -0
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/messages.py +0 -0
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/py.typed +0 -0
- {dirigent_testing-0.17.2 → dirigent_testing-0.18.0}/src/dirigent_testing/running.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirigent-testing
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Test doubles and pytest fixtures for writing and testing dirigent blocks.
|
|
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.
|
|
10
|
-
Requires-Dist: dirigent-plugin==0.
|
|
9
|
+
Requires-Dist: dirigent-common==0.18.0
|
|
10
|
+
Requires-Dist: dirigent-plugin==0.18.0
|
|
11
11
|
Requires-Dist: httpx2>=2.12.0
|
|
12
12
|
Requires-Dist: pydantic>=2.13.5
|
|
13
13
|
Requires-Dist: pytest>=9.1.1
|
|
@@ -24,6 +24,7 @@ Installing it registers the fixtures by entry point, so a block author writes no
|
|
|
24
24
|
`call_block` validates a config the way the engine does before making the block's first call.
|
|
25
25
|
|
|
26
26
|
`check_pack_examples` checks a pack's own example documents against its own `Contribution`
|
|
27
|
-
-- format, code, blocks, config schemas, and
|
|
27
|
+
-- format, code, blocks, config schemas, and the connections a step names, which the
|
|
28
|
+
document may either carry or list under `requires.connections` -- without importing
|
|
28
29
|
dirigent-core, and `assert_contribution_conforms` checks the blocks a contribution provides
|
|
29
30
|
are well-formed. Both return a list of human-readable issues; an empty list means it passed.
|
|
@@ -7,6 +7,7 @@ Installing it registers the fixtures by entry point, so a block author writes no
|
|
|
7
7
|
`call_block` validates a config the way the engine does before making the block's first call.
|
|
8
8
|
|
|
9
9
|
`check_pack_examples` checks a pack's own example documents against its own `Contribution`
|
|
10
|
-
-- format, code, blocks, config schemas, and
|
|
10
|
+
-- format, code, blocks, config schemas, and the connections a step names, which the
|
|
11
|
+
document may either carry or list under `requires.connections` -- without importing
|
|
11
12
|
dirigent-core, and `assert_contribution_conforms` checks the blocks a contribution provides
|
|
12
13
|
are well-formed. Both return a list of human-readable issues; an empty list means it passed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-testing"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.0"
|
|
4
4
|
description = "Test doubles and pytest fixtures for writing and testing dirigent blocks."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -11,8 +11,8 @@ classifiers = [
|
|
|
11
11
|
"Programming Language :: Python :: 3.13",
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"dirigent-common==0.
|
|
15
|
-
"dirigent-plugin==0.
|
|
14
|
+
"dirigent-common==0.18.0",
|
|
15
|
+
"dirigent-plugin==0.18.0",
|
|
16
16
|
"httpx2>=2.12.0",
|
|
17
17
|
"pydantic>=2.13.5",
|
|
18
18
|
"pytest>=9.1.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-testing"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.0"
|
|
4
4
|
description = "Test doubles and pytest fixtures for writing and testing dirigent blocks."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -11,8 +11,8 @@ classifiers = [
|
|
|
11
11
|
"Programming Language :: Python :: 3.13",
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"dirigent-common==0.
|
|
15
|
-
"dirigent-plugin==0.
|
|
14
|
+
"dirigent-common==0.18.0",
|
|
15
|
+
"dirigent-plugin==0.18.0",
|
|
16
16
|
"httpx2>=2.12.0",
|
|
17
17
|
"pydantic>=2.13.5",
|
|
18
18
|
"pytest>=9.1.1",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Test doubles and pytest fixtures for writing and testing dirigent blocks."""
|
|
2
2
|
|
|
3
3
|
from dirigent_testing.conformance import assert_contribution_conforms, check_pack_examples
|
|
4
|
-
from dirigent_testing.doubles import FakeContext, FakeRuns, FakeSink, FakeStorage, RecordingLogger
|
|
4
|
+
from dirigent_testing.doubles import FakeCapture, FakeContext, FakeRuns, FakeSink, FakeStorage, RecordingLogger
|
|
5
5
|
from dirigent_testing.environment import CONFIGURING_PREFIXES, TEST_WIDTH, pin_terminal, scrub_configuration
|
|
6
6
|
from dirigent_testing.running import call_block
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"CONFIGURING_PREFIXES",
|
|
10
10
|
"TEST_WIDTH",
|
|
11
|
+
"FakeCapture",
|
|
11
12
|
"FakeContext",
|
|
12
13
|
"FakeRuns",
|
|
13
14
|
"FakeSink",
|
|
@@ -6,8 +6,9 @@ conformance check re-derives the structural half here, over the pack's own ``Con
|
|
|
6
6
|
and its own example documents. It is deliberately lighter than the engine's preflight: it
|
|
7
7
|
proves an example is a well-formed ``dirigent/v1`` pipeline coded after its file, that every
|
|
8
8
|
block it names is one the pack contributes, that each config fits that block's published
|
|
9
|
-
schema, and that a
|
|
10
|
-
|
|
9
|
+
schema, and that a connection a step names is one the document accounts for -- carried, so
|
|
10
|
+
the document runs alone, or named under ``requires``. It does not resolve references, run
|
|
11
|
+
blocks, or reach for an instance.
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
import re
|
|
@@ -76,9 +77,9 @@ def check_pack_examples(contribution: Contribution, examples_dir: Path) -> list[
|
|
|
76
77
|
is a ``dirigent/v1`` pipeline coded after its file with a description, that every block a
|
|
77
78
|
step names is one ``contribution`` provides, that each step's config fits that block's
|
|
78
79
|
published ``config_schema`` (a ``${...}`` value is left for the run), and that a connection
|
|
79
|
-
a step names is carried by the document's own ``connections`` block
|
|
80
|
-
human-readable line prefixed with the file
|
|
81
|
-
examples conform.
|
|
80
|
+
a step names is either carried by the document's own ``connections`` block or named under
|
|
81
|
+
its ``requires.connections``. Every issue is a human-readable line prefixed with the file
|
|
82
|
+
it was found in; an empty list means the examples conform.
|
|
82
83
|
"""
|
|
83
84
|
blocks = _blocks_by_id(contribution)
|
|
84
85
|
issues: list[str] = []
|
|
@@ -118,15 +119,25 @@ def _check_document(path: Path, label: Path, blocks: dict[str, AnyOperator | Any
|
|
|
118
119
|
if not document.get("description"):
|
|
119
120
|
issues.append(f"{label}: has no description")
|
|
120
121
|
|
|
121
|
-
connections = document.get("connections")
|
|
122
|
-
known = set(cast("JsonMap", connections)) if isinstance(connections, dict) else set[str]()
|
|
123
122
|
steps = document.get("steps")
|
|
124
123
|
if isinstance(steps, dict):
|
|
124
|
+
known = _known_connections(document)
|
|
125
125
|
for name, step in cast("JsonMap", steps).items():
|
|
126
126
|
issues.extend(_check_step(label, str(name), step, blocks, known))
|
|
127
127
|
return issues
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def _known_connections(document: JsonMap) -> set[str]:
|
|
131
|
+
"""Every connection code a document accounts for: the ones it carries and the ones it names."""
|
|
132
|
+
carried = document.get("connections")
|
|
133
|
+
known = set(cast("JsonMap", carried)) if isinstance(carried, dict) else set[str]()
|
|
134
|
+
requires = document.get("requires")
|
|
135
|
+
named = cast("JsonMap", requires).get("connections") if isinstance(requires, dict) else None
|
|
136
|
+
if isinstance(named, list):
|
|
137
|
+
known |= {one for one in cast("list[object]", named) if isinstance(one, str)}
|
|
138
|
+
return known
|
|
139
|
+
|
|
140
|
+
|
|
130
141
|
def _check_step(
|
|
131
142
|
label: Path,
|
|
132
143
|
name: str,
|
|
@@ -134,7 +145,10 @@ def _check_step(
|
|
|
134
145
|
blocks: dict[str, AnyOperator | AnySensor],
|
|
135
146
|
connections: set[str],
|
|
136
147
|
) -> list[str]:
|
|
137
|
-
"""Check one step: the block it names, the config it carries, the connection it references.
|
|
148
|
+
"""Check one step: the block it names, the config it carries, the connection it references.
|
|
149
|
+
|
|
150
|
+
``connections`` is every code the document accounts for, carried or required.
|
|
151
|
+
"""
|
|
138
152
|
if not isinstance(step, dict):
|
|
139
153
|
return [f"{label}: step {name!r} is not a mapping"]
|
|
140
154
|
step_map = cast("JsonMap", step)
|
|
@@ -152,7 +166,9 @@ def _check_step(
|
|
|
152
166
|
|
|
153
167
|
named = config.get("connection")
|
|
154
168
|
if isinstance(named, str) and not _REFERENCE.search(named) and named not in connections:
|
|
155
|
-
issues.append(
|
|
169
|
+
issues.append(
|
|
170
|
+
f"{label}: step {name!r} names connection {named!r}, which the document neither carries nor requires"
|
|
171
|
+
)
|
|
156
172
|
return issues
|
|
157
173
|
|
|
158
174
|
|
|
@@ -15,6 +15,7 @@ from pydantic import BaseModel, JsonValue
|
|
|
15
15
|
from dirigent_common import format_checker_with
|
|
16
16
|
from dirigent_plugin import (
|
|
17
17
|
ByteSink,
|
|
18
|
+
Capture,
|
|
18
19
|
FormatCheck,
|
|
19
20
|
RunRefused,
|
|
20
21
|
RunSnapshot,
|
|
@@ -71,6 +72,19 @@ class FakeSink:
|
|
|
71
72
|
self._handle.close()
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
class FakeCapture:
|
|
76
|
+
"""A captured stream in a test: a sink carrying the URI it was opened under."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, uri: str, sink: ByteSink) -> None:
|
|
79
|
+
"""Bind the sink to the URI naming it."""
|
|
80
|
+
self.uri = uri
|
|
81
|
+
self._sink = sink
|
|
82
|
+
|
|
83
|
+
async def write(self, data: bytes) -> int:
|
|
84
|
+
"""Append bytes to the stream and return how many were accepted."""
|
|
85
|
+
return await self._sink.write(data)
|
|
86
|
+
|
|
87
|
+
|
|
74
88
|
class FakeStorage:
|
|
75
89
|
"""A storage facade over one directory, backing the storage blocks in a test."""
|
|
76
90
|
|
|
@@ -237,6 +251,22 @@ class FakeContext:
|
|
|
237
251
|
"""The run-scoped URI prefix."""
|
|
238
252
|
return self.scratch_uri
|
|
239
253
|
|
|
254
|
+
def capture(self, name: str, *, content_type: str = "text/plain") -> AbstractAsyncContextManager[Capture]:
|
|
255
|
+
"""Open a captured stream under the scratch prefix, named as the engine names it."""
|
|
256
|
+
uri = f"{self.scratch_uri.rstrip('/')}/{self._segment()}-{name}"
|
|
257
|
+
|
|
258
|
+
@asynccontextmanager
|
|
259
|
+
async def opened() -> AsyncGenerator[Capture]:
|
|
260
|
+
async with self.storage.open_write(uri, content_type=content_type) as sink:
|
|
261
|
+
yield FakeCapture(uri, sink)
|
|
262
|
+
|
|
263
|
+
return opened()
|
|
264
|
+
|
|
265
|
+
def _segment(self) -> str:
|
|
266
|
+
"""The path this attempt's captures sit under: the step, the fan-out item, and the attempt."""
|
|
267
|
+
item = f"/{self.run_item_id}" if self.run_item_id is not None else ""
|
|
268
|
+
return f"{self.step}{item}/attempt-{self.attempt}"
|
|
269
|
+
|
|
240
270
|
@property
|
|
241
271
|
def work(self) -> Path:
|
|
242
272
|
"""The run's directory on this worker's own filesystem, made on first read.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|