ocean-runner 0.2.18__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 +49 -58
- {ocean_runner-0.2.18.dist-info → ocean_runner-0.2.19.dist-info}/METADATA +3 -1
- ocean_runner-0.2.19.dist-info/RECORD +7 -0
- ocean_runner-0.2.18.dist-info/RECORD +0 -7
- {ocean_runner-0.2.18.dist-info → ocean_runner-0.2.19.dist-info}/WHEEL +0 -0
- {ocean_runner-0.2.18.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,13 +5,18 @@ 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]
|
|
19
|
+
|
|
15
20
|
|
|
16
21
|
def default_error_callback(algorithm: Algorithm, e: Exception) -> None:
|
|
17
22
|
algorithm.logger.exception("Error during algorithm execution")
|
|
@@ -24,56 +29,56 @@ def default_validation(algorithm: Algorithm) -> None:
|
|
|
24
29
|
assert algorithm.job_details.files, "Files missing"
|
|
25
30
|
|
|
26
31
|
|
|
27
|
-
def default_save(
|
|
32
|
+
def default_save(algorithm: Algorithm, result: ResultT, base: Path) -> None:
|
|
28
33
|
algorithm.logger.info("Saving results using default save")
|
|
29
34
|
with open(base / "result.txt", "w+") as f:
|
|
30
35
|
f.write(str(result))
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
@dataclass
|
|
34
|
-
class Algorithm(Generic[
|
|
39
|
+
class Algorithm(Generic[InputT, ResultT]):
|
|
35
40
|
"""
|
|
36
41
|
A configurable algorithm runner that behaves like a FastAPI app:
|
|
37
42
|
- You register `validate`, `run`, and `save_results` via decorators.
|
|
38
43
|
- You execute the full pipeline by calling `app()`.
|
|
39
44
|
"""
|
|
40
45
|
|
|
41
|
-
config: InitVar[Config | None] = None
|
|
46
|
+
config: InitVar[Config[InputT] | None] = field(default=None)
|
|
42
47
|
logger: Logger = field(init=False)
|
|
43
|
-
_job_details: JobDetails[
|
|
48
|
+
_job_details: JobDetails[InputT] = field(init=False)
|
|
44
49
|
_result: ResultT | None = field(default=None, init=False)
|
|
45
50
|
|
|
46
51
|
# Decorator-registered callbacks
|
|
47
|
-
_validate_fn:
|
|
48
|
-
default=
|
|
52
|
+
_validate_fn: ValidateFuncT = field(
|
|
53
|
+
default=default_validation,
|
|
49
54
|
init=False,
|
|
50
55
|
repr=False,
|
|
51
56
|
)
|
|
52
57
|
|
|
53
|
-
_run_fn:
|
|
58
|
+
_run_fn: RunFuncT = field(
|
|
54
59
|
default=None,
|
|
55
60
|
init=False,
|
|
56
61
|
repr=False,
|
|
57
62
|
)
|
|
58
63
|
|
|
59
|
-
_save_fn:
|
|
60
|
-
default=
|
|
64
|
+
_save_fn: SaveFuncT = field(
|
|
65
|
+
default=default_save,
|
|
61
66
|
init=False,
|
|
62
67
|
repr=False,
|
|
63
68
|
)
|
|
64
69
|
|
|
65
|
-
_error_callback:
|
|
66
|
-
default=
|
|
70
|
+
_error_callback: ErrorFuncT = field(
|
|
71
|
+
default=default_error_callback,
|
|
67
72
|
init=False,
|
|
68
73
|
repr=False,
|
|
69
74
|
)
|
|
70
75
|
|
|
71
|
-
def __post_init__(self, config: Config | None) -> None:
|
|
72
|
-
|
|
76
|
+
def __post_init__(self, config: Config[InputT] | None) -> None:
|
|
77
|
+
configuration = config or Config()
|
|
73
78
|
|
|
74
79
|
# Configure logger
|
|
75
|
-
if
|
|
76
|
-
self.logger =
|
|
80
|
+
if configuration.logger:
|
|
81
|
+
self.logger = configuration.logger
|
|
77
82
|
else:
|
|
78
83
|
import logging
|
|
79
84
|
|
|
@@ -82,20 +87,26 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
82
87
|
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
|
83
88
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
84
89
|
)
|
|
85
|
-
self.logger = logging.getLogger(
|
|
90
|
+
self.logger = logging.getLogger(__name__)
|
|
86
91
|
|
|
87
92
|
# Normalize base_dir
|
|
88
|
-
if isinstance(
|
|
89
|
-
|
|
93
|
+
if isinstance(configuration.environment.base_dir, str):
|
|
94
|
+
configuration.environment.base_dir = Path(
|
|
95
|
+
configuration.environment.base_dir
|
|
96
|
+
)
|
|
90
97
|
|
|
91
98
|
# Extend sys.path for custom imports
|
|
92
|
-
if
|
|
99
|
+
if configuration.source_paths:
|
|
93
100
|
import sys
|
|
94
101
|
|
|
95
|
-
sys.path.extend(
|
|
96
|
-
|
|
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
|
+
)
|
|
97
108
|
|
|
98
|
-
self.
|
|
109
|
+
self.configuration = configuration
|
|
99
110
|
|
|
100
111
|
class Error(RuntimeError): ...
|
|
101
112
|
|
|
@@ -115,19 +126,19 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
115
126
|
# Decorators (FastAPI-style)
|
|
116
127
|
# ---------------------------
|
|
117
128
|
|
|
118
|
-
def validate(self, fn:
|
|
129
|
+
def validate(self, fn: ValidateFuncT) -> ValidateFuncT:
|
|
119
130
|
self._validate_fn = fn
|
|
120
131
|
return fn
|
|
121
132
|
|
|
122
|
-
def run(self, fn:
|
|
133
|
+
def run(self, fn: RunFuncT) -> RunFuncT:
|
|
123
134
|
self._run_fn = fn
|
|
124
135
|
return fn
|
|
125
136
|
|
|
126
|
-
def save_results(self, fn:
|
|
137
|
+
def save_results(self, fn: SaveFuncT) -> SaveFuncT:
|
|
127
138
|
self._save_fn = fn
|
|
128
139
|
return fn
|
|
129
140
|
|
|
130
|
-
def on_error(self, fn:
|
|
141
|
+
def on_error(self, fn: ErrorFuncT) -> ErrorFuncT:
|
|
131
142
|
self._error_callback = fn
|
|
132
143
|
return fn
|
|
133
144
|
|
|
@@ -139,11 +150,11 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
139
150
|
"""Executes the algorithm pipeline: validate → run → save_results."""
|
|
140
151
|
# Load job details
|
|
141
152
|
self._job_details = JobDetails.load(
|
|
142
|
-
_type=self.
|
|
143
|
-
base_dir=self.
|
|
144
|
-
dids=self.
|
|
145
|
-
transformation_did=self.
|
|
146
|
-
secret=self.
|
|
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,
|
|
147
158
|
)
|
|
148
159
|
|
|
149
160
|
self.logger.info("Loaded JobDetails")
|
|
@@ -151,40 +162,20 @@ class Algorithm(Generic[JobDetailsT, ResultT]):
|
|
|
151
162
|
|
|
152
163
|
try:
|
|
153
164
|
# Validation step
|
|
154
|
-
|
|
155
|
-
self.logger.info("Running custom validation...")
|
|
156
|
-
self._validate_fn()
|
|
157
|
-
else:
|
|
158
|
-
self.logger.info("Running default validation...")
|
|
159
|
-
default_validation(self)
|
|
165
|
+
self._validate_fn(self)
|
|
160
166
|
|
|
161
167
|
# Run step
|
|
162
168
|
if self._run_fn:
|
|
163
169
|
self.logger.info("Running algorithm...")
|
|
164
|
-
self._result = self._run_fn()
|
|
170
|
+
self._result = self._run_fn(self)
|
|
165
171
|
else:
|
|
166
|
-
self.logger.
|
|
172
|
+
self.logger.error("No run() function defined. Skipping execution.")
|
|
167
173
|
self._result = None
|
|
168
174
|
|
|
169
175
|
# Save step
|
|
170
|
-
|
|
171
|
-
self.logger.info("Saving results...")
|
|
172
|
-
self._save_fn(
|
|
173
|
-
self._result,
|
|
174
|
-
self.job_details.paths.outputs,
|
|
175
|
-
)
|
|
176
|
-
else:
|
|
177
|
-
self.logger.info("No save_results() defined. Using default.")
|
|
178
|
-
default_save(
|
|
179
|
-
result=self._result,
|
|
180
|
-
base=self.job_details.paths.outputs,
|
|
181
|
-
algorithm=self,
|
|
182
|
-
)
|
|
176
|
+
self._save_fn(self, self._result, self.job_details.paths.outputs)
|
|
183
177
|
|
|
184
178
|
except Exception as e:
|
|
185
|
-
|
|
186
|
-
self._error_callback(e)
|
|
187
|
-
else:
|
|
188
|
-
default_error_callback(self, e)
|
|
179
|
+
self._error_callback(self, e)
|
|
189
180
|
|
|
190
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
|
|
@@ -18,6 +18,8 @@ Classifier: Operating System :: OS Independent
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
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=2j0XNk06gIlPoer_kVRtSf-noYZzhORi_7-UnPQBJxA,6005
|
|
4
|
-
ocean_runner-0.2.18.dist-info/METADATA,sha256=NsZ4U_FM2scbLrsgP9BrxczPoBzItV1xs2VlttEGTAI,6562
|
|
5
|
-
ocean_runner-0.2.18.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
-
ocean_runner-0.2.18.dist-info/licenses/LICENSE,sha256=_B25KqK4amoADWkMN150tnZFm_Fy7VvZpvIC8ZydWdI,1053
|
|
7
|
-
ocean_runner-0.2.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|