code-loader 1.0.139.dev5__py3-none-any.whl → 1.0.153.dev3__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.

Potentially problematic release.


This version of code-loader might be problematic. Click here for more details.

@@ -1,14 +1,18 @@
1
1
  # mypy: ignore-errors
2
2
  import os
3
3
  import warnings
4
+ import logging
4
5
  from collections import defaultdict
5
6
  from functools import lru_cache
6
7
  from pathlib import Path
7
- from typing import Optional, Union, Callable, List, Dict, get_args, get_origin
8
+ from typing import Optional, Union, Callable, List, Dict, Set, Any
9
+ from typing import Optional, Union, Callable, List, Dict, get_args, get_origin, DefaultDict
8
10
 
9
11
  import numpy as np
10
12
  import numpy.typing as npt
11
13
 
14
+ logger = logging.getLogger(__name__)
15
+
12
16
  from code_loader.contract.datasetclasses import CustomCallableInterfaceMultiArgs, \
13
17
  CustomMultipleReturnCallableInterfaceMultiArgs, ConfusionMatrixCallableInterfaceMultiArgs, CustomCallableInterface, \
14
18
  VisualizerCallableInterface, MetadataSectionCallableInterface, PreprocessResponse, SectionCallableInterface, \
@@ -20,15 +24,70 @@ from code_loader.contract.mapping import NodeMapping, NodeMappingType, NodeConne
20
24
  from code_loader.contract.visualizer_classes import LeapImage, LeapImageMask, LeapTextMask, LeapText, LeapGraph, \
21
25
  LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap
22
26
  from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
27
+ from code_loader.mixpanel_tracker import clear_integration_events, AnalyticsEvent, emit_integration_event_once
23
28
 
24
29
  import inspect
25
30
  import functools
31
+ from pathlib import Path
26
32
 
27
33
  _called_from_inside_tl_decorator = 0
28
34
  _called_from_inside_tl_integration_test_decorator = False
29
- _update_env_status = None
35
+ _call_from_tl_platform = os.environ.get('IS_TENSORLEAP_PLATFORM') == 'true'
36
+
37
+ # ---- warnings store (module-level) ----
38
+ _UNSET = object()
39
+ _STORED_WARNINGS: List[Dict[str, Any]] = []
40
+ _STORED_WARNING_KEYS: Set[tuple] = set()
41
+ # param_name -> set(user_func_name)
42
+ _PARAM_DEFAULT_FUNCS: DefaultDict[str, Set[str]] = defaultdict(set)
43
+ # param_name -> default_value used (repr-able)
44
+ _PARAM_DEFAULT_VALUE: Dict[str, Any] = {}
45
+ _PARAM_DEFAULT_DOCS: Dict[str, str] = {}
46
+
47
+
48
+ def get_entry_script_path() -> str:
49
+ import sys
50
+ argv0 = sys.argv[0] if sys.argv else ""
51
+ if argv0:
52
+ return str(Path(argv0).resolve())
53
+ import __main__
54
+ main_file = getattr(__main__, "__file__", "") or ""
55
+ return str(Path(main_file).resolve()) if main_file else ""
56
+
57
+ def started_from(filename: str) -> bool:
58
+ entry = get_entry_script_path()
59
+ return bool(entry) and Path(entry).name == filename
30
60
 
31
61
 
62
+ def store_warning_by_param(
63
+ *,
64
+ param_name: str,
65
+ user_func_name: str,
66
+ default_value: Any,
67
+ link_to_docs: str = None,
68
+ ) -> None:
69
+ _PARAM_DEFAULT_FUNCS[param_name].add(user_func_name)
70
+
71
+ if param_name not in _PARAM_DEFAULT_VALUE:
72
+ _PARAM_DEFAULT_VALUE[param_name] = default_value
73
+
74
+ if link_to_docs and param_name not in _PARAM_DEFAULT_DOCS:
75
+ _PARAM_DEFAULT_DOCS[param_name] = link_to_docs
76
+
77
+
78
+ def _get_param_default_warnings() -> Dict[str, Dict[str, Any]]:
79
+ out: Dict[str, Dict[str, Any]] = {}
80
+ for p, funcs in _PARAM_DEFAULT_FUNCS.items():
81
+ out[p] = {
82
+ "default_value": _PARAM_DEFAULT_VALUE.get(p, None),
83
+ "funcs": set(funcs),
84
+ "link_to_docs": _PARAM_DEFAULT_DOCS.get(p),
85
+ }
86
+ return out
87
+
88
+
89
+ def _get_stored_warnings() -> List[Dict[str, Any]]:
90
+ return list(_STORED_WARNINGS)
32
91
 
33
92
 
34
93
  def validate_args_structure(*args, types_order, func_name, expected_names, **kwargs):
@@ -89,7 +148,7 @@ def validate_args_structure(*args, types_order, func_name, expected_names, **kwa
89
148
  )
90
149
 
91
150
 
92
- def validate_output_structure(result, func_name: str, expected_type_name="np.ndarray",gt_flag=False):
151
+ def validate_output_structure(result, func_name: str, expected_type_name="np.ndarray", gt_flag=False):
93
152
  if result is None or (isinstance(result, float) and np.isnan(result)):
94
153
  if gt_flag:
95
154
  raise AssertionError(
@@ -127,8 +186,9 @@ def validate_output_structure(result, func_name: str, expected_type_name="np.nda
127
186
  f"{expected_type_name} (e.g., by concatenation or stacking)."
128
187
  )
129
188
 
189
+
130
190
  def batch_warning(result, func_name):
131
- if result.shape[0] == 1:
191
+ if len(result.shape) > 0 and result.shape[0] == 1:
132
192
  warnings.warn(
133
193
  f"{func_name} warning: Tensorleap will add a batch dimension at axis 0 to the output of {func_name}, "
134
194
  f"although the detected size of axis 0 is already 1. "
@@ -136,6 +196,8 @@ def batch_warning(result, func_name):
136
196
  f"Please ensure that the output of '{func_name}' is not already batched "
137
197
  f"to avoid computation errors."
138
198
  )
199
+
200
+
139
201
  def _add_mapping_connection(user_unique_name, connection_destinations, arg_names, name, node_mapping_type):
140
202
  connection_destinations = [connection_destination for connection_destination in connection_destinations
141
203
  if not isinstance(connection_destination, SamplePreprocessResponse)]
@@ -159,7 +221,7 @@ def tensorleap_integration_test():
159
221
  leap_binder.integration_test_func = integration_test_function
160
222
 
161
223
  def _validate_input_args(*args, **kwargs):
162
- sample_id,preprocess_response=args
224
+ sample_id, preprocess_response = args
163
225
  assert type(sample_id) == preprocess_response.sample_id_type, (
164
226
  f"tensorleap_integration_test validation failed: "
165
227
  f"sample_id type ({type(sample_id).__name__}) does not match the expected "
@@ -167,15 +229,23 @@ def tensorleap_integration_test():
167
229
  )
168
230
 
169
231
  def inner(*args, **kwargs):
232
+ if not _call_from_tl_platform:
233
+ set_current('tensorleap_integration_test')
170
234
  validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
171
- func_name='integration_test',expected_names=["idx", "preprocess"],**kwargs)
235
+ func_name='integration_test', expected_names=["idx", "preprocess"], **kwargs)
172
236
  _validate_input_args(*args, **kwargs)
173
237
 
174
238
  global _called_from_inside_tl_integration_test_decorator
239
+ # Clear integration test events for new test
240
+ try:
241
+ clear_integration_events()
242
+ except Exception as e:
243
+ logger.debug(f"Failed to clear integration events: {e}")
175
244
  try:
176
245
  _called_from_inside_tl_integration_test_decorator = True
177
- if not _update_env_status is None:
178
- _update_env_status("tensorleap_integration_test", "v")#put here because otherwise it will become v only if it finishes all the script
246
+ if not _call_from_tl_platform:
247
+ update_env_params_func("tensorleap_integration_test",
248
+ "v") # put here because otherwise it will become v only if it finishes all the script
179
249
  ret = integration_test_function(*args, **kwargs)
180
250
 
181
251
  try:
@@ -187,12 +257,15 @@ def tensorleap_integration_test():
187
257
  file_name = Path(first_tb.filename).name
188
258
  line_number = first_tb.lineno
189
259
  if isinstance(e, TypeError) and 'is not subscriptable' in str(e):
190
- print(f'Invalid integration code. File {file_name}, line {line_number}: '
191
- f"indexing is supported only on the model's predictions inside the integration test. Please remove this indexing operation usage from the integration test code.")
260
+ update_env_params_func("code_mapping", "x")
261
+ raise (f'Invalid integration code. File {file_name}, line {line_number}: '
262
+ f"indexing is supported only on the model's predictions inside the integration test. Please remove this indexing operation usage from the integration test code.")
192
263
  else:
193
- print(f'Invalid integration code. File {file_name}, line {line_number}: '
194
- f'Integration test is only allowed to call Tensorleap decorators. '
195
- f'Ensure any arithmetics, external library use, Python logic is placed within Tensorleap decoders')
264
+ update_env_params_func("code_mapping", "x")
265
+
266
+ raise (f'Invalid integration code. File {file_name}, line {line_number}: '
267
+ f'Integration test is only allowed to call Tensorleap decorators. '
268
+ f'Ensure any arithmetics, external library use, Python logic is placed within Tensorleap decoders')
196
269
  finally:
197
270
  if mapping_runtime_mode_env_var_mame in os.environ:
198
271
  del os.environ[mapping_runtime_mode_env_var_mame]
@@ -200,31 +273,55 @@ def tensorleap_integration_test():
200
273
  _called_from_inside_tl_integration_test_decorator = False
201
274
 
202
275
  leap_binder.check()
203
- return inner
204
276
 
277
+ return inner
205
278
 
206
279
  return decorating_function
207
280
 
281
+
208
282
  def _safe_get_item(key):
209
283
  try:
210
284
  return NodeMappingType[f'Input{str(key)}']
211
285
  except ValueError:
212
286
  raise Exception(f'Tensorleap currently supports models with no more then 10 inputs')
213
287
 
214
- def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]] = []):
215
- assert isinstance(prediction_types, list),(
288
+
289
+ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]] = _UNSET):
290
+ prediction_types_was_provided = prediction_types is not _UNSET
291
+
292
+ if not prediction_types_was_provided:
293
+ prediction_types = []
294
+ if not _call_from_tl_platform:
295
+ store_warning_by_param(
296
+ param_name="prediction_types",
297
+ user_func_name="tensorleap_load_model",
298
+ default_value=prediction_types,
299
+ link_to_docs="https://docs.tensorleap.ai/tensorleap-integration/integration-test#tensorleap_load_model"
300
+ )
301
+ assert isinstance(prediction_types, list), (
216
302
  f"tensorleap_load_model validation failed: "
217
- f" prediction_types is an optional argument of type List[PredictionTypeHandler]] but got {type(prediction_types).__name__}."
303
+ f" prediction_types is an optional argument of type List[PredictionTypeHandler]] but got {type(prediction_types).__name__}."
218
304
  )
219
305
  for i, prediction_type in enumerate(prediction_types):
220
- assert isinstance(prediction_type, PredictionTypeHandler),(f"tensorleap_load_model validation failed: "
221
- f" prediction_types at position {i} must be of type PredictionTypeHandler but got {type(prediction_types[i]).__name__}.")
306
+ assert isinstance(prediction_type, PredictionTypeHandler), (f"tensorleap_load_model validation failed: "
307
+ f" prediction_types at position {i} must be of type PredictionTypeHandler but got {type(prediction_types[i]).__name__}.")
308
+ prediction_type_channel_dim_was_provided = prediction_type.channel_dim != "tl_default_value"
309
+ if not prediction_type_channel_dim_was_provided:
310
+ prediction_types[i].channel_dim = -1
311
+ if not _call_from_tl_platform:
312
+ store_warning_by_param(
313
+ param_name=f"prediction_types[{i}].channel_dim",
314
+ user_func_name="tensorleap_load_model",
315
+ default_value=prediction_types[i].channel_dim,
316
+ link_to_docs="https://docs.tensorleap.ai/tensorleap-integration/integration-test#tensorleap_load_model"
317
+ )
222
318
  leap_binder.add_prediction(prediction_type.name, prediction_type.labels, prediction_type.channel_dim, i)
223
319
 
