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.
Files changed (74) hide show
  1. metaflow/__init__.py +7 -1
  2. metaflow/cli.py +16 -1
  3. metaflow/cli_components/init_cmd.py +1 -0
  4. metaflow/cli_components/run_cmds.py +6 -2
  5. metaflow/client/core.py +22 -30
  6. metaflow/datastore/task_datastore.py +0 -1
  7. metaflow/debug.py +5 -0
  8. metaflow/decorators.py +230 -70
  9. metaflow/extension_support/__init__.py +15 -8
  10. metaflow/extension_support/_empty_file.py +2 -2
  11. metaflow/flowspec.py +80 -53
  12. metaflow/graph.py +24 -2
  13. metaflow/meta_files.py +13 -0
  14. metaflow/metadata_provider/metadata.py +7 -1
  15. metaflow/metaflow_config.py +5 -0
  16. metaflow/metaflow_environment.py +82 -25
  17. metaflow/metaflow_version.py +1 -1
  18. metaflow/package/__init__.py +664 -0
  19. metaflow/packaging_sys/__init__.py +870 -0
  20. metaflow/packaging_sys/backend.py +113 -0
  21. metaflow/packaging_sys/distribution_support.py +153 -0
  22. metaflow/packaging_sys/tar_backend.py +86 -0
  23. metaflow/packaging_sys/utils.py +91 -0
  24. metaflow/packaging_sys/v1.py +476 -0
  25. metaflow/plugins/airflow/airflow.py +5 -1
  26. metaflow/plugins/airflow/airflow_cli.py +15 -4
  27. metaflow/plugins/argo/argo_workflows.py +23 -17
  28. metaflow/plugins/argo/argo_workflows_cli.py +16 -4
  29. metaflow/plugins/aws/batch/batch.py +22 -3
  30. metaflow/plugins/aws/batch/batch_cli.py +3 -0
  31. metaflow/plugins/aws/batch/batch_decorator.py +13 -5
  32. metaflow/plugins/aws/step_functions/step_functions.py +4 -1
  33. metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
  34. metaflow/plugins/cards/card_decorator.py +0 -5
  35. metaflow/plugins/kubernetes/kubernetes.py +8 -1
  36. metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
  37. metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
  38. metaflow/plugins/package_cli.py +25 -23
  39. metaflow/plugins/parallel_decorator.py +4 -2
  40. metaflow/plugins/pypi/bootstrap.py +8 -2
  41. metaflow/plugins/pypi/conda_decorator.py +39 -82
  42. metaflow/plugins/pypi/conda_environment.py +6 -2
  43. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  44. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  45. metaflow/plugins/timeout_decorator.py +0 -1
  46. metaflow/plugins/uv/bootstrap.py +11 -0
  47. metaflow/plugins/uv/uv_environment.py +4 -2
  48. metaflow/pylint_wrapper.py +5 -1
  49. metaflow/runner/click_api.py +5 -4
  50. metaflow/runner/subprocess_manager.py +14 -2
  51. metaflow/runtime.py +37 -11
  52. metaflow/task.py +91 -7
  53. metaflow/user_configs/config_options.py +13 -8
  54. metaflow/user_configs/config_parameters.py +0 -4
  55. metaflow/user_decorators/__init__.py +0 -0
  56. metaflow/user_decorators/common.py +144 -0
  57. metaflow/user_decorators/mutable_flow.py +499 -0
  58. metaflow/user_decorators/mutable_step.py +424 -0
  59. metaflow/user_decorators/user_flow_decorator.py +263 -0
  60. metaflow/user_decorators/user_step_decorator.py +712 -0
  61. metaflow/util.py +4 -1
  62. metaflow/version.py +1 -1
  63. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/METADATA +2 -2
  64. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/RECORD +71 -60
  65. metaflow/info_file.py +0 -25
  66. metaflow/package.py +0 -203
  67. metaflow/user_configs/config_decorators.py +0 -568
  68. {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Makefile +0 -0
  69. {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Tiltfile +0 -0
  70. {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  71. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/WHEEL +0 -0
  72. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/entry_points.txt +0 -0
  73. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/licenses/LICENSE +0 -0
  74. {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.util import get_metaflow_root
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__(attributes, statically_defined)
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
- del self.attributes["libraries"]
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
- # Create a symlink to metaflow installed outside the virtual environment.
156
- self.metaflow_dir = tempfile.TemporaryDirectory(dir="/tmp")
157
- os.symlink(
158
- os.path.join(get_metaflow_root(), "metaflow"),
159
- os.path.join(self.metaflow_dir.name, "metaflow"),
160
- )
161
-
162
- info = os.path.join(get_metaflow_root(), os.path.basename(INFO_FILE))
163
- # Symlink the INFO file as well to properly propagate down the Metaflow version
164
- if os.path.isfile(info):
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.metaflow_dir.name
295
- if self.addl_paths is not None:
296
- addl_paths = os.pathsep.join(self.addl_paths)
297
- python_path = os.pathsep.join([addl_paths, python_path])
298
- cli_args.env["PYTHONPATH"] = python_path
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.metaflow_dir.cleanup()
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__(attributes, statically_defined)
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
- del self.attributes["libraries"]
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 != "disabled"
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((manifest, os.path.basename(manifest)))
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
@@ -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
 
@@ -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 = [self._fname]
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:
@@ -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, CustomFlowDecorator)
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, CustomFlowDecorator):
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 CustomStep/FlowDecorators)
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
- for step in flow:
183
- for deco in step.decorators:
184
- deco.runtime_init(flow, graph, package, self._run_id)
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
- for step in self._flow:
570
- for deco in step.decorators:
571
- deco.runtime_finished(exception)
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 self.task.decos
1571
- if not deco.statically_defined
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