tracdap-runtime 0.6.3__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.
Files changed (38) hide show
  1. tracdap/rt/_exec/context.py +207 -100
  2. tracdap/rt/_exec/dev_mode.py +43 -3
  3. tracdap/rt/_exec/functions.py +14 -17
  4. tracdap/rt/_impl/data.py +70 -5
  5. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +18 -18
  6. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +18 -18
  7. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +8 -4
  8. tracdap/rt/_impl/static_api.py +26 -10
  9. tracdap/rt/_impl/validation.py +37 -4
  10. tracdap/rt/_version.py +1 -1
  11. tracdap/rt/api/hook.py +2 -4
  12. tracdap/rt/api/model_api.py +50 -7
  13. tracdap/rt/api/static_api.py +14 -6
  14. tracdap/rt/config/common.py +17 -17
  15. tracdap/rt/config/job.py +2 -2
  16. tracdap/rt/config/platform.py +25 -25
  17. tracdap/rt/config/result.py +2 -2
  18. tracdap/rt/config/runtime.py +3 -3
  19. tracdap/rt/launch/cli.py +7 -4
  20. tracdap/rt/launch/launch.py +19 -3
  21. tracdap/rt/metadata/common.py +2 -2
  22. tracdap/rt/metadata/custom.py +3 -3
  23. tracdap/rt/metadata/data.py +12 -12
  24. tracdap/rt/metadata/file.py +6 -6
  25. tracdap/rt/metadata/flow.py +6 -6
  26. tracdap/rt/metadata/job.py +8 -8
  27. tracdap/rt/metadata/model.py +15 -11
  28. tracdap/rt/metadata/object_id.py +8 -8
  29. tracdap/rt/metadata/search.py +5 -5
  30. tracdap/rt/metadata/stoarge.py +6 -6
  31. tracdap/rt/metadata/tag.py +1 -1
  32. tracdap/rt/metadata/tag_update.py +1 -1
  33. tracdap/rt/metadata/type.py +4 -4
  34. {tracdap_runtime-0.6.3.dist-info → tracdap_runtime-0.6.4.dist-info}/METADATA +1 -1
  35. {tracdap_runtime-0.6.3.dist-info → tracdap_runtime-0.6.4.dist-info}/RECORD +38 -38
  36. {tracdap_runtime-0.6.3.dist-info → tracdap_runtime-0.6.4.dist-info}/LICENSE +0 -0
  37. {tracdap_runtime-0.6.3.dist-info → tracdap_runtime-0.6.4.dist-info}/WHEEL +0 -0
  38. {tracdap_runtime-0.6.3.dist-info → tracdap_runtime-0.6.4.dist-info}/top_level.txt +0 -0
tracdap/rt/_impl/data.py CHANGED
@@ -74,17 +74,21 @@ class DataView:
74
74
 
75
75
  parts: tp.Dict[DataPartKey, tp.List[DataItem]]
76
76
 
77
+ @staticmethod
78
+ def create_empty() -> DataView:
79
+ return DataView(_meta.SchemaDefinition(), pa.schema([]), dict())
80
+
77
81
  @staticmethod
78
82
  def for_trac_schema(trac_schema: _meta.SchemaDefinition):
79
83
  arrow_schema = DataMapping.trac_to_arrow_schema(trac_schema)
80
84
  return DataView(trac_schema, arrow_schema, dict())
81
85
 
82
- def is_empty(self) -> bool:
83
- return self.parts is None or len(self.parts) == 0
86
+ def with_trac_schema(self, trac_schema: _meta.SchemaDefinition):
87
+ arrow_schema = DataMapping.trac_to_arrow_schema(trac_schema)
88
+ return DataView(trac_schema, arrow_schema, self.parts)
84
89
 
85
- @staticmethod
86
- def create_empty() -> DataView:
87
- return DataView(_meta.SchemaDefinition(), pa.schema([]), dict())
90
+ def is_empty(self) -> bool:
91
+ return self.parts is None or not any(self.parts.values())
88
92
 
89
93
 
90
94
  class _DataInternal:
@@ -170,9 +174,29 @@ class DataMapping:
170
174
  pa.float16(): pd.Float32Dtype(),
171
175
  pa.float32(): pd.Float32Dtype(),
172
176
  pa.float64(): pd.Float64Dtype(),
177
+ pa.string(): pd.StringDtype(),
173
178
  pa.utf8(): pd.StringDtype()
174
179
  }
175
180
 
181
+ __ARROW_TO_TRAC_BASIC_TYPE_MAPPING = {
182
+ pa.bool_(): _meta.BasicType.BOOLEAN,
183
+ pa.int8(): _meta.BasicType.INTEGER,
184
+ pa.int16(): _meta.BasicType.INTEGER,
185
+ pa.int32(): _meta.BasicType.INTEGER,
186
+ pa.int64():_meta.BasicType.INTEGER,
187
+ pa.uint8(): _meta.BasicType.INTEGER,
188
+ pa.uint16(): _meta.BasicType.INTEGER,
189
+ pa.uint32(): _meta.BasicType.INTEGER,
190
+ pa.uint64(): _meta.BasicType.INTEGER,
191
+ pa.float16(): _meta.BasicType.FLOAT,
192
+ pa.float32(): _meta.BasicType.FLOAT,
193
+ pa.float64(): _meta.BasicType.FLOAT,
194
+ pa.string(): _meta.BasicType.STRING,
195
+ pa.utf8(): _meta.BasicType.STRING,
196
+ pa.date32(): _meta.BasicType.DATE,
197
+ pa.date64(): _meta.BasicType.DATE
198
+ }
199
+
176
200
  @staticmethod
