tracdap-runtime 0.6.2__py3-none-any.whl → 0.6.4__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/actors.py +87 -10
- tracdap/rt/_exec/context.py +207 -100
- tracdap/rt/_exec/dev_mode.py +52 -20
- tracdap/rt/_exec/engine.py +79 -14
- tracdap/rt/_exec/functions.py +14 -17
- tracdap/rt/_exec/runtime.py +83 -40
- tracdap/rt/_exec/server.py +306 -29
- tracdap/rt/_impl/config_parser.py +219 -49
- tracdap/rt/_impl/data.py +70 -5
- tracdap/rt/_impl/grpc/codec.py +60 -5
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +19 -19
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.pyi +11 -9
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2_grpc.py +25 -25
- tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +18 -18
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +28 -16
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +37 -6
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +8 -3
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +13 -2
- tracdap/rt/_impl/guard_rails.py +21 -0
- tracdap/rt/_impl/models.py +25 -0
- tracdap/rt/_impl/static_api.py +43 -13
- tracdap/rt/_impl/type_system.py +17 -0
- tracdap/rt/_impl/validation.py +47 -4
- tracdap/rt/_plugins/config_local.py +49 -0
- tracdap/rt/_version.py +1 -1
- tracdap/rt/api/hook.py +6 -5
- tracdap/rt/api/model_api.py +50 -7
- tracdap/rt/api/static_api.py +81 -23
- tracdap/rt/config/__init__.py +4 -4
- tracdap/rt/config/common.py +25 -15
- tracdap/rt/config/job.py +2 -2
- tracdap/rt/config/platform.py +25 -35
- tracdap/rt/config/result.py +2 -2
- tracdap/rt/config/runtime.py +4 -2
- tracdap/rt/ext/config.py +34 -0
- tracdap/rt/ext/embed.py +1 -3
- tracdap/rt/ext/plugins.py +47 -6
- tracdap/rt/launch/cli.py +11 -4
- tracdap/rt/launch/launch.py +53 -12
- tracdap/rt/metadata/__init__.py +17 -17
- tracdap/rt/metadata/common.py +2 -2
- tracdap/rt/metadata/custom.py +3 -3
- tracdap/rt/metadata/data.py +12 -12
- tracdap/rt/metadata/file.py +6 -6
- tracdap/rt/metadata/flow.py +6 -6
- tracdap/rt/metadata/job.py +8 -8
- tracdap/rt/metadata/model.py +21 -11
- tracdap/rt/metadata/object.py +3 -0
- tracdap/rt/metadata/object_id.py +8 -8
- tracdap/rt/metadata/search.py +5 -5
- tracdap/rt/metadata/stoarge.py +6 -6
- tracdap/rt/metadata/tag.py +1 -1
- tracdap/rt/metadata/tag_update.py +1 -1
- tracdap/rt/metadata/type.py +4 -4
- {tracdap_runtime-0.6.2.dist-info → tracdap_runtime-0.6.4.dist-info}/METADATA +4 -4
- tracdap_runtime-0.6.4.dist-info/RECORD +112 -0
- {tracdap_runtime-0.6.2.dist-info → tracdap_runtime-0.6.4.dist-info}/WHEEL +1 -1
- tracdap/rt/_impl/grpc/tracdap/config/common_pb2.py +0 -55
- tracdap/rt/_impl/grpc/tracdap/config/common_pb2.pyi +0 -103
- tracdap/rt/_impl/grpc/tracdap/config/job_pb2.py +0 -42
- tracdap/rt/_impl/grpc/tracdap/config/job_pb2.pyi +0 -44
- tracdap/rt/_impl/grpc/tracdap/config/platform_pb2.py +0 -71
- tracdap/rt/_impl/grpc/tracdap/config/platform_pb2.pyi +0 -197
- tracdap/rt/_impl/grpc/tracdap/config/result_pb2.py +0 -37
- tracdap/rt/_impl/grpc/tracdap/config/result_pb2.pyi +0 -35
- tracdap/rt/_impl/grpc/tracdap/config/runtime_pb2.py +0 -42
- tracdap/rt/_impl/grpc/tracdap/config/runtime_pb2.pyi +0 -46
- tracdap/rt/ext/_guard.py +0 -37
- tracdap_runtime-0.6.2.dist-info/RECORD +0 -121
- {tracdap_runtime-0.6.2.dist-info → tracdap_runtime-0.6.4.dist-info}/LICENSE +0 -0
- {tracdap_runtime-0.6.2.dist-info → tracdap_runtime-0.6.4.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ _sym_db = _symbol_database.Default()
|
|
13
13
|
|
14
14
|
|
15
15
|
from tracdap.rt._impl.grpc.tracdap.metadata import object_id_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_metadata_dot_object__id__pb2
|
16
|
+
from tracdap.rt._impl.grpc.tracdap.metadata import type_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_metadata_dot_type__pb2
|
16
17
|
from tracdap.rt._impl.grpc.tracdap.metadata import data_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_metadata_dot_data__pb2
|
17
18
|
from tracdap.rt._impl.grpc.tracdap.metadata import model_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_metadata_dot_model__pb2
|
18
19
|
from tracdap.rt._impl.grpc.tracdap.metadata import flow_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_metadata_dot_flow__pb2
|
@@ -22,7 +23,7 @@ from tracdap.rt._impl.grpc.tracdap.metadata import custom_pb2 as tracdap_dot_rt_
|
|
22
23
|
from tracdap.rt._impl.grpc.tracdap.metadata import stoarge_pb2 as tracdap_dot_rt_dot___impl_dot_grpc_dot_tracdap_dot_metadata_dot_stoarge__pb2
|
23
24
|
|
24
25
|
|
25
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n3tracdap/rt/_impl/grpc/tracdap/metadata/object.proto\x12\x10tracdap.metadata\x1a\x36tracdap/rt/_impl/grpc/tracdap/metadata/object_id.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/data.proto\x1a\x32tracdap/rt/_impl/grpc/tracdap/metadata/model.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/flow.proto\x1a\x30tracdap/rt/_impl/grpc/tracdap/metadata/job.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/file.proto\x1a\x33tracdap/rt/_impl/grpc/tracdap/metadata/custom.proto\x1a\x34tracdap/rt/_impl/grpc/tracdap/metadata/stoarge.proto\"\
|
26
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n3tracdap/rt/_impl/grpc/tracdap/metadata/object.proto\x12\x10tracdap.metadata\x1a\x36tracdap/rt/_impl/grpc/tracdap/metadata/object_id.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/type.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/data.proto\x1a\x32tracdap/rt/_impl/grpc/tracdap/metadata/model.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/flow.proto\x1a\x30tracdap/rt/_impl/grpc/tracdap/metadata/job.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/file.proto\x1a\x33tracdap/rt/_impl/grpc/tracdap/metadata/custom.proto\x1a\x34tracdap/rt/_impl/grpc/tracdap/metadata/stoarge.proto\"\x87\x05\n\x10ObjectDefinition\x12\x30\n\nobjectType\x18\x01 \x01(\x0e\x32\x1c.tracdap.metadata.ObjectType\x12\x30\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32 .tracdap.metadata.DataDefinitionH\x00\x12\x32\n\x05model\x18\x03 \x01(\x0b\x32!.tracdap.metadata.ModelDefinitionH\x00\x12\x30\n\x04\x66low\x18\x04 \x01(\x0b\x32 .tracdap.metadata.FlowDefinitionH\x00\x12.\n\x03job\x18\x05 \x01(\x0b\x32\x1f.tracdap.metadata.JobDefinitionH\x00\x12\x30\n\x04\x66ile\x18\x06 \x01(\x0b\x32 .tracdap.metadata.FileDefinitionH\x00\x12\x34\n\x06\x63ustom\x18\x07 \x01(\x0b\x32\".tracdap.metadata.CustomDefinitionH\x00\x12\x36\n\x07storage\x18\x08 \x01(\x0b\x32#.tracdap.metadata.StorageDefinitionH\x00\x12\x34\n\x06schema\x18\t \x01(\x0b\x32\".tracdap.metadata.SchemaDefinitionH\x00\x12H\n\x0bobjectProps\x18\x64 \x03(\x0b\x32\x33.tracdap.metadata.ObjectDefinition.ObjectPropsEntry\x1aK\n\x10ObjectPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x0c\n\ndefinitionB2\n\x1aorg.finos.tracdap.metadataB\x12ObjectProtoWrapperP\x01\x62\x06proto3')
|
26
27
|
|
27
28
|
_globals = globals()
|
28
29
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
@@ -30,6 +31,10 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'tracdap.rt._impl.grpc.tracd
|
|
30
31
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
31
32
|
_globals['DESCRIPTOR']._options = None
|
32
33
|
_globals['DESCRIPTOR']._serialized_options = b'\n\032org.finos.tracdap.metadataB\022ObjectProtoWrapperP\001'
|
33
|
-
_globals['
|
34
|
-
_globals['
|
34
|
+
_globals['_OBJECTDEFINITION_OBJECTPROPSENTRY']._options = None
|
35
|
+
_globals['_OBJECTDEFINITION_OBJECTPROPSENTRY']._serialized_options = b'8\001'
|
36
|
+
_globals['_OBJECTDEFINITION']._serialized_start=543
|
37
|
+
_globals['_OBJECTDEFINITION']._serialized_end=1190
|
38
|
+
_globals['_OBJECTDEFINITION_OBJECTPROPSENTRY']._serialized_start=1101
|
39
|
+
_globals['_OBJECTDEFINITION_OBJECTPROPSENTRY']._serialized_end=1176
|
35
40
|
# @@protoc_insertion_point(module_scope)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from tracdap.rt._impl.grpc.tracdap.metadata import object_id_pb2 as _object_id_pb2
|
2
|
+
from tracdap.rt._impl.grpc.tracdap.metadata import type_pb2 as _type_pb2
|
2
3
|
from tracdap.rt._impl.grpc.tracdap.metadata import data_pb2 as _data_pb2
|
3
4
|
from tracdap.rt._impl.grpc.tracdap.metadata import model_pb2 as _model_pb2
|
4
5
|
from tracdap.rt._impl.grpc.tracdap.metadata import flow_pb2 as _flow_pb2
|
@@ -6,6 +7,7 @@ from tracdap.rt._impl.grpc.tracdap.metadata import job_pb2 as _job_pb2
|
|
6
7
|
from tracdap.rt._impl.grpc.tracdap.metadata import file_pb2 as _file_pb2
|
7
8
|
from tracdap.rt._impl.grpc.tracdap.metadata import custom_pb2 as _custom_pb2
|
8
9
|
from tracdap.rt._impl.grpc.tracdap.metadata import stoarge_pb2 as _stoarge_pb2
|
10
|
+
from google.protobuf.internal import containers as _containers
|
9
11
|
from google.protobuf import descriptor as _descriptor
|
10
12
|
from google.protobuf import message as _message
|
11
13
|
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
@@ -13,7 +15,14 @@ from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Opti
|
|
13
15
|
DESCRIPTOR: _descriptor.FileDescriptor
|
14
16
|
|
15
17
|
class ObjectDefinition(_message.Message):
|
16
|
-
__slots__ = ("objectType", "data", "model", "flow", "job", "file", "custom", "storage", "schema")
|
18
|
+
__slots__ = ("objectType", "data", "model", "flow", "job", "file", "custom", "storage", "schema", "objectProps")
|
19
|
+
class ObjectPropsEntry(_message.Message):
|
20
|
+
__slots__ = ("key", "value")
|
21
|
+
KEY_FIELD_NUMBER: _ClassVar[int]
|
22
|
+
VALUE_FIELD_NUMBER: _ClassVar[int]
|
23
|
+
key: str
|
24
|
+
value: _type_pb2.Value
|
25
|
+
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_type_pb2.Value, _Mapping]] = ...) -> None: ...
|
17
26
|
OBJECTTYPE_FIELD_NUMBER: _ClassVar[int]
|
18
27
|
DATA_FIELD_NUMBER: _ClassVar[int]
|
19
28
|
MODEL_FIELD_NUMBER: _ClassVar[int]
|
@@ -23,6 +32,7 @@ class ObjectDefinition(_message.Message):
|
|
23
32
|
CUSTOM_FIELD_NUMBER: _ClassVar[int]
|
24
33
|
STORAGE_FIELD_NUMBER: _ClassVar[int]
|
25
34
|
SCHEMA_FIELD_NUMBER: _ClassVar[int]
|
35
|
+
OBJECTPROPS_FIELD_NUMBER: _ClassVar[int]
|
26
36
|
objectType: _object_id_pb2.ObjectType
|
27
37
|
data: _data_pb2.DataDefinition
|
28
38
|
model: _model_pb2.ModelDefinition
|
@@ -32,4 +42,5 @@ class ObjectDefinition(_message.Message):
|
|
32
42
|
custom: _custom_pb2.CustomDefinition
|
33
43
|
storage: _stoarge_pb2.StorageDefinition
|
34
44
|
schema: _data_pb2.SchemaDefinition
|
35
|
-
|
45
|
+
objectProps: _containers.MessageMap[str, _type_pb2.Value]
|
46
|
+
def __init__(self, objectType: _Optional[_Union[_object_id_pb2.ObjectType, str]] = ..., data: _Optional[_Union[_data_pb2.DataDefinition, _Mapping]] = ..., model: _Optional[_Union[_model_pb2.ModelDefinition, _Mapping]] = ..., flow: _Optional[_Union[_flow_pb2.FlowDefinition, _Mapping]] = ..., job: _Optional[_Union[_job_pb2.JobDefinition, _Mapping]] = ..., file: _Optional[_Union[_file_pb2.FileDefinition, _Mapping]] = ..., custom: _Optional[_Union[_custom_pb2.CustomDefinition, _Mapping]] = ..., storage: _Optional[_Union[_stoarge_pb2.StorageDefinition, _Mapping]] = ..., schema: _Optional[_Union[_data_pb2.SchemaDefinition, _Mapping]] = ..., objectProps: _Optional[_Mapping[str, _type_pb2.Value]] = ...) -> None: ...
|
tracdap/rt/_impl/guard_rails.py
CHANGED
@@ -46,6 +46,27 @@ def _get_package_path(module_name):
|
|
46
46
|
return module_path.parents[depth]
|
47
47
|
|
48
48
|
|
49
|
+
def run_model_guard(operation: str = None):
|
50
|
+
|
51
|
+
# A simple guard method to block model code from accessing parts of the TRAC runtime framework
|
52
|
+
# To blocks calls to the Python stdlib or 3rd party libs, use PythonGuardRails instead
|
53
|
+
|
54
|
+
stack = inspect.stack()
|
55
|
+
frame = stack[-1]
|
56
|
+
|
57
|
+
if operation is None:
|
58
|
+
operation = f"Calling {frame.function}()"
|
59
|
+
|
60
|
+
for frame_index in range(len(stack) - 2, 0, -1):
|
61
|
+
|
62
|
+
parent_frame = frame
|
63
|
+
frame = stack[frame_index]
|
64
|
+
|
65
|
+
if frame.function == "run_model" and parent_frame.function == "_execute":
|
66
|
+
err = f"{operation} is not allowed inside run_model()"
|
67
|
+
raise ex.ERuntimeValidation(err)
|
68
|
+
|
69
|
+
|
49
70
|
class PythonGuardRails:
|
50
71
|
|
51
72
|
DANGEROUS_BUILTIN_FUNCTIONS = ["exec", "eval", "compile", "open", "input", "memoryview"]
|
tracdap/rt/_impl/models.py
CHANGED
@@ -215,12 +215,15 @@ class ModelLoader:
|
|
215
215
|
|
216
216
|
for name, param in model_def.parameters.items():
|
217
217
|
self.__log.info(f"Parameter [{name}] - {param.paramType.basicType.name}")
|
218
|
+
param.paramProps = self._encoded_props(param.paramProps, "parameter", name)
|
218
219
|
|
219
220
|
for name, schema in model_def.inputs.items():
|
220
221
|
self.__log.info(f"Input [{name}] - {schema.schema.schemaType.name}")
|
222
|
+
schema.inputProps = self._encoded_props(schema.inputProps, "input", name)
|
221
223
|
|
222
224
|
for name, schema in model_def.outputs.items():
|
223
225
|
self.__log.info(f"Output [{name}] - {schema.schema.schemaType.name}")
|
226
|
+
schema.outputProps = self._encoded_props(schema.outputProps, "input", name)
|
224
227
|
|
225
228
|
return model_def
|
226
229
|
|
@@ -231,3 +234,25 @@ class ModelLoader:
|
|
231
234
|
|
232
235
|
self.__log.error(msg, exc_info=True)
|
233
236
|
raise _ex.EModelValidation(msg) from e
|
237
|
+
|
238
|
+
@staticmethod
|
239
|
+
def _encoded_props(
|
240
|
+
raw_props: tp.Dict[str, tp.Any],
|
241
|
+
item_type: str, item_name: str) \
|
242
|
+
-> tp.Dict[str, _meta.Value]:
|
243
|
+
|
244
|
+
if raw_props is None:
|
245
|
+
return dict()
|
246
|
+
|
247
|
+
encoded_props = dict()
|
248
|
+
|
249
|
+
for key, raw_value in raw_props.items():
|
250
|
+
|
251
|
+
if raw_value is None:
|
252
|
+
raise _ex.EModelValidation(f"Invalid null property [{key}] for {item_type} [{item_name}]")
|
253
|
+
elif isinstance(raw_value, _meta.Value):
|
254
|
+
encoded_props[key] = raw_value
|
255
|
+
else:
|
256
|
+
encoded_props[key] = _types.MetadataCodec.encode_value(raw_value)
|
257
|
+
|
258
|
+
return encoded_props
|
tracdap/rt/_impl/static_api.py
CHANGED
@@ -71,10 +71,14 @@ class StaticApiImpl(_StaticApiHook):
|
|
71
71
|
|
72
72
|
def define_parameter(
|
73
73
|
self, param_name: str, param_type: _tp.Union[_meta.TypeDescriptor, _meta.BasicType],
|
74
|
-
label: str, default_value: _tp.Optional[_tp.Any] = None
|
74
|
+
label: str, default_value: _tp.Optional[_tp.Any] = None,
|
75
|
+
*, param_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
|
75
76
|
-> _Named[_meta.ModelParameter]:
|
76
77
|
|
77
|
-
_val.validate_signature(
|
78
|
+
_val.validate_signature(
|
79
|
+
self.define_parameter,
|
80
|
+
param_name, param_type, label, default_value,
|
81
|
+
param_props=param_props)
|
78
82
|
|
79
83
|
if isinstance(param_type, _meta.TypeDescriptor):
|
80
84
|
param_type_descriptor = param_type
|
@@ -88,7 +92,9 @@ class StaticApiImpl(_StaticApiHook):
|
|
88
92
|
msg = f"Default value for parameter [{param_name}] does not match the declared type"
|
89
93
|
raise _ex.EModelValidation(msg) from e
|
90
94
|
|
91
|
-
return _Named(param_name, _meta.ModelParameter(
|
95
|
+
return _Named(param_name, _meta.ModelParameter(
|
96
|
+
param_type_descriptor, label, default_value,
|
97
|
+
paramProps=param_props))
|
92
98
|
|
93
99
|
def define_parameters(
|
94
100
|
self, *params: _tp.Union[_Named[_meta.ModelParameter], _tp.List[_Named[_meta.ModelParameter]]]) \
|
@@ -148,25 +154,49 @@ class StaticApiImpl(_StaticApiHook):
|
|
148
154
|
|
149
155
|
def define_input_table(
|
150
156
|
self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
|
151
|
-
label: _tp.Optional[str] = None,
|
152
|
-
|
157
|
+
label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
|
158
|
+
input_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
|
153
159
|
-> _meta.ModelInputSchema:
|
154
160
|
|
155
|
-
_val.validate_signature(
|
161
|
+
_val.validate_signature(
|
162
|
+
self.define_input_table, *fields,
|
163
|
+
label=label, optional=optional, dynamic=dynamic,
|
164
|
+
input_props=input_props)
|
165
|
+
|
166
|
+
# Do not define details for dynamic schemas
|
167
|
+
|
168
|
+
if dynamic:
|
169
|
+
schema_def = _meta.SchemaDefinition(_meta.SchemaType.TABLE)
|
170
|
+
else:
|
171
|
+
schema_def = self.define_schema(*fields, schema_type=_meta.SchemaType.TABLE)
|
156
172
|
|
157
|
-
|
158
|
-
|
173
|
+
return _meta.ModelInputSchema(
|
174
|
+
schema=schema_def, label=label,
|
175
|
+
optional=optional, dynamic=dynamic,
|
176
|
+
inputProps=input_props)
|
159
177
|
|
160
178
|
def define_output_table(
|
161
179
|
self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
|
162
|
-
label: _tp.Optional[str] = None,
|
163
|
-
|
180
|
+
label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
|
181
|
+
output_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
|
164
182
|
-> _meta.ModelOutputSchema:
|
165
183
|
|
166
|
-
_val.validate_signature(
|
184
|
+
_val.validate_signature(
|
185
|
+
self.define_output_table, *fields,
|
186
|
+
label=label, optional=optional, dynamic=dynamic,
|
187
|
+
output_props=output_props)
|
188
|
+
|
189
|
+
# Do not define details for dynamic schemas
|
190
|
+
|
191
|
+
if dynamic:
|
192
|
+
schema_def = _meta.SchemaDefinition(_meta.SchemaType.TABLE)
|
193
|
+
else:
|
194
|
+
schema_def = self.define_schema(*fields, schema_type=_meta.SchemaType.TABLE)
|
167
195
|
|
168
|
-
|
169
|
-
|
196
|
+
return _meta.ModelOutputSchema(
|
197
|
+
schema=schema_def, label=label,
|
198
|
+
optional=optional, dynamic=dynamic,
|
199
|
+
outputProps=output_props)
|
170
200
|
|
171
201
|
@staticmethod
|
172
202
|
def _build_named_dict(
|
tracdap/rt/_impl/type_system.py
CHANGED
@@ -171,6 +171,23 @@ class MetadataCodec:
|
|
171
171
|
type_desc = _meta.TypeDescriptor(_meta.BasicType.DATE)
|
172
172
|
return _meta.Value(type_desc, dateValue=_meta.DateValue(value.isoformat()))
|
173
173
|
|
174
|
+
if isinstance(value, list):
|
175
|
+
|
176
|
+
if len(value) == 0:
|
177
|
+
raise _ex.ETracInternal("Cannot encode an empty list")
|
178
|
+
|
179
|
+
array_raw_type = type(value[0])
|
180
|
+
array_trac_type = TypeMapping.python_to_trac(array_raw_type)
|
181
|
+
|
182
|
+
if any(map(lambda x: type(x) != array_raw_type, value)):
|
183
|
+
raise _ex.ETracInternal("Cannot encode a list with values of different types")
|
184
|
+
|
185
|
+
encoded_items = list(map(lambda x: cls.convert_value(x, array_trac_type), value))
|
186
|
+
|
187
|
+
return _meta.Value(
|
188
|
+
_meta.TypeDescriptor(_meta.BasicType.ARRAY, arrayType=array_trac_type),
|
189
|
+
arrayValue=_meta.ArrayValue(encoded_items))
|
190
|
+
|
174
191
|
raise _ex.ETracInternal(f"Value type [{type(value)}] is not supported yet")
|
175
192
|
|
176
193
|
@classmethod
|
tracdap/rt/_impl/validation.py
CHANGED
@@ -38,7 +38,7 @@ def check_type(expected_type: tp.Type, value: tp.Any) -> bool:
|
|
38
38
|
|
39
39
|
|
40
40
|
def quick_validate_model_def(model_def: meta.ModelDefinition):
|
41
|
-
|
41
|
+
StaticValidator.quick_validate_model_def(model_def)
|
42
42
|
|
43
43
|
|
44
44
|
class _TypeValidator:
|
@@ -233,7 +233,7 @@ class _TypeValidator:
|
|
233
233
|
return type_var.__name__
|
234
234
|
|
235
235
|
|
236
|
-
class
|
236
|
+
class StaticValidator:
|
237
237
|
|
238
238
|
__identifier_pattern = re.compile("\\A[a-zA-Z_]\\w*\\Z", re.ASCII)
|
239
239
|
__reserved_identifier_pattern = re.compile("\\A(_|trac_)", re.ASCII)
|
@@ -301,6 +301,28 @@ class _StaticValidator:
|
|
301
301
|
cls._check_inputs_or_outputs(model_def.inputs)
|
302
302
|
cls._check_inputs_or_outputs(model_def.outputs)
|
303
303
|
|
304
|
+
@classmethod
|
305
|
+
def quick_validate_schema(cls, schema: meta.SchemaDefinition):
|
306
|
+
|
307
|
+
if schema.schemaType != meta.SchemaType.TABLE:
|
308
|
+
cls._fail(f"Unsupported schema type [{schema.schemaType}]")
|
309
|
+
|
310
|
+
if schema.partType != meta.PartType.PART_ROOT:
|
311
|
+
cls._fail(f"Unsupported partition type [{schema.partType}]")
|
312
|
+
|
313
|
+
if schema.table is None or schema.table.fields is None or len(schema.table.fields) == 0:
|
314
|
+
cls._fail(f"Table schema does not define any fields")
|
315
|
+
|
316
|
+
fields = schema.table.fields
|
317
|
+
field_names = list(map(lambda f: f.fieldName, fields))
|
318
|
+
property_type = f"field"
|
319
|
+
|
320
|
+
cls._valid_identifiers(field_names, property_type)
|
321
|
+
cls._case_insensitive_duplicates(field_names, property_type)
|
322
|
+
|
323
|
+
for field in fields:
|
324
|
+
cls._check_single_field(field, property_type)
|
325
|
+
|
304
326
|
@classmethod
|
305
327
|
def _check_label(cls, label, param_name):
|
306
328
|
if label is not None:
|
@@ -320,6 +342,9 @@ class _StaticValidator:
|
|
320
342
|
else:
|
321
343
|
cls._check_label(param.label, param_name)
|
322
344
|
|
345
|
+
if param.paramProps is not None:
|
346
|
+
cls._valid_identifiers(param.paramProps.keys(), "entry in param props")
|
347
|
+
|
323
348
|
@classmethod
|
324
349
|
def _check_inputs_or_outputs(cls, inputs_or_outputs):
|
325
350
|
|
@@ -327,10 +352,20 @@ class _StaticValidator:
|
|
327
352
|
|
328
353
|
cls._log.info(f"Checking {input_name}")
|
329
354
|
|
355
|
+
if input_schema.dynamic:
|
356
|
+
if input_schema.schema and input_schema.schema.table:
|
357
|
+
error = "Dynamic schemas must have schema.table = None"
|
358
|
+
cls._fail(f"Invalid schema for [{input_name}]: {error}")
|
359
|
+
else:
|
360
|
+
continue
|
361
|
+
|
330
362
|
fields = input_schema.schema.table.fields
|
331
363
|
field_names = list(map(lambda f: f.fieldName, fields))
|
332
364
|
property_type = f"field in [{input_name}]"
|
333
365
|
|
366
|
+
if len(fields) == 0:
|
367
|
+
cls._fail(f"Invalid schema for [{input_name}]: No fields defined")
|
368
|
+
|
334
369
|
cls._valid_identifiers(field_names, property_type)
|
335
370
|
cls._case_insensitive_duplicates(field_names, property_type)
|
336
371
|
|
@@ -340,6 +375,13 @@ class _StaticValidator:
|
|
340
375
|
label = input_schema.label
|
341
376
|
cls._check_label(label, input_name)
|
342
377
|
|
378
|
+
if isinstance(input_schema, meta.ModelInputSchema):
|
379
|
+
if input_schema.inputProps is not None:
|
380
|
+
cls._valid_identifiers(input_schema.inputProps.keys(), "entry in input props")
|
381
|
+
else:
|
382
|
+
if input_schema.outputProps is not None:
|
383
|
+
cls._valid_identifiers(input_schema.outputProps.keys(), "entry in output props")
|
384
|
+
|
343
385
|
@classmethod
|
344
386
|
def _check_single_field(cls, field: meta.FieldSchema, property_type):
|
345
387
|
|
@@ -365,8 +407,9 @@ class _StaticValidator:
|
|
365
407
|
if field.categorical and field.fieldType != meta.BasicType.STRING:
|
366
408
|
cls._fail(f"Invalid {property_type}: [{field.fieldName}] fieldType {field.fieldType} used as categorical")
|
367
409
|
|
368
|
-
|
369
|
-
|
410
|
+
# Do not require notNull = True for business keys here
|
411
|
+
# Instead setting businessKey = True will cause notNull = True to be set during normalization
|
412
|
+
# This agrees with the semantics in platform API and CSV schema loader
|
370
413
|
|
371
414
|
@classmethod
|
372
415
|
def _valid_identifiers(cls, keys, property_type):
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright 2024 Accenture Global Solutions Limited
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
import pathlib as pathlib
|
16
|
+
import typing as tp
|
17
|
+
|
18
|
+
import tracdap.rt.ext.plugins as plugins
|
19
|
+
import tracdap.rt.exceptions as ex
|
20
|
+
|
21
|
+
from tracdap.rt.ext.config import *
|
22
|
+
|
23
|
+
|
24
|
+
class LocalConfigLoader(IConfigLoader):
|
25
|
+
|
26
|
+
# Properties dict will be empty for config plugins
|
27
|
+
def __init__(self, properties: tp.Dict[str, str]): # noqa
|
28
|
+
pass
|
29
|
+
|
30
|
+
def has_config_file(self, config_url: str) -> bool:
|
31
|
+
if config_url.startswith("file:"):
|
32
|
+
config_url = config_url[5:]
|
33
|
+
config_path = pathlib.Path(config_url).resolve()
|
34
|
+
return config_path.exists() and config_path.is_file()
|
35
|
+
|
36
|
+
def load_config_file(self, config_url: str) -> bytes:
|
37
|
+
if config_url.startswith("file:"):
|
38
|
+
config_url = config_url[5:]
|
39
|
+
config_path = pathlib.Path(config_url).resolve()
|
40
|
+
return config_path.read_bytes()
|
41
|
+
|
42
|
+
def has_config_dict(self, config_url: str) -> bool:
|
43
|
+
return False
|
44
|
+
|
45
|
+
def load_config_dict(self, config_url: str) -> dict:
|
46
|
+
raise ex.ETracInternal("Local config loader does not support loading objects")
|
47
|
+
|
48
|
+
|
49
|
+
plugins.PluginManager.register_plugin(IConfigLoader, LocalConfigLoader, ["LOCAL", "file"])
|
tracdap/rt/_version.py
CHANGED
tracdap/rt/api/hook.py
CHANGED
@@ -80,7 +80,8 @@ class _StaticApiHook:
|
|
80
80
|
@_abc.abstractmethod
|
81
81
|
def define_parameter(
|
82
82
|
self, param_name: str, param_type: _tp.Union[_meta.TypeDescriptor, _meta.BasicType],
|
83
|
-
label: str, default_value: _tp.Optional[_tp.Any] = None
|
83
|
+
label: str, default_value: _tp.Optional[_tp.Any] = None,
|
84
|
+
*, param_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
|
84
85
|
-> _Named[_meta.ModelParameter]:
|
85
86
|
|
86
87
|
pass
|
@@ -120,8 +121,8 @@ class _StaticApiHook:
|
|
120
121
|
@_abc.abstractmethod
|
121
122
|
def define_input_table(
|
122
123
|
self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
|
123
|
-
label: _tp.Optional[str] = None,
|
124
|
-
|
124
|
+
label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
|
125
|
+
input_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
|
125
126
|
-> _meta.ModelInputSchema:
|
126
127
|
|
127
128
|
pass
|
@@ -129,8 +130,8 @@ class _StaticApiHook:
|
|
129
130
|
@_abc.abstractmethod
|
130
131
|
def define_output_table(
|
131
132
|
self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
|
132
|
-
label: _tp.Optional[str] = None,
|
133
|
-
|
133
|
+
label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
|
134
|
+
output_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
|
134
135
|
-> _meta.ModelOutputSchema:
|
135
136
|
|
136
137
|
pass
|
tracdap/rt/api/model_api.py
CHANGED
@@ -100,13 +100,26 @@ class TracContext:
|
|
100
100
|
"""
|
101
101
|
Get the schema of a model input or output
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
103
|
+
Use this method to get the :py:class:`SchemaDefinition <tracdap.rt.metadata.SchemaDefinition>`
|
104
|
+
for any input or output of the current model.
|
105
|
+
For datasets with static schemas, this will be the same schema that was defined in the
|
106
|
+
:py:class:`TracModel <tracdap.rt.api.TracModel>` methods
|
107
|
+
:py:meth:`define_inputs() <tracdap.rt.api.TracModel.define_inputs>` and
|
108
|
+
:py:meth:`define_outputs() <tracdap.rt.api.TracModel.define_outputs>`.
|
109
|
+
|
110
|
+
For inputs with dynamic schemas, the schema of the provided input dataset will be returned.
|
111
|
+
For outputs with dynamic schemas the schema must be set by calling
|
112
|
+
:py:meth:`put_schema() <tracdap.rt.api.TracContext.put_schema>`, after which this method
|
113
|
+
will return that schema. Calling :py:meth:`get_schema() <tracdap.rt.api.TracContext.get_schema>`
|
114
|
+
for a dynamic output before the schema is set will result in a runtime validation error.
|
115
|
+
|
116
|
+
For optional inputs, use :py:meth:`has_dataset() <tracdap.rt.api.TracContext.has_dataset>`
|
117
|
+
to check whether the input was provided. Calling :py:meth:`get_schema() <tracdap.rt.api.TracContext.get_schema>`
|
118
|
+
for an optional input that was not provided will always result in a validation error,
|
119
|
+
regardless of whether the input using a static or dynamic schema. For optional outputs
|
120
|
+
:py:meth:`get_schema() <tracdap.rt.api.TracContext.get_schema>` can be called, with the
|
121
|
+
normal proviso that dynamic schemas must first be set by calling
|
122
|
+
:py:meth:`put_schema() <tracdap.rt.api.TracContext.put_schema>`.
|
110
123
|
|
111
124
|
Attempting to retrieve the schema for a dataset that is not defined as a model input or output
|
112
125
|
will result in a runtime validation error, even if that dataset exists in the job config and
|
@@ -152,6 +165,36 @@ class TracContext:
|
|
152
165
|
"""
|
153
166
|
pass
|
154
167
|
|
168
|
+
@_abc.abstractmethod
|
169
|
+
def put_schema(self, dataset_name: str, schema: SchemaDefinition):
|
170
|
+
|
171
|
+
"""
|
172
|
+
Set the schema of a dynamic model output
|
173
|
+
|
174
|
+
For outputs marked as dynamic, a :py:class:`SchemaDefinition <tracdap.rt.metadata.SchemaDefinition>`
|
175
|
+
must be supplied before attempting to save the data. TRAC API functions are available to help with
|
176
|
+
building schemas, such as :py:func:`trac.F() <tracdap.rt.api.F>` to define fields or
|
177
|
+
:py:func:`load_schema() <tracdap.rt.api.load_schema>` to load predefined schemas.
|
178
|
+
Once a schema has been set, it can be retrieved by calling
|
179
|
+
:py:meth:`get_schema() <tracdap.rt.api.TracContext.get_schema>` as normal.
|
180
|
+
If :py:meth:`put_schema() <tracdap.rt.api.TracContext.put_schema>` is called for an optional
|
181
|
+
output the model must also supply data for that output, otherwise TRAC will report a runtime
|
182
|
+
validation error after the model completes.
|
183
|
+
|
184
|
+
Each schema can only be set once and the schema will be validated using the normal
|
185
|
+
validation rules. If validation fails this method will raise
|
186
|
+
:py:class:`ERuntimeValidation <tracdap.rt.exceptions.ERuntimeValidation>`.
|
187
|
+
Attempting to set the schema for a dataset that is not defined as a dynamic model output
|
188
|
+
for the current model will result in a runtime validation error.
|
189
|
+
|
190
|
+
:param dataset_name: The name of the output to set the schema for
|
191
|
+
:param schema: A TRAC schema definition to use for the named output
|
192
|
+
:type schema: :py:class:`SchemaDefinition <tracdap.rt.metadata.SchemaDefinition>`
|
193
|
+
:raises: :py:class:`ERuntimeValidation <tracdap.rt.exceptions.ERuntimeValidation>`
|
194
|
+
"""
|
195
|
+
|
196
|
+
pass
|
197
|
+
|
155
198
|
@_abc.abstractmethod
|
156
199
|
def put_pandas_table(self, dataset_name: str, dataset: pandas.DataFrame):
|
157
200
|
|