ocean-runner 0.2.16__py3-none-any.whl → 0.2.19__py3-none-any.whl
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.
- ocean_runner/__init__.py +1 -1
- ocean_runner/config.py +48 -32
- ocean_runner/runner.py +72 -63
- {ocean_runner-0.2.16.dist-info → ocean_runner-0.2.19.dist-info}/METADATA +4 -2
- ocean_runner-0.2.19.dist-info/RECORD +7 -0
- {ocean_runner-0.2.16.dist-info → ocean_runner-0.2.19.dist-info}/WHEEL +1 -1
- ocean_runner-0.2.16.dist-info/RECORD +0 -7
- {ocean_runner-0.2.16.dist-info → ocean_runner-0.2.19.dist-info}/licenses/LICENSE +0 -0
ocean_runner/__init__.py
CHANGED
ocean_runner/config.py
CHANGED
|
@@ -1,55 +1,71 @@
|
|
|
1
|
-
import
|
|
2
|
-
from dataclasses import asdict, dataclass, field
|
|
1
|
+
from enum import StrEnum, auto
|
|
3
2
|
from logging import Logger
|
|
4
3
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
4
|
+
from typing import Generic, Sequence, TypeVar
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
from pydantic_settings import BaseSettings
|
|
8
|
+
|
|
9
|
+
InputT = TypeVar("InputT")
|
|
8
10
|
|
|
9
11
|
DEFAULT = "DEFAULT"
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
class Keys(StrEnum):
|
|
15
|
+
SECRET = auto()
|
|
16
|
+
BASE_DIR = auto()
|
|
17
|
+
TRANSFORMATION_DID = auto()
|
|
18
|
+
DIDS = auto()
|
|
15
19
|
|
|
16
|
-
base_dir: str | None = field(
|
|
17
|
-
default_factory=lambda: os.environ.get("BASE_DIR", None),
|
|
18
|
-
)
|
|
19
|
-
"""Base data directory, defaults to '/data'"""
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
class Environment(BaseSettings):
|
|
22
|
+
"""Environment configuration loaded from environment variables"""
|
|
23
|
+
|
|
24
|
+
base_dir: str | Path | None = Field(
|
|
25
|
+
default_factory=lambda: Path("/data"),
|
|
26
|
+
validation_alias=Keys.BASE_DIR.value,
|
|
27
|
+
description="Base data directory, defaults to '/data'",
|
|
23
28
|
)
|
|
24
|
-
"""Datasets DID's, format: '["XXXX"]'"""
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
dids: str | list[Path] | None = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
validation_alias=Keys.DIDS.value,
|
|
33
|
+
description='Datasets DID\'s, format: ["XXXX"]',
|
|
28
34
|
)
|
|
29
|
-
"""Transformation (algorithm) DID"""
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
transformation_did: str = Field(
|
|
37
|
+
default=DEFAULT,
|
|
38
|
+
validation_alias=Keys.TRANSFORMATION_DID.value,
|
|
39
|
+
description="Transformation (algorithm) DID",
|
|
33
40
|
)
|
|
34
|
-
"""Super secret secret"""
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
secret: str = Field(
|
|
43
|
+
default=DEFAULT,
|
|
44
|
+
validation_alias=Keys.SECRET.value,
|
|
45
|
+
description="Super secret secret",
|
|
46
|
+
)
|
|
37
47
|
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
class Config:
|
|
49
|
+
class Config(BaseModel, Generic[InputT]):
|
|
41
50
|
"""Algorithm overall configuration"""
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
"""Algorithm's custom input types, must be a dataclass_json"""
|
|
52
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
custom_input: InputT | None = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="Algorithm's custom input types, must be a dataclass_json",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
logger: Logger | None = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="Logger to use in the algorithm",
|
|
62
|
+
)
|
|
48
63
|
|
|
49
|
-
source_paths:
|
|
50
|
-
default_factory=lambda: [Path("/algorithm/src")]
|
|
64
|
+
source_paths: Sequence[Path] = Field(
|
|
65
|
+
default_factory=lambda: [Path("/algorithm/src")],
|
|
66
|
+
description="Paths that should be included so the code executes correctly",
|
|
51
67
|
)
|
|
52
|
-
"""Paths that should be included so the code executes correctly"""
|
|
53
68
|
|
|
54
|
-
environment: Environment =
|
|
55
|
-
|
|
69
|
+
environment: Environment = Field(
|
|
70
|
+
default_factory=Environment, description="Environment configuration"
|
|
71
|
+
)
|
ocean_runner/runner.py
CHANGED
|
@@ -5,15 +5,21 @@ from logging import Logger
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Callable, Generic, TypeVar
|
|
7
7
|
|
|
8
|
-
from oceanprotocol_job_details import JobDetails
|
|
8
|
+
from oceanprotocol_job_details import JobDetails # type: ignore
|
|
9
9
|
|
|
10
10
|
from ocean_runner.config import Config
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
InputT = TypeVar("InputT")
|
|
13
13
|
ResultT = TypeVar("ResultT")
|
|
14
14
|
|
|
15
|
+
ValidateFuncT = Callable[["Algorithm"], None]
|
|
16
|
+
RunFuncT = Callable[["Algorithm"], ResultT] | None
|
|
17
|
+
SaveFuncT = Callable[["Algorithm", ResultT, Path], None]
|
|
18
|
+
ErrorFuncT = Callable[["Algorithm", Exception], None]
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
|
|
21
|
+
def default_error_callback(algorithm: Algorithm, e: Exception) -> None:
|
|
22
|
+
algorithm.logger.exception("Error during algorithm execution")
|
|
17
23
|
raise e
|
|
18
24
|
|
|
19
25
|
|
|
@@ -23,41 +29,56 @@ def default_validation(algorithm: Algorithm) -> None:
|
|
|
23
29
|
assert algorithm.job_details.files, "Files missing"
|
|
24
30
|
|
|
25
31
|
|
|
26
|
-
def default_save(
|
|
32
|
+
def default_save(algorithm: Algorithm, result: ResultT, base: Path) -> None:
|
|
27
33
|
algorithm.logger.info("Saving results using default save")
|
|
28
34
|
with open(base / "result.txt", "w+") as f:
|
|
29
35
|
f.write(str(result))
|
|
30
36
|
|
|
31
37
|
|
|
32
38
|
@dataclass
|
|
33
|
-
class Algorithm(Generic[
|
|
39
|
+
class Algorithm(Generic[InputT, ResultT]):
|
|
34
40
|
"""
|
|
35
41
|
A configurable algorithm runner that behaves like a FastAPI app:
|
|
36
42
|
- You register `validate`, `run`, and `save_results` via decorators.
|
|
37
43
|
- You execute the full pipeline by calling `app()`.
|
|
38
44
|
"""
|
|
39
45
|
|
|
40
|
-
config: InitVar[Config | None] = None
|
|
46
|
+
config: InitVar[Config[InputT] | None] = field(default=None)
|
|
41
47
|
logger: Logger = field(init=False)
|
|
42
|
-
_job_details: JobDetails[
|
|
48
|
+
_job_details: JobDetails[InputT] = field(init=False)
|
|
43
49
|
_result: ResultT | None = field(default=None, init=False)
|
|
44
50
|
|
|
45
51
|
# Decorator-registered callbacks
|
|
46
|
-
_validate_fn:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
_validate_fn: ValidateFuncT = field(
|
|
53
|
+
default=default_validation,
|
|
54
|
+
init=False,
|
|
55
|
+
repr=False,
|
|
50
56
|
)
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
|
|
58
|
+
_run_fn: RunFuncT = field(
|
|
59
|
+
default=None,
|
|
60
|
+
init=False,
|
|
61
|
+
repr=False,
|
|
53
62
|
)
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
_save_fn: SaveFuncT = field(
|
|
65
|
+
default=default_save,
|
|
66
|
+
init=False,
|
|
67
|
+
repr=False,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
_error_callback: ErrorFuncT = field(
|
|
71
|
+
default=default_error_callback,
|
|
72
|
+
init=False,
|
|
73
|
+
repr=False,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def __post_init__(self, config: Config[InputT] | None) -> None:
|
|
77
|
+
configuration = config or Config()
|
|
57
78
|
|
|
58
79
|
# Configure logger
|
|
59
|
-
if
|
|
60
|
-
self.logger =
|
|
80
|
+
if configuration.logger:
|
|
81
|
+
self.logger = configuration.logger
|
|
61
82
|
else:
|
|
62
83
|
import logging
|
|
63
84
|
|
|
@@ -66,32 +87,26 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
66
87
|
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
|
67
88
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
68
89
|
)
|
|
69
|
-
self.logger = logging.getLogger(
|
|
90
|
+
self.logger = logging.getLogger(__name__)
|
|
70
91
|
|
|
71
92
|
# Normalize base_dir
|
|
72
|
-
if isinstance(
|
|
73
|
-
|
|
93
|
+
if isinstance(configuration.environment.base_dir, str):
|
|
94
|
+
configuration.environment.base_dir = Path(
|
|
95
|
+
configuration.environment.base_dir
|
|
96
|
+
)
|
|
74
97
|
|
|
75
98
|
# Extend sys.path for custom imports
|
|
76
|
-
if
|
|
99
|
+
if configuration.source_paths:
|
|
77
100
|
import sys
|
|
78
101
|
|
|
79
|
-
sys.path.extend(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
base_dir=config.environment.base_dir,
|
|
86
|
-
dids=config.environment.dids,
|
|
87
|
-
transformation_did=config.environment.transformation_did,
|
|
88
|
-
secret=config.environment.secret,
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
self.logger.info("Loaded JobDetails")
|
|
92
|
-
self.logger.debug(asdict(self.job_details))
|
|
102
|
+
sys.path.extend(
|
|
103
|
+
[str(path.absolute()) for path in configuration.source_paths]
|
|
104
|
+
)
|
|
105
|
+
self.logger.debug(
|
|
106
|
+
f"Added [{len(configuration.source_paths)}] entries to PATH"
|
|
107
|
+
)
|
|
93
108
|
|
|
94
|
-
self.
|
|
109
|
+
self.configuration = configuration
|
|
95
110
|
|
|
96
111
|
class Error(RuntimeError): ...
|
|
97
112
|
|
|
@@ -111,19 +126,19 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
111
126
|
# Decorators (FastAPI-style)
|
|
112
127
|
# ---------------------------
|
|
113
128
|
|
|
114
|
-
def validate(self, fn:
|
|
129
|
+
def validate(self, fn: ValidateFuncT) -> ValidateFuncT:
|
|
115
130
|
self._validate_fn = fn
|
|
116
131
|
return fn
|
|
117
132
|
|
|
118
|
-
def run(self, fn:
|
|
133
|
+
def run(self, fn: RunFuncT) -> RunFuncT:
|
|
119
134
|
self._run_fn = fn
|
|
120
135
|
return fn
|
|
121
136
|
|
|
122
|
-
def save_results(self, fn:
|
|
137
|
+
def save_results(self, fn: SaveFuncT) -> SaveFuncT:
|
|
123
138
|
self._save_fn = fn
|
|
124
139
|
return fn
|
|
125
140
|
|
|
126
|
-
def on_error(self, fn:
|
|
141
|
+
def on_error(self, fn: ErrorFuncT) -> ErrorFuncT:
|
|
127
142
|
self._error_callback = fn
|
|
128
143
|
return fn
|
|
129
144
|
|
|
@@ -133,40 +148,34 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
133
148
|
|
|
134
149
|
def __call__(self) -> ResultT | None:
|
|
135
150
|
"""Executes the algorithm pipeline: validate → run → save_results."""
|
|
151
|
+
# Load job details
|
|
152
|
+
self._job_details = JobDetails.load(
|
|
153
|
+
_type=self.configuration.custom_input,
|
|
154
|
+
base_dir=self.configuration.environment.base_dir,
|
|
155
|
+
dids=self.configuration.environment.dids,
|
|
156
|
+
transformation_did=self.configuration.environment.transformation_did,
|
|
157
|
+
secret=self.configuration.environment.secret,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
self.logger.info("Loaded JobDetails")
|
|
161
|
+
self.logger.debug(asdict(self.job_details))
|
|
162
|
+
|
|
136
163
|
try:
|
|
137
164
|
# Validation step
|
|
138
|
-
|
|
139
|
-
self.logger.info("Running custom validation...")
|
|
140
|
-
self._validate_fn()
|
|
141
|
-
else:
|
|
142
|
-
self.logger.info("Running default validation...")
|
|
143
|
-
default_validation(self)
|
|
165
|
+
self._validate_fn(self)
|
|
144
166
|
|
|
145
167
|
# Run step
|
|
146
168
|
if self._run_fn:
|
|
147
169
|
self.logger.info("Running algorithm...")
|
|
148
|
-
self._result = self._run_fn()
|
|
170
|
+
self._result = self._run_fn(self)
|
|
149
171
|
else:
|
|
150
|
-
self.logger.
|
|
172
|
+
self.logger.error("No run() function defined. Skipping execution.")
|
|
151
173
|
self._result = None
|
|
152
174
|
|
|
153
175
|
# Save step
|
|
154
|
-
|
|
155
|
-
self.logger.info("Saving results...")
|
|
156
|
-
self._save_fn(
|
|
157
|
-
self._result,
|
|
158
|
-
self.job_details.paths.outputs,
|
|
159
|
-
)
|
|
160
|
-
else:
|
|
161
|
-
self.logger.info("No save_results() defined. Using default.")
|
|
162
|
-
default_save(
|
|
163
|
-
result=self._result,
|
|
164
|
-
base=self.job_details.paths.outputs,
|
|
165
|
-
algorithm=self,
|
|
166
|
-
)
|
|
176
|
+
self._save_fn(self, self._result, self.job_details.paths.outputs)
|
|
167
177
|
|
|
168
178
|
except Exception as e:
|
|
169
|
-
self.
|
|
170
|
-
self._error_callback(e)
|
|
179
|
+
self._error_callback(self, e)
|
|
171
180
|
|
|
172
181
|
return self._result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocean-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.19
|
|
4
4
|
Summary: A fluent API for OceanProtocol algorithms
|
|
5
5
|
Project-URL: Homepage, https://github.com/AgrospAI/ocean-runner
|
|
6
6
|
Project-URL: Issues, https://github.com/AgrospAI/ocean-runner/issues
|
|
@@ -17,7 +17,9 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: oceanprotocol-job-details>=0.2.
|
|
20
|
+
Requires-Dist: oceanprotocol-job-details>=0.2.8
|
|
21
|
+
Requires-Dist: pydantic-settings>=2.12.0
|
|
22
|
+
Requires-Dist: pydantic>=2.12.5
|
|
21
23
|
Requires-Dist: pytest>=8.4.2
|
|
22
24
|
Description-Content-Type: text/markdown
|
|
23
25
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ocean_runner/__init__.py,sha256=hRyMrE7K0DEbNRa-iNwA2xFOM85JJvjeKIdiWvcOm4Y,154
|
|
2
|
+
ocean_runner/config.py,sha256=paN2I3ISIxRO1isCIuUgNsL9dsRhEc_n6KjTH6yNZU4,1919
|
|
3
|
+
ocean_runner/runner.py,sha256=VaayC8UXn2Iyl1RuUcTap9vyTyDYkvm16p3ucaRAOhQ,5527
|
|
4
|
+
ocean_runner-0.2.19.dist-info/METADATA,sha256=4n10dG0cfNAPraB9qJ4WcINyzJNFAxwDQtfZufbs0sU,6635
|
|
5
|
+
ocean_runner-0.2.19.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
ocean_runner-0.2.19.dist-info/licenses/LICENSE,sha256=_B25KqK4amoADWkMN150tnZFm_Fy7VvZpvIC8ZydWdI,1053
|
|
7
|
+
ocean_runner-0.2.19.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
ocean_runner/__init__.py,sha256=awAmE6kZhuwcrD3gT7qFZArdhiuzW-EFTA6tGKhw06k,138
|
|
2
|
-
ocean_runner/config.py,sha256=gyyUotPJ7n8wPPdsJZIBUT4zBlkoNbhV876JDTdPNsY,1398
|
|
3
|
-
ocean_runner/runner.py,sha256=mPxnJCP-XYl0akRAI4HdAku0KlsTCrXTVPCwlQj5JoY,5721
|
|
4
|
-
ocean_runner-0.2.16.dist-info/METADATA,sha256=s4MrShZfoQzbWIXZRV06Rt0S4lhqYuRjK_OG9QKJoKg,6562
|
|
5
|
-
ocean_runner-0.2.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
ocean_runner-0.2.16.dist-info/licenses/LICENSE,sha256=_B25KqK4amoADWkMN150tnZFm_Fy7VvZpvIC8ZydWdI,1053
|
|
7
|
-
ocean_runner-0.2.16.dist-info/RECORD,,
|
|
File without changes
|