metaflow 2.15.20__py2.py3-none-any.whl → 2.16.0__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/__init__.py +7 -1
- 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/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/airflow/airflow.py +5 -1
- metaflow/plugins/airflow/airflow_cli.py +15 -4
- metaflow/plugins/argo/argo_workflows.py +23 -17
- metaflow/plugins/argo/argo_workflows_cli.py +16 -4
- 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 +4 -1
- metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
- metaflow/plugins/cards/card_decorator.py +0 -5
- 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/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/subprocess_manager.py +14 -2
- metaflow/runtime.py +37 -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
- {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/METADATA +2 -2
- {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/RECORD +71 -60
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Makefile +0 -0
- {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Tiltfile +0 -0
- {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
- {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/WHEEL +0 -0
- {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/entry_points.txt +0 -0
- {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/licenses/LICENSE +0 -0
- {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,3 @@
|
|
1
|
-
import importlib
|
2
|
-
import json
|
3
1
|
import os
|
4
2
|
import platform
|
5
3
|
import re
|
@@ -7,12 +5,9 @@ import sys
|
|
7
5
|
import tempfile
|
8
6
|
|
9
7
|
from metaflow.decorators import FlowDecorator, StepDecorator
|
10
|
-
from metaflow.extension_support import EXT_PKG
|
11
8
|
from metaflow.metadata_provider import MetaDatum
|
12
9
|
from metaflow.metaflow_environment import InvalidEnvironmentException
|
13
|
-
from metaflow.
|
14
|
-
|
15
|
-
from ...info_file import INFO_FILE
|
10
|
+
from metaflow.packaging_sys import ContentType
|
16
11
|
|
17
12
|
|
18
13
|
class CondaStepDecorator(StepDecorator):
|
@@ -45,26 +40,31 @@ class CondaStepDecorator(StepDecorator):
|
|
45
40
|
"python": None,
|
46
41
|
"disabled": None,
|
47
42
|
}
|
43
|
+
|
44
|
+
_metaflow_home = None
|
45
|
+
_addl_env_vars = None
|
46
|
+
|
48
47
|
# To define conda channels for the whole solve, users can specify
|
49
48
|
# CONDA_CHANNELS in their environment. For pinning specific packages to specific
|
50
49
|
# conda channels, users can specify channel::package as the package name.
|
51
50
|
|
52
|
-
def __init__(self, attributes=None, statically_defined=False):
|
51
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
53
52
|
self._attributes_with_user_values = (
|
54
53
|
set(attributes.keys()) if attributes is not None else set()
|
55
54
|
)
|
56
55
|
|
57
|
-
super(CondaStepDecorator, self).__init__(
|
56
|
+
super(CondaStepDecorator, self).__init__(
|
57
|
+
attributes, statically_defined, inserted_by
|
58
|
+
)
|
58
59
|
|
59
60
|
def init(self):
|
60
|
-
super(CondaStepDecorator, self).init()
|
61
|
-
|
62
61
|
# Support legacy 'libraries=' attribute for the decorator.
|
63
62
|
self.attributes["packages"] = {
|
64
63
|
**self.attributes["libraries"],
|
65
64
|
**self.attributes["packages"],
|
66
65
|
}
|
67
|
-
|
66
|
+
# Keep because otherwise make_decorator_spec will fail
|
67
|
+
self.attributes["libraries"] = {}
|
68
68
|
if self.attributes["packages"]:
|
69
69
|
self._attributes_with_user_values.add("packages")
|
70
70
|
|
@@ -152,67 +152,17 @@ class CondaStepDecorator(StepDecorator):
|
|
152
152
|
def runtime_init(self, flow, graph, package, run_id):
|
153
153
|
if self.disabled:
|
154
154
|
return
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
os.symlink(
|
166
|
-
info, os.path.join(self.metaflow_dir.name, os.path.basename(INFO_FILE))
|
155
|
+
# We need to make all the code package available to the user code in
|
156
|
+
# a temporary directory which will be added to the PYTHONPATH.
|
157
|
+
if self.__class__._metaflow_home is None:
|
158
|
+
# Do this ONCE per flow
|
159
|
+
self.__class__._metaflow_home = tempfile.TemporaryDirectory(dir="/tmp")
|
160
|
+
package.extract_into(
|
161
|
+
self.__class__._metaflow_home.name, ContentType.ALL_CONTENT
|
162
|
+
)
|
163
|
+
self.__class__._addl_env_vars = package.get_post_extract_env_vars(
|
164
|
+
package.package_metadata, self.__class__._metaflow_home.name
|
167
165
|
)
|
168
|
-
else:
|
169
|
-
# If there is no info file, we will actually create one in this new
|
170
|
-
# place because we won't be able to properly resolve the EXT_PKG extensions
|
171
|
-
# the same way as outside conda (looking at distributions, etc.). In a
|
172
|
-
# Conda environment, as shown below (where we set self.addl_paths), all
|
173
|
-
# EXT_PKG extensions are PYTHONPATH extensions. Instead of re-resolving,
|
174
|
-
# we use the resolved information that is written out to the INFO file.
|
175
|
-
with open(
|
176
|
-
os.path.join(self.metaflow_dir.name, os.path.basename(INFO_FILE)),
|
177
|
-
mode="wt",
|
178
|
-
encoding="utf-8",
|
179
|
-
) as f:
|
180
|
-
f.write(
|
181
|
-
json.dumps(
|
182
|
-
self.environment.get_environment_info(include_ext_info=True)
|
183
|
-
)
|
184
|
-
)
|
185
|
-
|
186
|
-
# Support metaflow extensions.
|
187
|
-
self.addl_paths = None
|
188
|
-
try:
|
189
|
-
m = importlib.import_module(EXT_PKG)
|
190
|
-
except ImportError:
|
191
|
-
# No additional check needed because if we are here, we already checked
|
192
|
-
# for other issues when loading at the toplevel.
|
193
|
-
pass
|
194
|
-
else:
|
195
|
-
custom_paths = list(set(m.__path__))
|
196
|
-
# For some reason, at times, unique paths appear multiple times. We
|
197
|
-
# simplify to avoid un-necessary links.
|
198
|
-
|
199
|
-
if len(custom_paths) == 1:
|
200
|
-
# Regular package; we take a quick shortcut here.
|
201
|
-
os.symlink(
|
202
|
-
custom_paths[0],
|
203
|
-
os.path.join(self.metaflow_dir.name, EXT_PKG),
|
204
|
-
)
|
205
|
-
else:
|
206
|
-
# This is a namespace package, we therefore create a bunch of
|
207
|
-
# directories so that we can symlink in those separately, and we will
|
208
|
-
# add those paths to the PYTHONPATH for the interpreter. Note that we
|
209
|
-
# don't symlink to the parent of the package because that could end up
|
210
|
-
# including more stuff we don't want
|
211
|
-
self.addl_paths = []
|
212
|
-
for p in custom_paths:
|
213
|
-
temp_dir = tempfile.mkdtemp(dir=self.metaflow_dir.name)
|
214
|
-
os.symlink(p, os.path.join(temp_dir, EXT_PKG))
|
215
|
-
self.addl_paths.append(temp_dir)
|
216
166
|
|
217
167
|
# # Also install any environment escape overrides directly here to enable
|
218
168
|
# # the escape to work even in non metaflow-created subprocesses
|
@@ -291,11 +241,15 @@ class CondaStepDecorator(StepDecorator):
|
|
291
241
|
if self.disabled:
|
292
242
|
return
|
293
243
|
# Ensure local installation of Metaflow is visible to user code
|
294
|
-
python_path = self.
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
244
|
+
python_path = self.__class__._metaflow_home.name
|
245
|
+
addl_env_vars = {}
|
246
|
+
if self.__class__._addl_env_vars is not None:
|
247
|
+
for key, value in self.__class__._addl_env_vars.items():
|
248
|
+
if key == "PYTHONPATH":
|
249
|
+
addl_env_vars[key] = os.pathsep.join([value, python_path])
|
250
|
+
else:
|
251
|
+
addl_env_vars[key] = value
|
252
|
+
cli_args.env.update(addl_env_vars)
|
299
253
|
if self.interpreter:
|
300
254
|
# https://github.com/conda/conda/issues/7707
|
301
255
|
# Also ref - https://github.com/Netflix/metaflow/pull/178
|
@@ -306,7 +260,9 @@ class CondaStepDecorator(StepDecorator):
|
|
306
260
|
def runtime_finished(self, exception):
|
307
261
|
if self.disabled:
|
308
262
|
return
|
309
|
-
self.
|
263
|
+
if self.__class__._metaflow_home is not None:
|
264
|
+
self.__class__._metaflow_home.cleanup()
|
265
|
+
self.__class__._metaflow_home = None
|
310
266
|
|
311
267
|
|
312
268
|
class CondaFlowDecorator(FlowDecorator):
|
@@ -339,22 +295,23 @@ class CondaFlowDecorator(FlowDecorator):
|
|
339
295
|
"disabled": None,
|
340
296
|
}
|
341
297
|
|
342
|
-
def __init__(self, attributes=None, statically_defined=False):
|
298
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
343
299
|
self._attributes_with_user_values = (
|
344
300
|
set(attributes.keys()) if attributes is not None else set()
|
345
301
|
)
|
346
302
|
|
347
|
-
super(CondaFlowDecorator, self).__init__(
|
303
|
+
super(CondaFlowDecorator, self).__init__(
|
304
|
+
attributes, statically_defined, inserted_by
|
305
|
+
)
|
348
306
|
|
349
307
|
def init(self):
|
350
|
-
super(CondaFlowDecorator, self).init()
|
351
|
-
|
352
308
|
# Support legacy 'libraries=' attribute for the decorator.
|
353
309
|
self.attributes["packages"] = {
|
354
310
|
**self.attributes["libraries"],
|
355
311
|
**self.attributes["packages"],
|
356
312
|
}
|
357
|
-
|
313
|
+
# Keep because otherwise make_decorator_spec will fail
|
314
|
+
self.attributes["libraries"] = {}
|
358
315
|
if self.attributes["python"]:
|
359
316
|
self.attributes["python"] = str(self.attributes["python"])
|
360
317
|
|
@@ -17,6 +17,7 @@ from metaflow.debug import debug
|
|
17
17
|
from metaflow.exception import MetaflowException
|
18
18
|
from metaflow.metaflow_config import get_pinned_conda_libs
|
19
19
|
from metaflow.metaflow_environment import MetaflowEnvironment
|
20
|
+
from metaflow.packaging_sys import ContentType
|
20
21
|
|
21
22
|
from . import MAGIC_FILE, _datastore_packageroot
|
22
23
|
from .utils import conda_platform
|
@@ -35,6 +36,7 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
35
36
|
_force_rebuild = False
|
36
37
|
|
37
38
|
def __init__(self, flow):
|
39
|
+
super().__init__(flow)
|
38
40
|
self.flow = flow
|
39
41
|
|
40
42
|
def set_local_root(self, local_root):
|
@@ -335,7 +337,7 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
335
337
|
environment[decorator.name] = {
|
336
338
|
k: copy.deepcopy(decorator.attributes[k])
|
337
339
|
for k in decorator.attributes
|
338
|
-
if k
|
340
|
+
if k not in ("disabled", "libraries")
|
339
341
|
}
|
340
342
|
else:
|
341
343
|
return {}
|
@@ -474,7 +476,9 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
474
476
|
files = []
|
475
477
|
manifest = self.get_environment_manifest_path()
|
476
478
|
if os.path.exists(manifest):
|
477
|
-
files.append(
|
479
|
+
files.append(
|
480
|
+
(manifest, os.path.basename(manifest), ContentType.OTHER_CONTENT)
|
481
|
+
)
|
478
482
|
return files
|
479
483
|
|
480
484
|
def bootstrap_commands(self, step_name, datastore_type):
|
@@ -24,12 +24,12 @@ class PyPIStepDecorator(StepDecorator):
|
|
24
24
|
name = "pypi"
|
25
25
|
defaults = {"packages": {}, "python": None, "disabled": None} # wheels
|
26
26
|
|
27
|
-
def __init__(self, attributes=None, statically_defined=False):
|
27
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
28
28
|
self._attributes_with_user_values = (
|
29
29
|
set(attributes.keys()) if attributes is not None else set()
|
30
30
|
)
|
31
31
|
|
32
|
-
super().__init__(attributes, statically_defined)
|
32
|
+
super().__init__(attributes, statically_defined, inserted_by)
|
33
33
|
|
34
34
|
def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
|
35
35
|
# The init_environment hook for Environment creates the relevant virtual
|
@@ -128,12 +128,12 @@ class PyPIFlowDecorator(FlowDecorator):
|
|
128
128
|
name = "pypi_base"
|
129
129
|
defaults = {"packages": {}, "python": None, "disabled": None}
|
130
130
|
|
131
|
-
def __init__(self, attributes=None, statically_defined=False):
|
131
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
132
132
|
self._attributes_with_user_values = (
|
133
133
|
set(attributes.keys()) if attributes is not None else set()
|
134
134
|
)
|
135
135
|
|
136
|
-
super().__init__(attributes, statically_defined)
|
136
|
+
super().__init__(attributes, statically_defined, inserted_by)
|
137
137
|
|
138
138
|
def flow_init(
|
139
139
|
self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
|
@@ -56,9 +56,9 @@ class InternalTestUnboundedForeachDecorator(StepDecorator):
|
|
56
56
|
name = "unbounded_test_foreach_internal"
|
57
57
|
results_dict = {}
|
58
58
|
|
59
|
-
def __init__(self, attributes=None, statically_defined=False):
|
59
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
60
60
|
super(InternalTestUnboundedForeachDecorator, self).__init__(
|
61
|
-
attributes, statically_defined
|
61
|
+
attributes, statically_defined, inserted_by
|
62
62
|
)
|
63
63
|
|
64
64
|
def step_init(
|
@@ -38,7 +38,6 @@ class TimeoutDecorator(StepDecorator):
|
|
38
38
|
defaults = {"seconds": 0, "minutes": 0, "hours": 0}
|
39
39
|
|
40
40
|
def init(self):
|
41
|
-
super().init()
|
42
41
|
# Initialize secs in __init__ so other decorators could safely use this
|
43
42
|
# value without worrying about decorator order.
|
44
43
|
# Convert values in attributes to type:int since they can be type:str
|
metaflow/plugins/uv/bootstrap.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
import shutil
|
2
3
|
import subprocess
|
3
4
|
import sys
|
4
5
|
import time
|
@@ -6,6 +7,7 @@ import time
|
|
6
7
|
from metaflow.util import which
|
7
8
|
from metaflow.info_file import read_info_file
|
8
9
|
from metaflow.metaflow_config import get_pinned_conda_libs
|
10
|
+
from metaflow.packaging_sys import MetaflowCodeContent, ContentType
|
9
11
|
from urllib.request import Request, urlopen
|
10
12
|
from urllib.error import URLError
|
11
13
|
|
@@ -93,6 +95,15 @@ if __name__ == "__main__":
|
|
93
95
|
return skip_pkgs
|
94
96
|
|
95
97
|
def sync_uv_project(datastore_type):
|
98
|
+
# Move the files to the current directory so uv can find them.
|
99
|
+
for filename in ["uv.lock", "pyproject.toml"]:
|
100
|
+
path_to_file = MetaflowCodeContent.get_filename(
|
101
|
+
filename, ContentType.OTHER_CONTENT
|
102
|
+
)
|
103
|
+
if path_to_file is None:
|
104
|
+
raise RuntimeError(f"Could not find {filename} in the package.")
|
105
|
+
shutil.move(path_to_file, os.path.join(os.getcwd(), filename))
|
106
|
+
|
96
107
|
print("Syncing uv project...")
|
97
108
|
dependencies = " ".join(get_dependencies(datastore_type))
|
98
109
|
skip_pkgs = " ".join(
|
@@ -2,6 +2,7 @@ import os
|
|
2
2
|
|
3
3
|
from metaflow.exception import MetaflowException
|
4
4
|
from metaflow.metaflow_environment import MetaflowEnvironment
|
5
|
+
from metaflow.packaging_sys import ContentType
|
5
6
|
|
6
7
|
|
7
8
|
class UVException(MetaflowException):
|
@@ -12,6 +13,7 @@ class UVEnvironment(MetaflowEnvironment):
|
|
12
13
|
TYPE = "uv"
|
13
14
|
|
14
15
|
def __init__(self, flow):
|
16
|
+
super().__init__(flow)
|
15
17
|
self.flow = flow
|
16
18
|
|
17
19
|
def validate_environment(self, logger, datastore_type):
|
@@ -43,8 +45,8 @@ class UVEnvironment(MetaflowEnvironment):
|
|
43
45
|
pyproject_path = _find("pyproject.toml")
|
44
46
|
uv_lock_path = _find("uv.lock")
|
45
47
|
files = [
|
46
|
-
(uv_lock_path, "uv.lock"),
|
47
|
-
(pyproject_path, "pyproject.toml"),
|
48
|
+
(uv_lock_path, "uv.lock", ContentType.OTHER_CONTENT),
|
49
|
+
(pyproject_path, "pyproject.toml", ContentType.OTHER_CONTENT),
|
48
50
|
]
|
49
51
|
return files
|
50
52
|
|
metaflow/pylint_wrapper.py
CHANGED
@@ -28,7 +28,11 @@ class PyLint(object):
|
|
28
28
|
return self._run is not None
|
29
29
|
|
30
30
|
def run(self, logger=None, warnings=False, pylint_config=[]):
|
31
|
-
args = [
|
31
|
+
args = [
|
32
|
+
self._fname,
|
33
|
+
"--signature-mutators",
|
34
|
+
"metaflow.user_decorators.user_step_decorator.user_step_decorator",
|
35
|
+
]
|
32
36
|
if not warnings:
|
33
37
|
args.append("--errors-only")
|
34
38
|
if pylint_config:
|
metaflow/runner/click_api.py
CHANGED
@@ -46,7 +46,6 @@ from metaflow.exception import MetaflowException
|
|
46
46
|
from metaflow.includefile import FilePathClass
|
47
47
|
from metaflow.metaflow_config import CLICK_API_PROCESS_CONFIG
|
48
48
|
from metaflow.parameters import JSONTypeClass, flow_context
|
49
|
-
from metaflow.user_configs.config_decorators import CustomFlowDecorator
|
50
49
|
from metaflow.user_configs.config_options import (
|
51
50
|
ConfigValue,
|
52
51
|
ConvertDictOrStr,
|
@@ -55,6 +54,7 @@ from metaflow.user_configs.config_options import (
|
|
55
54
|
MultipleTuple,
|
56
55
|
config_options_with_config_input,
|
57
56
|
)
|
57
|
+
from metaflow.user_decorators.user_flow_decorator import FlowMutator
|
58
58
|
|
59
59
|
# Define a recursive type alias for JSON
|
60
60
|
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
|
@@ -264,12 +264,12 @@ def extract_flow_class_from_file(flow_file: str) -> FlowSpec:
|
|
264
264
|
loaded_modules[flow_file] = module
|
265
265
|
|
266
266
|
classes = inspect.getmembers(
|
267
|
-
module, lambda x: inspect.isclass(x) or isinstance(x,
|
267
|
+
module, lambda x: inspect.isclass(x) or isinstance(x, FlowMutator)
|
268
268
|
)
|
269
269
|
flow_cls = None
|
270
270
|
|
271
271
|
for _, kls in classes:
|
272
|
-
if isinstance(kls,
|
272
|
+
if isinstance(kls, FlowMutator):
|
273
273
|
kls = kls._flow_cls
|
274
274
|
if (
|
275
275
|
kls is not FlowSpec
|
@@ -512,7 +512,7 @@ class MetaflowAPI(object):
|
|
512
512
|
|
513
513
|
# At this point, we are like in start() in cli.py -- we obtained the
|
514
514
|
# properly processed config_options which we can now use to process
|
515
|
-
# the config decorators (including
|
515
|
+
# the config decorators (including StepMutator/FlowMutator)
|
516
516
|
# Note that if CLICK_API_PROCESS_CONFIG is False, we still do this because
|
517
517
|
# it will init all parameters (config_options will be None)
|
518
518
|
# We ignore any errors if we don't check the configs in the click API.
|
@@ -658,6 +658,7 @@ if __name__ == "__main__":
|
|
658
658
|
.kubernetes()
|
659
659
|
.step(
|
660
660
|
step_name="process",
|
661
|
+
code_package_metadata="some_version",
|
661
662
|
code_package_sha="some_sha",
|
662
663
|
code_package_url="some_url",
|
663
664
|
)
|
@@ -9,6 +9,8 @@ import tempfile
|
|
9
9
|
import threading
|
10
10
|
from typing import Callable, Dict, Iterator, List, Optional, Tuple
|
11
11
|
|
12
|
+
from metaflow.packaging_sys import MetaflowCodeContent
|
13
|
+
from metaflow.util import get_metaflow_root
|
12
14
|
from .utils import check_process_exited
|
13
15
|
|
14
16
|
|
@@ -150,7 +152,12 @@ class SubprocessManager(object):
|
|
150
152
|
int
|
151
153
|
The process ID of the subprocess.
|
152
154
|
"""
|
153
|
-
|
155
|
+
updated_env = MetaflowCodeContent.get_env_vars_for_packaged_metaflow(
|
156
|
+
get_metaflow_root()
|
157
|
+
)
|
158
|
+
if updated_env:
|
159
|
+
env = env or {}
|
160
|
+
env.update(updated_env)
|
154
161
|
command_obj = CommandManager(command, env, cwd)
|
155
162
|
pid = command_obj.run(show_output=show_output)
|
156
163
|
self.commands[pid] = command_obj
|
@@ -181,7 +188,12 @@ class SubprocessManager(object):
|
|
181
188
|
int
|
182
189
|
The process ID of the subprocess.
|
183
190
|
"""
|
184
|
-
|
191
|
+
updated_env = MetaflowCodeContent.get_env_vars_for_packaged_metaflow(
|
192
|
+
get_metaflow_root()
|
193
|
+
)
|
194
|
+
if updated_env:
|
195
|
+
env = env or {}
|
196
|
+
env.update(updated_env)
|
185
197
|
command_obj = CommandManager(command, env, cwd)
|
186
198
|
pid = await command_obj.async_run()
|
187
199
|
self.commands[pid] = command_obj
|
metaflow/runtime.py
CHANGED
@@ -16,6 +16,7 @@ import time
|
|
16
16
|
import subprocess
|
17
17
|
from datetime import datetime
|
18
18
|
from io import BytesIO
|
19
|
+
from itertools import chain
|
19
20
|
from functools import partial
|
20
21
|
from concurrent import futures
|
21
22
|
|
@@ -24,7 +25,7 @@ from contextlib import contextmanager
|
|
24
25
|
|
25
26
|
from . import get_namespace
|
26
27
|
from .metadata_provider import MetaDatum
|
27
|
-
from .metaflow_config import MAX_ATTEMPTS, UI_URL
|
28
|
+
from .metaflow_config import FEAT_ALWAYS_UPLOAD_CODE_PACKAGE, MAX_ATTEMPTS, UI_URL
|
28
29
|
from .exception import (
|
29
30
|
MetaflowException,
|
30
31
|
MetaflowInternalError,
|
@@ -95,6 +96,7 @@ class NativeRuntime(object):
|
|
95
96
|
max_num_splits=MAX_NUM_SPLITS,
|
96
97
|
max_log_size=MAX_LOG_SIZE,
|
97
98
|
resume_identifier=None,
|
99
|
+
skip_decorator_hooks=False,
|
98
100
|
):
|
99
101
|
if run_id is None:
|
100
102
|
self._run_id = metadata.new_run_id()
|
@@ -107,6 +109,7 @@ class NativeRuntime(object):
|
|
107
109
|
self._flow_datastore = flow_datastore
|
108
110
|
self._metadata = metadata
|
109
111
|
self._environment = environment
|
112
|
+
self._package = package
|
110
113
|
self._logger = logger
|
111
114
|
self._max_workers = max_workers
|
112
115
|
self._active_tasks = dict() # Key: step name;
|
@@ -128,6 +131,7 @@ class NativeRuntime(object):
|
|
128
131
|
self._ran_or_scheduled_task_index = set()
|
129
132
|
self._reentrant = reentrant
|
130
133
|
self._run_url = None
|
134
|
+
self._skip_decorator_hooks = skip_decorator_hooks
|
131
135
|
|
132
136
|
# If steps_to_rerun is specified, we will not clone them in resume mode.
|
133
137
|
self._steps_to_rerun = steps_to_rerun or {}
|
@@ -179,9 +183,10 @@ class NativeRuntime(object):
|
|
179
183
|
# finished.
|
180
184
|
self._control_num_splits = {} # control_task -> num_splits mapping
|
181
185
|
|
182
|
-
|
183
|
-
for
|
184
|
-
deco
|
186
|
+
if not self._skip_decorator_hooks:
|
187
|
+
for step in flow:
|
188
|
+
for deco in step.decorators:
|
189
|
+
deco.runtime_init(flow, graph, package, self._run_id)
|
185
190
|
|
186
191
|
def _new_task(self, step, input_paths=None, **kwargs):
|
187
192
|
if input_paths is None:
|
@@ -192,7 +197,7 @@ class NativeRuntime(object):
|
|
192
197
|
if step in self._steps_to_rerun:
|
193
198
|
may_clone = False
|
194
199
|
|
195
|
-
if step == "_parameters":
|
200
|
+
if step == "_parameters" or self._skip_decorator_hooks:
|
196
201
|
decos = []
|
197
202
|
else:
|
198
203
|
decos = getattr(self._flow, step).decorators
|
@@ -566,9 +571,10 @@ class NativeRuntime(object):
|
|
566
571
|
raise
|
567
572
|
finally:
|
568
573
|
# on finish clean tasks
|
569
|
-
|
570
|
-
for
|
571
|
-
deco.
|
574
|
+
if not self._skip_decorator_hooks:
|
575
|
+
for step in self._flow:
|
576
|
+
for deco in step.decorators:
|
577
|
+
deco.runtime_finished(exception)
|
572
578
|
self._run_exit_hooks()
|
573
579
|
|
574
580
|
# assert that end was executed and it was successful
|
@@ -667,7 +673,6 @@ class NativeRuntime(object):
|
|
667
673
|
# Given the current task information (task_index), the type of transition,
|
668
674
|
# and the split index, return the new task index.
|
669
675
|
def _translate_index(self, task, next_step, type, split_index=None):
|
670
|
-
|
671
676
|
match = re.match(r"^(.+)\[(.*)\]$", task.task_index)
|
672
677
|
if match:
|
673
678
|
_, foreach_index = match.groups()
|
@@ -1005,6 +1010,22 @@ class NativeRuntime(object):
|
|
1005
1010
|
# Initialize the task (which can be expensive using remote datastores)
|
1006
1011
|
# before launching the worker so that cost is amortized over time, instead
|
1007
1012
|
# of doing it during _queue_push.
|
1013
|
+
if (
|
1014
|
+
FEAT_ALWAYS_UPLOAD_CODE_PACKAGE
|
1015
|
+
and "METAFLOW_CODE_SHA" not in os.environ
|
1016
|
+
):
|
1017
|
+
# We check if the code package is uploaded and, if so, we set the
|
1018
|
+
# environment variables that will cause the metadata service to
|
1019
|
+
# register the code package with the task created in _new_task below
|
1020
|
+
code_sha = self._package.package_sha(timeout=0.01)
|
1021
|
+
if code_sha:
|
1022
|
+
os.environ["METAFLOW_CODE_SHA"] = code_sha
|
1023
|
+
os.environ["METAFLOW_CODE_URL"] = self._package.package_url()
|
1024
|
+
os.environ["METAFLOW_CODE_DS"] = self._flow_datastore.TYPE
|
1025
|
+
os.environ["METAFLOW_CODE_METADATA"] = (
|
1026
|
+
self._package.package_metadata
|
1027
|
+
)
|
1028
|
+
|
1008
1029
|
task = self._new_task(step, **task_kwargs)
|
1009
1030
|
self._launch_worker(task)
|
1010
1031
|
|
@@ -1556,6 +1577,7 @@ class CLIArgs(object):
|
|
1556
1577
|
def __init__(self, task):
|
1557
1578
|
self.task = task
|
1558
1579
|
self.entrypoint = list(task.entrypoint)
|
1580
|
+
step_obj = getattr(self.task.flow, self.task.step)
|
1559
1581
|
self.top_level_options = {
|
1560
1582
|
"quiet": True,
|
1561
1583
|
"metadata": self.task.metadata_type,
|
@@ -1567,8 +1589,12 @@ class CLIArgs(object):
|
|
1567
1589
|
"datastore-root": self.task.datastore_sysroot,
|
1568
1590
|
"with": [
|
1569
1591
|
deco.make_decorator_spec()
|
1570
|
-
for deco in
|
1571
|
-
|
1592
|
+
for deco in chain(
|
1593
|
+
self.task.decos,
|
1594
|
+
step_obj.wrappers,
|
1595
|
+
step_obj.config_decorators,
|
1596
|
+
)
|
1597
|
+
if not deco.statically_defined and deco.inserted_by is None
|
1572
1598
|
],
|
1573
1599
|
}
|
1574
1600
|
|