177
201
  def arrow_to_python_type(arrow_type: pa.DataType) -> type:
178
202
 
@@ -265,6 +289,47 @@ class DataMapping:
265
289
  cls.__TRAC_DECIMAL_PRECISION,
266
290
  cls.__TRAC_DECIMAL_SCALE)
267
291
 
292
+ @classmethod
293
+ def arrow_to_trac_schema(cls, arrow_schema: pa.Schema) -> _meta.SchemaDefinition:
294
+
295
+ trac_fields = list(
296
+ cls.arrow_to_trac_field(i, arrow_schema.field(i))
297
+ for (i, f) in enumerate(arrow_schema.names))
298
+
299
+ return _meta.SchemaDefinition(
300
+ schemaType=_meta.SchemaType.TABLE,
301
+ partType=_meta.PartType.PART_ROOT,
302
+ table=_meta.TableSchema(trac_fields))
303
+
304
+ @classmethod
305
+ def arrow_to_trac_field(cls, field_index: int, field: pa.Field) -> _meta.FieldSchema:
306
+
307
+ field_type = cls.arrow_to_trac_type(field.type)
308
+ label = field.metadata["label"] if field.metadata and "label" in field.metadata else field.name
309
+
310
+ return _meta.FieldSchema(
311
+ field.name, field_index, field_type,
312
+ label=label,
313
+ businessKey=False,
314
+ notNull=not field.nullable,
315
+ categorical=False)
316
+
317
+ @classmethod
318
+ def arrow_to_trac_type(cls, arrow_type: pa.DataType) -> _meta.BasicType:
319
+
320
+ mapped_basic_type = cls.__ARROW_TO_TRAC_BASIC_TYPE_MAPPING.get(arrow_type) # noqa
321
+
322
+ if mapped_basic_type is not None:
323
+ return mapped_basic_type
324
+
325
+ if pa.types.is_decimal(arrow_type):
326
+ return _meta.BasicType.DECIMAL
327
+
328
+ if pa.types.is_timestamp(arrow_type):
329
+ return _meta.BasicType.DATETIME
330
+
331
+ raise _ex.ETracInternal(f"No data type mapping available for Arrow type [{arrow_type}]")
332
+
268
333
  @classmethod
269
334
  def pandas_date_type(cls):
270
335
  return cls.__PANDAS_DATE_TYPE
@@ -16,7 +16,7 @@ from tracdap.rt._impl.grpc.tracdap.metadata import type_pb2 as tracdap_dot_rt_do
16
16
  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
17
17
 
18
18
 
