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 CHANGED
@@ -1,4 +1,4 @@
1
1
  from ocean_runner.config import Config, Environment
2
2
  from ocean_runner.runner import Algorithm
3
3
 
4
- __all__ = [Config, Algorithm, Environment]
4
+ __all__ = [Config, Algorithm, Environment] # type: ignore
ocean_runner/config.py CHANGED
@@ -1,55 +1,71 @@
1
- import os
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 Iterable, TypeVar
4
+ from typing import Generic, Sequence, TypeVar
6
5
 
7
- T = TypeVar("T")
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
- @dataclass
13
- class Environment:
14
- """Environment variables mock"""
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
- dids: str = field(
22
- default_factory=lambda: os.environ.get("DIDS", None),
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
- transformation_did: str = field(
27
- default_factory=lambda: os.environ.get("TRANSFORMATION_DID", DEFAULT),
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
- secret: str = field(
32
- default_factory=lambda: os.environ.get("SECRET", DEFAULT),
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
- dict = asdict
42
+ secret: str = Field(
43
+ default=DEFAULT,
44
+ validation_alias=Keys.SECRET.value,
45
+ description="Super secret secret",
46
+ )
37
47
 
38
48
 
39
- @dataclass
40
- class Config:
49
+ class Config(BaseModel, Generic[InputT]):
41
50
  """Algorithm overall configuration"""
42
51
 
43
- custom_input: T | None = None
44
- """Algorithm's custom input types, must be a dataclass_json"""
52
+ model_config = ConfigDict(arbitrary_types_allowed=True)
45
53
 
46
- logger: Logger | None = None
47
- """Logger to use in the algorithm"""
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: Iterable[Path] = field(
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 = field(default_factory=lambda: Environment())
55
- """Mock of environment data"""
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
- JobDetailsT = TypeVar("JobDetailsT")
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(*, result: ResultT, base: Path, algorithm: Algorithm) -> None:
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[JobDetailsT, ResultT]):
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[JobDetailsT] = field(init=False)
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: Callable[[Algorithm], None] | None = field(
48
- default=None,
52
+ _validate_fn: ValidateFuncT = field(
53
+ default=default_validation,
49
54
  init=False,
50
55
  repr=False,
51
56
  )
52
57
 
53
- _run_fn: Callable[[Algorithm], ResultT] | None = field(
58
+ _run_fn: RunFuncT = field(
54
59
  default=None,
55
60
  init=False,
56
61
  repr=False,
57
62
  )
58
63
 
59
- _save_fn: Callable[[ResultT, Path, Algorithm], None] | None = field(
60
- default=None,
64
+ _save_fn: SaveFuncT = field(
65
+ default=default_save,
61
66
  init=False,
62
67
  repr=False,
63
68
  )
64
69
 
65
- _error_callback: Callable[[Algorithm, Exception], None] | None = field(
66
- default=None,
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
- config: Config = config or Config()
76
+ def __post_init__(self, config: Config[InputT] | None) -> None:
77
+ configuration = config or Config()
73
78
 
74
79
  # Configure logger
75
- if config.logger:
76
- self.logger = config.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("ocean_runner")
90
+ self.logger = logging.getLogger(__name__)
86
91
 
87
92
  # Normalize base_dir
88
- if isinstance(config.environment.base_dir, str):
89
- config.environment.base_dir = Path(config.environment.base_dir)
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 config.source_paths:
99
+ if configuration.source_paths:
93
100
  import sys
94
101
 
95
- sys.path.extend([str(path.absolute()) for path in config.source_paths])
96
- self.logger.debug(f"Added [{len(config.source_paths)}] entries to PATH")
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.config = config
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: Callable[[], None]) -> Callable[[], None]:
129
+ def validate(self, fn: ValidateFuncT) -> ValidateFuncT:
119
130
  self._validate_fn = fn
120
131
  return fn
121
132
 
122
- def run(self, fn: Callable[[], ResultT]) -> Callable[[], ResultT]:
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: Callable[[ResultT, Path], None]) -> Callable:
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: Callable[[Exception], None]) -> Callable:
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.config.custom_input,
143
- base_dir=self.config.environment.base_dir,
144
- dids=self.config.environment.dids,
145
- transformation_did=self.config.environment.transformation_did,
146
- secret=self.config.environment.secret,
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
- if self._validate_fn:
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.warning("No run() function defined. Skipping execution.")
172
+ self.logger.error("No run() function defined. Skipping execution.")
167
173
  self._result = None
168
174
 
169
175
  # Save step
170
- if self._save_fn:
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
- if self._error_callback:
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.18
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,,