mantatech-sdk 0.5b0.dev65__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.
- manta/__init__.light.py +22 -0
- manta/__init__.py +83 -0
- manta/__main__.py +21 -0
- manta/apis/__init__.py +7 -0
- manta/apis/async_user_api.py +6458 -0
- manta/apis/graph.py +498 -0
- manta/apis/module.py +316 -0
- manta/apis/results.py +251 -0
- manta/apis/swarm.py +206 -0
- manta/apis/user_api.py +1016 -0
- manta/cli/__init__.py +1 -0
- manta/cli/commands/__init__.py +1 -0
- manta/cli/commands/base_handler.py +229 -0
- manta/cli/commands/doc.py +192 -0
- manta/cli/commands/install.py +346 -0
- manta/cli/commands/sdk.py +9 -0
- manta/cli/commands/sdk_cluster.py +211 -0
- manta/cli/commands/sdk_config.py +347 -0
- manta/cli/commands/sdk_globals.py +280 -0
- manta/cli/commands/sdk_logs.py +174 -0
- manta/cli/commands/sdk_main.py +167 -0
- manta/cli/commands/sdk_module.py +516 -0
- manta/cli/commands/sdk_nodes.py +168 -0
- manta/cli/commands/sdk_original.py +3873 -0
- manta/cli/commands/sdk_results.py +265 -0
- manta/cli/commands/sdk_swarm.py +454 -0
- manta/cli/commands/sdk_user.py +234 -0
- manta/cli/commands/status.py +292 -0
- manta/cli/component_detector.py +112 -0
- manta/cli/config_manager.py +445 -0
- manta/cli/main.py +265 -0
- manta/cli/utils/__init__.py +27 -0
- manta/cli/utils/converters.py +140 -0
- manta/clients/cluster_management_client.py +486 -0
- manta/clients/local_client.py +149 -0
- manta/clients/module_management_client.py +217 -0
- manta/clients/swarm_management_client.py +562 -0
- manta/clients/user_management_client.py +395 -0
- manta/clients/world_client.py +195 -0
- manta/light/__init__.py +31 -0
- manta/light/globals.py +245 -0
- manta/light/local.py +407 -0
- manta/light/logging_config.py +39 -0
- manta/light/path.py +116 -0
- manta/light/results.py +236 -0
- manta/light/task.py +100 -0
- manta/light/utils.py +217 -0
- manta/light/world.py +177 -0
- mantatech_sdk-0.5b0.dev65.dist-info/METADATA +1039 -0
- mantatech_sdk-0.5b0.dev65.dist-info/RECORD +54 -0
- mantatech_sdk-0.5b0.dev65.dist-info/WHEEL +5 -0
- mantatech_sdk-0.5b0.dev65.dist-info/entry_points.txt +2 -0
- mantatech_sdk-0.5b0.dev65.dist-info/licenses/LICENSE +683 -0
- mantatech_sdk-0.5b0.dev65.dist-info/top_level.txt +1 -0
manta/apis/module.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import struct
|
|
7
|
+
import tempfile
|
|
8
|
+
import zipfile
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Optional, Union
|
|
12
|
+
|
|
13
|
+
from manta_common.build.common.tasks import MaximumType
|
|
14
|
+
from manta_common.build.common.tasks import Module as ModuleProto
|
|
15
|
+
from manta_common.build.common.tasks import Scheduling as SchedulingProto
|
|
16
|
+
from manta_common.build.core.user_services import ModuleRequest
|
|
17
|
+
|
|
18
|
+
# Import here to avoid circular imports
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def zip_module(python_program: Union[str, Path]) -> bytes:
|
|
22
|
+
"""
|
|
23
|
+
Zip multiple files into an executable python module
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
python_program : Union[str, Path]
|
|
28
|
+
Python program location
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
bytes
|
|
33
|
+
Zipped binary in memory
|
|
34
|
+
"""
|
|
35
|
+
buffer = io.BytesIO()
|
|
36
|
+
python_program = Path(python_program)
|
|
37
|
+
|
|
38
|
+
def nopycache(path: Path):
|
|
39
|
+
return "pycache" not in str(path)
|
|
40
|
+
|
|
41
|
+
with tempfile.TemporaryDirectory() as folder:
|
|
42
|
+
folder = Path(folder)
|
|
43
|
+
copy = shutil.copytree if python_program.is_dir() else shutil.copyfile
|
|
44
|
+
copy(python_program, folder / python_program.name)
|
|
45
|
+
|
|
46
|
+
# Create deterministic ZIP archive by manually setting fixed timestamps
|
|
47
|
+
# Fixed timestamp for deterministic ZIP creation (2023-01-01 00:00:00)
|
|
48
|
+
FIXED_TIMESTAMP = (2023, 1, 1, 0, 0, 0)
|
|
49
|
+
|
|
50
|
+
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
51
|
+
# Add all files from the source folder
|
|
52
|
+
for root, dirs, files in os.walk(folder):
|
|
53
|
+
# Filter out pycache directories
|
|
54
|
+
dirs[:] = [d for d in dirs if "pycache" not in d]
|
|
55
|
+
|
|
56
|
+
for file in files:
|
|
57
|
+
file_path = Path(root) / file
|
|
58
|
+
if nopycache(file_path):
|
|
59
|
+
# Calculate relative path for archive
|
|
60
|
+
arc_path = file_path.relative_to(folder)
|
|
61
|
+
|
|
62
|
+
# Create ZipInfo with fixed timestamp
|
|
63
|
+
zip_info = zipfile.ZipInfo(str(arc_path))
|
|
64
|
+
zip_info.date_time = FIXED_TIMESTAMP
|
|
65
|
+
zip_info.compress_type = zipfile.ZIP_DEFLATED
|
|
66
|
+
|
|
67
|
+
# Read file content and add to archive
|
|
68
|
+
with open(file_path, "rb") as src_file:
|
|
69
|
+
zf.writestr(zip_info, src_file.read())
|
|
70
|
+
|
|
71
|
+
# Add the __main__.py file for zipapp functionality
|
|
72
|
+
main_content = f"""# -*- coding: utf-8 -*-
|
|
73
|
+
import {python_program.stem}
|
|
74
|
+
{python_program.stem}.main()
|
|
75
|
+
"""
|
|
76
|
+
main_zip_info = zipfile.ZipInfo("__main__.py")
|
|
77
|
+
main_zip_info.date_time = FIXED_TIMESTAMP
|
|
78
|
+
main_zip_info.compress_type = zipfile.ZIP_DEFLATED
|
|
79
|
+
zf.writestr(main_zip_info, main_content.encode("utf-8"))
|
|
80
|
+
|
|
81
|
+
return buffer.getvalue()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class Module:
|
|
86
|
+
"""
|
|
87
|
+
Class which holds information about a `Module`
|
|
88
|
+
|
|
89
|
+
It helps the user to indicate which Nodes must executed a :class:`Module <manta.module.Module>`.
|
|
90
|
+
|
|
91
|
+
During the execution of a :class:`Swarm <manta.swarm.Swarm>`, the Manager selects nodes
|
|
92
|
+
depending on attributes set in this class.
|
|
93
|
+
|
|
94
|
+
For instance, :code:`Module(method="any", excepted_ids=["node_28"], maximum=0.8)`
|
|
95
|
+
indicates that the module will be executed on 80% of nodes excepted :code:`"node_28"`.
|
|
96
|
+
|
|
97
|
+
Attributes
|
|
98
|
+
----------
|
|
99
|
+
python_program : Union[str, Path]
|
|
100
|
+
python program code
|
|
101
|
+
image : str
|
|
102
|
+
image of the container in which the program will be executed
|
|
103
|
+
alias : Optional[str]
|
|
104
|
+
Alias task name. If specified, it is used to generate to a unique ID
|
|
105
|
+
network : Optional[str]
|
|
106
|
+
It indicates in which network the task will be executed.
|
|
107
|
+
Network name must exist in :code:`Swarm.networks`.
|
|
108
|
+
gpu : bool
|
|
109
|
+
If :code:`True`, the module will be executed on GPU
|
|
110
|
+
python_files : Dict[str, str]
|
|
111
|
+
Dictionary containing unzipped python files {filename: content}
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
python_program: Union[str, Path]
|
|
115
|
+
image: str
|
|
116
|
+
name: Optional[str] = field(default=None)
|
|
117
|
+
datasets: Optional[list] = field(default=None)
|
|
118
|
+
python_files: Dict[str, str] = field(default_factory=dict)
|
|
119
|
+
module_id: Optional[str] = field(default=None)
|
|
120
|
+
|
|
121
|
+
def __post_init__(self):
|
|
122
|
+
if self.name is None:
|
|
123
|
+
self.name = Path(self.python_program).stem
|
|
124
|
+
|
|
125
|
+
def to_proto(self):
|
|
126
|
+
"""Convert to protobuf object
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
Object
|
|
131
|
+
Protobuf object
|
|
132
|
+
"""
|
|
133
|
+
return ModuleProto(
|
|
134
|
+
python_program=zip_module(self.python_program),
|
|
135
|
+
image=self.image,
|
|
136
|
+
name=self.name or "",
|
|
137
|
+
datasets=self.datasets or [],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def __str__(self):
|
|
141
|
+
return self.name
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_proto(cls, proto: Union[ModuleProto, ModuleRequest]) -> "Module":
|
|
145
|
+
"""
|
|
146
|
+
Create a Module from a protobuf object
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
proto : Union[ModuleProto, ModuleRequest]
|
|
151
|
+
Protobuf module object
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
Module
|
|
156
|
+
Module object with unzipped python_files
|
|
157
|
+
"""
|
|
158
|
+
# Unzip the python_program bytes
|
|
159
|
+
python_files: Dict[str, str] = {}
|
|
160
|
+
if isinstance(proto, ModuleProto):
|
|
161
|
+
module = proto
|
|
162
|
+
module_id = ""
|
|
163
|
+
image = proto.image
|
|
164
|
+
name = proto.name
|
|
165
|
+
datasets = list(proto.datasets) if proto.datasets else None
|
|
166
|
+
|
|
167
|
+
elif isinstance(proto, ModuleRequest):
|
|
168
|
+
module = proto.module
|
|
169
|
+
module_id = proto.module_id
|
|
170
|
+
image = proto.module.image
|
|
171
|
+
name = proto.module.name
|
|
172
|
+
datasets = list(proto.module.datasets) if proto.module.datasets else None
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
with zipfile.ZipFile(io.BytesIO(module.python_program), "r") as zip_file:
|
|
176
|
+
for file_name in zip_file.namelist():
|
|
177
|
+
# Skip directories and __pycache__ files
|
|
178
|
+
if not file_name.endswith("/") and "__pycache__" not in file_name:
|
|
179
|
+
with zip_file.open(file_name) as file:
|
|
180
|
+
python_files[file_name] = file.read().decode("utf-8")
|
|
181
|
+
except zipfile.BadZipFile:
|
|
182
|
+
# If it's not a zip file, treat as raw python code
|
|
183
|
+
print(
|
|
184
|
+
f"Warning: {module.python_program} is not a zip file, treating as raw python code"
|
|
185
|
+
)
|
|
186
|
+
python_files["main.py"] = module.python_program.decode("utf-8")
|
|
187
|
+
|
|
188
|
+
return cls(
|
|
189
|
+
python_program="", # Empty since we have python_files
|
|
190
|
+
image=image,
|
|
191
|
+
name=name,
|
|
192
|
+
datasets=datasets,
|
|
193
|
+
python_files=python_files,
|
|
194
|
+
module_id=module_id,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def print_files(self):
|
|
198
|
+
"""
|
|
199
|
+
Print the module files in a readable format
|
|
200
|
+
"""
|
|
201
|
+
if self.python_files:
|
|
202
|
+
print(f"=== Module: {self.name} ===")
|
|
203
|
+
print(f"Image: {self.image}")
|
|
204
|
+
if self.datasets:
|
|
205
|
+
print(f"Datasets: {self.datasets}")
|
|
206
|
+
print(f"Files ({len(self.python_files)}):")
|
|
207
|
+
print("-" * 50)
|
|
208
|
+
|
|
209
|
+
for filename, content in self.python_files.items():
|
|
210
|
+
print(f"\n📁 {filename}")
|
|
211
|
+
print("-" * len(filename))
|
|
212
|
+
# Add line numbers for better readability
|
|
213
|
+
lines = content.split("\n")
|
|
214
|
+
for i, line in enumerate(lines, 1):
|
|
215
|
+
print(f"{i:3d} | {line}")
|
|
216
|
+
print("-" * 50)
|
|
217
|
+
else:
|
|
218
|
+
# Fallback to python_program if python_files is empty
|
|
219
|
+
print(f"=== Module: {self.name} ===")
|
|
220
|
+
print(f"Image: {self.image}")
|
|
221
|
+
if self.datasets:
|
|
222
|
+
print(f"Datasets: {self.datasets}")
|
|
223
|
+
print(f"Python program: {self.python_program}")
|
|
224
|
+
|
|
225
|
+
# Try to read and display the file if it's a path
|
|
226
|
+
if isinstance(self.python_program, (str, Path)):
|
|
227
|
+
try:
|
|
228
|
+
path = Path(self.python_program)
|
|
229
|
+
if path.exists():
|
|
230
|
+
print("-" * 50)
|
|
231
|
+
print(f"\n📁 {path.name}")
|
|
232
|
+
print("-" * len(path.name))
|
|
233
|
+
content = path.read_text(encoding="utf-8")
|
|
234
|
+
lines = content.split("\n")
|
|
235
|
+
for i, line in enumerate(lines, 1):
|
|
236
|
+
print(f"{i:3d} | {line}")
|
|
237
|
+
print("-" * 50)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
print(f"Could not read file: {e}")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass
|
|
243
|
+
class Scheduling:
|
|
244
|
+
"""
|
|
245
|
+
Class which holds scheduling information for a `Module`
|
|
246
|
+
|
|
247
|
+
Attributes
|
|
248
|
+
----------
|
|
249
|
+
method : str
|
|
250
|
+
Currently, "any" or "best_resources" or "tag-<your-tag>"
|
|
251
|
+
Note with tag, a result, associated to your tag, should
|
|
252
|
+
be available and sent by previous module
|
|
253
|
+
fixed : bool
|
|
254
|
+
if :code:`True`, the chosen nodes must remain the same
|
|
255
|
+
over the execution of the :class:`Swarm <manta.swarm.Swarm>`
|
|
256
|
+
excepted_ids : Optional[list]
|
|
257
|
+
List of ids that must not be chosen for this module
|
|
258
|
+
specified_ids : Optional[list]
|
|
259
|
+
List of ids that must be chosen for this module
|
|
260
|
+
maximum : Optional[Union[int, float]]
|
|
261
|
+
maximum of ids that can be chosen for this module.
|
|
262
|
+
If it is a float, it is a percentage between :code:`0.0` and :code:`1.0`
|
|
263
|
+
gpu : bool
|
|
264
|
+
If :code:`True`, the module will be executed on GPU
|
|
265
|
+
datasets : Optional[list]
|
|
266
|
+
List of datasets required for this module
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
method: str
|
|
270
|
+
fixed: bool
|
|
271
|
+
excepted_ids: Optional[list]
|
|
272
|
+
specified_ids: Optional[list]
|
|
273
|
+
maximum: Optional[Union[int, float]]
|
|
274
|
+
gpu: bool
|
|
275
|
+
|
|
276
|
+
def __post_init__(self):
|
|
277
|
+
if self.specified_ids is not None:
|
|
278
|
+
if self.excepted_ids is not None and not set(self.excepted_ids) & set(
|
|
279
|
+
self.specified_ids
|
|
280
|
+
):
|
|
281
|
+
raise ValueError("No coherence between specified ids and excepted ids")
|
|
282
|
+
offset = 0 if self.excepted_ids is None else len(self.excepted_ids)
|
|
283
|
+
if isinstance(self.maximum, int):
|
|
284
|
+
# Regulate maximum if maximum is over estimated
|
|
285
|
+
self.maximum = min(len(self.specified_ids) - offset, self.maximum)
|
|
286
|
+
|
|
287
|
+
def to_proto(self):
|
|
288
|
+
"""Convert to protobuf object
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
Object
|
|
293
|
+
Protobuf object
|
|
294
|
+
"""
|
|
295
|
+
# Serialize maximum value
|
|
296
|
+
if isinstance(self.maximum, float):
|
|
297
|
+
maximum = struct.pack("f", self.maximum)
|
|
298
|
+
max_type = MaximumType.FLOAT
|
|
299
|
+
elif isinstance(self.maximum, int):
|
|
300
|
+
maximum = struct.pack("i", self.maximum)
|
|
301
|
+
max_type = MaximumType.INT
|
|
302
|
+
elif self.maximum is None:
|
|
303
|
+
maximum = struct.pack("i", 0)
|
|
304
|
+
max_type = MaximumType.INT
|
|
305
|
+
else:
|
|
306
|
+
raise ValueError("Maximum must be a float or an int")
|
|
307
|
+
|
|
308
|
+
return SchedulingProto(
|
|
309
|
+
method=self.method,
|
|
310
|
+
fixed=self.fixed,
|
|
311
|
+
excepted_ids=self.excepted_ids or [],
|
|
312
|
+
specified_ids=self.specified_ids or [],
|
|
313
|
+
maximum=maximum,
|
|
314
|
+
max_type=max_type,
|
|
315
|
+
gpu=self.gpu,
|
|
316
|
+
)
|
manta/apis/results.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from functools import reduce
|
|
2
|
+
from operator import ior, itemgetter
|
|
3
|
+
from typing import Any, Dict, List, Tuple, Union
|
|
4
|
+
|
|
5
|
+
__all__ = ["Results"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_int(obj: Any) -> bool:
|
|
9
|
+
"""
|
|
10
|
+
Check if the :code:`obj` is an integer
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
obj : Any
|
|
15
|
+
Object input
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
bool
|
|
20
|
+
Is an integer
|
|
21
|
+
"""
|
|
22
|
+
return isinstance(obj, int)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Schema:
|
|
26
|
+
"""
|
|
27
|
+
This class represents the rows and columns of values from
|
|
28
|
+
results.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
values : List[Dict[str, Dict[str, Any]]]
|
|
33
|
+
List of values from results
|
|
34
|
+
|
|
35
|
+
Attributes
|
|
36
|
+
----------
|
|
37
|
+
unordered_rows : Set[str]
|
|
38
|
+
Node IDs
|
|
39
|
+
unordered_columns : Set[str]
|
|
40
|
+
Tags
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
__slots__ = ["unordered_rows", "unordered_columns"]
|
|
44
|
+
|
|
45
|
+
def __init__(self, values: List[Dict[str, Dict[str, Any]]]):
|
|
46
|
+
def ior_dict(dictn, next_item):
|
|
47
|
+
return ior(dictn, next_item.keys())
|
|
48
|
+
|
|
49
|
+
def reduce_ior_dict(dictn, next_item):
|
|
50
|
+
return reduce(ior_dict, next_item.values(), dictn)
|
|
51
|
+
|
|
52
|
+
self.unordered_rows = reduce(ior_dict, values, set())
|
|
53
|
+
self.unordered_columns = reduce(reduce_ior_dict, values, set())
|
|
54
|
+
|
|
55
|
+
def parse(self, values: Dict[str, Dict[str, Any]]) -> List[List[Any]]:
|
|
56
|
+
"""
|
|
57
|
+
Order results based on the schema values
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
values : Dict[str, Dict[str, Any]]
|
|
62
|
+
Input values for one iteration
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
List[List[Any]]
|
|
67
|
+
Ordered values
|
|
68
|
+
"""
|
|
69
|
+
return [
|
|
70
|
+
[values.get(row, {}).get(column, None) for column in self.columns]
|
|
71
|
+
for row in self.rows
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def rows(self) -> List[str]:
|
|
76
|
+
"""
|
|
77
|
+
Convenient to have sorted rows
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
List[str]
|
|
82
|
+
Sorted rows
|
|
83
|
+
"""
|
|
84
|
+
return sorted(self.unordered_rows)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def columns(self) -> List[str]:
|
|
88
|
+
"""
|
|
89
|
+
Convenient to have sorted columns
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
List[str]
|
|
94
|
+
Sorted columns
|
|
95
|
+
"""
|
|
96
|
+
return sorted(self.unordered_columns)
|
|
97
|
+
|
|
98
|
+
def __str__(self): # pragma: no cover
|
|
99
|
+
return f"Schema(rows={self.rows}, columns={self.columns})"
|
|
100
|
+
|
|
101
|
+
def __repr__(self): # pragma: no cover
|
|
102
|
+
return str(self)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Results:
|
|
106
|
+
"""
|
|
107
|
+
This class organizes the results gotten from the database
|
|
108
|
+
for ergonomic manipulations
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
values : List[Dict[str, Dict[str, Any]]]
|
|
113
|
+
Values organized by the class
|
|
114
|
+
|
|
115
|
+
Examples
|
|
116
|
+
--------
|
|
117
|
+
|
|
118
|
+
>>> values = [
|
|
119
|
+
... {
|
|
120
|
+
... "node_c": {"accuracy": 0.8, "params": [0.80, 0.84]},
|
|
121
|
+
... "node_a": {"accuracy": 0.7, "params": [0.61, 0.72]},
|
|
122
|
+
... "node_b": {"accuracy": 0.1, "params": [0.23, 0.37]},
|
|
123
|
+
... },
|
|
124
|
+
... {
|
|
125
|
+
... "node_c": {"accuracy": 0.1, "params": [0.53, 0.68]},
|
|
126
|
+
... "node_a": {"accuracy": 0.4, "params": [0.12, 0.90]},
|
|
127
|
+
... "node_b": {"accuracy": 0.9, "params": [0.11, 0.79]},
|
|
128
|
+
... },
|
|
129
|
+
... ]
|
|
130
|
+
>>> results = Results(values)
|
|
131
|
+
>>> results.schema.rows
|
|
132
|
+
["node_a", "node_b", "node_c"]
|
|
133
|
+
>>> results.schema.columns
|
|
134
|
+
["accuracy", "params"]
|
|
135
|
+
>>> results.iterations
|
|
136
|
+
[0, 1]
|
|
137
|
+
>>> results(0)
|
|
138
|
+
[[0.7, [0.61, 0.72]], [0.1, [0.23, 0.37]], [0.8, [0.8, 0.84]]]
|
|
139
|
+
>>> results("node_a")
|
|
140
|
+
[[0.7, [0.61, 0.72]], [0.4, [0.12, 0.9]]]
|
|
141
|
+
>>> results("accuracy")
|
|
142
|
+
[[0.7, 0.1, 0.8], [0.4, 0.9, 0.1]]
|
|
143
|
+
>>> results([0], "accuracy")
|
|
144
|
+
[[0.7, 0.1, 0.8]]
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
__slots__ = ["schema", "values"]
|
|
148
|
+
|
|
149
|
+
def __init__(self, values: List[Dict[str, Dict[str, Any]]]):
|
|
150
|
+
self.schema = Schema(values)
|
|
151
|
+
self.values = list(map(self.schema.parse, values))
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def iterations(self) -> List[int]:
|
|
155
|
+
"""
|
|
156
|
+
Return list of iterations
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
List[int]
|
|
161
|
+
Iterations
|
|
162
|
+
"""
|
|
163
|
+
return list(range(len(self.values)))
|
|
164
|
+
|
|
165
|
+
def sort_queries(
|
|
166
|
+
self,
|
|
167
|
+
indices: Tuple[List[int], List[int], List[int]],
|
|
168
|
+
query: Union[int, List[int], str],
|
|
169
|
+
) -> Tuple[List[int], List[int], List[int]]:
|
|
170
|
+
"""
|
|
171
|
+
Convert the query into index and add it to its corresponding
|
|
172
|
+
list of indices
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
indices : Tuple[List[int], List[int], List[int]],
|
|
177
|
+
Iteration indices, row indices, column indices
|
|
178
|
+
query : Union[int, List[int], str]
|
|
179
|
+
Query
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
Tuple[List[int], List[int], List[int]]
|
|
184
|
+
Updated iteration indices, row indices, column indices
|
|
185
|
+
|
|
186
|
+
Raises
|
|
187
|
+
------
|
|
188
|
+
TypeError
|
|
189
|
+
When query input is a list which not contains only integers
|
|
190
|
+
KeyError
|
|
191
|
+
When query can not be found in keys defined by :code:`self.values`
|
|
192
|
+
"""
|
|
193
|
+
it_indices, row_indices, column_indices = indices
|
|
194
|
+
if isinstance(query, int):
|
|
195
|
+
it_indices.append(query)
|
|
196
|
+
elif isinstance(query, list):
|
|
197
|
+
if not all(map(is_int, query)):
|
|
198
|
+
args = ", ".join(map(repr, query))
|
|
199
|
+
raise TypeError(
|
|
200
|
+
"Only integers in list queries are supported. "
|
|
201
|
+
f"Maybe try `results(..., {args})`."
|
|
202
|
+
)
|
|
203
|
+
it_indices.extend(query)
|
|
204
|
+
elif isinstance(query, str):
|
|
205
|
+
if query in self.schema.unordered_rows:
|
|
206
|
+
row_indices.append(self.schema.rows.index(query))
|
|
207
|
+
elif query in self.schema.unordered_columns:
|
|
208
|
+
column_indices.append(self.schema.columns.index(query))
|
|
209
|
+
else:
|
|
210
|
+
raise KeyError(f"Item {query!r} not found in schema.")
|
|
211
|
+
return (it_indices, row_indices, column_indices)
|
|
212
|
+
|
|
213
|
+
def __call__(self, *query: Union[str, int, List[int]]) -> list:
|
|
214
|
+
"""
|
|
215
|
+
Return a list filtered by queries and ordered by :code:`self.schema`
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
*query : Union[str, int, List[int]]
|
|
220
|
+
It can be an iteration, a list of iterations, a tag or a node ID
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
list
|
|
225
|
+
Filtered list of results
|
|
226
|
+
"""
|
|
227
|
+
if not query:
|
|
228
|
+
return self.values
|
|
229
|
+
it_indices, row_indices, column_indices = reduce(
|
|
230
|
+
self.sort_queries, query, ([], [], [])
|
|
231
|
+
)
|
|
232
|
+
values = itemgetter(*it_indices)(self.values) if it_indices else self.values
|
|
233
|
+
values = [values] if it_indices and (row_indices or column_indices) else values
|
|
234
|
+
values = map(itemgetter(*row_indices), values) if row_indices else values
|
|
235
|
+
values = [values] if row_indices and column_indices else values
|
|
236
|
+
values = (
|
|
237
|
+
(list(map(itemgetter(*column_indices), matrix)) for matrix in values)
|
|
238
|
+
if column_indices
|
|
239
|
+
else values
|
|
240
|
+
)
|
|
241
|
+
return list(values)
|
|
242
|
+
|
|
243
|
+
def __str__(self): # pragma: no cover
|
|
244
|
+
return (
|
|
245
|
+
f"Results(len(iterations)={len(self.iterations)},"
|
|
246
|
+
f" len(nodes)={len(self.schema.unordered_rows)}, "
|
|
247
|
+
f"len(tags)={len(self.schema.unordered_columns)})"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def __repr__(self): # pragma: no cover
|
|
251
|
+
return str(self)
|