reactor-runtime 2.2.0__tar.gz → 2.2.1__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.
Files changed (130) hide show
  1. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/PKG-INFO +1 -1
  2. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/pyproject.toml +1 -1
  3. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/capabilities.py +32 -10
  4. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/model/decorators.py +10 -1
  5. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/pipeline/input_state.py +2 -1
  6. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtimes/http/http_runtime.py +6 -2
  7. reactor_runtime-2.2.1/src/reactor_runtime/utils/typing.py +22 -0
  8. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
  9. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime.egg-info/SOURCES.txt +1 -0
  10. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/README.md +0 -0
  11. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/setup.cfg +0 -0
  12. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/api/__init__.py +0 -0
  13. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/commands/__init__.py +0 -0
  14. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/commands/capabilities.py +0 -0
  15. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/commands/init.py +0 -0
  16. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/commands/run.py +0 -0
  17. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/main.py +0 -0
  18. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/utils/__init__.py +0 -0
  19. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/utils/config.py +0 -0
  20. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/utils/runtime.py +0 -0
  21. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_cli/utils/version.py +0 -0
  22. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/__init__.py +0 -0
  23. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/config.py +0 -0
  24. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/__init__.py +0 -0
  25. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/driver/__init__.py +0 -0
  26. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/driver/pipeline_executor.py +0 -0
  27. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/driver/step_result.py +0 -0
  28. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/events/__init__.py +0 -0
  29. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/events/connected.py +0 -0
  30. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/events/event.py +0 -0
  31. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/events/messages.py +0 -0
  32. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/events/upload.py +0 -0
  33. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/internal/__init__.py +0 -0
  34. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
  35. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
  36. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
  37. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/model/__init__.py +0 -0
  38. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/model/handlers.py +0 -0
  39. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/model/input_fields.py +0 -0
  40. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
  41. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
  42. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
  43. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
  44. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
  45. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
  46. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/tracks/input.py +0 -0
  47. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/tracks/output.py +0 -0
  48. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/interface/upload.py +0 -0
  49. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/model_state.py +0 -0
  50. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/__init__.py +0 -0
  51. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
  52. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/backends/base.py +0 -0
  53. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/backends/file.py +0 -0
  54. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
  55. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/helpers.py +0 -0
  56. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
  57. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
  58. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/profiler.py +0 -0
  59. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/profiling/singleton.py +0 -0
  60. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtime_api.py +0 -0
  61. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtimes/headless/config.py +0 -0
  62. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtimes/headless/headless_runtime.py +0 -0
  63. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
  64. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtimes/http/config.py +0 -0
  65. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/runtimes/http/types.py +0 -0
  66. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/__init__.py +0 -0
  67. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
  68. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
  69. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/aiortc/client.py +0 -0
  70. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
  71. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
  72. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
  73. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/config.py +0 -0
  74. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/events.py +0 -0
  75. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
  76. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
  77. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
  78. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
  79. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
  80. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
  81. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
  82. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
  83. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
  84. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
  85. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
  86. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
  87. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
  88. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
  89. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
  90. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
  91. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
  92. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
  93. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
  94. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
  95. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
  96. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
  97. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
  98. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
  99. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
  100. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
  101. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
  102. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
  103. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
  104. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
  105. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
  106. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
  107. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
  108. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
  109. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
  110. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
  111. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/ice_uris.py +0 -0
  112. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/interface.py +0 -0
  113. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/media.py +0 -0
  114. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/transports/types.py +0 -0
  115. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/utils/launch.py +0 -0
  116. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/utils/loader.py +0 -0
  117. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/utils/log.py +0 -0
  118. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/utils/messages.py +0 -0
  119. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime/utils/schema.py +0 -0
  120. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
  121. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime.egg-info/entry_points.txt +0 -0
  122. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime.egg-info/requires.txt +0 -0
  123. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/reactor_runtime.egg-info/top_level.txt +0 -0
  124. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/README.md +0 -0
  125. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/__init__.py +0 -0
  126. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/config.yml +0 -0
  127. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/model.py +0 -0
  128. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/pipeline.py +0 -0
  129. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/reactor.yaml +0 -0
  130. {reactor_runtime-2.2.0 → reactor_runtime-2.2.1}/src/template/requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reactor_runtime"
