code-loader 1.0.187__tar.gz → 1.0.188__tar.gz

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 (37) hide show
  1. {code_loader-1.0.187 → code_loader-1.0.188}/PKG-INFO +3 -3
  2. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/inner_leap_binder/leapbinder.py +38 -6
  3. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/inner_leap_binder/leapbinder_decorators.py +72 -12
  4. {code_loader-1.0.187 → code_loader-1.0.188}/pyproject.toml +1 -1
  5. {code_loader-1.0.187 → code_loader-1.0.188}/LICENSE +0 -0
  6. {code_loader-1.0.187 → code_loader-1.0.188}/README.md +0 -0
  7. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/__init__.py +0 -0
  8. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/__init__.py +0 -0
  9. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/datasetclasses.py +0 -0
  10. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/enums.py +0 -0
  11. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/exceptions.py +0 -0
  12. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/mapping.py +0 -0
  13. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/responsedataclasses.py +0 -0
  14. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/sim_config.py +0 -0
  15. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/contract/visualizer_classes.py +0 -0
  16. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/default_losses.py +0 -0
  17. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/default_metrics.py +0 -0
  18. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/__init__.py +0 -0
  19. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/api.py +0 -0
  20. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/cli_config_utils.py +0 -0
  21. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/client.py +0 -0
  22. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/epoch.py +0 -0
  23. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/experiment.py +0 -0
  24. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/experiment_context.py +0 -0
  25. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/types.py +0 -0
  26. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/utils.py +0 -0
  27. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  28. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/inner_leap_binder/__init__.py +0 -0
  29. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/leaploader.py +0 -0
  30. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/leaploaderbase.py +0 -0
  31. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/mixpanel_tracker.py +0 -0
  32. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/plot_functions/__init__.py +0 -0
  33. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/plot_functions/plot_functions.py +0 -0
  34. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/plot_functions/visualize.py +0 -0
  35. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/utils.py +0 -0
  36. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.187 → code_loader-1.0.188}/code_loader/visualizers/default_visualizers.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: code-loader
3
- Version: 1.0.187
3
+ Version: 1.0.188
4
4
  Summary:
5
- Home-page: https://github.com/tensorleap/code-loader
6
5
  License: MIT
7
6
  Author: dorhar
8
7
  Author-email: doron.harnoy@tensorleap.ai
@@ -20,6 +19,7 @@ Requires-Dist: numpy (>=2.3.2,<3.0.0) ; python_version >= "3.11" and python_vers
20
19
  Requires-Dist: psutil (>=5.9.5,<6.0.0)
21
20
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
22
21
  Requires-Dist: requests (>=2.32.3,<3.0.0)
22
+ Project-URL: Homepage, https://github.com/tensorleap/code-loader
23
23
  Project-URL: Repository, https://github.com/tensorleap/code-loader
24
24
  Description-Content-Type: text/markdown
25
25
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  import inspect
3
- from typing import Callable, List, Optional, Dict, Any, Type, Union
3
+ from typing import Callable, List, Optional, Dict, Any, Type, Union, get_args
4
4
 
5
5
  import numpy as np
6
6
  import numpy.typing as npt
@@ -31,6 +31,27 @@ from code_loader.visualizers.default_visualizers import DefaultVisualizer, \
31
31
  mapping_runtime_mode_env_var_mame = '__MAPPING_RUNTIME_MODE__'
32
32
 
33
33
 
34
+ def _stringized_annotation_type_name(annotation: Any) -> Optional[str]:
35
+ """Bare type name if ``annotation`` is a stringized annotation (from ``from __future__
36
+ import annotations`` or a quoted hint), else ``None``. code_loader inspects raw
37
+ annotations expecting real classes, so stringization silently breaks type detection."""
38
+ if isinstance(annotation, str):
39
+ return annotation.split("[")[0].strip().rsplit(".", 1)[-1]
40
+ return None
41
+
42
+
43
+ def _reject_stringized_sample_preprocess_response(function: Callable[..., Any], arg_name: str, annotation: Any) -> None:
44
+ """Fail fast on a stringized SamplePreprocessResponse arg (otherwise it silently
45
+ mis-wires as a regular input, surfacing only on the platform)."""
46
+ if _stringized_annotation_type_name(annotation) == SamplePreprocessResponse.__name__:
47
+ raise Exception(
48
+ f"Argument '{arg_name}' of function '{function.__name__}' is annotated with a string "
49
+ f"('{annotation}') instead of the SamplePreprocessResponse type. This usually means the "
50
+ f"file uses 'from __future__ import annotations' (or a quoted hint), which stringizes "
51
+ f"annotations and breaks Tensorleap type detection. Remove that import (or the quotes) so "
52
+ f"SamplePreprocessResponse is referenced as a real type.")
53
+
54
+
34
55
 
