metaflow 2.12.21__py2.py3-none-any.whl → 2.12.23__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/cli.py +24 -19
- metaflow/client/core.py +1 -1
- metaflow/cmd/develop/stub_generator.py +17 -0
- metaflow/cmd/develop/stubs.py +3 -3
- metaflow/metaflow_version.py +8 -5
- metaflow/plugins/argo/argo_client.py +2 -0
- metaflow/plugins/argo/argo_workflows.py +93 -51
- metaflow/plugins/argo/argo_workflows_cli.py +26 -0
- metaflow/plugins/kubernetes/kubernetes_client.py +7 -1
- metaflow/plugins/kubernetes/kubernetes_decorator.py +5 -1
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +7 -1
- metaflow/plugins/pypi/bootstrap.py +1 -1
- metaflow/plugins/pypi/micromamba.py +26 -0
- metaflow/runner/deployer.py +4 -49
- metaflow/runner/metaflow_runner.py +22 -25
- metaflow/runner/subprocess_manager.py +33 -17
- metaflow/runner/utils.py +53 -1
- metaflow/version.py +1 -1
- {metaflow-2.12.21.dist-info → metaflow-2.12.23.dist-info}/METADATA +2 -2
- {metaflow-2.12.21.dist-info → metaflow-2.12.23.dist-info}/RECORD +24 -25
- metaflow/plugins/argo/daemon.py +0 -59
- {metaflow-2.12.21.dist-info → metaflow-2.12.23.dist-info}/LICENSE +0 -0
- {metaflow-2.12.21.dist-info → metaflow-2.12.23.dist-info}/WHEEL +0 -0
- {metaflow-2.12.21.dist-info → metaflow-2.12.23.dist-info}/entry_points.txt +0 -0
- {metaflow-2.12.21.dist-info → metaflow-2.12.23.dist-info}/top_level.txt +0 -0
metaflow/cli.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import inspect
|
2
|
-
import
|
2
|
+
import json
|
3
3
|
import sys
|
4
4
|
import traceback
|
5
5
|
from datetime import datetime
|
@@ -7,6 +7,7 @@ from functools import wraps
|
|
7
7
|
|
8
8
|
import metaflow.tracing as tracing
|
9
9
|
from metaflow._vendor import click
|
10
|
+
from metaflow.client.core import get_metadata
|
10
11
|
|
11
12
|
from . import decorators, lint, metaflow_version, namespace, parameters, plugins
|
12
13
|
from .cli_args import cli_args
|
@@ -698,15 +699,17 @@ def resume(
|
|
698
699
|
runtime.print_workflow_info()
|
699
700
|
|
700
701
|
runtime.persist_constants()
|
701
|
-
|
702
|
-
|
703
|
-
"
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
702
|
+
|
703
|
+
if runner_attribute_file:
|
704
|
+
with open(runner_attribute_file, "w") as f:
|
705
|
+
json.dump(
|
706
|
+
{
|
707
|
+
"run_id": runtime.run_id,
|
708
|
+
"flow_name": obj.flow.name,
|
709
|
+
"metadata": get_metadata(),
|
710
|
+
},
|
711
|
+
f,
|
712
|
+
)
|
710
713
|
|
711
714
|
# We may skip clone-only resume if this is not a resume leader,
|
712
715
|
# and clone is already complete.
|
@@ -774,15 +777,17 @@ def run(
|
|
774
777
|
obj.flow._set_constants(obj.graph, kwargs)
|
775
778
|
runtime.print_workflow_info()
|
776
779
|
runtime.persist_constants()
|
777
|
-
|
778
|
-
|
779
|
-
"
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
780
|
+
|
781
|
+
if runner_attribute_file:
|
782
|
+
with open(runner_attribute_file, "w") as f:
|
783
|
+
json.dump(
|
784
|
+
{
|
785
|
+
"run_id": runtime.run_id,
|
786
|
+
"flow_name": obj.flow.name,
|
787
|
+
"metadata": get_metadata(),
|
788
|
+
},
|
789
|
+
f,
|
790
|
+
)
|
786
791
|
runtime.execute()
|
787
792
|
|
788
793
|
|
metaflow/client/core.py
CHANGED
@@ -115,6 +115,11 @@ class StubGenerator:
|
|
115
115
|
:type members_from_other_modules: List[str]
|
116
116
|
"""
|
117
117
|
|
118
|
+
# Let metaflow know we are in stubgen mode. This is sometimes useful to skip
|
119
|
+
# some processing like loading libraries, etc. It is used in Metaflow extensions
|
120
|
+
# so do not remove even if you do not see a use for it directly in the code.
|
121
|
+
os.environ["METAFLOW_STUBGEN"] = "1"
|
122
|
+
|
118
123
|
self._write_generated_for = include_generated_for
|
119
124
|
self._pending_modules = ["metaflow"] # type: List[str]
|
120
125
|
self._pending_modules.extend(get_aliased_modules())
|
@@ -398,6 +403,18 @@ class StubGenerator:
|
|
398
403
|
name_with_module = self._get_element_name_with_module(clazz.__class__)
|
399
404
|
buff.write("metaclass=" + name_with_module + "):\n")
|
400
405
|
|
406
|
+
# Add class docstring
|
407
|
+
if clazz.__doc__:
|
408
|
+
buff.write('%s"""\n' % TAB)
|
409
|
+
my_doc = cast(str, deindent_docstring(clazz.__doc__))
|
410
|
+
init_blank = True
|
411
|
+
for line in my_doc.split("\n"):
|
412
|
+
if init_blank and len(line.strip()) == 0:
|
413
|
+
continue
|
414
|
+
init_blank = False
|
415
|
+
buff.write("%s%s\n" % (TAB, line.rstrip()))
|
416
|
+
buff.write('%s"""\n' % TAB)
|
417
|
+
|
401
418
|
# For NamedTuple, we have __annotations__ but no __init__. In that case,
|
402
419
|
# we are going to "create" a __init__ function with the annotations
|
403
420
|
# to show what the class takes.
|
metaflow/cmd/develop/stubs.py
CHANGED
@@ -170,7 +170,7 @@ def install(ctx: Any, force: bool):
|
|
170
170
|
"Metaflow stubs are already installed and valid -- use --force to reinstall"
|
171
171
|
)
|
172
172
|
return
|
173
|
-
mf_version, _ = get_mf_version()
|
173
|
+
mf_version, _ = get_mf_version(True)
|
174
174
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
175
175
|
with open(os.path.join(tmp_dir, "setup.py"), "w") as f:
|
176
176
|
f.write(
|
@@ -261,10 +261,10 @@ def split_version(vers: str) -> Tuple[str, Optional[str]]:
|
|
261
261
|
return vers_split[0], vers_split[1]
|
262
262
|
|
263
263
|
|
264
|
-
def get_mf_version() -> Tuple[str, Optional[str]]:
|
264
|
+
def get_mf_version(public: bool = False) -> Tuple[str, Optional[str]]:
|
265
265
|
from metaflow.metaflow_version import get_version
|
266
266
|
|
267
|
-
return split_version(get_version())
|
267
|
+
return split_version(get_version(public))
|
268
268
|
|
269
269
|
|
270
270
|
def get_stubs_version(stubs_root_path: Optional[str]) -> Tuple[str, Optional[str]]:
|
metaflow/metaflow_version.py
CHANGED
@@ -195,11 +195,14 @@ def get_version(public=False):
|
|
195
195
|
if ext_version is None:
|
196
196
|
ext_version = getattr(extension_module, "__version__", "<unk>")
|
197
197
|
# Update the package information about reported version for the extension
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
198
|
+
# (only for the full info which is called at least once -- if we update more
|
199
|
+
# it will error out since we can only update_package_info once)
|
200
|
+
if not public:
|
201
|
+
update_package_info(
|
202
|
+
package_name=pkg_name,
|
203
|
+
extension_name=ext_name,
|
204
|
+
package_version=ext_version,
|
205
|
+
)
|
203
206
|
ext_versions.append("%s(%s)" % (ext_name, ext_version))
|
204
207
|
|
205
208
|
# We now have all the information about extensions so we can form the final string
|
@@ -295,6 +295,8 @@ class ArgoClient(object):
|
|
295
295
|
"suspend": schedule is None,
|
296
296
|
"schedule": schedule,
|
297
297
|
"timezone": timezone,
|
298
|
+
"failedJobsHistoryLimit": 10000, # default is unfortunately 1
|
299
|
+
"successfulJobsHistoryLimit": 10000, # default is unfortunately 3
|
298
300
|
"workflowSpec": {"workflowTemplateRef": {"name": name}},
|
299
301
|
},
|
300
302
|
}
|
@@ -455,11 +455,17 @@ class ArgoWorkflows(object):
|
|
455
455
|
)
|
456
456
|
seen.add(norm)
|
457
457
|
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
458
|
+
extra_attrs = {}
|
459
|
+
if param.kwargs.get("type") == JSONType:
|
460
|
+
param_type = str(param.kwargs.get("type").name)
|
461
|
+
elif isinstance(param.kwargs.get("type"), FilePathClass):
|
462
462
|
param_type = str(param.kwargs.get("type").name)
|
463
|
+
extra_attrs["is_text"] = getattr(
|
464
|
+
param.kwargs.get("type"), "_is_text", True
|
465
|
+
)
|
466
|
+
extra_attrs["encoding"] = getattr(
|
467
|
+
param.kwargs.get("type"), "_encoding", "utf-8"
|
468
|
+
)
|
463
469
|
else:
|
464
470
|
param_type = str(param.kwargs.get("type").__name__)
|
465
471
|
|
@@ -487,6 +493,7 @@ class ArgoWorkflows(object):
|
|
487
493
|
type=param_type,
|
488
494
|
description=param.kwargs.get("help"),
|
489
495
|
is_required=is_required,
|
496
|
+
**extra_attrs
|
490
497
|
)
|
491
498
|
return parameters
|
492
499
|
|
@@ -1483,7 +1490,11 @@ class ArgoWorkflows(object):
|
|
1483
1490
|
# {{foo.bar['param_name']}}.
|
1484
1491
|
# https://argoproj.github.io/argo-events/tutorials/02-parameterization/
|
1485
1492
|
# http://masterminds.github.io/sprig/strings.html
|
1486
|
-
|
1493
|
+
(
|
1494
|
+
"--%s='{{workflow.parameters.%s}}'"
|
1495
|
+
if parameter["type"] == "JSON"
|
1496
|
+
else "--%s={{workflow.parameters.%s}}"
|
1497
|
+
)
|
1487
1498
|
% (parameter["name"], parameter["name"])
|
1488
1499
|
for parameter in self.parameters.values()
|
1489
1500
|
]
|
@@ -2515,10 +2526,29 @@ class ArgoWorkflows(object):
|
|
2515
2526
|
# Use all the affordances available to _parameters task
|
2516
2527
|
executable = self.environment.executable("_parameters")
|
2517
2528
|
run_id = "argo-{{workflow.name}}"
|
2518
|
-
|
2519
|
-
|
2529
|
+
script_name = os.path.basename(sys.argv[0])
|
2530
|
+
entrypoint = [executable, script_name]
|
2531
|
+
# FlowDecorators can define their own top-level options. These might affect run level information
|
2532
|
+
# so it is important to pass these to the heartbeat process as well, as it might be the first task to register a run.
|
2533
|
+
top_opts_dict = {}
|
2534
|
+
for deco in flow_decorators(self.flow):
|
2535
|
+
top_opts_dict.update(deco.get_top_level_options())
|
2536
|
+
|
2537
|
+
top_level = list(dict_to_cli_options(top_opts_dict)) + [
|
2538
|
+
"--quiet",
|
2539
|
+
"--metadata=%s" % self.metadata.TYPE,
|
2540
|
+
"--environment=%s" % self.environment.TYPE,
|
2541
|
+
"--datastore=%s" % self.flow_datastore.TYPE,
|
2542
|
+
"--datastore-root=%s" % self.flow_datastore.datastore_root,
|
2543
|
+
"--event-logger=%s" % self.event_logger.TYPE,
|
2544
|
+
"--monitor=%s" % self.monitor.TYPE,
|
2545
|
+
"--no-pylint",
|
2546
|
+
"--with=argo_workflows_internal:auto-emit-argo-events=%i"
|
2547
|
+
% self.auto_emit_argo_events,
|
2548
|
+
]
|
2549
|
+
heartbeat_cmds = "{entrypoint} {top_level} argo-workflows heartbeat --run_id {run_id} {tags}".format(
|
2520
2550
|
entrypoint=" ".join(entrypoint),
|
2521
|
-
|
2551
|
+
top_level=" ".join(top_level) if top_level else "",
|
2522
2552
|
run_id=run_id,
|
2523
2553
|
tags=" ".join(["--tag %s" % t for t in self.tags]) if self.tags else "",
|
2524
2554
|
)
|
@@ -2569,12 +2599,16 @@ class ArgoWorkflows(object):
|
|
2569
2599
|
"METAFLOW_SERVICE_URL": SERVICE_INTERNAL_URL,
|
2570
2600
|
"METAFLOW_SERVICE_HEADERS": json.dumps(SERVICE_HEADERS),
|
2571
2601
|
"METAFLOW_USER": "argo-workflows",
|
2602
|
+
"METAFLOW_DATASTORE_SYSROOT_S3": DATASTORE_SYSROOT_S3,
|
2603
|
+
"METAFLOW_DATATOOLS_S3ROOT": DATATOOLS_S3ROOT,
|
2572
2604
|
"METAFLOW_DEFAULT_DATASTORE": self.flow_datastore.TYPE,
|
2573
2605
|
"METAFLOW_DEFAULT_METADATA": DEFAULT_METADATA,
|
2606
|
+
"METAFLOW_CARD_S3ROOT": CARD_S3ROOT,
|
2574
2607
|
"METAFLOW_KUBERNETES_WORKLOAD": 1,
|
2608
|
+
"METAFLOW_KUBERNETES_FETCH_EC2_METADATA": KUBERNETES_FETCH_EC2_METADATA,
|
2575
2609
|
"METAFLOW_RUNTIME_ENVIRONMENT": "kubernetes",
|
2576
2610
|
"METAFLOW_OWNER": self.username,
|
2577
|
-
"METAFLOW_PRODUCTION_TOKEN": self.production_token,
|
2611
|
+
"METAFLOW_PRODUCTION_TOKEN": self.production_token, # Used in identity resolving. This affects system tags.
|
2578
2612
|
}
|
2579
2613
|
# support Metaflow sandboxes
|
2580
2614
|
env["METAFLOW_INIT_SCRIPT"] = KUBERNETES_SANDBOX_INIT_SCRIPT
|
@@ -2597,50 +2631,54 @@ class ArgoWorkflows(object):
|
|
2597
2631
|
)
|
2598
2632
|
from kubernetes import client as kubernetes_sdk
|
2599
2633
|
|
2600
|
-
return
|
2601
|
-
|
2602
|
-
|
2603
|
-
|
2604
|
-
|
2605
|
-
|
2606
|
-
|
2607
|
-
|
2608
|
-
|
2609
|
-
|
2610
|
-
|
2611
|
-
|
2612
|
-
|
2613
|
-
|
2614
|
-
|
2615
|
-
|
2634
|
+
return (
|
2635
|
+
DaemonTemplate("heartbeat-daemon")
|
2636
|
+
.service_account_name(resources["service_account"])
|
2637
|
+
.container(
|
2638
|
+
to_camelcase(
|
2639
|
+
kubernetes_sdk.V1Container(
|
2640
|
+
name="main",
|
2641
|
+
# TODO: Make the image configurable
|
2642
|
+
image=resources["image"],
|
2643
|
+
command=cmds,
|
2644
|
+
env=[
|
2645
|
+
kubernetes_sdk.V1EnvVar(name=k, value=str(v))
|
2646
|
+
for k, v in env.items()
|
2647
|
+
],
|
2648
|
+
env_from=[
|
2649
|
+
kubernetes_sdk.V1EnvFromSource(
|
2650
|
+
secret_ref=kubernetes_sdk.V1SecretEnvSource(
|
2651
|
+
name=str(k),
|
2652
|
+
# optional=True
|
2653
|
+
)
|
2616
2654
|
)
|
2617
|
-
|
2618
|
-
|
2619
|
-
|
2620
|
-
|
2621
|
-
|
2622
|
-
|
2623
|
-
|
2624
|
-
|
2655
|
+
for k in list(
|
2656
|
+
[]
|
2657
|
+
if not resources.get("secrets")
|
2658
|
+
else (
|
2659
|
+
[resources.get("secrets")]
|
2660
|
+
if isinstance(resources.get("secrets"), str)
|
2661
|
+
else resources.get("secrets")
|
2662
|
+
)
|
2625
2663
|
)
|
2626
|
-
|
2627
|
-
|
2628
|
-
|
2629
|
-
|
2630
|
-
|
2631
|
-
|
2632
|
-
|
2633
|
-
|
2634
|
-
|
2635
|
-
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
2639
|
-
|
2640
|
-
|
2641
|
-
|
2642
|
-
|
2643
|
-
)
|
2664
|
+
+ KUBERNETES_SECRETS.split(",")
|
2665
|
+
+ ARGO_WORKFLOWS_KUBERNETES_SECRETS.split(",")
|
2666
|
+
if k
|
2667
|
+
],
|
2668
|
+
resources=kubernetes_sdk.V1ResourceRequirements(
|
2669
|
+
# NOTE: base resources for this are kept to a minimum to save on running costs.
|
2670
|
+
# This has an adverse effect on startup time for the daemon, which can be completely
|
2671
|
+
# alleviated by using a base image that has the required dependencies pre-installed
|
2672
|
+
requests={
|
2673
|
+
"cpu": "200m",
|
2674
|
+
"memory": "100Mi",
|
2675
|
+
},
|
2676
|
+
limits={
|
2677
|
+
"cpu": "200m",
|
2678
|
+
"memory": "100Mi",
|
2679
|
+
},
|
2680
|
+
),
|
2681
|
+
)
|
2644
2682
|
)
|
2645
2683
|
)
|
2646
2684
|
)
|
@@ -3262,6 +3300,10 @@ class DaemonTemplate(object):
|
|
3262
3300
|
self.payload["container"] = container
|
3263
3301
|
return self
|
3264
3302
|
|
3303
|
+
def service_account_name(self, service_account_name):
|
3304
|
+
self.payload["serviceAccountName"] = service_account_name
|
3305
|
+
return self
|
3306
|
+
|
3265
3307
|
def to_json(self):
|
3266
3308
|
return self.payload
|
3267
3309
|
|
@@ -4,6 +4,7 @@ import platform
|
|
4
4
|
import re
|
5
5
|
import sys
|
6
6
|
from hashlib import sha1
|
7
|
+
from time import sleep
|
7
8
|
|
8
9
|
from metaflow import JSONType, Run, current, decorators, parameters
|
9
10
|
from metaflow._vendor import click
|
@@ -959,6 +960,31 @@ def list_workflow_templates(obj, all=None):
|
|
959
960
|
obj.echo_always(template_name)
|
960
961
|
|
961
962
|
|
963
|
+
# Internal CLI command to run a heartbeat daemon in an Argo Workflows Daemon container.
|
964
|
+
@argo_workflows.command(hidden=True, help="start heartbeat process for a run")
|
965
|
+
@click.option("--run_id", required=True)
|
966
|
+
@click.option(
|
967
|
+
"--tag",
|
968
|
+
"tags",
|
969
|
+
multiple=True,
|
970
|
+
default=None,
|
971
|
+
help="Annotate all objects produced by Argo Workflows runs "
|
972
|
+
"with the given tag. You can specify this option multiple "
|
973
|
+
"times to attach multiple tags.",
|
974
|
+
)
|
975
|
+
@click.pass_obj
|
976
|
+
def heartbeat(obj, run_id, tags=None):
|
977
|
+
# Try to register a run in case the start task has not taken care of it yet.
|
978
|
+
obj.metadata.register_run_id(run_id, tags)
|
979
|
+
# Start run heartbeat
|
980
|
+
obj.metadata.start_run_heartbeat(obj.flow.name, run_id)
|
981
|
+
# Keepalive loop
|
982
|
+
while True:
|
983
|
+
# Do not pollute daemon logs with anything unnecessary,
|
984
|
+
# as they might be extremely long running.
|
985
|
+
sleep(10)
|
986
|
+
|
987
|
+
|
962
988
|
def validate_run_id(
|
963
989
|
workflow_name, token_prefix, authorize, run_id, instructions_fn=None
|
964
990
|
):
|
@@ -121,7 +121,10 @@ class KubernetesClient(object):
|
|
121
121
|
job_api = self._client.BatchV1Api()
|
122
122
|
pods = self._find_active_pods(flow_name, run_id, user)
|
123
123
|
|
124
|
+
active_pods = False
|
125
|
+
|
124
126
|
def _kill_pod(pod):
|
127
|
+
active_pods = True
|
125
128
|
echo("Killing Kubernetes pod %s\n" % pod.metadata.name)
|
126
129
|
try:
|
127
130
|
stream(
|
@@ -155,7 +158,10 @@ class KubernetesClient(object):
|
|
155
158
|
echo("failed to kill pod %s - %s" % (pod.metadata.name, str(e)))
|
156
159
|
|
157
160
|
with ThreadPoolExecutor() as executor:
|
158
|
-
executor.map(_kill_pod,
|
161
|
+
executor.map(_kill_pod, pods)
|
162
|
+
|
163
|
+
if not active_pods:
|
164
|
+
echo("No active Kubernetes pods found for run *%s*" % run_id)
|
159
165
|
|
160
166
|
def jobset(self, **kwargs):
|
161
167
|
return KubernetesJobSet(self, **kwargs)
|
@@ -543,7 +543,11 @@ class KubernetesDecorator(StepDecorator):
|
|
543
543
|
|
544
544
|
# TODO: Unify this method with the multi-node setup in @batch
|
545
545
|
def _setup_multinode_environment():
|
546
|
-
# FIXME
|
546
|
+
# TODO [FIXME SOON]
|
547
|
+
# Even if Kubernetes may deploy control pods before worker pods, there is always a
|
548
|
+
# possibility that the worker pods may start before the control. In the case that this happens,
|
549
|
+
# the worker pods will not be able to resolve the control pod's IP address and this will cause
|
550
|
+
# the worker pods to fail. This function should account for this in the near future.
|
547
551
|
import socket
|
548
552
|
|
549
553
|
try:
|
@@ -866,7 +866,13 @@ class KubernetesJobSet(object):
|
|
866
866
|
spec=dict(
|
867
867
|
replicatedJobs=[self.control.dump(), self.worker.dump()],
|
868
868
|
suspend=False,
|
869
|
-
startupPolicy=
|
869
|
+
startupPolicy=dict(
|
870
|
+
# We explicitly set an InOrder Startup policy so that
|
871
|
+
# we can ensure that the control pod starts before the worker pods.
|
872
|
+
# This is required so that when worker pods try to access the control's IP
|
873
|
+
# we are able to resolve the control's IP address.
|
874
|
+
startupPolicyOrder="InOrder"
|
875
|
+
),
|
870
876
|
successPolicy=None,
|
871
877
|
# The Failure Policy helps setting the number of retries for the jobset.
|
872
878
|
# but we don't rely on it and instead rely on either the local scheduler
|
@@ -89,7 +89,7 @@ if __name__ == "__main__":
|
|
89
89
|
# TODO: micromamba installation can be pawned off to micromamba.py
|
90
90
|
f"""set -e;
|
91
91
|
if ! command -v micromamba >/dev/null 2>&1; then
|
92
|
-
mkdir micromamba;
|
92
|
+
mkdir -p micromamba;
|
93
93
|
python -c "import requests, bz2, sys; data = requests.get('https://micro.mamba.pm/api/micromamba/{architecture}/1.5.7').content; sys.stdout.buffer.write(bz2.decompress(data))" | tar -xv -C $(pwd)/micromamba bin/micromamba --strip-components 1;
|
94
94
|
export PATH=$PATH:$(pwd)/micromamba;
|
95
95
|
if ! command -v micromamba >/dev/null 2>&1; then
|
@@ -253,7 +253,33 @@ class Micromamba(object):
|
|
253
253
|
try:
|
254
254
|
output = json.loads(e.output)
|
255
255
|
err = []
|
256
|
+
v_pkgs = ["__cuda", "__glibc"]
|
256
257
|
for error in output.get("solver_problems", []):
|
258
|
+
# raise a specific error message for virtual package related errors
|
259
|
+
match = next((p for p in v_pkgs if p in error), None)
|
260
|
+
if match is not None:
|
261
|
+
vpkg_name = match[2:]
|
262
|
+
# try to strip version from error msg which are of the format:
|
263
|
+
# nothing provides <__vpkg> >=2.17,<3.0.a0 needed by <pkg_name>
|
264
|
+
try:
|
265
|
+
vpkg_version = error[
|
266
|
+
len("nothing provides %s " % match) : error.index(
|
267
|
+
" needed by"
|
268
|
+
)
|
269
|
+
]
|
270
|
+
except ValueError:
|
271
|
+
vpkg_version = None
|
272
|
+
raise MicromambaException(
|
273
|
+
"Please set the environment variable CONDA_OVERRIDE_{var} to a specific version{version} of {name}.\n"
|
274
|
+
"Here is an example of supplying environment variables through the command line -\n\n"
|
275
|
+
"CONDA_OVERRIDE_{var}=<{name}-version> python flow.py <args>".format(
|
276
|
+
var=vpkg_name.upper(),
|
277
|
+
version=(
|
278
|
+
"" if not vpkg_version else (" (%s)" % vpkg_version)
|
279
|
+
),
|
280
|
+
name=vpkg_name,
|
281
|
+
),
|
282
|
+
)
|
257
283
|
err.append(error)
|
258
284
|
raise MicromambaException(
|
259
285
|
msg.format(
|
metaflow/runner/deployer.py
CHANGED
@@ -6,56 +6,11 @@ import importlib
|
|
6
6
|
import functools
|
7
7
|
import tempfile
|
8
8
|
|
9
|
-
from subprocess import CalledProcessError
|
10
9
|
from typing import Optional, Dict, ClassVar
|
11
10
|
|
12
11
|
from metaflow.exception import MetaflowNotFound
|
13
|
-
from metaflow.runner.subprocess_manager import
|
14
|
-
from metaflow.runner.utils import
|
15
|
-
|
16
|
-
|
17
|
-
def handle_timeout(
|
18
|
-
tfp_runner_attribute, command_obj: CommandManager, file_read_timeout: int
|
19
|
-
):
|
20
|
-
"""
|
21
|
-
Handle the timeout for a running subprocess command that reads a file
|
22
|
-
and raises an error with appropriate logs if a TimeoutError occurs.
|
23
|
-
|
24
|
-
Parameters
|
25
|
-
----------
|
26
|
-
tfp_runner_attribute : NamedTemporaryFile
|
27
|
-
Temporary file that stores runner attribute data.
|
28
|
-
command_obj : CommandManager
|
29
|
-
Command manager object that encapsulates the running command details.
|
30
|
-
file_read_timeout : int
|
31
|
-
Timeout for reading the file.
|
32
|
-
|
33
|
-
Returns
|
34
|
-
-------
|
35
|
-
str
|
36
|
-
Content read from the temporary file.
|
37
|
-
|
38
|
-
Raises
|
39
|
-
------
|
40
|
-
RuntimeError
|
41
|
-
If a TimeoutError occurs, it raises a RuntimeError with the command's
|
42
|
-
stdout and stderr logs.
|
43
|
-
"""
|
44
|
-
try:
|
45
|
-
content = read_from_file_when_ready(
|
46
|
-
tfp_runner_attribute.name, command_obj, timeout=file_read_timeout
|
47
|
-
)
|
48
|
-
return content
|
49
|
-
except (CalledProcessError, TimeoutError) as e:
|
50
|
-
stdout_log = open(command_obj.log_files["stdout"]).read()
|
51
|
-
stderr_log = open(command_obj.log_files["stderr"]).read()
|
52
|
-
command = " ".join(command_obj.command)
|
53
|
-
error_message = "Error executing: '%s':\n" % command
|
54
|
-
if stdout_log.strip():
|
55
|
-
error_message += "\nStdout:\n%s\n" % stdout_log
|
56
|
-
if stderr_log.strip():
|
57
|
-
error_message += "\nStderr:\n%s\n" % stderr_log
|
58
|
-
raise RuntimeError(error_message) from e
|
12
|
+
from metaflow.runner.subprocess_manager import SubprocessManager
|
13
|
+
from metaflow.runner.utils import handle_timeout
|
59
14
|
|
60
15
|
|
61
16
|
def get_lower_level_group(
|
@@ -209,7 +164,7 @@ class TriggeredRun(object):
|
|
209
164
|
elif callable(v):
|
210
165
|
setattr(self, k, functools.partial(v, self))
|
211
166
|
else:
|
212
|
-
setattr(self
|
167
|
+
setattr(self, k, v)
|
213
168
|
|
214
169
|
def wait_for_run(self, timeout=None):
|
215
170
|
"""
|
@@ -287,7 +242,7 @@ class DeployedFlow(object):
|
|
287
242
|
elif callable(v):
|
288
243
|
setattr(self, k, functools.partial(v, self))
|
289
244
|
else:
|
290
|
-
setattr(self
|
245
|
+
setattr(self, k, v)
|
291
246
|
|
292
247
|
|
293
248
|
class DeployerImpl(object):
|
@@ -1,14 +1,14 @@
|
|
1
1
|
import importlib
|
2
2
|
import os
|
3
3
|
import sys
|
4
|
+
import json
|
4
5
|
import tempfile
|
5
6
|
|
6
|
-
from subprocess import CalledProcessError
|
7
7
|
from typing import Dict, Iterator, Optional, Tuple
|
8
8
|
|
9
9
|
from metaflow import Run, metadata
|
10
10
|
|
11
|
-
from .utils import
|
11
|
+
from .utils import handle_timeout, clear_and_set_os_environ
|
12
12
|
from .subprocess_manager import CommandManager, SubprocessManager
|
13
13
|
|
14
14
|
|
@@ -102,16 +102,19 @@ class ExecutingRun(object):
|
|
102
102
|
for executing the run.
|
103
103
|
|
104
104
|
The return value is one of the following strings:
|
105
|
+
- `timeout` indicates that the run timed out.
|
105
106
|
- `running` indicates a currently executing run.
|
106
107
|
- `failed` indicates a failed run.
|
107
|
-
- `successful` a successful run.
|
108
|
+
- `successful` indicates a successful run.
|
108
109
|
|
109
110
|
Returns
|
110
111
|
-------
|
111
112
|
str
|
112
113
|
The current status of the run.
|
113
114
|
"""
|
114
|
-
if self.command_obj.
|
115
|
+
if self.command_obj.timeout:
|
116
|
+
return "timeout"
|
117
|
+
elif self.command_obj.process.returncode is None:
|
115
118
|
return "running"
|
116
119
|
elif self.command_obj.process.returncode != 0:
|
117
120
|
return "failed"
|
@@ -271,28 +274,22 @@ class Runner(object):
|
|
271
274
|
|
272
275
|
# It is thus necessary to set them to correct values before we return
|
273
276
|
# the Run object.
|
274
|
-
try:
|
275
|
-
# Set the environment variables to what they were before the run executed.
|
276
|
-
clear_and_set_os_environ(self.old_env)
|
277
277
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
if stderr_log.strip():
|
294
|
-
error_message += "\nStderr:\n%s\n" % stderr_log
|
295
|
-
raise RuntimeError(error_message) from e
|
278
|
+
content = handle_timeout(
|
279
|
+
tfp_runner_attribute, command_obj, self.file_read_timeout
|
280
|
+
)
|
281
|
+
content = json.loads(content)
|
282
|
+
pathspec = "%s/%s" % (content.get("flow_name"), content.get("run_id"))
|
283
|
+
|
284
|
+
# Set the environment variables to what they were before the run executed.
|
285
|
+
clear_and_set_os_environ(self.old_env)
|
286
|
+
|
287
|
+
# Set the correct metadata from the runner_attribute file corresponding to this run.
|
288
|
+
metadata_for_flow = content.get("metadata")
|
289
|
+
metadata(metadata_for_flow)
|
290
|
+
|
291
|
+
run_object = Run(pathspec, _namespace_check=False)
|
292
|
+
return ExecutingRun(self, command_obj, run_object)
|
296
293
|
|
297
294
|
def run(self, **kwargs) -> ExecutingRun:
|
298
295
|
"""
|
@@ -42,6 +42,19 @@ class SubprocessManager(object):
|
|
42
42
|
def __init__(self):
|
43
43
|
self.commands: Dict[int, CommandManager] = {}
|
44
44
|
|
45
|
+
try:
|
46
|
+
loop = asyncio.get_running_loop()
|
47
|
+
loop.add_signal_handler(
|
48
|
+
signal.SIGINT,
|
49
|
+
lambda: self._handle_sigint(signum=signal.SIGINT, frame=None),
|
50
|
+
)
|
51
|
+
except RuntimeError:
|
52
|
+
signal.signal(signal.SIGINT, self._handle_sigint)
|
53
|
+
|
54
|
+
def _handle_sigint(self, signum, frame):
|
55
|
+
for each_command in self.commands.values():
|
56
|
+
each_command.kill(termination_timeout=2)
|
57
|
+
|
45
58
|
async def __aenter__(self) -> "SubprocessManager":
|
46
59
|
return self
|
47
60
|
|
@@ -83,6 +96,7 @@ class SubprocessManager(object):
|
|
83
96
|
command_obj = CommandManager(command, env, cwd)
|
84
97
|
pid = command_obj.run(show_output=show_output)
|
85
98
|
self.commands[pid] = command_obj
|
99
|
+
command_obj.sync_wait()
|
86
100
|
return pid
|
87
101
|
|
88
102
|
async def async_run_command(
|
@@ -169,11 +183,12 @@ class CommandManager(object):
|
|
169
183
|
self.cwd = cwd if cwd is not None else os.getcwd()
|
170
184
|
|
171
185
|
self.process = None
|
186
|
+
self.stdout_thread = None
|
187
|
+
self.stderr_thread = None
|
172
188
|
self.run_called: bool = False
|
189
|
+
self.timeout: bool = False
|
173
190
|
self.log_files: Dict[str, str] = {}
|
174
191
|
|
175
|
-
signal.signal(signal.SIGINT, self._handle_sigint)
|
176
|
-
|
177
192
|
async def __aenter__(self) -> "CommandManager":
|
178
193
|
return self
|
179
194
|
|
@@ -214,13 +229,22 @@ class CommandManager(object):
|
|
214
229
|
else:
|
215
230
|
await asyncio.wait_for(self.emit_logs(stream), timeout)
|
216
231
|
except asyncio.TimeoutError:
|
232
|
+
self.timeout = True
|
217
233
|
command_string = " ".join(self.command)
|
218
|
-
|
234
|
+
self.kill(termination_timeout=2)
|
219
235
|
print(
|
220
236
|
"Timeout: The process (PID %d; command: '%s') did not complete "
|
221
237
|
"within %s seconds." % (self.process.pid, command_string, timeout)
|
222
238
|
)
|
223
239
|
|
240
|
+
def sync_wait(self):
|
241
|
+
if not self.run_called:
|
242
|
+
raise RuntimeError("No command run yet to wait for...")
|
243
|
+
|
244
|
+
self.process.wait()
|
245
|
+
self.stdout_thread.join()
|
246
|
+
self.stderr_thread.join()
|
247
|
+
|
224
248
|
def run(self, show_output: bool = False):
|
225
249
|
"""
|
226
250
|
Run the subprocess synchronously. This can only be called once.
|
@@ -265,22 +289,17 @@ class CommandManager(object):
|
|
265
289
|
|
266
290
|
self.run_called = True
|
267
291
|
|
268
|
-
stdout_thread = threading.Thread(
|
292
|
+
self.stdout_thread = threading.Thread(
|
269
293
|
target=stream_to_stdout_and_file,
|
270
294
|
args=(self.process.stdout, stdout_logfile),
|
271
295
|
)
|
272
|
-
stderr_thread = threading.Thread(
|
296
|
+
self.stderr_thread = threading.Thread(
|
273
297
|
target=stream_to_stdout_and_file,
|
274
298
|
args=(self.process.stderr, stderr_logfile),
|
275
299
|
)
|
276
300
|
|
277
|
-
stdout_thread.start()
|
278
|
-
stderr_thread.start()
|
279
|
-
|
280
|
-
self.process.wait()
|
281
|
-
|
282
|
-
stdout_thread.join()
|
283
|
-
stderr_thread.join()
|
301
|
+
self.stdout_thread.start()
|
302
|
+
self.stderr_thread.start()
|
284
303
|
|
285
304
|
return self.process.pid
|
286
305
|
except Exception as e:
|
@@ -441,13 +460,13 @@ class CommandManager(object):
|
|
441
460
|
if self.run_called:
|
442
461
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
443
462
|
|
444
|
-
|
463
|
+
def kill(self, termination_timeout: float = 2):
|
445
464
|
"""
|
446
465
|
Kill the subprocess and its descendants.
|
447
466
|
|
448
467
|
Parameters
|
449
468
|
----------
|
450
|
-
termination_timeout : float, default
|
469
|
+
termination_timeout : float, default 2
|
451
470
|
The time to wait after sending a SIGTERM to the process and its descendants
|
452
471
|
before sending a SIGKILL.
|
453
472
|
"""
|
@@ -457,9 +476,6 @@ class CommandManager(object):
|
|
457
476
|
else:
|
458
477
|
print("No process to kill.")
|
459
478
|
|
460
|
-
def _handle_sigint(self, signum, frame):
|
461
|
-
asyncio.create_task(self.kill())
|
462
|
-
|
463
479
|
|
464
480
|
async def main():
|
465
481
|
flow_file = "../try.py"
|
metaflow/runner/utils.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import os
|
2
2
|
import ast
|
3
3
|
import time
|
4
|
+
import asyncio
|
4
5
|
|
5
6
|
from subprocess import CalledProcessError
|
6
7
|
from typing import Dict, TYPE_CHECKING
|
@@ -40,6 +41,13 @@ def clear_and_set_os_environ(env: Dict):
|
|
40
41
|
os.environ.update(env)
|
41
42
|
|
42
43
|
|
44
|
+
def check_process_status(command_obj: "CommandManager"):
|
45
|
+
if isinstance(command_obj.process, asyncio.subprocess.Process):
|
46
|
+
return command_obj.process.returncode is not None
|
47
|
+
else:
|
48
|
+
return command_obj.process.poll() is not None
|
49
|
+
|
50
|
+
|
43
51
|
def read_from_file_when_ready(
|
44
52
|
file_path: str, command_obj: "CommandManager", timeout: float = 5
|
45
53
|
):
|
@@ -47,7 +55,7 @@ def read_from_file_when_ready(
|
|
47
55
|
with open(file_path, "r", encoding="utf-8") as file_pointer:
|
48
56
|
content = file_pointer.read()
|
49
57
|
while not content:
|
50
|
-
if command_obj
|
58
|
+
if check_process_status(command_obj):
|
51
59
|
# Check to make sure the file hasn't been read yet to avoid a race
|
52
60
|
# where the file is written between the end of this while loop and the
|
53
61
|
# poll call above.
|
@@ -64,3 +72,47 @@ def read_from_file_when_ready(
|
|
64
72
|
time.sleep(0.1)
|
65
73
|
content = file_pointer.read()
|
66
74
|
return content
|
75
|
+
|
76
|
+
|
77
|
+
def handle_timeout(
|
78
|
+
tfp_runner_attribute, command_obj: "CommandManager", file_read_timeout: int
|
79
|
+
):
|
80
|
+
"""
|
81
|
+
Handle the timeout for a running subprocess command that reads a file
|
82
|
+
and raises an error with appropriate logs if a TimeoutError occurs.
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
tfp_runner_attribute : NamedTemporaryFile
|
87
|
+
Temporary file that stores runner attribute data.
|
88
|
+
command_obj : CommandManager
|
89
|
+
Command manager object that encapsulates the running command details.
|
90
|
+
file_read_timeout : int
|
91
|
+
Timeout for reading the file.
|
92
|
+
|
93
|
+
Returns
|
94
|
+
-------
|
95
|
+
str
|
96
|
+
Content read from the temporary file.
|
97
|
+
|
98
|
+
Raises
|
99
|
+
------
|
100
|
+
RuntimeError
|
101
|
+
If a TimeoutError occurs, it raises a RuntimeError with the command's
|
102
|
+
stdout and stderr logs.
|
103
|
+
"""
|
104
|
+
try:
|
105
|
+
content = read_from_file_when_ready(
|
106
|
+
tfp_runner_attribute.name, command_obj, timeout=file_read_timeout
|
107
|
+
)
|
108
|
+
return content
|
109
|
+
except (CalledProcessError, TimeoutError) as e:
|
110
|
+
stdout_log = open(command_obj.log_files["stdout"]).read()
|
111
|
+
stderr_log = open(command_obj.log_files["stderr"]).read()
|
112
|
+
command = " ".join(command_obj.command)
|
113
|
+
error_message = "Error executing: '%s':\n" % command
|
114
|
+
if stdout_log.strip():
|
115
|
+
error_message += "\nStdout:\n%s\n" % stdout_log
|
116
|
+
if stderr_log.strip():
|
117
|
+
error_message += "\nStderr:\n%s\n" % stderr_log
|
118
|
+
raise RuntimeError(error_message) from e
|
metaflow/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
metaflow_version = "2.12.
|
1
|
+
metaflow_version = "2.12.23"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: metaflow
|
3
|
-
Version: 2.12.
|
3
|
+
Version: 2.12.23
|
4
4
|
Summary: Metaflow: More Data Science, Less Engineering
|
5
5
|
Author: Metaflow Developers
|
6
6
|
Author-email: help@metaflow.org
|
@@ -26,7 +26,7 @@ License-File: LICENSE
|
|
26
26
|
Requires-Dist: requests
|
27
27
|
Requires-Dist: boto3
|
28
28
|
Provides-Extra: stubs
|
29
|
-
Requires-Dist: metaflow-stubs==2.12.
|
29
|
+
Requires-Dist: metaflow-stubs==2.12.23; extra == "stubs"
|
30
30
|
|
31
31
|

|
32
32
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
metaflow/R.py,sha256=CqVfIatvmjciuICNnoyyNGrwE7Va9iXfLdFbQa52hwA,3958
|
2
2
|
metaflow/__init__.py,sha256=9pnkRH00bbtzaQDSxEspANaB3r6YDC37_EjX7GxtJug,5641
|
3
3
|
metaflow/cards.py,sha256=tP1_RrtmqdFh741pqE4t98S7SA0MtGRlGvRICRZF1Mg,426
|
4
|
-
metaflow/cli.py,sha256=
|
4
|
+
metaflow/cli.py,sha256=vz8flftUkmRBdjHHwWREsFecfNqlFF0YoAKSzexE30w,34494
|
5
5
|
metaflow/cli_args.py,sha256=lcgBGNTvfaiPxiUnejAe60Upt9swG6lRy1_3OqbU6MY,2616
|
6
6
|
metaflow/clone_util.py,sha256=XfUX0vssu_hPlyZfhFl1AOnKkLqvt33Qp8xNrmdocGg,2057
|
7
7
|
metaflow/cmd_with_io.py,sha256=kl53HkAIyv0ecpItv08wZYczv7u3msD1VCcciqigqf0,588
|
@@ -21,7 +21,7 @@ metaflow/metaflow_config_funcs.py,sha256=5GlvoafV6SxykwfL8D12WXSfwjBN_NsyuKE_Q3g
|
|
21
21
|
metaflow/metaflow_current.py,sha256=pC-EMnAsnvBLvLd61W6MvfiCKcboryeui9f6r8z_sg8,7161
|
22
22
|
metaflow/metaflow_environment.py,sha256=rojFyGdyY56sN1HaEb1-0XX53Q3XPNnl0SaH-8xXZ8w,7987
|
23
23
|
metaflow/metaflow_profile.py,sha256=jKPEW-hmAQO-htSxb9hXaeloLacAh41A35rMZH6G8pA,418
|
24
|
-
metaflow/metaflow_version.py,sha256=
|
24
|
+
metaflow/metaflow_version.py,sha256=duhIzfKZtcxMVMs2uiBqBvUarSHJqyWDwMhaBOQd_g0,7491
|
25
25
|
metaflow/monitor.py,sha256=T0NMaBPvXynlJAO_avKtk8OIIRMyEuMAyF8bIp79aZU,5323
|
26
26
|
metaflow/multicore_utils.py,sha256=vdTNgczVLODifscUbbveJbuSDOl3Y9pAxhr7sqYiNf4,4760
|
27
27
|
metaflow/package.py,sha256=QutDP6WzjwGk1UCKXqBfXa9F10Q--FlRr0J7fwlple0,7399
|
@@ -36,7 +36,7 @@ metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
|
|
36
36
|
metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
|
37
37
|
metaflow/util.py,sha256=olAvJK3y1it_k99MhLulTaAJo7OFVt5rnrD-ulIFLCU,13616
|
38
38
|
metaflow/vendor.py,sha256=FchtA9tH22JM-eEtJ2c9FpUdMn8sSb1VHuQS56EcdZk,5139
|
39
|
-
metaflow/version.py,sha256
|
39
|
+
metaflow/version.py,sha256=4Kj4wLDHQlSoABrTJYqXZSqf1PE_WoOTlHFEMNeYCyI,29
|
40
40
|
metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
|
41
41
|
metaflow/_vendor/typing_extensions.py,sha256=0nUs5p1A_UrZigrAVBoOEM6TxU37zzPDUtiij1ZwpNc,110417
|
42
42
|
metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
|
@@ -111,7 +111,7 @@ metaflow/_vendor/v3_6/importlib_metadata/_meta.py,sha256=_F48Hu_jFxkfKWz5wcYS8vO
|
|
111
111
|
metaflow/_vendor/v3_6/importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
|
112
112
|
metaflow/_vendor/v3_6/importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
113
113
|
metaflow/client/__init__.py,sha256=1GtQB4Y_CBkzaxg32L1syNQSlfj762wmLrfrDxGi1b8,226
|
114
|
-
metaflow/client/core.py,sha256=
|
114
|
+
metaflow/client/core.py,sha256=vDRmLhoRXOfFiIplY2Xp3Go5lnn-CKeJdPqtOjWIX4Y,74173
|
115
115
|
metaflow/client/filecache.py,sha256=Wy0yhhCqC1JZgebqi7z52GCwXYnkAqMZHTtxThvwBgM,15229
|
116
116
|
metaflow/cmd/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
117
117
|
metaflow/cmd/configure_cmd.py,sha256=o-DKnUf2FBo_HiMVyoyzQaGBSMtpbEPEdFTQZ0hkU-k,33396
|
@@ -119,8 +119,8 @@ metaflow/cmd/main_cli.py,sha256=E546zT_jYQKysmjwfpEgzZd5QMsyirs28M2s0OPU93E,2966
|
|
119
119
|
metaflow/cmd/tutorials_cmd.py,sha256=8FdlKkicTOhCIDKcBR5b0Oz6giDvS-EMY3o9skIrRqw,5156
|
120
120
|
metaflow/cmd/util.py,sha256=jS_0rUjOnGGzPT65fzRLdGjrYAOOLA4jU2S0HJLV0oc,406
|
121
121
|
metaflow/cmd/develop/__init__.py,sha256=p1Sy8yU1MEKSrH5ttOWOZvNcI1qYu6J6jghdTHwPgOw,689
|
122
|
-
metaflow/cmd/develop/stub_generator.py,sha256
|
123
|
-
metaflow/cmd/develop/stubs.py,sha256=
|
122
|
+
metaflow/cmd/develop/stub_generator.py,sha256=_P_80CRFxyYjoMFynwg0IhAiexL9Wh2WqsnagiaVYVw,48050
|
123
|
+
metaflow/cmd/develop/stubs.py,sha256=JX2qNZDvG0upvPueAcLhoR_zyLtRranZMwY05tLdpRQ,11884
|
124
124
|
metaflow/datastore/__init__.py,sha256=VxP6ddJt3rwiCkpiSfAhyVkUCOe1pgZZsytVEJzFmSQ,155
|
125
125
|
metaflow/datastore/content_addressed_store.py,sha256=6T7tNqL29kpmecyMLHF35RhoSBOb-OZcExnsB65AvnI,7641
|
126
126
|
metaflow/datastore/datastore_set.py,sha256=R5pwnxg1DD8kBY9vElvd2eMknrvwTyiSwvQs67_z9bc,2361
|
@@ -173,14 +173,13 @@ metaflow/plugins/airflow/sensors/base_sensor.py,sha256=s-OQBfPWZ_T3wn96Ua59CCEj1
|
|
173
173
|
metaflow/plugins/airflow/sensors/external_task_sensor.py,sha256=zhYlrZnXT20KW8-fVk0fCNtTyNiKJB5PMVASacu30r0,6034
|
174
174
|
metaflow/plugins/airflow/sensors/s3_sensor.py,sha256=iDReG-7FKnumrtQg-HY6cCUAAqNA90nARrjjjEEk_x4,3275
|
175
175
|
metaflow/plugins/argo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
176
|
-
metaflow/plugins/argo/argo_client.py,sha256=
|
176
|
+
metaflow/plugins/argo/argo_client.py,sha256=KTUpP0DmnmNsMp4tbdNyKX_zOdTFRVpUkrf7Vv79d-o,16011
|
177
177
|
metaflow/plugins/argo/argo_events.py,sha256=_C1KWztVqgi3zuH57pInaE9OzABc2NnncC-zdwOMZ-w,5909
|
178
|
-
metaflow/plugins/argo/argo_workflows.py,sha256=
|
179
|
-
metaflow/plugins/argo/argo_workflows_cli.py,sha256=
|
178
|
+
metaflow/plugins/argo/argo_workflows.py,sha256=a-ew9ZwBq9nw9W9fCTLIcOlhnuKZZBTbBcyyofkjqWk,173003
|
179
|
+
metaflow/plugins/argo/argo_workflows_cli.py,sha256=E_PyhOtxuS2F8DwhBANsRZCMxpeZ5rfID8eksfSOPm8,37231
|
180
180
|
metaflow/plugins/argo/argo_workflows_decorator.py,sha256=yprszMdbE3rBTcEA9VR0IEnPjTprUauZBc4SBb-Q7sA,7878
|
181
181
|
metaflow/plugins/argo/argo_workflows_deployer.py,sha256=wSSZtThn_VPvE_Wu6NB1L0Q86LmBJh9g009v_lpvBPM,8125
|
182
182
|
metaflow/plugins/argo/capture_error.py,sha256=Ys9dscGrTpW-ZCirLBU0gD9qBM0BjxyxGlUMKcwewQc,1852
|
183
|
-
metaflow/plugins/argo/daemon.py,sha256=dJOS_UUISXBYffi3oGVKPwq4Pa4P_nGBGL15piPaPto,1776
|
184
183
|
metaflow/plugins/argo/generate_input_paths.py,sha256=loYsI6RFX9LlFsHb7Fe-mzlTTtRdySoOu7sYDy-uXK0,881
|
185
184
|
metaflow/plugins/argo/jobset_input_paths.py,sha256=_JhZWngA6p9Q_O2fx3pdzKI0WE-HPRHz_zFvY2pHPTQ,525
|
186
185
|
metaflow/plugins/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -283,18 +282,18 @@ metaflow/plugins/kubernetes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
283
282
|
metaflow/plugins/kubernetes/kube_utils.py,sha256=fYDlvqi8jYPsWijDwT6Z2qhQswyFqv7tiwtic_I80Vg,749
|
284
283
|
metaflow/plugins/kubernetes/kubernetes.py,sha256=bKBqgZXnIDkoa4xKtKoV6InPtYQy4CujfvcbQ3Pvsbc,31305
|
285
284
|
metaflow/plugins/kubernetes/kubernetes_cli.py,sha256=sFZ9Zrjef85vCO0MGpUF-em8Pw3dePFb3hbX3PtAH4I,13463
|
286
|
-
metaflow/plugins/kubernetes/kubernetes_client.py,sha256=
|
287
|
-
metaflow/plugins/kubernetes/kubernetes_decorator.py,sha256=
|
285
|
+
metaflow/plugins/kubernetes/kubernetes_client.py,sha256=y_CswSO_OdDW8eC56lwKwC69JqIfo9tYVw0njXc_sj8,6519
|
286
|
+
metaflow/plugins/kubernetes/kubernetes_decorator.py,sha256=xz2tEIapYWMd9rRiOe8qcYvjRIKT5piWnq-twdySpD8,26031
|
288
287
|
metaflow/plugins/kubernetes/kubernetes_job.py,sha256=Cfkee8LbXC17jSXWoeNdomQRvF_8YSeXNg1gvxm6E_M,31806
|
289
|
-
metaflow/plugins/kubernetes/kubernetes_jobsets.py,sha256=
|
288
|
+
metaflow/plugins/kubernetes/kubernetes_jobsets.py,sha256=wb0sK1OxW7pRbKdj6bWB4JsskXDsoKKqjyUWo4N9Y6E,41196
|
290
289
|
metaflow/plugins/metadata/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
291
290
|
metaflow/plugins/metadata/local.py,sha256=YhLJC5zjVJrvQFIyQ92ZBByiUmhCC762RUX7ITX12O8,22428
|
292
291
|
metaflow/plugins/metadata/service.py,sha256=ihq5F7KQZlxvYwzH_-jyP2aWN_I96i2vp92j_d697s8,20204
|
293
292
|
metaflow/plugins/pypi/__init__.py,sha256=0YFZpXvX7HCkyBFglatual7XGifdA1RwC3U4kcizyak,1037
|
294
|
-
metaflow/plugins/pypi/bootstrap.py,sha256=
|
293
|
+
metaflow/plugins/pypi/bootstrap.py,sha256=FI-itExqIz7DUzLnnkGwoB60rFBviygpIFThUtqk_4E,5227
|
295
294
|
metaflow/plugins/pypi/conda_decorator.py,sha256=fPeXxvmg51oSFTnlguNlcWUIdXHA9OuMnp9ElaxQPFo,15695
|
296
295
|
metaflow/plugins/pypi/conda_environment.py,sha256=--q-8lypKupCdGsASpqABNpNqRxtQi6UCDgq8iHDFe4,19476
|
297
|
-
metaflow/plugins/pypi/micromamba.py,sha256=
|
296
|
+
metaflow/plugins/pypi/micromamba.py,sha256=HQIxsixkLjqs0ukWGTlATNu5DrbisReOr39Qd21_GZo,13737
|
298
297
|
metaflow/plugins/pypi/pip.py,sha256=7B06mPOs5MvY33xbzPVYZlBr1iKMYaN-n8uulL9zSVg,13649
|
299
298
|
metaflow/plugins/pypi/pypi_decorator.py,sha256=rDMbHl7r81Ye7-TuIlKAVJ_CDnfjl9jV44ZPws-UsTY,7229
|
300
299
|
metaflow/plugins/pypi/pypi_environment.py,sha256=FYMg8kF3lXqcLfRYWD83a9zpVjcoo_TARqMGZ763rRk,230
|
@@ -304,12 +303,12 @@ metaflow/plugins/secrets/inline_secrets_provider.py,sha256=EChmoBGA1i7qM3jtYwPpL
|
|
304
303
|
metaflow/plugins/secrets/secrets_decorator.py,sha256=s-sFzPWOjahhpr5fMj-ZEaHkDYAPTO0isYXGvaUwlG8,11273
|
305
304
|
metaflow/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
306
305
|
metaflow/runner/click_api.py,sha256=Qfg4BOz5K2LaXTYBsi1y4zTfNIsGGHBVF3UkorX_-o8,13878
|
307
|
-
metaflow/runner/deployer.py,sha256=
|
308
|
-
metaflow/runner/metaflow_runner.py,sha256=
|
306
|
+
metaflow/runner/deployer.py,sha256=xNqxFIezWz3AcVqR4jeL-JnInGtefwEYMbXttxgB_I8,12276
|
307
|
+
metaflow/runner/metaflow_runner.py,sha256=I7Ao0GHHfP55nUCE8g6CpTPGjuWgXedSc9EZX-tIE2c,15001
|
309
308
|
metaflow/runner/nbdeploy.py,sha256=fP1s_5MeiDyT_igP82pB5EUqX9rOy2s06Hyc-OUbOvQ,4115
|
310
309
|
metaflow/runner/nbrun.py,sha256=lmvhzMCz7iC9LSPGRijifW1wMXxa4RW_jVmpdjQi22E,7261
|
311
|
-
metaflow/runner/subprocess_manager.py,sha256=
|
312
|
-
metaflow/runner/utils.py,sha256=
|
310
|
+
metaflow/runner/subprocess_manager.py,sha256=jC_PIYIeAp_G__lf6WHZF3Lxzpp-WAQleMrRZq9j7nc,20467
|
311
|
+
metaflow/runner/utils.py,sha256=aQ6WiNz9b-pqWWE14PdcAqti7_Zh_MIPlEA8zXJ6tXo,3807
|
313
312
|
metaflow/sidecar/__init__.py,sha256=1mmNpmQ5puZCpRmmYlCOeieZ4108Su9XQ4_EqF1FGOU,131
|
314
313
|
metaflow/sidecar/sidecar.py,sha256=EspKXvPPNiyRToaUZ51PS5TT_PzrBNAurn_wbFnmGr0,1334
|
315
314
|
metaflow/sidecar/sidecar_messages.py,sha256=zPsCoYgDIcDkkvdC9MEpJTJ3y6TSGm2JWkRc4vxjbFA,1071
|
@@ -346,9 +345,9 @@ metaflow/tutorials/07-worldview/README.md,sha256=5vQTrFqulJ7rWN6r20dhot9lI2sVj9W
|
|
346
345
|
metaflow/tutorials/07-worldview/worldview.ipynb,sha256=ztPZPI9BXxvW1QdS2Tfe7LBuVzvFvv0AToDnsDJhLdE,2237
|
347
346
|
metaflow/tutorials/08-autopilot/README.md,sha256=GnePFp_q76jPs991lMUqfIIh5zSorIeWznyiUxzeUVE,1039
|
348
347
|
metaflow/tutorials/08-autopilot/autopilot.ipynb,sha256=DQoJlILV7Mq9vfPBGW-QV_kNhWPjS5n6SJLqePjFYLY,3191
|
349
|
-
metaflow-2.12.
|
350
|
-
metaflow-2.12.
|
351
|
-
metaflow-2.12.
|
352
|
-
metaflow-2.12.
|
353
|
-
metaflow-2.12.
|
354
|
-
metaflow-2.12.
|
348
|
+
metaflow-2.12.23.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
|
349
|
+
metaflow-2.12.23.dist-info/METADATA,sha256=VWGnCbg946pb6ZQOT9i0GtBrF2dvLDawKzulk-CY2zI,5906
|
350
|
+
metaflow-2.12.23.dist-info/WHEEL,sha256=AHX6tWk3qWuce7vKLrj7lnulVHEdWoltgauo8bgCXgU,109
|
351
|
+
metaflow-2.12.23.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
|
352
|
+
metaflow-2.12.23.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
|
353
|
+
metaflow-2.12.23.dist-info/RECORD,,
|
metaflow/plugins/argo/daemon.py
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
from collections import namedtuple
|
2
|
-
from time import sleep
|
3
|
-
from metaflow.metaflow_config import DEFAULT_METADATA
|
4
|
-
from metaflow.metaflow_environment import MetaflowEnvironment
|
5
|
-
from metaflow.plugins import METADATA_PROVIDERS
|
6
|
-
from metaflow._vendor import click
|
7
|
-
|
8
|
-
|
9
|
-
class CliState:
|
10
|
-
pass
|
11
|
-
|
12
|
-
|
13
|
-
@click.group()
|
14
|
-
@click.option("--flow_name", required=True)
|
15
|
-
@click.option("--run_id", required=True)
|
16
|
-
@click.option(
|
17
|
-
"--tag",
|
18
|
-
"tags",
|
19
|
-
multiple=True,
|
20
|
-
default=None,
|
21
|
-
help="Annotate all objects produced by Argo Workflows runs "
|
22
|
-
"with the given tag. You can specify this option multiple "
|
23
|
-
"times to attach multiple tags.",
|
24
|
-
)
|
25
|
-
@click.pass_context
|
26
|
-
def cli(ctx, flow_name, run_id, tags=None):
|
27
|
-
ctx.obj = CliState()
|
28
|
-
ctx.obj.flow_name = flow_name
|
29
|
-
ctx.obj.run_id = run_id
|
30
|
-
ctx.obj.tags = tags
|
31
|
-
# Use a dummy flow to initialize the environment and metadata service,
|
32
|
-
# as we only need a name for the flow object.
|
33
|
-
flow = namedtuple("DummyFlow", "name")
|
34
|
-
dummyflow = flow(flow_name)
|
35
|
-
|
36
|
-
# Initialize a proper metadata service instance
|
37
|
-
environment = MetaflowEnvironment(dummyflow)
|
38
|
-
|
39
|
-
ctx.obj.metadata = [m for m in METADATA_PROVIDERS if m.TYPE == DEFAULT_METADATA][0](
|
40
|
-
environment, dummyflow, None, None
|
41
|
-
)
|
42
|
-
|
43
|
-
|
44
|
-
@cli.command(help="start heartbeat process for a run")
|
45
|
-
@click.pass_obj
|
46
|
-
def heartbeat(obj):
|
47
|
-
# Try to register a run in case the start task has not taken care of it yet.
|
48
|
-
obj.metadata.register_run_id(obj.run_id, obj.tags)
|
49
|
-
# Start run heartbeat
|
50
|
-
obj.metadata.start_run_heartbeat(obj.flow_name, obj.run_id)
|
51
|
-
# Keepalive loop
|
52
|
-
while True:
|
53
|
-
# Do not pollute daemon logs with anything unnecessary,
|
54
|
-
# as they might be extremely long running.
|
55
|
-
sleep(10)
|
56
|
-
|
57
|
-
|
58
|
-
if __name__ == "__main__":
|
59
|
-
cli()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|