ob-metaflow 2.11.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 (289) hide show
  1. metaflow/R.py +10 -7
  2. metaflow/__init__.py +40 -25
  3. metaflow/_vendor/imghdr/__init__.py +186 -0
  4. metaflow/_vendor/importlib_metadata/__init__.py +1063 -0
  5. metaflow/_vendor/importlib_metadata/_adapters.py +68 -0
  6. metaflow/_vendor/importlib_metadata/_collections.py +30 -0
  7. metaflow/_vendor/importlib_metadata/_compat.py +71 -0
  8. metaflow/_vendor/importlib_metadata/_functools.py +104 -0
  9. metaflow/_vendor/importlib_metadata/_itertools.py +73 -0
  10. metaflow/_vendor/importlib_metadata/_meta.py +48 -0
  11. metaflow/_vendor/importlib_metadata/_text.py +99 -0
  12. metaflow/_vendor/importlib_metadata/py.typed +0 -0
  13. metaflow/_vendor/typeguard/__init__.py +48 -0
  14. metaflow/_vendor/typeguard/_checkers.py +1070 -0
  15. metaflow/_vendor/typeguard/_config.py +108 -0
  16. metaflow/_vendor/typeguard/_decorators.py +233 -0
  17. metaflow/_vendor/typeguard/_exceptions.py +42 -0
  18. metaflow/_vendor/typeguard/_functions.py +308 -0
  19. metaflow/_vendor/typeguard/_importhook.py +213 -0
  20. metaflow/_vendor/typeguard/_memo.py +48 -0
  21. metaflow/_vendor/typeguard/_pytest_plugin.py +127 -0
  22. metaflow/_vendor/typeguard/_suppression.py +86 -0
  23. metaflow/_vendor/typeguard/_transformer.py +1229 -0
  24. metaflow/_vendor/typeguard/_union_transformer.py +55 -0
  25. metaflow/_vendor/typeguard/_utils.py +173 -0
  26. metaflow/_vendor/typeguard/py.typed +0 -0
  27. metaflow/_vendor/typing_extensions.py +3641 -0
  28. metaflow/_vendor/v3_7/importlib_metadata/__init__.py +1063 -0
  29. metaflow/_vendor/v3_7/importlib_metadata/_adapters.py +68 -0
  30. metaflow/_vendor/v3_7/importlib_metadata/_collections.py +30 -0
  31. metaflow/_vendor/v3_7/importlib_metadata/_compat.py +71 -0
  32. metaflow/_vendor/v3_7/importlib_metadata/_functools.py +104 -0
  33. metaflow/_vendor/v3_7/importlib_metadata/_itertools.py +73 -0
  34. metaflow/_vendor/v3_7/importlib_metadata/_meta.py +48 -0
  35. metaflow/_vendor/v3_7/importlib_metadata/_text.py +99 -0
  36. metaflow/_vendor/v3_7/importlib_metadata/py.typed +0 -0
  37. metaflow/_vendor/v3_7/typeguard/__init__.py +48 -0
  38. metaflow/_vendor/v3_7/typeguard/_checkers.py +906 -0
  39. metaflow/_vendor/v3_7/typeguard/_config.py +108 -0
  40. metaflow/_vendor/v3_7/typeguard/_decorators.py +237 -0
  41. metaflow/_vendor/v3_7/typeguard/_exceptions.py +42 -0
  42. metaflow/_vendor/v3_7/typeguard/_functions.py +310 -0
  43. metaflow/_vendor/v3_7/typeguard/_importhook.py +213 -0
  44. metaflow/_vendor/v3_7/typeguard/_memo.py +48 -0
  45. metaflow/_vendor/v3_7/typeguard/_pytest_plugin.py +100 -0
  46. metaflow/_vendor/v3_7/typeguard/_suppression.py +88 -0
  47. metaflow/_vendor/v3_7/typeguard/_transformer.py +1207 -0
  48. metaflow/_vendor/v3_7/typeguard/_union_transformer.py +54 -0
  49. metaflow/_vendor/v3_7/typeguard/_utils.py +169 -0
  50. metaflow/_vendor/v3_7/typeguard/py.typed +0 -0
  51. metaflow/_vendor/v3_7/typing_extensions.py +3072 -0
  52. metaflow/_vendor/yaml/__init__.py +427 -0
  53. metaflow/_vendor/yaml/composer.py +139 -0
  54. metaflow/_vendor/yaml/constructor.py +748 -0
  55. metaflow/_vendor/yaml/cyaml.py +101 -0
  56. metaflow/_vendor/yaml/dumper.py +62 -0
  57. metaflow/_vendor/yaml/emitter.py +1137 -0
  58. metaflow/_vendor/yaml/error.py +75 -0
  59. metaflow/_vendor/yaml/events.py +86 -0
  60. metaflow/_vendor/yaml/loader.py +63 -0
  61. metaflow/_vendor/yaml/nodes.py +49 -0
  62. metaflow/_vendor/yaml/parser.py +589 -0
  63. metaflow/_vendor/yaml/reader.py +185 -0
  64. metaflow/_vendor/yaml/representer.py +389 -0
  65. metaflow/_vendor/yaml/resolver.py +227 -0
  66. metaflow/_vendor/yaml/scanner.py +1435 -0
  67. metaflow/_vendor/yaml/serializer.py +111 -0
  68. metaflow/_vendor/yaml/tokens.py +104 -0
  69. metaflow/cards.py +5 -0
  70. metaflow/cli.py +331 -785
  71. metaflow/cli_args.py +17 -0
  72. metaflow/cli_components/__init__.py +0 -0
  73. metaflow/cli_components/dump_cmd.py +96 -0
  74. metaflow/cli_components/init_cmd.py +52 -0
  75. metaflow/cli_components/run_cmds.py +546 -0
  76. metaflow/cli_components/step_cmd.py +334 -0
  77. metaflow/cli_components/utils.py +140 -0
  78. metaflow/client/__init__.py +1 -0
  79. metaflow/client/core.py +467 -73
  80. metaflow/client/filecache.py +75 -35
  81. metaflow/clone_util.py +7 -1
  82. metaflow/cmd/code/__init__.py +231 -0
  83. metaflow/cmd/develop/stub_generator.py +756 -288
  84. metaflow/cmd/develop/stubs.py +12 -28
  85. metaflow/cmd/main_cli.py +6 -4
  86. metaflow/cmd/make_wrapper.py +78 -0
  87. metaflow/datastore/__init__.py +1 -0
  88. metaflow/datastore/content_addressed_store.py +41 -10
  89. metaflow/datastore/datastore_set.py +11 -2
  90. metaflow/datastore/flow_datastore.py +156 -10
  91. metaflow/datastore/spin_datastore.py +91 -0
  92. metaflow/datastore/task_datastore.py +154 -39
  93. metaflow/debug.py +5 -0
  94. metaflow/decorators.py +404 -78
  95. metaflow/exception.py +8 -2
  96. metaflow/extension_support/__init__.py +527 -376
  97. metaflow/extension_support/_empty_file.py +2 -2
  98. metaflow/extension_support/plugins.py +49 -31
  99. metaflow/flowspec.py +482 -33
  100. metaflow/graph.py +210 -42
  101. metaflow/includefile.py +84 -40
  102. metaflow/lint.py +141 -22
  103. metaflow/meta_files.py +13 -0
  104. metaflow/{metadata → metadata_provider}/heartbeat.py +24 -8
  105. metaflow/{metadata → metadata_provider}/metadata.py +86 -1
  106. metaflow/metaflow_config.py +175 -28
  107. metaflow/metaflow_config_funcs.py +51 -3
  108. metaflow/metaflow_current.py +4 -10
  109. metaflow/metaflow_environment.py +139 -53
  110. metaflow/metaflow_git.py +115 -0
  111. metaflow/metaflow_profile.py +18 -0
  112. metaflow/metaflow_version.py +150 -66
  113. metaflow/mflog/__init__.py +4 -3
  114. metaflow/mflog/save_logs.py +2 -2
  115. metaflow/multicore_utils.py +31 -14
  116. metaflow/package/__init__.py +673 -0
  117. metaflow/packaging_sys/__init__.py +880 -0
  118. metaflow/packaging_sys/backend.py +128 -0
  119. metaflow/packaging_sys/distribution_support.py +153 -0
  120. metaflow/packaging_sys/tar_backend.py +99 -0
  121. metaflow/packaging_sys/utils.py +54 -0
  122. metaflow/packaging_sys/v1.py +527 -0
  123. metaflow/parameters.py +149 -28
  124. metaflow/plugins/__init__.py +74 -5
  125. metaflow/plugins/airflow/airflow.py +40 -25
  126. metaflow/plugins/airflow/airflow_cli.py +22 -5
  127. metaflow/plugins/airflow/airflow_decorator.py +1 -1
  128. metaflow/plugins/airflow/airflow_utils.py +5 -3
  129. metaflow/plugins/airflow/sensors/base_sensor.py +4 -4
  130. metaflow/plugins/airflow/sensors/external_task_sensor.py +2 -2
  131. metaflow/plugins/airflow/sensors/s3_sensor.py +2 -2
  132. metaflow/plugins/argo/argo_client.py +78 -33
  133. metaflow/plugins/argo/argo_events.py +6 -6
  134. metaflow/plugins/argo/argo_workflows.py +2410 -527
  135. metaflow/plugins/argo/argo_workflows_cli.py +571 -121
  136. metaflow/plugins/argo/argo_workflows_decorator.py +43 -12
  137. metaflow/plugins/argo/argo_workflows_deployer.py +106 -0
  138. metaflow/plugins/argo/argo_workflows_deployer_objects.py +453 -0
  139. metaflow/plugins/argo/capture_error.py +73 -0
  140. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  141. metaflow/plugins/argo/exit_hooks.py +209 -0
  142. metaflow/plugins/argo/jobset_input_paths.py +15 -0
  143. metaflow/plugins/argo/param_val.py +19 -0
  144. metaflow/plugins/aws/aws_client.py +10 -3
  145. metaflow/plugins/aws/aws_utils.py +55 -2
  146. metaflow/plugins/aws/batch/batch.py +72 -5
  147. metaflow/plugins/aws/batch/batch_cli.py +33 -10
  148. metaflow/plugins/aws/batch/batch_client.py +4 -3
  149. metaflow/plugins/aws/batch/batch_decorator.py +102 -35
  150. metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
  151. metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
  152. metaflow/plugins/aws/step_functions/production_token.py +1 -1
  153. metaflow/plugins/aws/step_functions/step_functions.py +65 -8
  154. metaflow/plugins/aws/step_functions/step_functions_cli.py +101 -7
  155. metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -2
  156. metaflow/plugins/aws/step_functions/step_functions_deployer.py +97 -0
  157. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +264 -0
  158. metaflow/plugins/azure/azure_exceptions.py +1 -1
  159. metaflow/plugins/azure/azure_secret_manager_secrets_provider.py +240 -0
  160. metaflow/plugins/azure/azure_tail.py +1 -1
  161. metaflow/plugins/azure/includefile_support.py +2 -0
  162. metaflow/plugins/cards/card_cli.py +66 -30
  163. metaflow/plugins/cards/card_creator.py +25 -1
  164. metaflow/plugins/cards/card_datastore.py +21 -49
  165. metaflow/plugins/cards/card_decorator.py +132 -8
  166. metaflow/plugins/cards/card_modules/basic.py +112 -17
  167. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  168. metaflow/plugins/cards/card_modules/card.py +16 -1
  169. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  170. metaflow/plugins/cards/card_modules/components.py +665 -28
  171. metaflow/plugins/cards/card_modules/convert_to_native_type.py +36 -7
  172. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  173. metaflow/plugins/cards/card_modules/main.css +1 -0
  174. metaflow/plugins/cards/card_modules/main.js +68 -49
  175. metaflow/plugins/cards/card_modules/renderer_tools.py +1 -0
  176. metaflow/plugins/cards/card_modules/test_cards.py +26 -12
  177. metaflow/plugins/cards/card_server.py +39 -14
  178. metaflow/plugins/cards/component_serializer.py +2 -9
  179. metaflow/plugins/cards/metadata.py +22 -0
  180. metaflow/plugins/catch_decorator.py +9 -0
  181. metaflow/plugins/datastores/azure_storage.py +10 -1
  182. metaflow/plugins/datastores/gs_storage.py +6 -2
  183. metaflow/plugins/datastores/local_storage.py +12 -6
  184. metaflow/plugins/datastores/spin_storage.py +12 -0
  185. metaflow/plugins/datatools/local.py +2 -0
  186. metaflow/plugins/datatools/s3/s3.py +126 -75
  187. metaflow/plugins/datatools/s3/s3op.py +254 -121
  188. metaflow/plugins/env_escape/__init__.py +3 -3
  189. metaflow/plugins/env_escape/client_modules.py +102 -72
  190. metaflow/plugins/env_escape/server.py +7 -0
  191. metaflow/plugins/env_escape/stub.py +24 -5
  192. metaflow/plugins/events_decorator.py +343 -185
  193. metaflow/plugins/exit_hook/__init__.py +0 -0
  194. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  195. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  196. metaflow/plugins/gcp/__init__.py +1 -1
  197. metaflow/plugins/gcp/gcp_secret_manager_secrets_provider.py +11 -6
  198. metaflow/plugins/gcp/gs_tail.py +10 -6
  199. metaflow/plugins/gcp/includefile_support.py +3 -0
  200. metaflow/plugins/kubernetes/kube_utils.py +108 -0
  201. metaflow/plugins/kubernetes/kubernetes.py +411 -130
  202. metaflow/plugins/kubernetes/kubernetes_cli.py +168 -36
  203. metaflow/plugins/kubernetes/kubernetes_client.py +104 -2
  204. metaflow/plugins/kubernetes/kubernetes_decorator.py +246 -88
  205. metaflow/plugins/kubernetes/kubernetes_job.py +253 -581
  206. metaflow/plugins/kubernetes/kubernetes_jobsets.py +1071 -0
  207. metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
  208. metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
  209. metaflow/plugins/logs_cli.py +359 -0
  210. metaflow/plugins/{metadata → metadata_providers}/local.py +144 -84
  211. metaflow/plugins/{metadata → metadata_providers}/service.py +103 -26
  212. metaflow/plugins/metadata_providers/spin.py +16 -0
  213. metaflow/plugins/package_cli.py +36 -24
  214. metaflow/plugins/parallel_decorator.py +128 -11
  215. metaflow/plugins/parsers.py +16 -0
  216. metaflow/plugins/project_decorator.py +51 -5
  217. metaflow/plugins/pypi/bootstrap.py +357 -105
  218. metaflow/plugins/pypi/conda_decorator.py +82 -81
  219. metaflow/plugins/pypi/conda_environment.py +187 -52
  220. metaflow/plugins/pypi/micromamba.py +157 -47
  221. metaflow/plugins/pypi/parsers.py +268 -0
  222. metaflow/plugins/pypi/pip.py +88 -13
  223. metaflow/plugins/pypi/pypi_decorator.py +37 -1
  224. metaflow/plugins/pypi/utils.py +48 -2
  225. metaflow/plugins/resources_decorator.py +2 -2
  226. metaflow/plugins/secrets/__init__.py +3 -0
  227. metaflow/plugins/secrets/secrets_decorator.py +26 -181
  228. metaflow/plugins/secrets/secrets_func.py +49 -0
  229. metaflow/plugins/secrets/secrets_spec.py +101 -0
  230. metaflow/plugins/secrets/utils.py +74 -0
  231. metaflow/plugins/tag_cli.py +4 -7
  232. metaflow/plugins/test_unbounded_foreach_decorator.py +41 -6
  233. metaflow/plugins/timeout_decorator.py +3 -3
  234. metaflow/plugins/uv/__init__.py +0 -0
  235. metaflow/plugins/uv/bootstrap.py +128 -0
  236. metaflow/plugins/uv/uv_environment.py +72 -0
  237. metaflow/procpoll.py +1 -1
  238. metaflow/pylint_wrapper.py +5 -1
  239. metaflow/runner/__init__.py +0 -0
  240. metaflow/runner/click_api.py +717 -0
  241. metaflow/runner/deployer.py +470 -0
  242. metaflow/runner/deployer_impl.py +201 -0
  243. metaflow/runner/metaflow_runner.py +714 -0
  244. metaflow/runner/nbdeploy.py +132 -0
  245. metaflow/runner/nbrun.py +225 -0
  246. metaflow/runner/subprocess_manager.py +650 -0
  247. metaflow/runner/utils.py +335 -0
  248. metaflow/runtime.py +1078 -260
  249. metaflow/sidecar/sidecar_worker.py +1 -1
  250. metaflow/system/__init__.py +5 -0
  251. metaflow/system/system_logger.py +85 -0
  252. metaflow/system/system_monitor.py +108 -0
  253. metaflow/system/system_utils.py +19 -0
  254. metaflow/task.py +521 -225
  255. metaflow/tracing/__init__.py +7 -7
  256. metaflow/tracing/span_exporter.py +31 -38
  257. metaflow/tracing/tracing_modules.py +38 -43
  258. metaflow/tuple_util.py +27 -0
  259. metaflow/user_configs/__init__.py +0 -0
  260. metaflow/user_configs/config_options.py +563 -0
  261. metaflow/user_configs/config_parameters.py +598 -0
  262. metaflow/user_decorators/__init__.py +0 -0
  263. metaflow/user_decorators/common.py +144 -0
  264. metaflow/user_decorators/mutable_flow.py +512 -0
  265. metaflow/user_decorators/mutable_step.py +424 -0
  266. metaflow/user_decorators/user_flow_decorator.py +264 -0
  267. metaflow/user_decorators/user_step_decorator.py +749 -0
  268. metaflow/util.py +243 -27
  269. metaflow/vendor.py +23 -7
  270. metaflow/version.py +1 -1
  271. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Makefile +355 -0
  272. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Tiltfile +726 -0
  273. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/pick_services.sh +105 -0
  274. ob_metaflow-2.19.7.1rc0.dist-info/METADATA +87 -0
  275. ob_metaflow-2.19.7.1rc0.dist-info/RECORD +445 -0
  276. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  277. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +1 -0
  278. metaflow/_vendor/v3_5/__init__.py +0 -1
  279. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  280. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  281. metaflow/package.py +0 -188
  282. ob_metaflow-2.11.13.1.dist-info/METADATA +0 -85
  283. ob_metaflow-2.11.13.1.dist-info/RECORD +0 -308
  284. /metaflow/_vendor/{v3_5/zipp.py → zipp.py} +0 -0
  285. /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
  286. /metaflow/{metadata → metadata_provider}/util.py +0 -0
  287. /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
  288. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info/licenses}/LICENSE +0 -0
  289. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