35
56
 
36
57
  class LeapBinder:
@@ -123,6 +144,7 @@ class LeapBinder:
123
144
  regular_arg_names = inspect.getfullargspec(function)[0]
124
145
  preprocess_response_arg_name = None
125
146
  for arg_name, arg_type in inspect.getfullargspec(function).annotations.items():
147
+ _reject_stringized_sample_preprocess_response(function, arg_name, arg_type)
126
148
  if arg_type == SamplePreprocessResponse:
127
149
  if preprocess_response_arg_name is not None:
128
150
  raise Exception("only one argument can be of type SamplePreprocessResponse")
@@ -144,8 +166,8 @@ class LeapBinder:
144
166
 
145
167
  if visualizer_type.value not in map_leap_data_type_to_visualizer_class:
146
168
  raise Exception(
147
- f'The visualizer_type is invalid. current visualizer_type: {visualizer_type}, ' # type: ignore[attr-defined]
148
- f'should be one of : {", ".join([arg.__name__ for arg in LeapData.__args__])}')
169
+ f'The visualizer_type is invalid. current visualizer_type: {visualizer_type}, '
170
+ f'should be one of : {", ".join([arg.__name__ for arg in get_args(LeapData)])}')
149
171
 
150
172
  func_annotations = function.__annotations__
151
173
  if "return" not in func_annotations:
@@ -155,10 +177,17 @@ class LeapBinder:
155
177
  f"https://docs.python.org/3/library/typing.html")
156
178
  else:
157
179
  return_type = func_annotations["return"]
158
- if return_type not in LeapData.__args__: # type: ignore[attr-defined]
180
+ if isinstance(return_type, str):
181
+ raise Exception(
182
+ f"The return type hint of function '{function.__name__}' is a string "
183
+ f"('{return_type}') instead of a type. This usually means the file uses "
184
+ f"'from __future__ import annotations' (or a quoted return hint), which stringizes "
185
+ f"annotations and prevents Tensorleap from detecting the visualizer type. Remove "
186
+ f"that import (or the quotes) so the return type is a real class.")
187
+ if return_type not in get_args(LeapData):
159
188
  raise Exception(
160
- f'The return type of function {function.__name__} is invalid. current return type: {return_type}, ' # type: ignore[attr-defined]
161
- f'should be one of : {", ".join([arg.__name__ for arg in LeapData.__args__])}')
189
+ f'The return type of function {function.__name__} is invalid. current return type: {return_type}, '
190
+ f'should be one of : {", ".join([arg.__name__ for arg in get_args(LeapData)])}')
162
191
 
163
192
  expected_return_type = map_leap_data_type_to_visualizer_class[visualizer_type.value]
164
193
  if not issubclass(return_type, expected_return_type):
@@ -296,6 +325,7 @@ class LeapBinder:
296
325
  regular_arg_names = inspect.getfullargspec(function)[0]
297
326
  preprocess_response_arg_name = None
298
327
  for arg_name, arg_type in inspect.getfullargspec(function).annotations.items():
328
+ _reject_stringized_sample_preprocess_response(function, arg_name, arg_type)
299
329
  if arg_type == SamplePreprocessResponse:
300
330
  if preprocess_response_arg_name is not None:
301
331
  raise Exception("only one argument can be of type SamplePreprocessResponse")
@@ -339,6 +369,7 @@ class LeapBinder:
339
369
  regular_arg_names = inspect.getfullargspec(function)[0]
340
370
  preprocess_response_arg_name = None
341
371
  for arg_name, arg_type in inspect.getfullargspec(function).annotations.items():
372
+ _reject_stringized_sample_preprocess_response(function, arg_name, arg_type)
342
373
  if arg_type == SamplePreprocessResponse:
343
374
  if preprocess_response_arg_name is not None:
