ob-metaflow 2.15.0.1__py2.py3-none-any.whl → 2.15.5.1__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.
Potentially problematic release.
This version of ob-metaflow might be problematic. Click here for more details.
- metaflow/__init__.py +6 -0
- metaflow/cmd/code/__init__.py +230 -0
- metaflow/cmd/develop/stub_generator.py +5 -2
- metaflow/cmd/main_cli.py +1 -0
- metaflow/cmd/make_wrapper.py +35 -3
- metaflow/extension_support/plugins.py +1 -0
- metaflow/metaflow_config.py +2 -0
- metaflow/metaflow_environment.py +3 -1
- metaflow/mflog/__init__.py +4 -3
- metaflow/plugins/__init__.py +14 -0
- metaflow/plugins/argo/argo_client.py +9 -2
- metaflow/plugins/argo/argo_workflows.py +79 -28
- metaflow/plugins/argo/argo_workflows_cli.py +16 -25
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +5 -2
- metaflow/plugins/cards/card_modules/main.js +52 -50
- metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -1
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +2 -0
- metaflow/plugins/metadata_providers/service.py +16 -7
- metaflow/plugins/pypi/bootstrap.py +17 -26
- metaflow/plugins/pypi/conda_environment.py +8 -8
- metaflow/plugins/pypi/parsers.py +268 -0
- metaflow/plugins/pypi/utils.py +18 -0
- metaflow/runner/click_api.py +5 -1
- metaflow/runner/deployer.py +3 -2
- metaflow/version.py +1 -1
- {ob_metaflow-2.15.0.1.data → ob_metaflow-2.15.5.1.data}/data/share/metaflow/devtools/Makefile +36 -17
- {ob_metaflow-2.15.0.1.data → ob_metaflow-2.15.5.1.data}/data/share/metaflow/devtools/Tiltfile +29 -10
- ob_metaflow-2.15.5.1.dist-info/METADATA +87 -0
- {ob_metaflow-2.15.0.1.dist-info → ob_metaflow-2.15.5.1.dist-info}/RECORD +34 -32
- {ob_metaflow-2.15.0.1.dist-info → ob_metaflow-2.15.5.1.dist-info}/WHEEL +1 -1
- ob_metaflow-2.15.0.1.dist-info/METADATA +0 -94
- {ob_metaflow-2.15.0.1.data → ob_metaflow-2.15.5.1.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
- {ob_metaflow-2.15.0.1.dist-info → ob_metaflow-2.15.5.1.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.15.0.1.dist-info → ob_metaflow-2.15.5.1.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.15.0.1.dist-info → ob_metaflow-2.15.5.1.dist-info}/top_level.txt +0 -0
|
@@ -29,6 +29,7 @@ from metaflow.metaflow_config import (
|
|
|
29
29
|
KUBERNETES_SHARED_MEMORY,
|
|
30
30
|
KUBERNETES_TOLERATIONS,
|
|
31
31
|
KUBERNETES_QOS,
|
|
32
|
+
KUBERNETES_CONDA_ARCH,
|
|
32
33
|
)
|
|
33
34
|
from metaflow.plugins.resources_decorator import ResourcesDecorator
|
|
34
35
|
from metaflow.plugins.timeout_decorator import get_run_time_limit_for_task
|
|
@@ -160,7 +161,7 @@ class KubernetesDecorator(StepDecorator):
|
|
|
160
161
|
|
|
161
162
|
# Conda environment support
|
|
162
163
|
supports_conda_environment = True
|
|
163
|
-
target_platform = "linux-64"
|
|
164
|
+
target_platform = KUBERNETES_CONDA_ARCH or "linux-64"
|
|
164
165
|
|
|
165
166
|
def init(self):
|
|
166
167
|
super(KubernetesDecorator, self).init()
|
|
@@ -319,6 +319,8 @@ class RunningJobSet(object):
|
|
|
319
319
|
def kill(self):
|
|
320
320
|
plural = "jobsets"
|
|
321
321
|
client = self._client.get()
|
|
322
|
+
if not (self.is_running or self.is_waiting):
|
|
323
|
+
return
|
|
322
324
|
try:
|
|
323
325
|
# Killing the control pod will trigger the jobset to mark everything as failed.
|
|
324
326
|
# Since jobsets have a successPolicy set to `All` which ensures that everything has
|
|
@@ -350,23 +350,32 @@ class ServiceMetadataProvider(MetadataProvider):
|
|
|
350
350
|
List[str]
|
|
351
351
|
List of task pathspecs that satisfy the query
|
|
352
352
|
"""
|
|
353
|
-
query_params = {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
query_params = {}
|
|
354
|
+
|
|
355
|
+
if pattern == ".*":
|
|
356
|
+
# we do not need to filter tasks at all if pattern allows 'any'
|
|
357
|
+
query_params = {}
|
|
358
|
+
else:
|
|
359
|
+
if field_name:
|
|
360
|
+
query_params["metadata_field_name"] = field_name
|
|
361
|
+
if pattern:
|
|
362
|
+
query_params["pattern"] = pattern
|
|
363
|
+
|
|
358
364
|
url = ServiceMetadataProvider._obj_path(flow_name, run_id, step_name)
|
|
359
365
|
url = f"{url}/filtered_tasks?{urlencode(query_params)}"
|
|
366
|
+
|
|
360
367
|
try:
|
|
361
|
-
resp = cls._request(None, url, "GET")
|
|
368
|
+
resp, _ = cls._request(None, url, "GET")
|
|
362
369
|
except Exception as e:
|
|
363
370
|
if e.http_code == 404:
|
|
364
371
|
# filter_tasks_by_metadata endpoint does not exist in the version of metadata service
|
|
365
372
|
# deployed currently. Raise a more informative error message.
|
|
366
373
|
raise MetaflowInternalError(
|
|
367
374
|
"The version of metadata service deployed currently does not support filtering tasks by metadata. "
|
|
368
|
-
"Upgrade Metadata service to version 2.
|
|
375
|
+
"Upgrade Metadata service to version 2.5.0 or greater to use this feature."
|
|
369
376
|
) from e
|
|
377
|
+
# Other unknown exception
|
|
378
|
+
raise e
|
|
370
379
|
return resp
|
|
371
380
|
|
|
372
381
|
@staticmethod
|
|
@@ -8,6 +8,7 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
import tarfile
|
|
10
10
|
import time
|
|
11
|
+
import platform
|
|
11
12
|
from urllib.error import URLError
|
|
12
13
|
from urllib.request import urlopen
|
|
13
14
|
from metaflow.metaflow_config import DATASTORE_LOCAL_DIR, CONDA_USE_FAST_INIT
|
|
@@ -36,29 +37,6 @@ def timer(func):
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
if __name__ == "__main__":
|
|
39
|
-
# TODO: Detect architecture on the fly when dealing with arm architectures.
|
|
40
|
-
# ARCH=$(uname -m)
|
|
41
|
-
# OS=$(uname)
|
|
42
|
-
|
|
43
|
-
# if [[ "$OS" == "Linux" ]]; then
|
|
44
|
-
# PLATFORM="linux"
|
|
45
|
-
# if [[ "$ARCH" == "aarch64" ]]; then
|
|
46
|
-
# ARCH="aarch64";
|
|
47
|
-
# elif [[ $ARCH == "ppc64le" ]]; then
|
|
48
|
-
# ARCH="ppc64le";
|
|
49
|
-
# else
|
|
50
|
-
# ARCH="64";
|
|
51
|
-
# fi
|
|
52
|
-
# fi
|
|
53
|
-
|
|
54
|
-
# if [[ "$OS" == "Darwin" ]]; then
|
|
55
|
-
# PLATFORM="osx";
|
|
56
|
-
# if [[ "$ARCH" == "arm64" ]]; then
|
|
57
|
-
# ARCH="arm64";
|
|
58
|
-
# else
|
|
59
|
-
# ARCH="64"
|
|
60
|
-
# fi
|
|
61
|
-
# fi
|
|
62
40
|
|
|
63
41
|
def run_cmd(cmd, stdin_str=None):
|
|
64
42
|
result = subprocess.run(
|
|
@@ -350,12 +328,25 @@ if __name__ == "__main__":
|
|
|
350
328
|
cmd = f"fast-initializer --prefix {prefix} --packages-dir {pkgs_dir}"
|
|
351
329
|
run_cmd(cmd, all_package_urls)
|
|
352
330
|
|
|
353
|
-
if len(sys.argv) !=
|
|
354
|
-
print("Usage: bootstrap.py <flow_name> <id> <datastore_type>
|
|
331
|
+
if len(sys.argv) != 4:
|
|
332
|
+
print("Usage: bootstrap.py <flow_name> <id> <datastore_type>")
|
|
355
333
|
sys.exit(1)
|
|
356
334
|
|
|
357
335
|
try:
|
|
358
|
-
_, flow_name, id_, datastore_type
|
|
336
|
+
_, flow_name, id_, datastore_type = sys.argv
|
|
337
|
+
|
|
338
|
+
system = platform.system().lower()
|
|
339
|
+
arch_machine = platform.machine().lower()
|
|
340
|
+
|
|
341
|
+
if system == "darwin" and arch_machine == "arm64":
|
|
342
|
+
architecture = "osx-arm64"
|
|
343
|
+
elif system == "darwin":
|
|
344
|
+
architecture = "osx-64"
|
|
345
|
+
elif system == "linux" and arch_machine == "aarch64":
|
|
346
|
+
architecture = "linux-aarch64"
|
|
347
|
+
else:
|
|
348
|
+
# default fallback
|
|
349
|
+
architecture = "linux-64"
|
|
359
350
|
|
|
360
351
|
prefix = os.path.join(os.getcwd(), architecture, id_)
|
|
361
352
|
pkgs_dir = os.path.join(os.getcwd(), ".pkgs")
|
|
@@ -190,7 +190,6 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
190
190
|
# 4. Start PyPI solves in parallel after each conda environment is created
|
|
191
191
|
# 5. Download PyPI packages sequentially
|
|
192
192
|
# 6. Create and cache PyPI environments in parallel
|
|
193
|
-
|
|
194
193
|
with ThreadPoolExecutor() as executor:
|
|
195
194
|
# Start all conda solves in parallel
|
|
196
195
|
conda_futures = [
|
|
@@ -213,14 +212,14 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
213
212
|
|
|
214
213
|
# Queue PyPI solve to start after conda create
|
|
215
214
|
if result[0] in pypi_envs:
|
|
215
|
+
# solve pypi envs uniquely
|
|
216
|
+
pypi_env = pypi_envs.pop(result[0])
|
|
216
217
|
|
|
217
218
|
def pypi_solve(env):
|
|
218
219
|
create_future.result() # Wait for conda create
|
|
219
220
|
return solve(*env, "pypi")
|
|
220
221
|
|
|
221
|
-
pypi_futures.append(
|
|
222
|
-
executor.submit(pypi_solve, pypi_envs[result[0]])
|
|
223
|
-
)
|
|
222
|
+
pypi_futures.append(executor.submit(pypi_solve, pypi_env))
|
|
224
223
|
|
|
225
224
|
# Process PyPI results sequentially for downloads
|
|
226
225
|
for solve_future in pypi_futures:
|
|
@@ -242,7 +241,7 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
242
241
|
if id_:
|
|
243
242
|
# bootstrap.py is responsible for ensuring the validity of this executable.
|
|
244
243
|
# -s is important! Can otherwise leak packages to other environments.
|
|
245
|
-
return os.path.join("
|
|
244
|
+
return os.path.join("$MF_ARCH", id_, "bin/python -s")
|
|
246
245
|
else:
|
|
247
246
|
# for @conda/@pypi(disabled=True).
|
|
248
247
|
return super().executable(step_name, default)
|
|
@@ -315,7 +314,6 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
315
314
|
# 5. All resolved packages (Conda or PyPI) are cached
|
|
316
315
|
# 6. PyPI packages are only installed for local platform
|
|
317
316
|
|
|
318
|
-
# Resolve `linux-64` Conda environments if @batch or @kubernetes are in play
|
|
319
317
|
target_platform = conda_platform()
|
|
320
318
|
for decorator in step.decorators:
|
|
321
319
|
# NOTE: Keep the list of supported decorator names for backward compatibility purposes.
|
|
@@ -329,7 +327,6 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
329
327
|
"snowpark",
|
|
330
328
|
"slurm",
|
|
331
329
|
]:
|
|
332
|
-
# TODO: Support arm architectures
|
|
333
330
|
target_platform = getattr(decorator, "target_platform", "linux-64")
|
|
334
331
|
break
|
|
335
332
|
|
|
@@ -427,15 +424,18 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
427
424
|
if id_:
|
|
428
425
|
return [
|
|
429
426
|
"echo 'Bootstrapping virtual environment...'",
|
|
427
|
+
"flush_mflogs",
|
|
430
428
|
# We have to prevent the tracing module from loading,
|
|
431
429
|
# as the bootstrapping process uses the internal S3 client which would fail to import tracing
|
|
432
430
|
# due to the required dependencies being bundled into the conda environment,
|
|
433
431
|
# which is yet to be initialized at this point.
|
|
434
|
-
'DISABLE_TRACING=True python -m metaflow.plugins.pypi.bootstrap "%s" %s "%s"
|
|
432
|
+
'DISABLE_TRACING=True python -m metaflow.plugins.pypi.bootstrap "%s" %s "%s"'
|
|
435
433
|
% (self.flow.name, id_, self.datastore_type),
|
|
436
434
|
"echo 'Environment bootstrapped.'",
|
|
435
|
+
"flush_mflogs",
|
|
437
436
|
# To avoid having to install micromamba in the PATH in micromamba.py, we add it to the PATH here.
|
|
438
437
|
"export PATH=$PATH:$(pwd)/micromamba/bin",
|
|
438
|
+
"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)",
|
|
439
439
|
]
|
|
440
440
|
else:
|
|
441
441
|
# for @conda/@pypi(disabled=True).
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# this file can be overridden by extensions as is (e.g. metaflow-nflx-extensions)
|
|
2
|
+
from metaflow.exception import MetaflowException
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ParserValueError(MetaflowException):
|
|
6
|
+
headline = "Value error"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def requirements_txt_parser(content: str):
|
|
10
|
+
"""
|
|
11
|
+
Parse non-comment lines from a requirements.txt file as strictly valid
|
|
12
|
+
PEP 508 requirements.
|
|
13
|
+
|
|
14
|
+
Recognizes direct references (e.g. "my_lib @ git+https://..."), extras
|
|
15
|
+
(e.g. "requests[security]"), and version specifiers (e.g. "==2.0"). If
|
|
16
|
+
the package name is "python", its specifier is stored in the "python"
|
|
17
|
+
key instead of "packages".
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
content : str
|
|
22
|
+
Contents of a requirements.txt file.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
dict
|
|
27
|
+
A dictionary with two keys:
|
|
28
|
+
- "packages": dict(str -> str)
|
|
29
|
+
Mapping from package name (plus optional extras/references) to a
|
|
30
|
+
version specifier string.
|
|
31
|
+
- "python": str or None
|
|
32
|
+
The Python version constraints if present, otherwise None.
|
|
33
|
+
|
|
34
|
+
Raises
|
|
35
|
+
------
|
|
36
|
+
ParserValueError
|
|
37
|
+
If a requirement line is invalid PEP 508 or if environment markers are
|
|
38
|
+
detected, or if multiple Python constraints are specified.
|
|
39
|
+
"""
|
|
40
|
+
import re
|
|
41
|
+
from metaflow._vendor.packaging.requirements import Requirement, InvalidRequirement
|
|
42
|
+
|
|
43
|
+
parsed = {"packages": {}, "python": None}
|
|
44
|
+
|
|
45
|
+
inline_comment_pattern = re.compile(r"\s+#.*$")
|
|
46
|
+
for line in content.splitlines():
|
|
47
|
+
line = line.strip()
|
|
48
|
+
|
|
49
|
+
# support Rye lockfiles by skipping lines not compliant with requirements
|
|
50
|
+
if line == "-e file:.":
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
if not line or line.startswith("#"):
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
line = inline_comment_pattern.sub("", line).strip()
|
|
57
|
+
if not line:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
req = Requirement(line)
|
|
62
|
+
except InvalidRequirement:
|
|
63
|
+
raise ParserValueError(f"Not a valid PEP 508 requirement: '{line}'")
|
|
64
|
+
|
|
65
|
+
if req.marker is not None:
|
|
66
|
+
raise ParserValueError(
|
|
67
|
+
"Environment markers (e.g. 'platform_system==\"Linux\"') "
|
|
68
|
+
f"are not supported for line: '{line}'"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
dep_key = req.name
|
|
72
|
+
if req.extras:
|
|
73
|
+
dep_key += f"[{','.join(req.extras)}]"
|
|
74
|
+
if req.url:
|
|
75
|
+
dep_key += f"@{req.url}"
|
|
76
|
+
|
|
77
|
+
dep_spec = str(req.specifier).lstrip(" =")
|
|
78
|
+
|
|
79
|
+
if req.name.lower() == "python":
|
|
80
|
+
if parsed["python"] is not None and dep_spec:
|
|
81
|
+
raise ParserValueError(
|
|
82
|
+
f"Multiple Python version specs not allowed: '{line}'"
|
|
83
|
+
)
|
|
84
|
+
parsed["python"] = dep_spec or None
|
|
85
|
+
else:
|
|
86
|
+
parsed["packages"][dep_key] = dep_spec
|
|
87
|
+
|
|
88
|
+
return parsed
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def pyproject_toml_parser(content: str):
|
|
92
|
+
"""
|
|
93
|
+
Parse a pyproject.toml file per PEP 621.
|
|
94
|
+
|
|
95
|
+
Reads the 'requires-python' and 'dependencies' fields from the "[project]" section.
|
|
96
|
+
Each dependency line must be a valid PEP 508 requirement. If the package name is
|
|
97
|
+
"python", its specifier is stored in the "python" key instead of "packages".
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
content : str
|
|
102
|
+
Contents of a pyproject.toml file.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
dict
|
|
107
|
+
A dictionary with two keys:
|
|
108
|
+
- "packages": dict(str -> str)
|
|
109
|
+
Mapping from package name (plus optional extras/references) to a
|
|
110
|
+
version specifier string.
|
|
111
|
+
- "python": str or None
|
|
112
|
+
The Python version constraints if present, otherwise None.
|
|
113
|
+
|
|
114
|
+
Raises
|
|
115
|
+
------
|
|
116
|
+
RuntimeError
|
|
117
|
+
If no TOML library (tomllib in Python 3.11+ or tomli in earlier versions) is found.
|
|
118
|
+
ParserValueError
|
|
119
|
+
If a dependency is not valid PEP 508, if environment markers are used, or if
|
|
120
|
+
multiple Python constraints are specified.
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
import tomllib as toml # Python 3.11+
|
|
124
|
+
except ImportError:
|
|
125
|
+
try:
|
|
126
|
+
import tomli as toml # Python < 3.11 (requires "tomli" package)
|
|
127
|
+
except ImportError:
|
|
128
|
+
raise RuntimeError(
|
|
129
|
+
"Could not import a TOML library. For Python <3.11, please install 'tomli'."
|
|
130
|
+
)
|
|
131
|
+
from metaflow._vendor.packaging.requirements import Requirement, InvalidRequirement
|
|
132
|
+
|
|
133
|
+
data = toml.loads(content)
|
|
134
|
+
|
|
135
|
+
project = data.get("project", {})
|
|
136
|
+
requirements = project.get("dependencies", [])
|
|
137
|
+
requires_python = project.get("requires-python")
|
|
138
|
+
|
|
139
|
+
parsed = {"packages": {}, "python": None}
|
|
140
|
+
|
|
141
|
+
if requires_python is not None:
|
|
142
|
+
# If present, store verbatim; note that PEP 621 does not necessarily
|
|
143
|
+
# require "python" to be a dependency in the usual sense.
|
|
144
|
+
# Example: "requires-python" = ">=3.7,<4"
|
|
145
|
+
parsed["python"] = requires_python.lstrip("=").strip()
|
|
146
|
+
|
|
147
|
+
for dep_line in requirements:
|
|
148
|
+
dep_line_stripped = dep_line.strip()
|
|
149
|
+
try:
|
|
150
|
+
req = Requirement(dep_line_stripped)
|
|
151
|
+
except InvalidRequirement:
|
|
152
|
+
raise ParserValueError(
|
|
153
|
+
f"Not a valid PEP 508 requirement: '{dep_line_stripped}'"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if req.marker is not None:
|
|
157
|
+
raise ParserValueError(
|
|
158
|
+
f"Environment markers not supported for line: '{dep_line_stripped}'"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
dep_key = req.name
|
|
162
|
+
if req.extras:
|
|
163
|
+
dep_key += f"[{','.join(req.extras)}]"
|
|
164
|
+
if req.url:
|
|
165
|
+
dep_key += f"@{req.url}"
|
|
166
|
+
|
|
167
|
+
dep_spec = str(req.specifier).lstrip("=")
|
|
168
|
+
|
|
169
|
+
if req.name.lower() == "python":
|
|
170
|
+
if parsed["python"] is not None and dep_spec:
|
|
171
|
+
raise ParserValueError(
|
|
172
|
+
f"Multiple Python version specs not allowed: '{dep_line_stripped}'"
|
|
173
|
+
)
|
|
174
|
+
parsed["python"] = dep_spec or None
|
|
175
|
+
else:
|
|
176
|
+
parsed["packages"][dep_key] = dep_spec
|
|
177
|
+
|
|
178
|
+
return parsed
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def conda_environment_yml_parser(content: str):
|
|
182
|
+
"""
|
|
183
|
+
Parse a minimal environment.yml file under strict assumptions.
|
|
184
|
+
|
|
185
|
+
The file must contain a 'dependencies:' line, after which each dependency line
|
|
186
|
+
appears with a '- ' prefix. Python can appear as 'python=3.9', etc.; other
|
|
187
|
+
packages as 'numpy=1.21.2' or simply 'numpy'. Non-compliant lines raise ParserValueError.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
content : str
|
|
192
|
+
Contents of a environment.yml file.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
dict
|
|
197
|
+
A dictionary with keys:
|
|
198
|
+
{
|
|
199
|
+
"packages": dict(str -> str),
|
|
200
|
+
"python": str or None
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Raises
|
|
204
|
+
------
|
|
205
|
+
ParserValueError
|
|
206
|
+
If the file has malformed lines or unsupported sections.
|
|
207
|
+
"""
|
|
208
|
+
import re
|
|
209
|
+
|
|
210
|
+
packages = {}
|
|
211
|
+
python_version = None
|
|
212
|
+
|
|
213
|
+
inside_dependencies = False
|
|
214
|
+
|
|
215
|
+
# Basic pattern for lines like "numpy=1.21.2"
|
|
216
|
+
# Group 1: package name
|
|
217
|
+
# Group 2: optional operator + version (could be "=1.21.2", "==1.21.2", etc.)
|
|
218
|
+
line_regex = re.compile(r"^([A-Za-z0-9_\-\.]+)(\s*[=<>!~].+\s*)?$")
|
|
219
|
+
inline_comment_pattern = re.compile(r"\s+#.*$")
|
|
220
|
+
|
|
221
|
+
for line in content.splitlines():
|
|
222
|
+
line = line.strip()
|
|
223
|
+
if not line or line.startswith("#"):
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
line = inline_comment_pattern.sub("", line).strip()
|
|
227
|
+
if not line:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
if line.lower().startswith("dependencies:"):
|
|
231
|
+
inside_dependencies = True
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
if inside_dependencies and not line.startswith("-"):
|
|
235
|
+
inside_dependencies = False
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
if not inside_dependencies:
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
dep_line = line.lstrip("-").strip()
|
|
242
|
+
if dep_line.endswith(":"):
|
|
243
|
+
raise ParserValueError(
|
|
244
|
+
f"Unsupported subsection '{dep_line}' in environment.yml."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
match = line_regex.match(dep_line)
|
|
248
|
+
if not match:
|
|
249
|
+
raise ParserValueError(
|
|
250
|
+
f"Line '{dep_line}' is not a valid conda package specifier."
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
pkg_name, pkg_version_part = match.groups()
|
|
254
|
+
version_spec = pkg_version_part.strip() if pkg_version_part else ""
|
|
255
|
+
|
|
256
|
+
if version_spec.startswith("="):
|
|
257
|
+
version_spec = version_spec.lstrip("=").strip()
|
|
258
|
+
|
|
259
|
+
if pkg_name.lower() == "python":
|
|
260
|
+
if python_version is not None and version_spec:
|
|
261
|
+
raise ParserValueError(
|
|
262
|
+
f"Multiple Python version specs detected: '{dep_line}'"
|
|
263
|
+
)
|
|
264
|
+
python_version = version_spec
|
|
265
|
+
else:
|
|
266
|
+
packages[pkg_name] = version_spec
|
|
267
|
+
|
|
268
|
+
return {"packages": packages, "python": python_version}
|
metaflow/plugins/pypi/utils.py
CHANGED
|
@@ -72,6 +72,24 @@ def pip_tags(python_version, mamba_platform):
|
|
|
72
72
|
)
|
|
73
73
|
]
|
|
74
74
|
platforms.append("linux_x86_64")
|
|
75
|
+
elif mamba_platform == "linux-aarch64":
|
|
76
|
+
platforms = [
|
|
77
|
+
"manylinux%s_aarch64" % s
|
|
78
|
+
for s in (
|
|
79
|
+
"2014",
|
|
80
|
+
"_2_17",
|
|
81
|
+
"_2_18",
|
|
82
|
+
"_2_19",
|
|
83
|
+
"_2_20",
|
|
84
|
+
"_2_21",
|
|
85
|
+
"_2_23",
|
|
86
|
+
"_2_24",
|
|
87
|
+
"_2_25",
|
|
88
|
+
"_2_26",
|
|
89
|
+
"_2_27",
|
|
90
|
+
)
|
|
91
|
+
]
|
|
92
|
+
platforms.append("linux_aarch64")
|
|
75
93
|
elif mamba_platform == "osx-64":
|
|
76
94
|
platforms = tags.mac_platforms(arch="x86_64")
|
|
77
95
|
elif mamba_platform == "osx-arm64":
|
metaflow/runner/click_api.py
CHANGED
|
@@ -219,7 +219,11 @@ def get_inspect_param_obj(p: Union[click.Argument, click.Option], kind: str):
|
|
|
219
219
|
default=inspect.Parameter.empty if is_vararg else p.default,
|
|
220
220
|
annotation=annotation,
|
|
221
221
|
),
|
|
222
|
-
|
|
222
|
+
(
|
|
223
|
+
Optional[Union[TTuple[annotation], List[annotation]]]
|
|
224
|
+
if is_vararg
|
|
225
|
+
else annotation
|
|
226
|
+
),
|
|
223
227
|
)
|
|
224
228
|
|
|
225
229
|
|
metaflow/runner/deployer.py
CHANGED
|
@@ -106,7 +106,7 @@ class TriggeredRun(object):
|
|
|
106
106
|
self.pathspec = content_json.get("pathspec")
|
|
107
107
|
self.name = content_json.get("name")
|
|
108
108
|
|
|
109
|
-
def wait_for_run(self, timeout: Optional[int] = None):
|
|
109
|
+
def wait_for_run(self, check_interval: int = 5, timeout: Optional[int] = None):
|
|
110
110
|
"""
|
|
111
111
|
Wait for the `run` property to become available.
|
|
112
112
|
|
|
@@ -115,6 +115,8 @@ class TriggeredRun(object):
|
|
|
115
115
|
|
|
116
116
|
Parameters
|
|
117
117
|
----------
|
|
118
|
+
check_interval: int, default: 5
|
|
119
|
+
Frequency of checking for the `run` to become available, in seconds.
|
|
118
120
|
timeout : int, optional, default None
|
|
119
121
|
Maximum time to wait for the `run` to become available, in seconds. If
|
|
120
122
|
None, wait indefinitely.
|
|
@@ -125,7 +127,6 @@ class TriggeredRun(object):
|
|
|
125
127
|
If the `run` is not available within the specified timeout.
|
|
126
128
|
"""
|
|
127
129
|
start_time = time.time()
|
|
128
|
-
check_interval = 5
|
|
129
130
|
while True:
|
|
130
131
|
if self.run is not None:
|
|
131
132
|
return self.run
|
metaflow/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
metaflow_version = "2.15.
|
|
1
|
+
metaflow_version = "2.15.5.1"
|