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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ocean-runner
3
- Version: 0.2.21
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.2.8
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
- @dataclass
100
- class CustomInput:
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 dataclass instance.
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.next_path())
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(results: pd.DataFrame, path: Path):
185
- algorithm.logger.info(f"Descriptive statistics: {results}")
186
- results.to_csv(path / "results.csv")
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
- @dataclass
73
- class CustomInput:
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 dataclass instance.
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.next_path())
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(results: pd.DataFrame, path: Path):
158
- algorithm.logger.info(f"Descriptive statistics: {results}")
159
- results.to_csv(path / "results.csv")
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 | None = Field(
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 | list[Path] | None = Field(
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 asyncio
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 # type: ignore
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
- async def execute(self) -> ResultT | None:
158
- # Load job details
159
- self._job_details = JobDetails.load(
160
- _type=self.configuration.custom_input,
161
- base_dir=self.configuration.environment.base_dir,
162
- dids=self.configuration.environment.dids,
163
- transformation_did=self.configuration.environment.transformation_did,
164
- secret=self.configuration.environment.secret,
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(asdict(self.job_details))
155
+ self.logger.debug(self.job_details.model_dump())
169
156
 
170
157
  try:
171
- await execute(self._functions.validate, self)
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 = await execute(self._functions.run, self)
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
- await execute(
181
- self._functions.save,
182
- algorithm=self,
183
- result=self._result,
184
- base=self.job_details.paths.outputs,
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
- await execute(self._functions.error, self, e)
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 asyncio.run(self.execute())
182
+ return self.execute()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ocean-runner"
3
- version = "0.2.21"
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.2.8",
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
- include = ["ocean_runner"]
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