@@ -8,11 +8,11 @@ from concurrent.futures import ThreadPoolExecutor
8
8
  from itertools import chain, product
9
9
  from urllib.parse import unquote
10
10
 
11
+ from metaflow.debug import debug
11
12
  from metaflow.exception import MetaflowException
12
- from metaflow.util import which
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):
@@ -25,6 +25,23 @@ class PipException(MetaflowException):
25
25
  super(PipException, self).__init__(msg)
26
26
 
27
27
 
28
+ class PipPackageNotFound(Exception):
29
+ "Wrapper for pip package resolve errors."
30
+
31
+ def __init__(self, error):
32
+ self.error = error
33
+ try:
34
+ # Parse the package spec from error message:
35
+ # ERROR: ERROR: Could not find a version that satisfies the requirement pkg==0.0.1 (from versions: none)
36
+ # ERROR: No matching distribution found for pkg==0.0.1
37
+ self.package_spec = re.search(
38
+ "ERROR: No matching distribution found for (.*)", self.error
39
+ )[1]
40
+ self.package_name = re.match("\w*", self.package_spec)[0]
41
+ except Exception:
42
+ pass
43
+
44
+
28
45
  METADATA_FILE = "{prefix}/.pip/metadata"
29
46
  INSTALLATION_MARKER = "{prefix}/.pip/id"
30
47
 
@@ -34,10 +51,23 @@ INSTALLATION_MARKER = "{prefix}/.pip/id"
34
51
 
35
52
 
36
53
  class Pip(object):
37
- def __init__(self, micromamba=None):
54
+ def __init__(self, micromamba=None, logger=None):
38
55
  # pip is assumed to be installed inside a conda environment managed by
39
56
  # micromamba. pip commands are executed using `micromamba run --prefix`
40
- self.micromamba = micromamba or Micromamba()
57
+ self.micromamba = micromamba or Micromamba(logger)
58
+ if logger:
59
+ self.logger = logger
60
+ else:
61
+ self.logger = lambda *args, **kwargs: None # No-op logger if not provided
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
41
71
 
42
72
  def solve(self, id_, packages, python, platform):
43
73
  prefix = self.micromamba.path_to_environment(id_)
@@ -46,12 +76,19 @@ class Pip(object):
46
76
  msg += "for id {id}".format(id=id_)
47
77
  raise PipException(msg)
48
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
+
85
+ debug.conda_exec("Solving packages for PyPI environment %s" % id_)
49
86
  with tempfile.TemporaryDirectory() as tmp_dir:
50
87
  report = "{tmp_dir}/report.json".format(tmp_dir=tmp_dir)
51
88
  implementations, platforms, abis = zip(
52
89
  *[
53
90
  (tag.interpreter, tag.platform, tag.abi)
54
- for tag in pip_tags(python, platform)
91
+ for tag in pip_tags(resolved_python, platform)
55
92
  ]
56
93
  )
57
94
  custom_index_url, extra_index_urls = self.indices(prefix)
@@ -81,7 +118,25 @@ class Pip(object):
81
118
  cmd.append(f"{package}{version}")
82
119
  else:
83
120
  cmd.append(f"{package}=={version}")
84
- self._call(prefix, cmd)
121
+ try:
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)
133
+ except PipPackageNotFound as ex:
134
+ # pretty print package errors
135
+ raise PipException(
136
+ "Unable to find a binary distribution compatible with %s for %s.\n\n"
137
+ "Note: ***@pypi*** does not currently support source distributions"
138
+ % (ex.package_spec, platform)
139
+ )
85
140
 
86
141
  def _format(dl_info):
87
142
  res = {k: v for k, v in dl_info.items() if k in ["url"]}
@@ -96,9 +151,9 @@ class Pip(object):
96
151
  res["url"] = "{vcs}+{url}@{commit_id}{subdir_str}".format(
97
152
  **vcs_info,
98
153
  **res,
99
- subdir_str="#subdirectory=%s" % subdirectory
100
- if subdirectory
101
- else ""
154
+ subdir_str=(
155
+ "#subdirectory=%s" % subdirectory if subdirectory else ""
156
+ ),
102
157
  )
103
158
  # used to deduplicate the storage location in case wheel does not
104
159
  # build with enough unique identifiers.
@@ -112,15 +167,26 @@ class Pip(object):
112
167
 
113
168
  def download(self, id_, packages, python, platform):
114
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
+
115
177
  metadata_file = METADATA_FILE.format(prefix=prefix)
116
178
  # download packages only if they haven't ever been downloaded before
117
179
  if os.path.isfile(metadata_file):
118
- return
180
+ with open(metadata_file, "r") as file:
181
+ metadata = json.load(file)
182
+ if all(package["url"] in metadata for package in packages):
183
+ return
119
184
 
120
185
  metadata = {}
121
186
  custom_index_url, extra_index_urls = self.indices(prefix)
122
187
 
123
188
  # build wheels if needed
189
+ debug.conda_exec("Building wheels for PyPI environment %s if necessary" % id_)
124
190
  with ThreadPoolExecutor() as executor:
125
191
 
126
192
  def _build(key, package):
@@ -158,7 +224,11 @@ class Pip(object):
158
224
  if os.path.isfile(os.path.join(path, f)) and f.endswith(".whl")
159
225
  ]
160
226
  if (
161
- 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
+ )
162
232
  == 0
163
233
  ):
164
234
  raise PipException(
@@ -177,10 +247,11 @@ class Pip(object):
177
247
  implementations, platforms, abis = zip(
178
248
  *[
179
249
  (tag.interpreter, tag.platform, tag.abi)
180
- for tag in pip_tags(python, platform)
250
+ for tag in pip_tags(resolved_python, platform)
181
251
  ]
182
252
  )
183
253
 
254
+ debug.conda_exec("Downloading packages for PyPI environment %s" % id_)
184
255
  cmd = [
185
256
  "download",
186
257
  "--no-deps",
@@ -220,6 +291,7 @@ class Pip(object):
220
291
  # Pip can't install packages if the underlying virtual environment doesn't
221
292
  # share the same platform
222
293
  if self.micromamba.platform() == platform:
294
+ debug.conda_exec("Installing packages for local PyPI environment %s" % id_)
223
295
  cmd = [
224
296
  "install",
225
297
  "--no-compile",
@@ -302,11 +374,14 @@ class Pip(object):
302
374
  .strip()
303
375
  )
304
376
  except subprocess.CalledProcessError as e:
377
+ errors = e.stderr.decode()
378
+ if "No matching distribution" in errors:
379
+ raise PipPackageNotFound(errors)
305
380
  raise PipException(
306
381
  "command '{cmd}' returned error ({code}) {output}\n{stderr}".format(
307
382
  cmd=" ".join(e.cmd),
308
383
  code=e.returncode,
309
384
  output=e.output.decode(),
310
- stderr=e.stderr.decode(),
385
+ stderr=errors,
311
386
  )
312
387
  )
@@ -24,6 +24,13 @@ 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, inserted_by=None):
28
+ self._attributes_with_user_values = (
29
+ set(attributes.keys()) if attributes is not None else set()
30
+ )
31
+
32
+ super().__init__(attributes, statically_defined, inserted_by)
33
+
27
34
  def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
28
35
  # The init_environment hook for Environment creates the relevant virtual
29
36
  # environments. The step_init hook sets up the relevant state for that hook to
@@ -34,7 +41,11 @@ class PyPIStepDecorator(StepDecorator):
34
41
 
35
42
  # Support flow-level decorator
36
43
  if "pypi_base" in self.flow._flow_decorators:
37
- super_attributes = self.flow._flow_decorators["pypi_base"][0].attributes
44
+ pypi_base = self.flow._flow_decorators["pypi_base"][0]
45
+ super_attributes = pypi_base.attributes
46
+ self._attributes_with_user_values.update(
47
+ pypi_base._attributes_with_user_values
48
+ )
38
49
  self.attributes["packages"] = {
39
50
  **super_attributes["packages"],
40
51
  **self.attributes["packages"],
@@ -70,6 +81,10 @@ class PyPIStepDecorator(StepDecorator):
70
81
  # --environment=pypi to --environment=conda
71
82
  _supported_virtual_envs.extend(["pypi"])
72
83
 
84
+ # TODO: Hardcoded for now to support the fast bakery environment.
85
+ # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
86
+ _supported_virtual_envs.extend(["fast-bakery"])
87
+
73
88
  # The --environment= requirement ensures that valid virtual environments are
74
89
  # created for every step to execute it, greatly simplifying the @pypi
75
90
  # implementation.
@@ -83,6 +98,15 @@ class PyPIStepDecorator(StepDecorator):
83
98
  ),
84
99
  )
85
100
  )
101
+ # TODO: This code snippet can be done away with by altering the constructor of
102
+ # MetaflowEnvironment. A good first-task exercise.
103
+ # Avoid circular import
104
+ from metaflow.plugins.datastores.local_storage import LocalStorage
105
+
106
+ environment.set_local_root(LocalStorage.get_datastore_root_from_config(logger))
107
+
108
+ def is_attribute_user_defined(self, name):
109
+ return name in self._attributes_with_user_values
86
110
 
