ob-metaflow-extensions 1.4.33__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.
- metaflow_extensions/outerbounds/plugins/__init__.py +8 -1
- metaflow_extensions/outerbounds/plugins/apps/core/__init__.py +8 -2
- metaflow_extensions/outerbounds/plugins/apps/core/_state_machine.py +6 -6
- metaflow_extensions/outerbounds/plugins/apps/core/app_config.py +1 -19
- metaflow_extensions/outerbounds/plugins/apps/core/app_deploy_decorator.py +333 -0
- metaflow_extensions/outerbounds/plugins/apps/core/capsule.py +150 -79
- metaflow_extensions/outerbounds/plugins/apps/core/config/__init__.py +4 -1
- metaflow_extensions/outerbounds/plugins/apps/core/config/cli_generator.py +4 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/config_utils.py +103 -5
- metaflow_extensions/outerbounds/plugins/apps/core/config/schema_export.py +12 -1
- metaflow_extensions/outerbounds/plugins/apps/core/config/typed_configs.py +100 -6
- metaflow_extensions/outerbounds/plugins/apps/core/config/typed_init_generator.py +141 -2
- metaflow_extensions/outerbounds/plugins/apps/core/config/unified_config.py +74 -37
- metaflow_extensions/outerbounds/plugins/apps/core/config_schema.yaml +6 -6
- metaflow_extensions/outerbounds/plugins/apps/core/dependencies.py +2 -2
- metaflow_extensions/outerbounds/plugins/apps/core/deployer.py +1102 -105
- metaflow_extensions/outerbounds/plugins/apps/core/exceptions.py +341 -0
- metaflow_extensions/outerbounds/plugins/apps/core/perimeters.py +42 -6
- metaflow_extensions/outerbounds/plugins/aws/assume_role_decorator.py +43 -3
- metaflow_extensions/outerbounds/plugins/fast_bakery/baker.py +10 -1
- metaflow_extensions/outerbounds/plugins/optuna/__init__.py +2 -1
- metaflow_extensions/outerbounds/plugins/snowflake/snowflake.py +37 -7
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +18 -8
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +6 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +39 -15
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +5 -2
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +2 -2
- metaflow_extensions/outerbounds/remote_config.py +20 -7
- metaflow_extensions/outerbounds/toplevel/apps/__init__.py +9 -0
- metaflow_extensions/outerbounds/toplevel/apps/exceptions.py +11 -0
- metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +1 -1
- metaflow_extensions/outerbounds/toplevel/ob_internal.py +1 -1
- {ob_metaflow_extensions-1.4.33.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/METADATA +2 -2
- {ob_metaflow_extensions-1.4.33.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/RECORD +36 -34
- metaflow_extensions/outerbounds/plugins/apps/app_deploy_decorator.py +0 -146
- metaflow_extensions/outerbounds/plugins/apps/core/app_cli.py +0 -1200
- {ob_metaflow_extensions-1.4.33.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/WHEEL +0 -0
- {ob_metaflow_extensions-1.4.33.dist-info → ob_metaflow_extensions-1.6.2.dist-info}/top_level.txt +0 -0
|
@@ -73,6 +73,100 @@ class PackageConfigDict(TypedDict, total=False):
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
class TypedCoreConfig:
|
|
76
|
+
"""
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
name : str, optional
|
|
80
|
+
The name of the app to deploy.
|
|
81
|
+
|
|
82
|
+
port : int, optional
|
|
83
|
+
Port where the app is hosted. When deployed this will be port on which we will deploy the app.
|
|
84
|
+
|
|
85
|
+
description : str, optional
|
|
86
|
+
The description of the app to deploy.
|
|
87
|
+
|
|
88
|
+
app_type : str, optional
|
|
89
|
+
The User defined type of app to deploy. Its only used for bookkeeping purposes.
|
|
90
|
+
|
|
91
|
+
image : str, optional
|
|
92
|
+
The Docker image to deploy with the App.
|
|
93
|
+
|
|
94
|
+
tags : list, optional
|
|
95
|
+
The tags of the app to deploy.
|
|
96
|
+
|
|
97
|
+
secrets : list, optional
|
|
98
|
+
Outerbounds integrations to attach to the app. You can use the value you set in the `@secrets` decorator in your code.
|
|
99
|
+
|
|
100
|
+
compute_pools : list, optional
|
|
101
|
+
A list of compute pools to deploy the app to.
|
|
102
|
+
|
|
103
|
+
environment : dict, optional
|
|
104
|
+
Environment variables to deploy with the App.
|
|
105
|
+
|
|
106
|
+
commands : list, optional
|
|
107
|
+
A list of commands to run the app with.
|
|
108
|
+
|
|
109
|
+
resources : ResourceConfigDict, optional
|
|
110
|
+
Resource configuration for the app.
|
|
111
|
+
- cpu (str)
|
|
112
|
+
CPU requests
|
|
113
|
+
- memory (str)
|
|
114
|
+
Memory requests
|
|
115
|
+
- gpu (str)
|
|
116
|
+
GPU requests
|
|
117
|
+
- disk (str)
|
|
118
|
+
Storage disk size.
|
|
119
|
+
- shared_memory (str)
|
|
120
|
+
Shared memory
|
|
121
|
+
|
|
122
|
+
auth : AuthConfigDict, optional
|
|
123
|
+
Auth related configurations.
|
|
124
|
+
- type (str)
|
|
125
|
+
The type of authentication to use for the app.
|
|
126
|
+
- public (bool)
|
|
127
|
+
Whether the app is public or not.
|
|
128
|
+
|
|
129
|
+
replicas : ReplicaConfigDict, optional
|
|
130
|
+
The number of replicas to deploy the app with.
|
|
131
|
+
- fixed (int)
|
|
132
|
+
The fixed number of replicas to deploy the app with. If min and max are set, this will raise an error.
|
|
133
|
+
- min (int)
|
|
134
|
+
The minimum number of replicas to deploy the app with.
|
|
135
|
+
- max (int)
|
|
136
|
+
The maximum number of replicas to deploy the app with.
|
|
137
|
+
- scaling_policy (ScalingPolicyConfigDict)
|
|
138
|
+
Scaling policy defines the the metric based on which the replicas will horizontally scale. If min and max replicas are set and are not the same, then a scaling policy will be applied. Default scaling policies can be 60 rpm (ie 1 rps).
|
|
139
|
+
- rpm (int)
|
|
140
|
+
Scale up replicas when the requests per minute crosses this threshold. If nothing is provided and the replicas.max and replicas.min is set then the default rpm would be 60.
|
|
141
|
+
|
|
142
|
+
code_package : tuple, optional
|
|
143
|
+
Pre-packaged code from package_code(). A PackagedCode namedtuple containing url and key.
|
|
144
|
+
|
|
145
|
+
force_upgrade : bool, optional
|
|
146
|
+
Force upgrade the app even if it is currently being upgraded.
|
|
147
|
+
|
|
148
|
+
persistence : str, optional
|
|
149
|
+
The persistence mode to deploy the app with.
|
|
150
|
+
[Experimental] May change in the future.
|
|
151
|
+
|
|
152
|
+
project : str, optional
|
|
153
|
+
The project name to deploy the app to.
|
|
154
|
+
[Experimental] May change in the future.
|
|
155
|
+
|
|
156
|
+
branch : str, optional
|
|
157
|
+
The branch name to deploy the app to.
|
|
158
|
+
[Experimental] May change in the future.
|
|
159
|
+
|
|
160
|
+
models : list, optional
|
|
161
|
+
[Experimental] May change in the future.
|
|
162
|
+
|
|
163
|
+
data : list, optional
|
|
164
|
+
[Experimental] May change in the future.
|
|
165
|
+
|
|
166
|
+
generate_static_url : bool, optional
|
|
167
|
+
Generate a static URL for the app based on its name.
|
|
168
|
+
"""
|
|
169
|
+
|
|
76
170
|
def __init__(
|
|
77
171
|
self,
|
|
78
172
|
name: Optional[str] = None,
|
|
@@ -88,9 +182,7 @@ class TypedCoreConfig:
|
|
|
88
182
|
resources: Optional[ResourceConfigDict] = None,
|
|
89
183
|
auth: Optional[AuthConfigDict] = None,
|
|
90
184
|
replicas: Optional[ReplicaConfigDict] = None,
|
|
91
|
-
|
|
92
|
-
package: Optional[PackageConfigDict] = None,
|
|
93
|
-
no_deps: Optional[bool] = None,
|
|
185
|
+
code_package: Optional[tuple] = None,
|
|
94
186
|
force_upgrade: Optional[bool] = None,
|
|
95
187
|
persistence: Optional[str] = None,
|
|
96
188
|
project: Optional[str] = None,
|
|
@@ -114,9 +206,7 @@ class TypedCoreConfig:
|
|
|
114
206
|
"resources": resources,
|
|
115
207
|
"auth": auth,
|
|
116
208
|
"replicas": replicas,
|
|
117
|
-
"
|
|
118
|
-
"package": package,
|
|
119
|
-
"no_deps": no_deps,
|
|
209
|
+
"code_package": code_package,
|
|
120
210
|
"force_upgrade": force_upgrade,
|
|
121
211
|
"persistence": persistence,
|
|
122
212
|
"project": project,
|
|
@@ -131,9 +221,13 @@ class TypedCoreConfig:
|
|
|
131
221
|
self._kwargs = {k: v for k, v in self._kwargs.items() if v is not None}
|
|
132
222
|
self._config_class = CoreConfig
|
|
133
223
|
self._config = self.create_config()
|
|
224
|
+
self._init()
|
|
134
225
|
|
|
135
226
|
def create_config(self) -> CoreConfig:
|
|
136
227
|
return CoreConfig.from_dict(self._kwargs)
|
|
137
228
|
|
|
138
229
|
def to_dict(self) -> Dict[str, Any]:
|
|
139
230
|
return self._config.to_dict()
|
|
231
|
+
|
|
232
|
+
def _init(self):
|
|
233
|
+
raise NotImplementedError
|
|
@@ -43,6 +43,10 @@ def collect_nested_configs_recursive(
|
|
|
43
43
|
"""
|
|
44
44
|
Recursively collect all nested ConfigMeta classes from a config class.
|
|
45
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
|
+
|
|
46
50
|
Args:
|
|
47
51
|
config_class: A class that inherits from ConfigMeta
|
|
48
52
|
visited: Set of already visited class names to avoid infinite recursion
|
|
@@ -61,7 +65,7 @@ def collect_nested_configs_recursive(
|
|
|
61
65
|
|
|
62
66
|
visited.add(config_class.__name__)
|
|
63
67
|
|
|
64
|
-
# First pass: collect immediate nested configs
|
|
68
|
+
# First pass: collect immediate nested configs (all of them for TypedDict generation)
|
|
65
69
|
for field_name, field_info in config_class._fields.items():
|
|
66
70
|
if ConfigMeta.is_instance(field_info.field_type):
|
|
67
71
|
nested_class = field_info.field_type
|
|
@@ -76,6 +80,119 @@ def collect_nested_configs_recursive(
|
|
|
76
80
|
return nested_configs
|
|
77
81
|
|
|
78
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
|
+
|
|
79
196
|
def generate_typed_class_code(config_class: Type) -> str:
|
|
80
197
|
"""
|
|
81
198
|
Generate the actual Python code for a typed class that IDEs can understand.
|
|
@@ -98,6 +215,7 @@ def generate_typed_class_code(config_class: Type) -> str:
|
|
|
98
215
|
nested_configs = collect_nested_configs_recursive(config_class)
|
|
99
216
|
|
|
100
217
|
# Generate TypedDict classes for all nested configs
|
|
218
|
+
# Note: TypedDicts include ALL fields for type completeness (no context filtering)
|
|
101
219
|
for nested_name, nested_class in nested_configs.items():
|
|
102
220
|
dict_name = f"{nested_name}Dict"
|
|
103
221
|
fields = []
|
|
@@ -118,6 +236,10 @@ def generate_typed_class_code(config_class: Type) -> str:
|
|
|
118
236
|
all_assignments = []
|
|
119
237
|
|
|
120
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
|
+
|
|
121
243
|
field_type = field_info.field_type
|
|
122
244
|
|
|
123
245
|
# Handle nested ConfigMeta classes
|
|
@@ -147,7 +269,16 @@ def generate_typed_class_code(config_class: Type) -> str:
|
|
|
147
269
|
else:
|
|
148
270
|
params_with_kwargs = [" **kwargs"]
|
|
149
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
|
+
|
|
150
279
|
class_code = f"""class {class_name}:
|
|
280
|
+
{indented_class_docstring}
|
|
281
|
+
|
|
151
282
|
def __init__(
|
|
152
283
|
self,
|
|
153
284
|
{comma_newline.join(params_with_kwargs)}
|
|
@@ -161,12 +292,16 @@ def generate_typed_class_code(config_class: Type) -> str:
|
|
|
161
292
|
self._kwargs = {{k: v for k, v in self._kwargs.items() if v is not None}}
|
|
162
293
|
self._config_class = {config_class.__name__}
|
|
163
294
|
self._config = self.create_config()
|
|
295
|
+
self._init()
|
|
164
296
|
|
|
165
297
|
def create_config(self) -> {config_class.__name__}:
|
|
166
298
|
return {config_class.__name__}.from_dict(self._kwargs)
|
|
167
299
|
|
|
168
300
|
def to_dict(self) -> Dict[str, Any]:
|
|
169
|
-
return self._config.to_dict()
|
|
301
|
+
return self._config.to_dict()
|
|
302
|
+
|
|
303
|
+
def _init(self):
|
|
304
|
+
raise NotImplementedError"""
|
|
170
305
|
|
|
171
306
|
# Combine all code
|
|
172
307
|
full_code = []
|
|
@@ -299,6 +434,10 @@ def create_typed_init_class_dynamic(config_class: Type) -> Type:
|
|
|
299
434
|
annotations: Dict[str, Any] = {"return": type(None)}
|
|
300
435
|
|
|
301
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
|
+
|
|
302
441
|
field_type = field_info.field_type
|
|
303
442
|
|
|
304
443
|
# Handle nested ConfigMeta classes
|