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.

Files changed (93) hide show
  1. metaflow/__init__.py +7 -1
  2. metaflow/_vendor/imghdr/__init__.py +180 -0
  3. metaflow/cli.py +16 -1
  4. metaflow/cli_components/init_cmd.py +1 -0
  5. metaflow/cli_components/run_cmds.py +6 -2
  6. metaflow/client/core.py +22 -30
  7. metaflow/cmd/develop/stub_generator.py +19 -2
  8. metaflow/datastore/task_datastore.py +0 -1
  9. metaflow/debug.py +5 -0
  10. metaflow/decorators.py +230 -70
  11. metaflow/extension_support/__init__.py +15 -8
  12. metaflow/extension_support/_empty_file.py +2 -2
  13. metaflow/flowspec.py +80 -53
  14. metaflow/graph.py +24 -2
  15. metaflow/meta_files.py +13 -0
  16. metaflow/metadata_provider/metadata.py +7 -1
  17. metaflow/metaflow_config.py +5 -0
  18. metaflow/metaflow_environment.py +82 -25
  19. metaflow/metaflow_version.py +1 -1
  20. metaflow/package/__init__.py +664 -0
  21. metaflow/packaging_sys/__init__.py +870 -0
  22. metaflow/packaging_sys/backend.py +113 -0
  23. metaflow/packaging_sys/distribution_support.py +153 -0
  24. metaflow/packaging_sys/tar_backend.py +86 -0
  25. metaflow/packaging_sys/utils.py +91 -0
  26. metaflow/packaging_sys/v1.py +476 -0
  27. metaflow/plugins/__init__.py +3 -0
  28. metaflow/plugins/airflow/airflow.py +11 -1
  29. metaflow/plugins/airflow/airflow_cli.py +15 -4
  30. metaflow/plugins/argo/argo_workflows.py +346 -301
  31. metaflow/plugins/argo/argo_workflows_cli.py +16 -4
  32. metaflow/plugins/argo/exit_hooks.py +209 -0
  33. metaflow/plugins/aws/aws_utils.py +1 -1
  34. metaflow/plugins/aws/batch/batch.py +22 -3
  35. metaflow/plugins/aws/batch/batch_cli.py +3 -0
  36. metaflow/plugins/aws/batch/batch_decorator.py +13 -5
  37. metaflow/plugins/aws/step_functions/step_functions.py +10 -1
  38. metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
  39. metaflow/plugins/cards/card_cli.py +20 -1
  40. metaflow/plugins/cards/card_creator.py +24 -1
  41. metaflow/plugins/cards/card_decorator.py +57 -6
  42. metaflow/plugins/cards/card_modules/convert_to_native_type.py +5 -2
  43. metaflow/plugins/cards/card_modules/test_cards.py +16 -0
  44. metaflow/plugins/cards/metadata.py +22 -0
  45. metaflow/plugins/exit_hook/__init__.py +0 -0
  46. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  47. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  48. metaflow/plugins/kubernetes/kubernetes.py +8 -1
  49. metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
  50. metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
  51. metaflow/plugins/package_cli.py +25 -23
  52. metaflow/plugins/parallel_decorator.py +4 -2
  53. metaflow/plugins/pypi/bootstrap.py +8 -2
  54. metaflow/plugins/pypi/conda_decorator.py +39 -82
  55. metaflow/plugins/pypi/conda_environment.py +6 -2
  56. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  57. metaflow/plugins/secrets/__init__.py +3 -0
  58. metaflow/plugins/secrets/secrets_decorator.py +9 -173
  59. metaflow/plugins/secrets/secrets_func.py +49 -0
  60. metaflow/plugins/secrets/secrets_spec.py +101 -0
  61. metaflow/plugins/secrets/utils.py +74 -0
  62. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  63. metaflow/plugins/timeout_decorator.py +0 -1
  64. metaflow/plugins/uv/bootstrap.py +11 -0
  65. metaflow/plugins/uv/uv_environment.py +4 -2
  66. metaflow/pylint_wrapper.py +5 -1
  67. metaflow/runner/click_api.py +5 -4
  68. metaflow/runner/metaflow_runner.py +16 -1
  69. metaflow/runner/subprocess_manager.py +14 -2
  70. metaflow/runtime.py +82 -11
  71. metaflow/task.py +91 -7
  72. metaflow/user_configs/config_options.py +13 -8
  73. metaflow/user_configs/config_parameters.py +0 -4
  74. metaflow/user_decorators/__init__.py +0 -0
  75. metaflow/user_decorators/common.py +144 -0
  76. metaflow/user_decorators/mutable_flow.py +499 -0
  77. metaflow/user_decorators/mutable_step.py +424 -0
  78. metaflow/user_decorators/user_flow_decorator.py +263 -0
  79. metaflow/user_decorators/user_step_decorator.py +712 -0
  80. metaflow/util.py +4 -1
  81. metaflow/version.py +1 -1
  82. {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/Tiltfile +27 -2
  83. {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/METADATA +2 -2
  84. {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/RECORD +90 -70
  85. metaflow/info_file.py +0 -25
  86. metaflow/package.py +0 -203
  87. metaflow/user_configs/config_decorators.py +0 -568
  88. {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/Makefile +0 -0
  89. {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  90. {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/WHEEL +0 -0
  91. {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/entry_points.txt +0 -0
  92. {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/licenses/LICENSE +0 -0
  93. {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(CardCreator(self._create_top_level_args(flow)))
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
- import imghdr
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
- cls.package_url, cls.package_sha = flow_datastore.save_data(
661
- [package.blob], len_hint=1
662
- )[0]
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
@@ -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, obj.environment, obj.echo, obj.package_suffixes
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 current code package.")
29
+ @package.command(help="Output information about the code package.")
21
30
  @click.pass_obj
22
31
  def info(obj):
23
- obj.echo("Status of the current working directory:", fg="magenta", bold=False)
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 " "(change with --package-suffixes):",
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 in a tar file")
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("Code package saved in *%s*." % path, fg="magenta", bold=False)
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__(attributes, statically_defined)
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, get_metaflow_root
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
- os.path.join(get_metaflow_root(), MAGIC_FILE),
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: