morphml 1.0.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.
Potentially problematic release.
This version of morphml might be problematic. Click here for more details.
- morphml/__init__.py +14 -0
- morphml/api/__init__.py +26 -0
- morphml/api/app.py +326 -0
- morphml/api/auth.py +193 -0
- morphml/api/client.py +338 -0
- morphml/api/models.py +132 -0
- morphml/api/rate_limit.py +192 -0
- morphml/benchmarking/__init__.py +36 -0
- morphml/benchmarking/comparison.py +430 -0
- morphml/benchmarks/__init__.py +56 -0
- morphml/benchmarks/comparator.py +409 -0
- morphml/benchmarks/datasets.py +280 -0
- morphml/benchmarks/metrics.py +199 -0
- morphml/benchmarks/openml_suite.py +201 -0
- morphml/benchmarks/problems.py +289 -0
- morphml/benchmarks/suite.py +318 -0
- morphml/cli/__init__.py +5 -0
- morphml/cli/commands/experiment.py +329 -0
- morphml/cli/main.py +457 -0
- morphml/cli/quickstart.py +312 -0
- morphml/config.py +278 -0
- morphml/constraints/__init__.py +19 -0
- morphml/constraints/handler.py +205 -0
- morphml/constraints/predicates.py +285 -0
- morphml/core/__init__.py +3 -0
- morphml/core/crossover.py +449 -0
- morphml/core/dsl/README.md +359 -0
- morphml/core/dsl/__init__.py +72 -0
- morphml/core/dsl/ast_nodes.py +364 -0
- morphml/core/dsl/compiler.py +318 -0
- morphml/core/dsl/layers.py +368 -0
- morphml/core/dsl/lexer.py +336 -0
- morphml/core/dsl/parser.py +455 -0
- morphml/core/dsl/search_space.py +386 -0
- morphml/core/dsl/syntax.py +199 -0
- morphml/core/dsl/type_system.py +361 -0
- morphml/core/dsl/validator.py +386 -0
- morphml/core/graph/__init__.py +40 -0
- morphml/core/graph/edge.py +124 -0
- morphml/core/graph/graph.py +507 -0
- morphml/core/graph/mutations.py +409 -0
- morphml/core/graph/node.py +196 -0
- morphml/core/graph/serialization.py +361 -0
- morphml/core/graph/visualization.py +431 -0
- morphml/core/objectives/__init__.py +20 -0
- morphml/core/search/__init__.py +33 -0
- morphml/core/search/individual.py +252 -0
- morphml/core/search/parameters.py +453 -0
- morphml/core/search/population.py +375 -0
- morphml/core/search/search_engine.py +340 -0
- morphml/distributed/__init__.py +76 -0
- morphml/distributed/fault_tolerance.py +497 -0
- morphml/distributed/health_monitor.py +348 -0
- morphml/distributed/master.py +709 -0
- morphml/distributed/proto/README.md +224 -0
- morphml/distributed/proto/__init__.py +74 -0
- morphml/distributed/proto/worker.proto +170 -0
- morphml/distributed/proto/worker_pb2.py +79 -0
- morphml/distributed/proto/worker_pb2_grpc.py +423 -0
- morphml/distributed/resource_manager.py +416 -0
- morphml/distributed/scheduler.py +567 -0
- morphml/distributed/storage/__init__.py +33 -0
- morphml/distributed/storage/artifacts.py +381 -0
- morphml/distributed/storage/cache.py +366 -0
- morphml/distributed/storage/checkpointing.py +329 -0
- morphml/distributed/storage/database.py +459 -0
- morphml/distributed/worker.py +549 -0
- morphml/evaluation/__init__.py +5 -0
- morphml/evaluation/heuristic.py +237 -0
- morphml/exceptions.py +55 -0
- morphml/execution/__init__.py +5 -0
- morphml/execution/local_executor.py +350 -0
- morphml/integrations/__init__.py +28 -0
- morphml/integrations/jax_adapter.py +206 -0
- morphml/integrations/pytorch_adapter.py +530 -0
- morphml/integrations/sklearn_adapter.py +206 -0
- morphml/integrations/tensorflow_adapter.py +230 -0
- morphml/logging_config.py +93 -0
- morphml/meta_learning/__init__.py +66 -0
- morphml/meta_learning/architecture_similarity.py +277 -0
- morphml/meta_learning/experiment_database.py +240 -0
- morphml/meta_learning/knowledge_base/__init__.py +19 -0
- morphml/meta_learning/knowledge_base/embedder.py +179 -0
- morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
- morphml/meta_learning/knowledge_base/meta_features.py +265 -0
- morphml/meta_learning/knowledge_base/vector_store.py +271 -0
- morphml/meta_learning/predictors/__init__.py +27 -0
- morphml/meta_learning/predictors/ensemble.py +221 -0
- morphml/meta_learning/predictors/gnn_predictor.py +552 -0
- morphml/meta_learning/predictors/learning_curve.py +231 -0
- morphml/meta_learning/predictors/proxy_metrics.py +261 -0
- morphml/meta_learning/strategy_evolution/__init__.py +27 -0
- morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
- morphml/meta_learning/strategy_evolution/bandit.py +276 -0
- morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
- morphml/meta_learning/transfer.py +581 -0
- morphml/meta_learning/warm_start.py +286 -0
- morphml/optimizers/__init__.py +74 -0
- morphml/optimizers/adaptive_operators.py +399 -0
- morphml/optimizers/bayesian/__init__.py +52 -0
- morphml/optimizers/bayesian/acquisition.py +387 -0
- morphml/optimizers/bayesian/base.py +319 -0
- morphml/optimizers/bayesian/gaussian_process.py +635 -0
- morphml/optimizers/bayesian/smac.py +534 -0
- morphml/optimizers/bayesian/tpe.py +411 -0
- morphml/optimizers/differential_evolution.py +220 -0
- morphml/optimizers/evolutionary/__init__.py +61 -0
- morphml/optimizers/evolutionary/cma_es.py +416 -0
- morphml/optimizers/evolutionary/differential_evolution.py +556 -0
- morphml/optimizers/evolutionary/encoding.py +426 -0
- morphml/optimizers/evolutionary/particle_swarm.py +449 -0
- morphml/optimizers/genetic_algorithm.py +486 -0
- morphml/optimizers/gradient_based/__init__.py +22 -0
- morphml/optimizers/gradient_based/darts.py +550 -0
- morphml/optimizers/gradient_based/enas.py +585 -0
- morphml/optimizers/gradient_based/operations.py +474 -0
- morphml/optimizers/gradient_based/utils.py +601 -0
- morphml/optimizers/hill_climbing.py +169 -0
- morphml/optimizers/multi_objective/__init__.py +56 -0
- morphml/optimizers/multi_objective/indicators.py +504 -0
- morphml/optimizers/multi_objective/nsga2.py +647 -0
- morphml/optimizers/multi_objective/visualization.py +427 -0
- morphml/optimizers/nsga2.py +308 -0
- morphml/optimizers/random_search.py +172 -0
- morphml/optimizers/simulated_annealing.py +181 -0
- morphml/plugins/__init__.py +35 -0
- morphml/plugins/custom_evaluator_example.py +81 -0
- morphml/plugins/custom_optimizer_example.py +63 -0
- morphml/plugins/plugin_system.py +454 -0
- morphml/reports/__init__.py +30 -0
- morphml/reports/generator.py +362 -0
- morphml/tracking/__init__.py +7 -0
- morphml/tracking/experiment.py +309 -0
- morphml/tracking/logger.py +301 -0
- morphml/tracking/reporter.py +357 -0
- morphml/utils/__init__.py +6 -0
- morphml/utils/checkpoint.py +189 -0
- morphml/utils/comparison.py +390 -0
- morphml/utils/export.py +407 -0
- morphml/utils/progress.py +392 -0
- morphml/utils/validation.py +392 -0
- morphml/version.py +7 -0
- morphml/visualization/__init__.py +50 -0
- morphml/visualization/analytics.py +423 -0
- morphml/visualization/architecture_diagrams.py +353 -0
- morphml/visualization/architecture_plot.py +223 -0
- morphml/visualization/convergence_plot.py +174 -0
- morphml/visualization/crossover_viz.py +386 -0
- morphml/visualization/graph_viz.py +338 -0
- morphml/visualization/pareto_plot.py +149 -0
- morphml/visualization/plotly_dashboards.py +422 -0
- morphml/visualization/population.py +309 -0
- morphml/visualization/progress.py +260 -0
- morphml-1.0.0.dist-info/METADATA +434 -0
- morphml-1.0.0.dist-info/RECORD +158 -0
- morphml-1.0.0.dist-info/WHEEL +4 -0
- morphml-1.0.0.dist-info/entry_points.txt +3 -0
- morphml-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""Type system for MorphML DSL.
|
|
2
|
+
|
|
3
|
+
Provides type checking and type inference for DSL constructs.
|
|
4
|
+
|
|
5
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
6
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from morphml.core.dsl.ast_nodes import (
|
|
13
|
+
ASTVisitor,
|
|
14
|
+
ExperimentNode,
|
|
15
|
+
LayerNode,
|
|
16
|
+
ParamNode,
|
|
17
|
+
SearchSpaceNode,
|
|
18
|
+
)
|
|
19
|
+
from morphml.logging_config import get_logger
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Type(Enum):
|
|
25
|
+
"""Type system for DSL values."""
|
|
26
|
+
|
|
27
|
+
# Primitive types
|
|
28
|
+
INT = "int"
|
|
29
|
+
FLOAT = "float"
|
|
30
|
+
STRING = "string"
|
|
31
|
+
BOOL = "bool"
|
|
32
|
+
|
|
33
|
+
# Collection types
|
|
34
|
+
LIST = "list"
|
|
35
|
+
DICT = "dict"
|
|
36
|
+
|
|
37
|
+
# Special types
|
|
38
|
+
ANY = "any"
|
|
39
|
+
NONE = "none"
|
|
40
|
+
LAYER = "layer"
|
|
41
|
+
PARAM = "param"
|
|
42
|
+
|
|
43
|
+
def __repr__(self) -> str:
|
|
44
|
+
"""String representation."""
|
|
45
|
+
return f"Type.{self.name}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TypeEnvironment:
|
|
49
|
+
"""
|
|
50
|
+
Type environment for type checking.
|
|
51
|
+
|
|
52
|
+
Maintains a symbol table mapping names to types.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, parent: Optional["TypeEnvironment"] = None):
|
|
56
|
+
"""
|
|
57
|
+
Initialize type environment.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
parent: Parent environment for nested scopes
|
|
61
|
+
"""
|
|
62
|
+
self.parent = parent
|
|
63
|
+
self.symbols: Dict[str, Type] = {}
|
|
64
|
+
|
|
65
|
+
def define(self, name: str, type_: Type) -> None:
|
|
66
|
+
"""Define a symbol with its type."""
|
|
67
|
+
self.symbols[name] = type_
|
|
68
|
+
|
|
69
|
+
def lookup(self, name: str) -> Optional[Type]:
|
|
70
|
+
"""Look up symbol type."""
|
|
71
|
+
if name in self.symbols:
|
|
72
|
+
return self.symbols[name]
|
|
73
|
+
elif self.parent:
|
|
74
|
+
return self.parent.lookup(name)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
"""String representation."""
|
|
79
|
+
return f"TypeEnvironment({self.symbols})"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TypeError:
|
|
83
|
+
"""Represents a type error."""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
message: str,
|
|
88
|
+
node: str = "Unknown",
|
|
89
|
+
expected: Optional[Type] = None,
|
|
90
|
+
actual: Optional[Type] = None,
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
Initialize type error.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
message: Error message
|
|
97
|
+
node: Node type where error occurred
|
|
98
|
+
expected: Expected type
|
|
99
|
+
actual: Actual type
|
|
100
|
+
"""
|
|
101
|
+
self.message = message
|
|
102
|
+
self.node = node
|
|
103
|
+
self.expected = expected
|
|
104
|
+
self.actual = actual
|
|
105
|
+
|
|
106
|
+
def __str__(self) -> str:
|
|
107
|
+
"""String representation."""
|
|
108
|
+
if self.expected and self.actual:
|
|
109
|
+
return (
|
|
110
|
+
f"[{self.node}] {self.message}\n"
|
|
111
|
+
f" Expected: {self.expected}\n"
|
|
112
|
+
f" Actual: {self.actual}"
|
|
113
|
+
)
|
|
114
|
+
return f"[{self.node}] {self.message}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TypeChecker(ASTVisitor):
|
|
118
|
+
"""
|
|
119
|
+
Type checker for MorphML DSL.
|
|
120
|
+
|
|
121
|
+
Performs static type checking on the AST to catch type errors
|
|
122
|
+
before compilation.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> checker = TypeChecker()
|
|
126
|
+
>>> errors = checker.check(ast)
|
|
127
|
+
>>> if errors:
|
|
128
|
+
... for error in errors:
|
|
129
|
+
... print(error)
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(self) -> None:
|
|
133
|
+
"""Initialize type checker."""
|
|
134
|
+
self.errors: List[TypeError] = []
|
|
135
|
+
self.env = TypeEnvironment()
|
|
136
|
+
self.current_layer_type: Optional[str] = None
|
|
137
|
+
|
|
138
|
+
# Parameter type requirements for each layer type
|
|
139
|
+
self.layer_param_types: Dict[str, Dict[str, Type]] = {
|
|
140
|
+
"conv2d": {
|
|
141
|
+
"filters": Type.INT,
|
|
142
|
+
"kernel_size": Type.INT,
|
|
143
|
+
"strides": Type.INT,
|
|
144
|
+
"padding": Type.STRING,
|
|
145
|
+
},
|
|
146
|
+
"dense": {"units": Type.INT, "activation": Type.STRING},
|
|
147
|
+
"dropout": {"rate": Type.FLOAT},
|
|
148
|
+
"max_pool": {"pool_size": Type.INT},
|
|
149
|
+
"avg_pool": {"pool_size": Type.INT},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def check(self, ast: ExperimentNode) -> List[TypeError]:
|
|
153
|
+
"""
|
|
154
|
+
Type check complete experiment AST.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
ast: ExperimentNode to type check
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of type errors (empty if type-safe)
|
|
161
|
+
"""
|
|
162
|
+
logger.info("Starting type checking")
|
|
163
|
+
|
|
164
|
+
# Reset state
|
|
165
|
+
self.errors.clear()
|
|
166
|
+
self.env = TypeEnvironment()
|
|
167
|
+
|
|
168
|
+
# Type check via visitor pattern
|
|
169
|
+
ast.accept(self)
|
|
170
|
+
|
|
171
|
+
if self.errors:
|
|
172
|
+
logger.warning(f"Type checking found {len(self.errors)} errors")
|
|
173
|
+
else:
|
|
174
|
+
logger.info("Type checking successful")
|
|
175
|
+
|
|
176
|
+
return self.errors
|
|
177
|
+
|
|
178
|
+
def visit_search_space(self, node: SearchSpaceNode) -> None:
|
|
179
|
+
"""Type check search space."""
|
|
180
|
+
# Create new scope for search space
|
|
181
|
+
old_env = self.env
|
|
182
|
+
self.env = TypeEnvironment(parent=old_env)
|
|
183
|
+
|
|
184
|
+
# Type check global parameters
|
|
185
|
+
for param_name, param_node in node.global_params.items():
|
|
186
|
+
param_type = self._infer_param_type(param_node)
|
|
187
|
+
self.env.define(param_name, param_type)
|
|
188
|
+
|
|
189
|
+
# Type check layers
|
|
190
|
+
super().visit_search_space(node)
|
|
191
|
+
|
|
192
|
+
# Restore environment
|
|
193
|
+
self.env = old_env
|
|
194
|
+
|
|
195
|
+
def visit_layer(self, node: LayerNode) -> None:
|
|
196
|
+
"""Type check layer."""
|
|
197
|
+
self.current_layer_type = node.layer_type
|
|
198
|
+
|
|
199
|
+
# Get expected parameter types for this layer
|
|
200
|
+
expected_types = self.layer_param_types.get(node.layer_type, {})
|
|
201
|
+
|
|
202
|
+
# Check each parameter
|
|
203
|
+
for param_name, param_node in node.params.items():
|
|
204
|
+
# Infer actual type
|
|
205
|
+
actual_type = self._infer_param_type(param_node)
|
|
206
|
+
|
|
207
|
+
# Check against expected type if known
|
|
208
|
+
if param_name in expected_types:
|
|
209
|
+
expected_type = expected_types[param_name]
|
|
210
|
+
|
|
211
|
+
if not self._is_compatible(actual_type, expected_type):
|
|
212
|
+
self.errors.append(
|
|
213
|
+
TypeError(
|
|
214
|
+
f"Type mismatch for parameter '{param_name}' in layer '{node.layer_type}'",
|
|
215
|
+
node="Layer",
|
|
216
|
+
expected=expected_type,
|
|
217
|
+
actual=actual_type,
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
super().visit_layer(node)
|
|
222
|
+
|
|
223
|
+
self.current_layer_type = None
|
|
224
|
+
|
|
225
|
+
def visit_param(self, node: ParamNode) -> None:
|
|
226
|
+
"""Type check parameter."""
|
|
227
|
+
# Check type consistency within parameter values
|
|
228
|
+
if len(node.values) > 1:
|
|
229
|
+
types = [self._infer_type(v) for v in node.values]
|
|
230
|
+
if len(set(types)) > 1:
|
|
231
|
+
# Allow int/float mixing
|
|
232
|
+
if not (set(types) <= {Type.INT, Type.FLOAT}):
|
|
233
|
+
self.errors.append(
|
|
234
|
+
TypeError(
|
|
235
|
+
f"Inconsistent types in parameter '{node.name}': {types}",
|
|
236
|
+
node="Parameter",
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _infer_type(self, value: Any) -> Type:
|
|
241
|
+
"""
|
|
242
|
+
Infer type from Python value.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
value: Python value
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Type enum
|
|
249
|
+
"""
|
|
250
|
+
if isinstance(value, bool):
|
|
251
|
+
return Type.BOOL
|
|
252
|
+
elif isinstance(value, int):
|
|
253
|
+
return Type.INT
|
|
254
|
+
elif isinstance(value, float):
|
|
255
|
+
return Type.FLOAT
|
|
256
|
+
elif isinstance(value, str):
|
|
257
|
+
return Type.STRING
|
|
258
|
+
elif isinstance(value, list):
|
|
259
|
+
return Type.LIST
|
|
260
|
+
elif isinstance(value, dict):
|
|
261
|
+
return Type.DICT
|
|
262
|
+
elif value is None:
|
|
263
|
+
return Type.NONE
|
|
264
|
+
else:
|
|
265
|
+
return Type.ANY
|
|
266
|
+
|
|
267
|
+
def _infer_param_type(self, param_node: ParamNode) -> Type:
|
|
268
|
+
"""
|
|
269
|
+
Infer type for a parameter node.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
param_node: Parameter node
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Inferred type
|
|
276
|
+
"""
|
|
277
|
+
if not param_node.values:
|
|
278
|
+
return Type.NONE
|
|
279
|
+
|
|
280
|
+
# Infer from first value
|
|
281
|
+
first_type = self._infer_type(param_node.values[0])
|
|
282
|
+
|
|
283
|
+
# Check if all values have same type
|
|
284
|
+
all_same = all(self._infer_type(v) == first_type for v in param_node.values)
|
|
285
|
+
|
|
286
|
+
if all_same:
|
|
287
|
+
return first_type
|
|
288
|
+
|
|
289
|
+
# Mixed types - check for numeric
|
|
290
|
+
types = [self._infer_type(v) for v in param_node.values]
|
|
291
|
+
if set(types) <= {Type.INT, Type.FLOAT}:
|
|
292
|
+
return Type.FLOAT
|
|
293
|
+
|
|
294
|
+
return Type.ANY
|
|
295
|
+
|
|
296
|
+
def _is_compatible(self, actual: Type, expected: Type) -> bool:
|
|
297
|
+
"""
|
|
298
|
+
Check if actual type is compatible with expected type.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
actual: Actual type
|
|
302
|
+
expected: Expected type
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
True if compatible
|
|
306
|
+
"""
|
|
307
|
+
# Same type is compatible
|
|
308
|
+
if actual == expected:
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
# ANY is compatible with everything
|
|
312
|
+
if actual == Type.ANY or expected == Type.ANY:
|
|
313
|
+
return True
|
|
314
|
+
|
|
315
|
+
# INT is compatible with FLOAT
|
|
316
|
+
if actual == Type.INT and expected == Type.FLOAT:
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def check_types(ast: ExperimentNode) -> List[TypeError]:
|
|
323
|
+
"""
|
|
324
|
+
Convenience function to type check AST.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
ast: ExperimentNode to check
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List of type errors
|
|
331
|
+
"""
|
|
332
|
+
checker = TypeChecker()
|
|
333
|
+
return checker.check(ast)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def infer_value_type(value: Any) -> str:
|
|
337
|
+
"""
|
|
338
|
+
Infer type name from Python value.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
value: Python value
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Type name as string
|
|
345
|
+
"""
|
|
346
|
+
if isinstance(value, bool):
|
|
347
|
+
return "boolean"
|
|
348
|
+
elif isinstance(value, int):
|
|
349
|
+
return "integer"
|
|
350
|
+
elif isinstance(value, float):
|
|
351
|
+
return "float"
|
|
352
|
+
elif isinstance(value, str):
|
|
353
|
+
return "string"
|
|
354
|
+
elif isinstance(value, list):
|
|
355
|
+
return "list"
|
|
356
|
+
elif isinstance(value, dict):
|
|
357
|
+
return "dict"
|
|
358
|
+
elif value is None:
|
|
359
|
+
return "none"
|
|
360
|
+
else:
|
|
361
|
+
return "any"
|