7
- version = "2.2.0"
7
+ version = "2.2.1"
8
8
  description = "Reactor runtime with public model API"
9
9
  authors = [
10
10
  { name = "Reactor", email = "team@reactor.inc" }
@@ -14,6 +14,7 @@ The version field allows clients and tooling to detect schema changes.
14
14
  from __future__ import annotations
15
15
 
16
16
  import dataclasses
17
+ import logging
17
18
  from dataclasses import dataclass, field
18
19
  from typing import Any, Dict, Literal, Optional, get_args, get_origin
19
20
 
@@ -25,21 +26,32 @@ from reactor_runtime.interface.tracks.descriptors import Audio
25
26
  from reactor_runtime.interface.tracks.input import INPUT_REGISTRY
26
27
  from reactor_runtime.interface.tracks.output import OUTPUT_REGISTRY
27
28
  from reactor_runtime.interface.upload import UploadedFile
29
+ from reactor_runtime.utils.typing import unwrap_optional
28
30
 
29
31
 
30
- CAPABILITIES_VERSION = "1.2"
32
+ logger = logging.getLogger(__name__)
31
33
 
32
- _TYPE_MAP = {
34
+ CAPABILITIES_VERSION = "1.3"
35
+
36
+ _TYPE_MAP: Dict[type, str] = {
33
37
  int: "integer",
34
38
  float: "number",
35
39
  str: "string",
36
40
  bool: "boolean",
37
- UploadedFile: "file",
41
+ }
42
+
43
+ _UPLOADED_FILE_SCHEMA: Dict[str, Any] = {
44
+ "type": "object",
45
+ "format": "file-reference",
38
46
  }
39
47
 
40
48
 
41
49
  def _resolve_type(raw_type: Any) -> Dict[str, Any]:
42
- """Resolve a type annotation into a JSON-friendly schema fragment."""
50
+ """Resolve a type annotation into an OpenAPI 3.1 JSON Schema fragment."""
51
+ inner = unwrap_optional(raw_type)
52
+ if inner is not None:
53
+ return _resolve_type(inner)
54
+
43
55
  origin = get_origin(raw_type)
44
56
 
45
57
  if origin is Literal:
@@ -55,12 +67,22 @@ def _resolve_type(raw_type: Any) -> Dict[str, Any]:
55
67
  return {"type": base, "enum": values}
56
68
 
57
69
  if isinstance(raw_type, type):
58
- return {"type": _TYPE_MAP.get(raw_type, raw_type.__name__)}
59
-
60
- if isinstance(raw_type, str):
61
- return {"type": raw_type}
70
+ if raw_type is UploadedFile:
71
+ return dict(_UPLOADED_FILE_SCHEMA)
72
+ json_type = _TYPE_MAP.get(raw_type)
73
+ if json_type is not None:
74
+ return {"type": json_type}
75
+ logger.warning(
76
+ '_resolve_type: unsupported class %r, falling back to {"type": "object"}',
77
+ raw_type,
78
+ )
79
+ return {"type": "object"}
62
80
 
63
- return {"type": str(raw_type)}
81
+ logger.warning(
82
+ '_resolve_type: unsupported annotation %r, falling back to {"type": "object"}',
83
+ raw_type,
84
+ )
85
+ return {"type": "object"}
64
86
 
65
87
 
66
88
  def _apply_field_info(schema: Dict[str, Any], info: FieldInfo) -> None:
@@ -95,7 +117,7 @@ def _field_schema(
95
117
  elif field_infos is not None:
96
118
  info = field_infos.get(f.name)
97
119
 
98
- if default_val is not dataclasses.MISSING and default_val is not None:
120
+ if default_val is not dataclasses.MISSING:
99
121
  schema["default"] = default_val
100
122
  elif f.default_factory is not dataclasses.MISSING:
101
123
  schema["default"] = f.default_factory()
@@ -13,6 +13,7 @@ from typing import Any, Callable, List, Tuple, Type, Union, get_type_hints
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
15
  from reactor_runtime.interface.upload import UploadedFile
16
+ from reactor_runtime.utils.typing import unwrap_optional
16
17
 
17
18
  _CONNECTED_ATTR = "__reactor_connected__"
18
19
  _DISCONNECTED_ATTR = "__reactor_disconnected__"
@@ -160,6 +161,14 @@ def make_event_class(
160
161
  return make_dataclass(class_name, fields, bases=(Event,))
161
162
 
162
163
 
164
+ def _is_uploaded_file(raw_type: Any) -> bool:
165
+ """Return ``True`` if *raw_type* is ``UploadedFile`` or ``Optional[UploadedFile]``."""
166
+ if raw_type is UploadedFile:
167
+ return True
168
+ inner = unwrap_optional(raw_type)
169
+ return inner is UploadedFile
170
+
171
+
163
172
  def _make_event_from_method(func: Callable, event_name: str) -> Type[Event]:
164
173
  """Generate an Event subclass from a method's signature.
165
174
 
@@ -182,7 +191,7 @@ def _make_event_from_method(func: Callable, event_name: str) -> Type[Event]:
182
191
  continue
183
192
  param_type = hints.get(param_name, Any)
184
193
 
185
- if param_type is UploadedFile:
194
+ if _is_uploaded_file(param_type):
186
195
  upload_fields.add(param_name)
187
196
 
188
197
  if param.default != inspect.Parameter.empty:
@@ -22,6 +22,7 @@ from typing import Any, ClassVar, Dict, Set, get_type_hints
22
22
 
23
23
  from reactor_runtime.interface.model.input_fields import FieldInfo, _NO_DEFAULT
24
24
  from reactor_runtime.interface.upload import UploadedFile
25
+ from reactor_runtime.utils.typing import unwrap_optional
25
26
 
26
27
  _MISSING = object()
27
28
 
@@ -74,7 +75,7 @@ class InputState:
74
75
  for name in list(annotations):
75
76
  raw = cls.__dict__.get(name, _MISSING)
76
77
  ann = resolved_hints.get(name, annotations[name])
77
- is_upload = ann is UploadedFile
78
+ is_upload = ann is UploadedFile or unwrap_optional(ann) is UploadedFile
78
79
  if name.startswith("_"):
79
80
  private.add(name)
80
81
  elif is_upload:
@@ -495,11 +495,15 @@ class HttpRuntime(Runtime):
495
495
  await self._handle_incoming_data_channel_message(message)
496
496
 
497
497
  async def _download_uploaded_file(self, upload_id: str) -> bytes:
498
- """Read an uploaded file from the local in-memory store."""
498
+ """Read an uploaded file from the local in-memory store.
499
+
500
+ The data is retained so the same upload can be read multiple times
501
+ within a session. The entire store is cleared on session cleanup.
502
+ """
499
503
  entry = self._upload_store.get(upload_id)
500
504
  if entry is None or "data" not in entry:
501
505
  raise FileNotFoundError(f"Upload {upload_id} not found in local store")
502
- return entry.pop("data")
506
+ return entry["data"]
503
507
 
504
508
  def _on_ping_received(self) -> None:
505
509
  """Forward client ping to the WebRTC client's watchdog."""
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+ """Shared type-annotation helpers used across the runtime."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import types
7
+ from typing import Any, Optional, Union, get_args, get_origin
8
+
9
+
10
+ def unwrap_optional(raw_type: Any) -> Optional[Any]:
11
+ """If *raw_type* is ``Optional[X]`` (``Union[X, None]``), return ``X``.
12
+
13
+ Supports both ``typing.Union`` and Python 3.10+ ``X | None`` syntax.
14
+ Returns ``None`` when *raw_type* is not an Optional.
15
+ """
16
+ origin = get_origin(raw_type)
17
+ if origin is not Union and origin is not types.UnionType:
18
+ return None
19
+ args = [a for a in get_args(raw_type) if a is not type(None)]
20
+ if len(args) == 1:
21
+ return args[0]
22
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -118,6 +118,7 @@ src/reactor_runtime/utils/loader.py
118
118
  src/reactor_runtime/utils/log.py
119
119
  src/reactor_runtime/utils/messages.py
120
120
  src/reactor_runtime/utils/schema.py
121
+ src/reactor_runtime/utils/typing.py
121
122
  src/template/README.md
122
123
  src/template/__init__.py
123
124
  src/template/config.yml