aimodelshare 0.1.54__py3-none-any.whl → 0.1.59__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 aimodelshare might be problematic. Click here for more details.

Files changed (35) hide show
  1. aimodelshare/__init__.py +94 -14
  2. aimodelshare/aimsonnx.py +263 -82
  3. aimodelshare/api.py +13 -12
  4. aimodelshare/auth.py +163 -0
  5. aimodelshare/base_image.py +1 -1
  6. aimodelshare/containerisation.py +1 -1
  7. aimodelshare/data_sharing/download_data.py +133 -83
  8. aimodelshare/generatemodelapi.py +7 -6
  9. aimodelshare/main/authorization.txt +275 -275
  10. aimodelshare/main/eval_lambda.txt +81 -13
  11. aimodelshare/model.py +492 -196
  12. aimodelshare/modeluser.py +22 -0
  13. aimodelshare/moral_compass/README.md +367 -0
  14. aimodelshare/moral_compass/__init__.py +58 -0
  15. aimodelshare/moral_compass/_version.py +3 -0
  16. aimodelshare/moral_compass/api_client.py +553 -0
  17. aimodelshare/moral_compass/challenge.py +365 -0
  18. aimodelshare/moral_compass/config.py +187 -0
  19. aimodelshare/playground.py +26 -14
  20. aimodelshare/preprocessormodules.py +60 -6
  21. aimodelshare/pyspark/authorization.txt +258 -258
  22. aimodelshare/pyspark/eval_lambda.txt +1 -1
  23. aimodelshare/reproducibility.py +20 -5
  24. aimodelshare/utils/__init__.py +78 -0
  25. aimodelshare/utils/optional_deps.py +38 -0
  26. aimodelshare-0.1.59.dist-info/METADATA +258 -0
  27. {aimodelshare-0.1.54.dist-info → aimodelshare-0.1.59.dist-info}/RECORD +30 -24
  28. aimodelshare-0.1.59.dist-info/licenses/LICENSE +5 -0
  29. {aimodelshare-0.1.54.dist-info → aimodelshare-0.1.59.dist-info}/top_level.txt +0 -1
  30. aimodelshare-0.1.54.dist-info/METADATA +0 -63
  31. aimodelshare-0.1.54.dist-info/licenses/LICENSE +0 -2
  32. tests/__init__.py +0 -0
  33. tests/test_aimsonnx.py +0 -135
  34. tests/test_playground.py +0 -721
  35. {aimodelshare-0.1.54.dist-info → aimodelshare-0.1.59.dist-info}/WHEEL +0 -0
aimodelshare/__init__.py CHANGED
@@ -1,18 +1,98 @@
1
- from .playground import ModelPlayground, Competition,Experiment, Data
2
- from .preprocessormodules import export_preprocessor,upload_preprocessor,import_preprocessor
3
- from .data_sharing.download_data import download_data, import_quickstart_data
4
- from .reproducibility import export_reproducibility_env, import_reproducibility_env
1
+ """
2
+ Top-level aimodelshare package initializer.
5
3
 
4
+ Refactored to:
5
+ - Avoid eager importing of heavy optional dependencies (networkx, torch, tensorflow, etc.)
6
+ - Allow lightweight submodules (e.g. aimodelshare.moral_compass) to work in minimal environments.
7
+ - Preserve backward compatibility where possible without forcing heavy installs.
8
+ """
9
+
10
+ from importlib import import_module
11
+ import warnings
6
12
 
7
13
  __all__ = [
8
- # Object Oriented
9
- ModelPlayground,
10
- Competition,
11
- Data,
12
- Experiment,
13
- # Preprocessor
14
- upload_preprocessor,
15
- import_preprocessor,
16
- export_preprocessor,
17
- download_data
14
+ # Heavy/high-level objects (added only if imported successfully)
15
+ "ModelPlayground",
16
+ "Competition",
17
+ "Data",
18
+ "Experiment",
19
+ # Preprocessor helpers (optional)
20
+ "upload_preprocessor",
21
+ "import_preprocessor",
22
+ "export_preprocessor",
23
+ "download_data",
24
+ # Moral Compass client (lightweight)
25
+ "MoralcompassApiClient",
26
+ "MoralcompassTableMeta",
27
+ "MoralcompassUserStats",
28
+ "ApiClientError",
29
+ "NotFoundError",
30
+ "ServerError",
31
+ "get_api_base_url",
18
32
  ]
