ob-metaflow 2.15.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__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 (169) hide show
  1. metaflow/__init__.py +10 -3
  2. metaflow/_vendor/imghdr/__init__.py +186 -0
  3. metaflow/_vendor/yaml/__init__.py +427 -0
  4. metaflow/_vendor/yaml/composer.py +139 -0
  5. metaflow/_vendor/yaml/constructor.py +748 -0
  6. metaflow/_vendor/yaml/cyaml.py +101 -0
  7. metaflow/_vendor/yaml/dumper.py +62 -0
  8. metaflow/_vendor/yaml/emitter.py +1137 -0
  9. metaflow/_vendor/yaml/error.py +75 -0
  10. metaflow/_vendor/yaml/events.py +86 -0
  11. metaflow/_vendor/yaml/loader.py +63 -0
  12. metaflow/_vendor/yaml/nodes.py +49 -0
  13. metaflow/_vendor/yaml/parser.py +589 -0
  14. metaflow/_vendor/yaml/reader.py +185 -0
  15. metaflow/_vendor/yaml/representer.py +389 -0
  16. metaflow/_vendor/yaml/resolver.py +227 -0
  17. metaflow/_vendor/yaml/scanner.py +1435 -0
  18. metaflow/_vendor/yaml/serializer.py +111 -0
  19. metaflow/_vendor/yaml/tokens.py +104 -0
  20. metaflow/cards.py +4 -0
  21. metaflow/cli.py +125 -21
  22. metaflow/cli_components/init_cmd.py +1 -0
  23. metaflow/cli_components/run_cmds.py +204 -40
  24. metaflow/cli_components/step_cmd.py +160 -4
  25. metaflow/client/__init__.py +1 -0
  26. metaflow/client/core.py +198 -130
  27. metaflow/client/filecache.py +59 -32
  28. metaflow/cmd/code/__init__.py +2 -1
  29. metaflow/cmd/develop/stub_generator.py +49 -18
  30. metaflow/cmd/develop/stubs.py +9 -27
  31. metaflow/cmd/make_wrapper.py +30 -0
  32. metaflow/datastore/__init__.py +1 -0
  33. metaflow/datastore/content_addressed_store.py +40 -9
  34. metaflow/datastore/datastore_set.py +10 -1
  35. metaflow/datastore/flow_datastore.py +124 -4
  36. metaflow/datastore/spin_datastore.py +91 -0
  37. metaflow/datastore/task_datastore.py +92 -6
  38. metaflow/debug.py +5 -0
  39. metaflow/decorators.py +331 -82
  40. metaflow/extension_support/__init__.py +414 -356
  41. metaflow/extension_support/_empty_file.py +2 -2
  42. metaflow/flowspec.py +322 -82
  43. metaflow/graph.py +178 -15
  44. metaflow/includefile.py +25 -3
  45. metaflow/lint.py +94 -3
  46. metaflow/meta_files.py +13 -0
  47. metaflow/metadata_provider/metadata.py +13 -2
  48. metaflow/metaflow_config.py +66 -4
  49. metaflow/metaflow_environment.py +91 -25
  50. metaflow/metaflow_profile.py +18 -0
  51. metaflow/metaflow_version.py +16 -1
  52. metaflow/package/__init__.py +673 -0
  53. metaflow/packaging_sys/__init__.py +880 -0
  54. metaflow/packaging_sys/backend.py +128 -0
  55. metaflow/packaging_sys/distribution_support.py +153 -0
  56. metaflow/packaging_sys/tar_backend.py +99 -0
  57. metaflow/packaging_sys/utils.py +54 -0
  58. metaflow/packaging_sys/v1.py +527 -0
  59. metaflow/parameters.py +6 -2
  60. metaflow/plugins/__init__.py +6 -0
  61. metaflow/plugins/airflow/airflow.py +11 -1
  62. metaflow/plugins/airflow/airflow_cli.py +16 -5
  63. metaflow/plugins/argo/argo_client.py +42 -20
  64. metaflow/plugins/argo/argo_events.py +6 -6
  65. metaflow/plugins/argo/argo_workflows.py +1023 -344
  66. metaflow/plugins/argo/argo_workflows_cli.py +396 -94
  67. metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
  68. metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
  69. metaflow/plugins/argo/capture_error.py +5 -2
  70. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  71. metaflow/plugins/argo/exit_hooks.py +209 -0
  72. metaflow/plugins/argo/param_val.py +19 -0
  73. metaflow/plugins/aws/aws_client.py +6 -0
  74. metaflow/plugins/aws/aws_utils.py +33 -1
  75. metaflow/plugins/aws/batch/batch.py +72 -5
  76. metaflow/plugins/aws/batch/batch_cli.py +24 -3
  77. metaflow/plugins/aws/batch/batch_decorator.py +57 -6
  78. metaflow/plugins/aws/step_functions/step_functions.py +28 -3
  79. metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
  80. metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
  81. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
  82. metaflow/plugins/cards/card_cli.py +20 -1
  83. metaflow/plugins/cards/card_creator.py +24 -1
  84. metaflow/plugins/cards/card_datastore.py +21 -49
  85. metaflow/plugins/cards/card_decorator.py +58 -6
  86. metaflow/plugins/cards/card_modules/basic.py +38 -9
  87. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  88. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  89. metaflow/plugins/cards/card_modules/components.py +592 -3
  90. metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
  91. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  92. metaflow/plugins/cards/card_modules/main.css +1 -0
  93. metaflow/plugins/cards/card_modules/main.js +56 -41
  94. metaflow/plugins/cards/card_modules/test_cards.py +22 -6
  95. metaflow/plugins/cards/component_serializer.py +1 -8
  96. metaflow/plugins/cards/metadata.py +22 -0
  97. metaflow/plugins/catch_decorator.py +9 -0
  98. metaflow/plugins/datastores/local_storage.py +12 -6
  99. metaflow/plugins/datastores/spin_storage.py +12 -0
  100. metaflow/plugins/datatools/s3/s3.py +49 -17
  101. metaflow/plugins/datatools/s3/s3op.py +113 -66
  102. metaflow/plugins/env_escape/client_modules.py +102 -72
  103. metaflow/plugins/events_decorator.py +127 -121
  104. metaflow/plugins/exit_hook/__init__.py +0 -0
  105. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  106. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  107. metaflow/plugins/kubernetes/kubernetes.py +12 -1
  108. metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
  109. metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
  110. metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
  111. metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
  112. metaflow/plugins/metadata_providers/local.py +76 -82
  113. metaflow/plugins/metadata_providers/service.py +13 -9
  114. metaflow/plugins/metadata_providers/spin.py +16 -0
  115. metaflow/plugins/package_cli.py +36 -24
  116. metaflow/plugins/parallel_decorator.py +11 -2
  117. metaflow/plugins/parsers.py +16 -0
  118. metaflow/plugins/pypi/bootstrap.py +7 -1
  119. metaflow/plugins/pypi/conda_decorator.py +41 -82
  120. metaflow/plugins/pypi/conda_environment.py +14 -6
  121. metaflow/plugins/pypi/micromamba.py +9 -1
  122. metaflow/plugins/pypi/pip.py +41 -5
  123. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  124. metaflow/plugins/pypi/utils.py +22 -0
  125. metaflow/plugins/secrets/__init__.py +3 -0
  126. metaflow/plugins/secrets/secrets_decorator.py +14 -178
  127. metaflow/plugins/secrets/secrets_func.py +49 -0
  128. metaflow/plugins/secrets/secrets_spec.py +101 -0
  129. metaflow/plugins/secrets/utils.py +74 -0
  130. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  131. metaflow/plugins/timeout_decorator.py +0 -1
  132. metaflow/plugins/uv/bootstrap.py +29 -1
  133. metaflow/plugins/uv/uv_environment.py +5 -3
  134. metaflow/pylint_wrapper.py +5 -1
  135. metaflow/runner/click_api.py +79 -26
  136. metaflow/runner/deployer.py +208 -6
  137. metaflow/runner/deployer_impl.py +32 -12
  138. metaflow/runner/metaflow_runner.py +266 -33
  139. metaflow/runner/subprocess_manager.py +21 -1
  140. metaflow/runner/utils.py +27 -16
  141. metaflow/runtime.py +660 -66
  142. metaflow/task.py +255 -26
  143. metaflow/user_configs/config_options.py +33 -21
  144. metaflow/user_configs/config_parameters.py +220 -58
  145. metaflow/user_decorators/__init__.py +0 -0
  146. metaflow/user_decorators/common.py +144 -0
  147. metaflow/user_decorators/mutable_flow.py +512 -0
  148. metaflow/user_decorators/mutable_step.py +424 -0
  149. metaflow/user_decorators/user_flow_decorator.py +264 -0
  150. metaflow/user_decorators/user_step_decorator.py +749 -0
  151. metaflow/util.py +197 -7
  152. metaflow/vendor.py +23 -7
  153. metaflow/version.py +1 -1
  154. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
  155. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
  156. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
  157. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
  158. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
  159. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  160. metaflow/_vendor/v3_5/__init__.py +0 -1
  161. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  162. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  163. metaflow/_vendor/v3_5/zipp.py +0 -329
  164. metaflow/info_file.py +0 -25
  165. metaflow/package.py +0 -203
  166. metaflow/user_configs/config_decorators.py +0 -568
  167. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
  168. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
  169. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from metaflow.metaflow_config import DATASTORE_LOCAL_DIR
12
12
  from metaflow.mflog import TASK_LOG_SOURCE
13
13
  from metaflow.unbounded_foreach import UBF_CONTROL, UBF_TASK
14
14
  from .batch import Batch, BatchKilledException
15
+ from ..aws_utils import validate_aws_tag
15
16
 
16
17
 
17
18
  @click.group()
@@ -47,7 +48,7 @@ def _execute_cmd(func, flow_name, run_id, user, my_runs, echo):
47
48
  func(flow_name, run_id, user, echo)
48
49
 
49
50
 
50
- @batch.command(help="List unfinished AWS Batch tasks of this flow")
51
+ @batch.command("list", help="List unfinished AWS Batch tasks of this flow")
51
52
  @click.option(
52
53
  "--my-runs",
53
54
  default=False,
@@ -61,7 +62,7 @@ def _execute_cmd(func, flow_name, run_id, user, my_runs, echo):
61
62
  help="List unfinished tasks corresponding to the run id.",
62
63
  )
63
64
  @click.pass_context
64
- def list(ctx, run_id, user, my_runs):
65
+ def _list(ctx, run_id, user, my_runs):
65
66
  batch = Batch(ctx.obj.metadata, ctx.obj.environment)
66
67
  _execute_cmd(
67
68
  batch.list_jobs, ctx.obj.flow.name, run_id, user, my_runs, ctx.obj.echo
@@ -100,6 +101,7 @@ def kill(ctx, run_id, user, my_runs):
100
101
  "Metaflow."
101
102
  )
102
103
  @click.argument("step-name")
104
+ @click.argument("code-package-metadata")
103
105
  @click.argument("code-package-sha")
104
106
  @click.argument("code-package-url")
105
107
  @click.option("--executable", help="Executable requirement for AWS Batch.")
@@ -146,6 +148,13 @@ def kill(ctx, run_id, user, my_runs):
146
148
  help="Activate designated number of elastic fabric adapter devices. "
147
149
  "EFA driver must be installed and instance type compatible with EFA",
148
150
  )
151
+ @click.option(
152
+ "--aws-batch-tag",
153
+ "aws_batch_tags",
154
+ multiple=True,
155
+ default=None,
156
+ help="AWS tags. Format: key=value, multiple allowed",
157
+ )
149
158
  @click.option("--use-tmpfs", is_flag=True, help="tmpfs requirement for AWS Batch.")
150
159
  @click.option("--tmpfs-tempdir", is_flag=True, help="tmpfs requirement for AWS Batch.")
151
160
  @click.option("--tmpfs-size", help="tmpfs requirement for AWS Batch.")
@@ -185,6 +194,7 @@ def kill(ctx, run_id, user, my_runs):
185
194
  def step(
186
195
  ctx,
187
196
  step_name,
197
+ code_package_metadata,
188
198
  code_package_sha,
189
199
  code_package_url,
190
200
  executable=None,
@@ -201,6 +211,7 @@ def step(
201
211
  swappiness=None,
202
212
  inferentia=None,
203
213
  efa=None,
214
+ aws_batch_tags=None,
204
215
  use_tmpfs=None,
205
216
  tmpfs_tempdir=None,
206
217
  tmpfs_size=None,
@@ -211,7 +222,7 @@ def step(
211
222
  log_driver=None,
212
223
  log_options=None,
213
224
  num_parallel=None,
214
- **kwargs,
225
+ **kwargs
215
226
  ):
216
227
  def echo(msg, stream="stderr", batch_id=None, **kwargs):
217
228
  msg = util.to_unicode(msg)
@@ -275,6 +286,14 @@ def step(
275
286
 
276
287
  env = {"METAFLOW_FLOW_FILENAME": os.path.basename(sys.argv[0])}
277
288
 
289
+ if aws_batch_tags is not None:
290
+ # We do not need to validate these again,
291
+ # as they come supplied by the batch decorator which already performed validation.
292
+ batch_tags = {}
293
+ for item in list(aws_batch_tags):
294
+ key, value = item.split("=")
295
+ batch_tags[key] = value
296
+
278
297
  env_deco = [deco for deco in node.decorators if deco.name == "environment"]
279
298
  if env_deco:
280
299
  env.update(env_deco[0].attributes["vars"])
@@ -317,6 +336,7 @@ def step(
317
336
  step_name,
318
337
  step_cli,
319
338
  task_spec,
339
+ code_package_metadata,
320
340
  code_package_sha,
321
341
  code_package_url,
322
342
  ctx.obj.flow_datastore.TYPE,
@@ -338,6 +358,7 @@ def step(
338
358
  host_volumes=host_volumes,
339
359
  efs_volumes=efs_volumes,
340
360
  use_tmpfs=use_tmpfs,
361
+ aws_batch_tags=batch_tags,
341
362
  tmpfs_tempdir=tmpfs_tempdir,
342
363
  tmpfs_size=tmpfs_size,
343
364
  tmpfs_path=tmpfs_path,
@@ -10,10 +10,12 @@ from metaflow.metadata_provider.util import sync_local_metadata_to_datastore
10
10
  from metaflow.metaflow_config import (
11
11
  BATCH_CONTAINER_IMAGE,
12
12
  BATCH_CONTAINER_REGISTRY,
13
+ BATCH_DEFAULT_TAGS,
13
14
  BATCH_JOB_QUEUE,
14
15
  DATASTORE_LOCAL_DIR,
15
16
  ECS_FARGATE_EXECUTION_ROLE,
16
17
  ECS_S3_ACCESS_IAM_ROLE,
18
+ FEAT_ALWAYS_UPLOAD_CODE_PACKAGE,
17
19
  )
18
20
  from metaflow.plugins.timeout_decorator import get_run_time_limit_for_task
19
21
  from metaflow.sidecar import Sidecar
@@ -23,6 +25,7 @@ from ..aws_utils import (
23
25
  compute_resource_attributes,
24
26
  get_docker_registry,
25
27
  get_ec2_instance_metadata,
28
+ validate_aws_tag,
26
29
  )
27
30
  from .batch import BatchException
28
31
 
@@ -67,6 +70,9 @@ class BatchDecorator(StepDecorator):
67
70
  A swappiness value of 0 causes swapping not to happen unless absolutely
68
71
  necessary. A swappiness value of 100 causes pages to be swapped very
69
72
  aggressively. Accepted values are whole numbers between 0 and 100.
73
+ aws_batch_tags: Dict[str, str], optional, default None
74
+ Sets arbitrary AWS tags on the AWS Batch compute environment.
75
+ Set as string key-value pairs.
70
76
  use_tmpfs : bool, default False
71
77
  This enables an explicit tmpfs mount for this step. Note that tmpfs is
72
78
  not available on Fargate compute environments
@@ -113,6 +119,7 @@ class BatchDecorator(StepDecorator):
113
119
  "host_volumes": None,
114
120
  "efs_volumes": None,
115
121
  "use_tmpfs": False,
122
+ "aws_batch_tags": None,
116
123
  "tmpfs_tempdir": True,
117
124
  "tmpfs_size": None,
118
125
  "tmpfs_path": "/metaflow_temp",
@@ -126,6 +133,7 @@ class BatchDecorator(StepDecorator):
126
133
  "gpu": "0",
127
134
  "memory": "4096",
128
135
  }
136
+ package_metadata = None
129
137
  package_url = None
130
138
  package_sha = None
131
139
  run_time_limit = None
@@ -135,8 +143,6 @@ class BatchDecorator(StepDecorator):
135
143
  target_platform = "linux-64"
136
144
 
137
145
  def init(self):
138
- super(BatchDecorator, self).init()
139
-
140
146
  # If no docker image is explicitly specified, impute a default image.
141
147
  if not self.attributes["image"]:
142
148
  # If metaflow-config specifies a docker image, just use that.
@@ -175,6 +181,29 @@ class BatchDecorator(StepDecorator):
175
181
  if self.attributes["trainium"] is not None:
176
182
  self.attributes["inferentia"] = self.attributes["trainium"]
177
183
 
184
+ if not isinstance(BATCH_DEFAULT_TAGS, dict) and not all(
185
+ isinstance(k, str) and isinstance(v, str)
186
+ for k, v in BATCH_DEFAULT_TAGS.items()
187
+ ):
188
+ raise BatchException(
189
+ "BATCH_DEFAULT_TAGS environment variable must be Dict[str, str]"
190
+ )
191
+
192
+ if self.attributes["aws_batch_tags"] is not None:
193
+ if not isinstance(self.attributes["aws_batch_tags"], dict) and not all(
194
+ isinstance(k, str) and isinstance(v, str)
195
+ for k, v in self.attributes["aws_batch_tags"].items()
196
+ ):
197
+ raise BatchException("aws_batch_tags must be Dict[str, str]")
198
+ else:
199
+ self.attributes["aws_batch_tags"] = {}
200
+
201
+ if BATCH_DEFAULT_TAGS:
202
+ self.attributes["aws_batch_tags"] = {
203
+ **BATCH_DEFAULT_TAGS,
204
+ **self.attributes["aws_batch_tags"],
205
+ }
206
+
178
207
  # clean up the alias attribute so it is not passed on.
179
208
  self.attributes.pop("trainium", None)
180
209
 
@@ -207,6 +236,11 @@ class BatchDecorator(StepDecorator):
207
236
  if self.attributes["tmpfs_path"] and self.attributes["tmpfs_path"][0] != "/":
208
237
  raise BatchException("'tmpfs_path' needs to be an absolute path")
209
238
 
239
+ # Validate Batch tags
240
+ if self.attributes["aws_batch_tags"]:
241
+ for key, val in self.attributes["aws_batch_tags"].items():
242
+ validate_aws_tag(key, val)
243
+
210
244
  def runtime_init(self, flow, graph, package, run_id):
211
245
  # Set some more internal state.
212
246
  self.flow = flow
@@ -228,10 +262,20 @@ class BatchDecorator(StepDecorator):
228
262
  # to execute on AWS Batch anymore. We can execute possible fallback
229
263
  # code locally.
230
264
  cli_args.commands = ["batch", "step"]
265
+ cli_args.command_args.append(self.package_metadata)
231
266
  cli_args.command_args.append(self.package_sha)
232
267
  cli_args.command_args.append(self.package_url)
233
- cli_args.command_options.update(self.attributes)
268
+ # skip certain keys as CLI arguments
269
+ _skip_keys = ["aws_batch_tags"]
270
+ cli_args.command_options.update(
271
+ {k: v for k, v in self.attributes.items() if k not in _skip_keys}
272
+ )
234
273
  cli_args.command_options["run-time-limit"] = self.run_time_limit
274
+
275
+ # Pass the supplied AWS batch tags to the step CLI cmd
276
+ cli_args.command_options["aws-batch-tag"] = [
277
+ "%s=%s" % (k, v) for k, v in self.attributes["aws_batch_tags"].items()
278
+ ]
235
279
  if not R.use_r():
236
280
  cli_args.entrypoint[0] = sys.executable
237
281
 
@@ -403,9 +447,16 @@ class BatchDecorator(StepDecorator):
403
447
  @classmethod
404
448
  def _save_package_once(cls, flow_datastore, package):
405
449
  if cls.package_url is None:
406
- cls.package_url, cls.package_sha = flow_datastore.save_data(
407
- [package.blob], len_hint=1
408
- )[0]
450
+ if not FEAT_ALWAYS_UPLOAD_CODE_PACKAGE:
451
+ cls.package_url, cls.package_sha = flow_datastore.save_data(
452
+ [package.blob], len_hint=1
453
+ )[0]
454
+ cls.package_metadata = package.package_metadata
455
+ else:
456
+ # Blocks until the package is uploaded
457
+ cls.package_url = package.package_url()
458
+ cls.package_sha = package.package_sha()
459
+ cls.package_metadata = package.package_metadata
409
460
 
410
461
 
411
462
  def _setup_multinode_environment():
@@ -40,6 +40,7 @@ class StepFunctions(object):
40
40
  name,
41
41
  graph,
42
42
  flow,
43
+ code_package_metadata,
43
44
  code_package_sha,
44
45
  code_package_url,
45
46
  production_token,
@@ -49,16 +50,19 @@ class StepFunctions(object):
49
50
  event_logger,
50
51
  monitor,
51
52
  tags=None,
53
+ aws_batch_tags=None,
52
54
  namespace=None,
53
55
  username=None,
54
56
  max_workers=None,
55
57
  workflow_timeout=None,
56
58
  is_project=False,
57
59
  use_distributed_map=False,
60
+ compress_state_machine=False,
58
61
  ):
59
62
  self.name = name
60
63
  self.graph = graph
61
64
  self.flow = flow
65
+ self.code_package_metadata = code_package_metadata
62
66
  self.code_package_sha = code_package_sha
63
67
  self.code_package_url = code_package_url
64
68
  self.production_token = production_token
@@ -68,6 +72,7 @@ class StepFunctions(object):
68
72
  self.event_logger = event_logger
69
73
  self.monitor = monitor
70
74
  self.tags = tags
75
+ self.aws_batch_tags = aws_batch_tags or {}
71
76
  self.namespace = namespace
72
77
  self.username = username
73
78
  self.max_workers = max_workers
@@ -77,6 +82,9 @@ class StepFunctions(object):
77
82
  # https://aws.amazon.com/blogs/aws/step-functions-distributed-map-a-serverless-solution-for-large-scale-parallel-data-processing/
78
83
  self.use_distributed_map = use_distributed_map
79
84
 
85
+ # S3 command upload configuration
86
+ self.compress_state_machine = compress_state_machine
87
+
80
88
  self._client = StepFunctionsClient()
81
89
  self._workflow = self._compile()
82
90
  self._cron = self._cron()
@@ -192,6 +200,7 @@ class StepFunctions(object):
192
200
  "on AWS Step Functions. Please "
193
201
  "deploy your flow first." % name
194
202
  )
203
+
195
204
  # Dump parameters into `Parameters` input field.
196
205
  input = json.dumps({"Parameters": json.dumps(parameters)})
197
206
  # AWS Step Functions limits input to be 32KiB, but AWS Batch
@@ -301,6 +310,12 @@ class StepFunctions(object):
301
310
  "to AWS Step Functions is not supported currently."
302
311
  )
303
312
 
313
+ if self.flow._flow_decorators.get("exit_hook"):
314
+ raise StepFunctionsException(
315
+ "Deploying flows with the @exit_hook decorator "
316
+ "to AWS Step Functions is not currently supported."
317
+ )
318
+
304
319
  # Visit every node of the flow and recursively build the state machine.
305
320
  def _visit(node, workflow, exit_node=None):
306
321
  if node.parallel_foreach:
@@ -309,6 +324,12 @@ class StepFunctions(object):
309
324
  "to AWS Step Functions is not supported currently."
310
325
  )
311
326
 
327
+ if node.type == "split-switch":
328
+ raise StepFunctionsException(
329
+ "Deploying flows with switch statement "
330
+ "to AWS Step Functions is not supported currently."
331
+ )
332
+
312
333
  # Assign an AWS Batch job to the AWS Step Functions state
313
334
  # and pass the intermediate state by exposing `JobId` and
314
335
  # `Parameters` to the child job(s) as outputs. `Index` and
@@ -838,15 +859,17 @@ class StepFunctions(object):
838
859
  # AWS_BATCH_JOB_ATTEMPT as the job counter.
839
860
  "retry_count": "$((AWS_BATCH_JOB_ATTEMPT-1))",
840
861
  }
841
-
862
+ # merge batch tags supplied through step-fuctions CLI and ones defined in decorator
863
+ batch_tags = {**self.aws_batch_tags, **resources["aws_batch_tags"]}
842
864
  return (
843
- Batch(self.metadata, self.environment)
865
+ Batch(self.metadata, self.environment, self.flow_datastore)
844
866
  .create_job(
845
867
  step_name=node.name,
846
868
  step_cli=self._step_cli(
847
869
  node, input_paths, self.code_package_url, user_code_retries
848
870
  ),
849
871
  task_spec=task_spec,
872
+ code_package_metadata=self.code_package_metadata,
850
873
  code_package_sha=self.code_package_sha,
851
874
  code_package_url=self.code_package_url,
852
875
  code_package_ds=self.flow_datastore.TYPE,
@@ -863,6 +886,7 @@ class StepFunctions(object):
863
886
  swappiness=resources["swappiness"],
864
887
  efa=resources["efa"],
865
888
  use_tmpfs=resources["use_tmpfs"],
889
+ aws_batch_tags=batch_tags,
866
890
  tmpfs_tempdir=resources["tmpfs_tempdir"],
867
891
  tmpfs_size=resources["tmpfs_size"],
868
892
  tmpfs_path=resources["tmpfs_path"],
@@ -874,6 +898,7 @@ class StepFunctions(object):
874
898
  ephemeral_storage=resources["ephemeral_storage"],
875
899
  log_driver=resources["log_driver"],
876
900
  log_options=resources["log_options"],
901
+ offload_command_to_s3=self.compress_state_machine,
877
902
  )
878
903
  .attempts(total_retries + 1)
879
904
  )
@@ -907,7 +932,7 @@ class StepFunctions(object):
907
932
  "with": [
908
933
  decorator.make_decorator_spec()
909
934
  for decorator in node.decorators
910
- if not decorator.statically_defined
935
+ if not decorator.statically_defined and decorator.inserted_by is None
911
936
  ]
912
937
  }
913
938
  # FlowDecorators can define their own top-level options. They are
@@ -7,8 +7,10 @@ from metaflow import JSONType, current, decorators, parameters
7
7
  from metaflow._vendor import click
8
8
  from metaflow.exception import MetaflowException, MetaflowInternalError
9
9
  from metaflow.metaflow_config import (
10
+ FEAT_ALWAYS_UPLOAD_CODE_PACKAGE,
10
11
  SERVICE_VERSION_CHECK,
11
12
  SFN_STATE_MACHINE_PREFIX,
13
+ SFN_COMPRESS_STATE_MACHINE,
12
14
  UI_URL,
13
15
  )
14
16
  from metaflow.package import MetaflowPackage
@@ -18,6 +20,8 @@ from metaflow.util import get_username, to_bytes, to_unicode, version_parse
18
20
 
19
21
  from .production_token import load_token, new_token, store_token
20
22
  from .step_functions import StepFunctions
23
+ from metaflow.tagging_util import validate_tags
24
+ from ..aws_utils import validate_aws_tag
21
25
 
22
26
  VALID_NAME = re.compile(r"[^a-zA-Z0-9_\-\.]")
23
27
 
@@ -96,6 +100,13 @@ def step_functions(obj, name=None):
96
100
  "with the given tag. You can specify this option multiple "
97
101
  "times to attach multiple tags.",
98
102
  )
103
+ @click.option(
104
+ "--aws-batch-tag",
105
+ "aws_batch_tags",
106
+ multiple=True,
107
+ default=None,
108
+ help="AWS Batch tags.",
109
+ )
99
110
  @click.option(
100
111
  "--namespace",
101
112
  "user_namespace",
@@ -130,6 +141,12 @@ def step_functions(obj, name=None):
130
141
  help="Use AWS Step Functions Distributed Map instead of Inline Map for "
131
142
  "defining foreach tasks in Amazon State Language.",
132
143
  )
144
+ @click.option(
145
+ "--compress-state-machine/--no-compress-state-machine",
146
+ is_flag=True,
147
+ default=SFN_COMPRESS_STATE_MACHINE,
148
+ help="Compress AWS Step Functions state machine to fit within the 8K limit.",
149
+ )
133
150
  @click.option(
134
151
  "--deployer-attribute-file",
135
152
  default=None,
@@ -142,6 +159,7 @@ def step_functions(obj, name=None):
142
159
  def create(
143
160
  obj,
144
161
  tags=None,
162
+ aws_batch_tags=None,
145
163
  user_namespace=None,
146
164
  only_json=False,
147
165
  authorize=None,
@@ -151,6 +169,7 @@ def create(
151
169
  workflow_timeout=None,
152
170
  log_execution_history=False,
153
171
  use_distributed_map=False,
172
+ compress_state_machine=False,
154
173
  deployer_attribute_file=None,
155
174
  ):
156
175
  for node in obj.graph:
@@ -195,11 +214,13 @@ def create(
195
214
  token,
196
215
  obj.state_machine_name,
197
216
  tags,
217
+ aws_batch_tags,
198
218
  user_namespace,
199
219
  max_workers,
200
220
  workflow_timeout,
201
221
  obj.is_project,
202
222
  use_distributed_map,
223
+ compress_state_machine,
203
224
  )
204
225
 
205
226
  if only_json:
@@ -314,11 +335,13 @@ def make_flow(
314
335
  token,
315
336
  name,
316
337
  tags,
338
+ aws_batch_tags,
317
339
  namespace,
318
340
  max_workers,
319
341
  workflow_timeout,
320
342
  is_project,
321
343
  use_distributed_map,
344
+ compress_state_machine=False,
322
345
  ):
323
346
  if obj.flow_datastore.TYPE != "s3":
324
347
  raise MetaflowException("AWS Step Functions requires --datastore=s3.")
@@ -329,18 +352,38 @@ def make_flow(
329
352
  decorators._init_step_decorators(
330
353
  obj.flow, obj.graph, obj.environment, obj.flow_datastore, obj.logger
331
354
  )
355
+ obj.graph = obj.flow._graph
332
356
 
333
357
  obj.package = MetaflowPackage(
334
- obj.flow, obj.environment, obj.echo, obj.package_suffixes
358
+ obj.flow,
359
+ obj.environment,
360
+ obj.echo,
361
+ suffixes=obj.package_suffixes,
362
+ flow_datastore=obj.flow_datastore if FEAT_ALWAYS_UPLOAD_CODE_PACKAGE else None,
335
363
  )
336
- package_url, package_sha = obj.flow_datastore.save_data(
337
- [obj.package.blob], len_hint=1
338
- )[0]
364
+ # This blocks until the package is created
365
+ if FEAT_ALWAYS_UPLOAD_CODE_PACKAGE:
366
+ package_url = obj.package.package_url()
367
+ package_sha = obj.package.package_sha()
368
+ else:
369
+ package_url, package_sha = obj.flow_datastore.save_data(
370
+ [obj.package.blob], len_hint=1
371
+ )[0]
372
+
373
+ if aws_batch_tags is not None:
374
+ batch_tags = {}
375
+ for item in list(aws_batch_tags):
376
+ key, value = item.split("=")
377
+ # These are fresh AWS tags provided by the user through the CLI,
378
+ # so we must validate them.
379
+ validate_aws_tag(key, value)
380
+ batch_tags[key] = value
339
381
 
340
382
  return StepFunctions(
341
383
  name,
342
384
  obj.graph,
343
385
  obj.flow,
386
+ obj.package.package_metadata,
344
387
  package_sha,
345
388
  package_url,
346
389
  token,
@@ -350,12 +393,14 @@ def make_flow(
350
393
  obj.event_logger,
351
394
  obj.monitor,
352
395
  tags=tags,
396
+ aws_batch_tags=batch_tags,
353
397
  namespace=namespace,
354
398
  max_workers=max_workers,
355
399
  username=get_username(),
356
400
  workflow_timeout=workflow_timeout,
357
401
  is_project=is_project,
358
402
  use_distributed_map=use_distributed_map,
403
+ compress_state_machine=compress_state_machine,
359
404
  )
360
405
 
361
406
 
@@ -76,6 +76,9 @@ class StepFunctionsDeployer(DeployerImpl):
76
76
  use_distributed_map : bool, optional, default False
77
77
  Use AWS Step Functions Distributed Map instead of Inline Map for defining foreach
78
78
  tasks in Amazon State Language.
79
+ compress_state_machine : bool, optional, default False
80
+ Compress AWS Step Functions state machine to fit within the 8K limit.
81
+
79
82
  deployer_attribute_file : str, optional, default None
80
83
  Write the workflow name to the specified file. Used internally for Metaflow's Deployer API.
81
84
 
@@ -56,6 +56,20 @@ class StepFunctionsDeployedFlow(DeployedFlow):
56
56
 
57
57
  TYPE: ClassVar[Optional[str]] = "step-functions"
58
58
 
59
+ @classmethod
60
+ def list_deployed_flows(cls, flow_name: Optional[str] = None):
61
+ """
62
+ This method is not currently implemented for Step Functions.
63
+
64
+ Raises
65
+ ------
66
+ NotImplementedError
67
+ This method is not implemented for Step Functions.
68
+ """
69
+ raise NotImplementedError(
70
+ "list_deployed_flows is not implemented for StepFunctions"
71
+ )
72
+
59
73
  @classmethod
60
74
  def from_deployment(cls, identifier: str, metadata: Optional[str] = None):
61
75
  """
@@ -70,6 +84,22 @@ class StepFunctionsDeployedFlow(DeployedFlow):
70
84
  "from_deployment is not implemented for StepFunctions"
71
85
  )
72
86
 
87
+ @classmethod
88
+ def get_triggered_run(
89
+ cls, identifier: str, run_id: str, metadata: Optional[str] = None
90
+ ):
91
+ """
92
+ This method is not currently implemented for Step Functions.
93
+
94
+ Raises
95
+ ------
96
+ NotImplementedError
97
+ This method is not implemented for Step Functions.
98
+ """
99
+ raise NotImplementedError(
100
+ "get_triggered_run is not implemented for StepFunctions"
101
+ )
102
+
73
103
  @property
74
104
  def production_token(self: DeployedFlow) -> Optional[str]:
75
105
  """
@@ -30,7 +30,7 @@ from .exception import (
30
30
  )
31
31
  import traceback
32
32
  from collections import namedtuple
33
-
33
+ from .metadata import _save_metadata
34
34
  from .card_resolver import resolve_paths_from_task, resumed_info
35
35
 
36
36
  id_func = id
@@ -613,6 +613,14 @@ def update_card(mf_card, mode, task, data, timeout_value=None):
613
613
  hidden=True,
614
614
  help="Delete data-file and component-file after reading. (internal)",
615
615
  )
616
+ @click.option(
617
+ "--save-metadata",
618
+ default=None,
619
+ show_default=True,
620
+ type=JSONTypeClass(),
621
+ hidden=True,
622
+ help="JSON string containing metadata to be saved. (internal)",
623
+ )
616
624
  @click.pass_context
617
625
  def create(
618
626
  ctx,
@@ -627,6 +635,7 @@ def create(
627
635
  card_uuid=None,
628
636
  delete_input_files=None,
629
637
  id=None,
638
+ save_metadata=None,
630
639
  ):
631
640
  card_id = id
632
641
  rendered_info = None # Variable holding all the information which will be rendered
@@ -824,6 +833,16 @@ def create(
824
833
  % (card_info.type, card_info.hash[:NUM_SHORT_HASH_CHARS]),
825
834
  fg="green",
826
835
  )
836
+ if save_metadata:
837
+ _save_metadata(
838
+ ctx.obj.metadata,
839
+ task.parent.parent.id,
840
+ task.parent.id,
841
+ task.id,
842
+ task.current_attempt,
843
+ card_uuid,
844
+ save_metadata,
845
+ )
827
846
 
828
847
 
829
848
  @card.command()
@@ -5,6 +5,8 @@ import json
5
5
  import sys
6
6
  import os
7
7
  from metaflow import current
8
+ from typing import Callable, Tuple, Dict
9
+
8
10
 
9
11
  ASYNC_TIMEOUT = 30
10
12
 
@@ -44,8 +46,18 @@ class CardProcessManager:
44
46
 
45
47
 
46
48
  class CardCreator:
47
- def __init__(self, top_level_options):
49
+ def __init__(
50
+ self,
51
+ top_level_options,
52
+ should_save_metadata_lambda: Callable[[str], Tuple[bool, Dict]],
53
+ ):
54
+ # should_save_metadata_lambda is a lambda that provides a flag to indicate if
55
+ # card metadata should be written to the metadata store.
56
+ # It gets called only once when the card is created inside the subprocess.
57
+ # The intent is that this is a stateful lambda that will ensure that we only end
58
+ # up writing to the metadata store once.
48
59
  self._top_level_options = top_level_options
60
+ self._should_save_metadata = should_save_metadata_lambda
49
61
 
50
62
  def create(
51
63
  self,
@@ -62,6 +74,8 @@ class CardCreator:
62
74
  # Setting `final` will affect the Reload token set during the card refresh
63
75
  # data creation along with synchronous execution of subprocess.
64
76
  # Setting `sync` will only cause synchronous execution of subprocess.
77
+ save_metadata = False
78
+ metadata_dict = {}
65
79
  if mode != "render" and not runtime_card:
66
80
  # silently ignore runtime updates for cards that don't support them
67
81
  return
@@ -71,6 +85,8 @@ class CardCreator:
71
85
  component_strings = []
72
86
  else:
73
87
  component_strings = current.card._serialize_components(card_uuid)
88
+ # Since the mode is a render, we can check if we need to write to the metadata store.
89
+ save_metadata, metadata_dict = self._should_save_metadata(card_uuid)
74
90
  data = current.card._get_latest_data(card_uuid, final=final, mode=mode)
75
91
  runspec = "/".join([current.run_id, current.step_name, current.task_id])
76
92
  self._run_cards_subprocess(
@@ -85,6 +101,8 @@ class CardCreator:
85
101
  data,
86
102
  final=final,
87
103
  sync=sync,
104
+ save_metadata=save_metadata,
105
+ metadata_dict=metadata_dict,
88
106
  )
89
107
 
90
108
  def _run_cards_subprocess(
@@ -100,6 +118,8 @@ class CardCreator:
100
118
  data=None,
101
119
  final=False,
102
120
  sync=False,
121
+ save_metadata=False,
122
+ metadata_dict=None,
103
123
  ):
104
124
  components_file = data_file = None
105
125
  wait = final or sync
@@ -156,6 +176,9 @@ class CardCreator:
156
176
  if data_file is not None:
157
177
  cmd += ["--data-file", data_file.name]
158
178
 
179
+ if save_metadata:
180
+ cmd += ["--save-metadata", json.dumps(metadata_dict)]
181
+
159
182
  response, fail = self._run_command(
160
183
  cmd,
161
184
  card_uuid,