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
@@ -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
- from metaflow.metadata import MetaDatum
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 ... import INFO_FILE
10
+ from metaflow.packaging_sys import ContentType
16
11
 
17
12
 
18
13
  class CondaStepDecorator(StepDecorator):
@@ -45,19 +40,36 @@ 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):
53
- super(CondaStepDecorator, self).__init__(attributes, statically_defined)
51
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
52
+ self._attributes_with_user_values = (
53
+ set(attributes.keys()) if attributes is not None else set()
54
+ )
55
+
56
+ super(CondaStepDecorator, self).__init__(
57
+ attributes, statically_defined, inserted_by
58
+ )
54
59
 
60
+ def init(self):
55
61
  # Support legacy 'libraries=' attribute for the decorator.
56
62
  self.attributes["packages"] = {
57
63
  **self.attributes["libraries"],
58
64
  **self.attributes["packages"],
59
65
  }
60
- del self.attributes["libraries"]
66
+ # Keep because otherwise make_decorator_spec will fail
67
+ self.attributes["libraries"] = {}
68
+ if self.attributes["packages"]:
69
+ self._attributes_with_user_values.add("packages")
70
+
71
+ def is_attribute_user_defined(self, name):
72
+ return name in self._attributes_with_user_values
61
73
 
62
74
  def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
63
75
  # The init_environment hook for Environment creates the relevant virtual
@@ -71,11 +83,16 @@ class CondaStepDecorator(StepDecorator):
71
83
 
72
84
  # Support flow-level decorator.
73
85
  if "conda_base" in self.flow._flow_decorators:
74
- super_attributes = self.flow._flow_decorators["conda_base"][0].attributes
86
+ conda_base = self.flow._flow_decorators["conda_base"][0]
87
+ super_attributes = conda_base.attributes
75
88
  self.attributes["packages"] = {
76
89
  **super_attributes["packages"],
77
90
  **self.attributes["packages"],
78
91
  }
92
+ self._attributes_with_user_values.update(
93
+ conda_base._attributes_with_user_values
94
+ )
95
+
79
96
  self.attributes["python"] = (
80
97
  self.attributes["python"] or super_attributes["python"]
81
98
  )
@@ -100,6 +117,10 @@ class CondaStepDecorator(StepDecorator):
100
117
  # --environment=pypi to --environment=conda
101
118
  _supported_virtual_envs.extend(["pypi"])
102
119
 
120
+ # TODO: Hardcoded for now to support the fast bakery environment.
121
+ # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
122
+ _supported_virtual_envs.extend(["fast-bakery"])
123
+
103
124
  # The --environment= requirement ensures that valid virtual environments are
104
125
  # created for every step to execute it, greatly simplifying the @conda
105
126
  # implementation.
@@ -131,67 +152,17 @@ class CondaStepDecorator(StepDecorator):
131
152
  def runtime_init(self, flow, graph, package, run_id):
132
153
  if self.disabled:
133
154
  return
134
- # Create a symlink to metaflow installed outside the virtual environment.
135
- self.metaflow_dir = tempfile.TemporaryDirectory(dir="/tmp")
136
- os.symlink(
137
- os.path.join(get_metaflow_root(), "metaflow"),
138
- os.path.join(self.metaflow_dir.name, "metaflow"),
139
- )
140
-
141
- info = os.path.join(get_metaflow_root(), os.path.basename(INFO_FILE))
142
- # Symlink the INFO file as well to properly propagate down the Metaflow version
143
- if os.path.isfile(info):
144
- os.symlink(
145
- 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
146
165
  )