344
375
  raise Exception("only one argument can be of type SamplePreprocessResponse")
@@ -380,6 +411,7 @@ class LeapBinder:
380
411
  regular_arg_names = inspect.getfullargspec(function)[0]
381
412
  preprocess_response_arg_name = None
382
413
  for arg_name, arg_type in inspect.getfullargspec(function).annotations.items():
414
+ _reject_stringized_sample_preprocess_response(function, arg_name, arg_type)
383
415
  if arg_type == SamplePreprocessResponse:
384
416
  if preprocess_response_arg_name is not None:
385
417
  raise Exception("only one argument can be of type SamplePreprocessResponse")
@@ -223,6 +223,59 @@ def _add_mapping_connections(connects_to, arg_names, node_mapping_type, name):
223
223
  _add_mapping_connection(user_unique_name, connection_destinations, arg_names, name, node_mapping_type)
224
224
 
225
225
 
226
+ def _require_sample_preprocess_response_supplied(user_function: Callable, args: tuple, kwargs: dict) -> None:
227
+ """A SamplePreprocessResponse argument is auto-injected by the platform / check_dataset
228
+ but NOT inside integration_test, where the author calls the function directly. Fail fast
229
+ with an actionable message instead of a raw 'missing argument' TypeError."""
230
+ spr_arg_name = None
231
+ for arg_name, arg_type in inspect.getfullargspec(user_function).annotations.items():
232
+ if arg_type == SamplePreprocessResponse:
233
+ spr_arg_name = arg_name
234
+ break
235
+ if spr_arg_name is None:
236
+ return
237
+ signature = inspect.signature(user_function)
238
+ param = signature.parameters.get(spr_arg_name)
239
+ # Only enforce a REQUIRED SamplePreprocessResponse arg. If it has a default it is
240
+ # optional and the function may legitimately be called with or without it.
241
+ if param is None or param.default is not inspect.Parameter.empty:
242
+ return
243
+ try:
244
+ supplied = signature.bind_partial(*args, **kwargs).arguments
245
+ except TypeError:
246
+ return
247
+ if spr_arg_name not in supplied:
248
+ raise Exception(
249
+ f"'{user_function.__name__}' takes a SamplePreprocessResponse argument "
250
+ f"'{spr_arg_name}', but it was not provided. Inside integration_test pass it "
251
+ f"explicitly, e.g. {user_function.__name__}(..., "
252
+ f"SamplePreprocessResponse(sample_id, preprocess)).")
253
+
254
+
255
+ def _warn_loss_inputs_not_wired_to_model() -> None:
256
+ """Warn when a custom loss consumes an input encoder the model is not given.
257
+
258
+ Such an input stays a plain NodeMappingType.Input (model-fed inputs get re-tagged to
259
+ Input0/Input1/...) and crashes the platform model build. Losses only — the same pattern
260
+ in a metric or visualizer builds fine."""
261
+ loss_types = {NodeMappingType.Loss, NodeMappingType.CustomLoss}
262
+ for connection in leap_binder.mapping_connections:
263
+ if connection.node is None or connection.node.type not in loss_types:
264
+ continue
265
+ for arg_name, source in (connection.node_inputs or {}).items():
266
+ if source is not None and source.type == NodeMappingType.Input:
267
+ input_name = source.name or arg_name
268
+ store_general_warning(
269
+ key=("loss_input_not_wired_to_model", connection.node.name, input_name),
270
+ message=(
271
+ f"Custom loss '{connection.node.name}' consumes input '{input_name}', "
272
+ f"which is not fed to the model. This is not supported: the platform builds "
273
+ f"losses into the model graph, so an input the model does not take has no "
274
+ f"source there and the model build fails. Instead, carry this data in the "
275
+ f"ground truth and derive it inside the loss (not as a separate input "
276
+ f"encoder)."))
277
+
278
+
226
279
  def tensorleap_integration_test():
227
280
  def decorating_function(integration_test_function: Callable):
228
281
  leap_binder.integration_test_func = integration_test_function
@@ -279,6 +332,8 @@ def tensorleap_integration_test():
279
332
  finally:
280
333
  _called_from_inside_tl_integration_test_decorator = False
281
334
 
335
+ _warn_loss_inputs_not_wired_to_model()
336
+
282
337
  leap_binder.check()
283
338
 
284
339
  return inner
@@ -768,18 +823,14 @@ def tensorleap_custom_metric(name: str,
768
823
 
769
824
  leap_binder.setup_container.metrics[-1].metric_handler_data.direction = effective_direction
770
825
 
771
- if compute_insights is None:
772
- effective_compute_insights = None
773
- elif isinstance(compute_insights, dict):
774
- effective_compute_insights = {key: compute_insights.get(key, True) for key in result_keys}
775
- else:
776
- effective_compute_insights = {key: compute_insights for key in result_keys}
777
-
778
- leap_binder.setup_container.metrics[-1].metric_handler_data.compute_insights = effective_compute_insights
779
-
780
826
  if defaulted_direction_keys and not _call_from_tl_platform:
781
- warning_keys = {key for key in defaulted_direction_keys
782
- if (effective_compute_insights or {}).get(key, True)}
827
+ if compute_insights is None:
828
+ effective_compute_insights = {}
829
+ elif isinstance(compute_insights, dict):
830
+ effective_compute_insights = compute_insights
831
+ else:
832
+ effective_compute_insights = {k: compute_insights for k in result_keys}
833
+ warning_keys = {key for key in defaulted_direction_keys if effective_compute_insights.get(key, True)}
783
834
  if warning_keys:
784
835
  store_warning_by_param(
785
836
  param_name=f"direction[{warning_keys}]",
@@ -826,6 +877,7 @@ def tensorleap_custom_metric(name: str,
826
877
  _called_from_inside_tl_decorator += 1
827
878
 
828
879
  try:
880
+ _require_sample_preprocess_response_supplied(user_function, args, kwargs)
829
881
  result = user_function(*args, **kwargs)
830
882
  finally:
831
883
  _called_from_inside_tl_decorator -= 1
@@ -1035,6 +1087,7 @@ def tensorleap_custom_instances_metric(name: str,
1035
1087
  _called_from_inside_tl_decorator += 1
1036
1088
 
1037
1089
  try:
1090
+ _require_sample_preprocess_response_supplied(user_function, args, kwargs)
1038
1091
  result = user_function(*args, **kwargs)
1039
1092
  finally:
1040
1093
  _called_from_inside_tl_decorator -= 1
@@ -1387,6 +1440,7 @@ def tensorleap_custom_visualizer(name: str, visualizer_type: LeapDataType,
1387
1440
  _called_from_inside_tl_decorator += 1
1388
1441
 
1389
1442
  try:
1443
+ _require_sample_preprocess_response_supplied(user_function, args, kwargs)
1390
1444
  result = user_function(*args, **kwargs)
1391
1445
  finally:
1392
1446
  _called_from_inside_tl_decorator -= 1
@@ -1933,8 +1987,13 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
1933
1987
 
1934
1988
  )
1935
1989
 
1990
+ # channel_dim is the channel axis in the BATCHED tensor (batch = axis 0):
1991
+ # 1 for channel-first (NCHW), -1 for channel-last (NHWC); 0 is invalid.
1936
1992
  if channel_dim <= 0 and channel_dim != -1:
1937
- raise Exception(f"Channel dim for input {name} is expected to be either -1 or positive")
1993
+ raise Exception(
1994
+ f"Invalid channel_dim ({channel_dim}) for input '{name}'. channel_dim is the "
1995
+ f"channel axis in the batched tensor (batch = axis 0), so 0 is invalid. Use 1 "
1996
+ f"for channel-first (NCHW) and -1 for channel-last (NHWC).")
1938
1997
 
1939
1998
  def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
1940
1999
  assert type(sample_id) == preprocess_response.sample_id_type, \
@@ -2153,6 +2212,7 @@ def tensorleap_custom_loss(name: str, connects_to=None):
2153
2212
  _called_from_inside_tl_decorator += 1
2154
2213
 
2155
2214
  try:
2215
+ _require_sample_preprocess_response_supplied(user_function, args, kwargs)
2156
2216
  result = user_function(*args, **kwargs)
2157
2217
  finally:
2158
2218
  _called_from_inside_tl_decorator -= 1
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
3
 
4
- version = "1.0.187"
4
+ version = "1.0.188"
5
5
  description = ""
6
6
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
7
7
  license = "MIT"
File without changes
File without changes