oceanprotocol-job-details 0.0.2__tar.gz → 0.0.6__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.
Files changed (30) hide show
  1. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/PKG-INFO +3 -4
  2. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/README.md +2 -4
  3. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/dataclasses/constants.py +3 -0
  4. oceanprotocol_job_details-0.0.6/oceanprotocol_job_details/dataclasses/job_details.py +96 -0
  5. oceanprotocol_job_details-0.0.6/oceanprotocol_job_details/job_details.py +42 -0
  6. oceanprotocol_job_details-0.0.6/oceanprotocol_job_details/loaders/impl/map.py +112 -0
  7. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/pyproject.toml +1 -1
  8. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/__pycache__/__init__.cpython-313.pyc +0 -0
  9. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/__pycache__/__init__.cpython-39.pyc +0 -0
  10. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/__pycache__/constants.cpython-313.pyc +0 -0
  11. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/__pycache__/constants.cpython-39.pyc +0 -0
  12. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/__pycache__/job_details.cpython-313.pyc +0 -0
  13. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/__pycache__/job_details.cpython-39.pyc +0 -0
  14. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/dataclasses/job_details.py +0 -35
  15. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/job_details.py +0 -20
  16. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
  17. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/__pycache__/__init__.cpython-39.pyc +0 -0
  18. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/__pycache__/loader.cpython-313.pyc +0 -0
  19. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/__pycache__/loader.cpython-39.pyc +0 -0
  20. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/impl/__pycache__/__init__.cpython-313.pyc +0 -0
  21. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/impl/__pycache__/__init__.cpython-39.pyc +0 -0
  22. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/impl/__pycache__/environment.cpython-313.pyc +0 -0
  23. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/impl/__pycache__/environment.cpython-39.pyc +0 -0
  24. oceanprotocol_job_details-0.0.2/oceanprotocol_job_details/loaders/impl/environment.py +0 -98
  25. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/LICENSE +0 -0
  26. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/__init__.py +0 -0
  27. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/dataclasses/__init__.py +0 -0
  28. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/loaders/__init__.py +0 -0
  29. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/loaders/impl/__init__.py +0 -0
  30. {oceanprotocol_job_details-0.0.2 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/loaders/loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: oceanprotocol-job-details
3
- Version: 0.0.2
3
+ Version: 0.0.6
4
4
  Summary: A Python package to get details from OceanProtocol jobs
5
5
  License: Copyright 2025 Agrospai
6
6
 
@@ -39,8 +39,6 @@ from oceanprotocol_job_details.job_details import OceanProtocolJobDetails
39
39
 
40
40
  # Using default parameters
41
41
  job_details = OceanProtocolJobDetails().load()
42
-
43
- job_details
44
42
  ```
45
43
 
46
44
  ### Advanced Usage (not recommended)
@@ -49,7 +47,7 @@ If instead of the environment variables, we want to use another kind of mapping,
49
47
 
50
48
  ```Python
51
49
  from oceanprotocol_job_details.job_details import OceanProtocolJobDetails
52
- from src.oceanprotocol_job_details.loaders.impl.environment import Keys
50
+ from oceanprotocol_job_details.loaders.impl.environment import Keys
53
51
 
54
52
  # Fill in with values that will be used instead of env
55
53
  custom_mapper = {
@@ -61,3 +59,4 @@ custom_mapper = {
61
59
 
62
60
  job_details = OceanProtocolJobDetails(mapper=custom_mapper).load()
63
61
  ```
62
+
@@ -17,8 +17,6 @@ from oceanprotocol_job_details.job_details import OceanProtocolJobDetails
17
17
 
18
18
  # Using default parameters
19
19
  job_details = OceanProtocolJobDetails().load()
20
-
21
- job_details
22
20
  ```
23
21
 
24
22
  ### Advanced Usage (not recommended)
@@ -27,7 +25,7 @@ If instead of the environment variables, we want to use another kind of mapping,
27
25
 
28
26
  ```Python
29
27
  from oceanprotocol_job_details.job_details import OceanProtocolJobDetails
30
- from src.oceanprotocol_job_details.loaders.impl.environment import Keys
28
+ from oceanprotocol_job_details.loaders.impl.environment import Keys
31
29
 
32
30
  # Fill in with values that will be used instead of env
33
31
  custom_mapper = {
@@ -38,4 +36,4 @@ custom_mapper = {
38
36
  }
39
37
 
40
38
  job_details = OceanProtocolJobDetails(mapper=custom_mapper).load()
41
- ```
39
+ ```
@@ -25,11 +25,14 @@ class _Paths:
25
25
  """Common paths used in the Ocean Protocol directories"""
26
26
 
27
27
  DATA: Path = Path("data")
28
+
28
29
  INPUTS: Path = DATA / "inputs"
29
30
  DDOS: Path = DATA / "ddos"
30
31
  OUTPUTS: Path = DATA / "outputs"
31
32
  LOGS: Path = DATA / "logs"
32
33
 
34
+ ALGORITHM_CUSTOM_PARAMETERS: Path = INPUTS / "algoCustomData.json"
35
+
33
36
 
34
37
  DidKeys = _DidKeys()
35
38
  ServiceType = _ServiceType()
@@ -0,0 +1,96 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from dataclasses import InitVar, dataclass
5
+ from pathlib import Path
6
+ from typing import Any, Mapping, Optional, Sequence
7
+
8
+ from oceanprotocol_job_details.dataclasses.constants import Paths
9
+
10
+ _MetadataType = Mapping[str, Any]
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Parameters:
17
+ """Custom data for the algorithm, such as the algorithm's parameters"""
18
+
19
+ parameters: _MetadataType
20
+ """The parameters used by the algorithm"""
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class Algorithm:
25
+ """Details of the algorithm used to process the data"""
26
+
27
+ did: str
28
+ """The DID of the algorithm used to process the data"""
29
+
30
+ ddo: Path
31
+ """The DDO path of the algorithm used to process the data"""
32
+
33
+
34
+ @dataclass
35
+ class JobDetails:
36
+ """Details of the current job, such as the used inputs and algorithm"""
37
+
38
+ root: Path
39
+ """The root folder of the Ocean Protocol directories"""
40
+
41
+ dids: Sequence[Path]
42
+ """Identifiers for the inputs"""
43
+
44
+ files: Mapping[str, Sequence[Path]]
45
+ """Paths to the input files"""
46
+
47
+ secret: Optional[str]
48
+ """The secret used to process the data"""
49
+
50
+ algorithm: Optional[Algorithm]
51
+ """Details of the used algorithm"""
52
+
53
+ # Cache parameters, should not be included as _fields_ of the class
54
+ _parameters: InitVar[Optional[_MetadataType]] = None
55
+
56
+ def __post_init__(self, _):
57
+ os.makedirs(self.root / Paths.LOGS, exist_ok=True)
58
+
59
+ logging.getLogger().addHandler(
60
+ logging.FileHandler(
61
+ self.root / Paths.LOGS / "job_details.log",
62
+ mode="w",
63
+ )
64
+ )
65
+
66
+ @property
67
+ def parameters(self, parameters: Optional[Path] = None) -> _MetadataType:
68
+ """Parameters for algorithm job, read from default path"""
69
+
70
+ if parameters is None:
71
+ parameters = self.root / Paths.ALGORITHM_CUSTOM_PARAMETERS
72
+
73
+ if self._parameters is None:
74
+ if not parameters.exists():
75
+ logging.warning(
76
+ f"Parameters file {parameters} not found, supplying empty"
77
+ )
78
+ self._parameters = {}
79
+ else:
80
+ # Load the parameters from filesystem
81
+ with open(parameters, "r") as f:
82
+ try:
83
+ self._parameters = json.load(f)
84
+ except json.JSONDecodeError as e:
85
+ self._parameters = {}
86
+ logger.warning(
87
+ f"Error loading parameters file {parameters}: {e}"
88
+ )
89
+
90
+ return self._parameters
91
+
92
+
93
+ del _MetadataType
94
+
95
+
96
+ __all__ = ["Algorithm", "Parameters", "JobDetails"]
@@ -0,0 +1,42 @@
1
+ import logging
2
+ import os
3
+ from typing import Any, Literal, Mapping, Optional
4
+
5
+ from oceanprotocol_job_details.dataclasses.job_details import JobDetails
6
+ from oceanprotocol_job_details.loaders.impl.map import Keys, Map
7
+ from oceanprotocol_job_details.loaders.loader import Loader
8
+
9
+ # Logging setup for the module
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format="%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s",
13
+ handlers=[
14
+ logging.StreamHandler(),
15
+ ],
16
+ )
17
+
18
+ _Implementations = Literal["env"]
19
+
20
+
21
+ class OceanProtocolJobDetails(Loader[JobDetails]):
22
+ """Decorator that loads the JobDetails from the given implementation"""
23
+
24
+ def __init__(
25
+ self,
26
+ implementation: Optional[_Implementations] = "map",
27
+ mapper: Mapping[str, Any] = os.environ,
28
+ keys: Keys = Keys(),
29
+ *args,
30
+ **kwargs,
31
+ ):
32
+ if implementation == "map":
33
+ # As there are not more implementations, we can use the EnvironmentLoader directly
34
+ self._loader = lambda: Map(mapper=mapper, keys=keys, *args, **kwargs)
35
+ else:
36
+ raise NotImplementedError(f"Implementation {implementation} not supported")
37
+
38
+ def load(self) -> JobDetails:
39
+ return self._loader().load()
40
+
41
+
42
+ del _Implementations
@@ -0,0 +1,112 @@
1
+ """Loads the current Job Details from the environment variables, could be abstracted to a more general 'mapper loader' but won't, since right now it fits our needs"""
2
+
3
+ from dataclasses import dataclass
4
+ from json import JSONDecodeError, load, loads
5
+ from logging import getLogger
6
+ from pathlib import Path
7
+ from typing import Mapping, Optional, Sequence, final
8
+
9
+ from oceanprotocol_job_details.dataclasses.constants import DidKeys, Paths, ServiceType
10
+ from oceanprotocol_job_details.dataclasses.job_details import Algorithm, JobDetails
11
+ from oceanprotocol_job_details.loaders.loader import Loader
12
+
13
+ logger = getLogger(__name__)
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Keys:
18
+ """Environment keys passed to the algorithm"""
19
+
20
+ ROOT: str = "ROOT_FOLDER"
21
+ SECRET: str = "secret"
22
+ ALGORITHM: str = "TRANSFORMATION_DID"
23
+ DIDS: str = "DIDS"
24
+
25
+
26
+ @final
27
+ class Map(Loader[JobDetails]):
28
+ """Loads the current Job Details from the environment variables"""
29
+
30
+ def __init__(self, mapper: Mapping[str, str], keys: Keys, *args, **kwargs):
31
+ super().__init__(*args, **kwargs)
32
+
33
+ self._mapper = mapper
34
+ self._keys = keys
35
+
36
+ def load(self, *args, **kwargs) -> JobDetails:
37
+ root, dids = self._root(), self._dids()
38
+
39
+ return JobDetails(
40
+ root=root,
41
+ dids=dids,
42
+ files=self._files(root, dids),
43
+ algorithm=self._algorithm(root),
44
+ secret=self._secret(),
45
+ )
46
+
47
+ def _root(self) -> Path:
48
+ root = Path(self._mapper.get(self._keys.ROOT, Path.home()))
49
+
50
+ if not root.exists():
51
+ raise FileNotFoundError(f"Root folder {root} does not exist")
52
+
53
+ return root
54
+
55
+ def _dids(self) -> Sequence[Path]:
56
+ return (
57
+ loads(self._mapper.get(self._keys.DIDS))
58
+ if self._keys.DIDS in self._mapper
59
+ else []
60
+ )
61
+
62
+ def _files(
63
+ self,
64
+ root: Path,
65
+ dids: Optional[Sequence[Path]],
66
+ ) -> Mapping[str, Sequence[Path]]:
67
+
68
+ files: Mapping[str, Sequence[Path]] = {}
69
+
70
+ for did in dids:
71
+ # Retrieve DDO from disk
72
+ file_path = root / Paths.DDOS / did
73
+ if not file_path.exists():
74
+ raise FileNotFoundError(f"DDO file {file_path} does not exist")
75
+
76
+ with open(file_path, "r") as f:
77
+ try:
78
+ ddo = load(f)
79
+ except JSONDecodeError as e:
80
+ logger.warning(f"Error loading DDO file {file_path}: {e}")
81
+ continue
82
+
83
+ for service in ddo[DidKeys.SERVICE]:
84
+ if service[DidKeys.SERVICE_TYPE] != ServiceType.METADATA:
85
+ continue
86
+
87
+ did_path = root / Paths.INPUTS / did
88
+ files[did] = [
89
+ did_path / str(idx)
90
+ for idx in range(
91
+ len(
92
+ service[DidKeys.ATTRIBUTES][DidKeys.MAIN][DidKeys.FILES]
93
+ )
94
+ )
95
+ ]
96
+
97
+ return files
98
+
99
+ def _algorithm(self, root: Path) -> Optional[Algorithm]:
100
+ did = self._mapper.get(self._keys.ALGORITHM, None)
101
+
102
+ if not did:
103
+ return None
104
+
105
+ ddo = root / Paths.DDOS / did
106
+ if not ddo.exists():
107
+ raise FileNotFoundError(f"DDO file {ddo} does not exist")
108
+
109
+ return Algorithm(did, ddo)
110
+
111
+ def _secret(self) -> Optional[str]:
112
+ return self._mapper.get(self._keys.SECRET, None)
@@ -2,7 +2,7 @@
2
2
  license = { file = "LICENSE" }
3
3
 
4
4
  name = "oceanprotocol-job-details"
5
- version = "0.0.2"
5
+ version = "0.0.6"
6
6
  authors = [
7
7
  { name = "Christian López García", email = "christian.lopez@udl.cat" },
8
8
  ]
@@ -1,35 +0,0 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
- from typing import Any, Mapping, Optional, Sequence
4
-
5
-
6
- @dataclass(frozen=True)
7
- class Algorithm:
8
- did: str
9
- """The DID of the algorithm used to process the data"""
10
-
11
- ddo: Path
12
- """The DDO path of the algorithm used to process the data"""
13
-
14
-
15
- @dataclass(frozen=True)
16
- class JobDetails:
17
- """Details of the current job, such as the used inputs and algorithm"""
18
-
19
- root: Path
20
- """The root folder of the Ocean Protocol directories"""
21
-
22
- dids: Optional[Sequence[Path]]
23
- """Identifiers for the inputs"""
24
-
25
- metadata: Mapping[str, Any]
26
- """TODO: To define"""
27
-
28
- files: Mapping[str, Sequence[Path]]
29
- """Paths to the input files"""
30
-
31
- secret: Optional[str]
32
- """The secret used to process the data"""
33
-
34
- algorithm: Optional[Algorithm]
35
- """Details of the used algorithm"""
@@ -1,20 +0,0 @@
1
- from typing import Literal, Optional
2
- from oceanprotocol_job_details.dataclasses.job_details import JobDetails
3
- from oceanprotocol_job_details.loaders.loader import Loader
4
- from oceanprotocol_job_details.loaders.impl.environment import EnvironmentLoader
5
-
6
- _Implementations = Literal["env"]
7
-
8
-
9
- class OceanProtocolJobDetails(Loader[JobDetails]):
10
- """Decorator that loads the JobDetails from the given implementation"""
11
-
12
- def __init__(self, implementation: Optional[_Implementations], *args, **kwargs):
13
- # As there are not more implementations, we can use the EnvironmentLoader directly
14
- self._loader = lambda: EnvironmentLoader(*args, **kwargs)
15
-
16
- def load(self) -> JobDetails:
17
- return self._loader().load()
18
-
19
-
20
- del _Implementations
@@ -1,98 +0,0 @@
1
- """Loads the current Job Details from the environment variables, could be abstracted to a more general 'mapper loader' but won't, since right now it fits our needs"""
2
-
3
- import os
4
- from collections.abc import Mapping, Sequence
5
- from dataclasses import dataclass
6
- from json import load, loads
7
- from pathlib import Path
8
- from typing import Optional, final
9
-
10
- from oceanprotocol_job_details.dataclasses.constants import (
11
- DidKeys,
12
- Paths,
13
- ServiceType,
14
- )
15
- from oceanprotocol_job_details.dataclasses.job_details import Algorithm, JobDetails
16
- from oceanprotocol_job_details.loaders.loader import Loader
17
-
18
-
19
- @dataclass(frozen=True)
20
- class _Keys:
21
- """Environment keys passed to the algorithm"""
22
-
23
- ROOT: str = "ROOT_FOLDER"
24
- SECRET: str = "secret"
25
- ALGORITHM: str = "TRANSFORMATION_DID"
26
- DIDS: str = "DIDS"
27
-
28
-
29
- Keys = _Keys()
30
- del _Keys
31
-
32
-
33
- @final
34
- class EnvironmentLoader(Loader[JobDetails]):
35
- """Loads the current Job Details from the environment variables"""
36
-
37
- def __init__(self, mapper: Mapping[str, str] = os.environ):
38
- super().__init__()
39
- self.mapper = mapper
40
-
41
- def load(self, *args, **kwargs) -> JobDetails:
42
- root, dids = self._root(), self._dids()
43
-
44
- return JobDetails(
45
- root=root,
46
- dids=dids,
47
- metadata=self._metadata(),
48
- files=self._files(root, dids),
49
- algorithm=self._algorithm(root=root),
50
- secret=self._secret(),
51
- )
52
-
53
- def _root(self) -> Path:
54
- return Path(self.mapper.get(Keys.ROOT, ""))
55
-
56
- def _dids(self) -> Sequence[str]:
57
- return loads(self.mapper.get(Keys.DIDS)) if Keys.DIDS in self.mapper else []
58
-
59
- def _files(
60
- self,
61
- root: Path,
62
- dids: Optional[Sequence[Path]],
63
- ) -> Mapping[str, Sequence[Path]]:
64
- files: Mapping[str, Sequence[Path]] = {}
65
- for did in dids:
66
- # Retrieve DDO from disk
67
- file = root / Paths.DDOS / did
68
- with open(file, "r") as f:
69
- ddo = load(f)
70
- for service in ddo[DidKeys.SERVICE]:
71
- if service[DidKeys.SERVICE_TYPE] == ServiceType.METADATA:
72
- base_path = root / Paths.INPUTS / did
73
- files[did] = [
74
- base_path / str(idx)
75
- for idx in range(
76
- len(
77
- service[DidKeys.ATTRIBUTES][DidKeys.MAIN][
78
- DidKeys.FILES
79
- ]
80
- )
81
- )
82
- ]
83
- return files
84
-
85
- def _metadata(self) -> Mapping[str, str]:
86
- return {}
87
-
88
- def _algorithm(self, root: Path) -> Algorithm:
89
- did = self.mapper.get(Keys.ALGORITHM, None)
90
- if not did:
91
- return None
92
- return Algorithm(
93
- did=did,
94
- ddo=root / Paths.DDOS / did,
95
- )
96
-
97
- def _secret(self) -> str:
98
- return self.mapper.get(Keys.SECRET, "")