tracdap-runtime 0.7.0rc1__py3-none-any.whl → 0.8.0__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 (88) hide show
  1. tracdap/rt/_impl/core/__init__.py +14 -0
  2. tracdap/rt/_impl/{config_parser.py → core/config_parser.py} +61 -36
  3. tracdap/rt/_impl/{data.py → core/data.py} +136 -32
  4. tracdap/rt/_impl/core/logging.py +195 -0
  5. tracdap/rt/_impl/{models.py → core/models.py} +15 -12
  6. tracdap/rt/_impl/{repos.py → core/repos.py} +12 -3
  7. tracdap/rt/_impl/{schemas.py → core/schemas.py} +5 -5
  8. tracdap/rt/_impl/{shim.py → core/shim.py} +5 -4
  9. tracdap/rt/_impl/{storage.py → core/storage.py} +21 -10
  10. tracdap/rt/_impl/core/struct.py +547 -0
  11. tracdap/rt/_impl/{type_system.py → core/type_system.py} +73 -33
  12. tracdap/rt/_impl/{util.py → core/util.py} +1 -111
  13. tracdap/rt/_impl/{validation.py → core/validation.py} +99 -31
  14. tracdap/rt/_impl/exec/__init__.py +14 -0
  15. tracdap/rt/{_exec → _impl/exec}/actors.py +12 -14
  16. tracdap/rt/{_exec → _impl/exec}/context.py +228 -82
  17. tracdap/rt/{_exec → _impl/exec}/dev_mode.py +176 -89
  18. tracdap/rt/{_exec → _impl/exec}/engine.py +230 -105
  19. tracdap/rt/{_exec → _impl/exec}/functions.py +191 -100
  20. tracdap/rt/{_exec → _impl/exec}/graph.py +24 -36
  21. tracdap/rt/{_exec → _impl/exec}/graph_builder.py +252 -115
  22. tracdap/rt/_impl/grpc/codec.py +1 -1
  23. tracdap/rt/{_exec → _impl/grpc}/server.py +7 -6
  24. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +3 -3
  25. tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2_grpc.py +1 -1
  26. tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
  27. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +40 -0
  28. tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.pyi +62 -0
  29. tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
  30. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +32 -20
  31. tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.pyi +48 -2
  32. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +4 -2
  33. tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +8 -0
  34. tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
  35. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +65 -63
  36. tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +16 -2
  37. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +28 -26
  38. tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -4
  39. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +4 -4
  40. tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +6 -0
  41. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +9 -7
  42. tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +12 -4
  43. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +18 -5
  44. tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.pyi +42 -2
  45. tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +1 -1
  46. tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.py → storage_pb2.py} +4 -4
  47. tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +1 -1
  48. tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +1 -1
  49. tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +1 -1
  50. tracdap/rt/{_exec → _impl}/runtime.py +32 -18
  51. tracdap/rt/_impl/static_api.py +66 -38
  52. tracdap/rt/_plugins/format_csv.py +1 -1
  53. tracdap/rt/_plugins/repo_git.py +56 -11
  54. tracdap/rt/_plugins/storage_sql.py +13 -6
  55. tracdap/rt/_version.py +1 -1
  56. tracdap/rt/api/__init__.py +5 -24
  57. tracdap/rt/api/constants.py +57 -0
  58. tracdap/rt/api/experimental.py +32 -0
  59. tracdap/rt/api/hook.py +26 -7
  60. tracdap/rt/api/model_api.py +16 -0
  61. tracdap/rt/api/static_api.py +265 -127
  62. tracdap/rt/config/__init__.py +11 -11
  63. tracdap/rt/config/common.py +2 -26
  64. tracdap/rt/config/dynamic.py +28 -0
  65. tracdap/rt/config/platform.py +17 -31
  66. tracdap/rt/config/runtime.py +2 -0
  67. tracdap/rt/ext/embed.py +2 -2
  68. tracdap/rt/ext/plugins.py +3 -3
  69. tracdap/rt/launch/launch.py +12 -14
  70. tracdap/rt/metadata/__init__.py +31 -21
  71. tracdap/rt/metadata/config.py +95 -0
  72. tracdap/rt/metadata/data.py +40 -0
  73. tracdap/rt/metadata/file.py +10 -0
  74. tracdap/rt/metadata/job.py +16 -0
  75. tracdap/rt/metadata/model.py +12 -2
  76. tracdap/rt/metadata/object.py +9 -1
  77. tracdap/rt/metadata/object_id.py +6 -0
  78. tracdap/rt/metadata/resource.py +41 -1
  79. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info}/METADATA +33 -27
  80. tracdap_runtime-0.8.0.dist-info/RECORD +129 -0
  81. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info}/WHEEL +1 -1
  82. tracdap/rt/_exec/__init__.py +0 -0
  83. tracdap_runtime-0.7.0rc1.dist-info/RECORD +0 -121
  84. /tracdap/rt/_impl/{guard_rails.py → core/guard_rails.py} +0 -0
  85. /tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.pyi → storage_pb2.pyi} +0 -0
  86. /tracdap/rt/metadata/{stoarge.py → storage.py} +0 -0
  87. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info/licenses}/LICENSE +0 -0
  88. {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info}/top_level.txt +0 -0