147
- else:
148
- # If there is no info file, we will actually create one in this new
149
- # place because we won't be able to properly resolve the EXT_PKG extensions
150
- # the same way as outside conda (looking at distributions, etc.). In a
151
- # Conda environment, as shown below (where we set self.addl_paths), all
152
- # EXT_PKG extensions are PYTHONPATH extensions. Instead of re-resolving,
153
- # we use the resolved information that is written out to the INFO file.
154
- with open(
155
- os.path.join(self.metaflow_dir.name, os.path.basename(INFO_FILE)),
156
- mode="wt",
157
- encoding="utf-8",
158
- ) as f:
159
- f.write(
160
- json.dumps(
161
- self.environment.get_environment_info(include_ext_info=True)
162
- )
163
- )
164
-
165
- # Support metaflow extensions.
166
- self.addl_paths = None
167
- try:
168
- m = importlib.import_module(EXT_PKG)
169
- except ImportError:
170
- # No additional check needed because if we are here, we already checked
171
- # for other issues when loading at the toplevel.
172
- pass
173
- else:
174
- custom_paths = list(set(m.__path__))
175
- # For some reason, at times, unique paths appear multiple times. We
176
- # simplify to avoid un-necessary links.
177
-
178
- if len(custom_paths) == 1:
179
- # Regular package; we take a quick shortcut here.
180
- os.symlink(
181
- custom_paths[0],
182
- os.path.join(self.metaflow_dir.name, EXT_PKG),
183
- )
184
- else:
185
- # This is a namespace package, we therefore create a bunch of
186
- # directories so that we can symlink in those separately, and we will
187
- # add those paths to the PYTHONPATH for the interpreter. Note that we
188
- # don't symlink to the parent of the package because that could end up
189
- # including more stuff we don't want
190
- self.addl_paths = []
191
- for p in custom_paths:
192
- temp_dir = tempfile.mkdtemp(dir=self.metaflow_dir.name)
193
- os.symlink(p, os.path.join(temp_dir, EXT_PKG))
194
- self.addl_paths.append(temp_dir)
195
166
 
196
167
  # # Also install any environment escape overrides directly here to enable
197
168
  # # the escape to work even in non metaflow-created subprocesses
@@ -206,7 +177,8 @@ class CondaStepDecorator(StepDecorator):
206
177
  self.interpreter = (
207
178
  self.environment.interpreter(self.step)
208
179
  if not any(
209
- decorator.name in ["batch", "kubernetes"]
180
+ decorator.name
181
+ in ["batch", "kubernetes", "nvidia", "snowpark", "slurm", "nvct"]
210
182
  for decorator in next(
211
183
  step for step in self.flow if step.name == self.step
212
184
  ).decorators
@@ -269,11 +241,17 @@ class CondaStepDecorator(StepDecorator):
269
241
  if self.disabled:
270
242
  return
271
243
  # Ensure local installation of Metaflow is visible to user code
272
- python_path = self.metaflow_dir.name
273
- if self.addl_paths is not None:
274
- addl_paths = os.pathsep.join(self.addl_paths)
275
- python_path = os.pathsep.join([addl_paths, python_path])
276
- 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)
277
255
  if self.interpreter:
278
256
  # https://github.com/conda/conda/issues/7707
279
257
  # Also ref - https://github.com/Netflix/metaflow/pull/178
@@ -284,7 +262,9 @@ class CondaStepDecorator(StepDecorator):
284
262
  def runtime_finished(self, exception):
285
263
  if self.disabled:
286
264
  return
287
- 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
288
268
 
289
269
 
290
270
  class CondaFlowDecorator(FlowDecorator):
@@ -317,21 +297,38 @@ class CondaFlowDecorator(FlowDecorator):
317
297
  "disabled": None,
318
298
  }
319
299
 
320
- def __init__(self, attributes=None, statically_defined=False):
321
- super(CondaFlowDecorator, self).__init__(attributes, statically_defined)
300
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
301
+ self._attributes_with_user_values = (
302
+ set(attributes.keys()) if attributes is not None else set()
303
+ )
304
+
305
+ super(CondaFlowDecorator, self).__init__(
306
+ attributes, statically_defined, inserted_by
307
+ )
322
308
 
309
+ def init(self):
323
310
  # Support legacy 'libraries=' attribute for the decorator.
324
311
  self.attributes["packages"] = {
325
312
  **self.attributes["libraries"],
326
313
  **self.attributes["packages"],
327
314
  }
