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.
- metaflow/__init__.py +10 -3
- metaflow/_vendor/imghdr/__init__.py +186 -0
- metaflow/_vendor/yaml/__init__.py +427 -0
- metaflow/_vendor/yaml/composer.py +139 -0
- metaflow/_vendor/yaml/constructor.py +748 -0
- metaflow/_vendor/yaml/cyaml.py +101 -0
- metaflow/_vendor/yaml/dumper.py +62 -0
- metaflow/_vendor/yaml/emitter.py +1137 -0
- metaflow/_vendor/yaml/error.py +75 -0
- metaflow/_vendor/yaml/events.py +86 -0
- metaflow/_vendor/yaml/loader.py +63 -0
- metaflow/_vendor/yaml/nodes.py +49 -0
- metaflow/_vendor/yaml/parser.py +589 -0
- metaflow/_vendor/yaml/reader.py +185 -0
- metaflow/_vendor/yaml/representer.py +389 -0
- metaflow/_vendor/yaml/resolver.py +227 -0
- metaflow/_vendor/yaml/scanner.py +1435 -0
- metaflow/_vendor/yaml/serializer.py +111 -0
- metaflow/_vendor/yaml/tokens.py +104 -0
- metaflow/cards.py +4 -0
- metaflow/cli.py +125 -21
- metaflow/cli_components/init_cmd.py +1 -0
- metaflow/cli_components/run_cmds.py +204 -40
- metaflow/cli_components/step_cmd.py +160 -4
- metaflow/client/__init__.py +1 -0
- metaflow/client/core.py +198 -130
- metaflow/client/filecache.py +59 -32
- metaflow/cmd/code/__init__.py +2 -1
- metaflow/cmd/develop/stub_generator.py +49 -18
- metaflow/cmd/develop/stubs.py +9 -27
- metaflow/cmd/make_wrapper.py +30 -0
- metaflow/datastore/__init__.py +1 -0
- metaflow/datastore/content_addressed_store.py +40 -9
- metaflow/datastore/datastore_set.py +10 -1
- metaflow/datastore/flow_datastore.py +124 -4
- metaflow/datastore/spin_datastore.py +91 -0
- metaflow/datastore/task_datastore.py +92 -6
- metaflow/debug.py +5 -0
- metaflow/decorators.py +331 -82
- metaflow/extension_support/__init__.py +414 -356
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/flowspec.py +322 -82
- metaflow/graph.py +178 -15
- metaflow/includefile.py +25 -3
- metaflow/lint.py +94 -3
- metaflow/meta_files.py +13 -0
- metaflow/metadata_provider/metadata.py +13 -2
- metaflow/metaflow_config.py +66 -4
- metaflow/metaflow_environment.py +91 -25
- metaflow/metaflow_profile.py +18 -0
- metaflow/metaflow_version.py +16 -1
- metaflow/package/__init__.py +673 -0
- metaflow/packaging_sys/__init__.py +880 -0
- metaflow/packaging_sys/backend.py +128 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +99 -0
- metaflow/packaging_sys/utils.py +54 -0
- metaflow/packaging_sys/v1.py +527 -0
- metaflow/parameters.py +6 -2
- metaflow/plugins/__init__.py +6 -0
- metaflow/plugins/airflow/airflow.py +11 -1
- metaflow/plugins/airflow/airflow_cli.py +16 -5
- metaflow/plugins/argo/argo_client.py +42 -20
- metaflow/plugins/argo/argo_events.py +6 -6
- metaflow/plugins/argo/argo_workflows.py +1023 -344
- metaflow/plugins/argo/argo_workflows_cli.py +396 -94
- metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
- metaflow/plugins/argo/capture_error.py +5 -2
- metaflow/plugins/argo/conditional_input_paths.py +35 -0
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/argo/param_val.py +19 -0
- metaflow/plugins/aws/aws_client.py +6 -0
- metaflow/plugins/aws/aws_utils.py +33 -1
- metaflow/plugins/aws/batch/batch.py +72 -5
- metaflow/plugins/aws/batch/batch_cli.py +24 -3
- metaflow/plugins/aws/batch/batch_decorator.py +57 -6
- metaflow/plugins/aws/step_functions/step_functions.py +28 -3
- metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
- metaflow/plugins/cards/card_cli.py +20 -1
- metaflow/plugins/cards/card_creator.py +24 -1
- metaflow/plugins/cards/card_datastore.py +21 -49
- metaflow/plugins/cards/card_decorator.py +58 -6
- metaflow/plugins/cards/card_modules/basic.py +38 -9
- metaflow/plugins/cards/card_modules/bundle.css +1 -1
- metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
- metaflow/plugins/cards/card_modules/components.py +592 -3
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
- metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
- metaflow/plugins/cards/card_modules/main.css +1 -0
- metaflow/plugins/cards/card_modules/main.js +56 -41
- metaflow/plugins/cards/card_modules/test_cards.py +22 -6
- metaflow/plugins/cards/component_serializer.py +1 -8
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/catch_decorator.py +9 -0
- metaflow/plugins/datastores/local_storage.py +12 -6
- metaflow/plugins/datastores/spin_storage.py +12 -0
- metaflow/plugins/datatools/s3/s3.py +49 -17
- metaflow/plugins/datatools/s3/s3op.py +113 -66
- metaflow/plugins/env_escape/client_modules.py +102 -72
- metaflow/plugins/events_decorator.py +127 -121
- metaflow/plugins/exit_hook/__init__.py +0 -0
- metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
- metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
- metaflow/plugins/kubernetes/kubernetes.py +12 -1
- metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
- metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
- metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
- metaflow/plugins/metadata_providers/local.py +76 -82
- metaflow/plugins/metadata_providers/service.py +13 -9
- metaflow/plugins/metadata_providers/spin.py +16 -0
- metaflow/plugins/package_cli.py +36 -24
- metaflow/plugins/parallel_decorator.py +11 -2
- metaflow/plugins/parsers.py +16 -0
- metaflow/plugins/pypi/bootstrap.py +7 -1
- metaflow/plugins/pypi/conda_decorator.py +41 -82
- metaflow/plugins/pypi/conda_environment.py +14 -6
- metaflow/plugins/pypi/micromamba.py +9 -1
- metaflow/plugins/pypi/pip.py +41 -5
- metaflow/plugins/pypi/pypi_decorator.py +4 -4
- metaflow/plugins/pypi/utils.py +22 -0
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +14 -178
- metaflow/plugins/secrets/secrets_func.py +49 -0
- metaflow/plugins/secrets/secrets_spec.py +101 -0
- metaflow/plugins/secrets/utils.py +74 -0
- metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
- metaflow/plugins/timeout_decorator.py +0 -1
- metaflow/plugins/uv/bootstrap.py +29 -1
- metaflow/plugins/uv/uv_environment.py +5 -3
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/click_api.py +79 -26
- metaflow/runner/deployer.py +208 -6
- metaflow/runner/deployer_impl.py +32 -12
- metaflow/runner/metaflow_runner.py +266 -33
- metaflow/runner/subprocess_manager.py +21 -1
- metaflow/runner/utils.py +27 -16
- metaflow/runtime.py +660 -66
- metaflow/task.py +255 -26
- metaflow/user_configs/config_options.py +33 -21
- metaflow/user_configs/config_parameters.py +220 -58
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +512 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +264 -0
- metaflow/user_decorators/user_step_decorator.py +749 -0
- metaflow/util.py +197 -7
- metaflow/vendor.py +23 -7
- metaflow/version.py +1 -1
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
- metaflow/_vendor/v3_5/__init__.py +0 -1
- metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
- metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
- metaflow/_vendor/v3_5/zipp.py +0 -329
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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__(
|
|
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
|
-
|
|
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
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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.
|
|
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__(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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"):
|
metaflow/plugins/pypi/pip.py
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
metaflow/plugins/pypi/utils.py
CHANGED
|
@@ -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)
|