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 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,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
- 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]
15
19
 
16
- def default_error_callback(_, e: Exception) -> None:
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(*, result: ResultT, base: Path, algorithm: Algorithm) -> None:
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[JobDetailsT, ResultT]):
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[JobDetailsT] = field(init=False)
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: Callable[[Algorithm], None] | None = field(default=None, init=False)
47
- _run_fn: Callable[[Algorithm], ResultT] | None = field(default=None, init=False)
48
- _save_fn: Callable[[ResultT, Path, Algorithm], None] | None = field(
49
- default=None, init=False
52
+ _validate_fn: ValidateFuncT = field(
53
+ default=default_validation,
54
+ init=False,
55
+ repr=False,
50
56
  )
51
- _error_callback: Callable[[Algorithm, Exception], None] = field(
52
- default=default_error_callback, init=False
57
+
58
+ _run_fn: RunFuncT = field(
59
+ default=None,
60
+ init=False,
61
+ repr=False,
53
62
  )
54
63
 
55
- def __post_init__(self, config: Config | None) -> None:
56
- config: Config = config or Config()
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 config.logger:
60
- self.logger = config.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("ocean_runner")
90
+ self.logger = logging.getLogger(__name__)
70
91
 
71
92
  # Normalize base_dir
72
- if isinstance(config.environment.base_dir, str):
73
- 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
+ )
74
97
 
75
98
  # Extend sys.path for custom imports
76
- if config.source_paths:
99
+ if configuration.source_paths:
77
100
  import sys
78
101
 
79
- sys.path.extend([str(path.absolute()) for path in config.source_paths])
80
- self.logger.debug(f"Added [{len(config.source_paths)}] entries to PATH")
81
-
82
- # Load job details
83
- self._job_details = JobDetails.load(
84
- _type=config.custom_input,
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.config = config
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: Callable[[], None]) -> Callable[[], None]:
129
+ def validate(self, fn: ValidateFuncT) -> ValidateFuncT:
115
130
  self._validate_fn = fn
116
131
  return fn
117
132
 
118
- def run(self, fn: Callable[[], ResultT]) -> Callable[[], ResultT]:
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: Callable[[ResultT, Path], None]) -> Callable:
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: Callable[[Exception], None]) -> Callable:
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
- if self._validate_fn:
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.warning("No run() function defined. Skipping execution.")
172
+ self.logger.error("No run() function defined. Skipping execution.")
151
173
  self._result = None
152
174
 
153
175
  # Save step
154
- if self._save_fn:
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.logger.exception("Error during algorithm execution")
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.16
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.7
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,