dagflows 0.2.2__tar.gz → 0.2.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.
- {dagflows-0.2.2 → dagflows-0.2.3}/PKG-INFO +1 -1
- {dagflows-0.2.2 → dagflows-0.2.3}/pyproject.toml +1 -1
- dagflows-0.2.3/src/dagflows/_runtime/ready.py +38 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/runner.py +5 -0
- dagflows-0.2.3/tests/test_ready.py +67 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/uv.lock +1 -1
- {dagflows-0.2.2 → dagflows-0.2.3}/.gitattributes +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/.github/workflows/release.yml +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/.github/workflows/test.yml +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/.gitignore +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/LICENSE +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/Makefile +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/README.md +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/__init__.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/__main__.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_authoring/__init__.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_authoring/config.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_authoring/node.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_authoring/workflow.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_cli/__init__.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_cli/build.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_cli/dev.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/__init__.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/binding.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/content.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/decode.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/dispatch.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/encode.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/envelope.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/errors.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/http.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/jsonstream.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/multipart.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/outstream.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/_runtime/result.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/src/dagflows/py.typed +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/conftest.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_binding.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_cli.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_dispatch.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_emit.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_envelope.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_http.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_input.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_jsonstream.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_multipart.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_outstream.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_package.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_readme.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_result.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_roundtrip.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_runner.py +0 -0
- {dagflows-0.2.2 → dagflows-0.2.3}/tests/test_workflow.py +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ready signal: tells the guest agent the runtime has finished starting.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
# Descriptor the guest agent listens on for the runtime's ready signal.
|
|
10
|
+
READY_FD_ENV = "DAGFLOWS_READY_FD"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def signal_ready() -> None:
|
|
14
|
+
"""Notify the guest agent that runtime initialization is complete and the handler is starting.
|
|
15
|
+
|
|
16
|
+
The node's execution timeout begins counting from this signal, ensuring that
|
|
17
|
+
interpreter startup and module imports do not consume the user's execution budget.
|
|
18
|
+
|
|
19
|
+
The signal dynamically tightens the execution deadline. If never sent, the agent
|
|
20
|
+
terminates the process at the hard ceiling (init allowance + timeout).
|
|
21
|
+
|
|
22
|
+
This function is idempotent and safe to call multiple times. If the ready file
|
|
23
|
+
descriptor is unset or unavailable, it silently no-ops.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
fd = int(os.environ.get(READY_FD_ENV, ""))
|
|
27
|
+
except ValueError:
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
if fd <= 0:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
os.write(fd, b".")
|
|
35
|
+
except OSError:
|
|
36
|
+
# The descriptor is the platform's to provide. A node cannot do
|
|
37
|
+
# anything about a broken one, and only its own deadline is affected.
|
|
38
|
+
pass
|
|
@@ -14,6 +14,7 @@ from typing import Any
|
|
|
14
14
|
from . import binding
|
|
15
15
|
from .envelope import OUTPUT_ENV, Ctx, Inputs, load
|
|
16
16
|
from .errors import Fail
|
|
17
|
+
from .ready import signal_ready
|
|
17
18
|
from .result import Upload, to_envelope
|
|
18
19
|
|
|
19
20
|
Handler = Callable[..., Any]
|
|
@@ -102,6 +103,10 @@ def run(handler: Handler) -> None:
|
|
|
102
103
|
ctx, inputs = load()
|
|
103
104
|
|
|
104
105
|
try:
|
|
106
|
+
# The handler starts here, so this is where the node's timeout should
|
|
107
|
+
# begin being charged. Everything above was the runtime starting up.
|
|
108
|
+
signal_ready()
|
|
109
|
+
|
|
105
110
|
answer = _call(handler, ctx, inputs)
|
|
106
111
|
envelope = to_envelope(
|
|
107
112
|
answer,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""The ready signal that starts a node's timeout at its handler."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
import dagflows
|
|
9
|
+
from dagflows._runtime.ready import READY_FD_ENV, signal_ready
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def ready_file(tmp_path, monkeypatch):
|
|
14
|
+
"""Point the ready descriptor at a file and report what the signal wrote."""
|
|
15
|
+
path = tmp_path / "signal"
|
|
16
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT)
|
|
17
|
+
monkeypatch.setenv(READY_FD_ENV, str(fd))
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
yield path
|
|
21
|
+
finally:
|
|
22
|
+
os.close(fd)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_it_writes_a_single_byte_to_the_descriptor_the_platform_names(ready_file):
|
|
26
|
+
signal_ready()
|
|
27
|
+
|
|
28
|
+
assert ready_file.read_bytes() == b"."
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_it_does_nothing_when_no_descriptor_is_named(monkeypatch):
|
|
32
|
+
monkeypatch.delenv(READY_FD_ENV, raising=False)
|
|
33
|
+
|
|
34
|
+
# An older worker passes no descriptor. The node still runs; it just spends
|
|
35
|
+
# its own timeout on startup, as it did before.
|
|
36
|
+
signal_ready()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.parametrize("value", ["", "0", "-1", "not-a-number", "9999"])
|
|
40
|
+
def test_it_does_nothing_when_the_descriptor_is_unusable(monkeypatch, value):
|
|
41
|
+
monkeypatch.setenv(READY_FD_ENV, value)
|
|
42
|
+
|
|
43
|
+
signal_ready()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_run_signals_ready_before_the_handler_runs_not_after(tmp_path, monkeypatch, ready_file):
|
|
47
|
+
"""A signal sent after the handler charges startup to the node, so order is the point."""
|
|
48
|
+
envelope = {"ctx": {"node_key": "n", "inline_max_bytes": 1 << 20}, "payload": {"inputs": {}}}
|
|
49
|
+
in_path = tmp_path / "in.json"
|
|
50
|
+
out_path = tmp_path / "out.json"
|
|
51
|
+
in_path.write_text(json.dumps(envelope), encoding="utf-8")
|
|
52
|
+
|
|
53
|
+
monkeypatch.setenv("DAGFLOWS_INPUT", str(in_path))
|
|
54
|
+
monkeypatch.setenv("DAGFLOWS_OUTPUT", str(out_path))
|
|
55
|
+
|
|
56
|
+
seen = {}
|
|
57
|
+
|
|
58
|
+
def handler(ctx, inputs):
|
|
59
|
+
# Reading the signal file as the handler's first act: a byte here proves
|
|
60
|
+
# the signal was sent before the handler was entered.
|
|
61
|
+
seen["at_entry"] = ready_file.read_bytes()
|
|
62
|
+
return {"ok": True}
|
|
63
|
+
|
|
64
|
+
dagflows.run(handler)
|
|
65
|
+
|
|
66
|
+
assert seen["at_entry"] == b"."
|
|
67
|
+
assert json.loads(out_path.read_text(encoding="utf-8"))["status"] == "SUCCESS"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|