data_pipeline_api 0.2.0__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.
- data_pipeline_api/file_api.py +181 -0
- data_pipeline_api/metadata.py +21 -0
- data_pipeline_api/metadata_store.py +37 -0
- data_pipeline_api/overrides.py +21 -0
- data_pipeline_api/simple_network_sim_api.py +13 -0
- data_pipeline_api/standard_api.py +136 -0
- data_pipeline_api-0.2.0.dist-info/LICENSE +29 -0
- data_pipeline_api-0.2.0.dist-info/METADATA +21 -0
- data_pipeline_api-0.2.0.dist-info/RECORD +11 -0
- data_pipeline_api-0.2.0.dist-info/WHEEL +5 -0
- data_pipeline_api-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from io import IOBase
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Union
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from hashlib import sha1
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from data_pipeline_api.metadata import Metadata, MetadataKey
|
|
10
|
+
from data_pipeline_api.metadata_store import MetadataStore
|
|
11
|
+
from data_pipeline_api.overrides import Overrides
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileAPI:
|
|
15
|
+
def __init__(
|
|
16
|
+
self, config_filename: Union[Path, str],
|
|
17
|
+
):
|
|
18
|
+
self._open_timestamp = datetime.now()
|
|
19
|
+
self._accesses = []
|
|
20
|
+
config_filename = Path(config_filename)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with open(config_filename) as config_file:
|
|
24
|
+
self._config = yaml.safe_load(config_file)
|
|
25
|
+
if "run_id" in self._config:
|
|
26
|
+
self.run_id = self._config["run_id"]
|
|
27
|
+
else:
|
|
28
|
+
# Compute a run_id.
|
|
29
|
+
with open(config_filename, "rb") as file:
|
|
30
|
+
m = sha1(file.read())
|
|
31
|
+
m.update(str(self._open_timestamp).encode())
|
|
32
|
+
self.run_id = m.hexdigest()
|
|
33
|
+
self.data_directory = Path(self._config.get("data_directory", "."))
|
|
34
|
+
self.fail_on_hash_mismatch = self._config.get(
|
|
35
|
+
"fail_on_hash_mismatch", True
|
|
36
|
+
)
|
|
37
|
+
self._read_overrides = Overrides(
|
|
38
|
+
(override.get("where", {}), override.get("use", {}))
|
|
39
|
+
for override in self._config.get("read", ())
|
|
40
|
+
)
|
|
41
|
+
self._write_overrides = Overrides(
|
|
42
|
+
(override.get("where", {}), override.get("use", {}))
|
|
43
|
+
for override in self._config.get("write", ())
|
|
44
|
+
)
|
|
45
|
+
access_log = self._config.get("access_log", "access-{run_id}.yaml")
|
|
46
|
+
if access_log is False:
|
|
47
|
+
self.access_log_path = None
|
|
48
|
+
else:
|
|
49
|
+
self.access_log_path = Path(access_log.format(run_id=self.run_id))
|
|
50
|
+
if not self.access_log_path.is_absolute():
|
|
51
|
+
self.access_log_path = (
|
|
52
|
+
config_filename.parent / self.access_log_path
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
except Exception as exception:
|
|
56
|
+
raise ValueError("could not parse config file") from exception
|
|
57
|
+
|
|
58
|
+
if self.data_directory.is_absolute():
|
|
59
|
+
self.normalised_data_directory = self.data_directory
|
|
60
|
+
else:
|
|
61
|
+
self.normalised_data_directory = (
|
|
62
|
+
config_filename.parent / self.data_directory
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
metadata_filename = self.normalised_data_directory / "metadata.yaml"
|
|
66
|
+
if metadata_filename.exists():
|
|
67
|
+
with open(metadata_filename) as metadata_store_file:
|
|
68
|
+
metadata_store = yaml.safe_load(metadata_store_file)
|
|
69
|
+
else:
|
|
70
|
+
metadata_store = {}
|
|
71
|
+
self._metadata_store = MetadataStore(metadata_store)
|
|
72
|
+
|
|
73
|
+
def _record_access(
|
|
74
|
+
self,
|
|
75
|
+
access_type: str,
|
|
76
|
+
call_metadata: Metadata,
|
|
77
|
+
access_metadata: Metadata,
|
|
78
|
+
path: Path,
|
|
79
|
+
verify_hash: bool,
|
|
80
|
+
):
|
|
81
|
+
access_metadata = access_metadata.copy()
|
|
82
|
+
with open(path, "rb") as file:
|
|
83
|
+
access_metadata[MetadataKey.calculated_hash] = sha1(file.read()).hexdigest()
|
|
84
|
+
if verify_hash:
|
|
85
|
+
if (
|
|
86
|
+
access_metadata[MetadataKey.calculated_hash]
|
|
87
|
+
!= access_metadata[MetadataKey.verified_hash]
|
|
88
|
+
):
|
|
89
|
+
raise ValueError(
|
|
90
|
+
(
|
|
91
|
+
"calculated hash {calculated_hash} != "
|
|
92
|
+
"verified hash {verified_hash}"
|
|
93
|
+
).format(**access_metadata)
|
|
94
|
+
)
|
|
95
|
+
self._accesses.append(
|
|
96
|
+
{
|
|
97
|
+
"type": access_type,
|
|
98
|
+
"timestamp": datetime.now(),
|
|
99
|
+
"call_metadata": call_metadata,
|
|
100
|
+
"access_metadata": access_metadata,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def open_for_read(self, **metadata) -> IOBase:
|
|
105
|
+
"""Return a file open for reading corresponding to the given metadata.
|
|
106
|
+
|
|
107
|
+
The file contents are hashed, and a record is made of the read.
|
|
108
|
+
"""
|
|
109
|
+
read_metadata = metadata.copy()
|
|
110
|
+
self._read_overrides.apply(read_metadata)
|
|
111
|
+
read_metadata = self._metadata_store.find(read_metadata) or read_metadata
|
|
112
|
+
try:
|
|
113
|
+
filename = Path(read_metadata[MetadataKey.filename])
|
|
114
|
+
except KeyError:
|
|
115
|
+
raise KeyError(f"could not find {MetadataKey.filename} in {read_metadata}")
|
|
116
|
+
path = self.normalised_data_directory / filename
|
|
117
|
+
self._record_access(
|
|
118
|
+
"read",
|
|
119
|
+
metadata,
|
|
120
|
+
read_metadata,
|
|
121
|
+
path,
|
|
122
|
+
verify_hash=self.fail_on_hash_mismatch,
|
|
123
|
+
)
|
|
124
|
+
return open(path, mode="rb")
|
|
125
|
+
|
|
126
|
+
def open_for_write(self, **metadata) -> IOBase:
|
|
127
|
+
"""Return a file open for update corresponding to the given metadata.
|
|
128
|
+
|
|
129
|
+
When the file is closed the file contents are hashed, and a record is made of
|
|
130
|
+
the write.
|
|
131
|
+
"""
|
|
132
|
+
write_metadata = metadata.copy()
|
|
133
|
+
self._write_overrides.apply(write_metadata)
|
|
134
|
+
if MetadataKey.filename not in write_metadata:
|
|
135
|
+
write_metadata[MetadataKey.filename] = str(
|
|
136
|
+
Path(write_metadata[MetadataKey.data_product])
|
|
137
|
+
/ "{}.{}".format(self.run_id, write_metadata[MetadataKey.extension],)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
path = self.normalised_data_directory / write_metadata[MetadataKey.filename]
|
|
141
|
+
if path.exists():
|
|
142
|
+
file = open(path, mode="r+b")
|
|
143
|
+
else:
|
|
144
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
file = open(path, mode="w+b")
|
|
146
|
+
# Wrap the file close method with something to record the file access.
|
|
147
|
+
close_file = file.close
|
|
148
|
+
|
|
149
|
+
@wraps(close_file)
|
|
150
|
+
def close():
|
|
151
|
+
file.flush()
|
|
152
|
+
self._record_access("write", metadata, write_metadata, path, False)
|
|
153
|
+
return close_file()
|
|
154
|
+
|
|
155
|
+
file.close = close
|
|
156
|
+
return file
|
|
157
|
+
|
|
158
|
+
def close(self):
|
|
159
|
+
"""Close the session and write the access log.
|
|
160
|
+
"""
|
|
161
|
+
if self.access_log_path:
|
|
162
|
+
with open(self.access_log_path, "w") as output_file:
|
|
163
|
+
yaml.dump(
|
|
164
|
+
{
|
|
165
|
+
"data_directory": str(self.data_directory),
|
|
166
|
+
"open_timestamp": self._open_timestamp,
|
|
167
|
+
"close_timestamp": datetime.now(),
|
|
168
|
+
"run_id": self.run_id,
|
|
169
|
+
"config": self._config,
|
|
170
|
+
"io": self._accesses,
|
|
171
|
+
},
|
|
172
|
+
output_file,
|
|
173
|
+
sort_keys=False,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def __enter__(self):
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
180
|
+
if exc_type is None:
|
|
181
|
+
self.close()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Mapping, Optional
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
Metadata = Mapping[str, str]
|
|
5
|
+
|
|
6
|
+
class MetadataKey:
|
|
7
|
+
filename = "filename"
|
|
8
|
+
data_product = "data_product"
|
|
9
|
+
extension = "extension"
|
|
10
|
+
run_id = "run_id"
|
|
11
|
+
version = "version"
|
|
12
|
+
verified_hash = "verified_hash"
|
|
13
|
+
calculated_hash = "calculated_hash"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_superset(metadataA: Metadata, metadataB: Metadata) -> bool:
|
|
17
|
+
"""Return True if metadataA is a superset of metadataB.
|
|
18
|
+
"""
|
|
19
|
+
return all(
|
|
20
|
+
key in metadataA and metadataA[key] == value for key, value in metadataB.items()
|
|
21
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Sequence, Optional, NamedTuple
|
|
2
|
+
from operator import attrgetter
|
|
3
|
+
from semver import VersionInfo
|
|
4
|
+
from data_pipeline_api.metadata import Metadata, MetadataKey, is_superset
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MetadataRecord(NamedTuple):
|
|
8
|
+
metadata: Metadata
|
|
9
|
+
version: Optional[VersionInfo]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MetadataStore:
|
|
13
|
+
def __init__(self, metadata_sequence: Sequence[Metadata]):
|
|
14
|
+
try:
|
|
15
|
+
self._metadata_records = tuple(
|
|
16
|
+
MetadataRecord(
|
|
17
|
+
metadata=metadata,
|
|
18
|
+
version=VersionInfo.parse(metadata[MetadataKey.version])
|
|
19
|
+
if MetadataKey.version in metadata
|
|
20
|
+
else None,
|
|
21
|
+
)
|
|
22
|
+
for metadata in metadata_sequence
|
|
23
|
+
)
|
|
24
|
+
except Exception as exception:
|
|
25
|
+
raise ValueError("invalid metadata") from exception
|
|
26
|
+
|
|
27
|
+
def find(self, metadata: Metadata) -> Optional[Metadata]:
|
|
28
|
+
try:
|
|
29
|
+
return max(
|
|
30
|
+
filter(
|
|
31
|
+
lambda record: is_superset(record.metadata, metadata),
|
|
32
|
+
self._metadata_records,
|
|
33
|
+
),
|
|
34
|
+
key=attrgetter("version"),
|
|
35
|
+
).metadata
|
|
36
|
+
except ValueError:
|
|
37
|
+
return None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Iterable, NamedTuple, Sequence, Tuple
|
|
2
|
+
from data_pipeline_api.metadata import Metadata, is_superset
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Override(NamedTuple):
|
|
6
|
+
where: Metadata
|
|
7
|
+
use: Metadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Overrides:
|
|
11
|
+
def __init__(self, overrides: Sequence[Tuple[Metadata, Metadata]]):
|
|
12
|
+
self._overrides = tuple(Override(*override) for override in overrides)
|
|
13
|
+
|
|
14
|
+
def find(self, metadata: Metadata) -> Iterable[Override]:
|
|
15
|
+
return filter(
|
|
16
|
+
lambda override: is_superset(metadata, override.where), self._overrides,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def apply(self, metadata: Metadata):
|
|
20
|
+
for override in self.find(metadata):
|
|
21
|
+
metadata.update(override.use)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from io import TextIOWrapper
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from pandas import read_csv, DataFrame
|
|
4
|
+
from data_pipeline_api.file_api import FileAPI
|
|
5
|
+
|
|
6
|
+
class SimpleNetworkSimAPI(FileAPI):
|
|
7
|
+
def read_table(self, data_product: str) -> DataFrame:
|
|
8
|
+
with TextIOWrapper(self.open_for_read(data_product=data_product, extension="csv")) as csv_file:
|
|
9
|
+
return read_csv(csv_file)
|
|
10
|
+
|
|
11
|
+
def write_table(self, data_product: str, value: DataFrame):
|
|
12
|
+
with TextIOWrapper(self.open_for_write(data_product=data_product, extension="csv")) as csv_file:
|
|
13
|
+
value.to_csv(csv_file, index=False)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from io import TextIOWrapper
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from data_pipeline_api.file_api import FileAPI
|
|
7
|
+
from data_pipeline_api.file_formats import parameter_file, object_file
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StandardAPI(FileAPI):
|
|
11
|
+
# ==================================================================================
|
|
12
|
+
# Parameter files
|
|
13
|
+
# ==================================================================================
|
|
14
|
+
|
|
15
|
+
@contextmanager
|
|
16
|
+
def open_parameter_file_for_read(self, data_product: str, component: str):
|
|
17
|
+
with TextIOWrapper(
|
|
18
|
+
self.open_for_read(data_product=data_product, component=component)
|
|
19
|
+
) as parameter_file:
|
|
20
|
+
yield parameter_file
|
|
21
|
+
|
|
22
|
+
@contextmanager
|
|
23
|
+
def open_parameter_file_for_write(self, data_product: str, component: str):
|
|
24
|
+
with TextIOWrapper(
|
|
25
|
+
self.open_for_write(
|
|
26
|
+
data_product=data_product, component=component, extension="toml"
|
|
27
|
+
)
|
|
28
|
+
) as parameter_file:
|
|
29
|
+
yield parameter_file
|
|
30
|
+
|
|
31
|
+
# ----------------------------------------------------------------------------------
|
|
32
|
+
# Estimate
|
|
33
|
+
# ----------------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
def read_estimate(
|
|
36
|
+
self, data_product: str, component: str
|
|
37
|
+
) -> parameter_file.Estimate:
|
|
38
|
+
with self.open_parameter_file_for_read(data_product, component) as file:
|
|
39
|
+
parameter_type = parameter_file.read_type(file, component)
|
|
40
|
+
if parameter_type is parameter_file.Type.POINT_ESTIMATE:
|
|
41
|
+
return parameter_file.read_estimate(file, component)
|
|
42
|
+
elif parameter_type is parameter_file.Type.DISTRIBUTION:
|
|
43
|
+
return parameter_file.read_distribution(file, component).mean()
|
|
44
|
+
elif parameter_type is parameter_file.Type.SAMPLES:
|
|
45
|
+
return parameter_file.read_samples(file, component).mean()
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(f"unrecognised type {parameter_type}")
|
|
48
|
+
|
|
49
|
+
def write_estimate(
|
|
50
|
+
self, data_product: str, component: str, estimate: parameter_file.Estimate
|
|
51
|
+
):
|
|
52
|
+
with self.open_parameter_file_for_write(data_product, component) as file:
|
|
53
|
+
parameter_file.write_estimate(file, component, estimate)
|
|
54
|
+
|
|
55
|
+
# ----------------------------------------------------------------------------------
|
|
56
|
+
# Distribution
|
|
57
|
+
# ----------------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
def read_distribution(
|
|
60
|
+
self, data_product: str, component: str
|
|
61
|
+
) -> parameter_file.Distribution:
|
|
62
|
+
with self.open_parameter_file_for_read(data_product, component) as file:
|
|
63
|
+
parameter_type = parameter_file.read_type(file, component)
|
|
64
|
+
if parameter_type is parameter_file.Type.POINT_ESTIMATE:
|
|
65
|
+
raise ValueError("point-estimate cannot be read as a distribution")
|
|
66
|
+
elif parameter_type is parameter_file.Type.DISTRIBUTION:
|
|
67
|
+
return parameter_file.read_distribution(file, component)
|
|
68
|
+
elif parameter_type is parameter_file.Type.SAMPLES:
|
|
69
|
+
raise ValueError("samples cannot be read as a distribution")
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"unrecognised type {parameter_type}")
|
|
72
|
+
|
|
73
|
+
def write_distribution(
|
|
74
|
+
self,
|
|
75
|
+
data_product: str,
|
|
76
|
+
component: str,
|
|
77
|
+
distribution: parameter_file.Distribution,
|
|
78
|
+
):
|
|
79
|
+
with self.open_parameter_file_for_write(data_product, component) as file:
|
|
80
|
+
parameter_file.write_distribution(file, component, distribution)
|
|
81
|
+
|
|
82
|
+
# ----------------------------------------------------------------------------------
|
|
83
|
+
# Samples
|
|
84
|
+
# ----------------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def read_sample(self, data_product: str, component: str) -> float:
|
|
87
|
+
with self.open_parameter_file_for_read(data_product, component) as file:
|
|
88
|
+
parameter_type = parameter_file.read_type(file, component)
|
|
89
|
+
if parameter_type is parameter_file.Type.POINT_ESTIMATE:
|
|
90
|
+
return parameter_file.read_estimate(file, component)
|
|
91
|
+
elif parameter_type is parameter_file.Type.DISTRIBUTION:
|
|
92
|
+
return parameter_file.read_distribution(file, component).rvs()
|
|
93
|
+
elif parameter_type is parameter_file.Type.SAMPLES:
|
|
94
|
+
return np.random.choice(parameter_file.read_samples(file, component))
|
|
95
|
+
else:
|
|
96
|
+
raise ValueError(f"unrecognised type {parameter_type}")
|
|
97
|
+
|
|
98
|
+
def write_samples(
|
|
99
|
+
self, data_product: str, component: str, samples: parameter_file.Samples
|
|
100
|
+
):
|
|
101
|
+
with self.open_parameter_file_for_write(data_product, component) as file:
|
|
102
|
+
parameter_file.write_samples(file, component, samples)
|
|
103
|
+
|
|
104
|
+
# ==================================================================================
|
|
105
|
+
# Object (hdf5) files
|
|
106
|
+
# ==================================================================================
|
|
107
|
+
|
|
108
|
+
@contextmanager
|
|
109
|
+
def open_object_file_for_read(self, data_product: str, component: str):
|
|
110
|
+
with self.open_for_read(
|
|
111
|
+
data_product=data_product, component=component
|
|
112
|
+
) as object_file:
|
|
113
|
+
yield object_file
|
|
114
|
+
|
|
115
|
+
@contextmanager
|
|
116
|
+
def open_object_file_for_write(self, data_product: str, component: str):
|
|
117
|
+
with self.open_for_write(
|
|
118
|
+
data_product=data_product, component=component, extension="h5"
|
|
119
|
+
) as object_file:
|
|
120
|
+
yield object_file
|
|
121
|
+
|
|
122
|
+
def read_table(self, data_product: str, component: str) -> object_file.Table:
|
|
123
|
+
with self.open_object_file_for_read(data_product, component) as file:
|
|
124
|
+
return object_file.read_table(file, component)
|
|
125
|
+
|
|
126
|
+
def write_table(self, data_product: str, component: str, table: object_file.Table):
|
|
127
|
+
with self.open_object_file_for_write(data_product, component) as file:
|
|
128
|
+
object_file.write_table(file, component, table)
|
|
129
|
+
|
|
130
|
+
def read_array(self, data_product: str, component: str) -> object_file.Array:
|
|
131
|
+
with self.open_object_file_for_read(data_product, component) as file:
|
|
132
|
+
return object_file.read_array(file, component)
|
|
133
|
+
|
|
134
|
+
def write_array(self, data_product: str, component: str, array: object_file.Array):
|
|
135
|
+
with self.open_object_file_for_write(data_product, component) as file:
|
|
136
|
+
object_file.write_array(file, component, array)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020, SCRC
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: data-pipeline-api
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Interacts with SCRC data pipeline
|
|
5
|
+
Home-page: UNKNOWN
|
|
6
|
+
Author: SCRC
|
|
7
|
+
Author-email: scrc@glasgow.ac.uk
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Requires-Dist: networkx (>=2.4)
|
|
11
|
+
Requires-Dist: matplotlib (>=3.1.3)
|
|
12
|
+
Requires-Dist: pandas (>=1.0.3)
|
|
13
|
+
Requires-Dist: toml (>=0.9.4)
|
|
14
|
+
Requires-Dist: h5py (>=2.9.0)
|
|
15
|
+
Requires-Dist: scipy (>=1.4.1)
|
|
16
|
+
Requires-Dist: pyyaml (>=5.3.1)
|
|
17
|
+
Requires-Dist: semver (>=2.9.0)
|
|
18
|
+
|
|
19
|
+
UNKNOWN
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
data_pipeline_api/file_api.py,sha256=dwFovDj-KhVa4jMHjn5FV_lhTLExAMZ_8cqqpdtZ_3s,6860
|
|
2
|
+
data_pipeline_api/metadata.py,sha256=k6Wb1UAouI_IGtB-IGTmMAIlW6B0HRbUFSIljP9wrL4,570
|
|
3
|
+
data_pipeline_api/metadata_store.py,sha256=DXRO2sscGsFpbSVioibjh0cM73qpuoh4mr4v_rfz7RU,1222
|
|
4
|
+
data_pipeline_api/overrides.py,sha256=GXd6SMWfN4mKLUflhJMuTf3w4xx7d7eJqZ2PQD8yyCM,666
|
|
5
|
+
data_pipeline_api/simple_network_sim_api.py,sha256=iKFzxLWTSc0dLIhHfu11UX-osyxO1RO4Kqq8cgP39Co,595
|
|
6
|
+
data_pipeline_api/standard_api.py,sha256=0CEgrn8t4CtY1hsy5gAk4as2UIvVr-QMCYTs7Fc-x58,6414
|
|
7
|
+
data_pipeline_api-0.2.0.dist-info/LICENSE,sha256=ok0qSVR1GQvE2voRomNyZVHlq2aoO_Bo6HCZGCZUeZM,1512
|
|
8
|
+
data_pipeline_api-0.2.0.dist-info/METADATA,sha256=qWB2CiLgppz2V21fR9tbt5zX_rhPDrTrCw_Y6aRY2HM,470
|
|
9
|
+
data_pipeline_api-0.2.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
10
|
+
data_pipeline_api-0.2.0.dist-info/top_level.txt,sha256=f2wi2a5FngfSqjkTh5YGJg8ty1JsV-GExu6qZOeqlac,18
|
|
11
|
+
data_pipeline_api-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
data_pipeline_api
|