19
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n1tracdap/rt/_impl/grpc/tracdap/metadata/data.proto\x12\x10tracdap.metadata\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/type.proto\x1a\x36tracdap/rt/_impl/grpc/tracdap/metadata/object_id.proto\"\xe7\x01\n\x0b\x46ieldSchema\x12\x11\n\tfieldName\x18\x01 \x01(\t\x12\x12\n\nfieldOrder\x18\x02 \x01(\x11\x12.\n\tfieldType\x18\x03 \x01(\x0e\x32\x1b.tracdap.metadata.BasicType\x12\r\n\x05label\x18\x04 \x01(\t\x12\x13\n\x0b\x62usinessKey\x18\x05 \x01(\x08\x12\x13\n\x0b\x63\x61tegorical\x18\x06 \x01(\x08\x12\x14\n\x07notNull\x18\x08 \x01(\x08H\x00\x88\x01\x01\x12\x17\n\nformatCode\x18\x07 \x01(\tH\x01\x88\x01\x01\x42\n\n\x08_notNullB\r\n\x0b_formatCode\"<\n\x0bTableSchema\x12-\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x1d.tracdap.metadata.FieldSchema\"\xba\x01\n\x10SchemaDefinition\x12\x30\n\nschemaType\x18\x01 \x01(\x0e\x32\x1c.tracdap.metadata.SchemaType\x12,\n\x08partType\x18\x02 \x01(\x0e\x32\x1a.tracdap.metadata.PartType\x12.\n\x05table\x18\x03 \x01(\x0b\x32\x1d.tracdap.metadata.TableSchemaH\x00\x42\x16\n\x14schemaTypeDefinition\"\x81\x02\n\x07PartKey\x12\x11\n\topaqueKey\x18\x01 \x01(\t\x12,\n\x08partType\x18\x02 \x01(\x0e\x32\x1a.tracdap.metadata.PartType\x12+\n\npartValues\x18\x03 \x03(\x0b\x32\x17.tracdap.metadata.Value\x12\x32\n\x0cpartRangeMin\x18\x04 \x01(\x0b\x32\x17.tracdap.metadata.ValueH\x00\x88\x01\x01\x12\x32\n\x0cpartRangeMax\x18\x05 \x01(\x0b\x32\x17.tracdap.metadata.ValueH\x01\x88\x01\x01\x42\x0f\n\r_partRangeMinB\x0f\n\r_partRangeMax\"\xba\x04\n\x0e\x44\x61taDefinition\x12\x31\n\x08schemaId\x18\x01 \x01(\x0b\x32\x1d.tracdap.metadata.TagSelectorH\x00\x12\x34\n\x06schema\x18\x02 \x01(\x0b\x32\".tracdap.metadata.SchemaDefinitionH\x00\x12:\n\x05parts\x18\x03 \x03(\x0b\x32+.tracdap.metadata.DataDefinition.PartsEntry\x12\x30\n\tstorageId\x18\x04 \x01(\x0b\x32\x1d.tracdap.metadata.TagSelector\x1a-\n\x05\x44\x65lta\x12\x12\n\ndeltaIndex\x18\x01 \x01(\r\x12\x10\n\x08\x64\x61taItem\x18\x02 \x01(\t\x1aQ\n\x04Snap\x12\x11\n\tsnapIndex\x18\x01 \x01(\r\x12\x36\n\x06\x64\x65ltas\x18\x02 \x03(\x0b\x32&.tracdap.metadata.DataDefinition.Delta\x1ag\n\x04Part\x12*\n\x07partKey\x18\x01 \x01(\x0b\x32\x19.tracdap.metadata.PartKey\x12\x33\n\x04snap\x18\x02 \x01(\x0b\x32%.tracdap.metadata.DataDefinition.Snap\x1aS\n\nPartsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x34\n\x05value\x18\x02 \x01(\x0b\x32%.tracdap.metadata.DataDefinition.Part:\x02\x38\x01\x42\x11\n\x0fschemaSpecifier*0\n\nSchemaType\x12\x17\n\x13SCHEMA_TYPE_NOT_SET\x10\x00\x12\t\n\x05TABLE\x10\x01*?\n\x08PartType\x12\r\n\tPART_ROOT\x10\x00\x12\x11\n\rPART_BY_RANGE\x10\x01\x12\x11\n\rPART_BY_VALUE\x10\x02\x42\x1e\n\x1aorg.finos.tracdap.metadataP\x01\x62\x06proto3')
19
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n1tracdap/rt/_impl/grpc/tracdap/metadata/data.proto\x12\x10tracdap.metadata\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/type.proto\x1a\x36tracdap/rt/_impl/grpc/tracdap/metadata/object_id.proto\"\xe7\x01\n\x0b\x46ieldSchema\x12\x11\n\tfieldName\x18\x01 \x01(\t\x12\x12\n\nfieldOrder\x18\x02 \x01(\x11\x12.\n\tfieldType\x18\x03 \x01(\x0e\x32\x1b.tracdap.metadata.BasicType\x12\r\n\x05label\x18\x04 \x01(\t\x12\x13\n\x0b\x62usinessKey\x18\x05 \x01(\x08\x12\x13\n\x0b\x63\x61tegorical\x18\x06 \x01(\x08\x12\x14\n\x07notNull\x18\x08 \x01(\x08H\x00\x88\x01\x01\x12\x17\n\nformatCode\x18\x07 \x01(\tH\x01\x88\x01\x01\x42\n\n\x08_notNullB\r\n\x0b_formatCode\"<\n\x0bTableSchema\x12-\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x1d.tracdap.metadata.FieldSchema\"\xb3\x01\n\x10SchemaDefinition\x12\x30\n\nschemaType\x18\x01 \x01(\x0e\x32\x1c.tracdap.metadata.SchemaType\x12,\n\x08partType\x18\x02 \x01(\x0e\x32\x1a.tracdap.metadata.PartType\x12.\n\x05table\x18\x03 \x01(\x0b\x32\x1d.tracdap.metadata.TableSchemaH\x00\x42\x0f\n\rschemaDetails\"\x81\x02\n\x07PartKey\x12\x11\n\topaqueKey\x18\x01 \x01(\t\x12,\n\x08partType\x18\x02 \x01(\x0e\x32\x1a.tracdap.metadata.PartType\x12+\n\npartValues\x18\x03 \x03(\x0b\x32\x17.tracdap.metadata.Value\x12\x32\n\x0cpartRangeMin\x18\x04 \x01(\x0b\x32\x17.tracdap.metadata.ValueH\x00\x88\x01\x01\x12\x32\n\x0cpartRangeMax\x18\x05 \x01(\x0b\x32\x17.tracdap.metadata.ValueH\x01\x88\x01\x01\x42\x0f\n\r_partRangeMinB\x0f\n\r_partRangeMax\"\xba\x04\n\x0e\x44\x61taDefinition\x12\x31\n\x08schemaId\x18\x01 \x01(\x0b\x32\x1d.tracdap.metadata.TagSelectorH\x00\x12\x34\n\x06schema\x18\x02 \x01(\x0b\x32\".tracdap.metadata.SchemaDefinitionH\x00\x12:\n\x05parts\x18\x03 \x03(\x0b\x32+.tracdap.metadata.DataDefinition.PartsEntry\x12\x30\n\tstorageId\x18\x04 \x01(\x0b\x32\x1d.tracdap.metadata.TagSelector\x1a-\n\x05\x44\x65lta\x12\x12\n\ndeltaIndex\x18\x01 \x01(\r\x12\x10\n\x08\x64\x61taItem\x18\x02 \x01(\t\x1aQ\n\x04Snap\x12\x11\n\tsnapIndex\x18\x01 \x01(\r\x12\x36\n\x06\x64\x65ltas\x18\x02 \x03(\x0b\x32&.tracdap.metadata.DataDefinition.Delta\x1ag\n\x04Part\x12*\n\x07partKey\x18\x01 \x01(\x0b\x32\x19.tracdap.metadata.PartKey\x12\x33\n\x04snap\x18\x02 \x01(\x0b\x32%.tracdap.metadata.DataDefinition.Snap\x1aS\n\nPartsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x34\n\x05value\x18\x02 \x01(\x0b\x32%.tracdap.metadata.DataDefinition.Part:\x02\x38\x01\x42\x11\n\x0fschemaSpecifier*0\n\nSchemaType\x12\x17\n\x13SCHEMA_TYPE_NOT_SET\x10\x00\x12\t\n\x05TABLE\x10\x01*?\n\x08PartType\x12\r\n\tPART_ROOT\x10\x00\x12\x11\n\rPART_BY_RANGE\x10\x01\x12\x11\n\rPART_BY_VALUE\x10\x02\x42\x1e\n\x1aorg.finos.tracdap.metadataP\x01\x62\x06proto3')
20
20
 
