Skip to content

Commit 0ac53c3

Browse files
committed
Added in complete window type data to satisfy orca core on processor registration
1 parent b441585 commit 0ac53c3

7 files changed

Lines changed: 218 additions & 18 deletions

File tree

‎examples/simpletrigger/simpletrigger/algorithms.py‎

Whitespace-only changes.

‎orca_python/exceptions.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ class BadEnvVar(BaseOrcaException):
3333
class BadConfigFile(BaseOrcaException):
3434
"""Raised when the orca.json config file is poorly defined"""
3535

36+
3637
class BrokenRemoteAlgorithmStubs(BaseOrcaException):
37-
"""Raised when remote algorithm stubs cannot be properly parsed and read"""
38+
"""Raised when remote algorithm stubs cannot be properly parsed and read"""
39+

‎orca_python/main.py‎

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
which are managed by Orca-core.
77
"""
88

9-
from io import StringIO
109
import re
1110
import sys
1211
import asyncio
@@ -52,10 +51,10 @@
5251

5352
from orca_python import envs
5453
from orca_python.exceptions import (
55-
BrokenRemoteAlgorithmStubs,
5654
InvalidDependency,
5755
InvalidWindowArgument,
5856
InvalidAlgorithmArgument,
57+
BrokenRemoteAlgorithmStubs,
5958
InvalidAlgorithmReturnType,
6059
InvalidMetadataFieldArgument,
6160
)
@@ -207,13 +206,15 @@ def __call__(
207206
self, params: ExecutionParams, *args: Any, **kwargs: Any
208207
) -> returnResult: ...
209208

209+
210210
@dataclass
211-
class RemoteAlgorithm():
211+
class RemoteAlgorithm:
212212
ProcessorName: str
213213
ProcessorRuntime: str
214214
Name: str
215215
Version: str
216216

217+
217218
T = TypeVar("T", bound=AlgorithmFn)
218219

219220

@@ -331,7 +332,9 @@ def _add_algorithm(self, name: str, algorithm: Algorithm) -> None:
331332
)
332333
self._algorithms[name] = algorithm
333334

334-
def _add_dependency(self, algorithm: str, dependency: AlgorithmFn, remote: bool = False) -> None:
335+
def _add_dependency(
336+
self, algorithm: str, dependency: AlgorithmFn, remote: bool = False
337+
) -> None:
335338
"""
336339
Adds a dependency to an algorithm.
337340
@@ -345,22 +348,23 @@ def _add_dependency(self, algorithm: str, dependency: AlgorithmFn, remote: bool
345348
"""
346349
LOGGER.debug(f"Adding dependency for algorithm: {algorithm}")
347350
if remote:
348-
# TODO: how do we resolve this - we need information about a remote
349-
# algorithm but do not have it. We don't want to go further than stubs as
350-
# it makes deployment messy.
351351
remoteDepMetadata = getattr(dependency, "__orca_metadata__", None)
352352
if remoteDepMetadata is None:
353-
raise BrokenRemoteAlgorithmStubs("Could not parse metadata from Orca stubs. Rerun stub generation: `orca sync`")
353+
raise BrokenRemoteAlgorithmStubs(
354+
"Could not parse metadata from Orca stubs. Rerun stub generation: `orca sync`"
355+
)
354356
try:
355357
remoteAlgo = RemoteAlgorithm(**remoteDepMetadata)
356358
except Exception as e:
357-
raise BrokenRemoteAlgorithmStubs(f"Could not parse metadata from Orca stubs: {e} Rerun stub generation: `orca sync`")
359+
raise BrokenRemoteAlgorithmStubs(
360+
f"Could not parse metadata from Orca stubs: {e} Rerun stub generation: `orca sync`"
361+
)
358362

359363
if algorithm not in self._remoteDependencies:
360364
self._remoteDependencies[algorithm] = [remoteAlgo]
361365
else:
362366
self._remoteDependencies[algorithm].append(remoteAlgo)
363-
367+
364368
return
365369

366370
dependencyAlgo = None
@@ -694,7 +698,7 @@ def Register(self) -> None:
694698
algo_msg.window_type.version = algorithm.window_type.version
695699
algo_msg.window_type.description = algorithm.window_type.description
696700

697-
# Fill in metadata fields if present
701+
# fill in metadata fields if present
698702
if len(algorithm.window_type.metadataFields) > 0:
699703
for metadataField in algorithm.window_type.metadataFields:
700704
metadata_fields_msg = algo_msg.window_type.metadataFields.add()
@@ -709,10 +713,12 @@ def Register(self) -> None:
709713
dep_msg.version = dep.version
710714
dep_msg.processor_name = dep.processor
711715
dep_msg.processor_runtime = dep.runtime
712-
716+
713717
# Add remote dependencies if they exist
714718
if algorithm.full_name in self._algorithmsSingleton._remoteDependencies:
715-
for dep in self._algorithmsSingleton._remoteDependencies[algorithm.full_name]:
719+
for dep in self._algorithmsSingleton._remoteDependencies[
720+
algorithm.full_name
721+
]:
716722
dep_msg = algo_msg.dependencies.add()
717723
dep_msg.name = dep.Name
718724
dep_msg.version = dep.Version
@@ -840,7 +846,7 @@ def algorithm(
840846
f"Version '{version}' must follow basic semantic "
841847
"versioning (e.g., '1.0.0') without release portions"
842848
)
843-
849+
844850
def inner(algo: T) -> T:
845851
def wrapper(
846852
params: ExecutionParams,
@@ -892,14 +898,18 @@ def wrapper(
892898
)
893899

894900
for dependency in depends_on:
895-
if not getattr(dependency, "__orca_is_remote__", False) and not self._algorithmsSingleton._has_algorithm_fn(dependency):
901+
if not getattr(
902+
dependency, "__orca_is_remote__", False
903+
) and not self._algorithmsSingleton._has_algorithm_fn(dependency):
896904
message = (
897905
f"Cannot add function `{dependency.__name__}` to dependency stack. All dependencies must "
898906
"be decorated with `@algorithm` before they can be used as dependencies."
899907
)
900908
raise InvalidDependency(message)
901909
self._algorithmsSingleton._add_dependency(
902-
algorithm.full_name, dependency, getattr(dependency, "__orca_is_remote__", False)
910+
algorithm.full_name,
911+
dependency,
912+
getattr(dependency, "__orca_is_remote__", False),
903913
)
904914

905915
# TODO: check for circular dependencies. It's not easy to create one in python as the function

‎orca_python/registry/algorithms.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def _load_stub_metadata(func_name: str) -> dict[str, Any]:
3838
# look at previous lines for metadata comment
3939
for i in range(func_line - 1, max(0, func_line - 5), -1):
4040
line = lines[i].strip()
41-
if match := re.search(r"#\s*ALGO_METADATA:\s*(\{.*\})", line):
41+
if match := re.search(r"#\s*METADATA:\s*(\{.*\})", line):
4242
result["metadata"] = json.loads(match.group(1))
4343
break
4444

@@ -66,6 +66,7 @@ def __init__(self, func_name: str):
6666
self.__orca_metadata__ = stub_info["metadata"]
6767

6868
def __call__(self, *args, **kwargs):
69+
_, _ = args, kwargs
6970
raise NotImplementedError(
7071
f"{self.__name__} is only available as a remote algorithm. "
7172
f"Metadata: {self.__orca_metadata__}"
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import re
2+
import ast
3+
import json
4+
from typing import Any
5+
from pathlib import Path
6+
7+
from orca_python.exceptions import BrokenRemoteAlgorithmStubs
8+
9+
10+
def _load_stub_metadata(obj_name: str) -> dict[str, Any]:
11+
"""Load annotations and metadata from the .pyi stub file."""
12+
stub_file = (
13+
Path.cwd() / ".orca" / "orca_python" / "registry" / "metadata_fields.pyi"
14+
)
15+
16+
if not stub_file.exists():
17+
return {"annotations": {}, "metadata": {}}
18+
19+
try:
20+
content = stub_file.read_text()
21+
tree = ast.parse(content)
22+
23+
result = {"annotations": {}, "metadata": {}}
24+
25+
# get the annotated assignment node (e.g., asset_id: MetadataField)
26+
for node in ast.walk(tree):
27+
if isinstance(node, ast.AnnAssign):
28+
# check if this is the target we're looking for
29+
if isinstance(node.target, ast.Name) and node.target.id == obj_name:
30+
# get annotation
31+
if node.annotation:
32+
result["annotations"]["type"] = ast.unparse(node.annotation)
33+
34+
# get metadata from comment above assignment
35+
lines = content.split("\n")
36+
assign_line = node.lineno - 1 # 0-indexed
37+
38+
# look at previous lines for metadata comment
39+
for i in range(assign_line - 1, max(0, assign_line - 5), -1):
40+
line = lines[i].strip()
41+
if match := re.search(r"#\s*METADATA:\s*(\{.*\})", line):
42+
result["metadata"] = json.loads(match.group(1))
43+
break
44+
45+
break
46+
47+
return result
48+
except Exception as e:
49+
print(f"Error parsing stub: {e}")
50+
51+
return {"annotations": {}, "metadata": {}}
52+
53+
54+
def __getattr__(name):
55+
if name.startswith("_"):
56+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
57+
58+
stub_info = _load_stub_metadata(name)
59+
60+
class StubMetadataField:
61+
__orca_is_remote__ = True
62+
name: str
63+
description: str
64+
65+
def __init__(self, field_name: str):
66+
self.__name__ = field_name
67+
self.__annotations__ = stub_info["annotations"]
68+
self.__orca_metadata__ = stub_info["metadata"]
69+
name = stub_info["metadata"].get("Name", None)
70+
description = stub_info["metadata"].get("Description", None)
71+
if name is None or description is None:
72+
raise BrokenRemoteAlgorithmStubs(
73+
"Stubs appear broken. Regenerate with `orca sync`."
74+
)
75+
self.name = name
76+
self.description = description
77+
78+
return StubMetadataField(name)

‎orca_python/registry/processors.py‎

Whitespace-only changes.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import re
2+
import ast
3+
import json
4+
from typing import Any, List
5+
from pathlib import Path
6+
from dataclasses import dataclass
7+
8+
from orca_python.main import BrokenRemoteAlgorithmStubs
9+
10+
11+
def _load_stub_metadata(obj_name: str) -> dict[str, Any]:
12+
"""Load annotations and metadata from the .pyi stub file."""
13+
stub_file = Path.cwd() / ".orca" / "orca_python" / "registry" / "window_types.pyi"
14+
15+
if not stub_file.exists():
16+
return {"annotations": {}, "metadata": {}}
17+
18+
try:
19+
content = stub_file.read_text()
20+
tree = ast.parse(content)
21+
22+
result = {"annotations": {}, "metadata": {}}
23+
24+
# get the annotated assignment node
25+
for node in ast.walk(tree):
26+
if isinstance(node, ast.AnnAssign):
27+
# check if this is the target we're looking for
28+
if isinstance(node.target, ast.Name) and node.target.id == obj_name:
29+
# get annotation
30+
if node.annotation:
31+
result["annotations"]["type"] = ast.unparse(node.annotation)
32+
33+
# get metadata from comment above assignment
34+
lines = content.split("\n")
35+
assign_line = node.lineno - 1 # 0-indexed
36+
37+
# look at previous lines for metadata comment
38+
for i in range(assign_line - 1, max(0, assign_line - 5), -1):
39+
line = lines[i].strip()
40+
if match := re.search(r"#\s*METADATA:\s*(\{.*\})", line):
41+
result["metadata"] = json.loads(match.group(1))
42+
break
43+
44+
break
45+
46+
return result
47+
except Exception as e:
48+
print(f"Error parsing stub: {e}")
49+
50+
return {"annotations": {}, "metadata": {}}
51+
52+
53+
def __getattr__(name):
54+
if name.startswith("_"):
55+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
56+
57+
stub_info = _load_stub_metadata(name)
58+
59+
@dataclass
60+
class StubMetadataField:
61+
__orca_is_remote__ = True
62+
name: str
63+
description: str
64+
65+
class StubWindowType:
66+
__orca_is_remote__ = True
67+
name: str
68+
version: str
69+
description: str
70+
metadataFields: List[StubMetadataField]
71+
72+
def __init__(self, window_name: str):
73+
self.__annotations__ = stub_info["annotations"]
74+
self.__orca_metadata__ = stub_info["metadata"]
75+
76+
# assign the values that the main package expects from a window
77+
name = stub_info["metadata"].get("Name", None)
78+
version = stub_info["metadata"].get("Version", None)
79+
description = stub_info["metadata"].get("Description", None)
80+
metadataFields = stub_info["metadata"].get("MetadataFields", None)
81+
82+
if (
83+
name is None
84+
or version is None
85+
or description is None
86+
or metadataFields is None
87+
):
88+
raise BrokenRemoteAlgorithmStubs(
89+
"Stubs appear broken. Regenerate them with `orca sync`."
90+
)
91+
92+
self.name = name
93+
self.version = version
94+
self.description = description
95+
try:
96+
self.metadataFields = [
97+
StubMetadataField(
98+
name=v.get("Name"), description=v.get("Description")
99+
)
100+
for v in metadataFields
101+
]
102+
except Exception as e:
103+
raise BrokenRemoteAlgorithmStubs(
104+
f"Stubs appear broken: {e}. Regenerate with `orca sync`"
105+
)
106+
107+
self.__name__ = window_name
108+
109+
return StubWindowType(name)

0 commit comments

Comments
 (0)