ob-metaflow 2.15.18.1__py2.py3-none-any.whl → 2.16.0.1__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 might be problematic. Click here for more details.
- metaflow/__init__.py +7 -1
- metaflow/_vendor/imghdr/__init__.py +180 -0
- metaflow/cli.py +16 -1
- metaflow/cli_components/init_cmd.py +1 -0
- metaflow/cli_components/run_cmds.py +6 -2
- metaflow/client/core.py +22 -30
- metaflow/cmd/develop/stub_generator.py +19 -2
- metaflow/datastore/task_datastore.py +0 -1
- metaflow/debug.py +5 -0
- metaflow/decorators.py +230 -70
- metaflow/extension_support/__init__.py +15 -8
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/flowspec.py +80 -53
- metaflow/graph.py +24 -2
- metaflow/meta_files.py +13 -0
- metaflow/metadata_provider/metadata.py +7 -1
- metaflow/metaflow_config.py +5 -0
- metaflow/metaflow_environment.py +82 -25
- metaflow/metaflow_version.py +1 -1
- metaflow/package/__init__.py +664 -0
- metaflow/packaging_sys/__init__.py +870 -0
- metaflow/packaging_sys/backend.py +113 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +86 -0
- metaflow/packaging_sys/utils.py +91 -0
- metaflow/packaging_sys/v1.py +476 -0
- metaflow/plugins/__init__.py +3 -0
- metaflow/plugins/airflow/airflow.py +11 -1
- metaflow/plugins/airflow/airflow_cli.py +15 -4
- metaflow/plugins/argo/argo_workflows.py +346 -301
- metaflow/plugins/argo/argo_workflows_cli.py +16 -4
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/aws/aws_utils.py +1 -1
- metaflow/plugins/aws/batch/batch.py +22 -3
- metaflow/plugins/aws/batch/batch_cli.py +3 -0
- metaflow/plugins/aws/batch/batch_decorator.py +13 -5
- metaflow/plugins/aws/step_functions/step_functions.py +10 -1
- metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
- metaflow/plugins/cards/card_cli.py +20 -1
- metaflow/plugins/cards/card_creator.py +24 -1
- metaflow/plugins/cards/card_decorator.py +57 -6
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +5 -2
- metaflow/plugins/cards/card_modules/test_cards.py +16 -0
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/exit_hook/__init__.py +0 -0
- metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
- metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
- metaflow/plugins/kubernetes/kubernetes.py +8 -1
- metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
- metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
- metaflow/plugins/package_cli.py +25 -23
- metaflow/plugins/parallel_decorator.py +4 -2
- metaflow/plugins/pypi/bootstrap.py +8 -2
- metaflow/plugins/pypi/conda_decorator.py +39 -82
- metaflow/plugins/pypi/conda_environment.py +6 -2
- metaflow/plugins/pypi/pypi_decorator.py +4 -4
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +9 -173
- metaflow/plugins/secrets/secrets_func.py +49 -0
- metaflow/plugins/secrets/secrets_spec.py +101 -0
- metaflow/plugins/secrets/utils.py +74 -0
- metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
- metaflow/plugins/timeout_decorator.py +0 -1
- metaflow/plugins/uv/bootstrap.py +11 -0
- metaflow/plugins/uv/uv_environment.py +4 -2
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/click_api.py +5 -4
- metaflow/runner/metaflow_runner.py +16 -1
- metaflow/runner/subprocess_manager.py +14 -2
- metaflow/runtime.py +82 -11
- metaflow/task.py +91 -7
- metaflow/user_configs/config_options.py +13 -8
- metaflow/user_configs/config_parameters.py +0 -4
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +499 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +263 -0
- metaflow/user_decorators/user_step_decorator.py +712 -0
- metaflow/util.py +4 -1
- metaflow/version.py +1 -1
- {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/Tiltfile +27 -2
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/METADATA +2 -2
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/RECORD +90 -70
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/Makefile +0 -0
- {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/WHEEL +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/licenses/LICENSE +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/top_level.txt +0 -0
|
@@ -2,8 +2,10 @@ import json
|
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
4
|
import tempfile
|
|
5
|
+
from typing import Tuple, Dict
|
|
5
6
|
|
|
6
7
|
from metaflow.decorators import StepDecorator
|
|
8
|
+
from metaflow.metadata_provider import MetaDatum
|
|
7
9
|
from metaflow.metaflow_current import current
|
|
8
10
|
from metaflow.user_configs.config_options import ConfigInput
|
|
9
11
|
from metaflow.user_configs.config_parameters import dump_config_values
|
|
@@ -22,6 +24,24 @@ def warning_message(message, logger=None, ts=False):
|
|
|
22
24
|
logger(msg, timestamp=ts, bad=True)
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
class MetadataStateManager(object):
|
|
28
|
+
def __init__(self, info_func):
|
|
29
|
+
self._info_func = info_func
|
|
30
|
+
self._metadata_registered = {}
|
|
31
|
+
|
|
32
|
+
def register_metadata(self, card_uuid) -> Tuple[bool, Dict]:
|
|
33
|
+
info = self._info_func()
|
|
34
|
+
# Check that metadata was not written yet. We only want to write once.
|
|
35
|
+
if (
|
|
36
|
+
info is None
|
|
37
|
+
or info.get(card_uuid) is None
|
|
38
|
+
or self._metadata_registered.get(card_uuid)
|
|
39
|
+
):
|
|
40
|
+
return False, {}
|
|
41
|
+
self._metadata_registered[card_uuid] = True
|
|
42
|
+
return True, info.get(card_uuid)
|
|
43
|
+
|
|
44
|
+
|
|
25
45
|
class CardDecorator(StepDecorator):
|
|
26
46
|
"""
|
|
27
47
|
Creates a human-readable report, a Metaflow Card, after this step completes.
|
|
@@ -55,11 +75,14 @@ class CardDecorator(StepDecorator):
|
|
|
55
75
|
The or one of the cards attached to this step.
|
|
56
76
|
"""
|
|
57
77
|
|
|
78
|
+
_GLOBAL_CARD_INFO = {}
|
|
79
|
+
|
|
58
80
|
name = "card"
|
|
59
81
|
defaults = {
|
|
60
82
|
"type": "default",
|
|
61
83
|
"options": {},
|
|
62
84
|
"scope": "task",
|
|
85
|
+
"rank": None, # Can be one of "high", "medium", "low". Can help derive ordering on the UI.
|
|
63
86
|
"timeout": 45,
|
|
64
87
|
"id": None,
|
|
65
88
|
"save_errors": True,
|
|
@@ -91,6 +114,7 @@ class CardDecorator(StepDecorator):
|
|
|
91
114
|
self._is_editable = False
|
|
92
115
|
self._card_uuid = None
|
|
93
116
|
self._user_set_card_id = None
|
|
117
|
+
self._metadata_registered = False
|
|
94
118
|
|
|
95
119
|
@classmethod
|
|
96
120
|
def _set_card_creator(cls, card_creator):
|
|
@@ -131,6 +155,16 @@ class CardDecorator(StepDecorator):
|
|
|
131
155
|
json.dump(config_value, config_file)
|
|
132
156
|
cls._config_file_name = config_file.name
|
|
133
157
|
|
|
158
|
+
@classmethod
|
|
159
|
+
def _register_card_info(cls, **kwargs):
|
|
160
|
+
if not kwargs.get("card_uuid"):
|
|
161
|
+
raise ValueError("card_uuid is required")
|
|
162
|
+
cls._GLOBAL_CARD_INFO[kwargs["card_uuid"]] = kwargs
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def all_cards_info(cls):
|
|
166
|
+
return cls._GLOBAL_CARD_INFO.copy()
|
|
167
|
+
|
|
134
168
|
def step_init(
|
|
135
169
|
self, flow, graph, step_name, decorators, environment, flow_datastore, logger
|
|
136
170
|
):
|
|
@@ -191,6 +225,11 @@ class CardDecorator(StepDecorator):
|
|
|
191
225
|
# we need to ensure that a single config file is being referenced for all card create commands.
|
|
192
226
|
# This config file will be removed when the last card decorator has finished creating its card.
|
|
193
227
|
self._set_config_file_name(flow)
|
|
228
|
+
# The MetadataStateManager is used to track the state of the metadata registration.
|
|
229
|
+
# It is there to ensure that we only register metadata for the card once. This is so that we
|
|
230
|
+
# avoid any un-necessary metadata writes because the create command can be called multiple times during the
|
|
231
|
+
# card creation process.
|
|
232
|
+
self._metadata_state_manager = MetadataStateManager(self.all_cards_info)
|
|
194
233
|
|
|
195
234
|
card_type = self.attributes["type"]
|
|
196
235
|
card_class = get_card_class(card_type)
|
|
@@ -225,7 +264,12 @@ class CardDecorator(StepDecorator):
|
|
|
225
264
|
# we need to ensure that `current.card` has `CardComponentCollector` instantiated only once.
|
|
226
265
|
if not self._is_event_registered("pre-step"):
|
|
227
266
|
self._register_event("pre-step")
|
|
228
|
-
self._set_card_creator(
|
|
267
|
+
self._set_card_creator(
|
|
268
|
+
CardCreator(
|
|
269
|
+
self._create_top_level_args(flow),
|
|
270
|
+
self._metadata_state_manager.register_metadata,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
229
273
|
|
|
230
274
|
current._update_env(
|
|
231
275
|
{"card": CardComponentCollector(self._logger, self.card_creator)}
|
|
@@ -248,6 +292,18 @@ class CardDecorator(StepDecorator):
|
|
|
248
292
|
)
|
|
249
293
|
self._card_uuid = card_metadata["uuid"]
|
|
250
294
|
|
|
295
|
+
self._register_card_info(
|
|
296
|
+
card_uuid=self._card_uuid,
|
|
297
|
+
rank=self.attributes["rank"],
|
|
298
|
+
type=self.attributes["type"],
|
|
299
|
+
options=self.card_options,
|
|
300
|
+
is_editable=self._is_editable,
|
|
301
|
+
is_runtime_card=self._is_runtime_card,
|
|
302
|
+
refresh_interval=self.attributes["refresh_interval"],
|
|
303
|
+
customize=customize,
|
|
304
|
+
id=self._user_set_card_id,
|
|
305
|
+
)
|
|
306
|
+
|
|
251
307
|
# This means that we are calling `task_pre_step` on the last card decorator.
|
|
252
308
|
# We can now `finalize` method in the CardComponentCollector object.
|
|
253
309
|
# This will set up the `current.card` object for usage inside `@step` code.
|
|
@@ -305,11 +361,6 @@ class CardDecorator(StepDecorator):
|
|
|
305
361
|
|
|
306
362
|
return list(self._options(top_level_options))
|
|
307
363
|
|
|
308
|
-
def task_exception(
|
|
309
|
-
self, exception, step_name, flow, graph, retry_count, max_user_code_retries
|
|
310
|
-
):
|
|
311
|
-
self._cleanup(step_name)
|
|
312
|
-
|
|
313
364
|
def _cleanup(self, step_name):
|
|
314
365
|
self._increment_completed_counter()
|
|
315
366
|
if self.task_finished_decos == self.total_decos_on_step[step_name]:
|
|
@@ -143,7 +143,10 @@ class TaskToDict:
|
|
|
143
143
|
obj_type_name = self._get_object_type(data_object)
|
|
144
144
|
if obj_type_name == "bytes":
|
|
145
145
|
# Works for python 3.1+
|
|
146
|
-
|
|
146
|
+
# Python 3.13 removes the standard ``imghdr`` module. Metaflow
|
|
147
|
+
# vendors a copy so we can keep using ``what`` to detect image
|
|
148
|
+
# formats irrespective of the Python version.
|
|
149
|
+
from metaflow._vendor import imghdr
|
|
147
150
|
|
|
148
151
|
resp = imghdr.what(None, h=data_object)
|
|
149
152
|
# Only accept types supported on the web
|
|
@@ -157,7 +160,7 @@ class TaskToDict:
|
|
|
157
160
|
obj_type_name = self._get_object_type(data_object)
|
|
158
161
|
if obj_type_name == "bytes":
|
|
159
162
|
# Works for python 3.1+
|
|
160
|
-
import imghdr
|
|
163
|
+
from metaflow._vendor import imghdr
|
|
161
164
|
|
|
162
165
|
resp = imghdr.what(None, h=data_object)
|
|
163
166
|
# Only accept types supported on the web
|
|
@@ -213,3 +213,19 @@ class TestRefreshComponentCard(MetaflowCard):
|
|
|
213
213
|
if task.finished:
|
|
214
214
|
return "final"
|
|
215
215
|
return "runtime-%s" % _component_values_to_hash(data["components"])
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class TestImageCard(MetaflowCard):
|
|
219
|
+
"""Card that renders a tiny PNG using ``TaskToDict.parse_image``."""
|
|
220
|
+
|
|
221
|
+
type = "test_image_card"
|
|
222
|
+
|
|
223
|
+
def render(self, task):
|
|
224
|
+
from .convert_to_native_type import TaskToDict
|
|
225
|
+
import base64
|
|
226
|
+
|
|
227
|
+
png_bytes = base64.b64decode(
|
|
228
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGNgYGBgAAAABQABRDE8UwAAAABJRU5ErkJggg=="
|
|
229
|
+
)
|
|
230
|
+
img_src = TaskToDict().parse_image(png_bytes)
|
|
231
|
+
return f"<html><img src='{img_src}' /></html>"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from metaflow.metadata_provider import MetaDatum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _save_metadata(
|
|
6
|
+
metadata_provider,
|
|
7
|
+
run_id,
|
|
8
|
+
step_name,
|
|
9
|
+
task_id,
|
|
10
|
+
attempt_id,
|
|
11
|
+
card_uuid,
|
|
12
|
+
save_metadata,
|
|
13
|
+
):
|
|
14
|
+
entries = [
|
|
15
|
+
MetaDatum(
|
|
16
|
+
field=card_uuid,
|
|
17
|
+
value=json.dumps(save_metadata),
|
|
18
|
+
type="card-info",
|
|
19
|
+
tags=["attempt_id:{0}".format(attempt_id)],
|
|
20
|
+
)
|
|
21
|
+
]
|
|
22
|
+
metadata_provider.register_metadata(run_id, step_name, task_id, entries)
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from metaflow.decorators import FlowDecorator
|
|
2
|
+
from metaflow.exception import MetaflowException
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ExitHookDecorator(FlowDecorator):
|
|
6
|
+
name = "exit_hook"
|
|
7
|
+
allow_multiple = True
|
|
8
|
+
|
|
9
|
+
defaults = {
|
|
10
|
+
"on_success": [],
|
|
11
|
+
"on_error": [],
|
|
12
|
+
"options": {},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def flow_init(
|
|
16
|
+
self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
|
|
17
|
+
):
|
|
18
|
+
on_success = self.attributes["on_success"]
|
|
19
|
+
on_error = self.attributes["on_error"]
|
|
20
|
+
|
|
21
|
+
if not on_success and not on_error:
|
|
22
|
+
raise MetaflowException(
|
|
23
|
+
"Choose at least one of the options on_success/on_error"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
self.success_hooks = []
|
|
27
|
+
self.error_hooks = []
|
|
28
|
+
for success_fn in on_success:
|
|
29
|
+
if isinstance(success_fn, str):
|
|
30
|
+
self.success_hooks.append(success_fn)
|
|
31
|
+
elif callable(success_fn):
|
|
32
|
+
self.success_hooks.append(success_fn.__name__)
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"Exit hooks inside 'on_success' must be a function or a string referring to the function"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for error_fn in on_error:
|
|
39
|
+
if isinstance(error_fn, str):
|
|
40
|
+
self.error_hooks.append(error_fn)
|
|
41
|
+
elif callable(error_fn):
|
|
42
|
+
self.error_hooks.append(error_fn.__name__)
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"Exit hooks inside 'on_error' must be a function or a string referring to the function"
|
|
46
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import inspect
|
|
3
|
+
import importlib
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main(flow_file, fn_name_or_path, run_pathspec):
|
|
8
|
+
hook_fn = None
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
module_path, function_name = fn_name_or_path.rsplit(".", 1)
|
|
12
|
+
module = importlib.import_module(module_path)
|
|
13
|
+
hook_fn = getattr(module, function_name)
|
|
14
|
+
except (ImportError, AttributeError, ValueError):
|
|
15
|
+
try:
|
|
16
|
+
module_name = os.path.splitext(os.path.basename(flow_file))[0]
|
|
17
|
+
spec = importlib.util.spec_from_file_location(module_name, flow_file)
|
|
18
|
+
module = importlib.util.module_from_spec(spec)
|
|
19
|
+
spec.loader.exec_module(module)
|
|
20
|
+
hook_fn = getattr(module, fn_name_or_path)
|
|
21
|
+
except (AttributeError, IOError) as e:
|
|
22
|
+
print(
|
|
23
|
+
f"[exit_hook] Could not load function '{fn_name_or_path}' "
|
|
24
|
+
f"as an import path or from '{flow_file}': {e}"
|
|
25
|
+
)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
argspec = inspect.getfullargspec(hook_fn)
|
|
29
|
+
|
|
30
|
+
# Check if fn expects a run object as an arg.
|
|
31
|
+
if "run" in argspec.args or argspec.varkw is not None:
|
|
32
|
+
from metaflow import Run
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
_run = Run(run_pathspec, _namespace_check=False)
|
|
36
|
+
except Exception as ex:
|
|
37
|
+
print(ex)
|
|
38
|
+
_run = None
|
|
39
|
+
|
|
40
|
+
hook_fn(run=_run)
|
|
41
|
+
else:
|
|
42
|
+
hook_fn()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
try:
|
|
47
|
+
flow_file, fn_name, run_pathspec = sys.argv[1:4]
|
|
48
|
+
except Exception:
|
|
49
|
+
print("Usage: exit_hook_script.py <flow_file> <function_name> <run_pathspec>")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
main(flow_file, fn_name, run_pathspec)
|
|
@@ -90,6 +90,7 @@ class Kubernetes(object):
|
|
|
90
90
|
step_name,
|
|
91
91
|
task_id,
|
|
92
92
|
attempt,
|
|
93
|
+
code_package_metadata,
|
|
93
94
|
code_package_url,
|
|
94
95
|
step_cmds,
|
|
95
96
|
):
|
|
@@ -104,7 +105,7 @@ class Kubernetes(object):
|
|
|
104
105
|
stderr_path=STDERR_PATH,
|
|
105
106
|
)
|
|
106
107
|
init_cmds = self._environment.get_package_commands(
|
|
107
|
-
code_package_url, self._datastore.TYPE
|
|
108
|
+
code_package_url, self._datastore.TYPE, code_package_metadata
|
|
108
109
|
)
|
|
109
110
|
init_expr = " && ".join(init_cmds)
|
|
110
111
|
step_expr = bash_capture_logs(
|
|
@@ -165,6 +166,7 @@ class Kubernetes(object):
|
|
|
165
166
|
task_id,
|
|
166
167
|
attempt,
|
|
167
168
|
user,
|
|
169
|
+
code_package_metadata,
|
|
168
170
|
code_package_sha,
|
|
169
171
|
code_package_url,
|
|
170
172
|
code_package_ds,
|
|
@@ -232,6 +234,7 @@ class Kubernetes(object):
|
|
|
232
234
|
qos=qos,
|
|
233
235
|
security_context=security_context,
|
|
234
236
|
)
|
|
237
|
+
.environment_variable("METAFLOW_CODE_METADATA", code_package_metadata)
|
|
235
238
|
.environment_variable("METAFLOW_CODE_SHA", code_package_sha)
|
|
236
239
|
.environment_variable("METAFLOW_CODE_URL", code_package_url)
|
|
237
240
|
.environment_variable("METAFLOW_CODE_DS", code_package_ds)
|
|
@@ -431,6 +434,7 @@ class Kubernetes(object):
|
|
|
431
434
|
step_name=step_name,
|
|
432
435
|
task_id=_tskid,
|
|
433
436
|
attempt=attempt,
|
|
437
|
+
code_package_metadata=code_package_metadata,
|
|
434
438
|
code_package_url=code_package_url,
|
|
435
439
|
step_cmds=[
|
|
436
440
|
step_cli.replace(
|
|
@@ -479,6 +483,7 @@ class Kubernetes(object):
|
|
|
479
483
|
task_id,
|
|
480
484
|
attempt,
|
|
481
485
|
user,
|
|
486
|
+
code_package_metadata,
|
|
482
487
|
code_package_sha,
|
|
483
488
|
code_package_url,
|
|
484
489
|
code_package_ds,
|
|
@@ -527,6 +532,7 @@ class Kubernetes(object):
|
|
|
527
532
|
step_name=step_name,
|
|
528
533
|
task_id=task_id,
|
|
529
534
|
attempt=attempt,
|
|
535
|
+
code_package_metadata=code_package_metadata,
|
|
530
536
|
code_package_url=code_package_url,
|
|
531
537
|
step_cmds=[step_cli],
|
|
532
538
|
),
|
|
@@ -555,6 +561,7 @@ class Kubernetes(object):
|
|
|
555
561
|
qos=qos,
|
|
556
562
|
security_context=security_context,
|
|
557
563
|
)
|
|
564
|
+
.environment_variable("METAFLOW_CODE_METADATA", code_package_metadata)
|
|
558
565
|
.environment_variable("METAFLOW_CODE_SHA", code_package_sha)
|
|
559
566
|
.environment_variable("METAFLOW_CODE_URL", code_package_url)
|
|
560
567
|
.environment_variable("METAFLOW_CODE_DS", code_package_ds)
|
|
@@ -41,6 +41,7 @@ def kubernetes():
|
|
|
41
41
|
)
|
|
42
42
|
@tracing.cli("kubernetes/step")
|
|
43
43
|
@click.argument("step-name")
|
|
44
|
+
@click.argument("code-package-metadata")
|
|
44
45
|
@click.argument("code-package-sha")
|
|
45
46
|
@click.argument("code-package-url")
|
|
46
47
|
@click.option(
|
|
@@ -161,6 +162,7 @@ def kubernetes():
|
|
|
161
162
|
def step(
|
|
162
163
|
ctx,
|
|
163
164
|
step_name,
|
|
165
|
+
code_package_metadata,
|
|
164
166
|
code_package_sha,
|
|
165
167
|
code_package_url,
|
|
166
168
|
executable=None,
|
|
@@ -304,6 +306,7 @@ def step(
|
|
|
304
306
|
task_id=task_id,
|
|
305
307
|
attempt=str(retry_count),
|
|
306
308
|
user=util.get_username(),
|
|
309
|
+
code_package_metadata=code_package_metadata,
|
|
307
310
|
code_package_sha=code_package_sha,
|
|
308
311
|
code_package_url=code_package_url,
|
|
309
312
|
code_package_ds=ctx.obj.flow_datastore.TYPE,
|
|
@@ -11,6 +11,7 @@ from metaflow.metadata_provider import MetaDatum
|
|
|
11
11
|
from metaflow.metadata_provider.util import sync_local_metadata_to_datastore
|
|
12
12
|
from metaflow.metaflow_config import (
|
|
13
13
|
DATASTORE_LOCAL_DIR,
|
|
14
|
+
FEAT_ALWAYS_UPLOAD_CODE_PACKAGE,
|
|
14
15
|
KUBERNETES_CONTAINER_IMAGE,
|
|
15
16
|
KUBERNETES_CONTAINER_REGISTRY,
|
|
16
17
|
KUBERNETES_CPU,
|
|
@@ -170,6 +171,7 @@ class KubernetesDecorator(StepDecorator):
|
|
|
170
171
|
"qos": KUBERNETES_QOS,
|
|
171
172
|
"security_context": None,
|
|
172
173
|
}
|
|
174
|
+
package_metadata = None
|
|
173
175
|
package_url = None
|
|
174
176
|
package_sha = None
|
|
175
177
|
run_time_limit = None
|
|
@@ -179,8 +181,6 @@ class KubernetesDecorator(StepDecorator):
|
|
|
179
181
|
target_platform = KUBERNETES_CONDA_ARCH or "linux-64"
|
|
180
182
|
|
|
181
183
|
def init(self):
|
|
182
|
-
super(KubernetesDecorator, self).init()
|
|
183
|
-
|
|
184
184
|
if not self.attributes["namespace"]:
|
|
185
185
|
self.attributes["namespace"] = KUBERNETES_NAMESPACE
|
|
186
186
|
if not self.attributes["service_account"]:
|
|
@@ -486,6 +486,7 @@ class KubernetesDecorator(StepDecorator):
|
|
|
486
486
|
# to execute on Kubernetes anymore. We can execute possible fallback
|
|
487
487
|
# code locally.
|
|
488
488
|
cli_args.commands = ["kubernetes", "step"]
|
|
489
|
+
cli_args.command_args.append(self.package_metadata)
|
|
489
490
|
cli_args.command_args.append(self.package_sha)
|
|
490
491
|
cli_args.command_args.append(self.package_url)
|
|
491
492
|
|
|
@@ -657,9 +658,16 @@ class KubernetesDecorator(StepDecorator):
|
|
|
657
658
|
@classmethod
|
|
658
659
|
def _save_package_once(cls, flow_datastore, package):
|
|
659
660
|
if cls.package_url is None:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
661
|
+
if not FEAT_ALWAYS_UPLOAD_CODE_PACKAGE:
|
|
662
|
+
cls.package_url, cls.package_sha = flow_datastore.save_data(
|
|
663
|
+
[package.blob], len_hint=1
|
|
664
|
+
)[0]
|
|
665
|
+
cls.package_metadata = package.package_metadata
|
|
666
|
+
else:
|
|
667
|
+
# Blocks until the package is uploaded
|
|
668
|
+
cls.package_url = package.package_url()
|
|
669
|
+
cls.package_sha = package.package_sha()
|
|
670
|
+
cls.package_metadata = package.package_metadata
|
|
663
671
|
|
|
664
672
|
|
|
665
673
|
# TODO: Unify this method with the multi-node setup in @batch
|
metaflow/plugins/package_cli.py
CHANGED
|
@@ -9,35 +9,30 @@ def cli():
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@cli.group(help="Commands related to code packages.")
|
|
12
|
+
@click.option(
|
|
13
|
+
"--timeout", default=60, help="Timeout for package operations in seconds."
|
|
14
|
+
)
|
|
12
15
|
@click.pass_obj
|
|
13
|
-
def package(obj):
|
|
16
|
+
def package(obj, timeout):
|
|
14
17
|
# Prepare the package before any of the sub-commands are invoked.
|
|
18
|
+
# We explicitly will *not* upload it to the datastore.
|
|
15
19
|
obj.package = MetaflowPackage(
|
|
16
|
-
obj.flow,
|
|
20
|
+
obj.flow,
|
|
21
|
+
obj.environment,
|
|
22
|
+
obj.echo,
|
|
23
|
+
suffixes=obj.package_suffixes,
|
|
24
|
+
flow_datastore=None,
|
|
17
25
|
)
|
|
26
|
+
obj.package_op_timeout = timeout
|
|
18
27
|
|
|
19
28
|
|
|
20
|
-
@package.command(help="Output information about the
|
|
29
|
+
@package.command(help="Output information about the code package.")
|
|
21
30
|
@click.pass_obj
|
|
22
31
|
def info(obj):
|
|
23
|
-
obj.
|
|
24
|
-
obj.echo_always(
|
|
25
|
-
"Hash: *%s*" % sha1(obj.package.blob).hexdigest(),
|
|
26
|
-
highlight="green",
|
|
27
|
-
highlight_bold=False,
|
|
28
|
-
)
|
|
29
|
-
obj.echo_always(
|
|
30
|
-
"Package size: *%d* KB" % (len(obj.package.blob) / 1024),
|
|
31
|
-
highlight="green",
|
|
32
|
-
highlight_bold=False,
|
|
33
|
-
)
|
|
34
|
-
num = sum(1 for _ in obj.package.path_tuples())
|
|
35
|
-
obj.echo_always(
|
|
36
|
-
"Number of files: *%d*" % num, highlight="green", highlight_bold=False
|
|
37
|
-
)
|
|
32
|
+
obj.echo_always(obj.package.show())
|
|
38
33
|
|
|
39
34
|
|
|
40
|
-
@package.command(help="List files included in the code package.")
|
|
35
|
+
@package.command(help="List all files included in the code package.")
|
|
41
36
|
@click.option(
|
|
42
37
|
"--archive/--no-archive",
|
|
43
38
|
default=False,
|
|
@@ -47,8 +42,10 @@ def info(obj):
|
|
|
47
42
|
)
|
|
48
43
|
@click.pass_obj
|
|
49
44
|
def list(obj, archive=False):
|
|
45
|
+
_ = obj.package.blob_with_timeout(timeout=obj.package_op_timeout)
|
|
46
|
+
# We now have all the information about the blob
|
|
50
47
|
obj.echo(
|
|
51
|
-
"Files included in the code package
|
|
48
|
+
"Files included in the code package (change with --package-suffixes):",
|
|
52
49
|
fg="magenta",
|
|
53
50
|
bold=False,
|
|
54
51
|
)
|
|
@@ -58,10 +55,15 @@ def list(obj, archive=False):
|
|
|
58
55
|
obj.echo_always("\n".join(path for path, _ in obj.package.path_tuples()))
|
|
59
56
|
|
|
60
57
|
|
|
61
|
-
@package.command(help="Save the current code package
|
|
58
|
+
@package.command(help="Save the current code package to a file.")
|
|
62
59
|
@click.argument("path")
|
|
63
60
|
@click.pass_obj
|
|
64
61
|
def save(obj, path):
|
|
65
62
|
with open(path, "wb") as f:
|
|
66
|
-
f.write(obj.package.blob)
|
|
67
|
-
obj.echo(
|
|
63
|
+
f.write(obj.package.blob())
|
|
64
|
+
obj.echo(
|
|
65
|
+
"Code package saved in *%s* with metadata: %s"
|
|
66
|
+
% (path, obj.package.package_metadata),
|
|
67
|
+
fg="magenta",
|
|
68
|
+
bold=False,
|
|
69
|
+
)
|
|
@@ -36,8 +36,10 @@ class ParallelDecorator(StepDecorator):
|
|
|
36
36
|
defaults = {}
|
|
37
37
|
IS_PARALLEL = True
|
|
38
38
|
|
|
39
|
-
def __init__(self, attributes=None, statically_defined=False):
|
|
40
|
-
super(ParallelDecorator, self).__init__(
|
|
39
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
|
40
|
+
super(ParallelDecorator, self).__init__(
|
|
41
|
+
attributes, statically_defined, inserted_by
|
|
42
|
+
)
|
|
41
43
|
|
|
42
44
|
def runtime_step_cli(
|
|
43
45
|
self, cli_args, retry_count, max_user_code_retries, ubf_context
|
|
@@ -12,9 +12,10 @@ import platform
|
|
|
12
12
|
from urllib.error import URLError
|
|
13
13
|
from urllib.request import urlopen
|
|
14
14
|
from metaflow.metaflow_config import DATASTORE_LOCAL_DIR, CONDA_USE_FAST_INIT
|
|
15
|
+
from metaflow.packaging_sys import MetaflowCodeContent, ContentType
|
|
15
16
|
from metaflow.plugins import DATASTORES
|
|
16
17
|
from metaflow.plugins.pypi.utils import MICROMAMBA_MIRROR_URL, MICROMAMBA_URL
|
|
17
|
-
from metaflow.util import which
|
|
18
|
+
from metaflow.util import which
|
|
18
19
|
from urllib.request import Request
|
|
19
20
|
import warnings
|
|
20
21
|
|
|
@@ -365,8 +366,13 @@ if __name__ == "__main__":
|
|
|
365
366
|
|
|
366
367
|
# Move MAGIC_FILE inside local datastore.
|
|
367
368
|
os.makedirs(manifest_dir, exist_ok=True)
|
|
369
|
+
path_to_manifest = MetaflowCodeContent.get_filename(
|
|
370
|
+
MAGIC_FILE, ContentType.OTHER_CONTENT
|
|
371
|
+
)
|
|
372
|
+
if path_to_manifest is None:
|
|
373
|
+
raise RuntimeError(f"Cannot find {MAGIC_FILE} in the package")
|
|
368
374
|
shutil.move(
|
|
369
|
-
|
|
375
|
+
path_to_manifest,
|
|
370
376
|
os.path.join(manifest_dir, MAGIC_FILE),
|
|
371
377
|
)
|
|
372
378
|
with open(os.path.join(manifest_dir, MAGIC_FILE)) as f:
|