33
+
34
+ def _safe_import(module_name, symbols, remove_if_missing=True):
35
+ """
36
+ Attempt to import given symbols from module_name.
37
+ On failure, emit a warning and optionally remove them from __all__.
38
+ """
39
+ try:
40
+ mod = import_module(module_name)
41
+ for sym in symbols:
42
+ try:
43
+ globals()[sym] = getattr(mod, sym)
44
+ except AttributeError:
45
+ warnings.warn(
46
+ f"aimodelshare: symbol '{sym}' not found in module '{module_name}'."
47
+ )
48
+ if remove_if_missing and sym in __all__:
49
+ __all__.remove(sym)
50
+ except Exception as exc:
51
+ warnings.warn(
52
+ f"aimodelshare: optional module '{module_name}' not loaded ({exc}). "
53
+ "Lightweight submodules remain usable."
54
+ )
55
+ if remove_if_missing:
56
+ for sym in symbols:
57
+ if sym in __all__ and sym not in globals():
58
+ __all__.remove(sym)
59
+
60
+ # Attempt optional imports (silently skip if deps missing)
61
+ _safe_import(
62
+ "aimodelshare.playground",
63
+ ["ModelPlayground", "Competition", "Experiment", "Data"],
64
+ )
65
+
66
+ _safe_import(
67
+ "aimodelshare.preprocessormodules",
68
+ ["export_preprocessor", "upload_preprocessor", "import_preprocessor"],
69
+ )
70
+
71
+ _safe_import(
72
+ "aimodelshare.data_sharing.download_data",
73
+ ["download_data", "import_quickstart_data"], # import_quickstart_data not in __all__
74
+ remove_if_missing=True,
75
+ )
76
+ # If import_quickstart_data is missing, we don't expose it; if present we leave it accessible.
77
+ if "import_quickstart_data" not in globals():
78
+ # Ensure it's not accidentally in __all__
79
+ if "import_quickstart_data" in __all__:
80
+ __all__.remove("import_quickstart_data")
81
+
82
+ # Moral Compass submodule (expected always present in new branch)
83
+ _safe_import(
84
+ "aimodelshare.moral_compass",
85
+ [
86
+ "MoralcompassApiClient",
87
+ "MoralcompassTableMeta",
88
+ "MoralcompassUserStats",
89
+ "ApiClientError",
90
+ "NotFoundError",
91
+ "ServerError",
92
+ "get_api_base_url",
93
+ ],
94
+ remove_if_missing=False, # If this fails, keep names so import errors surface clearly later
95
+ )
96
+
97
+ # Ensure __all__ only contains names actually available (except intentional exposure for debug)
98
+ __all__ = [name for name in __all__ if name in globals()]
aimodelshare/aimsonnx.py CHANGED
@@ -2,28 +2,31 @@
2
2
  import pandas as pd
3
3
  import numpy as np
4
4
 
5
+ # Import optional dependency checker
6
+ from aimodelshare.utils.optional_deps import check_optional
7
+
5
8
  # ml frameworks
6
9
  try:
7
10
  import sklearn
8
11
  from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
9
12
  except:
10
- print("Warning: Please install sklearn to enable sklearn features")
13
+ check_optional("sklearn", "Scikit-learn")
11
14
 
12
15
  try:
13
16
  import torch
14
17
  except:
15
- print("Warning: Please install pytorch to enable pytorch features")
18
+ check_optional("torch", "PyTorch")
16
19
 
17
20
  try:
18
21
  import xgboost
19
22
  except:
20
- print("Warning: Please install xgboost to enable xgboost features")
23
+ check_optional("xgboost", "XGBoost")
21
24
 
22
25
  try:
23
26
  import tensorflow as tf
24
27
  import keras
25
28
  except:
26
- print("Warning: Please install tensorflow/keras to enable tensorflow/keras features")
29
+ check_optional("tensorflow", "TensorFlow/Keras")
27
30
 