87
111
 
88
112
  class PyPIFlowDecorator(FlowDecorator):
@@ -104,12 +128,20 @@ class PyPIFlowDecorator(FlowDecorator):
104
128
  name = "pypi_base"
105
129
  defaults = {"packages": {}, "python": None, "disabled": None}
106
130
 
131
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
132
+ self._attributes_with_user_values = (
133
+ set(attributes.keys()) if attributes is not None else set()
134
+ )
135
+
136
+ super().__init__(attributes, statically_defined, inserted_by)
137
+
107
138
  def flow_init(
108
139
  self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
109
140
  ):
110
141
  from metaflow import decorators
111
142
 
112
143
  decorators._attach_decorators(flow, ["pypi"])
144
+ decorators._init(flow)
113
145
 
114
146
  # @pypi uses a conda environment to create a virtual environment.
115
147
  # The conda environment can be created through micromamba.
@@ -119,6 +151,10 @@ class PyPIFlowDecorator(FlowDecorator):
119
151
  # --environment=pypi to --environment=conda
120
152
  _supported_virtual_envs.extend(["pypi"])
121
153
 
154
+ # TODO: Hardcoded for now to support the fast bakery environment.
155
+ # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
156
+ _supported_virtual_envs.extend(["fast-bakery"])
157
+
122
158
  # The --environment= requirement ensures that valid virtual environments are
123
159
  # created for every step to execute it, greatly simplifying the @conda
124
160
  # implementation.
@@ -1,4 +1,3 @@
1
- import os
2
1
  import platform
3
2
  import sys
4
3
 
@@ -17,10 +16,13 @@ else:
17
16
  from metaflow._vendor.packaging import tags
18
17
  from metaflow._vendor.packaging.utils import parse_wheel_filename
19
18
 
20
- from urllib.parse import unquote, urlparse
19
+ from urllib.parse import unquote
21
20
 
22
21
  from metaflow.exception import MetaflowException
23
22
 
23
+ MICROMAMBA_URL = "https://micro.mamba.pm/api/micromamba/{platform}/{version}"
24
+ MICROMAMBA_MIRROR_URL = "https://micromamba.outerbounds.sh/{platform}/{version}.tar.bz2"
25
+
24
26
 
25
27
  def conda_platform():
26
28
  # Returns the conda platform for the Python interpreter
@@ -39,6 +41,28 @@ def conda_platform():
39
41
  return "osx-64"
40
42
 
41
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
+
42
66
  def wheel_tags(wheel):
43
67
  _, _, _, tags = parse_wheel_filename(wheel)
44
68
  return list(tags)
@@ -67,9 +91,31 @@ def pip_tags(python_version, mamba_platform):
67
91
  "_2_25",
68
92
  "_2_26",
69
93
  "_2_27",
94
+ "_2_28",
95
+ "_2_29",
70
96
  )
71
97
  ]
72
98
  platforms.append("linux_x86_64")
99
+ elif mamba_platform == "linux-aarch64":
100
+ platforms = [
101
+ "manylinux%s_aarch64" % s
102
+ for s in (
103
+ "2014",
104
+ "_2_17",
105
+ "_2_18",
106
+ "_2_19",
107
+ "_2_20",
108
+ "_2_21",
109
+ "_2_23",
110
+ "_2_24",
111
+ "_2_25",
112
+ "_2_26",
113
+ "_2_27",
114
+ "_2_28",
115
+ "_2_29",
116
+ )
117
+ ]
118
+ platforms.append("linux_aarch64")
73
119
  elif mamba_platform == "osx-64":
74
120
  platforms = tags.mac_platforms(arch="x86_64")
75
121
  elif mamba_platform == "osx-arm64":
@@ -23,7 +23,7 @@ class ResourcesDecorator(StepDecorator):
23
23
  ----------
24
24
  cpu : int, default 1
25
25
  Number of CPUs required for this step.
26
- gpu : int, default 0
26
+ gpu : int, optional, default None
27
27
  Number of GPUs required for this step.
28
28
  disk : int, optional, default None
29
29
  Disk size (in MB) required for this step. Only applies on Kubernetes.
@@ -37,7 +37,7 @@ class ResourcesDecorator(StepDecorator):
37
37
  name = "resources"
