ob-metaflow-extensions 1.1.151__py2.py3-none-any.whl → 1.6.2__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.
Files changed (92) hide show
  1. metaflow_extensions/outerbounds/__init__.py +1 -1
  2. metaflow_extensions/outerbounds/plugins/__init__.py +24 -3
  3. metaflow_extensions/outerbounds/plugins/apps/app_cli.py +0 -0
  4. metaflow_extensions/outerbounds/plugins/apps/core/__init__.py +16 -0
  5. metaflow_extensions/outerbounds/plugins/apps/core/_state_machine.py +506 -0
  6. metaflow_extensions/outerbounds/plugins/apps/core/_vendor/__init__.py +0 -0
  7. metaflow_extensions/outerbounds/plugins/apps/core/_vendor/spinner/__init__.py +4 -0
  8. metaflow_extensions/outerbounds/plugins/apps/core/_vendor/spinner/spinners.py +478 -0
  9. metaflow_extensions/outerbounds/plugins/apps/core/app_config.py +128 -0
  10. metaflow_extensions/outerbounds/plugins/apps/core/app_deploy_decorator.py +333 -0
  11. metaflow_extensions/outerbounds/plugins/apps/core/artifacts.py +0 -0
  12. metaflow_extensions/outerbounds/plugins/apps/core/capsule.py +1029 -0
  13. metaflow_extensions/outerbounds/plugins/apps/core/click_importer.py +24 -0
  14. metaflow_extensions/outerbounds/plugins/apps/core/code_package/__init__.py +3 -0
  15. metaflow_extensions/outerbounds/plugins/apps/core/code_package/code_packager.py +618 -0
  16. metaflow_extensions/outerbounds/plugins/apps/core/code_package/examples.py +125 -0
  17. metaflow_extensions/outerbounds/plugins/apps/core/config/__init__.py +15 -0
  18. metaflow_extensions/outerbounds/plugins/apps/core/config/cli_generator.py +165 -0
  19. metaflow_extensions/outerbounds/plugins/apps/core/config/config_utils.py +966 -0
  20. metaflow_extensions/outerbounds/plugins/apps/core/config/schema_export.py +299 -0
  21. metaflow_extensions/outerbounds/plugins/apps/core/config/typed_configs.py +233 -0
  22. metaflow_extensions/outerbounds/plugins/apps/core/config/typed_init_generator.py +537 -0
  23. metaflow_extensions/outerbounds/plugins/apps/core/config/unified_config.py +1125 -0
  24. metaflow_extensions/outerbounds/plugins/apps/core/config_schema.yaml +337 -0
  25. metaflow_extensions/outerbounds/plugins/apps/core/dependencies.py +115 -0
  26. metaflow_extensions/outerbounds/plugins/apps/core/deployer.py +1300 -0
  27. metaflow_extensions/outerbounds/plugins/apps/core/exceptions.py +341 -0
  28. metaflow_extensions/outerbounds/plugins/apps/core/experimental/__init__.py +89 -0
  29. metaflow_extensions/outerbounds/plugins/apps/core/perimeters.py +123 -0
  30. metaflow_extensions/outerbounds/plugins/apps/core/secrets.py +164 -0
  31. metaflow_extensions/outerbounds/plugins/apps/core/utils.py +233 -0
  32. metaflow_extensions/outerbounds/plugins/apps/core/validations.py +17 -0
  33. metaflow_extensions/outerbounds/plugins/aws/__init__.py +4 -0
  34. metaflow_extensions/outerbounds/plugins/aws/assume_role.py +3 -0
  35. metaflow_extensions/outerbounds/plugins/aws/assume_role_decorator.py +118 -0
  36. metaflow_extensions/outerbounds/plugins/checkpoint_datastores/coreweave.py +9 -77
  37. metaflow_extensions/outerbounds/plugins/checkpoint_datastores/external_chckpt.py +85 -0
  38. metaflow_extensions/outerbounds/plugins/checkpoint_datastores/nebius.py +7 -78
  39. metaflow_extensions/outerbounds/plugins/fast_bakery/baker.py +119 -0
  40. metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +17 -3
  41. metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +1 -0
  42. metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py +18 -44
  43. metaflow_extensions/outerbounds/plugins/kubernetes/pod_killer.py +374 -0
  44. metaflow_extensions/outerbounds/plugins/nim/card.py +1 -6
  45. metaflow_extensions/outerbounds/plugins/nim/{__init__.py → nim_decorator.py} +13 -49
  46. metaflow_extensions/outerbounds/plugins/nim/nim_manager.py +294 -233
  47. metaflow_extensions/outerbounds/plugins/nim/utils.py +36 -0
  48. metaflow_extensions/outerbounds/plugins/nvcf/constants.py +2 -2
  49. metaflow_extensions/outerbounds/plugins/nvct/nvct_decorator.py +32 -8
  50. metaflow_extensions/outerbounds/plugins/nvct/nvct_runner.py +1 -1
  51. metaflow_extensions/outerbounds/plugins/ollama/__init__.py +171 -16
  52. metaflow_extensions/outerbounds/plugins/ollama/constants.py +1 -0
  53. metaflow_extensions/outerbounds/plugins/ollama/exceptions.py +22 -0
  54. metaflow_extensions/outerbounds/plugins/ollama/ollama.py +1710 -114
  55. metaflow_extensions/outerbounds/plugins/ollama/status_card.py +292 -0
  56. metaflow_extensions/outerbounds/plugins/optuna/__init__.py +49 -0
  57. metaflow_extensions/outerbounds/plugins/profilers/simple_card_decorator.py +96 -0
  58. metaflow_extensions/outerbounds/plugins/s3_proxy/__init__.py +7 -0
  59. metaflow_extensions/outerbounds/plugins/s3_proxy/binary_caller.py +132 -0
  60. metaflow_extensions/outerbounds/plugins/s3_proxy/constants.py +11 -0
  61. metaflow_extensions/outerbounds/plugins/s3_proxy/exceptions.py +13 -0
  62. metaflow_extensions/outerbounds/plugins/s3_proxy/proxy_bootstrap.py +59 -0
  63. metaflow_extensions/outerbounds/plugins/s3_proxy/s3_proxy_api.py +93 -0
  64. metaflow_extensions/outerbounds/plugins/s3_proxy/s3_proxy_decorator.py +250 -0
  65. metaflow_extensions/outerbounds/plugins/s3_proxy/s3_proxy_manager.py +225 -0
  66. metaflow_extensions/outerbounds/plugins/snowflake/snowflake.py +37 -7
  67. metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +18 -8
  68. metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +6 -0
  69. metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +45 -18
  70. metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +18 -9
  71. metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +10 -4
  72. metaflow_extensions/outerbounds/plugins/torchtune/__init__.py +163 -0
  73. metaflow_extensions/outerbounds/plugins/vllm/__init__.py +255 -0
  74. metaflow_extensions/outerbounds/plugins/vllm/constants.py +1 -0
  75. metaflow_extensions/outerbounds/plugins/vllm/exceptions.py +1 -0
  76. metaflow_extensions/outerbounds/plugins/vllm/status_card.py +352 -0
  77. metaflow_extensions/outerbounds/plugins/vllm/vllm_manager.py +621 -0
  78. metaflow_extensions/outerbounds/remote_config.py +46 -9
  79. metaflow_extensions/outerbounds/toplevel/apps/__init__.py +9 -0
  80. metaflow_extensions/outerbounds/toplevel/apps/exceptions.py +11 -0
  81. metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +86 -2
  82. metaflow_extensions/outerbounds/toplevel/ob_internal.py +4 -0
  83. metaflow_extensions/outerbounds/toplevel/plugins/optuna/__init__.py +1 -0
  84. metaflow_extensions/outerbounds/toplevel/plugins/torchtune/__init__.py +1 -0
  85. metaflow_extensions/outerbounds/toplevel/plugins/vllm/__init__.py +1 -0
  86. metaflow_extensions/outerbounds/toplevel/s3_proxy.py +88 -0
  87. {ob_metaflow_extensions-1.1.151.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/METADATA +2 -2
  88. ob_metaflow_extensions-1.6.2.dist-info/RECORD +136 -0
  89. metaflow_extensions/outerbounds/plugins/nim/utilities.py +0 -5
  90. ob_metaflow_extensions-1.1.151.dist-info/RECORD +0 -74
  91. {ob_metaflow_extensions-1.1.151.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/WHEEL +0 -0
  92. {ob_metaflow_extensions-1.1.151.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,537 @@
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
+ Note: This collects ALL nested configs regardless of ConfigContext.
47
+ TypedDict definitions are always generated for type completeness.
48
+ The filtering by context only happens for TypedCoreConfig.__init__ parameters.
49
+
50
+ Args:
51
+ config_class: A class that inherits from ConfigMeta
52
+ visited: Set of already visited class names to avoid infinite recursion
53
+
54
+ Returns:
55
+ Dictionary mapping class names to ConfigMeta classes
56
+ """
57
+ if visited is None:
58
+ visited = set()
59
+
60
+ nested_configs = {}
61
+
62
+ # Avoid infinite recursion by tracking visited classes
63
+ if config_class.__name__ in visited:
64
+ return nested_configs
65
+
66
+ visited.add(config_class.__name__)
67
+
68
+ # First pass: collect immediate nested configs (all of them for TypedDict generation)
69
+ for field_name, field_info in config_class._fields.items():
70
+ if ConfigMeta.is_instance(field_info.field_type):
71
+ nested_class = field_info.field_type
72
+ nested_configs[nested_class.__name__] = nested_class
73
+
74
+ # Recursively collect nested configs from this nested class
75
+ deeper_nested = collect_nested_configs_recursive(
76
+ nested_class, visited.copy()
77
+ )
78
+ nested_configs.update(deeper_nested)
79
+
80
+ return nested_configs
81
+
82
+
83
+ def _get_field_help(field_info) -> str:
84
+ """
85
+ Get help text from a ConfigField, checking both direct help and cli_meta.help.
86
+
87
+ Args:
88
+ field_info: A ConfigField instance
89
+
90
+ Returns:
91
+ Help text string or empty string if none available
92
+ """
93
+ # First check direct help attribute
94
+ if field_info.help:
95
+ return field_info.help
96
+ # Fall back to cli_meta.help if available
97
+ if (
98
+ hasattr(field_info, "cli_meta")
99
+ and field_info.cli_meta
100
+ and hasattr(field_info.cli_meta, "help")
101
+ ):
102
+ return field_info.cli_meta.help or ""
103
+ return ""
104
+
105
+
106
+ def _generate_nested_type_docs(nested_class: Type, indent: str = " ") -> List[str]:
107
+ """
108
+ Generate documentation for nested ConfigMeta class fields.
109
+
110
+ Note: Documents ALL fields in nested classes for completeness.
111
+
112
+ Args:
113
+ nested_class: A nested ConfigMeta class
114
+ indent: The indentation string to use
115
+
116
+ Returns:
117
+ List of documentation lines for the nested fields
118
+ """
119
+ lines = []
120
+ for sub_field_name, sub_field_info in nested_class._fields.items():
121
+ sub_help = _get_field_help(sub_field_info)
122
+ sub_type = sub_field_info.field_type
123
+
124
+ if ConfigMeta.is_instance(sub_type):
125
+ sub_type_str = f"{sub_type.__name__}Dict"
126
+ else:
127
+ sub_type_str = _get_type_string(sub_type) if sub_type else "Any"
128
+
129
+ # Field name and type on one line
130
+ lines.append(f"{indent}- {sub_field_name} ({sub_type_str})")
131
+ # Help text on next line with extra indentation
132
+ if sub_help:
133
+ lines.append(f"{indent} {sub_help}")
134
+
135
+ # Recursively document deeply nested types
136
+ if ConfigMeta.is_instance(sub_type):
137
+ deeper_lines = _generate_nested_type_docs(sub_type, indent + " ")
138
+ lines.extend(deeper_lines)
139
+
140
+ return lines
141
+
142
+
143
+ def _generate_class_docstring(config_class: Type) -> str:
144
+ """
145
+ Generate a class-level docstring with parameter descriptions in NumPy/Sphinx style.
146
+
147
+ Args:
148
+ config_class: A class that inherits from ConfigMeta
149
+
150
+ Returns:
151
+ Formatted docstring string for class level
152
+ """
153
+ lines = ['"""', "Parameters", "----------"]
154
+
155
+ first_param = True
156
+ for field_name, field_info in config_class._fields.items():
157
+ # Skip fields not available in programmatic context
158
+ if not field_info.is_available_in_programmatic():
159
+ continue
160
+
161
+ help_text = _get_field_help(field_info)
162
+ field_type = field_info.field_type
163
+ is_experimental = getattr(field_info, "is_experimental", False)
164
+
165
+ # Add blank line between parameters (except before the first one)
166
+ if not first_param:
167
+ lines.append("")
168
+ first_param = False
169
+
170
+ # Get type string for documentation
171
+ if ConfigMeta.is_instance(field_type):
172
+ type_str = f"{field_type.__name__}Dict"
173
+ else:
174
+ type_str = _get_type_string(field_type) if field_type else "Any"
175
+
176
+ # Build parameter doc line in NumPy style
177
+ lines.append(f"{field_name} : {type_str}, optional")
178
+
179
+ if help_text:
180
+ lines.append(f" {help_text}")
181
+
182
+ # Add experimental notice as suffix on next line if applicable
183
+ if is_experimental:
184
+ lines.append(" [Experimental] May change in the future.")
185
+
186
+ # For nested ConfigMeta types, expand their fields
187
+ if ConfigMeta.is_instance(field_type):
188
+ nested_docs = _generate_nested_type_docs(field_type, indent=" ")
189
+ if nested_docs:
190
+ lines.extend(nested_docs)
191
+
192
+ lines.append('"""')
193
+ return "\n".join(lines)
194
+
195
+
196
+ def generate_typed_class_code(config_class: Type) -> str:
197
+ """
198
+ Generate the actual Python code for a typed class that IDEs can understand.
199
+
200
+ Args:
201
+ config_class: A class that inherits from ConfigMeta
202
+
203
+ Returns:
204
+ Python code string for the typed class
205
+ """
206
+ if not hasattr(config_class, "_fields"):
207
+ raise ValueError(f"Class {config_class.__name__} is not a ConfigMeta class")
208
+
209
+ class_name = f"Typed{config_class.__name__}"
210
+
211
+ # Generate TypedDict for nested configs - now recursive
212
+ nested_typeddict_code = []
213
+
214
+ # Recursively collect all nested configs
215
+ nested_configs = collect_nested_configs_recursive(config_class)
216
+
217
+ # Generate TypedDict classes for all nested configs
218
+ # Note: TypedDicts include ALL fields for type completeness (no context filtering)
219
+ for nested_name, nested_class in nested_configs.items():
220
+ dict_name = f"{nested_name}Dict"
221
+ fields = []
222
+
223
+ for field_name, field_info in nested_class._fields.items():
224
+ field_type = _get_type_string(field_info.field_type, quote_config_meta=True)
225
+ if not field_info.required:
226
+ field_type = f"Optional[{field_type}]"
227
+ fields.append(f" {field_name}: {field_type}")
228
+
229
+ typeddict_code = f"""class {dict_name}(TypedDict, total=False):
230
+ {chr(10).join(fields)}"""
231
+ nested_typeddict_code.append(typeddict_code)
232
+
233
+ # Generate __init__ method signature
234
+ required_params = []
235
+ optional_params = []
236
+ all_assignments = []
237
+
238
+ for field_name, field_info in config_class._fields.items():
239
+ # Skip fields not available in programmatic context
240
+ if not field_info.is_available_in_programmatic():
241
+ continue
242
+
243
+ field_type = field_info.field_type
244
+
245
+ # Handle nested ConfigMeta classes
246
+ if ConfigMeta.is_instance(field_type):
247
+ type_hint = f"Optional[{field_type.__name__}Dict]"
248
+ param_line = f" {field_name}: {type_hint} = None"
249
+ optional_params.append(param_line)
250
+ else:
251
+ # All params will be set as options here even if the are required in the
252
+ # configMeta
253
+ type_hint = _get_type_string(field_type)
254
+ param_line = f" {field_name}: Optional[{type_hint}] = None"
255
+ optional_params.append(param_line)
256
+
257
+ all_assignments.append(f' "{field_name}": {field_name}')
258
+
259
+ # Combine required params first, then optional params
260
+ all_params = required_params + optional_params
261
+
262
+ # Generate the class code
263
+ newline = "\n"
264
+ comma_newline = ",\n"
265
+
266
+ # Add **kwargs to the parameter list
267
+ if all_params:
268
+ params_with_kwargs = all_params + [" **kwargs"]
269
+ else:
270
+ params_with_kwargs = [" **kwargs"]
271
+
272
+ # Generate class-level docstring with parameter help
273
+ class_docstring = _generate_class_docstring(config_class)
274
+ # Indent the docstring for class level (4 spaces)
275
+ indented_class_docstring = "\n".join(
276
+ " " + line if line else "" for line in class_docstring.split("\n")
277
+ )
278
+
279
+ class_code = f"""class {class_name}:
280
+ {indented_class_docstring}
281
+
282
+ def __init__(
283
+ self,
284
+ {comma_newline.join(params_with_kwargs)}
285
+ ) -> None:
286
+ self._kwargs = {{
287
+ {comma_newline.join(all_assignments)}
288
+ }}
289
+ # Add any additional kwargs
290
+ self._kwargs.update(kwargs)
291
+ # Remove None values
292
+ self._kwargs = {{k: v for k, v in self._kwargs.items() if v is not None}}
293
+ self._config_class = {config_class.__name__}
294
+ self._config = self.create_config()
295
+ self._init()
296
+
297
+ def create_config(self) -> {config_class.__name__}:
298
+ return {config_class.__name__}.from_dict(self._kwargs)
299
+
300
+ def to_dict(self) -> Dict[str, Any]:
301
+ return self._config.to_dict()
302
+
303
+ def _init(self):
304
+ raise NotImplementedError"""
305
+
306
+ # Combine all code
307
+ full_code = []
308
+ if nested_typeddict_code:
309
+ full_code.extend(nested_typeddict_code)
310
+ full_code.append("") # Empty line
311
+ full_code.append(class_code)
312
+
313
+ return (newline + newline).join(full_code)
314
+
315
+
316
+ def _get_type_string(field_type: Type, quote_config_meta: bool = False) -> str:
317
+ """Convert a type to its string representation for code generation.
318
+
319
+ Args:
320
+ field_type: The type to convert
321
+ quote_config_meta: Whether to quote ConfigMeta type references for forward declarations
322
+ """
323
+ if field_type == str:
324
+ return "str"
325
+ elif field_type == int:
326
+ return "int"
327
+ elif field_type == float:
328
+ return "float"
329
+ elif field_type == bool:
330
+ return "bool"
331
+ elif ConfigMeta.is_instance(field_type):
332
+ # Handle ConfigMeta classes by referencing their Dict type
333
+ dict_type = f"{field_type.__name__}Dict"
334
+ return f'"{dict_type}"' if quote_config_meta else dict_type
335
+ elif hasattr(field_type, "__origin__"):
336
+ # Handle generic types like List[str], Dict[str, str], etc.
337
+ origin = field_type.__origin__
338
+ args = getattr(field_type, "__args__", ())
339
+
340
+ if origin == list:
341
+ if args:
342
+ return f"List[{_get_type_string(args[0], quote_config_meta)}]"
343
+ return "List[Any]"
344
+ elif origin == dict:
345
+ if len(args) == 2:
346
+ return f"Dict[{_get_type_string(args[0], quote_config_meta)}, {_get_type_string(args[1], quote_config_meta)}]"
347
+ return "Dict[str, Any]"
348
+ elif origin == Union:
349
+ # Handle Optional types
350
+ if len(args) == 2 and type(None) in args:
351
+ non_none_type = args[0] if args[1] is type(None) else args[1]
352
+ return f"Optional[{_get_type_string(non_none_type, quote_config_meta)}]"
353
+ return f"Union[{', '.join(_get_type_string(arg, quote_config_meta) for arg in args)}]"
354
+
355
+ # Default case - use the type name
356
+ return getattr(field_type, "__name__", str(field_type))
357
+
358
+
359
+ def generate_typed_classes_module(
360
+ config_classes: List[Type], module_name: str = "typed_configs"
361
+ ) -> str:
362
+ """
363
+ Generate a complete Python module with typed classes for multiple ConfigMeta classes.
364
+
365
+ Args:
366
+ config_classes: List of ConfigMeta classes
367
+ module_name: Name for the generated module
368
+
369
+ Returns:
370
+ Complete Python module code
371
+ """
372
+ imports = [
373
+ "from typing import Optional, List, Dict, Any",
374
+ "from .unified_config import "
375
+ + ", ".join(cls.__name__ for cls in config_classes),
376
+ TYPED_DICT_IMPORT,
377
+ ]
378
+
379
+ class_codes = []
380
+ for config_class in config_classes:
381
+ class_codes.append(generate_typed_class_code(config_class))
382
+
383
+ # Use string concatenation instead of f-string with backslashes
384
+ newline = "\n"
385
+ module_code = (
386
+ '"""'
387
+ + newline
388
+ + "Auto-generated typed classes for ConfigMeta classes."
389
+ + newline
390
+ + newline
391
+ + "This module provides IDE-friendly typed interfaces for all configuration classes."
392
+ + newline
393
+ + "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."
394
+ + newline
395
+ + "The CoreConfig class is setup in a way that if any additionally params are missed out from being auto-generated "
396
+ + "then it will not affect the core functionality of the programmatic API."
397
+ + newline
398
+ + "The new parameters will just not show up in IDE autocompletions."
399
+ + newline
400
+ + "It is fine if this file is not regularly updated by running the script in the .pre-commit-config.app-changes.yaml"
401
+ + newline
402
+ + "but it is recommended that this file not be deleted or manually edited."
403
+ + newline
404
+ + newline
405
+ + '"""'
406
+ + newline
407
+ + newline
408
+ + newline.join(imports)
409
+ + newline
410
+ + newline
411
+ + (newline + newline).join(class_codes)
412
+ + newline
413
+ )
414
+
415
+ return module_code
416
+
417
+
418
+ def create_typed_init_class_dynamic(config_class: Type) -> Type:
419
+ """
420
+ Dynamically create a typed init class with proper IDE support.
421
+
422
+ This creates the class at runtime but with proper type annotations
423
+ that IDEs can understand.
424
+ """
425
+ if not hasattr(config_class, "_fields"):
426
+ raise ValueError(f"Class {config_class.__name__} is not a ConfigMeta class")
427
+
428
+ class_name = f"Typed{config_class.__name__}"
429
+
430
+ # Create the init method with proper signature
431
+ def create_init_method():
432
+ # Build the signature dynamically
433
+ sig_params = []
434
+ annotations: Dict[str, Any] = {"return": type(None)}
435
+
436
+ for field_name, field_info in config_class._fields.items():
437
+ # Skip fields not available in programmatic context
438
+ if not field_info.is_available_in_programmatic():
439
+ continue
440
+
441
+ field_type = field_info.field_type
442
+
443
+ # Handle nested ConfigMeta classes
444
+ if ConfigMeta.is_instance(field_type):
445
+ field_type = Dict[str, Any] # Use Dict for nested configs
446
+
447
+ # Handle Optional fields
448
+ if not field_info.required:
449
+ field_type = Optional[field_type]
450
+
451
+ annotations[field_name] = field_type
452
+
453
+ def __init__(self, **kwargs):
454
+ # Validate kwargs
455
+ required_fields = {
456
+ name for name, info in config_class._fields.items() if info.required
457
+ }
458
+ provided_fields = set(kwargs.keys())
459
+ valid_fields = set(config_class._fields.keys())
460
+
461
+ # Check required fields
462
+ missing_fields = required_fields - provided_fields
463
+ if missing_fields:
464
+ raise ValueError(
465
+ f"Missing required fields: {', '.join(missing_fields)}"
466
+ )
467
+
468
+ # Check for unknown fields - but allow them for flexibility
469
+ unknown_fields = provided_fields - valid_fields
470
+ if unknown_fields:
471
+ print(
472
+ f"Warning: Unknown fields will be passed through: {', '.join(unknown_fields)}"
473
+ )
474
+
475
+ self._kwargs = kwargs
476
+ self._config_class = config_class
477
+
478
+ # Set annotations
479
+ __init__.__annotations__ = annotations
480
+ return __init__
481
+
482
+ def create_config(self):
483
+ """Create and return the ConfigMeta class instance."""
484
+ return config_class.from_dict(self._kwargs)
485
+
486
+ def to_dict(self) -> Dict[str, Any]:
487
+ """Return the raw kwargs as a dictionary."""
488
+ return self._kwargs.copy()
489
+
490
+ def __repr__(self) -> str:
491
+ return f"{class_name}({self._kwargs})"
492
+
493
+ # Create the class
494
+ init_method = create_init_method()
495
+
496
+ TypedClass = type(
497
+ class_name,
498
+ (object,),
499
+ {
500
+ "__init__": init_method,
501
+ "create_config": create_config,
502
+ "to_dict": to_dict,
503
+ "__repr__": __repr__,
504
+ "__module__": __name__,
505
+ "__qualname__": class_name,
506
+ },
507
+ )
508
+
509
+ return TypedClass
510
+
511
+
512
+ # Auto-generate and write typed classes to a file
513
+ def generate_typed_classes_file(output_file: Optional[str] = None):
514
+ """
515
+ Generate typed classes and write them to a file for IDE support.
516
+
517
+ Args:
518
+ output_file: Path to write the generated classes. If None, prints to stdout.
519
+ """
520
+ from .unified_config import CoreConfig
521
+
522
+ config_classes = [CoreConfig]
523
+
524
+ module_code = generate_typed_classes_module(config_classes)
525
+
526
+ if output_file:
527
+ with open(output_file, "w") as f:
528
+ f.write(module_code)
529
+ print(f"Generated typed classes written to {output_file}")
530
+ else:
531
+ print(module_code)
532
+
533
+
534
+ # Example usage and testing
535
+ if __name__ == "__main__":
536
+ # Generate typed classes file
537
+ generate_typed_classes_file(os.path.join(current_dir, "typed_configs.py"))