28
31
  try:
29
32
  import pyspark
@@ -32,14 +35,17 @@ try:
32
35
  from pyspark.ml.tuning import CrossValidatorModel, TrainValidationSplitModel
33
36
  from onnxmltools import convert_sparkml
34
37
  except:
35
- print("Warning: Please install pyspark to enable pyspark features")
38
+ check_optional("pyspark", "PySpark")
36
39
 
37
40
 
38
41
  # onnx modules
39
42
  import onnx
40
43
  import skl2onnx
41
44
  from skl2onnx import convert_sklearn
42
- import tf2onnx
45
+ # tf2onnx import is lazy-loaded to avoid requiring TensorFlow for non-TF workflows
46
+ _TF2ONNX_AVAILABLE = None
47
+ _tf2onnx_module = None
48
+ _tensorflow_module = None
43
49
  try:
44
50
  from torch.onnx import export
45
51
  except:
@@ -71,18 +77,59 @@ import wget
71
77
  from copy import copy
72
78
  import psutil
73
79
  from pympler import asizeof
74
- from IPython.core.display import display, HTML, SVG
80
+ from IPython.display import display, HTML, SVG
75
81
  import absl.logging
76
82
  import networkx as nx
77
83
  import warnings
78
84
  from pathlib import Path
79
85
  import time
80
86
  import signal
81
- from scikeras.wrappers import KerasClassifier, KerasRegressor
87
+
88
+ # scikeras imports keras which requires TensorFlow - lazy load it
89
+ try:
90
+ from scikeras.wrappers import KerasClassifier, KerasRegressor
91
+ _SCIKERAS_AVAILABLE = True
92
+ except ImportError:
93
+ _SCIKERAS_AVAILABLE = False
94
+ KerasClassifier = None
95
+ KerasRegressor = None
82
96
 
83
97
 
84
98
  absl.logging.set_verbosity(absl.logging.ERROR)
85
99
 
100
+ def _check_tf2onnx_available():
101
+ """Check if tf2onnx and TensorFlow are available, and load them if needed.
102
+
103
+ Returns:
104
+ tuple: (tf2onnx_module, tensorflow_module) on success
105
+
106
+ Raises:
107
+ RuntimeError: If TensorFlow or tf2onnx are not installed
108
+ """
109
+ global _TF2ONNX_AVAILABLE, _tf2onnx_module, _tensorflow_module
110
+
111
+ if _TF2ONNX_AVAILABLE is None:
112
+ try:
113
+ import tf2onnx as tf2onnx_temp
114
+ import tensorflow as tf_temp
115
+ _tf2onnx_module = tf2onnx_temp
116
+ _tensorflow_module = tf_temp
117
+ _TF2ONNX_AVAILABLE = True
118
+ except ImportError as e:
119
+ _TF2ONNX_AVAILABLE = False
120
+ raise RuntimeError(
121
+ "TensorFlow and tf2onnx are required for Keras model conversion to ONNX. "
122
+ "Please install them with: pip install tensorflow tf2onnx"
123
+ ) from e
124
+
125
+ if not _TF2ONNX_AVAILABLE:
126
+ raise RuntimeError(
127
+ "TensorFlow and tf2onnx are required for Keras model conversion to ONNX. "
128
+ "Please install them with: pip install tensorflow tf2onnx"
129
+ )
130
+
131
+ return _tf2onnx_module, _tensorflow_module
132
+
86
133
  def _extract_onnx_metadata(onnx_model, framework):
87
134
  '''Extracts model metadata from ONNX file.'''
88
135
 