@@ -133,9 +133,13 @@ class MetadataCodec:
133
133
 
134
134
  if basic_type == _meta.BasicType.ARRAY:
135
135
  items = value.arrayValue.items
136
- return list(map(lambda x: MetadataCodec._decode_value_for_type(x, type_desc.arrayType), items))
136
+ return list(MetadataCodec._decode_value_for_type(x, type_desc.arrayType) for x in items)
137
137
 
138
- raise _ex.ETracInternal(f"Decoding value type [{basic_type}] is not supported yet")
138
+ if basic_type == _meta.BasicType.MAP:
139
+ items = value.mapValue.entries.items()
140
+ return dict((k, MetadataCodec._decode_value_for_type(v, type_desc.mapType)) for k, v in items)
141
+
142
+ raise _ex.ETracInternal(f"Cannot decode value of type [{basic_type}]")
139
143
 
140
144
  @classmethod
141
145
  def encode_value(cls, value: tp.Any) -> _meta.Value:
@@ -183,19 +187,36 @@ class MetadataCodec:
183
187
  if any(map(lambda x: type(x) != array_raw_type, value)):
184
188
  raise _ex.ETracInternal("Cannot encode a list with values of different types")
185
189
 
186
- encoded_items = list(map(lambda x: cls.convert_value(x, array_trac_type), value))
190
+ encoded_items = list(map(lambda x: cls.convert_value(x, array_trac_type, True), value))
187
191
 
188
192
  return _meta.Value(
189
193
  _meta.TypeDescriptor(_meta.BasicType.ARRAY, arrayType=array_trac_type),
190
194
  arrayValue=_meta.ArrayValue(encoded_items))
191
195
 
192
- raise _ex.ETracInternal(f"Value type [{type(value)}] is not supported yet")
196
+ if isinstance(value, dict):
197
+
198
+ if len(value) == 0:
199
+ raise _ex.ETracInternal("Cannot encode an empty dict")
200
+
201
+ map_raw_type = type(next(iter(value.values())))
202
+ map_trac_type = TypeMapping.python_to_trac(map_raw_type)
203
+
204
+ if any(map(lambda x: type(x) != array_raw_type, value.values())):
205
+ raise _ex.ETracInternal("Cannot encode a dict with values of different types")
206
+
207
+ encoded_entries = dict(map(lambda kv: (kv[0], cls.convert_value(kv[1], map_trac_type, True)), value.items()))
208
+
209
+ return _meta.Value(
210
+ _meta.TypeDescriptor(_meta.BasicType.ARRAY, mapType=map_trac_type),
211
+ mapValue=_meta.MapValue(encoded_entries))
212
+
213
+ raise _ex.ETracInternal(f"Cannot encode value of type [{type(value).__name__}]")
193
214
 
194
215
  @classmethod
195
- def convert_value(cls, raw_value: tp.Any, type_desc: _meta.TypeDescriptor):
216
+ def convert_value(cls, raw_value: tp.Any, type_desc: _meta.TypeDescriptor, nested: bool = False):
196
217
 
197
218
  if type_desc.basicType == _meta.BasicType.BOOLEAN:
198
- return cls.convert_boolean_value(raw_value)
219
+ return cls.convert_boolean_value(raw_value, nested)
199
220
 
200
221
  if type_desc.basicType == _meta.BasicType.INTEGER:
201
222
  return cls.convert_integer_value(raw_value)
@@ -218,78 +239,97 @@ class MetadataCodec:
218
239
  if type_desc.basicType == _meta.BasicType.ARRAY:
219
240
  return cls.convert_array_value(raw_value, type_desc.arrayType)
220
241
 
242
+ if type_desc.basicType == _meta.BasicType.MAP:
243
+ return cls.convert_map_value(raw_value, type_desc.mapType)
244
+
221
245
  raise _ex.ETracInternal(f"Conversion to value type [{type_desc.basicType.name}] is not supported yet")
222
246
 
223
247
  @staticmethod
224
248
  def convert_array_value(raw_value: tp.List[tp.Any], array_type: _meta.TypeDescriptor) -> _meta.Value:
225
249
 
226
- type_desc = _meta.TypeDescriptor(_meta.BasicType.ARRAY, array_type)
250
+ type_desc = _meta.TypeDescriptor(basicType=_meta.BasicType.ARRAY, arrayType=array_type)
227
251
 
228
252
  if not isinstance(raw_value, list):
229
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.ARRAY.name}"
253
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.ARRAY.name}"
230
254
  raise _ex.ETracInternal(msg)
231
255
 
232
- items = list(map(lambda x: MetadataCodec.convert_value(x, array_type), raw_value))
256
+ items = list(map(lambda x: MetadataCodec.convert_value(x, array_type, True), raw_value))
233
257
 
234
258
  return _meta.Value(type_desc, arrayValue=_meta.ArrayValue(items))
235
259
 
236
260
  @staticmethod
237
- def convert_boolean_value(raw_value: tp.Any) -> _meta.Value:
261
+ def convert_map_value(raw_value: tp.Dict[str, tp.Any], map_type: _meta.TypeDescriptor) -> _meta.Value:
262
+
263
+ type_desc = _meta.TypeDescriptor(basicType=_meta.BasicType.MAP, mapType=map_type)
264
+
265
+ if not isinstance(raw_value, dict):
266
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.MAP.name}"
267
+ raise _ex.ETracInternal(msg)
268
+
269
+ entries = dict(map(lambda kv: (kv[0], MetadataCodec.convert_value(kv[1], map_type, True)), raw_value.items()))
270
+
271
+ return _meta.Value(type_desc, mapValue=_meta.MapValue(entries))
272
+
273
+ @staticmethod
274
+ def convert_boolean_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
238
275
 
239
- type_desc = _meta.TypeDescriptor(_meta.BasicType.BOOLEAN)
276
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.BOOLEAN) if not nested else None
240
277
 
241
278
  if isinstance(raw_value, bool):
242
279
  return _meta.Value(type_desc, booleanValue=raw_value)
243
280
 
244
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.BOOLEAN.name}"
281
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.BOOLEAN.name}"
245
282
  raise _ex.ETracInternal(msg)
246
283
 
247
284
  @staticmethod
248
- def convert_integer_value(raw_value: tp.Any) -> _meta.Value:
285
+ def convert_integer_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
249
286
 
250
- type_desc = _meta.TypeDescriptor(_meta.BasicType.INTEGER)
287
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.INTEGER) if not nested else None
251
288
 
252
- if isinstance(raw_value, int):
289
+ # isinstance(bool_value, int) returns True! An explicit check is needed
290
+ if isinstance(raw_value, int) and not isinstance(raw_value, bool):
253
291
  return _meta.Value(type_desc, integerValue=raw_value)
254
292
 
255
293
  if isinstance(raw_value, float) and raw_value.is_integer():
256
294
  return _meta.Value(type_desc, integerValue=int(raw_value))
257
295
 
258
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.INTEGER.name}"
296
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.INTEGER.name}"
259
297
  raise _ex.ETracInternal(msg)
260
298
 
261
299
  @staticmethod
262
- def convert_float_value(raw_value: tp.Any) -> _meta.Value:
300
+ def convert_float_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
263
301
 
264
- type_desc = _meta.TypeDescriptor(_meta.BasicType.FLOAT)
302
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.FLOAT) if not nested else None
265
303
 
266
304
  if isinstance(raw_value, float):
267
305
  return _meta.Value(type_desc, floatValue=raw_value)
268
306
 
269
- if isinstance(raw_value, int):
307
+ # isinstance(bool_value, int) returns True! An explicit check is needed
308
+ if isinstance(raw_value, int) and not isinstance(raw_value, bool):
270
309
  return _meta.Value(type_desc, floatValue=float(raw_value))
271
310
 
272
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.FLOAT.name}"
311
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.FLOAT.name}"
273
312
  raise _ex.ETracInternal(msg)
274
313
 
275
314
  @staticmethod
276
- def convert_decimal_value(raw_value: tp.Any) -> _meta.Value:
315
+ def convert_decimal_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
277
316
 
278
- type_desc = _meta.TypeDescriptor(_meta.BasicType.DECIMAL)
317
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.DECIMAL) if not nested else None
279
318
 
280
319
  if isinstance(raw_value, decimal.Decimal):
281
320
  return _meta.Value(type_desc, decimalValue=_meta.DecimalValue(str(raw_value)))
282
321
 
