manta-node 0.5b0.dev9__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_node/__init__.py +5 -0
- manta_node/__main__.py +57 -0
- manta_node/cli/__init__.py +6 -0
- manta_node/cli/commands/__init__.py +21 -0
- manta_node/cli/commands/cluster.py +419 -0
- manta_node/cli/commands/config.py +490 -0
- manta_node/cli/commands/logs.py +168 -0
- manta_node/cli/commands/start.py +459 -0
- manta_node/cli/commands/status.py +204 -0
- manta_node/cli/commands/stop.py +253 -0
- manta_node/cli/config_manager.py +139 -0
- manta_node/cli/main.py +133 -0
- manta_node/cli/version.py +106 -0
- manta_node/domain/__init__.py +8 -0
- manta_node/domain/task_lifecycle.py +83 -0
- manta_node/infrastructure/__init__.py +13 -0
- manta_node/infrastructure/config/__init__.py +31 -0
- manta_node/infrastructure/config/node_config_manager.py +918 -0
- manta_node/infrastructure/container/__init__.py +9 -0
- manta_node/infrastructure/container/docker_adapter.py +253 -0
- manta_node/infrastructure/container/manager.py +536 -0
- manta_node/infrastructure/filesystem/__init__.py +7 -0
- manta_node/infrastructure/filesystem/dataset_manager.py +469 -0
- manta_node/infrastructure/grpc/__init__.py +11 -0
- manta_node/infrastructure/grpc/client.py +373 -0
- manta_node/infrastructure/grpc/local_servicer.py +151 -0
- manta_node/infrastructure/grpc/world_servicer.py +284 -0
- manta_node/infrastructure/metrics/__init__.py +10 -0
- manta_node/infrastructure/metrics/collector.py +94 -0
- manta_node/infrastructure/metrics/metrics_collector.py +351 -0
- manta_node/infrastructure/mqtt/__init__.py +7 -0
- manta_node/infrastructure/mqtt/command_handler.py +315 -0
- manta_node/node_orchestrator.py +462 -0
- manta_node/task_manager.py +519 -0
- manta_node/tasks.py +272 -0
- manta_node/utils.py +52 -0
- manta_node-0.5b0.dev9.dist-info/METADATA +799 -0
- manta_node-0.5b0.dev9.dist-info/RECORD +42 -0
- manta_node-0.5b0.dev9.dist-info/WHEEL +5 -0
- manta_node-0.5b0.dev9.dist-info/entry_points.txt +2 -0
- manta_node-0.5b0.dev9.dist-info/licenses/LICENSE +683 -0
- manta_node-0.5b0.dev9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Generator, List, Optional
|
|
5
|
+
|
|
6
|
+
from manta_common.build.common.informations import DatasetOverviews, Overview
|
|
7
|
+
from manta_common.const import CHUNK_SIZE
|
|
8
|
+
from manta_common.errors import MantaSecurityError, MantaValidationError
|
|
9
|
+
from manta_common.traces import Tracer
|
|
10
|
+
|
|
11
|
+
__all__ = ["DatasetManager"]
|
|
12
|
+
|
|
13
|
+
# File type to dtype mapping
|
|
14
|
+
DTYPES = {
|
|
15
|
+
".txt": "text",
|
|
16
|
+
".csv": "csv",
|
|
17
|
+
".npx": "numpy",
|
|
18
|
+
".npy": "numpy",
|
|
19
|
+
".npz": "numpy",
|
|
20
|
+
".np": "numpy",
|
|
21
|
+
".jpg": "image",
|
|
22
|
+
".jpeg": "image",
|
|
23
|
+
".png": "image",
|
|
24
|
+
".tif": "image",
|
|
25
|
+
".tiff": "image",
|
|
26
|
+
".bin": "binary",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DatasetManager:
|
|
31
|
+
"""
|
|
32
|
+
Dataset Manager that handles parsing, retrieval, and metadata extraction
|
|
33
|
+
for datasets connected to a Node.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
|
|
38
|
+
Node ID
|
|
39
|
+
data_directories : Dict[str, str]
|
|
40
|
+
Dictionary mapping dataset names to folder paths
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, data_directories: Optional[Dict[str, str]] = None):
|
|
45
|
+
self.tracer = Tracer(getLogger(__name__))
|
|
46
|
+
self.data_directories = {
|
|
47
|
+
name: Path(path) for name, path in (data_directories or {}).items()
|
|
48
|
+
}
|
|
49
|
+
# Parse the datasets to extract metadata
|
|
50
|
+
self._dataset_overviews = self._parse_datasets()
|
|
51
|
+
|
|
52
|
+
self.tracer.info(
|
|
53
|
+
f"Dataset manager initialized with {len(self.data_directories)} datasets"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def update_datasets(self, data_directories: Dict[str, str]) -> None:
|
|
57
|
+
"""Update dataset mappings at runtime.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
data_directories : Dict[str, str]
|
|
62
|
+
New dictionary mapping dataset names to folder paths
|
|
63
|
+
"""
|
|
64
|
+
self.tracer.info("Updating dataset mappings")
|
|
65
|
+
|
|
66
|
+
# Convert paths to Path objects
|
|
67
|
+
new_directories = {name: Path(path) for name, path in data_directories.items()}
|
|
68
|
+
|
|
69
|
+
# Check what's added and removed
|
|
70
|
+
added = set(new_directories.keys()) - set(self.data_directories.keys())
|
|
71
|
+
removed = set(self.data_directories.keys()) - set(new_directories.keys())
|
|
72
|
+
|
|
73
|
+
# Update the directories
|
|
74
|
+
self.data_directories = new_directories
|
|
75
|
+
|
|
76
|
+
# Re-parse datasets to get updated metadata
|
|
77
|
+
self._dataset_overviews = self._parse_datasets()
|
|
78
|
+
|
|
79
|
+
if added:
|
|
80
|
+
self.tracer.info(f"Added datasets: {added}")
|
|
81
|
+
if removed:
|
|
82
|
+
self.tracer.info(f"Removed datasets: {removed}")
|
|
83
|
+
|
|
84
|
+
self.tracer.info(
|
|
85
|
+
f"Dataset manager updated with {len(self.data_directories)} datasets"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _parse_datasets(self) -> Dict[str, Overview]:
|
|
89
|
+
"""
|
|
90
|
+
Parse all datasets and extract metadata
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
Dict[str, Overview]
|
|
95
|
+
Dictionary mapping dataset names to their overviews
|
|
96
|
+
"""
|
|
97
|
+
result = {}
|
|
98
|
+
for name, path in self.data_directories.items():
|
|
99
|
+
result[name] = self._parse_dataset(path)
|
|
100
|
+
self.tracer.info(f"Parsed dataset '{name}' with : {result[name]}")
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
def _parse_dataset(self, folder_path: Path) -> Overview:
|
|
104
|
+
"""
|
|
105
|
+
Parse a single dataset folder and extract metadata for all files
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
folder_path : Path
|
|
110
|
+
Path to the dataset folder
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
Overview
|
|
115
|
+
Dataset overview
|
|
116
|
+
|
|
117
|
+
Raises
|
|
118
|
+
------
|
|
119
|
+
MantaValidationError
|
|
120
|
+
If the dataset path does not exist
|
|
121
|
+
"""
|
|
122
|
+
if not folder_path.exists():
|
|
123
|
+
raise MantaValidationError(
|
|
124
|
+
f"Dataset path does not exist: {folder_path}. "
|
|
125
|
+
"Please create the dataset directory or file before starting the node."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if folder_path.is_file():
|
|
129
|
+
return Overview(
|
|
130
|
+
name=folder_path.stem,
|
|
131
|
+
description=f"File in {folder_path.name}",
|
|
132
|
+
dtype=DTYPES.get(folder_path.suffix.lower(), "binary"),
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
return Overview(
|
|
136
|
+
name=folder_path.name,
|
|
137
|
+
description=f"Folder in {folder_path.name}",
|
|
138
|
+
dtype="folder",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def get_dataset_as_bytes(self, dataset_name: str) -> Generator[bytes, Any, None]:
|
|
142
|
+
"""
|
|
143
|
+
Get binary data from a file
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
dataset_name : str
|
|
148
|
+
Name of the dataset
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
Generator[bytes, Any, None]
|
|
153
|
+
Generator yielding binary data from the file
|
|
154
|
+
|
|
155
|
+
Raises
|
|
156
|
+
------
|
|
157
|
+
FileNotFoundError
|
|
158
|
+
If the file or dataset doesn't exist
|
|
159
|
+
ValueError
|
|
160
|
+
If the dataset is a folder
|
|
161
|
+
"""
|
|
162
|
+
if dataset_name not in self._dataset_overviews:
|
|
163
|
+
raise FileNotFoundError(f"Dataset '{dataset_name}' not found")
|
|
164
|
+
|
|
165
|
+
if self._dataset_overviews[dataset_name].dtype == "folder":
|
|
166
|
+
raise ValueError(f"Dataset '{dataset_name}' is a folder")
|
|
167
|
+
elif self._dataset_overviews[dataset_name].dtype in [
|
|
168
|
+
"binary",
|
|
169
|
+
"csv",
|
|
170
|
+
"numpy",
|
|
171
|
+
"image",
|
|
172
|
+
"text",
|
|
173
|
+
]:
|
|
174
|
+
return self.get_file_chunks(self.data_directories[dataset_name])
|
|
175
|
+
|
|
176
|
+
raise FileNotFoundError(f"Unknown dataset type for '{dataset_name}'")
|
|
177
|
+
|
|
178
|
+
def read_file_lines(
|
|
179
|
+
self,
|
|
180
|
+
dataset_name: str,
|
|
181
|
+
file_path: str,
|
|
182
|
+
encoding: str = "utf-8",
|
|
183
|
+
errors: str = "strict",
|
|
184
|
+
newline: str = "\n",
|
|
185
|
+
) -> Generator[bytes, Any, None]:
|
|
186
|
+
"""
|
|
187
|
+
Read the lines of a file
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
dataset_name : str
|
|
192
|
+
Name of the dataset
|
|
193
|
+
file_path : str
|
|
194
|
+
Path to the file within the dataset
|
|
195
|
+
encoding : str, optional
|
|
196
|
+
Text encoding, by default "utf-8"
|
|
197
|
+
errors : str, optional
|
|
198
|
+
How to handle encoding errors, by default "strict"
|
|
199
|
+
newline : str, optional
|
|
200
|
+
Line ending character, by default "\n"
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
Generator[bytes, Any, None]
|
|
205
|
+
Generator yielding lines as bytes
|
|
206
|
+
|
|
207
|
+
Raises
|
|
208
|
+
------
|
|
209
|
+
FileNotFoundError
|
|
210
|
+
If the file or dataset doesn't exist
|
|
211
|
+
"""
|
|
212
|
+
if not self.check_exists(dataset_name, file_path):
|
|
213
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
214
|
+
full_path = self.get_dataset_file_path(dataset_name, file_path)
|
|
215
|
+
with full_path.open(
|
|
216
|
+
mode="r", encoding=encoding, errors=errors, newline=newline
|
|
217
|
+
) as f:
|
|
218
|
+
for line in f:
|
|
219
|
+
yield line.encode()
|
|
220
|
+
|
|
221
|
+
def get_file_chunks(self, file_path: Path) -> Generator[bytes, Any, None]:
|
|
222
|
+
"""
|
|
223
|
+
Get binary data from a file in chunks
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
file_path : Path
|
|
228
|
+
Path to the file within the dataset
|
|
229
|
+
|
|
230
|
+
Yields
|
|
231
|
+
------
|
|
232
|
+
bytes
|
|
233
|
+
Chunks of binary data
|
|
234
|
+
|
|
235
|
+
Raises
|
|
236
|
+
------
|
|
237
|
+
FileNotFoundError
|
|
238
|
+
If the file or dataset doesn't exist
|
|
239
|
+
"""
|
|
240
|
+
if not file_path.exists():
|
|
241
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
242
|
+
with file_path.open("rb") as f:
|
|
243
|
+
while chunk := f.read(CHUNK_SIZE):
|
|
244
|
+
yield chunk
|
|
245
|
+
|
|
246
|
+
def _validate_file_path(self, file_path: str) -> str:
|
|
247
|
+
"""
|
|
248
|
+
Validate and sanitize file paths to prevent directory traversal.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
file_path : str
|
|
253
|
+
The file path to validate
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
str
|
|
258
|
+
The validated and sanitized file path
|
|
259
|
+
|
|
260
|
+
Raises
|
|
261
|
+
------
|
|
262
|
+
MantaNodeError
|
|
263
|
+
If the path is invalid or contains directory traversal attempts
|
|
264
|
+
"""
|
|
265
|
+
if not file_path:
|
|
266
|
+
return file_path
|
|
267
|
+
|
|
268
|
+
# Normalize the path to handle redundant separators and up-level references
|
|
269
|
+
normalized = os.path.normpath(file_path)
|
|
270
|
+
|
|
271
|
+
# Check for directory traversal attempts
|
|
272
|
+
if normalized.startswith("..") or os.path.isabs(normalized):
|
|
273
|
+
self.tracer.warning(
|
|
274
|
+
f"Security violation detected: directory traversal attempt in path: {file_path}"
|
|
275
|
+
)
|
|
276
|
+
raise MantaSecurityError(
|
|
277
|
+
f"Invalid file path - directory traversal attempt: {file_path}"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Additional security: check for null bytes and other suspicious patterns
|
|
281
|
+
if "\x00" in normalized:
|
|
282
|
+
self.tracer.warning(
|
|
283
|
+
f"Security violation detected: null bytes in path: {file_path}"
|
|
284
|
+
)
|
|
285
|
+
raise MantaSecurityError(
|
|
286
|
+
f"Invalid file path - contains null bytes: {file_path}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return normalized
|
|
290
|
+
|
|
291
|
+
def get_dataset_file_path(self, dataset_name: str, file_path: str) -> Path:
|
|
292
|
+
"""
|
|
293
|
+
Get the full path to a dataset file with security validation
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
dataset_name : str
|
|
298
|
+
Name of the dataset
|
|
299
|
+
file_path : str
|
|
300
|
+
Path to the file within the dataset
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
Path
|
|
305
|
+
Full path to the file
|
|
306
|
+
|
|
307
|
+
Raises
|
|
308
|
+
------
|
|
309
|
+
FileNotFoundError
|
|
310
|
+
If the file or dataset doesn't exist
|
|
311
|
+
MantaNodeError
|
|
312
|
+
If the path contains directory traversal attempts
|
|
313
|
+
"""
|
|
314
|
+
if root := self.data_directories.get(dataset_name):
|
|
315
|
+
# Validate and sanitize the file path
|
|
316
|
+
validated_path = self._validate_file_path(file_path)
|
|
317
|
+
|
|
318
|
+
# Construct the full path
|
|
319
|
+
full_path = root / validated_path
|
|
320
|
+
|
|
321
|
+
# Ensure the resolved path is within the dataset directory
|
|
322
|
+
try:
|
|
323
|
+
# Resolve both paths to absolute paths to check containment
|
|
324
|
+
resolved_full = full_path.resolve()
|
|
325
|
+
resolved_root = root.resolve()
|
|
326
|
+
|
|
327
|
+
# Check if the resolved path is within the dataset directory
|
|
328
|
+
resolved_full.relative_to(resolved_root)
|
|
329
|
+
except ValueError:
|
|
330
|
+
raise MantaSecurityError(
|
|
331
|
+
f"Path traversal attempt detected: {file_path} resolves outside dataset directory"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return full_path
|
|
335
|
+
raise FileNotFoundError(f"Dataset '{dataset_name}' not found")
|
|
336
|
+
|
|
337
|
+
def list_files(
|
|
338
|
+
self, dataset_name: str, folder_path: str = ""
|
|
339
|
+
) -> List[Dict[str, Any]]:
|
|
340
|
+
"""
|
|
341
|
+
List files in a dataset folder with security validation
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
dataset_name : str
|
|
346
|
+
Name of the dataset
|
|
347
|
+
folder_path : str, optional
|
|
348
|
+
Path to the folder within the dataset, by default ""
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
List[Dict[str, Any]]
|
|
353
|
+
List of file information including name and whether it's a file or folder
|
|
354
|
+
|
|
355
|
+
Raises
|
|
356
|
+
------
|
|
357
|
+
FileNotFoundError
|
|
358
|
+
If the folder or dataset doesn't exist
|
|
359
|
+
MantaNodeError
|
|
360
|
+
If the path contains directory traversal attempts
|
|
361
|
+
"""
|
|
362
|
+
if root := self.data_directories.get(dataset_name):
|
|
363
|
+
# Validate and sanitize the folder path
|
|
364
|
+
validated_folder_path = self._validate_file_path(folder_path)
|
|
365
|
+
|
|
366
|
+
# Construct the directory path
|
|
367
|
+
dir_path = root / validated_folder_path
|
|
368
|
+
|
|
369
|
+
# Ensure the resolved path is within the dataset directory
|
|
370
|
+
try:
|
|
371
|
+
resolved_dir = dir_path.resolve()
|
|
372
|
+
resolved_root = root.resolve()
|
|
373
|
+
resolved_dir.relative_to(resolved_root)
|
|
374
|
+
except ValueError:
|
|
375
|
+
raise MantaSecurityError(
|
|
376
|
+
f"Path traversal attempt detected: {folder_path} resolves outside dataset directory"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if not dir_path.exists():
|
|
380
|
+
raise FileNotFoundError(f"Folder not found: {dir_path}")
|
|
381
|
+
return [
|
|
382
|
+
{"name": d.name, "is_file": d.is_file()} for d in dir_path.iterdir()
|
|
383
|
+
]
|
|
384
|
+
raise FileNotFoundError(f"Dataset '{dataset_name}' not found")
|
|
385
|
+
|
|
386
|
+
def check_exists(self, dataset_name: str, file_path: str) -> bool:
|
|
387
|
+
"""
|
|
388
|
+
Check if a file exists in a dataset with security validation
|
|
389
|
+
|
|
390
|
+
Parameters
|
|
391
|
+
----------
|
|
392
|
+
dataset_name : str
|
|
393
|
+
Name of the dataset
|
|
394
|
+
file_path : str
|
|
395
|
+
Path to the file within the dataset
|
|
396
|
+
|
|
397
|
+
Returns
|
|
398
|
+
-------
|
|
399
|
+
bool
|
|
400
|
+
True if the file exists, False otherwise
|
|
401
|
+
"""
|
|
402
|
+
try:
|
|
403
|
+
full_path = self.get_dataset_file_path(dataset_name, file_path)
|
|
404
|
+
return full_path.exists()
|
|
405
|
+
except (FileNotFoundError, MantaSecurityError, MantaValidationError):
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
def to_proto(self) -> DatasetOverviews:
|
|
409
|
+
"""
|
|
410
|
+
Convert all dataset overviews to a proto format
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
DatasetOverviews
|
|
415
|
+
List of all dataset overviews
|
|
416
|
+
"""
|
|
417
|
+
self.tracer.info(f"Dataset overviews: {self._dataset_overviews}")
|
|
418
|
+
return DatasetOverviews(
|
|
419
|
+
content=list(self._dataset_overviews.values()),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def validate_datasets(self) -> List[str]:
|
|
423
|
+
"""
|
|
424
|
+
Validate that all configured dataset paths exist and are accessible.
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
List[str]
|
|
429
|
+
List of validation issues (empty if all datasets are valid)
|
|
430
|
+
"""
|
|
431
|
+
issues = []
|
|
432
|
+
|
|
433
|
+
for dataset_name, dataset_path in self.data_directories.items():
|
|
434
|
+
# Expand user paths (e.g., ~/)
|
|
435
|
+
expanded_path = dataset_path.expanduser()
|
|
436
|
+
|
|
437
|
+
# Check if the path exists
|
|
438
|
+
if not expanded_path.exists():
|
|
439
|
+
issues.append(
|
|
440
|
+
f"Dataset '{dataset_name}' path does not exist: {dataset_path}"
|
|
441
|
+
)
|
|
442
|
+
continue
|
|
443
|
+
|
|
444
|
+
# Check if the path is readable (optimized using os.access)
|
|
445
|
+
try:
|
|
446
|
+
# Verify path type
|
|
447
|
+
if not (expanded_path.is_file() or expanded_path.is_dir()):
|
|
448
|
+
issues.append(
|
|
449
|
+
f"Dataset '{dataset_name}' path is neither a file nor a directory: {dataset_path}"
|
|
450
|
+
)
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
# Use os.access for efficient permission check (no I/O overhead)
|
|
454
|
+
if not os.access(expanded_path, os.R_OK):
|
|
455
|
+
issues.append(
|
|
456
|
+
f"Dataset '{dataset_name}' is not readable (permission denied): {dataset_path}"
|
|
457
|
+
)
|
|
458
|
+
except Exception as e:
|
|
459
|
+
issues.append(
|
|
460
|
+
f"Dataset '{dataset_name}' access error: {dataset_path} - {e}"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return issues
|
|
464
|
+
|
|
465
|
+
def refresh(self):
|
|
466
|
+
"""
|
|
467
|
+
Refresh the dataset metadata
|
|
468
|
+
"""
|
|
469
|
+
self._dataset_overviews = self._parse_datasets()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""gRPC infrastructure - Client and server implementations."""
|
|
2
|
+
|
|
3
|
+
from .client import NodeClient
|
|
4
|
+
from .local_servicer import LocalServicer
|
|
5
|
+
from .world_servicer import WorldServicer
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"NodeClient",
|
|
9
|
+
"LocalServicer",
|
|
10
|
+
"WorldServicer",
|
|
11
|
+
]
|