reactor-runtime 2.0.2__tar.gz → 2.2.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.
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/PKG-INFO +2 -1
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/pyproject.toml +3 -2
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/capabilities.py +3 -1
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/__init__.py +17 -1
- reactor_runtime-2.2.0/src/reactor_runtime/interface/driver/__init__.py +7 -0
- reactor_runtime-2.2.0/src/reactor_runtime/interface/driver/pipeline_executor.py +297 -0
- reactor_runtime-2.2.0/src/reactor_runtime/interface/driver/step_result.py +30 -0
- reactor_runtime-2.2.0/src/reactor_runtime/interface/events/upload.py +21 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/decorators.py +40 -1
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/handlers.py +24 -3
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/reactor_model.py +9 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/pipeline/input_state.py +24 -3
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +91 -23
- reactor_runtime-2.2.0/src/reactor_runtime/interface/upload.py +26 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtime_api.py +118 -2
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtimes/headless/headless_runtime.py +4 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtimes/http/http_runtime.py +78 -2
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtimes/http/types.py +13 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sender/video.py +8 -8
- reactor_runtime-2.2.0/src/reactor_runtime/transports/gstreamer/settings.py +207 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/utils/messages.py +1 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime.egg-info/PKG-INFO +2 -1
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime.egg-info/SOURCES.txt +5 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime.egg-info/requires.txt +1 -0
- reactor_runtime-2.0.2/src/reactor_runtime/transports/gstreamer/settings.py +0 -62
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/README.md +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/setup.cfg +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/api/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/commands/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/commands/capabilities.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/commands/init.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/commands/run.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/main.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/utils/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/utils/config.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/utils/runtime.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_cli/utils/version.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/config.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/events/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/events/connected.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/events/event.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/events/messages.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/internal/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/input_fields.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/tracks/input.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/tracks/output.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/model_state.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/backends/base.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/backends/file.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/helpers.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/profiler.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/profiling/singleton.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtimes/headless/config.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/runtimes/http/config.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/aiortc/client.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/config.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/events.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/ice_uris.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/interface.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/media.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/transports/types.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/utils/launch.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/utils/loader.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/utils/log.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/utils/schema.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime.egg-info/entry_points.txt +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime.egg-info/top_level.txt +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/README.md +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/__init__.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/config.yml +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/model.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/pipeline.py +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/reactor.yaml +0 -0
- {reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/template/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reactor_runtime
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Reactor runtime with public model API
|
|
5
5
|
Author-email: Reactor <team@reactor.inc>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -12,6 +12,7 @@ Requires-Dist: av>=14.0.0
|
|
|
12
12
|
Requires-Dist: aiortc>=1.14.0
|
|
13
13
|
Requires-Dist: fastapi>=0.100.0
|
|
14
14
|
Requires-Dist: uvicorn[standard]>=0.23.0
|
|
15
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
15
16
|
Requires-Dist: redis
|
|
16
17
|
Requires-Dist: opentelemetry-api~=1.39
|
|
17
18
|
Requires-Dist: opentelemetry-sdk~=1.39
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "reactor_runtime"
|
|
7
|
-
version = "2.0
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Reactor runtime with public model API"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Reactor", email = "team@reactor.inc" }
|
|
@@ -20,6 +20,7 @@ dependencies = [
|
|
|
20
20
|
"aiortc>=1.14.0",
|
|
21
21
|
"fastapi>=0.100.0",
|
|
22
22
|
"uvicorn[standard]>=0.23.0",
|
|
23
|
+
"aiohttp>=3.9.0",
|
|
23
24
|
"redis",
|
|
24
25
|
"opentelemetry-api~=1.39",
|
|
25
26
|
"opentelemetry-sdk~=1.39",
|
|
@@ -45,4 +46,4 @@ template = ["*.yaml", "*.yml", "*.txt", "*.md"]
|
|
|
45
46
|
# Proto dependency from reactor-team/reactor-proto
|
|
46
47
|
# Release tag format: golang/v{version}
|
|
47
48
|
# Wheel name format: reactor_proto-{version_without_hash}-py3-none-any.whl
|
|
48
|
-
version = "1.1
|
|
49
|
+
version = "1.4.1-g5660eed"
|
|
@@ -24,15 +24,17 @@ from reactor_runtime.interface.model.input_fields import FieldInfo
|
|
|
24
24
|
from reactor_runtime.interface.tracks.descriptors import Audio
|
|
25
25
|
from reactor_runtime.interface.tracks.input import INPUT_REGISTRY
|
|
26
26
|
from reactor_runtime.interface.tracks.output import OUTPUT_REGISTRY
|
|
27
|
+
from reactor_runtime.interface.upload import UploadedFile
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
CAPABILITIES_VERSION = "1.
|
|
30
|
+
CAPABILITIES_VERSION = "1.2"
|
|
30
31
|
|
|
31
32
|
_TYPE_MAP = {
|
|
32
33
|
int: "integer",
|
|
33
34
|
float: "number",
|
|
34
35
|
str: "string",
|
|
35
36
|
bool: "boolean",
|
|
37
|
+
UploadedFile: "file",
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
|
|
@@ -19,11 +19,18 @@ from reactor_runtime.interface.tracks.output import (
|
|
|
19
19
|
# Events & Messages
|
|
20
20
|
from reactor_runtime.interface.events.event import EVENT_REGISTRY, Event
|
|
21
21
|
from reactor_runtime.interface.events.connected import Connected, Disconnected
|
|
22
|
+
from reactor_runtime.interface.events.upload import FileUploaded
|
|
23
|
+
from reactor_runtime.interface.upload import UploadedFile
|
|
22
24
|
from reactor_runtime.interface.events.messages import MESSAGE_REGISTRY, ModelMessage
|
|
23
25
|
|
|
24
26
|
# Model layer (canonical) — includes decorators and field metadata
|
|
25
27
|
from reactor_runtime.interface.model.reactor_model import ReactorModel
|
|
26
|
-
from reactor_runtime.interface.model.decorators import
|
|
28
|
+
from reactor_runtime.interface.model.decorators import (
|
|
29
|
+
connected,
|
|
30
|
+
disconnected,
|
|
31
|
+
event,
|
|
32
|
+
file_uploaded,
|
|
33
|
+
)
|
|
27
34
|
from reactor_runtime.interface.model.input_fields import FieldInfo, InputField
|
|
28
35
|
|
|
29
36
|
# Pipeline — generator + typed state layer
|
|
@@ -31,6 +38,9 @@ from reactor_runtime.interface.pipeline.reactor_pipeline import ReactorPipeline
|
|
|
31
38
|
from reactor_runtime.interface.pipeline.input_state import InputState
|
|
32
39
|
from reactor_runtime.interface.pipeline.idle import Idle
|
|
33
40
|
|
|
41
|
+
# Driver — programmatic step-by-step control
|
|
42
|
+
from reactor_runtime.interface.driver import PipelineExecutor, StepResult
|
|
43
|
+
|
|
34
44
|
# Internal (exposed for runtime integration, not stable API)
|
|
35
45
|
from reactor_runtime.interface.internal.reactor_core import ReactorCore
|
|
36
46
|
from reactor_runtime.interface.internal.output_buffer import OutputBuffer
|
|
@@ -54,6 +64,8 @@ __all__ = [
|
|
|
54
64
|
"Event",
|
|
55
65
|
"Connected",
|
|
56
66
|
"Disconnected",
|
|
67
|
+
"FileUploaded",
|
|
68
|
+
"UploadedFile",
|
|
57
69
|
"EVENT_REGISTRY",
|
|
58
70
|
"ModelMessage",
|
|
59
71
|
"MESSAGE_REGISTRY",
|
|
@@ -62,12 +74,16 @@ __all__ = [
|
|
|
62
74
|
"event",
|
|
63
75
|
"connected",
|
|
64
76
|
"disconnected",
|
|
77
|
+
"file_uploaded",
|
|
65
78
|
"FieldInfo",
|
|
66
79
|
"InputField",
|
|
67
80
|
# Pipeline
|
|
68
81
|
"ReactorPipeline",
|
|
69
82
|
"InputState",
|
|
70
83
|
"Idle",
|
|
84
|
+
# Driver
|
|
85
|
+
"PipelineExecutor",
|
|
86
|
+
"StepResult",
|
|
71
87
|
# Internal
|
|
72
88
|
"ReactorCore",
|
|
73
89
|
"OutputBuffer",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""Programmatic pipeline executor — step-by-step control without a server."""
|
|
3
|
+
|
|
4
|
+
from reactor_runtime.interface.driver.pipeline_executor import PipelineExecutor
|
|
5
|
+
from reactor_runtime.interface.driver.step_result import StepResult
|
|
6
|
+
|
|
7
|
+
__all__ = ["PipelineExecutor", "StepResult"]
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""PipelineExecutor — programmatic step-by-step control of a ReactorPipeline."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import inspect
|
|
8
|
+
from typing import Any, Dict, Iterator, List, Optional, Type
|
|
9
|
+
|
|
10
|
+
from reactor_runtime.interface.driver.step_result import StepResult
|
|
11
|
+
from reactor_runtime.interface.events.messages import ModelMessage
|
|
12
|
+
from reactor_runtime.interface.model.handlers import Handlers, build_dispatch_table
|
|
13
|
+
from reactor_runtime.interface.model.input_fields import validate_field
|
|
14
|
+
from reactor_runtime.interface.pipeline.idle import Idle
|
|
15
|
+
from reactor_runtime.interface.pipeline.reactor_pipeline import (
|
|
16
|
+
GeneratorEnded,
|
|
17
|
+
ReactorPipeline,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PipelineExecutor(Iterator[StepResult]):
|
|
22
|
+
"""Execute a :class:`ReactorPipeline` step-by-step from synchronous code.
|
|
23
|
+
|
|
24
|
+
Wraps any ``ReactorPipeline`` subclass and exposes a clean
|
|
25
|
+
sequential API: ``connect()``, iteration via ``next()`` / ``for``,
|
|
26
|
+
``send_event()``, ``push_media()``, ``disconnect()``.
|
|
27
|
+
No server, no transport, no background threads.
|
|
28
|
+
|
|
29
|
+
Implements ``Iterator[StepResult]`` — each call to ``next(exe)``
|
|
30
|
+
advances the inference generator by one ``yield``. Iteration
|
|
31
|
+
stops with ``StopIteration`` when no session is active.
|
|
32
|
+
|
|
33
|
+
Each action returns the :class:`ModelMessage` instances the model
|
|
34
|
+
sent via ``self.send()`` during that action, enabling deterministic
|
|
35
|
+
assertions in tests and structured output capture in scripts.
|
|
36
|
+
|
|
37
|
+
Example::
|
|
38
|
+
|
|
39
|
+
with PipelineExecutor(EchoPipeline) as exe:
|
|
40
|
+
exe.connect()
|
|
41
|
+
exe.push_media("webcam", frame)
|
|
42
|
+
for r in exe:
|
|
43
|
+
if r.output is not Idle:
|
|
44
|
+
break
|
|
45
|
+
msgs = exe.send_event("set_effect", effect="sepia")
|
|
46
|
+
exe.disconnect()
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
pipeline_cls: Type[ReactorPipeline],
|
|
52
|
+
config: Optional[Dict[str, Any]] = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
# Instantiate the pipeline and run its one-time load().
|
|
55
|
+
self._model: ReactorPipeline = pipeline_cls()
|
|
56
|
+
self._model.load(config or {})
|
|
57
|
+
|
|
58
|
+
# Build the handler dispatch table from @event / @connected /
|
|
59
|
+
# @disconnected decorators discovered on the pipeline class.
|
|
60
|
+
self._handlers: Handlers = self._model._get_handlers()
|
|
61
|
+
self._dispatch_table = build_dispatch_table(self._handlers)
|
|
62
|
+
|
|
63
|
+
# Private event loop — _advance() and some handlers are async,
|
|
64
|
+
# so we need a loop. Everything still runs on the caller's
|
|
65
|
+
# thread; there are no background threads.
|
|
66
|
+
self._loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
|
|
67
|
+
self._loop.run_until_complete(self._init())
|
|
68
|
+
|
|
69
|
+
# Track whether inference() is sync or async so _advance()
|
|
70
|
+
# uses the right iteration protocol (next vs __anext__).
|
|
71
|
+
self._gen = None
|
|
72
|
+
self._is_async_gen: bool = inspect.isasyncgenfunction(self._model.inference)
|
|
73
|
+
self._model._async_inference = self._is_async_gen
|
|
74
|
+
|
|
75
|
+
# Message capture — override send() to collect ModelMessage
|
|
76
|
+
# instances instead of serializing them over transport.
|
|
77
|
+
# _run() clears this list before every action so that
|
|
78
|
+
# messages are scoped to the action that produced them.
|
|
79
|
+
self._captured: List[ModelMessage] = []
|
|
80
|
+
|
|
81
|
+
async def _capturing_send(message: ModelMessage) -> None:
|
|
82
|
+
self._captured.append(message)
|
|
83
|
+
|
|
84
|
+
self._model.send = _capturing_send # type: ignore[assignment]
|
|
85
|
+
|
|
86
|
+
async def _init(self) -> None:
|
|
87
|
+
self._model._init_async()
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
# Public API
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def connect(self) -> List[ModelMessage]:
|
|
94
|
+
"""Begin a session.
|
|
95
|
+
|
|
96
|
+
Creates a fresh :class:`InputState`, starts the ``inference()``
|
|
97
|
+
generator, sets the ``connected`` signal, and fires the
|
|
98
|
+
``@connected`` handler if defined.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Messages the model sent during the ``@connected`` handler.
|
|
102
|
+
"""
|
|
103
|
+
if self._gen is not None:
|
|
104
|
+
raise RuntimeError(
|
|
105
|
+
"connect() called on an already-connected executor. "
|
|
106
|
+
"Call disconnect() first."
|
|
107
|
+
)
|
|
108
|
+
return self._run(self._connect())
|
|
109
|
+
|
|
110
|
+
def __iter__(self) -> Iterator[StepResult]:
|
|
111
|
+
"""Return self — PipelineExecutor is its own iterator."""
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def __next__(self) -> StepResult:
|
|
115
|
+
"""Advance the inference generator by one ``yield``.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A :class:`StepResult` with the output, compute time, and
|
|
119
|
+
any messages sent during this step.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
StopIteration: No active session (before ``connect()``
|
|
123
|
+
or after ``disconnect()``).
|
|
124
|
+
"""
|
|
125
|
+
if self._gen is None:
|
|
126
|
+
raise StopIteration
|
|
127
|
+
return self._run(self._step())
|
|
128
|
+
|
|
129
|
+
def send_event(self, name: str, **kwargs: Any) -> List[ModelMessage]:
|
|
130
|
+
"""Deliver a named event to the model.
|
|
131
|
+
|
|
132
|
+
Validates strictly before calling the handler:
|
|
133
|
+
|
|
134
|
+
1. ``ValueError`` if the event name is unknown.
|
|
135
|
+
2. ``TypeError`` if the kwargs don't match the event dataclass.
|
|
136
|
+
3. ``ValueError`` if an ``InputField`` constraint is violated.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Messages the model sent during the event handler.
|
|
140
|
+
"""
|
|
141
|
+
entry = self._handlers.events.get(name)
|
|
142
|
+
if entry is None:
|
|
143
|
+
available = sorted(self._handlers.events.keys())
|
|
144
|
+
raise ValueError(f"Unknown event {name!r}. Available events: {available}")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
entry.event_cls(**kwargs)
|
|
148
|
+
except TypeError as exc:
|
|
149
|
+
raise TypeError(f"Invalid arguments for event {name!r}: {exc}") from None
|
|
150
|
+
|
|
151
|
+
field_infos: Dict = getattr(entry.event_cls, "_field_infos", {})
|
|
152
|
+
for field_name, info in field_infos.items():
|
|
153
|
+
if field_name in kwargs:
|
|
154
|
+
ok, reason = validate_field(field_name, kwargs[field_name], info)
|
|
155
|
+
if not ok:
|
|
156
|
+
raise ValueError(f"Validation failed for event {name!r}: {reason}")
|
|
157
|
+
|
|
158
|
+
return self._run(self._send_event(entry, **kwargs))
|
|
159
|
+
|
|
160
|
+
def push_media(self, track_name: str, data: Any) -> None:
|
|
161
|
+
"""Push a media frame into an input buffer.
|
|
162
|
+
|
|
163
|
+
The frame becomes available to the model on the next
|
|
164
|
+
iteration when ``inference()`` calls ``try_read()`` or
|
|
165
|
+
``read()``.
|
|
166
|
+
"""
|
|
167
|
+
self._model._push_media(track_name, data)
|
|
168
|
+
|
|
169
|
+
def disconnect(self) -> List[ModelMessage]:
|
|
170
|
+
"""End the current session.
|
|
171
|
+
|
|
172
|
+
Clears the ``connected`` signal, fires the ``@disconnected``
|
|
173
|
+
handler, closes the generator, and resets state and buffers.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Messages the model sent during the ``@disconnected`` handler.
|
|
177
|
+
"""
|
|
178
|
+
if self._gen is None:
|
|
179
|
+
raise RuntimeError(
|
|
180
|
+
"disconnect() called without an active session. Call connect() first."
|
|
181
|
+
)
|
|
182
|
+
return self._run(self._disconnect())
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def state(self):
|
|
186
|
+
"""The current session state, or ``None`` if no session is active."""
|
|
187
|
+
return self._model.state
|
|
188
|
+
|
|
189
|
+
def close(self) -> None:
|
|
190
|
+
"""Shut down the executor.
|
|
191
|
+
|
|
192
|
+
Disconnects if a session is active and closes the event loop.
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
if self._gen is not None:
|
|
196
|
+
self._run(self._disconnect())
|
|
197
|
+
finally:
|
|
198
|
+
if not self._loop.is_closed():
|
|
199
|
+
self._loop.close()
|
|
200
|
+
|
|
201
|
+
def __enter__(self):
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def __exit__(self, *exc: Any) -> None:
|
|
205
|
+
self.close()
|
|
206
|
+
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
# Async implementations
|
|
209
|
+
#
|
|
210
|
+
# Each method below is the async core of a public API method.
|
|
211
|
+
# The public wrappers delegate to these via _run(), which clears
|
|
212
|
+
# captured messages and runs the coroutine synchronously with
|
|
213
|
+
# run_until_complete().
|
|
214
|
+
# ------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
async def _connect(self) -> List[ModelMessage]:
|
|
217
|
+
"""Create a fresh InputState, start inference(), fire @connected."""
|
|
218
|
+
self._model.state = self._model._state_cls()
|
|
219
|
+
self._gen = self._model.inference()
|
|
220
|
+
self._model.connected.set()
|
|
221
|
+
await self._fire_handler(
|
|
222
|
+
self._handlers.connected,
|
|
223
|
+
self._handlers.connected_is_async,
|
|
224
|
+
)
|
|
225
|
+
return list(self._captured)
|
|
226
|
+
|
|
227
|
+
async def _step(self) -> StepResult:
|
|
228
|
+
"""Advance the inference generator by one ``yield``.
|
|
229
|
+
|
|
230
|
+
On ``GeneratorEnded`` the generator is restarted automatically
|
|
231
|
+
(same behavior as the production run-loop) and ``Idle`` is
|
|
232
|
+
returned for that tick.
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
output, compute_time = await self._model._advance(
|
|
236
|
+
self._gen, self._is_async_gen
|
|
237
|
+
)
|
|
238
|
+
except GeneratorEnded:
|
|
239
|
+
self._gen = self._model.inference()
|
|
240
|
+
return StepResult(
|
|
241
|
+
output=Idle, compute_time=0.0, messages=list(self._captured)
|
|
242
|
+
)
|
|
243
|
+
return StepResult(
|
|
244
|
+
output=output,
|
|
245
|
+
compute_time=compute_time,
|
|
246
|
+
messages=list(self._captured),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def _send_event(self, entry: Any, **kwargs: Any) -> List[ModelMessage]:
|
|
250
|
+
"""Dispatch a validated event to its handler function."""
|
|
251
|
+
await self._fire_handler(entry.handler, entry.is_async, **kwargs)
|
|
252
|
+
return list(self._captured)
|
|
253
|
+
|
|
254
|
+
async def _disconnect(self) -> List[ModelMessage]:
|
|
255
|
+
"""Clear connected, fire @disconnected, close generator, reset state."""
|
|
256
|
+
self._model.connected.clear()
|
|
257
|
+
await self._fire_handler(
|
|
258
|
+
self._handlers.disconnected,
|
|
259
|
+
self._handlers.disconnected_is_async,
|
|
260
|
+
)
|
|
261
|
+
if self._gen is not None:
|
|
262
|
+
if self._is_async_gen:
|
|
263
|
+
await self._gen.aclose()
|
|
264
|
+
else:
|
|
265
|
+
self._gen.close()
|
|
266
|
+
self._gen = None
|
|
267
|
+
self._model.state = None
|
|
268
|
+
for buf in self._model._input_buffers.values():
|
|
269
|
+
buf.reset()
|
|
270
|
+
return list(self._captured)
|
|
271
|
+
|
|
272
|
+
# ------------------------------------------------------------------
|
|
273
|
+
# Infrastructure
|
|
274
|
+
# ------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
async def _fire_handler(
|
|
277
|
+
self,
|
|
278
|
+
handler: Any,
|
|
279
|
+
is_async: bool,
|
|
280
|
+
**kwargs: Any,
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Call a lifecycle or event handler, dispatching sync/async."""
|
|
283
|
+
if handler is None:
|
|
284
|
+
return
|
|
285
|
+
if is_async:
|
|
286
|
+
await handler(self._model, **kwargs)
|
|
287
|
+
else:
|
|
288
|
+
handler(self._model, **kwargs)
|
|
289
|
+
|
|
290
|
+
def _run(self, coro: Any) -> Any:
|
|
291
|
+
"""Clear captured messages then run *coro* synchronously.
|
|
292
|
+
|
|
293
|
+
The clear-before-run pattern scopes messages: each public
|
|
294
|
+
action sees only the messages produced during that action.
|
|
295
|
+
"""
|
|
296
|
+
self._captured.clear()
|
|
297
|
+
return self._loop.run_until_complete(coro)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""StepResult — return type of ``next(PipelineExecutor)``."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import List, Union
|
|
8
|
+
|
|
9
|
+
from reactor_runtime.interface.events.messages import ModelMessage
|
|
10
|
+
from reactor_runtime.interface.pipeline.idle import _IdleType
|
|
11
|
+
from reactor_runtime.interface.tracks.output import Output
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class StepResult:
|
|
16
|
+
"""Result of a single inference step.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
output: The resolved :class:`Output` instance that the
|
|
20
|
+
generator yielded, or :data:`Idle` when it yielded
|
|
21
|
+
``Idle`` or ``None``.
|
|
22
|
+
compute_time: Wall-clock seconds spent inside the generator
|
|
23
|
+
for this step (excludes transport and emission overhead).
|
|
24
|
+
messages: :class:`ModelMessage` instances the model sent via
|
|
25
|
+
``self.send()`` during this step.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
output: Union[Output, _IdleType]
|
|
29
|
+
compute_time: float
|
|
30
|
+
messages: List[ModelMessage] = field(default_factory=list)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""Built-in file upload lifecycle event — :class:`FileUploaded`."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from reactor_runtime.interface.events.event import Event
|
|
9
|
+
from reactor_runtime.interface.upload import UploadedFile
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class FileUploaded(Event, internal=True): # type: ignore[call-arg]
|
|
14
|
+
"""Fired when an uploaded file has been downloaded and is ready.
|
|
15
|
+
|
|
16
|
+
Handle with the ``@file_uploaded`` decorator on your model method.
|
|
17
|
+
This is a lifecycle event pushed by the runtime — clients cannot
|
|
18
|
+
trigger it via the data channel.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
file: UploadedFile
|
{reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/decorators.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
2
|
"""
|
|
3
|
-
Model decorators — ``@connected``, ``@disconnected``, and ``@event``.
|
|
3
|
+
Model decorators — ``@connected``, ``@disconnected``, ``@file_uploaded``, and ``@event``.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -12,9 +12,11 @@ from typing import Any, Callable, List, Tuple, Type, Union, get_type_hints
|
|
|
12
12
|
|
|
13
13
|
from reactor_runtime.interface.events.event import Event, _snake_to_pascal
|
|
14
14
|
from reactor_runtime.interface.model.input_fields import FieldInfo, _NO_DEFAULT
|
|
15
|
+
from reactor_runtime.interface.upload import UploadedFile
|
|
15
16
|
|
|
16
17
|
_CONNECTED_ATTR = "__reactor_connected__"
|
|
17
18
|
_DISCONNECTED_ATTR = "__reactor_disconnected__"
|
|
19
|
+
_FILE_UPLOADED_ATTR = "__reactor_file_uploaded__"
|
|
18
20
|
_EVENT_HANDLER_ATTR = "__reactor_event_meta__"
|
|
19
21
|
_IS_ASYNC_ATTR = "__reactor_is_async__"
|
|
20
22
|
|
|
@@ -59,6 +61,36 @@ def disconnected(func: Callable) -> Callable:
|
|
|
59
61
|
return func
|
|
60
62
|
|
|
61
63
|
|
|
64
|
+
def file_uploaded(func: Callable) -> Callable:
|
|
65
|
+
"""Mark a method as the handler called when a file is uploaded.
|
|
66
|
+
|
|
67
|
+
Called each time the client uploads a file to the runtime.
|
|
68
|
+
Only one ``@file_uploaded`` handler is allowed per model class.
|
|
69
|
+
The handler receives a single ``uploaded_file`` keyword argument
|
|
70
|
+
of type :class:`UploadedFile`. The handler may be ``async def``
|
|
71
|
+
or a plain ``def``.
|
|
72
|
+
|
|
73
|
+
Example::
|
|
74
|
+
|
|
75
|
+
@file_uploaded
|
|
76
|
+
async def on_file(self, uploaded_file: UploadedFile):
|
|
77
|
+
if uploaded_file.mime_type.startswith("image/"):
|
|
78
|
+
pil_image = Image.open(io.BytesIO(uploaded_file.data))
|
|
79
|
+
"""
|
|
80
|
+
if not callable(func):
|
|
81
|
+
raise TypeError(f"@file_uploaded expected a callable, got {type(func)}")
|
|
82
|
+
sig = inspect.signature(func)
|
|
83
|
+
params = [p for p in sig.parameters if p != "self"]
|
|
84
|
+
if params != ["uploaded_file"]:
|
|
85
|
+
raise TypeError(
|
|
86
|
+
f"@file_uploaded handler must accept exactly one parameter named "
|
|
87
|
+
f"'uploaded_file', got: {params}"
|
|
88
|
+
)
|
|
89
|
+
setattr(func, _FILE_UPLOADED_ATTR, True)
|
|
90
|
+
setattr(func, _IS_ASYNC_ATTR, inspect.iscoroutinefunction(func))
|
|
91
|
+
return func
|
|
92
|
+
|
|
93
|
+
|
|
62
94
|
def event(*, name: str, description: str = "", dedupe: bool = False):
|
|
63
95
|
"""Mark a method as a named event handler.
|
|
64
96
|
|
|
@@ -143,12 +175,16 @@ def _make_event_from_method(func: Callable, event_name: str) -> Type[Event]:
|
|
|
143
175
|
|
|
144
176
|
fields: list = []
|
|
145
177
|
field_infos: dict[str, FieldInfo] = {}
|
|
178
|
+
upload_fields: set[str] = set()
|
|
146
179
|
|
|
147
180
|
for param_name, param in sig.parameters.items():
|
|
148
181
|
if param_name == "self":
|
|
149
182
|
continue
|
|
150
183
|
param_type = hints.get(param_name, Any)
|
|
151
184
|
|
|
185
|
+
if param_type is UploadedFile:
|
|
186
|
+
upload_fields.add(param_name)
|
|
187
|
+
|
|
152
188
|
if param.default != inspect.Parameter.empty:
|
|
153
189
|
raw_default = param.default
|
|
154
190
|
if isinstance(raw_default, FieldInfo):
|
|
@@ -175,4 +211,7 @@ def _make_event_from_method(func: Callable, event_name: str) -> Type[Event]:
|
|
|
175
211
|
if field_infos:
|
|
176
212
|
event_cls._field_infos = field_infos # type: ignore[attr-defined]
|
|
177
213
|
|
|
214
|
+
if upload_fields:
|
|
215
|
+
event_cls._upload_fields = upload_fields # type: ignore[attr-defined]
|
|
216
|
+
|
|
178
217
|
return event_cls
|
{reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/handlers.py
RENAMED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Handler collection and dispatch table construction.
|
|
4
4
|
|
|
5
|
-
Scans a model class's MRO for ``@connected``, ``@disconnected``,
|
|
6
|
-
``@event`` decorated methods and builds a
|
|
7
|
-
:class:`ReactorModel`.
|
|
5
|
+
Scans a model class's MRO for ``@connected``, ``@disconnected``,
|
|
6
|
+
``@file_uploaded``, and ``@event`` decorated methods and builds a
|
|
7
|
+
dispatch table used by :class:`ReactorModel`.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -16,6 +16,7 @@ from reactor_runtime.interface.model.decorators import (
|
|
|
16
16
|
_CONNECTED_ATTR,
|
|
17
17
|
_DISCONNECTED_ATTR,
|
|
18
18
|
_EVENT_HANDLER_ATTR,
|
|
19
|
+
_FILE_UPLOADED_ATTR,
|
|
19
20
|
_IS_ASYNC_ATTR,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -48,6 +49,8 @@ class Handlers:
|
|
|
48
49
|
"connected_is_async",
|
|
49
50
|
"disconnected",
|
|
50
51
|
"disconnected_is_async",
|
|
52
|
+
"file_uploaded",
|
|
53
|
+
"file_uploaded_is_async",
|
|
51
54
|
"events",
|
|
52
55
|
)
|
|
53
56
|
|
|
@@ -56,6 +59,8 @@ class Handlers:
|
|
|
56
59
|
self.connected_is_async: bool = False
|
|
57
60
|
self.disconnected: Optional[Callable] = None
|
|
58
61
|
self.disconnected_is_async: bool = False
|
|
62
|
+
self.file_uploaded: Optional[Callable] = None
|
|
63
|
+
self.file_uploaded_is_async: bool = False
|
|
59
64
|
self.events: Dict[str, EventEntry] = {}
|
|
60
65
|
|
|
61
66
|
|
|
@@ -78,6 +83,7 @@ def collect_handlers(cls: type, skip_classes: tuple = ()) -> Handlers:
|
|
|
78
83
|
|
|
79
84
|
connected_in_class: List[Callable] = []
|
|
80
85
|
disconnected_in_class: List[Callable] = []
|
|
86
|
+
file_uploaded_in_class: List[Callable] = []
|
|
81
87
|
|
|
82
88
|
for _attr_name, attr_value in vars(klass).items():
|
|
83
89
|
if not callable(attr_value):
|
|
@@ -89,6 +95,9 @@ def collect_handlers(cls: type, skip_classes: tuple = ()) -> Handlers:
|
|
|
89
95
|
if getattr(attr_value, _DISCONNECTED_ATTR, False):
|
|
90
96
|
disconnected_in_class.append(attr_value)
|
|
91
97
|
|
|
98
|
+
if getattr(attr_value, _FILE_UPLOADED_ATTR, False):
|
|
99
|
+
file_uploaded_in_class.append(attr_value)
|
|
100
|
+
|
|
92
101
|
meta = getattr(attr_value, _EVENT_HANDLER_ATTR, None)
|
|
93
102
|
if meta is not None and meta["name"] not in seen_events:
|
|
94
103
|
handlers.events[meta["name"]] = EventEntry(
|
|
@@ -117,6 +126,11 @@ def collect_handlers(cls: type, skip_classes: tuple = ()) -> Handlers:
|
|
|
117
126
|
f"Multiple @disconnected handlers in {klass.__qualname__}: "
|
|
118
127
|
f"{[f.__qualname__ for f in disconnected_in_class]}"
|
|
119
128
|
)
|
|
129
|
+
if len(file_uploaded_in_class) > 1:
|
|
130
|
+
raise TypeError(
|
|
131
|
+
f"Multiple @file_uploaded handlers in {klass.__qualname__}: "
|
|
132
|
+
f"{[f.__qualname__ for f in file_uploaded_in_class]}"
|
|
133
|
+
)
|
|
120
134
|
|
|
121
135
|
if connected_in_class and handlers.connected is None:
|
|
122
136
|
handlers.connected = connected_in_class[0]
|
|
@@ -132,6 +146,13 @@ def collect_handlers(cls: type, skip_classes: tuple = ()) -> Handlers:
|
|
|
132
146
|
_IS_ASYNC_ATTR,
|
|
133
147
|
False,
|
|
134
148
|
)
|
|
149
|
+
if file_uploaded_in_class and handlers.file_uploaded is None:
|
|
150
|
+
handlers.file_uploaded = file_uploaded_in_class[0]
|
|
151
|
+
handlers.file_uploaded_is_async = getattr(
|
|
152
|
+
file_uploaded_in_class[0],
|
|
153
|
+
_IS_ASYNC_ATTR,
|
|
154
|
+
False,
|
|
155
|
+
)
|
|
135
156
|
|
|
136
157
|
return handlers
|
|
137
158
|
|
{reactor_runtime-2.0.2 → reactor_runtime-2.2.0}/src/reactor_runtime/interface/model/reactor_model.py
RENAMED
|
@@ -16,6 +16,7 @@ from typing import Any, Callable, List, Tuple, Type
|
|
|
16
16
|
|
|
17
17
|
from reactor_runtime.interface.events.connected import Connected, Disconnected
|
|
18
18
|
from reactor_runtime.interface.events.event import Event
|
|
19
|
+
from reactor_runtime.interface.events.upload import FileUploaded
|
|
19
20
|
from reactor_runtime.interface.model.handlers import (
|
|
20
21
|
Handlers,
|
|
21
22
|
build_dispatch_table,
|
|
@@ -156,6 +157,14 @@ class ReactorModel(ReactorCore):
|
|
|
156
157
|
handlers.disconnected_is_async,
|
|
157
158
|
)
|
|
158
159
|
|
|
160
|
+
elif isinstance(event, FileUploaded):
|
|
161
|
+
if handlers.file_uploaded is not None:
|
|
162
|
+
await self._invoke(
|
|
163
|
+
handlers.file_uploaded,
|
|
164
|
+
handlers.file_uploaded_is_async,
|
|
165
|
+
uploaded_file=event.file,
|
|
166
|
+
)
|
|
167
|
+
|
|
159
168
|
else:
|
|
160
169
|
await self._dispatch_event(event, dispatch_table)
|
|
161
170
|
|