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
         |