283
- if isinstance(raw_value, int) or isinstance(raw_value, float):
322
+ # isinstance(bool_value, int) returns True! An explicit check is needed
323
+ if isinstance(raw_value, int) or isinstance(raw_value, float) and not isinstance(raw_value, bool):
284
324
  return _meta.Value(type_desc, decimalValue=_meta.DecimalValue(str(raw_value)))
285
325
 
286
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.DECIMAL.name}"
326
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.DECIMAL.name}"
287
327
  raise _ex.ETracInternal(msg)
288
328
 
289
329
  @staticmethod
290
- def convert_string_value(raw_value: tp.Any) -> _meta.Value:
330
+ def convert_string_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
291
331
 
292
- type_desc = _meta.TypeDescriptor(_meta.BasicType.STRING)
332
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.STRING) if not nested else None
293
333
 
294
334
  if isinstance(raw_value, str):
295
335
  return _meta.Value(type_desc, stringValue=raw_value)
@@ -301,13 +341,13 @@ class MetadataCodec:
301
341
 
302
342
  return _meta.Value(type_desc, stringValue=str(raw_value))
303
343
 
304
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.STRING.name}"
344
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.STRING.name}"
305
345
  raise _ex.ETracInternal(msg)
306
346
 
307
347
  @staticmethod
308
- def convert_date_value(raw_value: tp.Any) -> _meta.Value:
348
+ def convert_date_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
309
349
 
310
- type_desc = _meta.TypeDescriptor(_meta.BasicType.DATE)
350
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.DATE) if not nested else None
311
351
 
312
352
  if isinstance(raw_value, dt.date):
313
353
  return _meta.Value(type_desc, dateValue=_meta.DateValue(isoDate=raw_value.isoformat()))
@@ -316,13 +356,13 @@ class MetadataCodec:
316
356
  date_value = dt.date.fromisoformat(raw_value)
317
357
  return _meta.Value(type_desc, dateValue=_meta.DateValue(isoDate=date_value.isoformat()))
318
358
 
319
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.DATE.name}"
359
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.DATE.name}"
320
360
  raise _ex.ETracInternal(msg)
321
361
 
322
362
  @staticmethod
323
- def convert_datetime_value(raw_value: tp.Any) -> _meta.Value:
363
+ def convert_datetime_value(raw_value: tp.Any, nested: bool = False) -> _meta.Value:
324
364
 
325
- type_desc = _meta.TypeDescriptor(_meta.BasicType.DATETIME)
365
+ type_desc = _meta.TypeDescriptor(_meta.BasicType.DATETIME) if not nested else None
326
366
 
327
367
  if isinstance(raw_value, dt.datetime):
328
368
  return _meta.Value(type_desc, datetimeValue=_meta.DatetimeValue(isoDatetime=raw_value.isoformat()))
@@ -331,5 +371,5 @@ class MetadataCodec:
331
371
  datetime_value = dt.datetime.fromisoformat(raw_value)
332
372
  return _meta.Value(type_desc, datetimeValue=_meta.DatetimeValue(isoDatetime=datetime_value.isoformat()))
333
373
 
334
- msg = f"Value of type [{type(raw_value)}] cannot be converted to {_meta.BasicType.DATETIME.name}"
374
+ msg = f"Value of type [{type(raw_value).__name__}] cannot be converted to {_meta.BasicType.DATETIME.name}"
335
375
  raise _ex.ETracInternal(msg)
@@ -14,11 +14,9 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import datetime as dt
17
- import logging
18
17
  import pathlib
19
18
  import platform
20
19
 
21
- import sys
22
20
  import typing as tp
23
21
  import uuid
24
22
 
@@ -38,114 +36,6 @@ def is_windows():
38
36
  return __IS_WINDOWS
39
37
 
40
38
 
