tracdap-runtime 0.6.4__py3-none-any.whl → 0.6.5__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.
- tracdap/rt/_exec/context.py +382 -29
- tracdap/rt/_exec/dev_mode.py +123 -94
- tracdap/rt/_exec/engine.py +120 -9
- tracdap/rt/_exec/functions.py +125 -20
- tracdap/rt/_exec/graph.py +38 -13
- tracdap/rt/_exec/graph_builder.py +120 -9
- tracdap/rt/_impl/data.py +115 -49
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +74 -30
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +120 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +12 -10
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +29 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.pyi +16 -0
- tracdap/rt/_impl/models.py +8 -0
- tracdap/rt/_impl/static_api.py +16 -0
- tracdap/rt/_impl/storage.py +37 -25
- tracdap/rt/_impl/validation.py +76 -7
- tracdap/rt/_plugins/repo_git.py +1 -1
- tracdap/rt/_version.py +1 -1
- tracdap/rt/api/experimental.py +220 -0
- tracdap/rt/api/hook.py +4 -0
- tracdap/rt/api/model_api.py +48 -6
- tracdap/rt/config/__init__.py +2 -2
- tracdap/rt/config/common.py +6 -0
- tracdap/rt/metadata/__init__.py +25 -20
- tracdap/rt/metadata/job.py +54 -0
- tracdap/rt/metadata/model.py +18 -0
- tracdap/rt/metadata/resource.py +24 -0
- {tracdap_runtime-0.6.4.dist-info → tracdap_runtime-0.6.5.dist-info}/METADATA +3 -1
- {tracdap_runtime-0.6.4.dist-info → tracdap_runtime-0.6.5.dist-info}/RECORD +33 -29
- {tracdap_runtime-0.6.4.dist-info → tracdap_runtime-0.6.5.dist-info}/LICENSE +0 -0
- {tracdap_runtime-0.6.4.dist-info → tracdap_runtime-0.6.5.dist-info}/WHEEL +0 -0
- {tracdap_runtime-0.6.4.dist-info → tracdap_runtime-0.6.5.dist-info}/top_level.txt +0 -0
tracdap/rt/_exec/context.py
CHANGED
@@ -19,13 +19,13 @@ import typing as tp
|
|
19
19
|
import re
|
20
20
|
import traceback
|
21
21
|
|
22
|
-
import pandas as pd
|
23
|
-
|
24
22
|
import tracdap.rt.api as _api
|
23
|
+
import tracdap.rt.api.experimental as _eapi
|
25
24
|
import tracdap.rt.metadata as _meta
|
26
25
|
import tracdap.rt.exceptions as _ex
|
27
26
|
import tracdap.rt._impl.type_system as _types # noqa
|
28
27
|
import tracdap.rt._impl.data as _data # noqa
|
28
|
+
import tracdap.rt._impl.storage as _storage # noqa
|
29
29
|
import tracdap.rt._impl.util as _util # noqa
|
30
30
|
import tracdap.rt._impl.validation as _val # noqa
|
31
31
|
|
@@ -61,6 +61,7 @@ class TracContextImpl(_api.TracContext):
|
|
61
61
|
model_def: _meta.ModelDefinition,
|
62
62
|
model_class: _api.TracModel.__class__,
|
63
63
|
local_ctx: tp.Dict[str, tp.Any],
|
64
|
+
dynamic_outputs: tp.List[str] = None,
|
64
65
|
checkout_directory: pathlib.Path = None):
|
65
66
|
|
66
67
|
self.__ctx_log = _util.logger_for_object(self)
|
@@ -68,12 +69,14 @@ class TracContextImpl(_api.TracContext):
|
|
68
69
|
|
69
70
|
self.__model_def = model_def
|
70
71
|
self.__model_class = model_class
|
71
|
-
self.__local_ctx = local_ctx
|
72
|
+
self.__local_ctx = local_ctx if local_ctx is not None else {}
|
73
|
+
self.__dynamic_outputs = dynamic_outputs if dynamic_outputs is not None else []
|
72
74
|
|
73
75
|
self.__val = TracContextValidator(
|
74
76
|
self.__ctx_log,
|
75
77
|
self.__model_def,
|
76
78
|
self.__local_ctx,
|
79
|
+
self.__dynamic_outputs,
|
77
80
|
checkout_directory)
|
78
81
|
|
79
82
|
def get_parameter(self, parameter_name: str) -> tp.Any:
|
@@ -131,10 +134,45 @@ class TracContextImpl(_api.TracContext):
|
|
131
134
|
else:
|
132
135
|
return copy.deepcopy(data_view.trac_schema)
|
133
136
|
|
134
|
-
def
|
137
|
+
def get_table(self, dataset_name: str, framework, **kwargs) -> _eapi._DATA_FRAMEWORK: # noqa
|
138
|
+
|
139
|
+
# Support the experimental API data framework syntax
|
140
|
+
|
141
|
+
if framework == _eapi.PANDAS:
|
142
|
+
return self.get_pandas_table(dataset_name, **kwargs)
|
143
|
+
elif framework == _eapi.POLARS:
|
144
|
+
return self.get_polars_table(dataset_name)
|
145
|
+
else:
|
146
|
+
raise _ex.ERuntimeValidation(f"Unsupported data framework [{framework}]")
|
147
|
+
|
148
|
+
def get_pandas_table(self, dataset_name: str, use_temporal_objects: tp.Optional[bool] = None) \
|
149
|
+
-> "_data.pandas.DataFrame":
|
135
150
|
|
151
|
+
_val.require_package("pandas", _data.pandas)
|
136
152
|
_val.validate_signature(self.get_pandas_table, dataset_name, use_temporal_objects)
|
137
153
|
|
154
|
+
data_view, schema = self.__get_data_view(dataset_name)
|
155
|
+
part_key = _data.DataPartKey.for_root()
|
156
|
+
|
157
|
+
if use_temporal_objects is None:
|
158
|
+
use_temporal_objects = self.__DEFAULT_TEMPORAL_OBJECTS
|
159
|
+
|
160
|
+
return _data.DataMapping.view_to_pandas(data_view, part_key, schema, use_temporal_objects)
|
161
|
+
|
162
|
+
def get_polars_table(self, dataset_name: str) -> "_data.polars.DataFrame":
|
163
|
+
|
164
|
+
_val.require_package("polars", _data.polars)
|
165
|
+
_val.validate_signature(self.get_polars_table, dataset_name)
|
166
|
+
|
167
|
+
data_view, schema = self.__get_data_view(dataset_name)
|
168
|
+
part_key = _data.DataPartKey.for_root()
|
169
|
+
|
170
|
+
return _data.DataMapping.view_to_polars(data_view, part_key, schema)
|
171
|
+
|
172
|
+
def __get_data_view(self, dataset_name: str):
|
173
|
+
|
174
|
+
_val.validate_signature(self.__get_data_view, dataset_name)
|
175
|
+
|
138
176
|
self.__val.check_dataset_valid_identifier(dataset_name)
|
139
177
|
self.__val.check_dataset_defined_in_model(dataset_name)
|
140
178
|
self.__val.check_dataset_available_in_context(dataset_name)
|
@@ -155,10 +193,7 @@ class TracContextImpl(_api.TracContext):
|
|
155
193
|
else:
|
156
194
|
schema = data_view.arrow_schema
|
157
195
|
|
158
|
-
|
159
|
-
use_temporal_objects = self.__DEFAULT_TEMPORAL_OBJECTS
|
160
|
-
|
161
|
-
return _data.DataMapping.view_to_pandas(data_view, part_key, schema, use_temporal_objects)
|
196
|
+
return data_view, schema
|
162
197
|
|
163
198
|
def put_schema(self, dataset_name: str, schema: _meta.SchemaDefinition):
|
164
199
|
|
@@ -190,17 +225,57 @@ class TracContextImpl(_api.TracContext):
|
|
190
225
|
|
191
226
|
self.__local_ctx[dataset_name] = updated_view
|
192
227
|
|
193
|
-
def
|
228
|
+
def put_table(self, dataset_name: str, dataset: _eapi._DATA_FRAMEWORK, **kwargs): # noqa
|
229
|
+
|
230
|
+
# Support the experimental API data framework syntax
|
194
231
|
|
232
|
+
if _data.pandas and isinstance(dataset, _data.pandas.DataFrame):
|
233
|
+
self.put_pandas_table(dataset_name, dataset)
|
234
|
+
elif _data.polars and isinstance(dataset, _data.polars.DataFrame):
|
235
|
+
self.put_polars_table(dataset_name, dataset)
|
236
|
+
else:
|
237
|
+
raise _ex.ERuntimeValidation(f"Unsupported data framework[{type(dataset)}]")
|
238
|
+
|
239
|
+
def put_pandas_table(self, dataset_name: str, dataset: "_data.pandas.DataFrame"):
|
240
|
+
|
241
|
+
_val.require_package("pandas", _data.pandas)
|
195
242
|
_val.validate_signature(self.put_pandas_table, dataset_name, dataset)
|
196
243
|
|
244
|
+
part_key = _data.DataPartKey.for_root()
|
245
|
+
data_view, schema = self.__put_data_view(dataset_name, part_key, dataset, _data.pandas.DataFrame)
|
246
|
+
|
247
|
+
# Data conformance is applied inside these conversion functions
|
248
|
+
|
249
|
+
updated_item = _data.DataMapping.pandas_to_item(dataset, schema)
|
250
|
+
updated_view = _data.DataMapping.add_item_to_view(data_view, part_key, updated_item)
|
251
|
+
|
252
|
+
self.__local_ctx[dataset_name] = updated_view
|
253
|
+
|
254
|
+
def put_polars_table(self, dataset_name: str, dataset: "_data.polars.DataFrame"):
|
255
|
+
|
256
|
+
_val.require_package("polars", _data.polars)
|
257
|
+
_val.validate_signature(self.put_polars_table, dataset_name, dataset)
|
258
|
+
|
259
|
+
part_key = _data.DataPartKey.for_root()
|
260
|
+
data_view, schema = self.__put_data_view(dataset_name, part_key, dataset, _data.polars.DataFrame)
|
261
|
+
|
262
|
+
# Data conformance is applied inside these conversion functions
|
263
|
+
|
264
|
+
updated_item = _data.DataMapping.polars_to_item(dataset, schema)
|
265
|
+
updated_view = _data.DataMapping.add_item_to_view(data_view, part_key, updated_item)
|
266
|
+
|
267
|
+
self.__local_ctx[dataset_name] = updated_view
|
268
|
+
|
269
|
+
def __put_data_view(self, dataset_name: str, part_key: _data.DataPartKey, dataset: tp.Any, framework: type):
|
270
|
+
|
271
|
+
_val.validate_signature(self.__put_data_view, dataset_name, part_key, dataset, framework)
|
272
|
+
|
197
273
|
self.__val.check_dataset_valid_identifier(dataset_name)
|
198
274
|
self.__val.check_dataset_is_model_output(dataset_name)
|
199
|
-
self.__val.check_provided_dataset_type(dataset,
|
275
|
+
self.__val.check_provided_dataset_type(dataset, framework)
|
200
276
|
|
201
277
|
static_schema = self.__get_static_schema(self.__model_def, dataset_name)
|
202
278
|
data_view = self.__local_ctx.get(dataset_name)
|
203
|
-
part_key = _data.DataPartKey.for_root()
|
204
279
|
|
205
280
|
if data_view is None:
|
206
281
|
if static_schema is not None:
|
@@ -219,12 +294,7 @@ class TracContextImpl(_api.TracContext):
|
|
219
294
|
else:
|
220
295
|
schema = data_view.arrow_schema
|
221
296
|
|
222
|
-
|
223
|
-
|
224
|
-
updated_item = _data.DataMapping.pandas_to_item(dataset, schema)
|
225
|
-
updated_view = _data.DataMapping.add_item_to_view(data_view, part_key, updated_item)
|
226
|
-
|
227
|
-
self.__local_ctx[dataset_name] = updated_view
|
297
|
+
return data_view, schema
|
228
298
|
|
229
299
|
def log(self) -> logging.Logger:
|
230
300
|
|
@@ -260,20 +330,212 @@ class TracContextImpl(_api.TracContext):
|
|
260
330
|
return schema_def
|
261
331
|
|
262
332
|
|
263
|
-
class
|
264
|
-
|
265
|
-
__VALID_IDENTIFIER = re.compile("^[a-zA-Z_]\\w*$",)
|
266
|
-
__RESERVED_IDENTIFIER = re.compile("^(trac_|_)\\w*")
|
333
|
+
class TracDataContextImpl(TracContextImpl, _eapi.TracDataContext):
|
267
334
|
|
268
335
|
def __init__(
|
269
|
-
self,
|
270
|
-
|
271
|
-
|
272
|
-
checkout_directory: pathlib.Path):
|
336
|
+
self, model_def: _meta.ModelDefinition, model_class: _api.TracModel.__class__,
|
337
|
+
local_ctx: tp.Dict[str, tp.Any], dynamic_outputs: tp.List[str],
|
338
|
+
storage_map: tp.Dict[str, tp.Union[_eapi.TracFileStorage]],
|
339
|
+
checkout_directory: pathlib.Path = None):
|
340
|
+
|
341
|
+
super().__init__(model_def, model_class, local_ctx, dynamic_outputs, checkout_directory)
|
273
342
|
|
274
|
-
self.__log = log
|
275
343
|
self.__model_def = model_def
|
276
344
|
self.__local_ctx = local_ctx
|
345
|
+
self.__dynamic_outputs = dynamic_outputs
|
346
|
+
self.__storage_map = storage_map
|
347
|
+
self.__checkout_directory = checkout_directory
|
348
|
+
|
349
|
+
self.__val = self._TracContextImpl__val # noqa
|
350
|
+
|
351
|
+
def get_file_storage(self, storage_key: str) -> _eapi.TracFileStorage:
|
352
|
+
|
353
|
+
_val.validate_signature(self.get_file_storage, storage_key)
|
354
|
+
|
355
|
+
self.__val.check_storage_valid_identifier(storage_key)
|
356
|
+
self.__val.check_storage_available(self.__storage_map, storage_key)
|
357
|
+
self.__val.check_storage_type(self.__storage_map, storage_key, _eapi.TracFileStorage)
|
358
|
+
|
359
|
+
return self.__storage_map[storage_key]
|
360
|
+
|
361
|
+
def get_data_storage(self, storage_key: str) -> None:
|
362
|
+
raise _ex.ERuntimeValidation("Data storage API not available yet")
|
363
|
+
|
364
|
+
def add_data_import(self, dataset_name: str):
|
365
|
+
|
366
|
+
_val.validate_signature(self.add_data_import, dataset_name)
|
367
|
+
|
368
|
+
self.__val.check_dataset_valid_identifier(dataset_name)
|
369
|
+
self.__val.check_dataset_not_defined_in_model(dataset_name)
|
370
|
+
self.__val.check_dataset_not_available_in_context(dataset_name)
|
371
|
+
|
372
|
+
self.__local_ctx[dataset_name] = _data.DataView.create_empty()
|
373
|
+
self.__dynamic_outputs.append(dataset_name)
|
374
|
+
|
375
|
+
def set_source_metadata(self, dataset_name: str, storage_key: str, source_info: _eapi.FileStat):
|
376
|
+
|
377
|
+
_val.validate_signature(self.add_data_import, dataset_name, storage_key, source_info)
|
378
|
+
|
379
|
+
pass # Not implemented yet, only required when imports are sent back to the platform
|
380
|
+
|
381
|
+
def set_attribute(self, dataset_name: str, attribute_name: str, value: tp.Any):
|
382
|
+
|
383
|
+
_val.validate_signature(self.add_data_import, dataset_name, attribute_name, value)
|
384
|
+
|
385
|
+
pass # Not implemented yet, only required when imports are sent back to the platform
|
386
|
+
|
387
|
+
def set_schema(self, dataset_name: str, schema: _meta.SchemaDefinition):
|
388
|
+
|
389
|
+
_val.validate_signature(self.set_schema, dataset_name, schema)
|
390
|
+
|
391
|
+
# Forward to existing method (these should be swapped round)
|
392
|
+
self.put_schema(dataset_name, schema)
|
393
|
+
|
394
|
+
|
395
|
+
class TracFileStorageImpl(_eapi.TracFileStorage):
|
396
|
+
|
397
|
+
def __init__(self, storage_key: str, storage_impl: _storage.IFileStorage, write_access: bool, checkout_directory):
|
398
|
+
|
399
|
+
self.__storage_key = storage_key
|
400
|
+
|
401
|
+
self.__exists = lambda sp: storage_impl.exists(sp)
|
402
|
+
self.__size = lambda sp: storage_impl.size(sp)
|
403
|
+
self.__stat = lambda sp: storage_impl.stat(sp)
|
404
|
+
self.__ls = lambda sp, rec: storage_impl.ls(sp, rec)
|
405
|
+
self.__read_byte_stream = lambda sp: storage_impl.read_byte_stream(sp)
|
406
|
+
|
407
|
+
if write_access:
|
408
|
+
self.__mkdir = lambda sp, rec: storage_impl.mkdir(sp, rec)
|
409
|
+
self.__rm = lambda sp: storage_impl.rm(sp)
|
410
|
+
self.__rmdir = lambda sp: storage_impl.rmdir(sp)
|
411
|
+
self.__write_byte_stream = lambda sp: storage_impl.write_byte_stream(sp)
|
412
|
+
else:
|
413
|
+
self.__mkdir = None
|
414
|
+
self.__rm = None
|
415
|
+
self.__rmdir = None
|
416
|
+
self.__write_byte_stream = None
|
417
|
+
|
418
|
+
self.__log = _util.logger_for_object(self)
|
419
|
+
self.__val = TracStorageValidator(self.__log, checkout_directory, self.__storage_key)
|
420
|
+
|
421
|
+
def get_storage_key(self) -> str:
|
422
|
+
|
423
|
+
_val.validate_signature(self.get_storage_key)
|
424
|
+
|
425
|
+
return self.__storage_key
|
426
|
+
|
427
|
+
def exists(self, storage_path: str) -> bool:
|
428
|
+
|
429
|
+
_val.validate_signature(self.exists, storage_path)
|
430
|
+
|
431
|
+
self.__val.check_operation_available(self.exists, self.__exists)
|
432
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
433
|
+
|
434
|
+
return self.__exists(storage_path)
|
435
|
+
|
436
|
+
def size(self, storage_path: str) -> int:
|
437
|
+
|
438
|
+
_val.validate_signature(self.size, storage_path)
|
439
|
+
|
440
|
+
self.__val.check_operation_available(self.size, self.__size)
|
441
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
442
|
+
|
443
|
+
return self.__size(storage_path)
|
444
|
+
|
445
|
+
def stat(self, storage_path: str) -> _eapi.FileStat:
|
446
|
+
|
447
|
+
_val.validate_signature(self.stat, storage_path)
|
448
|
+
|
449
|
+
self.__val.check_operation_available(self.stat, self.__stat)
|
450
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
451
|
+
|
452
|
+
stat = self.__stat(storage_path)
|
453
|
+
return _eapi.FileStat(**stat.__dict__)
|
454
|
+
|
455
|
+
def ls(self, storage_path: str, recursive: bool = False) -> tp.List[_eapi.FileStat]:
|
456
|
+
|
457
|
+
_val.validate_signature(self.ls, storage_path, recursive)
|
458
|
+
|
459
|
+
self.__val.check_operation_available(self.ls, self.__ls)
|
460
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
461
|
+
|
462
|
+
listing = self.__ls(storage_path, recursive)
|
463
|
+
return list(_eapi.FileStat(**stat.__dict__) for stat in listing)
|
464
|
+
|
465
|
+
def mkdir(self, storage_path: str, recursive: bool = False):
|
466
|
+
|
467
|
+
_val.validate_signature(self.mkdir, storage_path, recursive)
|
468
|
+
|
469
|
+
self.__val.check_operation_available(self.mkdir, self.__mkdir)
|
470
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
471
|
+
self.__val.check_storage_path_is_not_root(storage_path)
|
472
|
+
|
473
|
+
self.__mkdir(storage_path, recursive)
|
474
|
+
|
475
|
+
def rm(self, storage_path: str):
|
476
|
+
|
477
|
+
_val.validate_signature(self.rm, storage_path)
|
478
|
+
|
479
|
+
self.__val.check_operation_available(self.rm, self.__rm)
|
480
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
481
|
+
self.__val.check_storage_path_is_not_root(storage_path)
|
482
|
+
|
483
|
+
self.__rm(storage_path)
|
484
|
+
|
485
|
+
def rmdir(self, storage_path: str):
|
486
|
+
|
487
|
+
_val.validate_signature(self.rmdir, storage_path)
|
488
|
+
|
489
|
+
self.__val.check_operation_available(self.rmdir, self.__rmdir)
|
490
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
491
|
+
self.__val.check_storage_path_is_not_root(storage_path)
|
492
|
+
|
493
|
+
self.__rmdir(storage_path)
|
494
|
+
|
495
|
+
def read_byte_stream(self, storage_path: str) -> tp.ContextManager[tp.BinaryIO]:
|
496
|
+
|
497
|
+
_val.validate_signature(self.read_byte_stream, storage_path)
|
498
|
+
|
499
|
+
self.__val.check_operation_available(self.read_byte_stream, self.__read_byte_stream)
|
500
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
501
|
+
|
502
|
+
return self.__read_byte_stream(storage_path)
|
503
|
+
|
504
|
+
def read_bytes(self, storage_path: str) -> bytes:
|
505
|
+
|
506
|
+
_val.validate_signature(self.read_bytes, storage_path)
|
507
|
+
|
508
|
+
self.__val.check_operation_available(self.read_bytes, self.__read_byte_stream)
|
509
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
510
|
+
|
511
|
+
return super().read_bytes(storage_path)
|
512
|
+
|
513
|
+
def write_byte_stream(self, storage_path: str) -> tp.ContextManager[tp.BinaryIO]:
|
514
|
+
|
515
|
+
_val.validate_signature(self.write_byte_stream, storage_path)
|
516
|
+
|
517
|
+
self.__val.check_operation_available(self.write_byte_stream, self.__write_byte_stream)
|
518
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
519
|
+
self.__val.check_storage_path_is_not_root(storage_path)
|
520
|
+
|
521
|
+
return self.__write_byte_stream(storage_path)
|
522
|
+
|
523
|
+
def write_bytes(self, storage_path: str, data: bytes):
|
524
|
+
|
525
|
+
_val.validate_signature(self.write_bytes, storage_path)
|
526
|
+
|
527
|
+
self.__val.check_operation_available(self.write_bytes, self.__write_byte_stream)
|
528
|
+
self.__val.check_storage_path_is_valid(storage_path)
|
529
|
+
self.__val.check_storage_path_is_not_root(storage_path)
|
530
|
+
|
531
|
+
super().write_bytes(storage_path, data)
|
532
|
+
|
533
|
+
|
534
|
+
class TracContextErrorReporter:
|
535
|
+
|
536
|
+
def __init__(self, log: logging.Logger, checkout_directory: pathlib.Path):
|
537
|
+
|
538
|
+
self.__log = log
|
277
539
|
self.__checkout_directory = checkout_directory
|
278
540
|
|
279
541
|
def _report_error(self, message, cause: Exception = None):
|
@@ -292,6 +554,25 @@ class TracContextValidator:
|
|
292
554
|
else:
|
293
555
|
raise _ex.ERuntimeValidation(message)
|
294
556
|
|
557
|
+
|
558
|
+
class TracContextValidator(TracContextErrorReporter):
|
559
|
+
|
560
|
+
__VALID_IDENTIFIER = re.compile("^[a-zA-Z_]\\w*$",)
|
561
|
+
__RESERVED_IDENTIFIER = re.compile("^(trac_|_)\\w*")
|
562
|
+
|
563
|
+
def __init__(
|
564
|
+
self, log: logging.Logger,
|
565
|
+
model_def: _meta.ModelDefinition,
|
566
|
+
local_ctx: tp.Dict[str, tp.Any],
|
567
|
+
dynamic_outputs: tp.List[str],
|
568
|
+
checkout_directory: pathlib.Path):
|
569
|
+
|
570
|
+
super().__init__(log, checkout_directory)
|
571
|
+
|
572
|
+
self.__model_def = model_def
|
573
|
+
self.__local_ctx = local_ctx
|
574
|
+
self.__dynamic_outputs = dynamic_outputs
|
575
|
+
|
295
576
|
def check_param_valid_identifier(self, param_name: str):
|
296
577
|
|
297
578
|
if param_name is None:
|
@@ -318,6 +599,14 @@ class TracContextValidator:
|
|
318
599
|
if not self.__VALID_IDENTIFIER.match(dataset_name):
|
319
600
|
self._report_error(f"Dataset name {dataset_name} is not a valid identifier")
|
320
601
|
|
602
|
+
def check_dataset_not_defined_in_model(self, dataset_name: str):
|
603
|
+
|
604
|
+
if dataset_name in self.__model_def.inputs or dataset_name in self.__model_def.outputs:
|
605
|
+
self._report_error(f"Dataset {dataset_name} is already defined in the model")
|
606
|
+
|
607
|
+
if dataset_name in self.__model_def.parameters:
|
608
|
+
self._report_error(f"Dataset name {dataset_name} is already in use as a model parameter")
|
609
|
+
|
321
610
|
def check_dataset_defined_in_model(self, dataset_name: str):
|
322
611
|
|
323
612
|
if dataset_name not in self.__model_def.inputs and dataset_name not in self.__model_def.outputs:
|
@@ -325,17 +614,18 @@ class TracContextValidator:
|
|
325
614
|
|
326
615
|
def check_dataset_is_model_output(self, dataset_name: str):
|
327
616
|
|
328
|
-
if dataset_name not in self.__model_def.outputs:
|
617
|
+
if dataset_name not in self.__model_def.outputs and dataset_name not in self.__dynamic_outputs:
|
329
618
|
self._report_error(f"Dataset {dataset_name} is not defined as a model output")
|
330
619
|
|
331
620
|
def check_dataset_is_dynamic_output(self, dataset_name: str):
|
332
621
|
|
333
622
|
model_output: _meta.ModelOutputSchema = self.__model_def.outputs.get(dataset_name)
|
623
|
+
dynamic_output = dataset_name in self.__dynamic_outputs
|
334
624
|
|
335
|
-
if model_output is None:
|
625
|
+
if model_output is None and not dynamic_output:
|
336
626
|
self._report_error(f"Dataset {dataset_name} is not defined as a model output")
|
337
627
|
|
338
|
-
if not model_output.dynamic:
|
628
|
+
if model_output and not model_output.dynamic:
|
339
629
|
self._report_error(f"Model output {dataset_name} is not a dynamic output")
|
340
630
|
|
341
631
|
def check_dataset_available_in_context(self, item_name: str):
|
@@ -343,6 +633,11 @@ class TracContextValidator:
|
|
343
633
|
if item_name not in self.__local_ctx:
|
344
634
|
self._report_error(f"Dataset {item_name} is not available in the current context")
|
345
635
|
|
636
|
+
def check_dataset_not_available_in_context(self, item_name: str):
|
637
|
+
|
638
|
+
if item_name in self.__local_ctx:
|
639
|
+
self._report_error(f"Dataset {item_name} already exists in the current context")
|
640
|
+
|
346
641
|
def check_dataset_schema_defined(self, dataset_name: str, data_view: _data.DataView):
|
347
642
|
|
348
643
|
schema = data_view.trac_schema if data_view is not None else None
|
@@ -415,6 +710,33 @@ class TracContextValidator:
|
|
415
710
|
f"The object referenced by [{item_name}] in the current context has the wrong type" +
|
416
711
|
f" (expected {expected_type_name}, got {actual_type_name})")
|
417
712
|
|
713
|
+
def check_storage_valid_identifier(self, storage_key):
|
714
|
+
|
715
|
+
if storage_key is None:
|
716
|
+
self._report_error(f"Storage key is null")
|
717
|
+
|
718
|
+
if not self.__VALID_IDENTIFIER.match(storage_key):
|
719
|
+
self._report_error(f"Storage key {storage_key} is not a valid identifier")
|
720
|
+
|
721
|
+
def check_storage_available(self, storage_map: tp.Dict, storage_key: str):
|
722
|
+
|
723
|
+
storage_instance = storage_map.get(storage_key)
|
724
|
+
|
725
|
+
if storage_instance is None:
|
726
|
+
self._report_error(f"Storage not available for storage key [{storage_key}]")
|
727
|
+
|
728
|
+
def check_storage_type(
|
729
|
+
self, storage_map: tp.Dict, storage_key: str,
|
730
|
+
storage_type: tp.Union[_eapi.TracFileStorage.__class__]):
|
731
|
+
|
732
|
+
storage_instance = storage_map.get(storage_key)
|
733
|
+
|
734
|
+
if not isinstance(storage_instance, storage_type):
|
735
|
+
if storage_type == _eapi.TracFileStorage:
|
736
|
+
self._report_error(f"Storage key [{storage_key}] refers to data storage, not file storage")
|
737
|
+
else:
|
738
|
+
self._report_error(f"Storage key [{storage_key}] refers to file storage, not data storage")
|
739
|
+
|
418
740
|
@staticmethod
|
419
741
|
def _type_name(type_: type):
|
420
742
|
|
@@ -424,3 +746,34 @@ class TracContextValidator:
|
|
424
746
|
return type_.__qualname__
|
425
747
|
|
426
748
|
return module + '.' + type_.__name__
|
749
|
+
|
750
|
+
|
751
|
+
class TracStorageValidator(TracContextErrorReporter):
|
752
|
+
|
753
|
+
def __init__(self, log, checkout_directory, storage_key):
|
754
|
+
super().__init__(log, checkout_directory)
|
755
|
+
self.__storage_key = storage_key
|
756
|
+
|
757
|
+
def check_operation_available(self, public_func: tp.Callable, impl_func: tp.Callable):
|
758
|
+
|
759
|
+
if impl_func is None:
|
760
|
+
self._report_error(f"Operation [{public_func.__name__}] is not available for storage [{self.__storage_key}]")
|
761
|
+
|
762
|
+
def check_storage_path_is_valid(self, storage_path: str):
|
763
|
+
|
764
|
+
if _val.StorageValidator.storage_path_is_empty(storage_path):
|
765
|
+
self._report_error(f"Storage path is None or empty")
|
766
|
+
|
767
|
+
if _val.StorageValidator.storage_path_invalid(storage_path):
|
768
|
+
self._report_error(f"Storage path [{storage_path}] contains invalid characters")
|
769
|
+
|
770
|
+
if _val.StorageValidator.storage_path_not_relative(storage_path):
|
771
|
+
self._report_error(f"Storage path [{storage_path}] is not a relative path")
|
772
|
+
|
773
|
+
if _val.StorageValidator.storage_path_outside_root(storage_path):
|
774
|
+
self._report_error(f"Storage path [{storage_path}] is outside the storage root")
|
775
|
+
|
776
|
+
def check_storage_path_is_not_root(self, storage_path: str):
|
777
|
+
|
778
|
+
if _val.StorageValidator.storage_path_is_empty(storage_path):
|
779
|
+
self._report_error(f"Storage path [{storage_path}] is not allowed")
|