21
21
  _globals = globals()
22
22
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -26,26 +26,26 @@ if _descriptor._USE_C_DESCRIPTORS == False:
26
26
  _globals['DESCRIPTOR']._serialized_options = b'\n\032org.finos.tracdap.metadataP\001'
27
27
  _globals['_DATADEFINITION_PARTSENTRY']._options = None
28
28
  _globals['_DATADEFINITION_PARTSENTRY']._serialized_options = b'8\001'
29
- _globals['_SCHEMATYPE']._serialized_start=1496
30
- _globals['_SCHEMATYPE']._serialized_end=1544
31
- _globals['_PARTTYPE']._serialized_start=1546
32
- _globals['_PARTTYPE']._serialized_end=1609
29
+ _globals['_SCHEMATYPE']._serialized_start=1489
30
+ _globals['_SCHEMATYPE']._serialized_end=1537
31
+ _globals['_PARTTYPE']._serialized_start=1539
32
+ _globals['_PARTTYPE']._serialized_end=1602
33
33
  _globals['_FIELDSCHEMA']._serialized_start=179
34
34
  _globals['_FIELDSCHEMA']._serialized_end=410
35
35
  _globals['_TABLESCHEMA']._serialized_start=412
36
36
  _globals['_TABLESCHEMA']._serialized_end=472
37
37
  _globals['_SCHEMADEFINITION']._serialized_start=475
38
- _globals['_SCHEMADEFINITION']._serialized_end=661
39
- _globals['_PARTKEY']._serialized_start=664
40
- _globals['_PARTKEY']._serialized_end=921
41
- _globals['_DATADEFINITION']._serialized_start=924
42
- _globals['_DATADEFINITION']._serialized_end=1494
43
- _globals['_DATADEFINITION_DELTA']._serialized_start=1157
44
- _globals['_DATADEFINITION_DELTA']._serialized_end=1202
45
- _globals['_DATADEFINITION_SNAP']._serialized_start=1204
46
- _globals['_DATADEFINITION_SNAP']._serialized_end=1285
47
- _globals['_DATADEFINITION_PART']._serialized_start=1287
48
- _globals['_DATADEFINITION_PART']._serialized_end=1390
49
- _globals['_DATADEFINITION_PARTSENTRY']._serialized_start=1392
50
- _globals['_DATADEFINITION_PARTSENTRY']._serialized_end=1475
38
+ _globals['_SCHEMADEFINITION']._serialized_end=654
39
+ _globals['_PARTKEY']._serialized_start=657
40
+ _globals['_PARTKEY']._serialized_end=914
41
+ _globals['_DATADEFINITION']._serialized_start=917
42
+ _globals['_DATADEFINITION']._serialized_end=1487
43
+ _globals['_DATADEFINITION_DELTA']._serialized_start=1150
44
+ _globals['_DATADEFINITION_DELTA']._serialized_end=1195
45
+ _globals['_DATADEFINITION_SNAP']._serialized_start=1197
46
+ _globals['_DATADEFINITION_SNAP']._serialized_end=1278
47
+ _globals['_DATADEFINITION_PART']._serialized_start=1280
48
+ _globals['_DATADEFINITION_PART']._serialized_end=1383
49
+ _globals['_DATADEFINITION_PARTSENTRY']._serialized_start=1385
50
+ _globals['_DATADEFINITION_PARTSENTRY']._serialized_end=1468
51
51
  # @@protoc_insertion_point(module_scope)
@@ -16,7 +16,7 @@ from tracdap.rt._impl.grpc.tracdap.metadata import type_pb2 as tracdap_dot_rt_do
16
16
  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
17
 
18
18
 