328
- del self.attributes["libraries"]
315
+ # Keep because otherwise make_decorator_spec will fail
316
+ self.attributes["libraries"] = {}
329
317
  if self.attributes["python"]:
330
318
  self.attributes["python"] = str(self.attributes["python"])
331
319
 
320
+ def is_attribute_user_defined(self, name):
321
+ return name in self._attributes_with_user_values
322
+
332
323
  def flow_init(
333
324
  self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
334
325
  ):
326
+ # NOTE: Important for extensions implementing custom virtual environments.
327
+ # Without this steps will not have an implicit conda step decorator on them unless the environment adds one in its decospecs.
328
+ from metaflow import decorators
329
+
330
+ decorators._attach_decorators(flow, ["conda"])
331
+
335
332
  # @conda uses a conda environment to create a virtual environment.
336
333
  # The conda environment can be created through micromamba.
337
334
  _supported_virtual_envs = ["conda"]
@@ -340,6 +337,10 @@ class CondaFlowDecorator(FlowDecorator):
340
337
  # --environment=pypi to --environment=conda
341
338
  _supported_virtual_envs.extend(["pypi"])
342
339
 
340
+ # TODO: Hardcoded for now to support the fast bakery environment.
341
+ # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
342
+ _supported_virtual_envs.extend(["fast-bakery"])
343
+
343
344
  # The --environment= requirement ensures that valid virtual environments are
344
345
  # created for every step to execute it, greatly simplifying the @conda
345
346
  # implementation.
@@ -5,21 +5,19 @@ import functools
5
5
  import io
6
6
  import json
7
7
  import os
8
- import sys
9
8
  import tarfile
10
- import time
11
- from concurrent.futures import ThreadPoolExecutor
9
+ import threading
10
+ from concurrent.futures import ThreadPoolExecutor, as_completed
11
+ from functools import wraps
12
12
  from hashlib import sha256
13
13
  from io import BufferedIOBase, BytesIO
14
- from itertools import chain
15
14
  from urllib.parse import unquote, urlparse
16
15
 
17
- import requests
18
-
16
+ from metaflow.debug import debug
19
17
  from metaflow.exception import MetaflowException
20
18
  from metaflow.metaflow_config import get_pinned_conda_libs
21
19
  from metaflow.metaflow_environment import MetaflowEnvironment
22
- from metaflow.metaflow_profile import profile
20
+ from metaflow.packaging_sys import ContentType
23
21
 
24
22
  from . import MAGIC_FILE, _datastore_packageroot
25
23
  from .utils import conda_platform
@@ -35,8 +33,10 @@ class CondaEnvironmentException(MetaflowException):
35
33
  class CondaEnvironment(MetaflowEnvironment):
36
34
  TYPE = "conda"
37
35
  _filecache = None
36
+ _force_rebuild = False
38
37
 
39
38
  def __init__(self, flow):
39
+ super().__init__(flow)
40
40
  self.flow = flow
41
41
 
42
42
  def set_local_root(self, local_root):
@@ -48,9 +48,8 @@ class CondaEnvironment(MetaflowEnvironment):
48
48
  # Apply conda decorator to manage the task execution lifecycle.
49
49
  return ("conda",) + super().decospecs()
50
50
 
51
- def validate_environment(self, echo, datastore_type):
51
+ def validate_environment(self, logger, datastore_type):
52
52
  self.datastore_type = datastore_type
53
- self.echo = echo
54
53
 
55
54
  # Avoiding circular imports.
56
55
  from metaflow.plugins import DATASTORES
@@ -62,10 +61,23 @@ class CondaEnvironment(MetaflowEnvironment):
62
61
  from .micromamba import Micromamba
63
62
  from .pip import Pip
64
63
 
65
- micromamba = Micromamba()
66
- self.solvers = {"conda": micromamba, "pypi": Pip(micromamba)}
64
+ print_lock = threading.Lock()
65
+
66
+ def make_thread_safe(func):
67
+ @wraps(func)
68
+ def wrapper(*args, **kwargs):
69
+ with print_lock:
70
+ return func(*args, **kwargs)
71
+
72
+ return wrapper
73
+
74
+ self.logger = make_thread_safe(logger)
67
75
 