224
320
  def _validate_result(result) -> None:
225
- valid_types=["onnxruntime","keras"]
226
- err_message=f"tensorleap_load_model validation failed:\nSupported models are Keras and onnxruntime only and non of them was returned."
227
- validate_output_structure(result, func_name="tensorleap_load_model", expected_type_name= [" | ".join(t for t in valid_types)][0])
321
+ valid_types = ["onnxruntime", "keras"]
322
+ err_message = f"tensorleap_load_model validation failed:\nSupported models are Keras and onnxruntime only and non of them was returned."
323
+ validate_output_structure(result, func_name="tensorleap_load_model",
324
+ expected_type_name=[" | ".join(t for t in valid_types)][0])
228
325
  try:
229
326
  import keras
230
327
  except ImportError:
@@ -248,28 +345,41 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
248
345
  is_onnx_model = bool(onnxruntime and isinstance(result, onnxruntime.InferenceSession))
249
346
 
250
347
  if not any([is_keras_model, is_onnx_model]):
251
- raise AssertionError( err_message)
252
-
253
-
348
+ raise AssertionError(err_message)
254
349
 
255
- def decorating_function(load_model_func):
350
+ def decorating_function(load_model_func, prediction_types=prediction_types):
256
351
  class TempMapping:
257
352
  pass
258
353
 
259
354
  @lru_cache()
260
355
  def inner(*args, **kwargs):
356
+ if not _call_from_tl_platform:
357
+ set_current('tensorleap_load_model')
261
358
  validate_args_structure(*args, types_order=[],
262
- func_name='tensorleap_load_model',expected_names=[],**kwargs)
359
+ func_name='tensorleap_load_model', expected_names=[], **kwargs)
360
+
263
361
  class ModelPlaceholder:
264
- def __init__(self):
265
- self.model = load_model_func() #TODO- check why this fails on onnx model
362
+ def __init__(self, prediction_types):
363
+ self.model = load_model_func() # TODO- check why this fails on onnx model
364
+ self.prediction_types = prediction_types
266
365
  _validate_result(self.model)
267
366
 
268
367
  # keras interface
269
368
  def __call__(self, arg):
270
369
  ret = self.model(arg)
370
+ self.validate_declared_prediction_types(ret)
371
+ if isinstance(ret, list):
372
+ return [r.numpy() for r in ret]
271
373
  return ret.numpy()
272
374
 
375
+ def validate_declared_prediction_types(self, ret):
376
+ if not (len(self.prediction_types) == len(ret) if isinstance(ret, list) else 1) and len(
377
+ self.prediction_types) != 0:
378
+ if not _call_from_tl_platform:
379
+ update_env_params_func("tensorleap_load_model", "x")
380
+ raise Exception(
381
+ f"tensorleap_load_model validation failed: number of declared prediction types({len(prediction_types)}) != number of model outputs({len(ret) if isinstance(ret, list) else 1})")
382
+
273
383
  def _convert_onnx_inputs_to_correct_type(
274
384
  self, float_arrays_inputs: Dict[str, np.ndarray]
275
385
  ) -> Dict[str, np.ndarray]:
@@ -323,13 +433,16 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
323
433
  # onnx runtime interface
324
434
  def run(self, output_names, input_dict):
325
435
  corrected_type_inputs = self._convert_onnx_inputs_to_correct_type(input_dict)
326
- return self.model.run(output_names, corrected_type_inputs)
436
+ ret = self.model.run(output_names, corrected_type_inputs)
437
+ self.validate_declared_prediction_types(ret)
438
+ return ret
327
439
 
328
440
  def get_inputs(self):
329
441
  return self.model.get_inputs()
330
- model_placeholder=ModelPlaceholder()
331
- if not _update_env_status is None:
332
- _update_env_status("tensorleap_load_model", "v")
442
+
443
+ model_placeholder = ModelPlaceholder(prediction_types)
444
+ if not _call_from_tl_platform:
445
+ update_env_params_func("tensorleap_load_model", "v")
333
446
  return model_placeholder
334
447
 
335
448
  def mapping_inner():
@@ -377,7 +490,7 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
377
490
  def get_inputs(self):
378
491
  class FollowIndex:
379
492
  def __init__(self, index):
380
- self.name = _safe_get_item(index)
493
+ self.name = _safe_get_item(index)
381
494
 
382
495
  class FollowInputIndex:
383
496
  def __init__(self):
@@ -398,19 +511,35 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
398
511
  return mapping_inner()
399
512
  else:
400
513
  return inner(*args, **kwargs)
514
+
401
515
  return final_inner
402
516
 
403
517
  return decorating_function
404
518
 
405
519
 
406
520
  def tensorleap_custom_metric(name: str,
407
- direction: Union[MetricDirection, Dict[str, MetricDirection]] = MetricDirection.Downward,
521
+ direction: Union[MetricDirection, Dict[str, MetricDirection]] = _UNSET,
408
522
  compute_insights: Optional[Union[bool, Dict[str, bool]]] = None,
409
523
  connects_to=None):
410
524
  name_to_unique_name = defaultdict(set)
525
+
411
526
  def decorating_function(
412
527
  user_function: Union[CustomCallableInterfaceMultiArgs, CustomMultipleReturnCallableInterfaceMultiArgs,
413
528
  ConfusionMatrixCallableInterfaceMultiArgs]):
529
+ nonlocal direction
530
+
531
+ direction_was_provided = direction is not _UNSET
532
+
533
+ if not direction_was_provided:
534
+ direction = MetricDirection.Downward
535
+ if not _call_from_tl_platform:
536
+ store_warning_by_param(
537
+ param_name="direction",
538
+ user_func_name=user_function.__name__,
539
+ default_value=direction,
540
+ link_to_docs="https://docs.tensorleap.ai/tensorleap-integration/writing-integration-code/custom-metrics"
541
+
542
+ )
414
543
 
415
544
  def _validate_decorators_signature():
416
545
  err_message = f"{user_function.__name__} validation failed.\n"
@@ -480,7 +609,6 @@ def tensorleap_custom_metric(name: str,
480
609
  f"but found element types: {invalid_elems}."
481
610
  )
482
611
 
483
-
484
612
  _validate_decorators_signature()
485
613
 
486
614
  for metric_handler in leap_binder.setup_container.metrics:
@@ -489,7 +617,7 @@ def tensorleap_custom_metric(name: str,
489
617
  f'Please choose another')
490
618
 
491
619
  def _validate_input_args(*args, **kwargs) -> None:
492
- assert len(args) > 0, (
620
+ assert len(args) + len(kwargs) > 0, (
493
621
  f"{user_function.__name__}() validation failed: "
494
622
  f"Expected at least one positional|key-word argument of type np.ndarray, "
495
623
  f"but received none. "
@@ -522,7 +650,7 @@ def tensorleap_custom_metric(name: str,
522
650
  f'{user_function.__name__}() has returned unsupported type.\nSupported types are List[float|int|None], '
523
651
  f'List[List[ConfusionMatrixElement]], NDArray[np.float32] or dictonary with one of these types as its values types. ')
524
652
 
525
- def _validate_single_metric(single_metric_result,key=None):
653
+ def _validate_single_metric(single_metric_result, key=None):
526
654
  if isinstance(single_metric_result, list):
527
655
  if isinstance(single_metric_result[0], list):
528
656
  assert all(isinstance(cm, ConfusionMatrixElement) for cm in single_metric_result[0]), (
@@ -532,7 +660,7 @@ def tensorleap_custom_metric(name: str,
532
660
  )
533
661
 
534
662
  else:
535
- assert all(isinstance(v, (float,int,type(None),np.float32)) for v in single_metric_result), (
663
+ assert all(isinstance(v, (float, int, type(None), np.float32)) for v in single_metric_result), (
536
664
  f"{supported_types_message}\n"
537
665
  f"Got {'a dict where the value of ' + str(key) + ' is of type ' if key is not None else ''}"
538
666
  f"List[{', '.join(type(v).__name__ for v in single_metric_result)}]."
@@ -549,7 +677,7 @@ def tensorleap_custom_metric(name: str,
549
677
 
550
678
  if isinstance(result, dict):
551
679
  for key, value in result.items():
552
- _validate_single_metric(value,key)
680
+ _validate_single_metric(value, key)
553
681
 
554
682
  assert isinstance(key, str), \
555
683
  (f'{user_function.__name__}() validation failed: '
@@ -604,13 +732,15 @@ def tensorleap_custom_metric(name: str,
604
732
  _add_mapping_connections(connects_to, arg_names, NodeMappingType.Metric, name)
605
733
 
606
734
  def inner(*args, **kwargs):
735
+ if not _call_from_tl_platform:
736
+ set_current('tensorleap_custom_metric')
607
737
  _validate_input_args(*args, **kwargs)
608
738
 
609
739
  result = inner_without_validate(*args, **kwargs)
610
740
 
611
741
  _validate_result(result)
612
- if not _update_env_status is None:
613
- _update_env_status("tensorleap_custom_metric","v")
742
+ if not _call_from_tl_platform:
743
+ update_env_params_func("tensorleap_custom_metric", "v")
614
744
  return result
615
745
 
616
746
  def mapping_inner(*args, **kwargs):
@@ -650,17 +780,16 @@ def tensorleap_custom_visualizer(name: str, visualizer_type: LeapDataType,
650
780
  name_to_unique_name = defaultdict(set)
651
781
 
652
782
  def decorating_function(user_function: VisualizerCallableInterface):
653
- assert isinstance(visualizer_type,LeapDataType),(f"{user_function.__name__} validation failed: "
654
- f"visualizer_type should be of type {LeapDataType.__name__} but got {type(visualizer_type)}"
655
- )
656
-
783
+ assert isinstance(visualizer_type, LeapDataType), (f"{user_function.__name__} validation failed: "
784
+ f"visualizer_type should be of type {LeapDataType.__name__} but got {type(visualizer_type)}"
785
+ )
657
786
  for viz_handler in leap_binder.setup_container.visualizers:
658
787
  if viz_handler.visualizer_handler_data.name == name:
659
788
  raise Exception(f'Visualizer with name {name} already exists. '
660
789
  f'Please choose another')
661
790
 
662
791
  def _validate_input_args(*args, **kwargs):
663
- assert len(args) > 0, (
792
+ assert len(args) + len(kwargs) > 0, (
664
793
  f"{user_function.__name__}() validation failed: "
665
794
  f"Expected at least one positional|key-word argument of type np.ndarray, "
666
795
  f"but received none. "
@@ -726,13 +855,15 @@ def tensorleap_custom_visualizer(name: str, visualizer_type: LeapDataType,
726
855
  _add_mapping_connections(connects_to, arg_names, NodeMappingType.Visualizer, name)
727
856
 
728
857
  def inner(*args, **kwargs):
858
+ if not _call_from_tl_platform:
859
+ set_current('tensorleap_custom_visualizer')
729
860
  _validate_input_args(*args, **kwargs)
730
861
 
731
862
  result = inner_without_validate(*args, **kwargs)
732
863
 
733
864
  _validate_result(result)
734
- if not _update_env_status is None:
735
- _update_env_status("tensorleap_custom_visualizer","v")
865
+ if not _call_from_tl_platform:
866
+ update_env_params_func("tensorleap_custom_visualizer", "v")
736
867
  return result
737
868
 
738
869
  def mapping_inner(*args, **kwargs):
@@ -810,19 +941,21 @@ def tensorleap_metadata(
810
941
 
811
942
  leap_binder.set_metadata(inner_without_validate, name, metadata_type)
812
943
 
813
- def inner(*args,**kwargs):
944
+ def inner(*args, **kwargs):
945
+ if not _call_from_tl_platform:
946
+ set_current('tensorleap_metadata')
814
947
  if os.environ.get(mapping_runtime_mode_env_var_mame):
815
948
  return None
816
949
  validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
817
- func_name=user_function.__name__, expected_names=["idx", "preprocess"],**kwargs)
818
- sample_id, preprocess_response = args if len(args)!=0 else kwargs.values()
950
+ func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
951
+ sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
819
952
  _validate_input_args(sample_id, preprocess_response)
820
953
 
821
954
  result = inner_without_validate(sample_id, preprocess_response)
822
955
 
823
956
  _validate_result(result)
824
- if not _update_env_status is None:
825
- _update_env_status("tensorleap_metadata","v")
957
+ if not _call_from_tl_platform:
958
+ update_env_params_func("tensorleap_metadata", "v")
826
959
  return result
827
960
 
828
961
  return inner
@@ -830,7 +963,6 @@ def tensorleap_metadata(
830
963
  return decorating_function
831
964
 
832
965
 
833
-
834
966
  def tensorleap_custom_latent_space():
835
967
  def decorating_function(user_function: SectionCallableInterface):
836
968
  def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
@@ -884,7 +1016,7 @@ def tensorleap_preprocess():
884
1016
  leap_binder.set_preprocess(user_function)
885
1017
 
886
1018
  def _validate_input_args(*args, **kwargs):
887
- assert len(args) == 0 and len(kwargs) == 0, \
1019
+ assert len(args) + len(kwargs) == 0, \
888
1020
  (f'{user_function.__name__}() validation failed: '
889
1021
  f'The function should not take any arguments. Got {args} and {kwargs}.')
890
1022
 
@@ -905,14 +1037,24 @@ def tensorleap_preprocess():
905
1037
  f'The return list should not contain duplicate PreprocessResponse objects.')
906
1038
 
907
1039
  def inner(*args, **kwargs):
1040
+ if not _call_from_tl_platform:
1041
+ set_current('tensorleap_metadata')
908
1042
  if os.environ.get(mapping_runtime_mode_env_var_mame):
909
1043
  return [None, None, None, None]
910
1044
 
911
1045
  _validate_input_args(*args, **kwargs)
912
1046
  result = user_function()
913
1047
  _validate_result(result)
914
- if not _update_env_status is None:
915
- _update_env_status("tensorleap_preprocess", "v")
1048
+
1049
+ # Emit integration test event once per test
1050
+ try:
1051
+ emit_integration_event_once(AnalyticsEvent.PREPROCESS_INTEGRATION_TEST, {
1052
+ 'preprocess_responses_count': len(result)
1053
+ })
1054
+ except Exception as e:
1055
+ logger.debug(f"Failed to emit preprocess integration test event: {e}")
1056
+ if not _call_from_tl_platform:
1057
+ update_env_params_func("tensorleap_preprocess", "v")
916
1058
  return result
917
1059
 
918
1060
  return inner
@@ -943,11 +1085,33 @@ def tensorleap_element_instance_preprocess(
943
1085
  preprocess_response.sample_ids = all_sample_ids
944
1086
  return result
945
1087
 
1088
+ # def extract_extra_instance_metadata():
1089
+ # result = user_function()
1090
+ # for preprocess_response in result:
1091
+ # for sample_id in preprocess_response.sample_ids:
1092
+ # instances_length = instance_length_encoder(sample_id, preprocess_response)
1093
+ # if instances_length > 0:
1094
+ # element_instance = instance_mask_encoder(sample_id, preprocess_response, 0)
1095
+ # instance_metadata = element_instance.instance_metadata
1096
+ # if instance_metadata is None:
1097
+ # return {}
1098
+ # return instance_metadata
1099
+ # return {}
1100
+
1101
+
946
1102
  def builtin_instance_metadata(idx: str, preprocess: PreprocessResponse) -> Dict[str, str]:
947
1103
  return {'is_instance': '0', 'original_sample_id': idx, 'instance_name': 'none'}
948
1104
 
1105
+ # def builtin_instance_extra_metadata(idx: str, preprocess: PreprocessResponse) -> Dict[str, str]:
1106
+ # instance_metadata = extract_extra_instance_metadata()
1107
+ # for key, value in instance_metadata.items():
1108
+ # instance_metadata[key] = 'unset'
1109
+ # return instance_metadata
1110
+
949
1111
  leap_binder.set_preprocess(user_function_instance)
950
1112
  leap_binder.set_metadata(builtin_instance_metadata, "builtin_instance_metadata")
1113
+ # leap_binder.set_metadata(builtin_instance_extra_metadata, "builtin_instance_extra_metadata")
1114
+
951
1115
 
952
1116
  def _validate_input_args(*args, **kwargs):
953
1117
  assert len(args) == 0 and len(kwargs) == 0, \
@@ -974,6 +1138,8 @@ def tensorleap_element_instance_preprocess(
974
1138
 
975
1139
  result = user_function_instance()
976
1140
  _validate_result(result)
1141
+ if not _call_from_tl_platform:
1142
+ update_env_params_func("tensorleap_preprocess", "v")
977
1143
  return result
978
1144
 
979
1145
  return inner
@@ -1056,10 +1222,11 @@ def tensorleap_instances_masks_encoder(name: str):
1056
1222
 
1057
1223
  return decorating_function
1058
1224
 
1225
+
1059
1226
  def tensorleap_instances_length_encoder(name: str):
1060
1227
  def decorating_function(user_function: InstanceLengthCallableInterface):
1061
1228
  def _validate_input_args(sample_id: str, preprocess_response: PreprocessResponse):
1062
- assert isinstance(sample_id, str), \
1229
+ assert isinstance(sample_id, (str, int)), \
1063
1230
  (f'tensorleap_instances_length_encoder validation failed: '
1064
1231
  f'Argument sample_id should be str. Got {type(sample_id)}.')
1065
1232
  assert isinstance(preprocess_response, PreprocessResponse), \
@@ -1101,12 +1268,28 @@ def tensorleap_instances_length_encoder(name: str):
1101
1268
 
1102
1269
  return decorating_function
1103
1270
 
1104
- def tensorleap_input_encoder(name: str, channel_dim=-1, model_input_index=None):
1271
+
1272
+ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=None):
1105
1273
  def decorating_function(user_function: SectionCallableInterface):
1106
1274
  for input_handler in leap_binder.setup_container.inputs:
1107
1275
  if input_handler.name == name:
1108
1276
  raise Exception(f'Input with name {name} already exists. '
1109
1277
  f'Please choose another')
1278
+ nonlocal channel_dim
1279
+
1280
+ channel_dim_was_provided = channel_dim is not _UNSET
1281
+
1282
+ if not channel_dim_was_provided:
1283
+ channel_dim = -1
1284
+ if not _call_from_tl_platform:
1285
+ store_warning_by_param(
1286
+ param_name="channel_dim",
1287
+ user_func_name=user_function.__name__,
1288
+ default_value=channel_dim,
1289
+ link_to_docs="https://docs.tensorleap.ai/tensorleap-integration/writing-integration-code/input-encoder"
1290
+
1291
+ )
1292
+
1110
1293
  if channel_dim <= 0 and channel_dim != -1:
1111
1294
  raise Exception(f"Channel dim for input {name} is expected to be either -1 or positive")
1112
1295
 
@@ -1117,7 +1300,7 @@ def tensorleap_input_encoder(name: str, channel_dim=-1, model_input_index=None):
1117
1300
  f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
1118
1301
 
1119
1302
  def _validate_result(result):
1120
- validate_output_structure(result, func_name=user_function.__name__, expected_type_name = "np.ndarray")
1303
+ validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray")
1121
1304
  assert isinstance(result, np.ndarray), \
1122
1305
  (f'{user_function.__name__}() validation failed: '
1123
1306
  f'Unsupported return type. Should be a numpy array. Got {type(result)}.')
@@ -1140,11 +1323,12 @@ def tensorleap_input_encoder(name: str, channel_dim=-1, model_input_index=None):
1140
1323
 
1141
1324
  leap_binder.set_input(inner_without_validate, name, channel_dim=channel_dim)
1142
1325
 
1143
-
1144
1326
  def inner(*args, **kwargs):
1327
+ if not _call_from_tl_platform:
1328
+ set_current("tensorleap_input_encoder")
1145
1329
  validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
1146
- func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
1147
- sample_id, preprocess_response = args if len(args)!=0 else kwargs.values()
1330
+ func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
1331
+ sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
1148
1332
  _validate_input_args(sample_id, preprocess_response)
1149
1333
 
1150
1334
  result = inner_without_validate(sample_id, preprocess_response)
@@ -1152,14 +1336,22 @@ def tensorleap_input_encoder(name: str, channel_dim=-1, model_input_index=None):
1152
1336
  _validate_result(result)
1153
1337
 
1154
1338
  if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
1155
- batch_warning(result,user_function.__name__)
1339
+ batch_warning(result, user_function.__name__)
1156
1340
  result = np.expand_dims(result, axis=0)
1157
- if not _update_env_status is None:
1158
- _update_env_status("tensorleap_input_encoder", "v")
1341
+ # Emit integration test event once per test
1342
+ try:
1343
+ emit_integration_event_once(AnalyticsEvent.INPUT_ENCODER_INTEGRATION_TEST, {
1344
+ 'encoder_name': name,
1345
+ 'channel_dim': channel_dim,
1346
+ 'model_input_index': model_input_index
1347
+ })
1348
+ except Exception as e:
1349
+ logger.debug(f"Failed to emit input_encoder integration test event: {e}")
1350
+ if not _call_from_tl_platform:
1351
+ update_env_params_func("tensorleap_input_encoder", "v")
1159
1352
 
1160
1353
  return result
1161
1354
 
1162
-
1163
1355
  node_mapping_type = NodeMappingType.Input
1164
1356
  if model_input_index is not None:
1165
1357
  node_mapping_type = NodeMappingType(f'Input{str(model_input_index)}')
@@ -1204,7 +1396,8 @@ def tensorleap_gt_encoder(name: str):
1204
1396
  f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
1205
1397
 
1206
1398
  def _validate_result(result):
1207
- validate_output_structure(result, func_name=user_function.__name__, expected_type_name = "np.ndarray",gt_flag=True)
1399
+ validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray",
1400
+ gt_flag=True)
1208
1401
  assert isinstance(result, np.ndarray), \
1209
1402
  (f'{user_function.__name__}() validation failed: '
1210
1403
  f'Unsupported return type. Should be a numpy array. Got {type(result)}.')
@@ -1225,8 +1418,9 @@ def tensorleap_gt_encoder(name: str):
1225
1418
 
1226
1419
  leap_binder.set_ground_truth(inner_without_validate, name)
1227
1420
 
1228
-
1229
1421
  def inner(*args, **kwargs):
1422
+ if not _call_from_tl_platform:
1423
+ set_current("tensorleap_gt_encoder")
1230
1424
  validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
1231
1425
  func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
1232
1426
  sample_id, preprocess_response = args
@@ -1239,8 +1433,15 @@ def tensorleap_gt_encoder(name: str):
1239
1433
  if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
1240
1434
  batch_warning(result, user_function.__name__)
1241
1435
  result = np.expand_dims(result, axis=0)
1242
- if not _update_env_status is None:
1243
- _update_env_status("tensorleap_gt_encoder", "v")
1436
+ # Emit integration test event once per test
1437
+ try:
1438
+ emit_integration_event_once(AnalyticsEvent.GT_ENCODER_INTEGRATION_TEST, {
1439
+ 'encoder_name': name
1440
+ })
1441
+ except Exception as e:
1442
+ logger.debug(f"Failed to emit gt_encoder integration test event: {e}")
1443
+ if not _call_from_tl_platform:
1444
+ update_env_params_func("tensorleap_gt_encoder", "v")
1244
1445
  return result
1245
1446
 
1246
1447
  inner.node_mapping = NodeMapping(name, NodeMappingType.GroundTruth)
@@ -1281,28 +1482,18 @@ def tensorleap_custom_loss(name: str, connects_to=None):
1281
1482
  valid_types = (np.ndarray, SamplePreprocessResponse)
1282
1483
 
1283
1484
  def _validate_input_args(*args, **kwargs):
1284
- assert len(args) > 0 and len(kwargs)==0, (
1485
+ assert len(args) + len(kwargs) > 0, (
1285
1486
  f"{user_function.__name__}() validation failed: "
1286
- f"Expected at least one positional|key-word argument of the allowed types (np.ndarray|SamplePreprocessResponse|list(np.ndarray|SamplePreprocessResponse)). "
1487
+ f"Expected at least one positional|key-word argument of the allowed types (np.ndarray|SamplePreprocessResponse|). "
1287
1488
  f"but received none. "
1288
1489
  f"Correct usage example: {user_function.__name__}(input_array: np.ndarray, ...)"
1289
1490
  )
1290
1491
  for i, arg in enumerate(args):
1291
- if isinstance(arg, list):
1292
- for y, elem in enumerate(arg):
1293
- assert isinstance(elem, valid_types), (f'{user_function.__name__}() validation failed: '
1294
- f'Element #{y} of list should be a numpy array. Got {type(elem)}.')
1295
- else:
1296
- assert isinstance(arg, valid_types), (f'{user_function.__name__}() validation failed: '
1297
- f'Argument #{i} should be a numpy array. Got {type(arg)}.')
1492
+ assert isinstance(arg, valid_types), (f'{user_function.__name__}() validation failed: '
1493
+ f'Argument #{i} should be a numpy array. Got {type(arg)}.')
1298
1494
  for _arg_name, arg in kwargs.items():
1299
- if isinstance(arg, list):
1300
- for y, elem in enumerate(arg):
1301
- assert isinstance(elem, valid_types), (f'{user_function.__name__}() validation failed: '
1302
- f'Element #{y} of list should be a numpy array. Got {type(elem)}.')
1303
- else:
1304
- assert isinstance(arg, valid_types), (f'{user_function.__name__}() validation failed: '
1305
- f'Argument #{_arg_name} should be a numpy array. Got {type(arg)}.')
1495
+ assert isinstance(arg, valid_types), (f'{user_function.__name__}() validation failed: '
1496
+ f'Argument #{_arg_name} should be a numpy array. Got {type(arg)}.')
1306
1497
 
1307
1498
  def _validate_result(result):
1308
1499
  validate_output_structure(result, func_name=user_function.__name__,
@@ -1310,8 +1501,8 @@ def tensorleap_custom_loss(name: str, connects_to=None):
1310
1501
  assert isinstance(result, np.ndarray), \
1311
1502
  (f'{user_function.__name__} validation failed: '
1312
1503
  f'The return type should be a numpy array. Got {type(result)}.')
1313
- assert result.ndim<2 ,(f'{user_function.__name__} validation failed: '
1314
- f'The return type should be a 1Dim numpy array but got {result.ndim}Dim.')
1504
+ assert result.ndim < 2, (f'{user_function.__name__} validation failed: '
1505
+ f'The return type should be a 1Dim numpy array but got {result.ndim}Dim.')
1315
1506
 
1316
1507
  @functools.wraps(user_function)
1317
1508
  def inner_without_validate(*args, **kwargs):
@@ -1337,13 +1528,15 @@ def tensorleap_custom_loss(name: str, connects_to=None):
1337
1528
  _add_mapping_connections(connects_to, arg_names, NodeMappingType.CustomLoss, name)
1338
1529
 
1339
1530
  def inner(*args, **kwargs):
1531
+ if not _call_from_tl_platform:
1532
+ set_current("tensorleap_custom_loss")
1340
1533
  _validate_input_args(*args, **kwargs)
1341
1534
 
1342
1535
  result = inner_without_validate(*args, **kwargs)
1343
1536
 
1344
1537
  _validate_result(result)
1345
- if not _update_env_status is None:
1346
- _update_env_status("tensorleap_custom_loss", "v")
1538
+ if not _call_from_tl_platform:
1539
+ update_env_params_func("tensorleap_custom_loss", "v")
1347
1540
 
1348
1541
  return result
1349
1542
 
@@ -1404,80 +1597,172 @@ def tensorleap_custom_layer(name: str):
1404
1597
 
1405
1598
 
1406
1599
  def tensorleap_status_table():
1407
- '''
1408
- Usage example:
1409
- ###################
1410
- leap_integration.py
1411
- ###################
1412
- from code_loader.inner_leap_binder.leapbinder_decorators import tensorleap_status_table
1413
- ...
1414
- ...
1415
- ...
1416
- if __name__ == '__main__':
1417
- tensorleap_status_table()
1418
- ...
1419
- '''
1420
1600
  import atexit
1421
1601
  import sys
1422
1602
  import traceback
1603
+ from typing import Any
1604
+
1423
1605
  CHECK = "✅"
1424
1606
  CROSS = "❌"
1607
+ UNKNOWN = "❔"
1425
1608
 
1426
- table = [
1427
- {"name": "tensorleap_preprocess", "Added to integration": CROSS},
1428
- {"name": "tensorleap_integration_test", "Added to integration": CROSS},
1429
- {"name": "tensorleap_input_encoder", "Added to integration": CROSS},
1430
- {"name": "tensorleap_gt_encoder", "Added to integration": CROSS},
1431
- {"name": "tensorleap_load_model", "Added to integration": CROSS},
1432
- {"name": "tensorleap_custom_loss", "Added to integration": CROSS},
1433
- {"name": "tensorleap_custom_metric (optional)", "Added to integration": CROSS},
1434
- {"name": "tensorleap_metadata (optional)", "Added to integration": CROSS},
1435
- {"name": "tensorleap_custom_visualizer (optional)", "Added to integration": CROSS},
1609
+ code_mapping_failure = [0]
1436
1610
 
1611
+ table = [
1612
+ {"name": "tensorleap_preprocess", "Added to integration": UNKNOWN},
1613
+ {"name": "tensorleap_integration_test", "Added to integration": UNKNOWN},
1614
+ {"name": "tensorleap_input_encoder", "Added to integration": UNKNOWN},
1615
+ {"name": "tensorleap_gt_encoder", "Added to integration": UNKNOWN},
1616
+ {"name": "tensorleap_load_model", "Added to integration": UNKNOWN},
1617
+ {"name": "tensorleap_custom_loss", "Added to integration": UNKNOWN},
1618
+ {"name": "tensorleap_custom_metric (optional)", "Added to integration": UNKNOWN},
1619
+ {"name": "tensorleap_metadata (optional)", "Added to integration": UNKNOWN},
1620
+ {"name": "tensorleap_custom_visualizer (optional)", "Added to integration": UNKNOWN},
1437
1621
  ]
1438
1622
 
1439
1623
  _finalizer_called = {"done": False}
1624
+ _crashed = {"value": False}
1625
+ _current_func = {"name": None}
1626
+
1627
+ def _link(url: str) -> str:
1628
+ return f"\033{url}\033"
1629
+
1630
+ def _remove_suffix(s: str, suffix: str) -> str:
1631
+ if suffix and s.endswith(suffix):
1632
+ return s[:-len(suffix)]
1633
+ return s
1634
+
1635
+ def _find_row(name: str):
1636
+ for row in table:
1637
+ if _remove_suffix(row["name"], " (optional)") == name:
1638
+ return row
1639
+ return None
1640
+
1641
+ def _set_status(name: str, status_symbol: str):
1642
+ row = _find_row(name)
1643
+ if not row:
1644
+ return
1645
+
1646
+ cur = row["Added to integration"]
1647
+ if status_symbol == UNKNOWN:
1648
+ return
1649
+ if cur == CHECK and status_symbol != CHECK:
1650
+ return
1651
+
1652
+ row["Added to integration"] = status_symbol
1653
+
1654
+ def _mark_unknowns_as_cross():
1655
+ for row in table:
1656
+ if row["Added to integration"] == UNKNOWN:
1657
+ row["Added to integration"] = CROSS
1658
+
1659
+ def _format_default_value(v: Any) -> str:
1660
+ if hasattr(v, "name"):
1661
+ return str(v.name)
1662
+ if isinstance(v, str):
1663
+ return v
1664
+ s = repr(v)
1665
+ return s if len(s) <= 120 else s[:120] + "..."
1666
+
1667
+ def _print_param_default_warnings():
1668
+ data = _get_param_default_warnings()
1669
+ if not data:
1670
+ return
1671
+
1672
+ print("\nWarnings (Default use. It is recommended to set values explicitly):")
1673
+ for param_name in sorted(data.keys()):
1674
+ default_value = data[param_name]["default_value"]
1675
+ funcs = ", ".join(sorted(data[param_name]["funcs"]))
1676
+ dv = _format_default_value(default_value)
1677
+
1678
+ docs_link = data[param_name].get("link_to_docs")
1679
+ docs_part = f" {_link(docs_link)}" if docs_link else ""
1680
+ print(
1681
+ f" ⚠️ Parameter '{param_name}' defaults to {dv} in the following functions: [{funcs}]. "
1682
+ f"For more information, check {docs_part}")
1683
+ print("\nIf this isn’t the intended behaviour, set them explicitly.")
1440
1684
 
1441
1685
  def _print_table():
1686
+ _print_param_default_warnings()
1687
+
1688
+ if not started_from("leap_integration.py"):
1689
+ return
1690
+
1442
1691
  ready_mess = "\nAll parts have been successfully set. If no errors accured, you can now push the project to the Tensorleap system."
1443
1692
  not_ready_mess = "\nSome mandatory components have not yet been added to the Integration test. Recommended next interface to add is: "
1444
1693
  mandatory_ready_mess = "\nAll mandatory parts have been successfully set. If no errors accured, you can now push the project to the Tensorleap system or continue to the next optional reccomeded interface,adding: "
1694
+ code_mapping_failure_mes = "Tensorleap_integration_test code flow failed, check raised exception."
1445
1695
 
1446
1696
  name_width = max(len(row["name"]) for row in table)
1447
1697
  status_width = max(len(row["Added to integration"]) for row in table)
1698
+
1448
1699
  header = f"{'Decorator Name'.ljust(name_width)} | {'Added to integration'.ljust(status_width)}"
1449
1700
  sep = "-" * len(header)
1701
+
1450
1702
  print("\n" + header)
1451
1703
  print(sep)
1452
- ready=True
1704
+
1705
+ ready = True
1706
+ next_step = None
1707
+
1453
1708
  for row in table:
1454
1709
  print(f"{row['name'].ljust(name_width)} | {row['Added to integration'].ljust(status_width)}")
1455
- if row['Added to integration']==CROSS and ready:
1456
- ready=False
1457
- next_step=row['name']
1710
+ if not _crashed["value"] and ready:
1711
+ if row["Added to integration"] != CHECK:
1712
+ ready = False
1713
+ next_step = row["name"]
1458
1714
 
1715
+ if _crashed["value"]:
1716
+ print(f"\nScript crashed before completing all steps. crashed at function '{_current_func['name']}'.")
1717
+ return
1718
+
1719
+ if code_mapping_failure[0]:
1720
+ print(f"\n{CROSS + code_mapping_failure_mes}.")
1721
+ return
1722
+
1723
+ print(ready_mess) if ready else print(
1724
+ mandatory_ready_mess + next_step
1725
+ ) if (next_step and "optional" in next_step) else print(not_ready_mess + (next_step or ""))
1726
+
1727
+ def set_current(name: str):
1728
+ _current_func["name"] = name
1729
+
1730
+ def update_env_params(name: str, status: str = "v"):
1731
+ if name == "code_mapping":
1732
+ code_mapping_failure[0] = 1
1733
+ if status == "v":
1734
+ _set_status(name, CHECK)
1735
+ else:
1736
+ _set_status(name, CROSS)
1459
1737
 
1460
- print(ready_mess) if ready else print(mandatory_ready_mess+next_step) if "optional" in next_step else print(not_ready_mess+next_step)
1461
- def update_env_params(name: str, status: str = "✓"):
1462
- for row in table:
1463
- if row["name"].removesuffix(" (optional)") == name:
1464
- row["Added to integration"] = CHECK if status=="v" else CROSS
1465
- break
1466
1738
  def run_on_exit():
1467
1739
  if _finalizer_called["done"]:
1468
1740
  return
1469
1741
  _finalizer_called["done"] = True
1742
+ if not _crashed["value"]:
1743
+ _mark_unknowns_as_cross()
1744
+
1470
1745
  _print_table()
1746
+
1471
1747
  def handle_exception(exc_type, exc_value, exc_traceback):
1748
+ _crashed["value"] = True
1749
+ crashed_name = _current_func["name"]
1750
+ if crashed_name:
1751
+ row = _find_row(crashed_name)
1752
+ if row and row["Added to integration"] != CHECK:
1753
+ row["Added to integration"] = CROSS
1754
+
1472
1755
  traceback.print_exception(exc_type, exc_value, exc_traceback)
1473
1756
  run_on_exit()
1757
+
1474
1758
  atexit.register(run_on_exit)
1475
1759
  sys.excepthook = handle_exception
1476
- global _update_env_status
1477
- _update_env_status = update_env_params
1478
- return update_env_params
1760
+
1761
+ return set_current, update_env_params
1479
1762
 
1480
1763
 
1764
+ if not _call_from_tl_platform:
1765
+ set_current, update_env_params_func = tensorleap_status_table()
1481
1766
 
1482
1767
 
1483
1768