dagflows 0.2.0__tar.gz → 0.2.2__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.
- {dagflows-0.2.0 → dagflows-0.2.2}/.gitignore +1 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/Makefile +2 -2
- {dagflows-0.2.0 → dagflows-0.2.2}/PKG-INFO +1 -1
- {dagflows-0.2.0 → dagflows-0.2.2}/pyproject.toml +4 -1
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/__init__.py +23 -2
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/outstream.py +6 -2
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_input.py +13 -5
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_outstream.py +17 -0
- dagflows-0.2.2/tests/test_package.py +47 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/uv.lock +2 -2
- {dagflows-0.2.0 → dagflows-0.2.2}/.gitattributes +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/.github/workflows/release.yml +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/.github/workflows/test.yml +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/LICENSE +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/README.md +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/__main__.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_authoring/__init__.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_authoring/config.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_authoring/node.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_authoring/workflow.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_cli/__init__.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_cli/build.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_cli/dev.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/__init__.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/binding.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/content.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/decode.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/dispatch.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/encode.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/envelope.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/errors.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/http.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/jsonstream.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/multipart.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/result.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/_runtime/runner.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/src/dagflows/py.typed +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/conftest.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_binding.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_cli.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_dispatch.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_emit.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_envelope.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_http.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_jsonstream.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_multipart.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_readme.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_result.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_roundtrip.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_runner.py +0 -0
- {dagflows-0.2.0 → dagflows-0.2.2}/tests/test_workflow.py +0 -0
|
@@ -31,7 +31,7 @@ readme: ## just the README, about five seconds
|
|
|
31
31
|
matrix: ## the suite on every version requires-python claims
|
|
32
32
|
@for v in 3.11 3.12 3.13; do \
|
|
33
33
|
printf 'python %s: ' $$v; \
|
|
34
|
-
uv run --python $$v --group dev pytest -q | tail -1; \
|
|
34
|
+
UV_PROJECT_ENVIRONMENT=.venv-matrix-$$v uv run --python $$v --group dev pytest -q | tail -1; \
|
|
35
35
|
done
|
|
36
36
|
|
|
37
37
|
build: ## build the artifacts and check the metadata is publishable
|
|
@@ -57,4 +57,4 @@ wheel: build ## prove the built wheel works, not just the working tree
|
|
|
57
57
|
@$(VENV)/Scripts/python -m dagflows --help > /dev/null && echo "cli ok"
|
|
58
58
|
|
|
59
59
|
clean: ## remove build and test leftovers
|
|
60
|
-
rm -rf dist $(VENV) .pytest_cache
|
|
60
|
+
rm -rf dist $(VENV) .venv-matrix-* .pytest_cache
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dagflows"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "SDK for authoring and running Dagflows workflows"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -42,6 +42,9 @@ packages = ["src/dagflows"]
|
|
|
42
42
|
|
|
43
43
|
[tool.pytest.ini_options]
|
|
44
44
|
testpaths = ["tests"]
|
|
45
|
+
# A hang self-reports with every thread's stack instead of eating the CI
|
|
46
|
+
# timeout in silence. Found one deadlock already.
|
|
47
|
+
faulthandler_timeout = 300
|
|
45
48
|
|
|
46
49
|
[tool.ruff]
|
|
47
50
|
line-length = 100
|
|
@@ -37,6 +37,9 @@ _AUTHORING = {
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def __getattr__(name: str) -> Any:
|
|
40
|
+
if name == "__version__":
|
|
41
|
+
return _version()
|
|
42
|
+
|
|
40
43
|
module = _AUTHORING.get(name)
|
|
41
44
|
if module is None:
|
|
42
45
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -44,6 +47,26 @@ def __getattr__(name: str) -> Any:
|
|
|
44
47
|
return getattr(import_module(module, __name__), name)
|
|
45
48
|
|
|
46
49
|
|
|
50
|
+
def _version() -> str:
|
|
51
|
+
"""Return the installed package version from metadata."""
|
|
52
|
+
global _cached_version
|
|
53
|
+
if _cached_version:
|
|
54
|
+
return _cached_version
|
|
55
|
+
|
|
56
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
_cached_version = version("dagflows")
|
|
60
|
+
except PackageNotFoundError:
|
|
61
|
+
# An uninstalled source tree has no metadata to ask.
|
|
62
|
+
_cached_version = "0.0.0"
|
|
63
|
+
|
|
64
|
+
return _cached_version
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_cached_version = ""
|
|
68
|
+
|
|
69
|
+
|
|
47
70
|
def __dir__() -> list[str]:
|
|
48
71
|
return sorted(__all__)
|
|
49
72
|
|
|
@@ -68,5 +91,3 @@ __all__ = [
|
|
|
68
91
|
"Workflow",
|
|
69
92
|
"run",
|
|
70
93
|
]
|
|
71
|
-
|
|
72
|
-
__version__ = "0.1.0"
|
|
@@ -8,6 +8,7 @@ from types import TracebackType
|
|
|
8
8
|
from typing import Any, Self
|
|
9
9
|
|
|
10
10
|
from .content import ContentType
|
|
11
|
+
from .envelope import DEFAULT_INLINE_MAX_BYTES
|
|
11
12
|
from .multipart import Multipart
|
|
12
13
|
from .result import RowSink, Upload, Written, buffer_cap
|
|
13
14
|
|
|
@@ -23,11 +24,14 @@ class OutputStream:
|
|
|
23
24
|
memory_limit_mb: int = 0,
|
|
24
25
|
multipart: Multipart | None = None,
|
|
25
26
|
) -> None:
|
|
27
|
+
# Fall back to default limit when unset (0) to avoid immediately uploading or rejecting rows.
|
|
28
|
+
limit = inline_max_bytes if inline_max_bytes > 0 else DEFAULT_INLINE_MAX_BYTES
|
|
29
|
+
|
|
26
30
|
self.content_type = content_type
|
|
27
31
|
self._sink = RowSink(
|
|
28
32
|
content_type,
|
|
29
|
-
|
|
30
|
-
buffer_cap(
|
|
33
|
+
limit,
|
|
34
|
+
buffer_cap(limit, upload, memory_limit_mb),
|
|
31
35
|
upload,
|
|
32
36
|
multipart,
|
|
33
37
|
)
|
|
@@ -5,7 +5,7 @@ Tests for inline and reference input handling and streaming.
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import threading
|
|
8
|
-
from http.server import BaseHTTPRequestHandler,
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
|
|
@@ -21,7 +21,8 @@ BODIES = {
|
|
|
21
21
|
"/rows.json": b'[{"id": 1, "name": "ana"}, {"id": 2, "name": "bo"}]',
|
|
22
22
|
"/object.json": b'{"id": 1}',
|
|
23
23
|
"/blob.bin": bytes(range(256)) * 4,
|
|
24
|
-
|
|
24
|
+
# Large enough to exceed socket buffers so early-stop tests do not race against buffering.
|
|
25
|
+
"/big.ndjson": b'{"i": 0}\n' + (b'{"i": 1, "pad": "' + b"x" * 100 + b'"}\n') * 550_000,
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
served = {"gets": 0, "bytes": 0}
|
|
@@ -55,7 +56,9 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
55
56
|
|
|
56
57
|
@pytest.fixture(scope="module")
|
|
57
58
|
def base():
|
|
58
|
-
|
|
59
|
+
# Use daemon threads so server shutdown does not deadlock on blocked writes.
|
|
60
|
+
httpd = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
61
|
+
httpd.daemon_threads = True
|
|
59
62
|
threading.Thread(target=httpd.serve_forever, daemon=True).start()
|
|
60
63
|
yield f"http://127.0.0.1:{httpd.server_address[1]}"
|
|
61
64
|
httpd.shutdown()
|
|
@@ -162,6 +165,7 @@ def test_iteration_is_re_entrant_rather_than_a_spent_generator(base):
|
|
|
162
165
|
|
|
163
166
|
|
|
164
167
|
def test_stopping_early_does_not_pay_for_the_rest(base):
|
|
168
|
+
"""Verify breaking out of iteration stops downloading remaining data."""
|
|
165
169
|
handle = reference(base, "/big.ndjson", "application/x-ndjson")
|
|
166
170
|
|
|
167
171
|
for row in handle:
|
|
@@ -268,8 +272,12 @@ def test_stopping_early_reads_at_most_one_batch_further(base, monkeypatch):
|
|
|
268
272
|
monkeypatch.setattr(http, "stream", counting)
|
|
269
273
|
|
|
270
274
|
async def first_only():
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
# Close explicitly to ensure the connection is promptly terminated.
|
|
276
|
+
records = aiter(reference(base, "/big.ndjson", "application/x-ndjson"))
|
|
277
|
+
try:
|
|
278
|
+
return await anext(records)
|
|
279
|
+
finally:
|
|
280
|
+
await records.aclose()
|
|
273
281
|
|
|
274
282
|
assert asyncio.run(first_only()) == {"i": 0}
|
|
275
283
|
assert consumed < len(BODIES["/big.ndjson"]) / 4, (
|
|
@@ -85,6 +85,23 @@ def test_a_small_stream_stays_inline(no_network):
|
|
|
85
85
|
assert "body" not in no_network
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def test_a_zero_inline_limit_from_the_platform_means_the_default(no_network):
|
|
89
|
+
"""Verify zero inline_max_bytes falls back to the default inline limit."""
|
|
90
|
+
raw = {"node_key": "n", "inline_max_bytes": 0, "memory_limit_mb": 512}
|
|
91
|
+
|
|
92
|
+
offered = Ctx.from_raw({**raw, "output_upload_url": "https://s/put", "output_key": "k"})
|
|
93
|
+
with offered.output_stream() as out:
|
|
94
|
+
out.write({"id": 1})
|
|
95
|
+
|
|
96
|
+
assert out.ref().block["type"] == "INLINE"
|
|
97
|
+
assert "body" not in no_network, "a small output must not be uploaded"
|
|
98
|
+
|
|
99
|
+
with Ctx.from_raw(raw).output_stream() as out:
|
|
100
|
+
out.write({"id": 1})
|
|
101
|
+
|
|
102
|
+
assert out.ref().block["type"] == "INLINE"
|
|
103
|
+
|
|
104
|
+
|
|
88
105
|
def test_writing_outside_the_block_is_refused():
|
|
89
106
|
stream = ctx().output_stream()
|
|
90
107
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The package's claims about itself.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
import dagflows
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_version_comes_from_the_installed_metadata():
|
|
13
|
+
"""Ensure __version__ matches the installed package metadata."""
|
|
14
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
expected = version("dagflows")
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
pytest.skip("running from an uninstalled source tree, no metadata to compare")
|
|
20
|
+
|
|
21
|
+
assert dagflows.__version__ == expected
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_version_agrees_with_pyproject():
|
|
25
|
+
"""The end to end claim: what the code reports is what the build declares."""
|
|
26
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
version("dagflows")
|
|
30
|
+
except PackageNotFoundError:
|
|
31
|
+
pytest.skip("running from an uninstalled source tree")
|
|
32
|
+
|
|
33
|
+
pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
34
|
+
declared = ""
|
|
35
|
+
for line in pyproject.read_text(encoding="utf-8").splitlines():
|
|
36
|
+
if line.startswith("version = "):
|
|
37
|
+
declared = line.split('"')[1]
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
assert declared, "pyproject.toml declares no version"
|
|
41
|
+
assert dagflows.__version__ == declared
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_an_unknown_attribute_still_raises():
|
|
45
|
+
"""The __getattr__ gained a branch, the error for everything else stays."""
|
|
46
|
+
with pytest.raises(AttributeError, match="nonsense"):
|
|
47
|
+
dagflows.nonsense # noqa: B018 - the attribute access is what raises
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 3
|
|
3
3
|
requires-python = ">=3.11"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -35,7 +35,7 @@ wheels = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "dagflows"
|
|
38
|
-
version = "0.2.
|
|
38
|
+
version = "0.2.2"
|
|
39
39
|
source = { editable = "." }
|
|
40
40
|
|
|
41
41
|
[package.optional-dependencies]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|