38
38
  defaults = {
39
39
  "cpu": "1",
40
- "gpu": "0",
40
+ "gpu": None,
41
41
  "disk": None,
42
42
  "memory": "4096",
43
43
  "shared_memory": None,
@@ -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
@@ -1,182 +1,16 @@
1
1
  import os
2
- import re
3
2
 
4
3
  from metaflow.exception import MetaflowException
5
4
  from metaflow.decorators import StepDecorator
6
5
  from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
7
- from metaflow.unbounded_foreach import UBF_CONTROL
8
-
9
- from typing import Any, Dict, List, Union
10
-
11
- DISALLOWED_SECRETS_ENV_VAR_PREFIXES = ["METAFLOW_"]
12
-
13
-
14
- def get_default_secrets_backend_type():
15
- from metaflow.metaflow_config import DEFAULT_SECRETS_BACKEND_TYPE
16
-
17
- if DEFAULT_SECRETS_BACKEND_TYPE is None:
18
- raise MetaflowException(
19
- "No default secrets backend type configured, but needed by @secrets. "
20
- "Set METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE."
21
- )
22
- return DEFAULT_SECRETS_BACKEND_TYPE
23
-
24
-
25
- class SecretSpec:
26
- def __init__(self, secrets_backend_type, secret_id, options={}, role=None):
27
- self._secrets_backend_type = secrets_backend_type
28
- self._secret_id = secret_id
29
- self._options = options
30
- self._role = role
31
-
32
- @property
33
- def secrets_backend_type(self):
34
- return self._secrets_backend_type
35
-
36
- @property
37
- def secret_id(self):
38
- return self._secret_id
39
-
40
- @property
41
- def options(self):
42
- return self._options
43
-
44
- @property
45
- def role(self):
46
- return self._role
47
-
48
- def to_json(self):
49
- """Mainly used for testing... not the same as the input dict in secret_spec_from_dict()!"""
50
- return {
51
- "secrets_backend_type": self.secrets_backend_type,
52
- "secret_id": self.secret_id,
53
- "options": self.options,
54
- "role": self.role,
55
- }
56
-
57
- def __str__(self):
58
- return "%s (%s)" % (self._secret_id, self._secrets_backend_type)
59
-
60
- @staticmethod
61
- def secret_spec_from_str(secret_spec_str, role):
62
- # "." may be used in secret_id one day (provider specific). HOWEVER, it provides the best UX for
63
- # non-conflicting cases (i.e. for secret ids that don't contain "."). This is true for all AWS
64
- # Secrets Manager secrets.
65
- #
66
- # So we skew heavily optimize for best upfront UX for the present (1/2023).
67
- #
68
- # If/when a certain secret backend supports "." secret names, we can figure out a solution at that time.
69
- # At a minimum, dictionary style secret spec may be used with no code changes (see secret_spec_from_dict()).
70
- # Other options could be:
71
- # - accept and document that "." secret_ids don't work in Metaflow (across all possible providers)
72
- # - add a Metaflow config variable that specifies the separator (default ".")
73
- # - smarter spec parsing, that errors on secrets that look ambiguous. "aws-secrets-manager.XYZ" could mean:
74
- # + secret_id "XYZ" in aws-secrets-manager backend, OR
75
- # + secret_id "aws-secrets-manager.XYZ" default backend (if it is defined).
76
- # + in this case, user can simply set "azure-key-vault.aws-secrets-manager.XYZ" instead!
77
- parts = secret_spec_str.split(".", maxsplit=1)
78
- if len(parts) == 1:
79
- secrets_backend_type = get_default_secrets_backend_type()
80
- secret_id = parts[0]
81
- else:
82
- secrets_backend_type = parts[0]
83
- secret_id = parts[1]
84
- return SecretSpec(
85
- secrets_backend_type, secret_id=secret_id, options={}, role=role
86
- )
87
-
88
- @staticmethod
89
- def secret_spec_from_dict(secret_spec_dict, role):
90
- if "type" not in secret_spec_dict:
91
- secrets_backend_type = get_default_secrets_backend_type()
92
- else:
93
- secrets_backend_type = secret_spec_dict["type"]
94
- if not isinstance(secrets_backend_type, str):
95
- raise MetaflowException(
96
- "Bad @secrets specification - 'type' must be a string - found %s"
97
- % type(secrets_backend_type)
98
- )
99
- secret_id = secret_spec_dict.get("id")
100
- if not isinstance(secret_id, str):
101
- raise MetaflowException(
102
- "Bad @secrets specification - 'id' must be a string - found %s"
103
- % type(secret_id)
104
- )
105
- options = secret_spec_dict.get("options", {})
106
- if not isinstance(options, dict):
107
- raise MetaflowException(
108
- "Bad @secrets specification - 'option' must be a dict - found %s"
109
- % type(options)
110
- )
111
- role_for_source = secret_spec_dict.get("role", None)
112
- if role_for_source is not None:
113
- if not isinstance(role_for_source, str):
114
- raise MetaflowException(
115
- "Bad @secrets specification - 'role' must be a str - found %s"
116
- % type(role_for_source)
117
- )
118
- role = role_for_source
119
- return SecretSpec(
120
- secrets_backend_type, secret_id=secret_id, options=options, role=role
121
- )
122
-
123
-
124
- def validate_env_vars_across_secrets(all_secrets_env_vars):
125
- vars_injected_by = {}
126
- for secret_spec, env_vars in all_secrets_env_vars:
127
- for k in env_vars:
128
- if k in vars_injected_by:
129
- raise MetaflowException(
130
- "Secret '%s' will inject '%s' as env var, and it is also added by '%s'"
131
- % (secret_spec, k, vars_injected_by[k])
132
- )
133
- vars_injected_by[k] = secret_spec
134
-
135
-
136
- def validate_env_vars_vs_existing_env(all_secrets_env_vars):
137
- for secret_spec, env_vars in all_secrets_env_vars:
138
- for k in env_vars:
139
- if k in os.environ:
140
- raise MetaflowException(
141
- "Secret '%s' will inject '%s' as env var, but it already exists in env"
142
- % (secret_spec, k)
143
- )
144
-
145
-
146
- def validate_env_vars(env_vars):
147
- for k, v in env_vars.items():
148
- if not isinstance(k, str):
149
- raise MetaflowException("Found non string key %s (%s)" % (str(k), type(k)))
150
- if not isinstance(v, str):
151
- raise MetaflowException(
152
- "Found non string value %s (%s)" % (str(v), type(v))
153
- )
154
- if not re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", k):
155
- raise MetaflowException("Found invalid env var name '%s'." % k)
156
- for disallowed_prefix in DISALLOWED_SECRETS_ENV_VAR_PREFIXES:
157
- if k.startswith(disallowed_prefix):
158
- raise MetaflowException(
159
- "Found disallowed env var name '%s' (starts with '%s')."
160
- % (k, disallowed_prefix)
161
- )
162
-
163
-
164
- def get_secrets_backend_provider(secrets_backend_type):
165
- from metaflow.plugins import SECRETS_PROVIDERS
166
-
167
- try:
168
- provider_cls = [
169
- pc for pc in SECRETS_PROVIDERS if pc.TYPE == secrets_backend_type
170
- ][0]
171
- return provider_cls()
172
- except IndexError:
173
- raise MetaflowException(
174
- "Unknown secrets backend type %s (available types: %s)"
175
- % (
176
- secrets_backend_type,
177
- ", ".join(pc.TYPE for pc in SECRETS_PROVIDERS if pc.TYPE != "inline"),
178
- )
179
- )
6
+ from metaflow.plugins.secrets.secrets_spec import SecretSpec
7
+ from metaflow.plugins.secrets.utils import (
8
+ get_secrets_backend_provider,
9
+ validate_env_vars,
10
+ validate_env_vars_across_secrets,
11
+ validate_env_vars_vs_existing_env,
12
+ )
13
+ from metaflow.unbounded_foreach import UBF_TASK
180
14
 
181
15
 
182
16
  class SecretsDecorator(StepDecorator):
@@ -188,13 +22,14 @@ class SecretsDecorator(StepDecorator):
188
22
  ----------
189
23
  sources : List[Union[str, Dict[str, Any]]], default: []
190
24
  List of secret specs, defining how the secrets are to be retrieved
25
+ role : str, optional, default: None
26
+ Role to use for fetching secrets
27
+ allow_override : bool, optional, default: False
28
+ Toggle whether secrets can replace existing environment variables.
191
29
  """
192
30
 
193
31
  name = "secrets"
194
- defaults = {
195
- "sources": [],
196
- "role": None,
197
- }
32
+ defaults = {"sources": [], "role": None, "allow_override": False}
198
33
 
199
34
  def task_pre_step(
200
35
  self,
@@ -210,8 +45,17 @@ class SecretsDecorator(StepDecorator):
210
45
  ubf_context,
211
46
  inputs,
212
47
  ):
213
- if ubf_context == UBF_CONTROL:
214
- """control tasks (as used in "unbounded for each") don't need secrets"""
48
+ if (
49
+ ubf_context
50
+ and ubf_context == UBF_TASK
51
+ and os.environ.get("METAFLOW_RUNTIME_ENVIRONMENT", "local") == "local"
52
+ ):
53
+ # We will skip the secret injection for "locally" launched UBF_TASK (worker) tasks
54
+ # When we "locally" run @parallel tasks, the control task will create the worker tasks and the environment variables
55
+ # of the control task are inherited by the worker tasks. If we don't skip setting secrets in the worker task then the
56
+ # worker tasks will try to set the environment variables again which will cause a clash with the control task's env vars,
57
+ # causing the @secrets' `task_pre_step` to fail. In remote settings, (e.g. AWS Batch/Kubernetes), the worker task and
58
+ # control task are independently created so there is no chances of an env var clash.
215
59
  return
216
60
  # List of pairs (secret_spec, env_vars_from_this_spec)
217
61
  all_secrets_env_vars = []
@@ -267,7 +111,8 @@ class SecretsDecorator(StepDecorator):
267
111
  all_secrets_env_vars.append((secret_spec, env_vars_for_secret))
268
112
 
269
113
  validate_env_vars_across_secrets(all_secrets_env_vars)
270
- validate_env_vars_vs_existing_env(all_secrets_env_vars)
114
+ if not self.attributes["allow_override"]:
115
+ validate_env_vars_vs_existing_env(all_secrets_env_vars)
271
116
 
272
117
  # By this point
273
118
  # all_secrets_env_vars contains a list of dictionaries... env maps.
@@ -0,0 +1,49 @@
1
+ from typing import Any, Dict, Optional, Union
2
+
3
+ from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
4
+ from metaflow.exception import MetaflowException
5
+ from metaflow.plugins.secrets.secrets_spec import SecretSpec
6
+ from metaflow.plugins.secrets.utils import get_secrets_backend_provider
7
+
8
+
9
+ def get_secret(
10
+ source: Union[str, Dict[str, Any]], role: Optional[str] = None
11
+ ) -> Dict[str, str]:
12
+ """
13
+ Get secret from source
14
+
15
+ Parameters
16
+ ----------
17
+ source : Union[str, Dict[str, Any]]
18
+ Secret spec, defining how the secret is to be retrieved
19
+ role : str, optional
20
+ Role to use for fetching secrets
21
+ """
22
+ if role is None:
23
+ role = DEFAULT_SECRETS_ROLE
24
+
25
+ secret_spec = None
26
+
27
+ if isinstance(source, str):
28
+ secret_spec = SecretSpec.secret_spec_from_str(source, role=role)
29
+ elif isinstance(source, dict):
30
+ secret_spec = SecretSpec.secret_spec_from_dict(source, role=role)
31
+ else:
32
+ raise MetaflowException(
33
+ "get_secrets sources items must be either a string or a dict"
34
+ )
35
+
36
+ secrets_backend_provider = get_secrets_backend_provider(
37
+ secret_spec.secrets_backend_type
38
+ )
39
+ try:
40
+ dict_for_secret = secrets_backend_provider.get_secret_as_dict(
41
+ secret_spec.secret_id,
42
+ options=secret_spec.options,
43
+ role=secret_spec.role,
44
+ )
45
+ return dict_for_secret
46
+ except Exception as e:
47
+ raise MetaflowException(
48
+ "Failed to retrieve secret '%s': %s" % (secret_spec.secret_id, e)
49
+ )