ob-metaflow-extensions 1.1.171rc1__py2.py3-none-any.whl → 1.4.35__py2.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 ob-metaflow-extensions might be problematic. Click here for more details.
- metaflow_extensions/outerbounds/plugins/__init__.py +6 -3
- metaflow_extensions/outerbounds/plugins/apps/app_cli.py +0 -29
- metaflow_extensions/outerbounds/plugins/apps/app_deploy_decorator.py +146 -0
- metaflow_extensions/outerbounds/plugins/apps/core/__init__.py +10 -0
- metaflow_extensions/outerbounds/plugins/apps/core/_state_machine.py +506 -0
- metaflow_extensions/outerbounds/plugins/apps/core/_vendor/__init__.py +0 -0
- metaflow_extensions/outerbounds/plugins/apps/core/_vendor/spinner/__init__.py +4 -0
- metaflow_extensions/outerbounds/plugins/apps/core/_vendor/spinner/spinners.py +478 -0
- metaflow_extensions/outerbounds/plugins/apps/core/app_cli.py +1200 -0
- metaflow_extensions/outerbounds/plugins/apps/core/app_config.py +146 -0
- metaflow_extensions/outerbounds/plugins/apps/core/artifacts.py +0 -0
- metaflow_extensions/outerbounds/plugins/apps/core/capsule.py +958 -0
- metaflow_extensions/outerbounds/plugins/apps/core/click_importer.py +24 -0
- metaflow_extensions/outerbounds/plugins/apps/core/code_package/__init__.py +3 -0
- metaflow_extensions/outerbounds/plugins/apps/core/code_package/code_packager.py +618 -0
- metaflow_extensions/outerbounds/plugins/apps/core/code_package/examples.py +125 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/__init__.py +12 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/cli_generator.py +161 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/config_utils.py +868 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/schema_export.py +288 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/typed_configs.py +139 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/typed_init_generator.py +398 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/unified_config.py +1088 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config_schema.yaml +337 -0
- metaflow_extensions/outerbounds/plugins/apps/core/dependencies.py +115 -0
- metaflow_extensions/outerbounds/plugins/apps/core/deployer.py +303 -0
- metaflow_extensions/outerbounds/plugins/apps/core/experimental/__init__.py +89 -0
- metaflow_extensions/outerbounds/plugins/apps/core/perimeters.py +87 -0
- metaflow_extensions/outerbounds/plugins/apps/core/secrets.py +164 -0
- metaflow_extensions/outerbounds/plugins/apps/core/utils.py +233 -0
- metaflow_extensions/outerbounds/plugins/apps/core/validations.py +17 -0
- metaflow_extensions/outerbounds/plugins/aws/assume_role_decorator.py +25 -12
- metaflow_extensions/outerbounds/plugins/checkpoint_datastores/coreweave.py +9 -77
- metaflow_extensions/outerbounds/plugins/checkpoint_datastores/external_chckpt.py +85 -0
- metaflow_extensions/outerbounds/plugins/checkpoint_datastores/nebius.py +7 -78
- metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +6 -2
- metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +1 -0
- metaflow_extensions/outerbounds/plugins/nvct/nvct_decorator.py +8 -8
- metaflow_extensions/outerbounds/plugins/optuna/__init__.py +48 -0
- metaflow_extensions/outerbounds/plugins/profilers/simple_card_decorator.py +96 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/__init__.py +7 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/binary_caller.py +132 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/constants.py +11 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/exceptions.py +13 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/proxy_bootstrap.py +59 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/s3_proxy_api.py +93 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/s3_proxy_decorator.py +250 -0
- metaflow_extensions/outerbounds/plugins/s3_proxy/s3_proxy_manager.py +225 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +6 -3
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +13 -7
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +8 -2
- metaflow_extensions/outerbounds/plugins/torchtune/__init__.py +4 -0
- metaflow_extensions/outerbounds/plugins/vllm/__init__.py +173 -95
- metaflow_extensions/outerbounds/plugins/vllm/status_card.py +9 -9
- metaflow_extensions/outerbounds/plugins/vllm/vllm_manager.py +159 -9
- metaflow_extensions/outerbounds/remote_config.py +8 -3
- metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +62 -1
- metaflow_extensions/outerbounds/toplevel/ob_internal.py +2 -0
- metaflow_extensions/outerbounds/toplevel/plugins/optuna/__init__.py +1 -0
- metaflow_extensions/outerbounds/toplevel/s3_proxy.py +88 -0
- {ob_metaflow_extensions-1.1.171rc1.dist-info → ob_metaflow_extensions-1.4.35.dist-info}/METADATA +2 -2
- {ob_metaflow_extensions-1.1.171rc1.dist-info → ob_metaflow_extensions-1.4.35.dist-info}/RECORD +64 -22
- {ob_metaflow_extensions-1.1.171rc1.dist-info → ob_metaflow_extensions-1.4.35.dist-info}/WHEEL +0 -0
- {ob_metaflow_extensions-1.1.171rc1.dist-info → ob_metaflow_extensions-1.4.35.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Typed Init Generator for ConfigMeta Classes
|
|
3
|
+
|
|
4
|
+
This module provides a mechanism to dynamically generate explicit typed classes
|
|
5
|
+
from ConfigMeta classes that IDEs can understand and provide autocomplete for.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union, Type, Set
|
|
9
|
+
|
|
10
|
+
from .config_utils import ConfigMeta
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
current_dir = os.path.dirname(__file__)
|
|
15
|
+
|
|
16
|
+
TYPED_DICT_IMPORT = """
|
|
17
|
+
import sys
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
# on 3.8+ use the stdlib TypedDict;
|
|
21
|
+
# in TYPE_CHECKING blocks mypy/pyright still pick it up on older Pythons
|
|
22
|
+
if sys.version_info >= (3, 8):
|
|
23
|
+
from typing import TypedDict
|
|
24
|
+
else:
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
# for the benefit of type-checkers
|
|
27
|
+
from typing import TypedDict # noqa: F401
|
|
28
|
+
# runtime no-op TypedDict shim
|
|
29
|
+
class _TypedDictMeta(type):
|
|
30
|
+
def __new__(cls, name, bases, namespace, total=True):
|
|
31
|
+
# ignore total at runtime
|
|
32
|
+
return super().__new__(cls, name, bases, namespace)
|
|
33
|
+
|
|
34
|
+
class TypedDict(dict, metaclass=_TypedDictMeta):
|
|
35
|
+
# Runtime stand-in for typing.TypedDict on <3.8.
|
|
36
|
+
pass
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def collect_nested_configs_recursive(
|
|
41
|
+
config_class: Type, visited: Optional[Set[str]] = None
|
|
42
|
+
) -> Dict[str, Type]:
|
|
43
|
+
"""
|
|
44
|
+
Recursively collect all nested ConfigMeta classes from a config class.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
config_class: A class that inherits from ConfigMeta
|
|
48
|
+
visited: Set of already visited class names to avoid infinite recursion
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dictionary mapping class names to ConfigMeta classes
|
|
52
|
+
"""
|
|
53
|
+
if visited is None:
|
|
54
|
+
visited = set()
|
|
55
|
+
|
|
56
|
+
nested_configs = {}
|
|
57
|
+
|
|
58
|
+
# Avoid infinite recursion by tracking visited classes
|
|
59
|
+
if config_class.__name__ in visited:
|
|
60
|
+
return nested_configs
|
|
61
|
+
|
|
62
|
+
visited.add(config_class.__name__)
|
|
63
|
+
|
|
64
|
+
# First pass: collect immediate nested configs
|
|
65
|
+
for field_name, field_info in config_class._fields.items():
|
|
66
|
+
if ConfigMeta.is_instance(field_info.field_type):
|
|
67
|
+
nested_class = field_info.field_type
|
|
68
|
+
nested_configs[nested_class.__name__] = nested_class
|
|
69
|
+
|
|
70
|
+
# Recursively collect nested configs from this nested class
|
|
71
|
+
deeper_nested = collect_nested_configs_recursive(
|
|
72
|
+
nested_class, visited.copy()
|
|
73
|
+
)
|
|
74
|
+
nested_configs.update(deeper_nested)
|
|
75
|
+
|
|
76
|
+
return nested_configs
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def generate_typed_class_code(config_class: Type) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Generate the actual Python code for a typed class that IDEs can understand.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
config_class: A class that inherits from ConfigMeta
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Python code string for the typed class
|
|
88
|
+
"""
|
|
89
|
+
if not hasattr(config_class, "_fields"):
|
|
90
|
+
raise ValueError(f"Class {config_class.__name__} is not a ConfigMeta class")
|
|
91
|
+
|
|
92
|
+
class_name = f"Typed{config_class.__name__}"
|
|
93
|
+
|
|
94
|
+
# Generate TypedDict for nested configs - now recursive
|
|
95
|
+
nested_typeddict_code = []
|
|
96
|
+
|
|
97
|
+
# Recursively collect all nested configs
|
|
98
|
+
nested_configs = collect_nested_configs_recursive(config_class)
|
|
99
|
+
|
|
100
|
+
# Generate TypedDict classes for all nested configs
|
|
101
|
+
for nested_name, nested_class in nested_configs.items():
|
|
102
|
+
dict_name = f"{nested_name}Dict"
|
|
103
|
+
fields = []
|
|
104
|
+
|
|
105
|
+
for field_name, field_info in nested_class._fields.items():
|
|
106
|
+
field_type = _get_type_string(field_info.field_type, quote_config_meta=True)
|
|
107
|
+
if not field_info.required:
|
|
108
|
+
field_type = f"Optional[{field_type}]"
|
|
109
|
+
fields.append(f" {field_name}: {field_type}")
|
|
110
|
+
|
|
111
|
+
typeddict_code = f"""class {dict_name}(TypedDict, total=False):
|
|
112
|
+
{chr(10).join(fields)}"""
|
|
113
|
+
nested_typeddict_code.append(typeddict_code)
|
|
114
|
+
|
|
115
|
+
# Generate __init__ method signature
|
|
116
|
+
required_params = []
|
|
117
|
+
optional_params = []
|
|
118
|
+
all_assignments = []
|
|
119
|
+
|
|
120
|
+
for field_name, field_info in config_class._fields.items():
|
|
121
|
+
field_type = field_info.field_type
|
|
122
|
+
|
|
123
|
+
# Handle nested ConfigMeta classes
|
|
124
|
+
if ConfigMeta.is_instance(field_type):
|
|
125
|
+
type_hint = f"Optional[{field_type.__name__}Dict]"
|
|
126
|
+
param_line = f" {field_name}: {type_hint} = None"
|
|
127
|
+
optional_params.append(param_line)
|
|
128
|
+
else:
|
|
129
|
+
# All params will be set as options here even if the are required in the
|
|
130
|
+
# configMeta
|
|
131
|
+
type_hint = _get_type_string(field_type)
|
|
132
|
+
param_line = f" {field_name}: Optional[{type_hint}] = None"
|
|
133
|
+
optional_params.append(param_line)
|
|
134
|
+
|
|
135
|
+
all_assignments.append(f' "{field_name}": {field_name}')
|
|
136
|
+
|
|
137
|
+
# Combine required params first, then optional params
|
|
138
|
+
all_params = required_params + optional_params
|
|
139
|
+
|
|
140
|
+
# Generate the class code
|
|
141
|
+
newline = "\n"
|
|
142
|
+
comma_newline = ",\n"
|
|
143
|
+
|
|
144
|
+
# Add **kwargs to the parameter list
|
|
145
|
+
if all_params:
|
|
146
|
+
params_with_kwargs = all_params + [" **kwargs"]
|
|
147
|
+
else:
|
|
148
|
+
params_with_kwargs = [" **kwargs"]
|
|
149
|
+
|
|
150
|
+
class_code = f"""class {class_name}:
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
{comma_newline.join(params_with_kwargs)}
|
|
154
|
+
) -> None:
|
|
155
|
+
self._kwargs = {{
|
|
156
|
+
{comma_newline.join(all_assignments)}
|
|
157
|
+
}}
|
|
158
|
+
# Add any additional kwargs
|
|
159
|
+
self._kwargs.update(kwargs)
|
|
160
|
+
# Remove None values
|
|
161
|
+
self._kwargs = {{k: v for k, v in self._kwargs.items() if v is not None}}
|
|
162
|
+
self._config_class = {config_class.__name__}
|
|
163
|
+
self._config = self.create_config()
|
|
164
|
+
|
|
165
|
+
def create_config(self) -> {config_class.__name__}:
|
|
166
|
+
return {config_class.__name__}.from_dict(self._kwargs)
|
|
167
|
+
|
|
168
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
169
|
+
return self._config.to_dict()"""
|
|
170
|
+
|
|
171
|
+
# Combine all code
|
|
172
|
+
full_code = []
|
|
173
|
+
if nested_typeddict_code:
|
|
174
|
+
full_code.extend(nested_typeddict_code)
|
|
175
|
+
full_code.append("") # Empty line
|
|
176
|
+
full_code.append(class_code)
|
|
177
|
+
|
|
178
|
+
return (newline + newline).join(full_code)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _get_type_string(field_type: Type, quote_config_meta: bool = False) -> str:
|
|
182
|
+
"""Convert a type to its string representation for code generation.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
field_type: The type to convert
|
|
186
|
+
quote_config_meta: Whether to quote ConfigMeta type references for forward declarations
|
|
187
|
+
"""
|
|
188
|
+
if field_type == str:
|
|
189
|
+
return "str"
|
|
190
|
+
elif field_type == int:
|
|
191
|
+
return "int"
|
|
192
|
+
elif field_type == float:
|
|
193
|
+
return "float"
|
|
194
|
+
elif field_type == bool:
|
|
195
|
+
return "bool"
|
|
196
|
+
elif ConfigMeta.is_instance(field_type):
|
|
197
|
+
# Handle ConfigMeta classes by referencing their Dict type
|
|
198
|
+
dict_type = f"{field_type.__name__}Dict"
|
|
199
|
+
return f'"{dict_type}"' if quote_config_meta else dict_type
|
|
200
|
+
elif hasattr(field_type, "__origin__"):
|
|
201
|
+
# Handle generic types like List[str], Dict[str, str], etc.
|
|
202
|
+
origin = field_type.__origin__
|
|
203
|
+
args = getattr(field_type, "__args__", ())
|
|
204
|
+
|
|
205
|
+
if origin == list:
|
|
206
|
+
if args:
|
|
207
|
+
return f"List[{_get_type_string(args[0], quote_config_meta)}]"
|
|
208
|
+
return "List[Any]"
|
|
209
|
+
elif origin == dict:
|
|
210
|
+
if len(args) == 2:
|
|
211
|
+
return f"Dict[{_get_type_string(args[0], quote_config_meta)}, {_get_type_string(args[1], quote_config_meta)}]"
|
|
212
|
+
return "Dict[str, Any]"
|
|
213
|
+
elif origin == Union:
|
|
214
|
+
# Handle Optional types
|
|
215
|
+
if len(args) == 2 and type(None) in args:
|
|
216
|
+
non_none_type = args[0] if args[1] is type(None) else args[1]
|
|
217
|
+
return f"Optional[{_get_type_string(non_none_type, quote_config_meta)}]"
|
|
218
|
+
return f"Union[{', '.join(_get_type_string(arg, quote_config_meta) for arg in args)}]"
|
|
219
|
+
|
|
220
|
+
# Default case - use the type name
|
|
221
|
+
return getattr(field_type, "__name__", str(field_type))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def generate_typed_classes_module(
|
|
225
|
+
config_classes: List[Type], module_name: str = "typed_configs"
|
|
226
|
+
) -> str:
|
|
227
|
+
"""
|
|
228
|
+
Generate a complete Python module with typed classes for multiple ConfigMeta classes.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
config_classes: List of ConfigMeta classes
|
|
232
|
+
module_name: Name for the generated module
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Complete Python module code
|
|
236
|
+
"""
|
|
237
|
+
imports = [
|
|
238
|
+
"from typing import Optional, List, Dict, Any",
|
|
239
|
+
"from .unified_config import "
|
|
240
|
+
+ ", ".join(cls.__name__ for cls in config_classes),
|
|
241
|
+
TYPED_DICT_IMPORT,
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
class_codes = []
|
|
245
|
+
for config_class in config_classes:
|
|
246
|
+
class_codes.append(generate_typed_class_code(config_class))
|
|
247
|
+
|
|
248
|
+
# Use string concatenation instead of f-string with backslashes
|
|
249
|
+
newline = "\n"
|
|
250
|
+
module_code = (
|
|
251
|
+
'"""'
|
|
252
|
+
+ newline
|
|
253
|
+
+ "Auto-generated typed classes for ConfigMeta classes."
|
|
254
|
+
+ newline
|
|
255
|
+
+ newline
|
|
256
|
+
+ "This module provides IDE-friendly typed interfaces for all configuration classes."
|
|
257
|
+
+ newline
|
|
258
|
+
+ "The reason we auto-generate this file is because we want to provide a bridge between what is the ConfigMeta classes and the typed programmatic interface."
|
|
259
|
+
+ newline
|
|
260
|
+
+ "The CoreConfig class is setup in a way that if any additionally params are missed out from being auto-generated "
|
|
261
|
+
+ "then it will not affect the core functionality of the programmatic API."
|
|
262
|
+
+ newline
|
|
263
|
+
+ "The new parameters will just not show up in IDE autocompletions."
|
|
264
|
+
+ newline
|
|
265
|
+
+ "It is fine if this file is not regularly updated by running the script in the .pre-commit-config.app-changes.yaml"
|
|
266
|
+
+ newline
|
|
267
|
+
+ "but it is recommended that this file not be deleted or manually edited."
|
|
268
|
+
+ newline
|
|
269
|
+
+ newline
|
|
270
|
+
+ '"""'
|
|
271
|
+
+ newline
|
|
272
|
+
+ newline
|
|
273
|
+
+ newline.join(imports)
|
|
274
|
+
+ newline
|
|
275
|
+
+ newline
|
|
276
|
+
+ (newline + newline).join(class_codes)
|
|
277
|
+
+ newline
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return module_code
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def create_typed_init_class_dynamic(config_class: Type) -> Type:
|
|
284
|
+
"""
|
|
285
|
+
Dynamically create a typed init class with proper IDE support.
|
|
286
|
+
|
|
287
|
+
This creates the class at runtime but with proper type annotations
|
|
288
|
+
that IDEs can understand.
|
|
289
|
+
"""
|
|
290
|
+
if not hasattr(config_class, "_fields"):
|
|
291
|
+
raise ValueError(f"Class {config_class.__name__} is not a ConfigMeta class")
|
|
292
|
+
|
|
293
|
+
class_name = f"Typed{config_class.__name__}"
|
|
294
|
+
|
|
295
|
+
# Create the init method with proper signature
|
|
296
|
+
def create_init_method():
|
|
297
|
+
# Build the signature dynamically
|
|
298
|
+
sig_params = []
|
|
299
|
+
annotations: Dict[str, Any] = {"return": type(None)}
|
|
300
|
+
|
|
301
|
+
for field_name, field_info in config_class._fields.items():
|
|
302
|
+
field_type = field_info.field_type
|
|
303
|
+
|
|
304
|
+
# Handle nested ConfigMeta classes
|
|
305
|
+
if ConfigMeta.is_instance(field_type):
|
|
306
|
+
field_type = Dict[str, Any] # Use Dict for nested configs
|
|
307
|
+
|
|
308
|
+
# Handle Optional fields
|
|
309
|
+
if not field_info.required:
|
|
310
|
+
field_type = Optional[field_type]
|
|
311
|
+
|
|
312
|
+
annotations[field_name] = field_type
|
|
313
|
+
|
|
314
|
+
def __init__(self, **kwargs):
|
|
315
|
+
# Validate kwargs
|
|
316
|
+
required_fields = {
|
|
317
|
+
name for name, info in config_class._fields.items() if info.required
|
|
318
|
+
}
|
|
319
|
+
provided_fields = set(kwargs.keys())
|
|
320
|
+
valid_fields = set(config_class._fields.keys())
|
|
321
|
+
|
|
322
|
+
# Check required fields
|
|
323
|
+
missing_fields = required_fields - provided_fields
|
|
324
|
+
if missing_fields:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"Missing required fields: {', '.join(missing_fields)}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Check for unknown fields - but allow them for flexibility
|
|
330
|
+
unknown_fields = provided_fields - valid_fields
|
|
331
|
+
if unknown_fields:
|
|
332
|
+
print(
|
|
333
|
+
f"Warning: Unknown fields will be passed through: {', '.join(unknown_fields)}"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
self._kwargs = kwargs
|
|
337
|
+
self._config_class = config_class
|
|
338
|
+
|
|
339
|
+
# Set annotations
|
|
340
|
+
__init__.__annotations__ = annotations
|
|
341
|
+
return __init__
|
|
342
|
+
|
|
343
|
+
def create_config(self):
|
|
344
|
+
"""Create and return the ConfigMeta class instance."""
|
|
345
|
+
return config_class.from_dict(self._kwargs)
|
|
346
|
+
|
|
347
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
348
|
+
"""Return the raw kwargs as a dictionary."""
|
|
349
|
+
return self._kwargs.copy()
|
|
350
|
+
|
|
351
|
+
def __repr__(self) -> str:
|
|
352
|
+
return f"{class_name}({self._kwargs})"
|
|
353
|
+
|
|
354
|
+
# Create the class
|
|
355
|
+
init_method = create_init_method()
|
|
356
|
+
|
|
357
|
+
TypedClass = type(
|
|
358
|
+
class_name,
|
|
359
|
+
(object,),
|
|
360
|
+
{
|
|
361
|
+
"__init__": init_method,
|
|
362
|
+
"create_config": create_config,
|
|
363
|
+
"to_dict": to_dict,
|
|
364
|
+
"__repr__": __repr__,
|
|
365
|
+
"__module__": __name__,
|
|
366
|
+
"__qualname__": class_name,
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return TypedClass
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
# Auto-generate and write typed classes to a file
|
|
374
|
+
def generate_typed_classes_file(output_file: Optional[str] = None):
|
|
375
|
+
"""
|
|
376
|
+
Generate typed classes and write them to a file for IDE support.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
output_file: Path to write the generated classes. If None, prints to stdout.
|
|
380
|
+
"""
|
|
381
|
+
from .unified_config import CoreConfig
|
|
382
|
+
|
|
383
|
+
config_classes = [CoreConfig]
|
|
384
|
+
|
|
385
|
+
module_code = generate_typed_classes_module(config_classes)
|
|
386
|
+
|
|
387
|
+
if output_file:
|
|
388
|
+
with open(output_file, "w") as f:
|
|
389
|
+
f.write(module_code)
|
|
390
|
+
print(f"Generated typed classes written to {output_file}")
|
|
391
|
+
else:
|
|
392
|
+
print(module_code)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# Example usage and testing
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
# Generate typed classes file
|
|
398
|
+
generate_typed_classes_file(os.path.join(current_dir, "typed_configs.py"))
|