@@ -265,28 +312,8 @@ def _sklearn_to_onnx(model, initial_types=None, transfer_learning=None,
265
312
 
266
313
  onx = convert_sklearn(model, initial_types=initial_types,target_opset={'': 15, 'ai.onnx.ml': 2})
267
314
 
268
- ## Dynamically set model ir_version to ensure sklearn opsets work properly
269
- from onnx.helper import VERSION_TABLE
270
- import onnx
271
- import numpy as np
272
-
273
- indexlocationlist=[]
274
- for i in VERSION_TABLE:
275
- indexlocationlist.append(str(i).find(str(onnx.__version__)))
276
-
277
-
278
- arr = np.array(indexlocationlist)
279
-
280
- def condition(x): return x > -1
281
-
282
- bool_arr = condition(arr)
283
-
284
- output = np.where(bool_arr)[0]
285
-
286
- ir_version=VERSION_TABLE[output[0]][1]
287
-
288
- #add to model object before saving
289
- onx.ir_version = ir_version
315
+ ## set model ir_version to ensure sklearn opsets work properly
316
+ onx.ir_version = 8
290
317
 
291
318
  # generate metadata dict
292
319
  metadata = {}
@@ -555,8 +582,9 @@ def _keras_to_onnx(model, transfer_learning=None,
555
582
  deep_learning=None, task_type=None, epochs=None):
556
583
  '''Converts a Keras model to ONNX and extracts metadata.'''
557
584
 
558
- import tf2onnx
559
- import tensorflow as tf
585
+ # Check and load tf2onnx and TensorFlow lazily (only when needed)
586
+ tf2onnx, tf = _check_tf2onnx_available()
587
+
560
588
  import numpy as np
561
589
  import onnx
562
590
  import pickle
@@ -934,7 +962,7 @@ def model_to_onnx(model, framework=None, model_input=None, initial_types=None,
934
962
  from pyspark.ml.tuning import CrossValidatorModel, TrainValidationSplitModel
935
963
  from onnxmltools import convert_sparkml
936
964
  except:
937
- print("Warning: Please install pyspark to enable pyspark features")
965
+ check_optional("pyspark", "PySpark")
938
966
  onnx = _pyspark_to_onnx(model, initial_types=initial_types,
939
967
  transfer_learning=transfer_learning,
940
968
  deep_learning=deep_learning,
@@ -989,23 +1017,39 @@ def model_to_onnx_timed(model_filepath, force_onnx=False, timeout=60, model_inpu
989
1017
 
990
1018
  except:
991
1019
  print("Timeout: Model to ONNX conversion is taking longer than expected. This can be the case for big models.")
992
- response = ''
993
- while response not in {"1", "2"}:
994
- response = input("Do you want to keep trying (1) or submit predictions only (2)? ")
995
-
996
- if response == "1":
997
- try:
998
- import torch
999
- if isinstance(model_filepath, torch.nn.Module):
1020
+
1021
+ # Detect CI/testing environment for non-interactive fallback
1022
+ is_non_interactive = (
1023
+ os.environ.get("PYTEST_CURRENT_TEST") is not None or
1024
+ os.environ.get("AIMS_NON_INTERACTIVE") == "1"
1025
+ )
1026
+
1027
+ if is_non_interactive:
1028
+ # Auto-fallback to predictions-only in CI/testing environment
1029
+ print("Non-interactive environment detected. Falling back to predictions-only submission.")
1030
+ model_filepath = None
1031
+ else:
1032
+ # Interactive prompt for manual runs
1033
+ response = ''
1034
+ while response not in {"1", "2"}:
1035
+ response = input("Do you want to keep trying (1) or submit predictions only (2)? ")
1036
+
1037
+ if response == "1":
1038
+ try:
1039
+ import torch
1040
+ if isinstance(model_filepath, torch.nn.Module):
1041
+ onnx_model = model_to_onnx(model_filepath, model_input=model_input)
1042
+ else:
1043
+ onnx_model = model_to_onnx(model_filepath)
1044
+ except Exception as e:
1045
+ # Final fallback - if torch-specific handling failed, try generic conversion
1046
+ # This handles cases where torch module detection fails but conversion might still work
1047
+ warnings.warn(f"PyTorch-specific ONNX conversion failed ({e}), attempting generic conversion")
1000
1048
  onnx_model = model_to_onnx(model_filepath, model_input=model_input)
1001
- else:
1002
- onnx_model = model_to_onnx(model_filepath)
1003
- except:
1004
- onnx_model = model_to_onnx(model_filepath)
1005
- model_filepath = onnx_model
1049
+ model_filepath = onnx_model
1006
1050
 
1007
- elif response == "2":
1008
- model_filepath = None
1051
+ elif response == "2":
1052
+ model_filepath = None
1009
1053
 
1010
1054
  finally:
1011
1055
  print()
@@ -1024,6 +1068,12 @@ def _get_metadata(onnx_model):
1024
1068
  #assert(isinstance(onnx_model, onnx.onnx_ml_pb2.ModelProto)), \
1025
1069
  #"Please pass a onnx model object."
1026
1070
 
1071
+ # Handle None input gracefully - always return a dict
1072
+ if onnx_model is None:
1073
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1074
+ print("[DEBUG] _get_metadata: onnx_model is None, returning empty dict")
1075
+ return {}
1076
+
1027
1077
  try:
1028
1078
  onnx_meta = onnx_model.metadata_props
1029
1079
 
@@ -1034,36 +1084,121 @@ def _get_metadata(onnx_model):
1034
1084
 
1035
1085
  onnx_meta_dict = ast.literal_eval(onnx_meta_dict['model_metadata'])
1036
1086
 
1087
+ # Handle case where metadata is stored as a list instead of dict
1088
+ if isinstance(onnx_meta_dict, list):
1089
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1090
+ print(f"[DEBUG] _get_metadata: metadata is a list of length {len(onnx_meta_dict)}")
1091
+ if len(onnx_meta_dict) > 0 and isinstance(onnx_meta_dict[0], dict):
1092
+ onnx_meta_dict = onnx_meta_dict[0]
1093
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1094
+ print("[DEBUG] _get_metadata: Extracted first dict from list")
1095
+ else:
1096
+ # Return empty dict if list doesn't contain valid dicts
1097
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1098
+ print("[DEBUG] _get_metadata: List does not contain valid dicts, returning empty dict")
1099
+ return {}
1100
+
1101
+ # Ensure we have a dict at this point
1102
+ if not isinstance(onnx_meta_dict, dict):
1103
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1104
+ print(f"[DEBUG] _get_metadata: Unexpected metadata type {type(onnx_meta_dict)}, returning empty dict")
1105
+ return {}
1106
+
1037
1107
  #if onnx_meta_dict['model_config'] != None and \
1038
1108
  #onnx_meta_dict['ml_framework'] != 'pytorch':
1039
1109
  # onnx_meta_dict['model_config'] = ast.literal_eval(onnx_meta_dict['model_config'])
1040
1110
 
1041
- if onnx_meta_dict['model_architecture'] != None:
1042
- onnx_meta_dict['model_architecture'] = ast.literal_eval(onnx_meta_dict['model_architecture'])
1111
+ # Attempt to parse nested fields only if they are string representations of dicts
1112
+ if 'model_architecture' in onnx_meta_dict and onnx_meta_dict['model_architecture'] != None:
1113
+ try:
1114
+ if isinstance(onnx_meta_dict['model_architecture'], str):
1115
+ onnx_meta_dict['model_architecture'] = ast.literal_eval(onnx_meta_dict['model_architecture'])
1116
+ except (ValueError, SyntaxError):
1117
+ # Keep as-is if parsing fails
1118
+ pass
1119
+
1120
+ if 'model_config' in onnx_meta_dict and onnx_meta_dict['model_config'] != None:
1121
+ try:
1122
+ if isinstance(onnx_meta_dict['model_config'], str):
1123
+ onnx_meta_dict['model_config'] = ast.literal_eval(onnx_meta_dict['model_config'])
1124
+ except (ValueError, SyntaxError):
1125
+ # Keep as-is if parsing fails
1126
+ pass
1043
1127
 
1044
- if onnx_meta_dict['metadata_onnx'] != None:
1045
- onnx_meta_dict['metadata_onnx'] = ast.literal_eval(onnx_meta_dict['metadata_onnx'])
1128
+ if 'metadata_onnx' in onnx_meta_dict and onnx_meta_dict['metadata_onnx'] != None:
1129
+ try:
1130
+ if isinstance(onnx_meta_dict['metadata_onnx'], str):
1131
+ onnx_meta_dict['metadata_onnx'] = ast.literal_eval(onnx_meta_dict['metadata_onnx'])
1132
+ except (ValueError, SyntaxError):
1133
+ # Keep as-is if parsing fails
1134
+ pass
1046
1135
 
1047
1136
  # onnx_meta_dict['model_image'] = onnx_to_image(onnx_model)
1048
1137
 
1049
1138
  except Exception as e:
1050
1139
 
1051
- print(e)
1140
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1141
+ print(f"[DEBUG] _get_metadata: Exception during metadata extraction: {e}")
1052
1142
 
1053
- onnx_meta_dict = ast.literal_eval(onnx_meta_dict)
1143
+ try:
1144
+ onnx_meta_dict = ast.literal_eval(onnx_meta_dict)
1145
+ # Handle list case in exception path as well
1146
+ if isinstance(onnx_meta_dict, list) and len(onnx_meta_dict) > 0 and isinstance(onnx_meta_dict[0], dict):
1147
+ onnx_meta_dict = onnx_meta_dict[0]
1148
+ elif not isinstance(onnx_meta_dict, dict):
1149
+ onnx_meta_dict = {}
1150
+ except:
1151
+ onnx_meta_dict = {}
1152
+
1153
+ # Final safety check: ensure we always return a dict
1154
+ if not isinstance(onnx_meta_dict, dict):
1155
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1156
+ print(f"[DEBUG] _get_metadata: Final check failed, returning empty dict instead of {type(onnx_meta_dict)}")
1157
+ return {}
1054
1158
 
1055
1159
  return onnx_meta_dict
1056
1160
 
1057
1161
 
1058
1162
 
1059
1163
  def _get_leaderboard_data(onnx_model, eval_metrics=None):
1164
+ '''Extract leaderboard data from ONNX model or return defaults.
1060
1165
 
1166
+ This function performs single-pass normalization and safely handles:
1167
+ - None onnx_model (returns defaults)
1168
+ - Invalid metadata structures
1169
+ - Missing keys in metadata
1170
+ '''
1171
+
1172
+ # Start with eval_metrics if provided, otherwise empty dict
1061
1173
  if eval_metrics is not None:
1062
- metadata = eval_metrics
1174
+ metadata = dict(eval_metrics) if isinstance(eval_metrics, dict) else {}
1063
1175
  else:
1064
- metadata = dict()
1176
+ metadata = {}
1177
+
1178
+ # Handle None onnx_model gracefully
1179
+ if onnx_model is None:
1180
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1181
+ print("[DEBUG] _get_leaderboard_data: onnx_model is None, using default metadata")
1182
+ # Return metadata with safe defaults injected
1183
+ metadata['ml_framework'] = metadata.get('ml_framework', None)
1184
+ metadata['transfer_learning'] = metadata.get('transfer_learning', None)
1185
+ metadata['deep_learning'] = metadata.get('deep_learning', None)
1186
+ metadata['model_type'] = metadata.get('model_type', None)
1187
+ metadata['depth'] = metadata.get('depth', 0)
1188
+ metadata['num_params'] = metadata.get('num_params', 0)
1189
+ return metadata
1065
1190
 
1191
+ # Get metadata from ONNX - _get_metadata now always returns a dict
1066
1192
  metadata_raw = _get_metadata(onnx_model)
1193
+
1194
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1195
+ print(f"[DEBUG] _get_leaderboard_data: metadata_raw type={type(metadata_raw)}, keys={list(metadata_raw.keys()) if isinstance(metadata_raw, dict) else 'N/A'}")
1196
+
1197
+ # Single-pass normalization: ensure metadata_raw is a dict
1198
+ if not isinstance(metadata_raw, dict):
1199
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1200
+ print(f"[DEBUG] _get_leaderboard_data: metadata_raw is not a dict (type={type(metadata_raw)}), using empty dict")
1201
+ metadata_raw = {}
1067
1202
 
1068
1203
  # get list of current layer types
1069
1204
  layer_list_keras, activation_list_keras = _get_layer_names()
@@ -1072,46 +1207,55 @@ def _get_leaderboard_data(onnx_model, eval_metrics=None):
1072
1207
  layer_list = list(set(layer_list_keras + layer_list_pytorch))
1073
1208
  activation_list = list(set(activation_list_keras + activation_list_pytorch))
1074
1209
 
1075
- # get general model info
1076
- metadata['ml_framework'] = metadata_raw['ml_framework']
1077
- metadata['transfer_learning'] = metadata_raw['transfer_learning']
1078
- metadata['deep_learning'] = metadata_raw['deep_learning']
1079
- metadata['model_type'] = metadata_raw['model_type']
1210
+ # get general model info - use .get() for safety
1211
+ metadata['ml_framework'] = metadata_raw.get('ml_framework')
1212
+ metadata['transfer_learning'] = metadata_raw.get('transfer_learning')
1213
+ metadata['deep_learning'] = metadata_raw.get('deep_learning')
1214
+ metadata['model_type'] = metadata_raw.get('model_type')
1080
1215
 
1081
1216
 
1082
1217
  # get neural network metrics
1083
- if metadata_raw['ml_framework'] in ['keras', 'pytorch'] or metadata_raw['model_type'] in ['MLPClassifier', 'MLPRegressor']:
1084
- metadata['depth'] = metadata_raw['model_architecture']['layers_number']
1085
- metadata['num_params'] = sum(metadata_raw['model_architecture']['layers_n_params'])
1218
+ # Add isinstance check for model_architecture to prevent TypeError
1219
+ if (metadata_raw.get('ml_framework') in ['keras', 'pytorch'] or
1220
+ metadata_raw.get('model_type') in ['MLPClassifier', 'MLPRegressor']) and \
1221
+ isinstance(metadata_raw.get('model_architecture'), dict):
1222
+
1223
+ metadata['depth'] = metadata_raw['model_architecture'].get('layers_number', 0)
1224
+ metadata['num_params'] = sum(metadata_raw['model_architecture'].get('layers_n_params', []))
1086
1225
 
1087
1226
  for i in layer_list:
1088
- if i in metadata_raw['model_architecture']['layers_summary']:
1089
- metadata[i.lower()+'_layers'] = metadata_raw['model_architecture']['layers_summary'][i]
1227
+ layers_summary = metadata_raw['model_architecture'].get('layers_summary', {})
1228
+ if i in layers_summary:
1229
+ metadata[i.lower()+'_layers'] = layers_summary[i]
1090
1230
  elif i.lower()+'_layers' not in metadata.keys():
1091
1231
  metadata[i.lower()+'_layers'] = 0
1092
1232
 
1093
1233
  for i in activation_list:
1094
- if i in metadata_raw['model_architecture']['activations_summary']:
1234
+ activations_summary = metadata_raw['model_architecture'].get('activations_summary', {})
1235
+ if i in activations_summary:
1095
1236
  if i.lower()+'_act' in metadata:
1096
- metadata[i.lower()+'_act'] += metadata_raw['model_architecture']['activations_summary'][i]
1237
+ metadata[i.lower()+'_act'] += activations_summary[i]
1097
1238
  else:
1098
- metadata[i.lower()+'_act'] = metadata_raw['model_architecture']['activations_summary'][i]
1239
+ metadata[i.lower()+'_act'] = activations_summary[i]
1099
1240
  else:
1100
1241
  if i.lower()+'_act' not in metadata:
1101
1242
  metadata[i.lower()+'_act'] = 0
1102
1243
 
1103
- metadata['loss'] = metadata_raw['model_architecture']['loss']
1104
- metadata['optimizer'] = metadata_raw['model_architecture']["optimizer"]
1105
- metadata['model_config'] = metadata_raw['model_config']
1106
- metadata['epochs'] = metadata_raw['epochs']
1107
- metadata['memory_size'] = metadata_raw['memory_size']
1244
+ metadata['loss'] = metadata_raw['model_architecture'].get('loss')
1245
+ metadata['optimizer'] = metadata_raw['model_architecture'].get('optimizer')
1246
+ metadata['model_config'] = metadata_raw.get('model_config')
1247
+ metadata['epochs'] = metadata_raw.get('epochs')
1248
+ metadata['memory_size'] = metadata_raw.get('memory_size')
1108
1249
 
1109
1250
  # get sklearn & pyspark model metrics
1110
- elif metadata_raw['ml_framework'] in ['sklearn', 'xgboost', 'pyspark']:
1251
+ elif metadata_raw.get('ml_framework') in ['sklearn', 'xgboost', 'pyspark']:
1111
1252
  metadata['depth'] = 0
1112
1253
 
1113
1254
  try:
1114
- metadata['num_params'] = sum(metadata_raw['model_architecture']['layers_n_params'])
1255
+ if isinstance(metadata_raw.get('model_architecture'), dict):
1256
+ metadata['num_params'] = sum(metadata_raw['model_architecture'].get('layers_n_params', []))
1257
+ else:
1258
+ metadata['num_params'] = 0
1115
1259
  except:
1116
1260
  metadata['num_params'] = 0
1117
1261
 
@@ -1124,15 +1268,29 @@ def _get_leaderboard_data(onnx_model, eval_metrics=None):
1124
1268
  metadata['loss'] = None
1125
1269
 
1126
1270
  try:
1127
- metadata['optimizer'] = metadata_raw['model_architecture']['optimizer']
1271
+ if isinstance(metadata_raw.get('model_architecture'), dict):
1272
+ metadata['optimizer'] = metadata_raw['model_architecture'].get('optimizer')
1273
+ else:
1274
+ metadata['optimizer'] = None
1128
1275
  except:
1129
1276
  metadata['optimizer'] = None
1130
1277
 
1131
1278
  try:
1132
- metadata['model_config'] = metadata_raw['model_config']
1279
+ metadata['model_config'] = metadata_raw.get('model_config')
1133
1280
  except:
1134
1281
  metadata['model_config'] = None
1135
1282
 
1283
+ # Default handling for unknown frameworks
1284
+ else:
1285
+ if os.environ.get("AIMODELSHARE_DEBUG_METADATA"):
1286
+ print(f"[DEBUG] _get_leaderboard_data: Unknown framework '{metadata_raw.get('ml_framework')}', using defaults")
1287
+ metadata.setdefault('depth', 0)
1288
+ metadata.setdefault('num_params', 0)
1289
+ for i in layer_list:
1290
+ metadata.setdefault(i.lower()+'_layers', 0)
1291
+ for i in activation_list:
1292
+ metadata.setdefault(i.lower()+'_act', 0)
1293
+
1136
1294
  return metadata
1137
1295
 
1138
1296
 
@@ -1553,7 +1711,8 @@ def _get_sklearn_modules():
1553
1711
 
1554
1712
  sklearn_modules = ['ensemble', 'gaussian_process', 'isotonic',
1555
1713
  'linear_model', 'mixture', 'multiclass', 'naive_bayes',
1556
- 'neighbors', 'neural_network', 'svm', 'tree']
1714
+ 'neighbors', 'neural_network', 'svm', 'tree',
1715
+ 'discriminant_analysis', 'calibration']
1557
1716
 
1558
1717
  models_modules_dict = {}
1559
1718
 
@@ -1569,9 +1728,31 @@ def _get_sklearn_modules():
1569
1728
 
1570
1729
  def model_from_string(model_type):
1571
1730
  models_modules_dict = _get_sklearn_modules()
1572
- module = models_modules_dict[model_type]
1573
- model_class = getattr(importlib.import_module(module), model_type)
1574
- return model_class
1731
+ try:
1732
+ module = models_modules_dict[model_type]
1733
+ model_class = getattr(importlib.import_module(module), model_type)
1734
+ return model_class
1735
+ except KeyError:
1736
+ # Return a placeholder class if estimator not found
1737
+ import warnings
1738
+ warnings.warn(f"Model type '{model_type}' not found in sklearn modules. Returning placeholder class.")
1739
+
1740
+ # Create a minimal placeholder class that can be instantiated
1741
+ class PlaceholderModel:
1742
+ def __init__(self, **kwargs):
1743
+ self._model_type = model_type
1744
+ self._params = kwargs
1745
+
1746
+ def get_params(self, deep=True):
1747
+ return self._params
1748
+
1749
+ def __str__(self):
1750
+ return f"PlaceholderModel({self._model_type})"
1751
+
1752
+ def __repr__(self):
1753
+ return f"PlaceholderModel({self._model_type})"
1754
+
1755
+ return PlaceholderModel
1575
1756
 
1576
1757
  def _get_pyspark_modules():
1577
1758
  try: