ob-metaflow-extensions 1.1.175rc1__py2.py3-none-any.whl → 1.1.175rc3__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.

Files changed (28) hide show
  1. metaflow_extensions/outerbounds/plugins/__init__.py +1 -1
  2. metaflow_extensions/outerbounds/plugins/apps/app_deploy_decorator.py +112 -0
  3. metaflow_extensions/outerbounds/plugins/apps/core/__init__.py +9 -0
  4. metaflow_extensions/outerbounds/plugins/apps/core/app_cli.py +42 -374
  5. metaflow_extensions/outerbounds/plugins/apps/core/app_config.py +45 -225
  6. metaflow_extensions/outerbounds/plugins/apps/core/capsule.py +22 -6
  7. metaflow_extensions/outerbounds/plugins/apps/core/code_package/code_packager.py +16 -15
  8. metaflow_extensions/outerbounds/plugins/apps/core/config/__init__.py +12 -0
  9. metaflow_extensions/outerbounds/plugins/apps/core/config/cli_generator.py +161 -0
  10. metaflow_extensions/outerbounds/plugins/apps/core/config/config_utils.py +828 -0
  11. metaflow_extensions/outerbounds/plugins/apps/core/config/schema_export.py +285 -0
  12. metaflow_extensions/outerbounds/plugins/apps/core/config/typed_configs.py +104 -0
  13. metaflow_extensions/outerbounds/plugins/apps/core/config/typed_init_generator.py +317 -0
  14. metaflow_extensions/outerbounds/plugins/apps/core/config/unified_config.py +994 -0
  15. metaflow_extensions/outerbounds/plugins/apps/core/config_schema.yaml +217 -211
  16. metaflow_extensions/outerbounds/plugins/apps/core/dependencies.py +3 -3
  17. metaflow_extensions/outerbounds/plugins/apps/core/deployer.py +132 -0
  18. metaflow_extensions/outerbounds/plugins/apps/core/experimental/__init__.py +4 -36
  19. metaflow_extensions/outerbounds/plugins/apps/core/perimeters.py +44 -2
  20. metaflow_extensions/outerbounds/plugins/apps/core/validations.py +4 -9
  21. metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +1 -0
  22. metaflow_extensions/outerbounds/toplevel/ob_internal.py +1 -0
  23. {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc3.dist-info}/METADATA +1 -1
  24. {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc3.dist-info}/RECORD +26 -20
  25. metaflow_extensions/outerbounds/plugins/apps/core/cli_to_config.py +0 -99
  26. metaflow_extensions/outerbounds/plugins/apps/core/config_schema_autogen.json +0 -336
  27. {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc3.dist-info}/WHEEL +0 -0
  28. {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,285 @@
1
+ """
2
+ Schema Export Module for Unified Configuration System
3
+
4
+ This module provides standalone functions to export configuration schemas in various formats:
5
+ - OpenAPI schemas in YAML or JSON format
6
+ - JSON schemas in YAML or JSON format
7
+
8
+ Usage:
9
+ from schema_export import export_schema, to_openapi_schema, to_json_schema, to_dict
10
+
11
+ # Export schema to file
12
+ export_schema(CoreConfig, "schema.yaml")
13
+ export_schema(CoreConfig, "schema.json", schema_type="json", format="json")
14
+
15
+ # Generate schema in memory
16
+ openapi_schema = to_openapi_schema(CoreConfig)
17
+ json_schema = to_json_schema(CoreConfig)
18
+
19
+ # Export config instance to dict
20
+ config_instance = CoreConfig(name="myapp", port=8000)
21
+ config_dict = to_dict(config_instance)
22
+
23
+ No external dependencies required for basic functionality.
24
+ PyYAML is optional for YAML export support.
25
+ """
26
+
27
+ import json
28
+ import textwrap
29
+ from collections import OrderedDict
30
+ from typing import Any, Dict
31
+
32
+ try:
33
+ import yaml
34
+
35
+ HAS_YAML = True
36
+ except ImportError:
37
+ HAS_YAML = False
38
+
39
+
40
+ def to_openapi_schema(config_class) -> Dict[str, Any]:
41
+ """Generate OpenAPI schema for a configuration class.
42
+
43
+ Args:
44
+ config_class: The configuration class to generate schema for
45
+
46
+ Returns:
47
+ OpenAPI schema dictionary
48
+ """
49
+ return _generate_openapi_schema(config_class)
50
+
51
+
52
+ def to_json_schema(config_class) -> Dict[str, Any]:
53
+ """Generate JSON schema for a configuration class.
54
+
55
+ Args:
56
+ config_class: The configuration class to generate schema for
57
+
58
+ Returns:
59
+ JSON schema dictionary
60
+ """
61
+ return _generate_json_schema(config_class)
62
+
63
+
64
+ def export_schema(
65
+ config_class, filepath: str, schema_type: str = "openapi", format: str = "yaml"
66
+ ) -> None:
67
+ """Export configuration schema to file.
68
+
69
+ Args:
70
+ config_class: The configuration class to export schema for
71
+ filepath: Path to save the schema file
72
+ schema_type: Type of schema to generate ('openapi' or 'json')
73
+ format: Output format ('yaml' or 'json')
74
+
75
+ Examples:
76
+ # Export OpenAPI schema as YAML (default)
77
+ export_schema(CoreConfig, "schema.yaml")
78
+
79
+ # Export JSON schema as YAML
80
+ export_schema(CoreConfig, "schema.yaml", schema_type="json")
81
+
82
+ # Export OpenAPI schema as JSON
83
+ export_schema(CoreConfig, "schema.json", schema_type="openapi", format="json")
84
+
85
+ # Export JSON schema as JSON
86
+ export_schema(CoreConfig, "schema.json", schema_type="json", format="json")
87
+ """
88
+ # Validate inputs
89
+ if schema_type.lower() not in ["openapi", "json"]:
90
+ raise ValueError(
91
+ f"Unsupported schema type: {schema_type}. Use 'openapi' or 'json'."
92
+ )
93
+
94
+ if format.lower() not in ["yaml", "json"]:
95
+ raise ValueError(f"Unsupported format: {format}. Use 'yaml' or 'json'.")
96
+
97
+ # Generate the appropriate schema
98
+ if schema_type.lower() == "openapi":
99
+ base_schema = _generate_openapi_schema(config_class)
100
+ # Wrap in OpenAPI document structure with proper ordering
101
+ schema_data = OrderedDict(
102
+ [
103
+ ("openapi", "3.0.0"),
104
+ (
105
+ "info",
106
+ OrderedDict(
107
+ [
108
+ ("title", f"{config_class.__name__} Configuration Schema"),
109
+ ("version", "1.0.0"),
110
+ ]
111
+ ),
112
+ ),
113
+ (
114
+ "components",
115
+ OrderedDict(
116
+ [
117
+ (
118
+ "schemas",
119
+ OrderedDict([(config_class.__name__, base_schema)]),
120
+ )
121
+ ]
122
+ ),
123
+ ),
124
+ ]
125
+ )
126
+ else: # json schema
127
+ schema_data = _generate_json_schema(config_class)
128
+
129
+ # Export in the requested format
130
+ if format.lower() == "yaml":
131
+ if not HAS_YAML:
132
+ raise ImportError(
133
+ "PyYAML is required for YAML export. Install with: pip install pyyaml"
134
+ )
135
+
136
+ # Custom YAML representer for multiline strings
137
+ def multiline_representer(dumper, data):
138
+ if "\n" in data or len(data) > 80:
139
+ style = "|" # use literal block
140
+ else:
141
+ style = None # normal flow
142
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data, style=style)
143
+
144
+ # Custom YAML representer for OrderedDict to preserve order
145
+ def ordered_dict_representer(dumper, data):
146
+ return dumper.represent_dict(data.items())
147
+
148
+ yaml.add_representer(str, multiline_representer)
149
+ yaml.add_representer(OrderedDict, ordered_dict_representer)
150
+
151
+ with open(filepath, "w") as f:
152
+ yaml.dump(
153
+ schema_data,
154
+ f,
155
+ default_flow_style=False,
156
+ sort_keys=False,
157
+ allow_unicode=True,
158
+ width=120,
159
+ indent=2,
160
+ )
161
+ else: # json format
162
+ with open(filepath, "w") as f:
163
+ json.dump(schema_data, f, indent=2)
164
+
165
+
166
+ # Private helper functions
167
+ def _generate_openapi_schema(cls) -> Dict[str, Any]:
168
+ """Generate OpenAPI schema for a configuration class."""
169
+ # Clean up class docstring for better YAML formatting
170
+ description = f"{cls.__name__} configuration"
171
+ get_description = getattr(cls, "SCHEMA_DOC", None)
172
+ if get_description:
173
+ description = get_description
174
+ elif cls.__doc__:
175
+ # Remove common indentation and clean up whitespace
176
+ cleaned_doc = textwrap.dedent(cls.__doc__).strip()
177
+ # Replace multiple spaces with single spaces but preserve line breaks
178
+ lines = [line.strip() for line in cleaned_doc.split("\n") if line.strip()]
179
+ description = "\n".join(lines)
180
+
181
+ # Create ordered schema with specific order: title, description, type, required, then properties
182
+ schema = OrderedDict(
183
+ [
184
+ ("title", cls.__name__),
185
+ ("description", description),
186
+ ("type", "object"),
187
+ ("required", []),
188
+ ("properties", OrderedDict()),
189
+ ]
190
+ )
191
+
192
+ for field_name, field_info in cls._fields.items():
193
+ if field_name.startswith("_"):
194
+ continue
195
+
196
+ field_schema = _get_field_schema(field_info)
197
+ schema["properties"][field_name] = field_schema
198
+
199
+ # Add to required if field is required
200
+ if field_info.required:
201
+ schema["required"].append(field_name)
202
+
203
+ return schema
204
+
205
+
206
+ def _generate_json_schema(cls) -> Dict[str, Any]:
207
+ """Generate JSON schema for a configuration class."""
208
+ openapi_schema = _generate_openapi_schema(cls)
209
+
210
+ # Create ordered JSON schema with $schema first
211
+ schema = OrderedDict(
212
+ [
213
+ ("$schema", "https://json-schema.org/draft/2020-12/schema"),
214
+ ("title", openapi_schema["title"]),
215
+ ("description", openapi_schema["description"]),
216
+ ("type", openapi_schema["type"]),
217
+ ("required", openapi_schema["required"]),
218
+ ("properties", openapi_schema["properties"]),
219
+ ]
220
+ )
221
+
222
+ return schema
223
+
224
+
225
+ def _get_field_schema(field_info) -> Dict[str, Any]:
226
+ """Generate schema for a single field."""
227
+ field_schema = OrderedDict()
228
+
229
+ # Get description from ConfigField.help first, then CLI metadata
230
+ description = field_info.help or (
231
+ field_info.cli_meta.help if field_info.cli_meta else None
232
+ )
233
+ if description:
234
+ field_schema["description"] = description
235
+
236
+ # Handle field type
237
+ if field_info.field_type == str:
238
+ field_schema["type"] = "string"
239
+ elif field_info.field_type == int:
240
+ field_schema["type"] = "integer"
241
+ elif field_info.field_type == float:
242
+ field_schema["type"] = "number"
243
+ elif field_info.field_type == bool:
244
+ field_schema["type"] = "boolean"
245
+ elif field_info.field_type == list:
246
+ field_schema["type"] = "array"
247
+ field_schema["items"] = {"type": "string"} # Default to string items
248
+ elif field_info.field_type == dict:
249
+ field_schema["type"] = "object"
250
+ field_schema["additionalProperties"] = True
251
+ elif hasattr(field_info.field_type, "_fields"):
252
+ # Nested configuration object
253
+ field_schema = _generate_openapi_schema(field_info.field_type)
254
+ else:
255
+ # Fallback to string for unknown types
256
+ field_schema["type"] = "string"
257
+
258
+ # Add default value
259
+ if field_info.default is not None and not callable(field_info.default):
260
+ field_schema["default"] = field_info.default
261
+
262
+ # Handle choices from CLI metadata
263
+ if field_info.cli_meta and field_info.cli_meta.choices:
264
+ if "type" in field_schema:
265
+ field_schema["enum"] = field_info.cli_meta.choices
266
+
267
+ # Add examples from ConfigField
268
+ if field_info.example is not None:
269
+ field_schema["example"] = field_info.example
270
+
271
+ # Add experimental flag
272
+ if field_info.is_experimental:
273
+ field_schema["experimental"] = True
274
+
275
+ # Add Field Behavior
276
+ if field_info.behavior:
277
+ field_schema["mutation_behavior"] = field_info.behavior
278
+
279
+ # Handle validation from CLI metadata
280
+ if field_info.cli_meta and field_info.validation_fn:
281
+ # Add validation hints in description
282
+ if "description" in field_schema:
283
+ field_schema["description"] += " (validation applied)"
284
+
285
+ return field_schema
@@ -0,0 +1,104 @@
1
+ """
2
+ Auto-generated typed classes for ConfigMeta classes.
3
+
4
+ This module provides IDE-friendly typed interfaces for all configuration classes.
5
+ """
6
+
7
+ from typing import Optional, List, Dict, Any, TypedDict
8
+ from .unified_config import CoreConfig
9
+
10
+
11
+ class ResourceConfigDict(TypedDict, total=False):
12
+ cpu: Optional[str]
13
+ memory: Optional[str]
14
+ gpu: Optional[str]
15
+ disk: Optional[str]
16
+
17
+
18
+ class AuthConfigDict(TypedDict, total=False):
19
+ type: Optional[str]
20
+ public: Optional[bool]
21
+
22
+
23
+ class ReplicaConfigDict(TypedDict, total=False):
24
+ fixed: Optional[int]
25
+ min: Optional[int]
26
+ max: Optional[int]
27
+
28
+
29
+ class DependencyConfigDict(TypedDict, total=False):
30
+ from_requirements_file: Optional[str]
31
+ from_pyproject_toml: Optional[str]
32
+ python: Optional[str]
33
+ pypi: Optional[dict]
34
+ conda: Optional[dict]
35
+
36
+
37
+ class PackageConfigDict(TypedDict, total=False):
38
+ src_path: Optional[str]
39
+ suffixes: Optional[list]
40
+
41
+
42
+ class TypedCoreConfig:
43
+ def __init__(
44
+ self,
45
+ name: Optional[str] = None,
46
+ port: Optional[int] = None,
47
+ description: Optional[str] = None,
48
+ app_type: Optional[str] = None,
49
+ image: Optional[str] = None,
50
+ tags: Optional[list] = None,
51
+ secrets: Optional[list] = None,
52
+ compute_pools: Optional[list] = None,
53
+ environment: Optional[dict] = None,
54
+ commands: Optional[list] = None,
55
+ resources: Optional[ResourceConfigDict] = None,
56
+ auth: Optional[AuthConfigDict] = None,
57
+ replicas: Optional[ReplicaConfigDict] = None,
58
+ dependencies: Optional[DependencyConfigDict] = None,
59
+ package: Optional[PackageConfigDict] = None,
60
+ no_deps: Optional[bool] = None,
61
+ force_upgrade: Optional[bool] = None,
62
+ persistence: Optional[str] = None,
63
+ project: Optional[str] = None,
64
+ branch: Optional[str] = None,
65
+ models: Optional[list] = None,
66
+ data: Optional[list] = None,
67
+ **kwargs
68
+ ) -> None:
69
+ self._kwargs = {
70
+ "name": name,
71
+ "port": port,
72
+ "description": description,
73
+ "app_type": app_type,
74
+ "image": image,
75
+ "tags": tags,
76
+ "secrets": secrets,
77
+ "compute_pools": compute_pools,
78
+ "environment": environment,
79
+ "commands": commands,
80
+ "resources": resources,
81
+ "auth": auth,
82
+ "replicas": replicas,
83
+ "dependencies": dependencies,
84
+ "package": package,
85
+ "no_deps": no_deps,
86
+ "force_upgrade": force_upgrade,
87
+ "persistence": persistence,
88
+ "project": project,
89
+ "branch": branch,
90
+ "models": models,
91
+ "data": data,
92
+ }
93
+ # Add any additional kwargs
94
+ self._kwargs.update(kwargs)
95
+ # Remove None values
96
+ self._kwargs = {k: v for k, v in self._kwargs.items() if v is not None}
97
+ self._config_class = CoreConfig
98
+ self._config = self.create_config()
99
+
100
+ def create_config(self) -> CoreConfig:
101
+ return CoreConfig.from_dict(self._kwargs)
102
+
103
+ def to_dict(self) -> Dict[str, Any]:
104
+ return self._config.to_dict()
@@ -0,0 +1,317 @@
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
9
+
10
+ from .config_utils import ConfigMeta
11
+
12
+ import os
13
+
14
+ current_dir = os.path.dirname(__file__)
15
+
16
+
17
+ def generate_typed_class_code(config_class: Type) -> str:
18
+ """
19
+ Generate the actual Python code for a typed class that IDEs can understand.
20
+
21
+ Args:
22
+ config_class: A class that inherits from ConfigMeta
23
+
24
+ Returns:
25
+ Python code string for the typed class
26
+ """
27
+ if not hasattr(config_class, "_fields"):
28
+ raise ValueError(f"Class {config_class.__name__} is not a ConfigMeta class")
29
+
30
+ class_name = f"Typed{config_class.__name__}"
31
+
32
+ # Generate TypedDict for nested configs
33
+ nested_typeddict_code = []
34
+
35
+ # First pass: collect all nested configs
36
+ nested_configs = {}
37
+ for field_name, field_info in config_class._fields.items():
38
+ if ConfigMeta.is_instance(field_info.field_type):
39
+ nested_configs[field_info.field_type.__name__] = field_info.field_type
40
+
41
+ # Generate TypedDict classes for nested configs
42
+ for nested_name, nested_class in nested_configs.items():
43
+ dict_name = f"{nested_name}Dict"
44
+ fields = []
45
+
46
+ for field_name, field_info in nested_class._fields.items():
47
+ field_type = _get_type_string(field_info.field_type)
48
+ if not field_info.required:
49
+ field_type = f"Optional[{field_type}]"
50
+ fields.append(f" {field_name}: {field_type}")
51
+
52
+ typeddict_code = f"""class {dict_name}(TypedDict, total=False):
53
+ {chr(10).join(fields)}"""
54
+ nested_typeddict_code.append(typeddict_code)
55
+
56
+ # Generate __init__ method signature
57
+ required_params = []
58
+ optional_params = []
59
+ all_assignments = []
60
+
61
+ for field_name, field_info in config_class._fields.items():
62
+ field_type = field_info.field_type
63
+
64
+ # Handle nested ConfigMeta classes
65
+ if ConfigMeta.is_instance(field_type):
66
+ type_hint = f"Optional[{field_type.__name__}Dict]"
67
+ param_line = f" {field_name}: {type_hint} = None"
68
+ optional_params.append(param_line)
69
+ else:
70
+ # All params will be set as options here even if the are required in the
71
+ # configMeta
72
+ type_hint = _get_type_string(field_type)
73
+ param_line = f" {field_name}: Optional[{type_hint}] = None"
74
+ optional_params.append(param_line)
75
+
76
+ all_assignments.append(f' "{field_name}": {field_name}')
77
+
78
+ # Combine required params first, then optional params
79
+ all_params = required_params + optional_params
80
+
81
+ # Generate the class code
82
+ newline = "\n"
83
+ comma_newline = ",\n"
84
+
85
+ # Add **kwargs to the parameter list
86
+ if all_params:
87
+ params_with_kwargs = all_params + [" **kwargs"]
88
+ else:
89
+ params_with_kwargs = [" **kwargs"]
90
+
91
+ class_code = f"""class {class_name}:
92
+ def __init__(
93
+ self,
94
+ {comma_newline.join(params_with_kwargs)}
95
+ ) -> None:
96
+ self._kwargs = {{
97
+ {comma_newline.join(all_assignments)}
98
+ }}
99
+ # Add any additional kwargs
100
+ self._kwargs.update(kwargs)
101
+ # Remove None values
102
+ self._kwargs = {{k: v for k, v in self._kwargs.items() if v is not None}}
103
+ self._config_class = {config_class.__name__}
104
+ self._config = self.create_config()
105
+
106
+ def create_config(self) -> {config_class.__name__}:
107
+ return {config_class.__name__}.from_dict(self._kwargs)
108
+
109
+ def to_dict(self) -> Dict[str, Any]:
110
+ return self._config.to_dict()"""
111
+
112
+ # Combine all code
113
+ full_code = []
114
+ if nested_typeddict_code:
115
+ full_code.extend(nested_typeddict_code)
116
+ full_code.append("") # Empty line
117
+ full_code.append(class_code)
118
+
119
+ return (newline + newline).join(full_code)
120
+
121
+
122
+ def _get_type_string(field_type: Type) -> str:
123
+ """Convert a type to its string representation for code generation."""
124
+ if field_type == str:
125
+ return "str"
126
+ elif field_type == int:
127
+ return "int"
128
+ elif field_type == float:
129
+ return "float"
130
+ elif field_type == bool:
131
+ return "bool"
132
+ elif hasattr(field_type, "__origin__"):
133
+ # Handle generic types like List[str], Dict[str, str], etc.
134
+ origin = field_type.__origin__
135
+ args = getattr(field_type, "__args__", ())
136
+
137
+ if origin == list:
138
+ if args:
139
+ return f"List[{_get_type_string(args[0])}]"
140
+ return "List[Any]"
141
+ elif origin == dict:
142
+ if len(args) == 2:
143
+ return f"Dict[{_get_type_string(args[0])}, {_get_type_string(args[1])}]"
144
+ return "Dict[str, Any]"
145
+ elif origin == Union:
146
+ # Handle Optional types
147
+ if len(args) == 2 and type(None) in args:
148
+ non_none_type = args[0] if args[1] is type(None) else args[1]
149
+ return f"Optional[{_get_type_string(non_none_type)}]"
150
+ return f"Union[{', '.join(_get_type_string(arg) for arg in args)}]"
151
+
152
+ # Default case - use the type name
153
+ return getattr(field_type, "__name__", str(field_type))
154
+
155
+
156
+ def generate_typed_classes_module(
157
+ config_classes: List[Type], module_name: str = "typed_configs"
158
+ ) -> str:
159
+ """
160
+ Generate a complete Python module with typed classes for multiple ConfigMeta classes.
161
+
162
+ Args:
163
+ config_classes: List of ConfigMeta classes
164
+ module_name: Name for the generated module
165
+
166
+ Returns:
167
+ Complete Python module code
168
+ """
169
+ imports = [
170
+ "from typing import Optional, List, Dict, Any, TypedDict",
171
+ "from .unified_config import "
172
+ + ", ".join(cls.__name__ for cls in config_classes),
173
+ ]
174
+
175
+ class_codes = []
176
+ for config_class in config_classes:
177
+ class_codes.append(generate_typed_class_code(config_class))
178
+
179
+ # Use string concatenation instead of f-string with backslashes
180
+ newline = "\n"
181
+ module_code = (
182
+ '"""'
183
+ + newline
184
+ + "Auto-generated typed classes for ConfigMeta classes."
185
+ + newline
186
+ + newline
187
+ + "This module provides IDE-friendly typed interfaces for all configuration classes."
188
+ + newline
189
+ + '"""'
190
+ + newline
191
+ + newline
192
+ + newline.join(imports)
193
+ + newline
194
+ + newline
195
+ + (newline + newline).join(class_codes)
196
+ + newline
197
+ )
198
+
199
+ return module_code
200
+
201
+
202
+ def create_typed_init_class_dynamic(config_class: Type) -> Type:
203
+ """
204
+ Dynamically create a typed init class with proper IDE support.
205
+
206
+ This creates the class at runtime but with proper type annotations
207
+ that IDEs can understand.
208
+ """
209
+ if not hasattr(config_class, "_fields"):
210
+ raise ValueError(f"Class {config_class.__name__} is not a ConfigMeta class")
211
+
212
+ class_name = f"Typed{config_class.__name__}"
213
+
214
+ # Create the init method with proper signature
215
+ def create_init_method():
216
+ # Build the signature dynamically
217
+ sig_params = []
218
+ annotations = {"return": None}
219
+
220
+ for field_name, field_info in config_class._fields.items():
221
+ field_type = field_info.field_type
222
+
223
+ # Handle nested ConfigMeta classes
224
+ if ConfigMeta.is_instance(field_type):
225
+ field_type = Dict[str, Any] # Use Dict for nested configs
226
+
227
+ # Handle Optional fields
228
+ if not field_info.required:
229
+ field_type = Optional[field_type]
230
+
231
+ annotations[field_name] = field_type
232
+
233
+ def __init__(self, **kwargs):
234
+ # Validate kwargs
235
+ required_fields = {
236
+ name for name, info in config_class._fields.items() if info.required
237
+ }
238
+ provided_fields = set(kwargs.keys())
239
+ valid_fields = set(config_class._fields.keys())
240
+
241
+ # Check required fields
242
+ missing_fields = required_fields - provided_fields
243
+ if missing_fields:
244
+ raise ValueError(
245
+ f"Missing required fields: {', '.join(missing_fields)}"
246
+ )
247
+
248
+ # Check for unknown fields - but allow them for flexibility
249
+ unknown_fields = provided_fields - valid_fields
250
+ if unknown_fields:
251
+ print(
252
+ f"Warning: Unknown fields will be passed through: {', '.join(unknown_fields)}"
253
+ )
254
+
255
+ self._kwargs = kwargs
256
+ self._config_class = config_class
257
+
258
+ # Set annotations
259
+ __init__.__annotations__ = annotations
260
+ return __init__
261
+
262
+ def create_config(self) -> config_class:
263
+ """Create and return the ConfigMeta class instance."""
264
+ return config_class.from_dict(self._kwargs)
265
+
266
+ def to_dict(self) -> Dict[str, Any]:
267
+ """Return the raw kwargs as a dictionary."""
268
+ return self._kwargs.copy()
269
+
270
+ def __repr__(self) -> str:
271
+ return f"{class_name}({self._kwargs})"
272
+
273
+ # Create the class
274
+ init_method = create_init_method()
275
+
276
+ TypedClass = type(
277
+ class_name,
278
+ (object,),
279
+ {
280
+ "__init__": init_method,
281
+ "create_config": create_config,
282
+ "to_dict": to_dict,
283
+ "__repr__": __repr__,
284
+ "__module__": __name__,
285
+ "__qualname__": class_name,
286
+ },
287
+ )
288
+
289
+ return TypedClass
290
+
291
+
292
+ # Auto-generate and write typed classes to a file
293
+ def generate_typed_classes_file(output_file: str = None):
294
+ """
295
+ Generate typed classes and write them to a file for IDE support.
296
+
297
+ Args:
298
+ output_file: Path to write the generated classes. If None, prints to stdout.
299
+ """
300
+ from .unified_config import CoreConfig
301
+
302
+ config_classes = [CoreConfig]
303
+
304
+ module_code = generate_typed_classes_module(config_classes)
305
+
306
+ if output_file:
307
+ with open(output_file, "w") as f:
308
+ f.write(module_code)
309
+ print(f"Generated typed classes written to {output_file}")
310
+ else:
311
+ print(module_code)
312
+
313
+
314
+ # Example usage and testing
315
+ if __name__ == "__main__":
316
+ # Generate typed classes file
317
+ generate_typed_classes_file(os.path.join(current_dir, "typed_configs.py"))