oceanprotocol-job-details 0.0.5__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.
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/PKG-INFO +1 -1
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/dataclasses/constants.py +3 -0
- oceanprotocol_job_details-0.0.6/oceanprotocol_job_details/dataclasses/job_details.py +96 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/job_details.py +19 -5
- oceanprotocol_job_details-0.0.5/oceanprotocol_job_details/loaders/impl/environment.py → oceanprotocol_job_details-0.0.6/oceanprotocol_job_details/loaders/impl/map.py +46 -46
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/pyproject.toml +1 -1
- oceanprotocol_job_details-0.0.5/oceanprotocol_job_details/dataclasses/job_details.py +0 -35
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/LICENSE +0 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/README.md +0 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/__init__.py +0 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/dataclasses/__init__.py +0 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/loaders/__init__.py +0 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/loaders/impl/__init__.py +0 -0
- {oceanprotocol_job_details-0.0.5 → oceanprotocol_job_details-0.0.6}/oceanprotocol_job_details/loaders/loader.py +0 -0
|
@@ -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"]
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Literal, Mapping, Optional
|
|
4
|
+
|
|
2
5
|
from oceanprotocol_job_details.dataclasses.job_details import JobDetails
|
|
6
|
+
from oceanprotocol_job_details.loaders.impl.map import Keys, Map
|
|
3
7
|
from oceanprotocol_job_details.loaders.loader import Loader
|
|
4
|
-
|
|
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
|
+
)
|
|
5
17
|
|
|
6
18
|
_Implementations = Literal["env"]
|
|
7
19
|
|
|
@@ -11,13 +23,15 @@ class OceanProtocolJobDetails(Loader[JobDetails]):
|
|
|
11
23
|
|
|
12
24
|
def __init__(
|
|
13
25
|
self,
|
|
14
|
-
implementation: Optional[_Implementations] = "
|
|
26
|
+
implementation: Optional[_Implementations] = "map",
|
|
27
|
+
mapper: Mapping[str, Any] = os.environ,
|
|
28
|
+
keys: Keys = Keys(),
|
|
15
29
|
*args,
|
|
16
30
|
**kwargs,
|
|
17
31
|
):
|
|
18
|
-
if implementation == "
|
|
32
|
+
if implementation == "map":
|
|
19
33
|
# As there are not more implementations, we can use the EnvironmentLoader directly
|
|
20
|
-
self._loader = lambda:
|
|
34
|
+
self._loader = lambda: Map(mapper=mapper, keys=keys, *args, **kwargs)
|
|
21
35
|
else:
|
|
22
36
|
raise NotImplementedError(f"Implementation {implementation} not supported")
|
|
23
37
|
|
|
@@ -1,23 +1,20 @@
|
|
|
1
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
2
|
|
|
3
|
-
import os
|
|
4
|
-
from collections.abc import Mapping, Sequence
|
|
5
3
|
from dataclasses import dataclass
|
|
6
|
-
from json import load, loads
|
|
4
|
+
from json import JSONDecodeError, load, loads
|
|
5
|
+
from logging import getLogger
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
from typing import Optional, final
|
|
7
|
+
from typing import Mapping, Optional, Sequence, final
|
|
9
8
|
|
|
10
|
-
from oceanprotocol_job_details.dataclasses.constants import
|
|
11
|
-
DidKeys,
|
|
12
|
-
Paths,
|
|
13
|
-
ServiceType,
|
|
14
|
-
)
|
|
9
|
+
from oceanprotocol_job_details.dataclasses.constants import DidKeys, Paths, ServiceType
|
|
15
10
|
from oceanprotocol_job_details.dataclasses.job_details import Algorithm, JobDetails
|
|
16
11
|
from oceanprotocol_job_details.loaders.loader import Loader
|
|
17
12
|
|
|
13
|
+
logger = getLogger(__name__)
|
|
14
|
+
|
|
18
15
|
|
|
19
16
|
@dataclass(frozen=True)
|
|
20
|
-
class
|
|
17
|
+
class Keys:
|
|
21
18
|
"""Environment keys passed to the algorithm"""
|
|
22
19
|
|
|
23
20
|
ROOT: str = "ROOT_FOLDER"
|
|
@@ -26,17 +23,15 @@ class _Keys:
|
|
|
26
23
|
DIDS: str = "DIDS"
|
|
27
24
|
|
|
28
25
|
|
|
29
|
-
Keys = _Keys()
|
|
30
|
-
del _Keys
|
|
31
|
-
|
|
32
|
-
|
|
33
26
|
@final
|
|
34
|
-
class
|
|
27
|
+
class Map(Loader[JobDetails]):
|
|
35
28
|
"""Loads the current Job Details from the environment variables"""
|
|
36
29
|
|
|
37
|
-
def __init__(self, mapper: Mapping[str, str]
|
|
38
|
-
super().__init__()
|
|
39
|
-
|
|
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
|
|
40
35
|
|
|
41
36
|
def load(self, *args, **kwargs) -> JobDetails:
|
|
42
37
|
root, dids = self._root(), self._dids()
|
|
@@ -44,29 +39,34 @@ class EnvironmentLoader(Loader[JobDetails]):
|
|
|
44
39
|
return JobDetails(
|
|
45
40
|
root=root,
|
|
46
41
|
dids=dids,
|
|
47
|
-
metadata=self._metadata(),
|
|
48
42
|
files=self._files(root, dids),
|
|
49
|
-
algorithm=self._algorithm(root
|
|
43
|
+
algorithm=self._algorithm(root),
|
|
50
44
|
secret=self._secret(),
|
|
51
45
|
)
|
|
52
46
|
|
|
53
47
|
def _root(self) -> Path:
|
|
54
|
-
root = Path(self.
|
|
48
|
+
root = Path(self._mapper.get(self._keys.ROOT, Path.home()))
|
|
55
49
|
|
|
56
50
|
if not root.exists():
|
|
57
51
|
raise FileNotFoundError(f"Root folder {root} does not exist")
|
|
58
52
|
|
|
59
53
|
return root
|
|
60
54
|
|
|
61
|
-
def _dids(self) -> Sequence[
|
|
62
|
-
return
|
|
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
|
+
)
|
|
63
61
|
|
|
64
62
|
def _files(
|
|
65
63
|
self,
|
|
66
64
|
root: Path,
|
|
67
65
|
dids: Optional[Sequence[Path]],
|
|
68
66
|
) -> Mapping[str, Sequence[Path]]:
|
|
67
|
+
|
|
69
68
|
files: Mapping[str, Sequence[Path]] = {}
|
|
69
|
+
|
|
70
70
|
for did in dids:
|
|
71
71
|
# Retrieve DDO from disk
|
|
72
72
|
file_path = root / Paths.DDOS / did
|
|
@@ -74,27 +74,30 @@ class EnvironmentLoader(Loader[JobDetails]):
|
|
|
74
74
|
raise FileNotFoundError(f"DDO file {file_path} does not exist")
|
|
75
75
|
|
|
76
76
|
with open(file_path, "r") as f:
|
|
77
|
-
|
|
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
|
+
|
|
78
83
|
for service in ddo[DidKeys.SERVICE]:
|
|
79
|
-
if service[DidKeys.SERVICE_TYPE]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
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]
|
|
89
93
|
)
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
)
|
|
95
|
+
]
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
return {}
|
|
97
|
+
return files
|
|
95
98
|
|
|
96
|
-
def _algorithm(self, root: Path) -> Algorithm:
|
|
97
|
-
did = self.
|
|
99
|
+
def _algorithm(self, root: Path) -> Optional[Algorithm]:
|
|
100
|
+
did = self._mapper.get(self._keys.ALGORITHM, None)
|
|
98
101
|
|
|
99
102
|
if not did:
|
|
100
103
|
return None
|
|
@@ -103,10 +106,7 @@ class EnvironmentLoader(Loader[JobDetails]):
|
|
|
103
106
|
if not ddo.exists():
|
|
104
107
|
raise FileNotFoundError(f"DDO file {ddo} does not exist")
|
|
105
108
|
|
|
106
|
-
return Algorithm(
|
|
107
|
-
did=did,
|
|
108
|
-
ddo=ddo,
|
|
109
|
-
)
|
|
109
|
+
return Algorithm(did, ddo)
|
|
110
110
|
|
|
111
|
-
def _secret(self) -> str:
|
|
112
|
-
return self.
|
|
111
|
+
def _secret(self) -> Optional[str]:
|
|
112
|
+
return self._mapper.get(self._keys.SECRET, None)
|
|
@@ -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"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|