19
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n2tracdap/rt/_impl/grpc/tracdap/metadata/model.proto\x12\x10tracdap.metadata\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/type.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/data.proto\"\xab\x02\n\x0eModelParameter\x12\x33\n\tparamType\x18\x01 \x01(\x0b\x32 .tracdap.metadata.TypeDescriptor\x12\r\n\x05label\x18\x02 \x01(\t\x12\x32\n\x0c\x64\x65\x66\x61ultValue\x18\x03 \x01(\x0b\x32\x17.tracdap.metadata.ValueH\x00\x88\x01\x01\x12\x44\n\nparamProps\x18\x04 \x03(\x0b\x32\x30.tracdap.metadata.ModelParameter.ParamPropsEntry\x1aJ\n\x0fParamPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x0f\n\r_defaultValue\"\x8a\x02\n\x10ModelInputSchema\x12\x32\n\x06schema\x18\x01 \x01(\x0b\x32\".tracdap.metadata.SchemaDefinition\x12\x12\n\x05label\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x08optional\x18\x03 \x01(\x08\x12\x46\n\ninputProps\x18\x04 \x03(\x0b\x32\x32.tracdap.metadata.ModelInputSchema.InputPropsEntry\x1aJ\n\x0fInputPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x08\n\x06_label\"\x8f\x02\n\x11ModelOutputSchema\x12\x32\n\x06schema\x18\x01 \x01(\x0b\x32\".tracdap.metadata.SchemaDefinition\x12\x12\n\x05label\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x08optional\x18\x03 \x01(\x08\x12I\n\x0boutputProps\x18\x04 \x03(\x0b\x32\x34.tracdap.metadata.ModelOutputSchema.OutputPropsEntry\x1aK\n\x10OutputPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x08\n\x06_label\"\x9e\x06\n\x0fModelDefinition\x12\x10\n\x08language\x18\x01 \x01(\t\x12\x12\n\nrepository\x18\x02 \x01(\t\x12\x19\n\x0cpackageGroup\x18\n \x01(\tH\x00\x88\x01\x01\x12\x0f\n\x07package\x18\x0b \x01(\t\x12\x0f\n\x07version\x18\x06 \x01(\t\x12\x12\n\nentryPoint\x18\x05 \x01(\t\x12\x11\n\x04path\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x45\n\nparameters\x18\x07 \x03(\x0b\x32\x31.tracdap.metadata.ModelDefinition.ParametersEntry\x12=\n\x06inputs\x18\x08 \x03(\x0b\x32-.tracdap.metadata.ModelDefinition.InputsEntry\x12?\n\x07outputs\x18\t \x03(\x0b\x32..tracdap.metadata.ModelDefinition.OutputsEntry\x12Q\n\x10staticAttributes\x18\x0c \x03(\x0b\x32\x37.tracdap.metadata.ModelDefinition.StaticAttributesEntry\x1aS\n\x0fParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x01(\x0b\x32 .tracdap.metadata.ModelParameter:\x02\x38\x01\x1aQ\n\x0bInputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x31\n\x05value\x18\x02 \x01(\x0b\x32\".tracdap.metadata.ModelInputSchema:\x02\x38\x01\x1aS\n\x0cOutputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x32\n\x05value\x18\x02 \x01(\x0b\x32#.tracdap.metadata.ModelOutputSchema:\x02\x38\x01\x1aP\n\x15StaticAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x0f\n\r_packageGroupB\x07\n\x05_pathB\x1e\n\x1aorg.finos.tracdap.metadataP\x01\x62\x06proto3')
19
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n2tracdap/rt/_impl/grpc/tracdap/metadata/model.proto\x12\x10tracdap.metadata\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/type.proto\x1a\x31tracdap/rt/_impl/grpc/tracdap/metadata/data.proto\"\xab\x02\n\x0eModelParameter\x12\x33\n\tparamType\x18\x01 \x01(\x0b\x32 .tracdap.metadata.TypeDescriptor\x12\r\n\x05label\x18\x02 \x01(\t\x12\x32\n\x0c\x64\x65\x66\x61ultValue\x18\x03 \x01(\x0b\x32\x17.tracdap.metadata.ValueH\x00\x88\x01\x01\x12\x44\n\nparamProps\x18\x04 \x03(\x0b\x32\x30.tracdap.metadata.ModelParameter.ParamPropsEntry\x1aJ\n\x0fParamPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x0f\n\r_defaultValue\"\x9b\x02\n\x10ModelInputSchema\x12\x32\n\x06schema\x18\x01 \x01(\x0b\x32\".tracdap.metadata.SchemaDefinition\x12\x12\n\x05label\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x08optional\x18\x03 \x01(\x08\x12\x0f\n\x07\x64ynamic\x18\x05 \x01(\x08\x12\x46\n\ninputProps\x18\x04 \x03(\x0b\x32\x32.tracdap.metadata.ModelInputSchema.InputPropsEntry\x1aJ\n\x0fInputPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x08\n\x06_label\"\xa0\x02\n\x11ModelOutputSchema\x12\x32\n\x06schema\x18\x01 \x01(\x0b\x32\".tracdap.metadata.SchemaDefinition\x12\x12\n\x05label\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x08optional\x18\x03 \x01(\x08\x12\x0f\n\x07\x64ynamic\x18\x05 \x01(\x08\x12I\n\x0boutputProps\x18\x04 \x03(\x0b\x32\x34.tracdap.metadata.ModelOutputSchema.OutputPropsEntry\x1aK\n\x10OutputPropsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x08\n\x06_label\"\x9e\x06\n\x0fModelDefinition\x12\x10\n\x08language\x18\x01 \x01(\t\x12\x12\n\nrepository\x18\x02 \x01(\t\x12\x19\n\x0cpackageGroup\x18\n \x01(\tH\x00\x88\x01\x01\x12\x0f\n\x07package\x18\x0b \x01(\t\x12\x0f\n\x07version\x18\x06 \x01(\t\x12\x12\n\nentryPoint\x18\x05 \x01(\t\x12\x11\n\x04path\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x45\n\nparameters\x18\x07 \x03(\x0b\x32\x31.tracdap.metadata.ModelDefinition.ParametersEntry\x12=\n\x06inputs\x18\x08 \x03(\x0b\x32-.tracdap.metadata.ModelDefinition.InputsEntry\x12?\n\x07outputs\x18\t \x03(\x0b\x32..tracdap.metadata.ModelDefinition.OutputsEntry\x12Q\n\x10staticAttributes\x18\x0c \x03(\x0b\x32\x37.tracdap.metadata.ModelDefinition.StaticAttributesEntry\x1aS\n\x0fParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x01(\x0b\x32 .tracdap.metadata.ModelParameter:\x02\x38\x01\x1aQ\n\x0bInputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x31\n\x05value\x18\x02 \x01(\x0b\x32\".tracdap.metadata.ModelInputSchema:\x02\x38\x01\x1aS\n\x0cOutputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x32\n\x05value\x18\x02 \x01(\x0b\x32#.tracdap.metadata.ModelOutputSchema:\x02\x38\x01\x1aP\n\x15StaticAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.tracdap.metadata.Value:\x02\x38\x01\x42\x0f\n\r_packageGroupB\x07\n\x05_pathB\x1e\n\x1aorg.finos.tracdap.metadataP\x01\x62\x06proto3')
20
20
 