41
- class ColorFormatter(logging.Formatter):
42
-
43
- _BLACK, _RED, _GREEN, _YELLOW, _BLUE, _MAGENTA, _CYAN, _WHITE, _DEFAULT_WHITE = range(9)
44
- _DARK_BASE = 30
45
- _LIGHT_BASE = 90
46
-
47
- # DARK_BASE + WHITE = light grey
48
- # DARK_BASE + DEFAULT_WHITE = regular console white
49
- # LIGHT_BASE + WHITE = bright white (0xffffff), very bright!
50
-
51
- def __init__(self, is_bright: bool):
52
-
53
- super().__init__(self._base_fmt(is_bright))
54
- self._level_colors = self._make_level_colors(is_bright)
55
- self._default_color = self._make_default_color(is_bright)
56
-
57
- def format(self, record):
58
-
59
- level_name = record.levelname
60
- level_color = self._level_colors.get(level_name)
61
-
62
- if level_color:
63
- record.levelname = level_color
64
- else:
65
- record.levelname = self._default_color + level_name
66
-
67
- return logging.Formatter.format(self, record)
68
-
69
- def _base_fmt(self, is_bright: bool):
70
-
71
- if is_bright:
72
- base_color = self._make_ansi_code(self._DARK_BASE, self._DEFAULT_WHITE, is_bold=False)
73
- message_color = self._make_ansi_code(self._LIGHT_BASE, self._CYAN, is_bold=False)
74
- else:
75
- base_color = self._make_ansi_code(self._DARK_BASE, self._WHITE, is_bold=False)
76
- message_color = self._make_ansi_code(self._DARK_BASE, self._CYAN, is_bold=False)
77
-
78
- return f"{base_color}%(asctime)s [%(threadName)s] %(levelname)s{base_color} %(name)s" + \
79
- f" - {message_color}%(message)s"
80
-
81
- def _make_level_colors(self, is_bright: bool):
82
-
83
- base_code = self._LIGHT_BASE if is_bright else self._DARK_BASE
84
-
85
- green = self._make_ansi_code(base_code, self._GREEN, is_bold=is_bright)
86
- yellow = self._make_ansi_code(base_code, self._YELLOW, is_bold=is_bright)
87
- red = self._make_ansi_code(base_code, self._RED, is_bold=is_bright)
88
-
89
- level_colors = {
90
- 'CRITICAL': f"{red}CRITICAL",
91
- 'ERROR': f"{red}ERROR",
92
- 'WARNING': f"{yellow}WARNING",
93
- 'INFO': f"{green}INFO"
94
- }
95
-
96
- return level_colors
97
-
98
- def _make_default_color(self, is_bright: bool):
99
-
100
- base_code = self._LIGHT_BASE if is_bright else self._DARK_BASE
101
- blue = self._make_ansi_code(base_code, self._BLUE, is_bold=is_bright)
102
-
103
- return blue
104
-
105
- @classmethod
106
- def _make_ansi_code(cls, base_code: int, color_offset: int, is_bold: bool):
107
- return f"\033[{1 if is_bold else 0};{base_code + color_offset}m"
108
-
109
-
110
- def configure_logging(enable_debug=False):
111
-
112
- root_logger = logging.getLogger()
113
- log_level = logging.DEBUG if enable_debug else logging.INFO
114
-
115
- if not root_logger.hasHandlers():
116
-
117
- console_formatter = ColorFormatter(is_bright=True)
118
- console_handler = logging.StreamHandler(sys.stdout)
119
- console_handler.setFormatter(console_formatter)
120
- console_handler.setLevel(logging.INFO)
121
- root_logger.addHandler(console_handler)
122
- root_logger.setLevel(log_level)
123
-
124
- # Use is_bright=False for logs from the TRAC runtime, so model logs stand out
125
-
126
- trac_logger = logging.getLogger("tracdap.rt")
127
-
128
- console_formatter = ColorFormatter(is_bright=False)
129
- console_handler = logging.StreamHandler(sys.stdout)
130
- console_handler.setFormatter(console_formatter)
131
- console_handler.setLevel(log_level)
132
- trac_logger.addHandler(console_handler)
133
- trac_logger.propagate = False
134
-
135
-
136
- def logger_for_object(obj: object) -> logging.Logger:
137
- return logger_for_class(obj.__class__)
138
-
139
-
140
- def logger_for_class(clazz: type) -> logging.Logger:
141
- qualified_class_name = f"{clazz.__module__}.{clazz.__name__}"
142
- return logging.getLogger(qualified_class_name)
143
-
144
-
145
- def logger_for_namespace(namespace: str) -> logging.Logger:
146
- return logging.getLogger(namespace)
147
-
148
-
149
39
  def format_file_size(size: int) -> str:
150
40
 
151
41
  if size < 1024:
