ocean-runner 0.2.21__tar.gz → 0.2.25__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.
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/PKG-INFO +20 -23
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/README.md +18 -20
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/ocean_runner/config.py +5 -5
- ocean_runner-0.2.25/ocean_runner/py.typed +0 -0
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/ocean_runner/runner.py +27 -39
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/pyproject.toml +7 -7
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/.gitignore +0 -0
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/LICENSE +0 -0
- {ocean_runner-0.2.21 → ocean_runner-0.2.25}/ocean_runner/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocean-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.25
|
|
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,18 +18,16 @@ Classifier: Operating System :: OS Independent
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Requires-Dist: aiofiles>=25.1.0
|
|
21
|
-
Requires-Dist: oceanprotocol-job-details>=0.
|
|
21
|
+
Requires-Dist: oceanprotocol-job-details>=0.3.11
|
|
22
22
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
23
23
|
Requires-Dist: pydantic>=2.12.5
|
|
24
24
|
Requires-Dist: pytest>=8.4.2
|
|
25
|
-
Requires-Dist: types-aiofiles>=25.1.0.20251011
|
|
26
25
|
Description-Content-Type: text/markdown
|
|
27
26
|
|
|
28
27
|
# ocean-runner
|
|
29
28
|
|
|
30
29
|
Ocean Runner is a package that eases algorithm creation in the scope of OceanProtocol.
|
|
31
30
|
|
|
32
|
-
|
|
33
31
|
## Installation
|
|
34
32
|
|
|
35
33
|
```bash
|
|
@@ -50,7 +48,7 @@ algorithm = Algorithm()
|
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
@algorithm.run
|
|
53
|
-
def run():
|
|
51
|
+
def run(_: Algorithm):
|
|
54
52
|
return random.randint()
|
|
55
53
|
|
|
56
54
|
|
|
@@ -77,14 +75,14 @@ algorithm = Algorithm(
|
|
|
77
75
|
Config(
|
|
78
76
|
custom_input: ... # dataclass
|
|
79
77
|
# Custom algorithm parameters dataclass.
|
|
80
|
-
|
|
78
|
+
|
|
81
79
|
logger: ... # type: logging.Logger
|
|
82
80
|
# Custom logger to use.
|
|
83
81
|
|
|
84
82
|
source_paths: ... # type: Iterable[Path]
|
|
85
83
|
# Source paths to include in the PATH
|
|
86
|
-
|
|
87
|
-
environment: ...
|
|
84
|
+
|
|
85
|
+
environment: ...
|
|
88
86
|
# type: ocean_runner.Environment. Mock of environment variables.
|
|
89
87
|
)
|
|
90
88
|
)
|
|
@@ -93,12 +91,12 @@ algorithm = Algorithm(
|
|
|
93
91
|
```python
|
|
94
92
|
import logging
|
|
95
93
|
|
|
94
|
+
from pydantic import BaseModel
|
|
96
95
|
from ocean_runner import Algorithm, Config
|
|
97
96
|
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
foobar: string
|
|
98
|
+
class CustomInput(BaseModel):
|
|
99
|
+
foobar: string
|
|
102
100
|
|
|
103
101
|
|
|
104
102
|
logger = logging.getLogger(__name__)
|
|
@@ -108,7 +106,7 @@ algorithm = Algorithm(
|
|
|
108
106
|
Config(
|
|
109
107
|
custom_input: CustomInput,
|
|
110
108
|
"""
|
|
111
|
-
Load the Algorithm's Custom Input into a CustomInput
|
|
109
|
+
Load the Algorithm's Custom Input into a CustomInput instance.
|
|
112
110
|
"""
|
|
113
111
|
|
|
114
112
|
source_paths: [Path("/algorithm/src")],
|
|
@@ -164,34 +162,32 @@ algorithm = Algorithm()
|
|
|
164
162
|
|
|
165
163
|
|
|
166
164
|
@algorithm.on_error
|
|
167
|
-
def error_callback(ex: Exception):
|
|
165
|
+
def error_callback(algorithm: Algorithm, ex: Exception):
|
|
168
166
|
algorithm.logger.exception(ex)
|
|
169
167
|
raise algorithm.Error() from ex
|
|
170
168
|
|
|
171
169
|
|
|
172
170
|
@algorithm.validate
|
|
173
|
-
def val():
|
|
171
|
+
def val(algorithm: Algorithm):
|
|
174
172
|
assert algorithm.job_details.files, "Empty input dir"
|
|
175
173
|
|
|
176
174
|
|
|
177
175
|
@algorithm.run
|
|
178
|
-
def run() -> pd.DataFrame:
|
|
179
|
-
_, filename = next(algorithm.job_details.
|
|
176
|
+
def run(algorithm: Algorithm) -> pd.DataFrame:
|
|
177
|
+
_, filename = next(algorithm.job_details.inputs())
|
|
180
178
|
return pd.read_csv(filename).describe(include="all")
|
|
181
179
|
|
|
182
180
|
|
|
183
181
|
@algorithm.save_results
|
|
184
|
-
def save(
|
|
185
|
-
algorithm.logger.info(f"Descriptive statistics: {
|
|
186
|
-
|
|
182
|
+
def save(algorithm: Algorithm, result: pd.DataFrame, base: Path):
|
|
183
|
+
algorithm.logger.info(f"Descriptive statistics: {result}")
|
|
184
|
+
result.to_csv(base / "result.csv")
|
|
187
185
|
|
|
188
186
|
|
|
189
187
|
if __name__ == "__main__":
|
|
190
188
|
algorithm()
|
|
191
189
|
```
|
|
192
190
|
|
|
193
|
-
|
|
194
|
-
|
|
195
191
|
### Default implementations
|
|
196
192
|
|
|
197
193
|
As seen in the minimal example, all methods implemented in `Algorithm` have a default implementation which will be commented here.
|
|
@@ -207,7 +203,7 @@ As seen in the minimal example, all methods implemented in `Algorithm` have a de
|
|
|
207
203
|
|
|
208
204
|
.run()
|
|
209
205
|
|
|
210
|
-
"""
|
|
206
|
+
"""
|
|
211
207
|
Has NO default implementation, must pass a callback that returns a result of any type.
|
|
212
208
|
"""
|
|
213
209
|
|
|
@@ -223,7 +219,8 @@ As seen in the minimal example, all methods implemented in `Algorithm` have a de
|
|
|
223
219
|
To load the OceanProtocol JobDetails instance, the program will read some environment variables, they can be mocked passing an instance of `Environment` through the configuration of the algorithm.
|
|
224
220
|
|
|
225
221
|
Environment variables:
|
|
222
|
+
|
|
226
223
|
- `DIDS` (optional) Input dataset(s) DID's, must have format: `["abc..90"]`. Defaults to reading them automatically from the `DDO` data directory.
|
|
227
224
|
- `TRANSFORMATION_DID` (optional, default="DEFAULT"): Algorithm DID, must have format: `abc..90`.
|
|
228
|
-
- `SECRET` (optional, default="DEFAULT"): Algorithm secret.
|
|
225
|
+
- `SECRET` (optional, default="DEFAULT"): Algorithm secret.
|
|
229
226
|
- `BASE_DIR` (optional, default="/data"): Base path to the OceanProtocol data directories.
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Ocean Runner is a package that eases algorithm creation in the scope of OceanProtocol.
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
## Installation
|
|
7
6
|
|
|
8
7
|
```bash
|
|
@@ -23,7 +22,7 @@ algorithm = Algorithm()
|
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
@algorithm.run
|
|
26
|
-
def run():
|
|
25
|
+
def run(_: Algorithm):
|
|
27
26
|
return random.randint()
|
|
28
27
|
|
|
29
28
|
|
|
@@ -50,14 +49,14 @@ algorithm = Algorithm(
|
|
|
50
49
|
Config(
|
|
51
50
|
custom_input: ... # dataclass
|
|
52
51
|
# Custom algorithm parameters dataclass.
|
|
53
|
-
|
|
52
|
+
|
|
54
53
|
logger: ... # type: logging.Logger
|
|
55
54
|
# Custom logger to use.
|
|
56
55
|
|
|
57
56
|
source_paths: ... # type: Iterable[Path]
|
|
58
57
|
# Source paths to include in the PATH
|
|
59
|
-
|
|
60
|
-
environment: ...
|
|
58
|
+
|
|
59
|
+
environment: ...
|
|
61
60
|
# type: ocean_runner.Environment. Mock of environment variables.
|
|
62
61
|
)
|
|
63
62
|
)
|
|
@@ -66,12 +65,12 @@ algorithm = Algorithm(
|
|
|
66
65
|
```python
|
|
67
66
|
import logging
|
|
68
67
|
|
|
68
|
+
from pydantic import BaseModel
|
|
69
69
|
from ocean_runner import Algorithm, Config
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
foobar: string
|
|
72
|
+
class CustomInput(BaseModel):
|
|
73
|
+
foobar: string
|
|
75
74
|
|
|
76
75
|
|
|
77
76
|
logger = logging.getLogger(__name__)
|
|
@@ -81,7 +80,7 @@ algorithm = Algorithm(
|
|
|
81
80
|
Config(
|
|
82
81
|
custom_input: CustomInput,
|
|
83
82
|
"""
|
|
84
|
-
Load the Algorithm's Custom Input into a CustomInput
|
|
83
|
+
Load the Algorithm's Custom Input into a CustomInput instance.
|
|
85
84
|
"""
|
|
86
85
|
|
|
87
86
|
source_paths: [Path("/algorithm/src")],
|
|
@@ -137,34 +136,32 @@ algorithm = Algorithm()
|
|
|
137
136
|
|
|
138
137
|
|
|
139
138
|
@algorithm.on_error
|
|
140
|
-
def error_callback(ex: Exception):
|
|
139
|
+
def error_callback(algorithm: Algorithm, ex: Exception):
|
|
141
140
|
algorithm.logger.exception(ex)
|
|
142
141
|
raise algorithm.Error() from ex
|
|
143
142
|
|
|
144
143
|
|
|
145
144
|
@algorithm.validate
|
|
146
|
-
def val():
|
|
145
|
+
def val(algorithm: Algorithm):
|
|
147
146
|
assert algorithm.job_details.files, "Empty input dir"
|
|
148
147
|
|
|
149
148
|
|
|
150
149
|
@algorithm.run
|
|
151
|
-
def run() -> pd.DataFrame:
|
|
152
|
-
_, filename = next(algorithm.job_details.
|
|
150
|
+
def run(algorithm: Algorithm) -> pd.DataFrame:
|
|
151
|
+
_, filename = next(algorithm.job_details.inputs())
|
|
153
152
|
return pd.read_csv(filename).describe(include="all")
|
|
154
153
|
|
|
155
154
|
|
|
156
155
|
@algorithm.save_results
|
|
157
|
-
def save(
|
|
158
|
-
algorithm.logger.info(f"Descriptive statistics: {
|
|
159
|
-
|
|
156
|
+
def save(algorithm: Algorithm, result: pd.DataFrame, base: Path):
|
|
157
|
+
algorithm.logger.info(f"Descriptive statistics: {result}")
|
|
158
|
+
result.to_csv(base / "result.csv")
|
|
160
159
|
|
|
161
160
|
|
|
162
161
|
if __name__ == "__main__":
|
|
163
162
|
algorithm()
|
|
164
163
|
```
|
|
165
164
|
|
|
166
|
-
|
|
167
|
-
|
|
168
165
|
### Default implementations
|
|
169
166
|
|
|
170
167
|
As seen in the minimal example, all methods implemented in `Algorithm` have a default implementation which will be commented here.
|
|
@@ -180,7 +177,7 @@ As seen in the minimal example, all methods implemented in `Algorithm` have a de
|
|
|
180
177
|
|
|
181
178
|
.run()
|
|
182
179
|
|
|
183
|
-
"""
|
|
180
|
+
"""
|
|
184
181
|
Has NO default implementation, must pass a callback that returns a result of any type.
|
|
185
182
|
"""
|
|
186
183
|
|
|
@@ -196,7 +193,8 @@ As seen in the minimal example, all methods implemented in `Algorithm` have a de
|
|
|
196
193
|
To load the OceanProtocol JobDetails instance, the program will read some environment variables, they can be mocked passing an instance of `Environment` through the configuration of the algorithm.
|
|
197
194
|
|
|
198
195
|
Environment variables:
|
|
196
|
+
|
|
199
197
|
- `DIDS` (optional) Input dataset(s) DID's, must have format: `["abc..90"]`. Defaults to reading them automatically from the `DDO` data directory.
|
|
200
198
|
- `TRANSFORMATION_DID` (optional, default="DEFAULT"): Algorithm DID, must have format: `abc..90`.
|
|
201
|
-
- `SECRET` (optional, default="DEFAULT"): Algorithm secret.
|
|
199
|
+
- `SECRET` (optional, default="DEFAULT"): Algorithm secret.
|
|
202
200
|
- `BASE_DIR` (optional, default="/data"): Base path to the OceanProtocol data directories.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from enum import StrEnum, auto
|
|
2
2
|
from logging import Logger
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Generic, Sequence, TypeVar
|
|
4
|
+
from typing import Generic, Sequence, Type, TypeVar
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, ConfigDict, Field
|
|
7
7
|
from pydantic_settings import BaseSettings
|
|
8
8
|
|
|
9
|
-
InputT = TypeVar("InputT")
|
|
9
|
+
InputT = TypeVar("InputT", BaseModel, None)
|
|
10
10
|
|
|
11
11
|
DEFAULT = "DEFAULT"
|
|
12
12
|
|
|
@@ -21,13 +21,13 @@ class Keys(StrEnum):
|
|
|
21
21
|
class Environment(BaseSettings):
|
|
22
22
|
"""Environment configuration loaded from environment variables"""
|
|
23
23
|
|
|
24
|
-
base_dir: str | Path
|
|
24
|
+
base_dir: str | Path = Field(
|
|
25
25
|
default_factory=lambda: Path("/data"),
|
|
26
26
|
validation_alias=Keys.BASE_DIR.value,
|
|
27
27
|
description="Base data directory, defaults to '/data'",
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
dids: str |
|
|
30
|
+
dids: str | None = Field(
|
|
31
31
|
default=None,
|
|
32
32
|
validation_alias=Keys.DIDS.value,
|
|
33
33
|
description='Datasets DID\'s, format: ["XXXX"]',
|
|
@@ -51,7 +51,7 @@ class Config(BaseModel, Generic[InputT]):
|
|
|
51
51
|
|
|
52
52
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
53
53
|
|
|
54
|
-
custom_input: InputT | None = Field(
|
|
54
|
+
custom_input: Type[InputT] | None = Field(
|
|
55
55
|
default=None,
|
|
56
56
|
description="Algorithm's custom input types, must be a dataclass_json",
|
|
57
57
|
)
|
|
File without changes
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import inspect
|
|
5
|
-
from dataclasses import InitVar, asdict, dataclass, field
|
|
3
|
+
from dataclasses import InitVar, dataclass, field
|
|
6
4
|
from logging import Logger
|
|
7
5
|
from pathlib import Path
|
|
8
|
-
from typing import Awaitable, Callable, Generic, TypeAlias, TypeVar
|
|
6
|
+
from typing import Awaitable, Callable, Dict, Generic, TypeAlias, TypeVar
|
|
9
7
|
|
|
10
|
-
from oceanprotocol_job_details import JobDetails
|
|
8
|
+
from oceanprotocol_job_details import JobDetails, load_job_details, run_in_executor
|
|
9
|
+
from pydantic import BaseModel, JsonValue
|
|
11
10
|
|
|
12
11
|
from ocean_runner.config import Config
|
|
13
12
|
|
|
14
|
-
InputT = TypeVar("InputT")
|
|
13
|
+
InputT = TypeVar("InputT", BaseModel, None)
|
|
15
14
|
ResultT = TypeVar("ResultT")
|
|
16
15
|
T = TypeVar("T")
|
|
17
16
|
|
|
@@ -42,19 +41,6 @@ async def default_save(algorithm: Algorithm, result: ResultT, base: Path) -> Non
|
|
|
42
41
|
await f.write(str(result))
|
|
43
42
|
|
|
44
43
|
|
|
45
|
-
async def execute(
|
|
46
|
-
function: Callable[..., T | Awaitable[T]],
|
|
47
|
-
*args,
|
|
48
|
-
**kwargs,
|
|
49
|
-
) -> T:
|
|
50
|
-
result = function(*args, **kwargs)
|
|
51
|
-
|
|
52
|
-
if inspect.isawaitable(result):
|
|
53
|
-
return await result
|
|
54
|
-
|
|
55
|
-
return result
|
|
56
|
-
|
|
57
|
-
|
|
58
44
|
@dataclass(slots=True)
|
|
59
45
|
class Functions(Generic[InputT, ResultT]):
|
|
60
46
|
validate: ValidateFuncT = field(default=default_validation, init=False)
|
|
@@ -114,7 +100,7 @@ class Algorithm(Generic[InputT, ResultT]):
|
|
|
114
100
|
f"Added [{len(configuration.source_paths)}] entries to PATH"
|
|
115
101
|
)
|
|
116
102
|
|
|
117
|
-
self.configuration = configuration
|
|
103
|
+
self.configuration: Config[InputT] = configuration
|
|
118
104
|
|
|
119
105
|
class Error(RuntimeError): ...
|
|
120
106
|
|
|
@@ -154,41 +140,43 @@ class Algorithm(Generic[InputT, ResultT]):
|
|
|
154
140
|
# Execution Pipeline
|
|
155
141
|
# ---------------------------
|
|
156
142
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
transformation_did
|
|
164
|
-
|
|
165
|
-
|
|
143
|
+
def execute(self) -> ResultT | None:
|
|
144
|
+
env = self.configuration.environment
|
|
145
|
+
config: Dict[str, JsonValue] = {
|
|
146
|
+
"base_dir": str(env.base_dir),
|
|
147
|
+
"dids": env.dids,
|
|
148
|
+
"secret": env.secret,
|
|
149
|
+
"transformation_did": env.transformation_did,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
self._job_details = load_job_details(config, self.configuration.custom_input)
|
|
166
153
|
|
|
167
154
|
self.logger.info("Loaded JobDetails")
|
|
168
|
-
self.logger.debug(
|
|
155
|
+
self.logger.debug(self.job_details.model_dump())
|
|
169
156
|
|
|
170
157
|
try:
|
|
171
|
-
|
|
158
|
+
run_in_executor(self._functions.validate(self))
|
|
172
159
|
|
|
173
160
|
if self._functions.run:
|
|
174
161
|
self.logger.info("Running algorithm...")
|
|
175
|
-
self._result =
|
|
162
|
+
self._result = run_in_executor(self._functions.run(self))
|
|
176
163
|
else:
|
|
177
164
|
self.logger.error("No run() function defined. Skipping execution.")
|
|
178
165
|
self._result = None
|
|
179
166
|
|
|
180
|
-
|
|
181
|
-
self._functions.save
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
167
|
+
run_in_executor(
|
|
168
|
+
self._functions.save(
|
|
169
|
+
algorithm=self,
|
|
170
|
+
result=self._result,
|
|
171
|
+
base=self.job_details.paths.outputs,
|
|
172
|
+
),
|
|
185
173
|
)
|
|
186
174
|
|
|
187
175
|
except Exception as e:
|
|
188
|
-
|
|
176
|
+
run_in_executor(self._functions.error(self, e))
|
|
189
177
|
|
|
190
178
|
return self._result
|
|
191
179
|
|
|
192
180
|
def __call__(self) -> ResultT | None:
|
|
193
181
|
"""Executes the algorithm pipeline: validate → run → save_results."""
|
|
194
|
-
return
|
|
182
|
+
return self.execute()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ocean-runner"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.25"
|
|
4
4
|
description = "A fluent API for OceanProtocol algorithms"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "AgrospAI", email = "agrospai@udl.cat" },
|
|
@@ -16,11 +16,10 @@ classifiers = [
|
|
|
16
16
|
]
|
|
17
17
|
dependencies = [
|
|
18
18
|
"aiofiles>=25.1.0",
|
|
19
|
-
"oceanprotocol-job-details>=0.
|
|
19
|
+
"oceanprotocol-job-details>=0.3.11",
|
|
20
20
|
"pydantic>=2.12.5",
|
|
21
21
|
"pydantic-settings>=2.12.0",
|
|
22
22
|
"pytest>=8.4.2",
|
|
23
|
-
"types-aiofiles>=25.1.0.20251011",
|
|
24
23
|
]
|
|
25
24
|
|
|
26
25
|
[project.urls]
|
|
@@ -37,15 +36,16 @@ requires = ["hatchling"]
|
|
|
37
36
|
build-backend = "hatchling.build"
|
|
38
37
|
|
|
39
38
|
[dependency-groups]
|
|
40
|
-
dev = [
|
|
41
|
-
"mypy>=1.19.1",
|
|
42
|
-
]
|
|
39
|
+
dev = ["mypy>=1.19.1", "types-aiofiles>=25.1.0.20251011"]
|
|
43
40
|
|
|
44
41
|
[tool.hatch.build.targets.sdist]
|
|
45
42
|
include = ["ocean_runner"]
|
|
46
43
|
|
|
47
44
|
[tool.hatch.build.targets.wheel]
|
|
48
|
-
|
|
45
|
+
packages = ["ocean_runner"]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel.package-data]
|
|
48
|
+
ocean_runner = ["py.typed"]
|
|
49
49
|
|
|
50
50
|
[tool.mypy]
|
|
51
51
|
plugins = ['pydantic.mypy']
|
|
File without changes
|
|
File without changes
|
|
File without changes
|