68
- def init_environment(self, echo):
76
+ # TODO: Wire up logging
77
+ micromamba = Micromamba(self.logger, self._force_rebuild)
78
+ self.solvers = {"conda": micromamba, "pypi": Pip(micromamba, self.logger)}
79
+
80
+ def init_environment(self, echo, only_steps=None):
69
81
  # The implementation optimizes for latency to ensure as many operations can
70
82
  # be turned into cheap no-ops as feasible. Otherwise, we focus on maintaining
71
83
  # a balance between latency and maintainability of code without re-implementing
@@ -77,6 +89,8 @@ class CondaEnvironment(MetaflowEnvironment):
77
89
  def environments(type_):
78
90
  seen = set()
79
91
  for step in self.flow:
92
+ if only_steps and step.name not in only_steps:
93
+ continue
80
94
  environment = self.get_environment(step)
81
95
  if type_ in environment and environment["id_"] not in seen:
82
96
  seen.add(environment["id_"])
@@ -96,7 +110,10 @@ class CondaEnvironment(MetaflowEnvironment):
96
110
  return (
97
111
  id_,
98
112
  (
99
- 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
+ )
100
117
  or self.write_to_environment_manifest(
101
118
  [id_, platform, type_],
102
119
  self.solvers[type_].solve(id_, **environment),
@@ -107,6 +124,11 @@ class CondaEnvironment(MetaflowEnvironment):
107
124
  )
108
125
 
109
126
  def cache(storage, results, type_):
127
+ debug.conda_exec(
128
+ "Caching packages for %s environments %s"
129
+ % (type_, [result[0] for result in results])
130
+ )
131
+
110
132
  def _path(url, local_path):
111
133
  # Special handling for VCS packages
112
134
  if url.startswith("git+"):
@@ -137,7 +159,7 @@ class CondaEnvironment(MetaflowEnvironment):
137
159
  _meta = copy.deepcopy(local_packages)
138
160
  for id_, packages, _, _ in results:
139
161
  for package in packages:
140
- if package.get("path"):
162
+ if package.get("path") and not self._force_rebuild:
141
163
  # Cache only those packages that manifest is unaware of
142
164
  local_packages.pop(package["url"], None)
143
165
  else:
@@ -148,6 +170,9 @@ class CondaEnvironment(MetaflowEnvironment):
148
170
  (
149
171
  package["path"],
150
172
  # Lazily fetch package from the interweb if needed.
173
+ # TODO: Depending on the len_hint, the package might be downloaded from
174
+ # the interweb prematurely. save_bytes needs to be adjusted to handle
175
+ # this scenario.
151
176
  LazyOpen(
152
177
  package["local_path"],
153
178
  "rb",
@@ -156,41 +181,131 @@ class CondaEnvironment(MetaflowEnvironment):
156
181
  )
157
182
  for url, package in local_packages.items()
158
183
  ]
184
+ debug.conda_exec(
185
+ "Caching %s new packages to the datastore for %s environment %s"
186
+ % (
187
+ len(list_of_path_and_filehandle),
188
+ type_,
189
+ [result[0] for result in results],
190
+ )
191
+ )
159
192
  storage.save_bytes(
160
193
  list_of_path_and_filehandle,
161
194
  len_hint=len(list_of_path_and_filehandle),
195
+ overwrite=self._force_rebuild,
162
196
  )
163
197
  for id_, packages, _, platform in results:
164
198
  if id_ in dirty:
165
199
  self.write_to_environment_manifest([id_, platform, type_], packages)
166
200
 
167
- # First resolve environments through Conda, before PyPI.
168
- echo("Bootstrapping virtual environment(s) ...")
169
- for solver in ["conda", "pypi"]:
170
- with ThreadPoolExecutor() as executor:
171
- results = list(
172
- executor.map(lambda x: solve(*x, solver), environments(solver))
201
+ debug.conda_exec("Finished caching packages.")
202
+
203
+ storage = None
204
+ if self.datastore_type not in ["local"]:
205
+ # Initialize storage for caching if using a remote datastore
206
+ storage = self.datastore(_datastore_packageroot(self.datastore, echo))
207
+
208
+ self.logger("Bootstrapping virtual environment(s) ...")
209
+ # Sequence of operations:
210
+ # 1. Start all conda solves in parallel
211
+ # 2. Download conda packages sequentially
212
+ # 3. Create and cache conda environments in parallel
213
+ # 4. Start PyPI solves in parallel after each conda environment is created
214
+ # 5. Download PyPI packages sequentially
215
+ # 6. Create and cache PyPI environments in parallel
216
+ with ThreadPoolExecutor() as executor:
217
+ # Start all conda solves in parallel
218
+ debug.conda_exec("Solving packages for Conda environments..")
219
+ conda_solve_futures = [
220
+ executor.submit(lambda x: solve(*x, "conda"), env)
221
+ for env in environments("conda")
222
+ ]
223
+ conda_create_futures = []
224
+
225
+ pypi_envs = {env[0]: env for env in environments("pypi")}
226
+ pypi_solve_futures = []
227
+ pypi_create_futures = []
228
+
229
+ cache_futures = []
230
+ # Process conda results sequentially for downloads
231
+ for future in as_completed(conda_solve_futures):
232
+ result = future.result()
233
+ # Sequential conda download
234
+ debug.conda_exec(
235
+ "Downloading packages for Conda environment %s" % result[0]
173
236
  )
174
- _ = list(map(lambda x: self.solvers[solver].download(*x), results))
175
- with ThreadPoolExecutor() as executor:
176
- _ = list(
177
- executor.map(lambda x: self.solvers[solver].create(*x), results)
237
+ self.solvers["conda"].download(*result)
238
+ # Parallel conda create and cache
239
+ conda_create_future = executor.submit(
240
+ self.solvers["conda"].create, *result
178
241
  )
179
- if self.datastore_type not in ["local"]:
180
- # Cache packages only when a remote datastore is in play.
181
- storage = self.datastore(
182
- _datastore_packageroot(self.datastore, self.echo)
242
+ if storage:
243
+ cache_futures.append(
244
+ executor.submit(cache, storage, [result], "conda")
245
+ )
246
+
247
+ # Queue PyPI solve to start after conda create
248
+ if result[0] in pypi_envs:
249
+ debug.conda_exec(
250
+ "Solving packages for PyPI environment %s" % result[0]
251
+ )
252
+ # solve pypi envs uniquely
253
+ pypi_env = pypi_envs.pop(result[0])
254
+
255
+ def pypi_solve(env):
256
+ conda_create_future.result() # Wait for conda create
257
+ return solve(*env, "pypi")
258
+
259
+ pypi_solve_futures.append(executor.submit(pypi_solve, pypi_env))
260
+ else:
261
+ # add conda create future to the generic list
262
+ conda_create_futures.append(conda_create_future)
263
+
264
+ # Process PyPI results sequentially for downloads
265
+ for solve_future in as_completed(pypi_solve_futures):
266
+ result = solve_future.result()
267
+ # Sequential PyPI download
268
+ debug.conda_exec(
269
+ "Downloading packages for PyPI environment %s" % result[0]
270
+ )
271
+ self.solvers["pypi"].download(*result)
272
+ # Parallel PyPI create and cache
273
+ pypi_create_futures.append(
274
+ executor.submit(self.solvers["pypi"].create, *result)
183
275
  )
184
- cache(storage, results, solver)
185
- echo("Virtual environment(s) bootstrapped!")
276
+ if storage:
277
+ cache_futures.append(
278
+ executor.submit(cache, storage, [result], "pypi")
279
+ )
280
+
281
+ # Raise exceptions for conda create
282
+ debug.conda_exec("Checking results for Conda create..")
283
+ for future in as_completed(conda_create_futures):
284
+ future.result()
285
+
286
+ # Raise exceptions for pypi create
287
+ debug.conda_exec("Checking results for PyPI create..")
288
+ for future in as_completed(pypi_create_futures):
289
+ future.result()
290
+
291
+ # Raise exceptions for caching
292
+ debug.conda_exec("Checking results for caching..")
293
+ for future in as_completed(cache_futures):
294
+ # check for result in order to raise any exceptions.
295
+ future.result()
296
+
297
+ self.logger("Virtual environment(s) bootstrapped!")
186
298
 
187
299
  def executable(self, step_name, default=None):
188
- step = next(step for step in self.flow if step.name == step_name)
300
+ step = next((step for step in self.flow if step.name == step_name), None)
301
+ if step is None:
302
+ # requesting internal steps e.g. _parameters
303
+ return super().executable(step_name, default)
189
304
  id_ = self.get_environment(step).get("id_")
190
305
  if id_:
191
306
  # bootstrap.py is responsible for ensuring the validity of this executable.
192
307
  # -s is important! Can otherwise leak packages to other environments.
193
- return os.path.join("linux-64", id_, "bin/python -s")
308
+ return os.path.join("$MF_ARCH", id_, "bin/python -s")
194
309
  else:
195
310
  # for @conda/@pypi(disabled=True).
196
311
  return super().executable(step_name, default)
@@ -220,9 +335,9 @@ class CondaEnvironment(MetaflowEnvironment):
220
335
  disabled = decorator.attributes["disabled"]
221
336
  if not disabled or str(disabled).lower() == "false":
222
337
  environment[decorator.name] = {
223
- k: decorator.attributes[k]
338
+ k: copy.deepcopy(decorator.attributes[k])
224
339
  for k in decorator.attributes
225
- if k != "disabled"
340
+ if k not in ("disabled", "libraries")
226
341
  }
227
342
  else:
228
343
  return {}
@@ -263,14 +378,21 @@ class CondaEnvironment(MetaflowEnvironment):
263
378
  # 5. All resolved packages (Conda or PyPI) are cached
264
379
  # 6. PyPI packages are only installed for local platform
265
380
 
266
- # Resolve `linux-64` Conda environments if @batch or @kubernetes are in play
267
381
  target_platform = conda_platform()
268
382
  for decorator in step.decorators:
269
- # TODO: rather than relying on decorator names, rely on attributes
270
- # to make them extensible.
271
- if decorator.name in ["batch", "kubernetes", "nvidia"]:
272
- # TODO: Support arm architectures
273
- target_platform = "linux-64"
383
+ # NOTE: Keep the list of supported decorator names for backward compatibility purposes.
384
+ # Older versions did not implement the 'support_conda_environment' attribute.
385
+ if getattr(
386
+ decorator, "supports_conda_environment", False
387
+ ) or decorator.name in [
388
+ "batch",
389
+ "kubernetes",
390
+ "nvidia",
391
+ "snowpark",
392
+ "slurm",
393
+ "nvct",
394
+ ]:
395
+ target_platform = getattr(decorator, "target_platform", "linux-64")
274
396
  break
275
397
 
276
398
  environment["conda"]["platforms"] = [target_platform]
@@ -288,19 +410,20 @@ class CondaEnvironment(MetaflowEnvironment):
288
410
  # PyPI registries, the usage of environment variable `GOOGLE_APPLICATION_CREDENTIALS`
289
411
  # demands that `keyrings.google-artifactregistry-auth` has to be installed
290
412
  # and available in the underlying python environment.
291
- if os.getenv("GOOGLE_APPLICATION_CREDENTIALS"):
292
- environment["conda"]["packages"][
293
- "keyrings.google-artifactregistry-auth"
294
- ] = ">=1.1.1"
413
+
414
+ # commenting this out per https://outerboundsco.slack.com/archives/C040K733FND/p1719262399355449
415
+ # this should be a temporary workaround. Need to find a better fix
416
+ # if os.getenv("GOOGLE_APPLICATION_CREDENTIALS"):
417
+ # environment["conda"]["packages"][
418
+ # "keyrings.google-artifactregistry-auth"
419
+ # ] = ">=1.1.1"
295
420
 
296
421
  # Z combinator for a recursive lambda
297
422
  deep_sort = (lambda f: f(f))(
298
423
  lambda f: lambda obj: (
299
424
  {k: f(f)(v) for k, v in sorted(obj.items())}
300
425
  if isinstance(obj, dict)
301
- else sorted([f(f)(e) for e in obj])
302
- if isinstance(obj, list)
303
- else obj
426
+ else sorted([f(f)(e) for e in obj]) if isinstance(obj, list) else obj
304
427
  )
305
428
  )
306
429
 
@@ -316,7 +439,7 @@ class CondaEnvironment(MetaflowEnvironment):
316
439
  **environment,
317
440
  **{
318
441
  "package_root": _datastore_packageroot(
319
- self.datastore, self.echo
442
+ self.datastore, self.logger
320
443
  )
321
444
  },
322
445
  }
@@ -356,7 +479,9 @@ class CondaEnvironment(MetaflowEnvironment):
356
479
  files = []
357
480
  manifest = self.get_environment_manifest_path()
358
481
  if os.path.exists(manifest):
359
- files.append((manifest, os.path.basename(manifest)))
482
+ files.append(
483
+ (manifest, os.path.basename(manifest), ContentType.OTHER_CONTENT)
484
+ )
360
485
  return files
361
486
 
362
487
  def bootstrap_commands(self, step_name, datastore_type):
@@ -366,14 +491,18 @@ class CondaEnvironment(MetaflowEnvironment):
366
491
  if id_:
367
492
  return [
368
493
  "echo 'Bootstrapping virtual environment...'",
494
+ "flush_mflogs",
369
495
  # We have to prevent the tracing module from loading,
370
496
  # as the bootstrapping process uses the internal S3 client which would fail to import tracing
371
497
  # due to the required dependencies being bundled into the conda environment,
372
498
  # which is yet to be initialized at this point.
373
- 'DISABLE_TRACING=True python -m metaflow.plugins.pypi.bootstrap "%s" %s "%s" linux-64'
499
+ 'DISABLE_TRACING=True python -m metaflow.plugins.pypi.bootstrap "%s" %s "%s"'
374
500
  % (self.flow.name, id_, self.datastore_type),
375
501
  "echo 'Environment bootstrapped.'",
376
- "export PATH=$PATH:$(pwd)/micromamba",
502
+ "flush_mflogs",
503
+ # To avoid having to install micromamba in the PATH in micromamba.py, we add it to the PATH here.
504
+ "export PATH=$PATH:$(pwd)/micromamba/bin",
505
+ "export MF_ARCH=$(case $(uname)/$(uname -m) in Darwin/arm64)echo osx-arm64;;Darwin/*)echo osx-64;;Linux/aarch64)echo linux-aarch64;;*)echo linux-64;;esac)",
377
506
  ]
378
507
  else:
379
508
  # for @conda/@pypi(disabled=True).
@@ -434,6 +563,7 @@ class LazyOpen(BufferedIOBase):
434
563
  self._file = None
435
564
  self._buffer = None
436
565
  self._position = 0
566
+ self.requests = None
437
567
 
438
568
  def _ensure_file(self):
439
569
  if not self._file:
@@ -450,8 +580,13 @@ class LazyOpen(BufferedIOBase):
450
580
  raise ValueError("Both filename and url are missing")
451
581
 
452
582
  def _download_to_buffer(self):
583
+ if self.requests is None:
584
+ # TODO: Remove dependency on requests
585
+ import requests
586
+
587
+ self.requests = requests
453
588
  # TODO: Stream it in chunks?
454
- response = requests.get(self.url, stream=True)
589
+ response = self.requests.get(self.url, stream=True)
455
590
  response.raise_for_status()
456
591
  return response.content
457
592