21
21
  _globals = globals()
22
22
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -43,21 +43,21 @@ if _descriptor._USE_C_DESCRIPTORS == False:
43
43
  _globals['_MODELPARAMETER_PARAMPROPSENTRY']._serialized_start=383
44
44
  _globals['_MODELPARAMETER_PARAMPROPSENTRY']._serialized_end=457
45
45
  _globals['_MODELINPUTSCHEMA']._serialized_start=477
46
- _globals['_MODELINPUTSCHEMA']._serialized_end=743
47
- _globals['_MODELINPUTSCHEMA_INPUTPROPSENTRY']._serialized_start=659
48
- _globals['_MODELINPUTSCHEMA_INPUTPROPSENTRY']._serialized_end=733
49
- _globals['_MODELOUTPUTSCHEMA']._serialized_start=746
50
- _globals['_MODELOUTPUTSCHEMA']._serialized_end=1017
51
- _globals['_MODELOUTPUTSCHEMA_OUTPUTPROPSENTRY']._serialized_start=932
52
- _globals['_MODELOUTPUTSCHEMA_OUTPUTPROPSENTRY']._serialized_end=1007
53
- _globals['_MODELDEFINITION']._serialized_start=1020
54
- _globals['_MODELDEFINITION']._serialized_end=1818
55
- _globals['_MODELDEFINITION_PARAMETERSENTRY']._serialized_start=1459
56
- _globals['_MODELDEFINITION_PARAMETERSENTRY']._serialized_end=1542
57
- _globals['_MODELDEFINITION_INPUTSENTRY']._serialized_start=1544
58
- _globals['_MODELDEFINITION_INPUTSENTRY']._serialized_end=1625
59
- _globals['_MODELDEFINITION_OUTPUTSENTRY']._serialized_start=1627
60
- _globals['_MODELDEFINITION_OUTPUTSENTRY']._serialized_end=1710
61
- _globals['_MODELDEFINITION_STATICATTRIBUTESENTRY']._serialized_start=1712
62
- _globals['_MODELDEFINITION_STATICATTRIBUTESENTRY']._serialized_end=1792
46
+ _globals['_MODELINPUTSCHEMA']._serialized_end=760
47
+ _globals['_MODELINPUTSCHEMA_INPUTPROPSENTRY']._serialized_start=676
48
+ _globals['_MODELINPUTSCHEMA_INPUTPROPSENTRY']._serialized_end=750
49
+ _globals['_MODELOUTPUTSCHEMA']._serialized_start=763
50
+ _globals['_MODELOUTPUTSCHEMA']._serialized_end=1051
51
+ _globals['_MODELOUTPUTSCHEMA_OUTPUTPROPSENTRY']._serialized_start=966
52
+ _globals['_MODELOUTPUTSCHEMA_OUTPUTPROPSENTRY']._serialized_end=1041
53
+ _globals['_MODELDEFINITION']._serialized_start=1054
54
+ _globals['_MODELDEFINITION']._serialized_end=1852
55
+ _globals['_MODELDEFINITION_PARAMETERSENTRY']._serialized_start=1493
56
+ _globals['_MODELDEFINITION_PARAMETERSENTRY']._serialized_end=1576
57
+ _globals['_MODELDEFINITION_INPUTSENTRY']._serialized_start=1578
58
+ _globals['_MODELDEFINITION_INPUTSENTRY']._serialized_end=1659
59
+ _globals['_MODELDEFINITION_OUTPUTSENTRY']._serialized_start=1661
60
+ _globals['_MODELDEFINITION_OUTPUTSENTRY']._serialized_end=1744
61
+ _globals['_MODELDEFINITION_STATICATTRIBUTESENTRY']._serialized_start=1746
62
+ _globals['_MODELDEFINITION_STATICATTRIBUTESENTRY']._serialized_end=1826
63
63
  # @@protoc_insertion_point(module_scope)
@@ -27,7 +27,7 @@ class ModelParameter(_message.Message):
27
27
  def __init__(self, paramType: _Optional[_Union[_type_pb2.TypeDescriptor, _Mapping]] = ..., label: _Optional[str] = ..., defaultValue: _Optional[_Union[_type_pb2.Value, _Mapping]] = ..., paramProps: _Optional[_Mapping[str, _type_pb2.Value]] = ...) -> None: ...
28
28
 
29
29
  class ModelInputSchema(_message.Message):
30
- __slots__ = ("schema", "label", "optional", "inputProps")
30
+ __slots__ = ("schema", "label", "optional", "dynamic", "inputProps")
31
31
  class InputPropsEntry(_message.Message):
32
32
  __slots__ = ("key", "value")
33
33
  KEY_FIELD_NUMBER: _ClassVar[int]
@@ -38,15 +38,17 @@ class ModelInputSchema(_message.Message):
38
38
  SCHEMA_FIELD_NUMBER: _ClassVar[int]
39
39
  LABEL_FIELD_NUMBER: _ClassVar[int]
40
40
  OPTIONAL_FIELD_NUMBER: _ClassVar[int]
41
+ DYNAMIC_FIELD_NUMBER: _ClassVar[int]
41
42
  INPUTPROPS_FIELD_NUMBER: _ClassVar[int]
42
43
  schema: _data_pb2.SchemaDefinition
43
44
  label: str
44
45
  optional: bool
46
+ dynamic: bool
45
47
  inputProps: _containers.MessageMap[str, _type_pb2.Value]
46
- def __init__(self, schema: _Optional[_Union[_data_pb2.SchemaDefinition, _Mapping]] = ..., label: _Optional[str] = ..., optional: bool = ..., inputProps: _Optional[_Mapping[str, _type_pb2.Value]] = ...) -> None: ...
48
+ def __init__(self, schema: _Optional[_Union[_data_pb2.SchemaDefinition, _Mapping]] = ..., label: _Optional[str] = ..., optional: bool = ..., dynamic: bool = ..., inputProps: _Optional[_Mapping[str, _type_pb2.Value]] = ...) -> None: ...
47
49
 
48
50
  class ModelOutputSchema(_message.Message):
49
- __slots__ = ("schema", "label", "optional", "outputProps")
51
+ __slots__ = ("schema", "label", "optional", "dynamic", "outputProps")
50
52
  class OutputPropsEntry(_message.Message):
51
53
  __slots__ = ("key", "value")
52
54
  KEY_FIELD_NUMBER: _ClassVar[int]
@@ -57,12 +59,14 @@ class ModelOutputSchema(_message.Message):
57
59
  SCHEMA_FIELD_NUMBER: _ClassVar[int]
58
60
  LABEL_FIELD_NUMBER: _ClassVar[int]
59
61
  OPTIONAL_FIELD_NUMBER: _ClassVar[int]
62
+ DYNAMIC_FIELD_NUMBER: _ClassVar[int]
60
63
  OUTPUTPROPS_FIELD_NUMBER: _ClassVar[int]
61
64
  schema: _data_pb2.SchemaDefinition
62
65
  label: str
63
66
  optional: bool
67
+ dynamic: bool
64
68
  outputProps: _containers.MessageMap[str, _type_pb2.Value]
65
- def __init__(self, schema: _Optional[_Union[_data_pb2.SchemaDefinition, _Mapping]] = ..., label: _Optional[str] = ..., optional: bool = ..., outputProps: _Optional[_Mapping[str, _type_pb2.Value]] = ...) -> None: ...
69
+ def __init__(self, schema: _Optional[_Union[_data_pb2.SchemaDefinition, _Mapping]] = ..., label: _Optional[str] = ..., optional: bool = ..., dynamic: bool = ..., outputProps: _Optional[_Mapping[str, _type_pb2.Value]] = ...) -> None: ...
66
70
 
67
71
  class ModelDefinition(_message.Message):
68
72
  __slots__ = ("language", "repository", "packageGroup", "package", "version", "entryPoint", "path", "parameters", "inputs", "outputs", "staticAttributes")
@@ -154,33 +154,49 @@ class StaticApiImpl(_StaticApiHook):
154
154
 
155
155
  def define_input_table(
156
156
  self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
157
- label: _tp.Optional[str] = None,
158
- optional: bool = False,
157
+ label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
159
158
  input_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
160
159
  -> _meta.ModelInputSchema:
161
160
 
162
161
  _val.validate_signature(
163
162
  self.define_input_table, *fields,
164
- label=label, optional=optional,
163
+ label=label, optional=optional, dynamic=dynamic,
165
164
  input_props=input_props)
166
165
 
167
- schema_def = self.define_schema(*fields, schema_type=_meta.SchemaType.TABLE)
168
- return _meta.ModelInputSchema(schema=schema_def, label=label, optional=optional, inputProps=input_props)
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)
172
+
173
+ return _meta.ModelInputSchema(
174
+ schema=schema_def, label=label,
175
+ optional=optional, dynamic=dynamic,
176
+ inputProps=input_props)
169
177
 
170
178
  def define_output_table(
171
179
  self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
172
- label: _tp.Optional[str] = None,
173
- optional: bool = False,
180
+ label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
174
181
  output_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
175
182
  -> _meta.ModelOutputSchema:
176
183
 
177
184
  _val.validate_signature(
178
185
  self.define_output_table, *fields,
179
- label=label, optional=optional,
186
+ label=label, optional=optional, dynamic=dynamic,
180
187
  output_props=output_props)
181
188
 
182
- schema_def = self.define_schema(*fields, schema_type=_meta.SchemaType.TABLE)
183
- return _meta.ModelOutputSchema(schema=schema_def, label=label, optional=optional, outputProps=output_props)
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)
195
+
196
+ return _meta.ModelOutputSchema(
197
+ schema=schema_def, label=label,
198
+ optional=optional, dynamic=dynamic,
199
+ outputProps=output_props)
184
200
 
185
201
  @staticmethod
186
202
  def _build_named_dict(
@@ -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
- _StaticValidator.quick_validate_model_def(model_def)
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 _StaticValidator:
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:
@@ -330,10 +352,20 @@ class _StaticValidator:
330
352
 
331
353
  cls._log.info(f"Checking {input_name}")
332
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
+
333
362
  fields = input_schema.schema.table.fields
334
363
  field_names = list(map(lambda f: f.fieldName, fields))
335
364
  property_type = f"field in [{input_name}]"
336
365
 
366
+ if len(fields) == 0:
367
+ cls._fail(f"Invalid schema for [{input_name}]: No fields defined")
368
+
337
369
  cls._valid_identifiers(field_names, property_type)
338
370
  cls._case_insensitive_duplicates(field_names, property_type)
339
371
 
@@ -375,8 +407,9 @@ class _StaticValidator:
375
407
  if field.categorical and field.fieldType != meta.BasicType.STRING:
376
408
  cls._fail(f"Invalid {property_type}: [{field.fieldName}] fieldType {field.fieldType} used as categorical")
377
409
 
378
- if field.businessKey and not field.notNull:
379
- cls._fail(f"Invalid {property_type}: [{field.fieldName}] is a business key but not_null = False")
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
380
413
 
381
414
  @classmethod
382
415
  def _valid_identifiers(cls, keys, property_type):
tracdap/rt/_version.py CHANGED
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.6.3"
15
+ __version__ = "0.6.4"
tracdap/rt/api/hook.py CHANGED
@@ -121,8 +121,7 @@ class _StaticApiHook:
121
121
  @_abc.abstractmethod
122
122
  def define_input_table(
123
123
  self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
124
- label: _tp.Optional[str] = None,
125
- optional: bool = False,
124
+ label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
126
125
  input_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
127
126
  -> _meta.ModelInputSchema:
128
127
 
@@ -131,8 +130,7 @@ class _StaticApiHook:
131
130
  @_abc.abstractmethod
132
131
  def define_output_table(
133
132
  self, *fields: _tp.Union[_meta.FieldSchema, _tp.List[_meta.FieldSchema]],
134
- label: _tp.Optional[str] = None,
135
- optional: bool = False,
133
+ label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
136
134
  output_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
137
135
  -> _meta.ModelOutputSchema:
138
136
 
@@ -100,13 +100,26 @@ class TracContext:
100
100
  """
101
101
  Get the schema of a model input or output
102
102
 
103
- The schema of an input or output can be retrieved and examined at runtime using this method.
104
- Inputs must be defined in :py:meth:`TracModel.define_inputs`
105
- and outputs in :py:meth:`TracModel.define_outputs`.
106
- Input and output names are case-sensitive.
107
-
108
- In the current version of the runtime all model inputs and outputs are defined statically,
109
- :py:meth:`get_schema` will return the schema as it was defined.
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
 
@@ -451,8 +451,7 @@ def load_schema(
451
451
 
452
452
  def define_input_table(
453
453
  *fields: _tp.Union[FieldSchema, _tp.List[FieldSchema]],
454
- label: _tp.Optional[str] = None,
455
- optional: bool = False,
454
+ label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
456
455
  input_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
457
456
  -> ModelInputSchema:
458
457
 
@@ -469,6 +468,7 @@ def define_input_table(
469
468
  :param fields: A set of fields to make up a :py:class:`TableSchema <tracdap.rt.metadata.TableSchema>`
470
469
  :param label: An optional label (of type str) for a model input schema. Default value: None.
471
470
  :param optional: Mark this input as an optional model input
471
+ :param dynamic: Mark this input as a dynamic model input (the list of fields must be empty)
472
472
  :param input_props: Associate key-value properties with this input (not used by the TRAC engine)
473
473
  :return: A model input schema, suitable for returning from :py:meth:`TracModel.define_inputs`
474
474
 
@@ -476,12 +476,16 @@ def define_input_table(
476
476
  List[:py:class:`FieldSchema <tracdap.rt.metadata.FieldSchema>`]
477
477
  :type label: Optional[str]
478
478
  :type optional: bool
479
+ :type dynamic: bool
479
480
  :type input_props: Optional[Dict[str, Any]]
480
481
  :rtype: :py:class:`ModelInputSchema <tracdap.rt.metadata.ModelInputSchema>`
481
482
  """
482
483
 
483
484
  sa = _StaticApiHook.get_instance()
484
- return sa.define_input_table(*fields, label=label, optional=optional, input_props=input_props)
485
+
486
+ return sa.define_input_table(
487
+ *fields, label=label, optional=optional, dynamic=dynamic,
488
+ input_props=input_props)
485
489
 
486
490
 
487
491
  def declare_input_table(
@@ -507,8 +511,7 @@ def declare_input_table(
507
511
 
508
512
  def define_output_table(
509
513
  *fields: _tp.Union[FieldSchema, _tp.List[FieldSchema]],
510
- label: _tp.Optional[str] = None,
511
- optional: bool = False,
514
+ label: _tp.Optional[str] = None, optional: bool = False, dynamic: bool = False,
512
515
  output_props: _tp.Optional[_tp.Dict[str, _tp.Any]] = None) \
513
516
  -> ModelOutputSchema:
514
517
 
@@ -525,6 +528,7 @@ def define_output_table(
525
528
  :param fields: A set of fields to make up a :py:class:`TableSchema <tracdap.rt.metadata.TableSchema>`
526
529
  :param label: An optional label (of type str) for a model output schema. Default value: None.
527
530
  :param optional: Mark this output as an optional model output
531
+ :param dynamic: Mark this output as a dynamic model output (the list of fields must be empty)
528
532
  :param output_props: Associate key-value properties with this output (not used by the TRAC engine)
529
533
  :return: A model output schema, suitable for returning from :py:meth:`TracModel.define_outputs`
530
534
 
@@ -532,12 +536,16 @@ def define_output_table(
532
536
  List[:py:class:`FieldSchema <tracdap.rt.metadata.FieldSchema>`]
533
537
  :type label: Optional[str]
534
538
  :type optional: bool
539
+ :type dynamic: bool
535
540
  :type output_props: Optional[Dict[str, Any]]
536
541
  :rtype: :py:class:`ModelOutputSchema <tracdap.rt.metadata.ModelOutputSchema>`
537
542
  """
538
543
 
539
544
  sa = _StaticApiHook.get_instance()
540
- return sa.define_output_table(*fields, label=label, optional=optional, output_props=output_props)
545
+
546
+ return sa.define_output_table(
547
+ *fields, label=label, optional=optional, dynamic=dynamic,
548
+ output_props=output_props)
541
549
 
542
550
 
543
551
  def declare_output_table(