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
@@ -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,17 @@ 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:
247
+ for key, value in self.__class__._addl_env_vars.items():
248
+ if key.endswith(":"):
249
+ addl_env_vars[key[:-1]] = value
250
+ elif key == "PYTHONPATH":
251
+ addl_env_vars[key] = os.pathsep.join([value, python_path])
252
+ else:
253
+ addl_env_vars[key] = value
254
+ cli_args.env.update(addl_env_vars)
299
255
  if self.interpreter:
300
256
  # https://github.com/conda/conda/issues/7707
301
257
  # Also ref - https://github.com/Netflix/metaflow/pull/178
@@ -306,7 +262,9 @@ class CondaStepDecorator(StepDecorator):
306
262
  def runtime_finished(self, exception):
307
263
  if self.disabled:
308
264
  return
309
- self.metaflow_dir.cleanup()
265
+ if self.__class__._metaflow_home is not None:
266
+ self.__class__._metaflow_home.cleanup()
267
+ self.__class__._metaflow_home = None
310
268
 
311
269
 
312
270
  class CondaFlowDecorator(FlowDecorator):
@@ -339,22 +297,23 @@ class CondaFlowDecorator(FlowDecorator):
339
297
  "disabled": None,
340
298
  }
341
299
 
342
- def __init__(self, attributes=None, statically_defined=False):
300
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
343
301
  self._attributes_with_user_values = (
344
302
  set(attributes.keys()) if attributes is not None else set()
345
303
  )
346
304
 
347
- super(CondaFlowDecorator, self).__init__(attributes, statically_defined)
305
+ super(CondaFlowDecorator, self).__init__(
306
+ attributes, statically_defined, inserted_by
307
+ )
348
308
 
349
309
  def init(self):
350
- super(CondaFlowDecorator, self).init()
351
-
352
310
  # Support legacy 'libraries=' attribute for the decorator.
353
311
  self.attributes["packages"] = {
354
312
  **self.attributes["libraries"],
355
313
  **self.attributes["packages"],
356
314
  }
357
- del self.attributes["libraries"]
315
+ # Keep because otherwise make_decorator_spec will fail
316
+ self.attributes["libraries"] = {}
358
317
  if self.attributes["python"]:
359
318
  self.attributes["python"] = str(self.attributes["python"])
360
319
 
@@ -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
@@ -32,8 +33,10 @@ class CondaEnvironmentException(MetaflowException):
32
33
  class CondaEnvironment(MetaflowEnvironment):
33
34
  TYPE = "conda"
34
35
  _filecache = None
36
+ _force_rebuild = False
35
37
 
36
38
  def __init__(self, flow):
39
+ super().__init__(flow)
37
40
  self.flow = flow
38
41
 
39
42
  def set_local_root(self, local_root):
@@ -71,7 +74,7 @@ class CondaEnvironment(MetaflowEnvironment):
71
74
  self.logger = make_thread_safe(logger)
72
75
 
73
76
  # TODO: Wire up logging
74
- micromamba = Micromamba(self.logger)
77
+ micromamba = Micromamba(self.logger, self._force_rebuild)
75
78
  self.solvers = {"conda": micromamba, "pypi": Pip(micromamba, self.logger)}
76
79
 
77
80
  def init_environment(self, echo, only_steps=None):
@@ -107,7 +110,10 @@ class CondaEnvironment(MetaflowEnvironment):
107
110
  return (
108
111
  id_,
109
112
  (
110
- self.read_from_environment_manifest([id_, platform, type_])
113
+ (
114
+ not self._force_rebuild
115
+ and self.read_from_environment_manifest([id_, platform, type_])
116
+ )
111
117
  or self.write_to_environment_manifest(
112
118
  [id_, platform, type_],
113
119
  self.solvers[type_].solve(id_, **environment),
@@ -153,7 +159,7 @@ class CondaEnvironment(MetaflowEnvironment):
153
159
  _meta = copy.deepcopy(local_packages)
154
160
  for id_, packages, _, _ in results:
155
161
  for package in packages:
156
- if package.get("path"):
162
+ if package.get("path") and not self._force_rebuild:
157
163
  # Cache only those packages that manifest is unaware of
158
164
  local_packages.pop(package["url"], None)
159
165
  else:
@@ -186,7 +192,7 @@ class CondaEnvironment(MetaflowEnvironment):
186
192
  storage.save_bytes(
187
193
  list_of_path_and_filehandle,
188
194
  len_hint=len(list_of_path_and_filehandle),
189
- # overwrite=True,
195
+ overwrite=self._force_rebuild,
190
196
  )
191
197
  for id_, packages, _, platform in results:
192
198
  if id_ in dirty:
@@ -331,7 +337,7 @@ class CondaEnvironment(MetaflowEnvironment):
331
337
  environment[decorator.name] = {
332
338
  k: copy.deepcopy(decorator.attributes[k])
333
339
  for k in decorator.attributes
334
- if k != "disabled"
340
+ if k not in ("disabled", "libraries")
335
341
  }
336
342
  else:
337
343
  return {}
@@ -473,7 +479,9 @@ class CondaEnvironment(MetaflowEnvironment):
473
479
  files = []
474
480
  manifest = self.get_environment_manifest_path()
475
481
  if os.path.exists(manifest):
476
- files.append((manifest, os.path.basename(manifest)))
482
+ files.append(
483
+ (manifest, os.path.basename(manifest), ContentType.OTHER_CONTENT)
484
+ )
477
485
  return files
478
486
 
479
487
  def bootstrap_commands(self, step_name, datastore_type):
@@ -2,6 +2,7 @@ import functools
2
2
  import json
3
3
  import os
4
4
  import re
5
+ import shutil
5
6
  import subprocess
6
7
  import tempfile
7
8
  import time
@@ -30,7 +31,7 @@ _double_equal_match = re.compile("==(?=[<=>!~])")
30
31
 
31
32
 
32
33
  class Micromamba(object):
33
- def __init__(self, logger=None):
34
+ def __init__(self, logger=None, force_rebuild=False):
34
35
  # micromamba is a tiny version of the mamba package manager and comes with
35
36
  # metaflow specific performance enhancements.
36
37
 
@@ -60,6 +61,8 @@ class Micromamba(object):
60
61
  # which causes a race condition in case micromamba needs to be installed first.
61
62
  self.install_mutex = Lock()
62
63
 
64
+ self.force_rebuild = force_rebuild
65
+
63
66
  @property
64
67
  def bin(self) -> str:
65
68
  "Defer installing Micromamba until when the binary path is actually requested"
@@ -152,6 +155,11 @@ class Micromamba(object):
152
155
  keyword="metaflow", # indicates metaflow generated environment
153
156
  id=id_,
154
157
  )
158
+ # If we are forcing a rebuild of the environment, we make sure to remove existing files beforehand.
159
+ # This is to ensure that no irrelevant packages get bundled relative to the resolved environment.
160
+ # NOTE: download always happens before create, so we want to do the cleanup here instead.
161
+ if self.force_rebuild:
162
+ shutil.rmtree(self.path_to_environment(id_, platform), ignore_errors=True)
155
163
 
156
164
  # cheap check
157
165
  if os.path.exists(f"{prefix}/fake.done"):
@@ -12,7 +12,7 @@ from metaflow.debug import debug
12
12
  from metaflow.exception import MetaflowException
13
13
 
14
14
  from .micromamba import Micromamba
15
- from .utils import pip_tags, wheel_tags
15
+ from .utils import pip_tags, wheel_tags, markers_from_platform, conda_platform
16
16
 
17
17
 
18
18
  class PipException(MetaflowException):
@@ -60,6 +60,15 @@ class Pip(object):
60
60
  else:
61
61
  self.logger = lambda *args, **kwargs: None # No-op logger if not provided
62
62
 
63
+ def _get_resolved_python_version(self, prefix):
64
+ try:
65
+ result = self.micromamba._call(["list", "--prefix", prefix, "--json"])
66
+ for package in result:
67
+ if package.get("name") == "python":
68
+ return package["version"]
69
+ except Exception:
70
+ return None
71
+
63
72
  def solve(self, id_, packages, python, platform):
64
73
  prefix = self.micromamba.path_to_environment(id_)
65
74
  if prefix is None:
@@ -67,13 +76,19 @@ class Pip(object):
67
76
  msg += "for id {id}".format(id=id_)
68
77
  raise PipException(msg)
69
78
 
79
+ resolved_python = self._get_resolved_python_version(prefix)
80
+ if not resolved_python:
81
+ raise PipException(
82
+ "Could not determine Python version from conda environment"
83
+ )
84
+
70
85
  debug.conda_exec("Solving packages for PyPI environment %s" % id_)
71
86
  with tempfile.TemporaryDirectory() as tmp_dir:
72
87
  report = "{tmp_dir}/report.json".format(tmp_dir=tmp_dir)
73
88
  implementations, platforms, abis = zip(
74
89
  *[
75
90
  (tag.interpreter, tag.platform, tag.abi)
76
- for tag in pip_tags(python, platform)
91
+ for tag in pip_tags(resolved_python, platform)
77
92
  ]
78
93
  )
79
94
  custom_index_url, extra_index_urls = self.indices(prefix)
@@ -104,7 +119,17 @@ class Pip(object):
104
119
  else:
105
120
  cmd.append(f"{package}=={version}")
106
121
  try:
107
- self._call(prefix, cmd)
122
+ env = {}
123
+ if conda_platform() != platform:
124
+ # cross-platform resolving requires patching the machine and system info for pip to pick up all the relevant packages.
125
+ marker_overrides = markers_from_platform(platform)
126
+ env = {
127
+ "PIP_CUSTOMIZE_OVERRIDES": json.dumps(marker_overrides),
128
+ "PYTHONPATH": os.path.join(
129
+ os.path.dirname(__file__), "pip_patcher"
130
+ ),
131
+ }
132
+ self._call(prefix, cmd, env)
108
133
  except PipPackageNotFound as ex:
109
134
  # pretty print package errors
110
135
  raise PipException(
@@ -142,6 +167,13 @@ class Pip(object):
142
167
 
143
168
  def download(self, id_, packages, python, platform):
144
169
  prefix = self.micromamba.path_to_environment(id_)
170
+
171
+ resolved_python = self._get_resolved_python_version(prefix)
172
+ if not resolved_python:
173
+ raise PipException(
174
+ "Could not determine Python version from conda environment"
175
+ )
176
+
145
177
  metadata_file = METADATA_FILE.format(prefix=prefix)
146
178
  # download packages only if they haven't ever been downloaded before
147
179
  if os.path.isfile(metadata_file):
@@ -192,7 +224,11 @@ class Pip(object):
192
224
  if os.path.isfile(os.path.join(path, f)) and f.endswith(".whl")
193
225
  ]
194
226
  if (
195
- len(set(pip_tags(python, platform)).intersection(wheel_tags(wheel)))
227
+ len(
228
+ set(pip_tags(resolved_python, platform)).intersection(
229
+ wheel_tags(wheel)
230
+ )
231
+ )
196
232
  == 0
197
233
  ):
198
234
  raise PipException(
@@ -211,7 +247,7 @@ class Pip(object):
211
247
  implementations, platforms, abis = zip(
212
248
  *[
213
249
  (tag.interpreter, tag.platform, tag.abi)
214
- for tag in pip_tags(python, platform)
250
+ for tag in pip_tags(resolved_python, platform)
215
251
  ]
216
252
  )
217
253
 
@@ -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
@@ -41,6 +41,28 @@ def conda_platform():
41
41
  return "osx-64"
42
42
 
43
43
 
44
+ def markers_from_platform(platform):
45
+ plat, mach = platform.split("-")
46
+
47
+ platform_system = {"osx": "Darwin", "linux": "Linux"}.get(plat)
48
+ platform_machine = {
49
+ "32": "x86",
50
+ "64": "x86_64",
51
+ "arm64": "aarch64",
52
+ "aarch64": "aarch64",
53
+ }.get(mach)
54
+
55
+ markers = {
56
+ k: v
57
+ for k, v in {
58
+ "platform_system": platform_system,
59
+ "platform_machine": platform_machine,
60
+ }.items()
61
+ if v is not None
62
+ }
63
+ return markers
64
+
65
+
44
66
  def wheel_tags(wheel):
45
67
  _, _, _, tags = parse_wheel_filename(wheel)
46
68
  return list(tags)
@@ -9,3 +9,6 @@ class SecretsProvider(abc.ABC):
9
9
  def get_secret_as_dict(self, secret_id, options={}, role=None) -> Dict[str, str]:
10
10
  """Retrieve the secret from secrets backend, and return a dictionary of
11
11
  environment variables."""
12
+
13
+
14
+ from .secrets_func import get_secret