@@ -235,7 +125,7 @@ def get_job_resource(
235
125
  if optional:
236
126
  return None
237
127
 
238
- err = f"Missing required {selector.objectType} resource [{object_key(selector)}]"
128
+ err = f"Missing required {selector.objectType.name} resource [{object_key(selector)}]"
239
129
  raise ex.ERuntimeValidation(err)
240
130
 
241
131
 
@@ -22,7 +22,8 @@ import pathlib
22
22
 
23
23
  import tracdap.rt.metadata as meta
24
24
  import tracdap.rt.exceptions as ex
25
- import tracdap.rt._impl.util as util
25
+ import tracdap.rt._impl.core.logging as log
26
+ import tracdap.rt._impl.core.util as util
26
27
 
27
28
  # _Named placeholder type from API hook is needed for API type checking
28
29
  from tracdap.rt.api.hook import _Named # noqa
@@ -66,15 +67,23 @@ class SkipValidation(tp.Generic[T_SKIP_VAL]):
66
67
 
67
68
  class _TypeValidator:
68
69
 
69
- # The metaclass for generic types varies between versions of the typing library
70
- # To work around this, detect the correct metaclass by inspecting a generic type variable
71
- __generic_metaclass = type(tp.List[object])
70
+ # Support both new and old styles for generic, union and optional types
71
+ # Old-style annotations are still valid, even when the new style is fully supported
72
+ __generic_metaclass = [
73
+ types.GenericAlias,
74
+ type(tp.List[object]),
75
+ type(tp.Optional[object])
76
+ ]
77
+
78
+ # UnionType was added to the types module in Python 3.10, we support 3.9 (Jan 2025)
79
+ if hasattr(types, "UnionType"):
80
+ __generic_metaclass.append(types.UnionType)
72
81
 
73
82
  # Cache method signatures to avoid inspection on every call
74
83
  # Inspecting a function signature can take ~ half a second in Python 3.7
75
84
  __method_cache: tp.Dict[str, tp.Tuple[inspect.Signature, tp.Any]] = dict()
76
85
 
77
- _log: logging.Logger = util.logger_for_namespace(__name__)
86
+ _log: logging.Logger = log.logger_for_namespace(__name__)
78
87
 
79
88
  @classmethod
80
89
  def validate_signature(cls, method: tp.Callable, *args, **kwargs):
@@ -203,7 +212,7 @@ class _TypeValidator:
203
212
  if value.skip_type == expected_type:
204
213
  return True
205
214
 
206
- if isinstance(expected_type, cls.__generic_metaclass):
215
+ if any(map(lambda _t: isinstance(expected_type, _t), cls.__generic_metaclass)):
207
216
 
208
217
  origin = util.get_origin(expected_type)
209
218
  args = util.get_args(expected_type)
@@ -239,6 +248,33 @@ class _TypeValidator:
239
248
  all(map(lambda k: cls._validate_type(key_type, k), value.keys())) and \
240
249
  all(map(lambda v: cls._validate_type(value_type, v), value.values()))
241
250
 
251
+ if origin is type:
252
+
253
+ if not isinstance(value, type):
254
+ return False
255
+
256
+ type_arg = args[0]
257
+
258
+ if type_arg == tp.Any:
259
+ return True
260
+
261
+ if isinstance(type_arg, tp.TypeVar):
262
+
263
+ constraints = util.get_constraints(type_arg)
264
+ bound = util.get_bound(type_arg)
265
+
266
+ if constraints:
267
+ if not any(map(lambda c: expected_type == c, constraints)):
268
+ return False
269
+
270
+ if bound:
271
+ if not issubclass(expected_type, bound):
272
+ return False
273
+
274
+ # So long as constraints / bound are ok, any type matches a generic type var
275
+ return True
276
+
277
+
242
278
  if origin.__module__.startswith("tracdap.rt.api."):
243
279
  return isinstance(value, origin)
244
280
 
@@ -273,7 +309,7 @@ class _TypeValidator:
273
309
  @classmethod
274
310
  def _type_name(cls, type_var: tp.Type, qualified: bool = False) -> str:
275
311
 
276
- if isinstance(type_var, cls.__generic_metaclass):
312
+ if any(map(lambda _t: isinstance(type_var, _t), cls.__generic_metaclass)):
277
313
 
278
314
  origin = util.get_origin(type_var)
279
315
  args = util.get_args(type_var)
@@ -290,7 +326,11 @@ class _TypeValidator:
290
326
 
291
327
  if origin is list:
292
328
  list_type = cls._type_name(args[0])
293
- return f"List[{list_type}]"
329
+ return f"list[{list_type}]"
330
+
331
+ if origin is type:
332
+ type_arg = cls._type_name(args[0])
333
+ return f"type[{type_arg}]"
294
334
 
295
335
  raise ex.ETracInternal(f"Validation of [{origin.__name__}] generic parameters is not supported yet")
296
336
 
@@ -306,6 +346,9 @@ class StaticValidator:
306
346
  __reserved_identifier_pattern = re.compile("\\A(_|trac_)", re.ASCII)
307
347
  __label_length_limit = 4096
308
348
 
349
+ __file_extension_pattern = re.compile('\\A[a-zA-Z0-9]+\\Z')
350
+ __mime_type_pattern = re.compile('\\A\\w+/[-.\\w]+(?:\\+[-.\\w]+)?\\Z')
351
+
309
352
  __PRIMITIVE_TYPES = [
310
353
  meta.BasicType.BOOLEAN,
311
354
  meta.BasicType.INTEGER,
@@ -321,7 +364,7 @@ class StaticValidator:
321
364
  meta.BasicType.INTEGER,
322
365
  meta.BasicType.DATE]
323
366
 
324
- _log: logging.Logger = util.logger_for_namespace(__name__)
367
+ _log: logging.Logger = log.logger_for_namespace(__name__)
325
368
 
326
369
  @classmethod
327
370
  def is_primitive_type(cls, basic_type: meta.BasicType) -> bool:
@@ -418,25 +461,49 @@ class StaticValidator:
418
461
  cls._valid_identifiers(param.paramProps.keys(), "entry in param props")
419
462
 
420
463
  @classmethod
421
- def _check_inputs_or_outputs(cls, inputs_or_outputs):
464
+ def _check_inputs_or_outputs(cls, sockets):
422
465
 
423
- for input_name, input_schema in inputs_or_outputs.items():
466
+ for socket_name, socket in sockets.items():
424
467
 
425
- cls._log.info(f"Checking {input_name}")
468
+ if socket.objectType == meta.ObjectType.DATA:
469
+ cls._check_socket_schema(socket_name, socket)
470
+ elif socket.objectType == meta.ObjectType.FILE:
471
+ cls._check_socket_file_type(socket_name, socket)
472
+ else:
473
+ raise ex.EModelValidation(f"Invalid object type [{socket.objectType.name}] for [{socket_name}]")
426
474
 
427
- if input_schema.dynamic:
428
- if input_schema.schema and input_schema.schema.table:
429
- error = "Dynamic schemas must have schema.table = None"
430
- cls._fail(f"Invalid schema for [{input_name}]: {error}")
431
- else:
432
- continue
475
+ label = socket.label
476
+ cls._check_label(label, socket_name)
477
+
478
+ if isinstance(socket, meta.ModelInputSchema):
479
+ if socket.inputProps is not None:
480
+ cls._valid_identifiers(socket.inputProps.keys(), "entry in input props")
481
+ else:
482
+ if socket.outputProps is not None:
483
+ cls._valid_identifiers(socket.outputProps.keys(), "entry in output props")
433
484
 
434
- fields = input_schema.schema.table.fields
485
+ @classmethod
486
+ def _check_socket_schema(cls, socket_name, socket):
487
+
488
+ if socket.schema is None:
489
+ cls._fail(f"Missing schema requirement for [{socket_name}]")
490
+ return
491
+
492
+ if socket.dynamic:
493
+ if socket.schema and socket.schema.table:
494
+ error = "Dynamic schemas must have schema.table = None"
495
+ cls._fail(f"Invalid schema for [{socket_name}]: {error}")
496
+ else:
497
+ return
498
+
499
+ if socket.schema.schemaType == meta.SchemaType.TABLE:
500
+
501
+ fields = socket.schema.table.fields
435
502
  field_names = list(map(lambda f: f.fieldName, fields))
436
- property_type = f"field in [{input_name}]"
503
+ property_type = f"field in [{socket_name}]"
437
504
 
438
505
  if len(fields) == 0:
439
- cls._fail(f"Invalid schema for [{input_name}]: No fields defined")
506
+ cls._fail(f"Invalid schema for [{socket_name}]: No fields defined")
440
507
 
441
508
  cls._valid_identifiers(field_names, property_type)
442
509
  cls._case_insensitive_duplicates(field_names, property_type)
@@ -444,23 +511,24 @@ class StaticValidator:
444
511
  for field in fields:
445
512
  cls._check_single_field(field, property_type)
446
513
 
447
- label = input_schema.label
448
- cls._check_label(label, input_name)
514
+ @classmethod
515
+ def _check_socket_file_type(cls, socket_name, socket):
449
516
 
450
- if isinstance(input_schema, meta.ModelInputSchema):
451
- if input_schema.inputProps is not None:
452
- cls._valid_identifiers(input_schema.inputProps.keys(), "entry in input props")
453
- else:
454
- if input_schema.outputProps is not None:
455
- cls._valid_identifiers(input_schema.outputProps.keys(), "entry in output props")
517
+ if socket.fileType is None:
518
+ cls._fail(f"Missing file type requirement for [{socket_name}]")
519
+ return
520
+
521
+ if not cls.__file_extension_pattern.match(socket.fileType.extension):
522
+ cls._fail(f"Invalid extension [{socket.fileType.extension}] for [{socket_name}]")
523
+
524
+ if not cls.__mime_type_pattern.match(socket.fileType.mimeType):
525
+ cls._fail(f"Invalid mime type [{socket.fileType.mimeType}] for [{socket_name}]")
456
526
 
457
527
  @classmethod
458
528
  def _check_single_field(cls, field: meta.FieldSchema, property_type):
459
529
 
460
530
  # Valid identifier and not trac reserved checked separately
461
531
 
462
- cls._log.info(field.fieldName)
463
-
464
532
  if field.fieldOrder < 0:
465
533
  cls._fail(f"Invalid {property_type}: [{field.fieldName}] fieldOrder < 0")
466
534
 
@@ -0,0 +1,14 @@
1
+ # Licensed to the Fintech Open Source Foundation (FINOS) under one or
2
+ # more contributor license agreements. See the NOTICE file distributed
3
+ # with this work for additional information regarding copyright ownership.
4
+ # FINOS licenses this file to you under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from __future__ import annotations
17
-
18
16
  import logging
19
17
  import threading
20
18
  import functools as func
@@ -25,8 +23,8 @@ import inspect
25
23
  import queue
26
24
  import time
27
25
 
28
- import tracdap.rt._impl.util as util # noqa
29
- import tracdap.rt._impl.validation as _val # noqa
26
+ import tracdap.rt._impl.core.logging as _logging
27
+ import tracdap.rt._impl.core.validation as _val
30
28
  import tracdap.rt.exceptions as _ex
31
29
 
32
30
 
@@ -118,7 +116,7 @@ class Actor:
118
116
  def error(self) -> tp.Optional[Exception]:
119
117
  return self.__ctx.get_error()
120
118
 
121
- def actors(self) -> ActorContext:
119
+ def actors(self) -> "ActorContext":
122
120
  return self.__ctx
123
121
 
124
122
  def on_start(self):
@@ -134,7 +132,7 @@ class Actor:
134
132
  class ActorContext:
135
133
 
136
134
  def __init__(
137
- self, node: ActorNode, message: str,
135
+ self, node: "ActorNode", message: str,
138
136
  current_actor: ActorId, parent: ActorId, sender: tp.Optional[ActorId]):
139
137
 
140
138
  self.__node = node
@@ -188,13 +186,13 @@ class ThreadsafeActor(Actor):
188
186
  super().__init__()
189
187
  self.__threadsafe: tp.Optional[ThreadsafeContext] = None
190
188
 
191
- def threadsafe(self) -> ThreadsafeContext:
189
+ def threadsafe(self) -> "ThreadsafeContext":
192
190
  return self.__threadsafe
193
191
 
194
192
 
195
193
  class ThreadsafeContext:
196
194
 
197
- def __init__(self, node: ActorNode):
195
+ def __init__(self, node: "ActorNode"):
198
196
  self.__node = node
199
197
  self.__id = node.actor_id
200
198
  self.__parent = node.parent.actor_id if node.parent is not None else None
@@ -235,7 +233,7 @@ class EventLoop:
235
233
  self.__shutdown = False
236
234
  self.__shutdown_now = False
237
235
  self.__done = False
238
- self.__log = util.logger_for_object(self)
236
+ self.__log = _logging.logger_for_object(self)
239
237
 
240
238
  def post_message(self, msg: _T_MSG, processor: tp.Callable[[_T_MSG], None]) -> bool:
241
239
  with self.__msg_lock:
@@ -365,12 +363,12 @@ class FunctionCache:
365
363
 
366
364
  class ActorNode:
367
365
 
368
- _log = util.logger_for_class(Actor)
366
+ _log = _logging.logger_for_class(Actor)
369
367
 
370
368
  def __init__(
371
369
  self, actor_id: ActorId, actor: Actor,
372
- parent: tp.Optional[ActorNode],
373
- system: ActorSystem,
370
+ parent: "tp.Optional[ActorNode]",
371
+ system: "ActorSystem",
374
372
  event_loop: EventLoop):
375
373
 
376
374
  self.actor_id = actor_id
@@ -483,7 +481,7 @@ class ActorNode:
483
481
 
484
482
  target_node._accept(msg)
485
483
 
486
- def _lookup_node(self, target_id: ActorId) -> tp.Optional[ActorNode]:
484
+ def _lookup_node(self, target_id: ActorId) -> "tp.Optional[ActorNode]":
487
485
 
488
486
  # Check self first
489
487
 
@@ -904,7 +902,7 @@ class ActorSystem:
904
902
 
905
903
  super().__init__()
906
904
 
907
- self._log = util.logger_for_object(self)
905
+ self._log = _logging.logger_for_object(self)
908
906
 
909
907
  # self.__actors: tp.Dict[ActorId, ActorNode] = {self.__ROOT_ID: ActorNode("", self.__ROOT_ID, None)}
910
908
  # self.__message_queue: tp.List[Msg] = list()