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.
- tracdap/rt/_impl/core/__init__.py +14 -0
- tracdap/rt/_impl/{config_parser.py → core/config_parser.py} +61 -36
- tracdap/rt/_impl/{data.py → core/data.py} +136 -32
- tracdap/rt/_impl/core/logging.py +195 -0
- tracdap/rt/_impl/{models.py → core/models.py} +15 -12
- tracdap/rt/_impl/{repos.py → core/repos.py} +12 -3
- tracdap/rt/_impl/{schemas.py → core/schemas.py} +5 -5
- tracdap/rt/_impl/{shim.py → core/shim.py} +5 -4
- tracdap/rt/_impl/{storage.py → core/storage.py} +21 -10
- tracdap/rt/_impl/core/struct.py +547 -0
- tracdap/rt/_impl/{type_system.py → core/type_system.py} +73 -33
- tracdap/rt/_impl/{util.py → core/util.py} +1 -111
- tracdap/rt/_impl/{validation.py → core/validation.py} +99 -31
- tracdap/rt/_impl/exec/__init__.py +14 -0
- tracdap/rt/{_exec → _impl/exec}/actors.py +12 -14
- tracdap/rt/{_exec → _impl/exec}/context.py +228 -82
- tracdap/rt/{_exec → _impl/exec}/dev_mode.py +176 -89
- tracdap/rt/{_exec → _impl/exec}/engine.py +230 -105
- tracdap/rt/{_exec → _impl/exec}/functions.py +191 -100
- tracdap/rt/{_exec → _impl/exec}/graph.py +24 -36
- tracdap/rt/{_exec → _impl/exec}/graph_builder.py +252 -115
- tracdap/rt/_impl/grpc/codec.py +1 -1
- tracdap/rt/{_exec → _impl/grpc}/server.py +7 -6
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +3 -3
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2_grpc.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +40 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.pyi +62 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +32 -20
- tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.pyi +48 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +4 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.pyi +8 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +65 -63
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +16 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +28 -26
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.pyi +14 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +4 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.pyi +6 -0
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +9 -7
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.pyi +12 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +18 -5
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.pyi +42 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.py → storage_pb2.py} +4 -4
- tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +1 -1
- tracdap/rt/{_exec → _impl}/runtime.py +32 -18
- tracdap/rt/_impl/static_api.py +66 -38
- tracdap/rt/_plugins/format_csv.py +1 -1
- tracdap/rt/_plugins/repo_git.py +56 -11
- tracdap/rt/_plugins/storage_sql.py +13 -6
- tracdap/rt/_version.py +1 -1
- tracdap/rt/api/__init__.py +5 -24
- tracdap/rt/api/constants.py +57 -0
- tracdap/rt/api/experimental.py +32 -0
- tracdap/rt/api/hook.py +26 -7
- tracdap/rt/api/model_api.py +16 -0
- tracdap/rt/api/static_api.py +265 -127
- tracdap/rt/config/__init__.py +11 -11
- tracdap/rt/config/common.py +2 -26
- tracdap/rt/config/dynamic.py +28 -0
- tracdap/rt/config/platform.py +17 -31
- tracdap/rt/config/runtime.py +2 -0
- tracdap/rt/ext/embed.py +2 -2
- tracdap/rt/ext/plugins.py +3 -3
- tracdap/rt/launch/launch.py +12 -14
- tracdap/rt/metadata/__init__.py +31 -21
- tracdap/rt/metadata/config.py +95 -0
- tracdap/rt/metadata/data.py +40 -0
- tracdap/rt/metadata/file.py +10 -0
- tracdap/rt/metadata/job.py +16 -0
- tracdap/rt/metadata/model.py +12 -2
- tracdap/rt/metadata/object.py +9 -1
- tracdap/rt/metadata/object_id.py +6 -0
- tracdap/rt/metadata/resource.py +41 -1
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info}/METADATA +33 -27
- tracdap_runtime-0.8.0.dist-info/RECORD +129 -0
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info}/WHEEL +1 -1
- tracdap/rt/_exec/__init__.py +0 -0
- tracdap_runtime-0.7.0rc1.dist-info/RECORD +0 -121
- /tracdap/rt/_impl/{guard_rails.py → core/guard_rails.py} +0 -0
- /tracdap/rt/_impl/grpc/tracdap/metadata/{stoarge_pb2.pyi → storage_pb2.pyi} +0 -0
- /tracdap/rt/metadata/{stoarge.py → storage.py} +0 -0
- {tracdap_runtime-0.7.0rc1.dist-info → tracdap_runtime-0.8.0.dist-info/licenses}/LICENSE +0 -0
- {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(
|
136
|
+
return list(MetadataCodec._decode_value_for_type(x, type_desc.arrayType) for x in items)
|
137
137
|
|
138
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
#
|
70
|
-
#
|
71
|
-
__generic_metaclass =
|
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 =
|
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"
|
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 =
|
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,
|
464
|
+
def _check_inputs_or_outputs(cls, sockets):
|
422
465
|
|
423
|
-
for
|
466
|
+
for socket_name, socket in sockets.items():
|
424
467
|
|
425
|
-
|
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
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
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
|
-
|
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 [{
|
503
|
+
property_type = f"field in [{socket_name}]"
|
437
504
|
|
438
505
|
if len(fields) == 0:
|
439
|
-
cls._fail(f"Invalid schema for [{
|
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
|
-
|
448
|
-
|
514
|
+
@classmethod
|
515
|
+
def _check_socket_file_type(cls, socket_name, socket):
|
449
516
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
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.
|
29
|
-
import tracdap.rt._impl.validation as _val
|
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 =
|
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 =
|
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 =
|
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()
|