metaflow 2.12.38__py2.py3-none-any.whl → 2.13__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- metaflow/__init__.py +1 -1
 - metaflow/cli.py +111 -36
 - metaflow/cli_args.py +2 -2
 - metaflow/cli_components/run_cmds.py +3 -1
 - metaflow/datastore/flow_datastore.py +2 -2
 - metaflow/exception.py +8 -2
 - metaflow/flowspec.py +48 -36
 - metaflow/graph.py +28 -27
 - metaflow/includefile.py +2 -2
 - metaflow/lint.py +35 -20
 - metaflow/metaflow_config.py +5 -0
 - metaflow/parameters.py +11 -4
 - metaflow/plugins/argo/argo_workflows_deployer_objects.py +47 -1
 - metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
 - metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +3 -0
 - metaflow/plugins/cards/card_creator.py +1 -0
 - metaflow/plugins/cards/card_decorator.py +46 -8
 - metaflow/plugins/pypi/bootstrap.py +196 -61
 - metaflow/plugins/pypi/conda_decorator.py +14 -26
 - metaflow/plugins/pypi/conda_environment.py +76 -21
 - metaflow/plugins/pypi/micromamba.py +42 -15
 - metaflow/plugins/pypi/pip.py +8 -3
 - metaflow/plugins/pypi/pypi_decorator.py +10 -9
 - metaflow/runner/click_api.py +175 -39
 - metaflow/runner/deployer.py +1 -1
 - metaflow/runner/deployer_impl.py +8 -3
 - metaflow/runner/metaflow_runner.py +10 -2
 - metaflow/runner/nbdeploy.py +2 -0
 - metaflow/runner/nbrun.py +1 -1
 - metaflow/runner/subprocess_manager.py +3 -1
 - metaflow/runner/utils.py +41 -19
 - metaflow/user_configs/config_options.py +87 -34
 - metaflow/user_configs/config_parameters.py +44 -25
 - metaflow/util.py +2 -2
 - metaflow/version.py +1 -1
 - {metaflow-2.12.38.dist-info → metaflow-2.13.dist-info}/METADATA +2 -2
 - {metaflow-2.12.38.dist-info → metaflow-2.13.dist-info}/RECORD +41 -41
 - {metaflow-2.12.38.dist-info → metaflow-2.13.dist-info}/LICENSE +0 -0
 - {metaflow-2.12.38.dist-info → metaflow-2.13.dist-info}/WHEEL +0 -0
 - {metaflow-2.12.38.dist-info → metaflow-2.13.dist-info}/entry_points.txt +0 -0
 - {metaflow-2.12.38.dist-info → metaflow-2.13.dist-info}/top_level.txt +0 -0
 
    
        metaflow/plugins/pypi/pip.py
    CHANGED
    
    | 
         @@ -4,6 +4,7 @@ import re 
     | 
|
| 
       4 
4 
     | 
    
         
             
            import shutil
         
     | 
| 
       5 
5 
     | 
    
         
             
            import subprocess
         
     | 
| 
       6 
6 
     | 
    
         
             
            import tempfile
         
     | 
| 
      
 7 
     | 
    
         
            +
            import time
         
     | 
| 
       7 
8 
     | 
    
         
             
            from concurrent.futures import ThreadPoolExecutor
         
     | 
| 
       8 
9 
     | 
    
         
             
            from itertools import chain, product
         
     | 
| 
       9 
10 
     | 
    
         
             
            from urllib.parse import unquote
         
     | 
| 
         @@ -50,10 +51,14 @@ INSTALLATION_MARKER = "{prefix}/.pip/id" 
     | 
|
| 
       50 
51 
     | 
    
         | 
| 
       51 
52 
     | 
    
         | 
| 
       52 
53 
     | 
    
         
             
            class Pip(object):
         
     | 
| 
       53 
     | 
    
         
            -
                def __init__(self, micromamba=None):
         
     | 
| 
      
 54 
     | 
    
         
            +
                def __init__(self, micromamba=None, logger=None):
         
     | 
| 
       54 
55 
     | 
    
         
             
                    # pip is assumed to be installed inside a conda environment managed by
         
     | 
| 
       55 
56 
     | 
    
         
             
                    # micromamba. pip commands are executed using `micromamba run --prefix`
         
     | 
| 
       56 
     | 
    
         
            -
                    self.micromamba = micromamba or Micromamba()
         
     | 
| 
      
 57 
     | 
    
         
            +
                    self.micromamba = micromamba or Micromamba(logger)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    if logger:
         
     | 
| 
      
 59 
     | 
    
         
            +
                        self.logger = logger
         
     | 
| 
      
 60 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 61 
     | 
    
         
            +
                        self.logger = lambda *args, **kwargs: None  # No-op logger if not provided
         
     | 
| 
       57 
62 
     | 
    
         | 
| 
       58 
63 
     | 
    
         
             
                def solve(self, id_, packages, python, platform):
         
     | 
| 
       59 
64 
     | 
    
         
             
                    prefix = self.micromamba.path_to_environment(id_)
         
     | 
| 
         @@ -123,7 +128,7 @@ class Pip(object): 
     | 
|
| 
       123 
128 
     | 
    
         
             
                                    **res,
         
     | 
| 
       124 
129 
     | 
    
         
             
                                    subdir_str=(
         
     | 
| 
       125 
130 
     | 
    
         
             
                                        "#subdirectory=%s" % subdirectory if subdirectory else ""
         
     | 
| 
       126 
     | 
    
         
            -
                                    )
         
     | 
| 
      
 131 
     | 
    
         
            +
                                    ),
         
     | 
| 
       127 
132 
     | 
    
         
             
                                )
         
     | 
| 
       128 
133 
     | 
    
         
             
                                # used to deduplicate the storage location in case wheel does not
         
     | 
| 
       129 
134 
     | 
    
         
             
                                # build with enough unique identifiers.
         
     | 
| 
         @@ -25,9 +25,10 @@ class PyPIStepDecorator(StepDecorator): 
     | 
|
| 
       25 
25 
     | 
    
         
             
                defaults = {"packages": {}, "python": None, "disabled": None}  # wheels
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
                def __init__(self, attributes=None, statically_defined=False):
         
     | 
| 
       28 
     | 
    
         
            -
                    self. 
     | 
| 
       29 
     | 
    
         
            -
                        attributes. 
     | 
| 
      
 28 
     | 
    
         
            +
                    self._attributes_with_user_values = (
         
     | 
| 
      
 29 
     | 
    
         
            +
                        set(attributes.keys()) if attributes is not None else set()
         
     | 
| 
       30 
30 
     | 
    
         
             
                    )
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       31 
32 
     | 
    
         
             
                    super().__init__(attributes, statically_defined)
         
     | 
| 
       32 
33 
     | 
    
         | 
| 
       33 
34 
     | 
    
         
             
                def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
         
     | 
| 
         @@ -42,10 +43,9 @@ class PyPIStepDecorator(StepDecorator): 
     | 
|
| 
       42 
43 
     | 
    
         
             
                    if "pypi_base" in self.flow._flow_decorators:
         
     | 
| 
       43 
44 
     | 
    
         
             
                        pypi_base = self.flow._flow_decorators["pypi_base"][0]
         
     | 
| 
       44 
45 
     | 
    
         
             
                        super_attributes = pypi_base.attributes
         
     | 
| 
       45 
     | 
    
         
            -
                        self. 
     | 
| 
       46 
     | 
    
         
            -
                             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
                        }
         
     | 
| 
      
 46 
     | 
    
         
            +
                        self._attributes_with_user_values.update(
         
     | 
| 
      
 47 
     | 
    
         
            +
                            pypi_base._attributes_with_user_values
         
     | 
| 
      
 48 
     | 
    
         
            +
                        )
         
     | 
| 
       49 
49 
     | 
    
         
             
                        self.attributes["packages"] = {
         
     | 
| 
       50 
50 
     | 
    
         
             
                            **super_attributes["packages"],
         
     | 
| 
       51 
51 
     | 
    
         
             
                            **self.attributes["packages"],
         
     | 
| 
         @@ -106,7 +106,7 @@ class PyPIStepDecorator(StepDecorator): 
     | 
|
| 
       106 
106 
     | 
    
         
             
                    environment.set_local_root(LocalStorage.get_datastore_root_from_config(logger))
         
     | 
| 
       107 
107 
     | 
    
         | 
| 
       108 
108 
     | 
    
         
             
                def is_attribute_user_defined(self, name):
         
     | 
| 
       109 
     | 
    
         
            -
                    return name in self. 
     | 
| 
      
 109 
     | 
    
         
            +
                    return name in self._attributes_with_user_values
         
     | 
| 
       110 
110 
     | 
    
         | 
| 
       111 
111 
     | 
    
         | 
| 
       112 
112 
     | 
    
         
             
            class PyPIFlowDecorator(FlowDecorator):
         
     | 
| 
         @@ -129,9 +129,10 @@ class PyPIFlowDecorator(FlowDecorator): 
     | 
|
| 
       129 
129 
     | 
    
         
             
                defaults = {"packages": {}, "python": None, "disabled": None}
         
     | 
| 
       130 
130 
     | 
    
         | 
| 
       131 
131 
     | 
    
         
             
                def __init__(self, attributes=None, statically_defined=False):
         
     | 
| 
       132 
     | 
    
         
            -
                    self. 
     | 
| 
       133 
     | 
    
         
            -
                        attributes. 
     | 
| 
      
 132 
     | 
    
         
            +
                    self._attributes_with_user_values = (
         
     | 
| 
      
 133 
     | 
    
         
            +
                        set(attributes.keys()) if attributes is not None else set()
         
     | 
| 
       134 
134 
     | 
    
         
             
                    )
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
       135 
136 
     | 
    
         
             
                    super().__init__(attributes, statically_defined)
         
     | 
| 
       136 
137 
     | 
    
         | 
| 
       137 
138 
     | 
    
         
             
                def flow_init(
         
     | 
    
        metaflow/runner/click_api.py
    CHANGED
    
    | 
         @@ -18,6 +18,7 @@ import json 
     | 
|
| 
       18 
18 
     | 
    
         
             
            from collections import OrderedDict
         
     | 
| 
       19 
19 
     | 
    
         
             
            from typing import Any, Callable, Dict, List, Optional
         
     | 
| 
       20 
20 
     | 
    
         
             
            from typing import OrderedDict as TOrderedDict
         
     | 
| 
      
 21 
     | 
    
         
            +
            from typing import Tuple as TTuple
         
     | 
| 
       21 
22 
     | 
    
         
             
            from typing import Union
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
            from metaflow import FlowSpec, Parameter
         
     | 
| 
         @@ -38,8 +39,16 @@ from metaflow._vendor.typeguard import TypeCheckError, check_type 
     | 
|
| 
       38 
39 
     | 
    
         
             
            from metaflow.decorators import add_decorator_options
         
     | 
| 
       39 
40 
     | 
    
         
             
            from metaflow.exception import MetaflowException
         
     | 
| 
       40 
41 
     | 
    
         
             
            from metaflow.includefile import FilePathClass
         
     | 
| 
      
 42 
     | 
    
         
            +
            from metaflow.metaflow_config import CLICK_API_PROCESS_CONFIG
         
     | 
| 
       41 
43 
     | 
    
         
             
            from metaflow.parameters import JSONTypeClass, flow_context
         
     | 
| 
       42 
     | 
    
         
            -
            from metaflow.user_configs.config_options import  
     | 
| 
      
 44 
     | 
    
         
            +
            from metaflow.user_configs.config_options import (
         
     | 
| 
      
 45 
     | 
    
         
            +
                ConfigValue,
         
     | 
| 
      
 46 
     | 
    
         
            +
                ConvertDictOrStr,
         
     | 
| 
      
 47 
     | 
    
         
            +
                ConvertPath,
         
     | 
| 
      
 48 
     | 
    
         
            +
                LocalFileInput,
         
     | 
| 
      
 49 
     | 
    
         
            +
                MultipleTuple,
         
     | 
| 
      
 50 
     | 
    
         
            +
                config_options_with_config_input,
         
     | 
| 
      
 51 
     | 
    
         
            +
            )
         
     | 
| 
       43 
52 
     | 
    
         | 
| 
       44 
53 
     | 
    
         
             
            # Define a recursive type alias for JSON
         
     | 
| 
       45 
54 
     | 
    
         
             
            JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
         
     | 
| 
         @@ -58,6 +67,7 @@ click_to_python_types = { 
     | 
|
| 
       58 
67 
     | 
    
         
             
                JSONTypeClass: JSON,
         
     | 
| 
       59 
68 
     | 
    
         
             
                FilePathClass: str,
         
     | 
| 
       60 
69 
     | 
    
         
             
                LocalFileInput: str,
         
     | 
| 
      
 70 
     | 
    
         
            +
                MultipleTuple: TTuple[str, Union[JSON, ConfigValue]],
         
     | 
| 
       61 
71 
     | 
    
         
             
            }
         
     | 
| 
       62 
72 
     | 
    
         | 
| 
       63 
73 
     | 
    
         | 
| 
         @@ -68,7 +78,7 @@ def _method_sanity_check( 
     | 
|
| 
       68 
78 
     | 
    
         
             
                defaults: TOrderedDict[str, Any],
         
     | 
| 
       69 
79 
     | 
    
         
             
                **kwargs
         
     | 
| 
       70 
80 
     | 
    
         
             
            ) -> Dict[str, Any]:
         
     | 
| 
       71 
     | 
    
         
            -
                method_params = {"args": {}, "options": {}}
         
     | 
| 
      
 81 
     | 
    
         
            +
                method_params = {"args": {}, "options": {}, "defaults": defaults}
         
     | 
| 
       72 
82 
     | 
    
         | 
| 
       73 
83 
     | 
    
         
             
                possible_params = OrderedDict()
         
     | 
| 
       74 
84 
     | 
    
         
             
                possible_params.update(possible_arg_params)
         
     | 
| 
         @@ -90,10 +100,26 @@ def _method_sanity_check( 
     | 
|
| 
       90 
100 
     | 
    
         
             
                            % (supplied_k, annotations[supplied_k], defaults[supplied_k])
         
     | 
| 
       91 
101 
     | 
    
         
             
                        )
         
     | 
| 
       92 
102 
     | 
    
         | 
| 
       93 
     | 
    
         
            -
                    #  
     | 
| 
       94 
     | 
    
         
            -
                     
     | 
| 
       95 
     | 
    
         
            -
                         
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
      
 103 
     | 
    
         
            +
                    # Clean up values to make them into what click expects
         
     | 
| 
      
 104 
     | 
    
         
            +
                    if annotations[supplied_k] == JSON:
         
     | 
| 
      
 105 
     | 
    
         
            +
                        # JSON should be a string (json dumps)
         
     | 
| 
      
 106 
     | 
    
         
            +
                        supplied_v = json.dumps(supplied_v)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    elif supplied_k == "config_value":
         
     | 
| 
      
 108 
     | 
    
         
            +
                        # Special handling of config value because we need to go look in the tuple
         
     | 
| 
      
 109 
     | 
    
         
            +
                        new_list = []
         
     | 
| 
      
 110 
     | 
    
         
            +
                        for cfg_name, cfg_value in supplied_v:
         
     | 
| 
      
 111 
     | 
    
         
            +
                            if isinstance(cfg_value, ConfigValue):
         
     | 
| 
      
 112 
     | 
    
         
            +
                                # ConfigValue should be JSONified and converted to a string
         
     | 
| 
      
 113 
     | 
    
         
            +
                                new_list.append((cfg_name, json.dumps(cfg_value.to_dict())))
         
     | 
| 
      
 114 
     | 
    
         
            +
                            elif isinstance(cfg_value, dict):
         
     | 
| 
      
 115 
     | 
    
         
            +
                                # ConfigValue passed as a dictionary
         
     | 
| 
      
 116 
     | 
    
         
            +
                                new_list.append((cfg_name, json.dumps(cfg_value)))
         
     | 
| 
      
 117 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 118 
     | 
    
         
            +
                                raise TypeError(
         
     | 
| 
      
 119 
     | 
    
         
            +
                                    "Invalid type for a config-value, expected a ConfigValue or "
         
     | 
| 
      
 120 
     | 
    
         
            +
                                    "dict but got '%s'" % type(cfg_value)
         
     | 
| 
      
 121 
     | 
    
         
            +
                                )
         
     | 
| 
      
 122 
     | 
    
         
            +
                        supplied_v = new_list
         
     | 
| 
       97 
123 
     | 
    
         | 
| 
       98 
124 
     | 
    
         
             
                    if supplied_k in possible_arg_params:
         
     | 
| 
       99 
125 
     | 
    
         
             
                        cli_name = possible_arg_params[supplied_k].opts[0].strip("-")
         
     | 
| 
         @@ -188,35 +214,56 @@ loaded_modules = {} 
     | 
|
| 
       188 
214 
     | 
    
         
             
            def extract_flow_class_from_file(flow_file: str) -> FlowSpec:
         
     | 
| 
       189 
215 
     | 
    
         
             
                if not os.path.exists(flow_file):
         
     | 
| 
       190 
216 
     | 
    
         
             
                    raise FileNotFoundError("Flow file not present at '%s'" % flow_file)
         
     | 
| 
       191 
     | 
    
         
            -
             
     | 
| 
       192 
     | 
    
         
            -
                 
     | 
| 
       193 
     | 
    
         
            -
             
     | 
| 
       194 
     | 
    
         
            -
             
     | 
| 
       195 
     | 
    
         
            -
             
     | 
| 
       196 
     | 
    
         
            -
             
     | 
| 
       197 
     | 
    
         
            -
                     
     | 
| 
       198 
     | 
    
         
            -
                     
     | 
| 
       199 
     | 
    
         
            -
             
     | 
| 
       200 
     | 
    
         
            -
             
     | 
| 
       201 
     | 
    
         
            -
             
     | 
| 
       202 
     | 
    
         
            -
             
     | 
| 
       203 
     | 
    
         
            -
             
     | 
| 
       204 
     | 
    
         
            -
             
     | 
| 
       205 
     | 
    
         
            -
             
     | 
| 
       206 
     | 
    
         
            -
                         
     | 
| 
       207 
     | 
    
         
            -
             
     | 
| 
       208 
     | 
    
         
            -
             
     | 
| 
       209 
     | 
    
         
            -
             
     | 
| 
       210 
     | 
    
         
            -
                         
     | 
| 
       211 
     | 
    
         
            -
             
     | 
| 
       212 
     | 
    
         
            -
             
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                flow_dir = os.path.dirname(os.path.abspath(flow_file))
         
     | 
| 
      
 219 
     | 
    
         
            +
                path_was_added = False
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                # Only add to path if it's not already there
         
     | 
| 
      
 222 
     | 
    
         
            +
                if flow_dir not in sys.path:
         
     | 
| 
      
 223 
     | 
    
         
            +
                    sys.path.insert(0, flow_dir)
         
     | 
| 
      
 224 
     | 
    
         
            +
                    path_was_added = True
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                try:
         
     | 
| 
      
 227 
     | 
    
         
            +
                    # Check if the module has already been loaded
         
     | 
| 
      
 228 
     | 
    
         
            +
                    if flow_file in loaded_modules:
         
     | 
| 
      
 229 
     | 
    
         
            +
                        module = loaded_modules[flow_file]
         
     | 
| 
      
 230 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 231 
     | 
    
         
            +
                        # Load the module if it's not already loaded
         
     | 
| 
      
 232 
     | 
    
         
            +
                        spec = importlib.util.spec_from_file_location("module", flow_file)
         
     | 
| 
      
 233 
     | 
    
         
            +
                        module = importlib.util.module_from_spec(spec)
         
     | 
| 
      
 234 
     | 
    
         
            +
                        spec.loader.exec_module(module)
         
     | 
| 
      
 235 
     | 
    
         
            +
                        # Cache the loaded module
         
     | 
| 
      
 236 
     | 
    
         
            +
                        loaded_modules[flow_file] = module
         
     | 
| 
      
 237 
     | 
    
         
            +
                    classes = inspect.getmembers(module, inspect.isclass)
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
                    flow_cls = None
         
     | 
| 
      
 240 
     | 
    
         
            +
                    for _, kls in classes:
         
     | 
| 
      
 241 
     | 
    
         
            +
                        if kls != FlowSpec and issubclass(kls, FlowSpec):
         
     | 
| 
      
 242 
     | 
    
         
            +
                            if flow_cls is not None:
         
     | 
| 
      
 243 
     | 
    
         
            +
                                raise MetaflowException(
         
     | 
| 
      
 244 
     | 
    
         
            +
                                    "Multiple FlowSpec classes found in %s" % flow_file
         
     | 
| 
      
 245 
     | 
    
         
            +
                                )
         
     | 
| 
      
 246 
     | 
    
         
            +
                            flow_cls = kls
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
                    if flow_cls is None:
         
     | 
| 
      
 249 
     | 
    
         
            +
                        raise MetaflowException("No FlowSpec class found in %s" % flow_file)
         
     | 
| 
      
 250 
     | 
    
         
            +
                    return flow_cls
         
     | 
| 
      
 251 
     | 
    
         
            +
                finally:
         
     | 
| 
      
 252 
     | 
    
         
            +
                    # Only remove from path if we added it
         
     | 
| 
      
 253 
     | 
    
         
            +
                    if path_was_added:
         
     | 
| 
      
 254 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 255 
     | 
    
         
            +
                            sys.path.remove(flow_dir)
         
     | 
| 
      
 256 
     | 
    
         
            +
                        except ValueError:
         
     | 
| 
      
 257 
     | 
    
         
            +
                            # User's code might have removed it already
         
     | 
| 
      
 258 
     | 
    
         
            +
                            pass
         
     | 
| 
       213 
259 
     | 
    
         | 
| 
       214 
260 
     | 
    
         | 
| 
       215 
261 
     | 
    
         
             
            class MetaflowAPI(object):
         
     | 
| 
       216 
     | 
    
         
            -
                def __init__(self, parent=None, flow_cls=None, **kwargs):
         
     | 
| 
      
 262 
     | 
    
         
            +
                def __init__(self, parent=None, flow_cls=None, config_input=None, **kwargs):
         
     | 
| 
       217 
263 
     | 
    
         
             
                    self._parent = parent
         
     | 
| 
       218 
264 
     | 
    
         
             
                    self._chain = [{self._API_NAME: kwargs}]
         
     | 
| 
       219 
265 
     | 
    
         
             
                    self._flow_cls = flow_cls
         
     | 
| 
      
 266 
     | 
    
         
            +
                    self._config_input = config_input
         
     | 
| 
       220 
267 
     | 
    
         
             
                    self._cached_computed_parameters = None
         
     | 
| 
       221 
268 
     | 
    
         | 
| 
       222 
269 
     | 
    
         
             
                @property
         
     | 
| 
         @@ -238,11 +285,19 @@ class MetaflowAPI(object): 
     | 
|
| 
       238 
285 
     | 
    
         
             
                    flow_cls = extract_flow_class_from_file(flow_file)
         
     | 
| 
       239 
286 
     | 
    
         | 
| 
       240 
287 
     | 
    
         
             
                    with flow_context(flow_cls) as _:
         
     | 
| 
       241 
     | 
    
         
            -
                         
     | 
| 
      
 288 
     | 
    
         
            +
                        cli_collection, config_input = config_options_with_config_input(
         
     | 
| 
      
 289 
     | 
    
         
            +
                            cli_collection
         
     | 
| 
      
 290 
     | 
    
         
            +
                        )
         
     | 
| 
      
 291 
     | 
    
         
            +
                        cli_collection = add_decorator_options(cli_collection)
         
     | 
| 
       242 
292 
     | 
    
         | 
| 
       243 
293 
     | 
    
         
             
                    def getattr_wrapper(_self, name):
         
     | 
| 
       244 
294 
     | 
    
         
             
                        # Functools.partial do not automatically bind self (no __get__)
         
     | 
| 
       245 
     | 
    
         
            -
                         
     | 
| 
      
 295 
     | 
    
         
            +
                        with flow_context(flow_cls) as _:
         
     | 
| 
      
 296 
     | 
    
         
            +
                            # We also wrap this in the proper flow context because since commands
         
     | 
| 
      
 297 
     | 
    
         
            +
                            # are loaded lazily, we need the proper flow context to compute things
         
     | 
| 
      
 298 
     | 
    
         
            +
                            # like parameters. If we do not do this, the outer flow's context will
         
     | 
| 
      
 299 
     | 
    
         
            +
                            # be used.
         
     | 
| 
      
 300 
     | 
    
         
            +
                            return _self._internal_getattr(_self, name)
         
     | 
| 
       246 
301 
     | 
    
         | 
| 
       247 
302 
     | 
    
         
             
                    class_dict = {
         
     | 
| 
       248 
303 
     | 
    
         
             
                        "__module__": "metaflow",
         
     | 
| 
         @@ -272,7 +327,12 @@ class MetaflowAPI(object): 
     | 
|
| 
       272 
327 
     | 
    
         
             
                            defaults,
         
     | 
| 
       273 
328 
     | 
    
         
             
                            **kwargs,
         
     | 
| 
       274 
329 
     | 
    
         
             
                        )
         
     | 
| 
       275 
     | 
    
         
            -
                        return to_return( 
     | 
| 
      
 330 
     | 
    
         
            +
                        return to_return(
         
     | 
| 
      
 331 
     | 
    
         
            +
                            parent=None,
         
     | 
| 
      
 332 
     | 
    
         
            +
                            flow_cls=flow_cls,
         
     | 
| 
      
 333 
     | 
    
         
            +
                            config_input=config_input,
         
     | 
| 
      
 334 
     | 
    
         
            +
                            **method_params,
         
     | 
| 
      
 335 
     | 
    
         
            +
                        )
         
     | 
| 
       276 
336 
     | 
    
         | 
| 
       277 
337 
     | 
    
         
             
                    m = _method
         
     | 
| 
       278 
338 
     | 
    
         
             
                    m.__name__ = cli_collection.name
         
     | 
| 
         @@ -313,8 +373,12 @@ class MetaflowAPI(object): 
     | 
|
| 
       313 
373 
     | 
    
         
             
                            for k, v in options.items():
         
     | 
| 
       314 
374 
     | 
    
         
             
                                if isinstance(v, list):
         
     | 
| 
       315 
375 
     | 
    
         
             
                                    for i in v:
         
     | 
| 
       316 
     | 
    
         
            -
                                         
     | 
| 
       317 
     | 
    
         
            -
             
     | 
| 
      
 376 
     | 
    
         
            +
                                        if isinstance(i, tuple):
         
     | 
| 
      
 377 
     | 
    
         
            +
                                            components.append("--%s" % k)
         
     | 
| 
      
 378 
     | 
    
         
            +
                                            components.extend(map(str, i))
         
     | 
| 
      
 379 
     | 
    
         
            +
                                        else:
         
     | 
| 
      
 380 
     | 
    
         
            +
                                            components.append("--%s" % k)
         
     | 
| 
      
 381 
     | 
    
         
            +
                                            components.append(str(i))
         
     | 
| 
       318 
382 
     | 
    
         
             
                                else:
         
     | 
| 
       319 
383 
     | 
    
         
             
                                    components.append("--%s" % k)
         
     | 
| 
       320 
384 
     | 
    
         
             
                                    if v != "flag":
         
     | 
| 
         @@ -323,21 +387,93 @@ class MetaflowAPI(object): 
     | 
|
| 
       323 
387 
     | 
    
         
             
                    return components
         
     | 
| 
       324 
388 
     | 
    
         | 
| 
       325 
389 
     | 
    
         
             
                def _compute_flow_parameters(self):
         
     | 
| 
       326 
     | 
    
         
            -
                    if  
     | 
| 
      
 390 
     | 
    
         
            +
                    if (
         
     | 
| 
      
 391 
     | 
    
         
            +
                        self._flow_cls is None
         
     | 
| 
      
 392 
     | 
    
         
            +
                        or self._config_input is None
         
     | 
| 
      
 393 
     | 
    
         
            +
                        or self._parent is not None
         
     | 
| 
      
 394 
     | 
    
         
            +
                    ):
         
     | 
| 
       327 
395 
     | 
    
         
             
                        raise RuntimeError(
         
     | 
| 
       328 
396 
     | 
    
         
             
                            "Computing flow-level parameters for a non start API. "
         
     | 
| 
       329 
397 
     | 
    
         
             
                            "Please report to the Metaflow team."
         
     | 
| 
       330 
398 
     | 
    
         
             
                        )
         
     | 
| 
       331 
     | 
    
         
            -
             
     | 
| 
       332 
     | 
    
         
            -
                    # would involve processing the options at least partially. We will do this
         
     | 
| 
       333 
     | 
    
         
            -
                    # before GA but for now making it work for regular parameters
         
     | 
| 
      
 399 
     | 
    
         
            +
             
     | 
| 
       334 
400 
     | 
    
         
             
                    if self._cached_computed_parameters is not None:
         
     | 
| 
       335 
401 
     | 
    
         
             
                        return self._cached_computed_parameters
         
     | 
| 
       336 
402 
     | 
    
         
             
                    self._cached_computed_parameters = []
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
                    config_options = None
         
     | 
| 
      
 405 
     | 
    
         
            +
                    if CLICK_API_PROCESS_CONFIG:
         
     | 
| 
      
 406 
     | 
    
         
            +
                        with flow_context(self._flow_cls) as _:
         
     | 
| 
      
 407 
     | 
    
         
            +
                            # We are going to resolve the configs first and then get the parameters.
         
     | 
| 
      
 408 
     | 
    
         
            +
                            # Note that configs may update/add parameters so the order is important
         
     | 
| 
      
 409 
     | 
    
         
            +
                            # Since part of the processing of configs happens by click, we need to
         
     | 
| 
      
 410 
     | 
    
         
            +
                            # "fake" it.
         
     | 
| 
      
 411 
     | 
    
         
            +
             
     | 
| 
      
 412 
     | 
    
         
            +
                            # Extract any config options as well as datastore and quiet options
         
     | 
| 
      
 413 
     | 
    
         
            +
                            method_params = self._chain[0][self._API_NAME]
         
     | 
| 
      
 414 
     | 
    
         
            +
                            opts = method_params["options"]
         
     | 
| 
      
 415 
     | 
    
         
            +
                            defaults = method_params["defaults"]
         
     | 
| 
      
 416 
     | 
    
         
            +
             
     | 
| 
      
 417 
     | 
    
         
            +
                            ds = opts.get("datastore", defaults["datastore"])
         
     | 
| 
      
 418 
     | 
    
         
            +
                            quiet = opts.get("quiet", defaults["quiet"])
         
     | 
| 
      
 419 
     | 
    
         
            +
                            is_default = False
         
     | 
| 
      
 420 
     | 
    
         
            +
                            config_file = opts.get("config-file")
         
     | 
| 
      
 421 
     | 
    
         
            +
                            if config_file is None:
         
     | 
| 
      
 422 
     | 
    
         
            +
                                is_default = True
         
     | 
| 
      
 423 
     | 
    
         
            +
                                config_file = defaults.get("config_file")
         
     | 
| 
      
 424 
     | 
    
         
            +
             
     | 
| 
      
 425 
     | 
    
         
            +
                            if config_file:
         
     | 
| 
      
 426 
     | 
    
         
            +
                                config_file = map(
         
     | 
| 
      
 427 
     | 
    
         
            +
                                    lambda x: (x[0], ConvertPath.convert_value(x[1], is_default)),
         
     | 
| 
      
 428 
     | 
    
         
            +
                                    config_file,
         
     | 
| 
      
 429 
     | 
    
         
            +
                                )
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
                            is_default = False
         
     | 
| 
      
 432 
     | 
    
         
            +
                            config_value = opts.get("config-value")
         
     | 
| 
      
 433 
     | 
    
         
            +
                            if config_value is None:
         
     | 
| 
      
 434 
     | 
    
         
            +
                                is_default = True
         
     | 
| 
      
 435 
     | 
    
         
            +
                                config_value = defaults.get("config_value")
         
     | 
| 
      
 436 
     | 
    
         
            +
             
     | 
| 
      
 437 
     | 
    
         
            +
                            if config_value:
         
     | 
| 
      
 438 
     | 
    
         
            +
                                config_value = map(
         
     | 
| 
      
 439 
     | 
    
         
            +
                                    lambda x: (
         
     | 
| 
      
 440 
     | 
    
         
            +
                                        x[0],
         
     | 
| 
      
 441 
     | 
    
         
            +
                                        ConvertDictOrStr.convert_value(x[1], is_default),
         
     | 
| 
      
 442 
     | 
    
         
            +
                                    ),
         
     | 
| 
      
 443 
     | 
    
         
            +
                                    config_value,
         
     | 
| 
      
 444 
     | 
    
         
            +
                                )
         
     | 
| 
      
 445 
     | 
    
         
            +
             
     | 
| 
      
 446 
     | 
    
         
            +
                            if (config_file is None) ^ (config_value is None):
         
     | 
| 
      
 447 
     | 
    
         
            +
                                # If we have one, we should have the other
         
     | 
| 
      
 448 
     | 
    
         
            +
                                raise MetaflowException(
         
     | 
| 
      
 449 
     | 
    
         
            +
                                    "Options were not properly set -- this is an internal error."
         
     | 
| 
      
 450 
     | 
    
         
            +
                                )
         
     | 
| 
      
 451 
     | 
    
         
            +
             
     | 
| 
      
 452 
     | 
    
         
            +
                            if config_file:
         
     | 
| 
      
 453 
     | 
    
         
            +
                                # Process both configurations; the second one will return all the merged
         
     | 
| 
      
 454 
     | 
    
         
            +
                                # configuration options properly processed.
         
     | 
| 
      
 455 
     | 
    
         
            +
                                self._config_input.process_configs(
         
     | 
| 
      
 456 
     | 
    
         
            +
                                    self._flow_cls.__name__, "config_file", config_file, quiet, ds
         
     | 
| 
      
 457 
     | 
    
         
            +
                                )
         
     | 
| 
      
 458 
     | 
    
         
            +
                                config_options = self._config_input.process_configs(
         
     | 
| 
      
 459 
     | 
    
         
            +
                                    self._flow_cls.__name__, "config_value", config_value, quiet, ds
         
     | 
| 
      
 460 
     | 
    
         
            +
                                )
         
     | 
| 
      
 461 
     | 
    
         
            +
             
     | 
| 
      
 462 
     | 
    
         
            +
                    # At this point, we are like in start() in cli.py -- we obtained the
         
     | 
| 
      
 463 
     | 
    
         
            +
                    # properly processed config_options which we can now use to process
         
     | 
| 
      
 464 
     | 
    
         
            +
                    # the config decorators (including CustomStep/FlowDecorators)
         
     | 
| 
      
 465 
     | 
    
         
            +
                    # Note that if CLICK_API_PROCESS_CONFIG is False, we still do this because
         
     | 
| 
      
 466 
     | 
    
         
            +
                    # it will init all parameters (config_options will be None)
         
     | 
| 
      
 467 
     | 
    
         
            +
                    # We ignore any errors if we don't check the configs in the click API.
         
     | 
| 
      
 468 
     | 
    
         
            +
                    new_cls = self._flow_cls._process_config_decorators(
         
     | 
| 
      
 469 
     | 
    
         
            +
                        config_options, ignore_errors=not CLICK_API_PROCESS_CONFIG
         
     | 
| 
      
 470 
     | 
    
         
            +
                    )
         
     | 
| 
      
 471 
     | 
    
         
            +
                    if new_cls:
         
     | 
| 
      
 472 
     | 
    
         
            +
                        self._flow_cls = new_cls
         
     | 
| 
      
 473 
     | 
    
         
            +
             
     | 
| 
       337 
474 
     | 
    
         
             
                    for _, param in self._flow_cls._get_parameters():
         
     | 
| 
       338 
475 
     | 
    
         
             
                        if param.IS_CONFIG_PARAMETER:
         
     | 
| 
       339 
476 
     | 
    
         
             
                            continue
         
     | 
| 
       340 
     | 
    
         
            -
                        param.init()
         
     | 
| 
       341 
477 
     | 
    
         
             
                        self._cached_computed_parameters.append(param)
         
     | 
| 
       342 
478 
     | 
    
         
             
                    return self._cached_computed_parameters
         
     | 
| 
       343 
479 
     | 
    
         | 
    
        metaflow/runner/deployer.py
    CHANGED
    
    | 
         @@ -64,7 +64,7 @@ class Deployer(metaclass=DeployerMeta): 
     | 
|
| 
       64 
64 
     | 
    
         
             
                    The directory to run the subprocess in; if not specified, the current
         
     | 
| 
       65 
65 
     | 
    
         
             
                    directory is used.
         
     | 
| 
       66 
66 
     | 
    
         
             
                file_read_timeout : int, default 3600
         
     | 
| 
       67 
     | 
    
         
            -
                    The timeout until which we try to read the deployer attribute file.
         
     | 
| 
      
 67 
     | 
    
         
            +
                    The timeout until which we try to read the deployer attribute file (in seconds).
         
     | 
| 
       68 
68 
     | 
    
         
             
                **kwargs : Any
         
     | 
| 
       69 
69 
     | 
    
         
             
                    Additional arguments that you would pass to `python myflow.py` before
         
     | 
| 
       70 
70 
     | 
    
         
             
                    the deployment command.
         
     | 
    
        metaflow/runner/deployer_impl.py
    CHANGED
    
    | 
         @@ -37,7 +37,7 @@ class DeployerImpl(object): 
     | 
|
| 
       37 
37 
     | 
    
         
             
                    The directory to run the subprocess in; if not specified, the current
         
     | 
| 
       38 
38 
     | 
    
         
             
                    directory is used.
         
     | 
| 
       39 
39 
     | 
    
         
             
                file_read_timeout : int, default 3600
         
     | 
| 
       40 
     | 
    
         
            -
                    The timeout until which we try to read the deployer attribute file.
         
     | 
| 
      
 40 
     | 
    
         
            +
                    The timeout until which we try to read the deployer attribute file (in seconds).
         
     | 
| 
       41 
41 
     | 
    
         
             
                **kwargs : Any
         
     | 
| 
       42 
42 
     | 
    
         
             
                    Additional arguments that you would pass to `python myflow.py` before
         
     | 
| 
       43 
43 
     | 
    
         
             
                    the deployment command.
         
     | 
| 
         @@ -61,8 +61,13 @@ class DeployerImpl(object): 
     | 
|
| 
       61 
61 
     | 
    
         
             
                            "of DeployerImpl."
         
     | 
| 
       62 
62 
     | 
    
         
             
                        )
         
     | 
| 
       63 
63 
     | 
    
         | 
| 
      
 64 
     | 
    
         
            +
                    from metaflow.parameters import flow_context
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
       64 
66 
     | 
    
         
             
                    if "metaflow.cli" in sys.modules:
         
     | 
| 
       65 
     | 
    
         
            -
                         
     | 
| 
      
 67 
     | 
    
         
            +
                        # Reload the CLI with an "empty" flow -- this will remove any configuration
         
     | 
| 
      
 68 
     | 
    
         
            +
                        # options. They are re-added in from_cli (called below).
         
     | 
| 
      
 69 
     | 
    
         
            +
                        with flow_context(None) as _:
         
     | 
| 
      
 70 
     | 
    
         
            +
                            importlib.reload(sys.modules["metaflow.cli"])
         
     | 
| 
       66 
71 
     | 
    
         
             
                    from metaflow.cli import start
         
     | 
| 
       67 
72 
     | 
    
         
             
                    from metaflow.runner.click_api import MetaflowAPI
         
     | 
| 
       68 
73 
     | 
    
         | 
| 
         @@ -144,7 +149,7 @@ class DeployerImpl(object): 
     | 
|
| 
       144 
149 
     | 
    
         
             
                        # Additional info is used to pass additional deployer specific information.
         
     | 
| 
       145 
150 
     | 
    
         
             
                        # It is used in non-OSS deployers (extensions).
         
     | 
| 
       146 
151 
     | 
    
         
             
                        self.additional_info = content.get("additional_info", {})
         
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
      
 152 
     | 
    
         
            +
                        command_obj.sync_wait()
         
     | 
| 
       148 
153 
     | 
    
         
             
                        if command_obj.process.returncode == 0:
         
     | 
| 
       149 
154 
     | 
    
         
             
                            return create_class(deployer=self)
         
     | 
| 
       150 
155 
     | 
    
         | 
| 
         @@ -221,7 +221,7 @@ class Runner(object): 
     | 
|
| 
       221 
221 
     | 
    
         
             
                    The directory to run the subprocess in; if not specified, the current
         
     | 
| 
       222 
222 
     | 
    
         
             
                    directory is used.
         
     | 
| 
       223 
223 
     | 
    
         
             
                file_read_timeout : int, default 3600
         
     | 
| 
       224 
     | 
    
         
            -
                    The timeout until which we try to read the runner attribute file.
         
     | 
| 
      
 224 
     | 
    
         
            +
                    The timeout until which we try to read the runner attribute file (in seconds).
         
     | 
| 
       225 
225 
     | 
    
         
             
                **kwargs : Any
         
     | 
| 
       226 
226 
     | 
    
         
             
                    Additional arguments that you would pass to `python myflow.py` before
         
     | 
| 
       227 
227 
     | 
    
         
             
                    the `run` command.
         
     | 
| 
         @@ -245,8 +245,13 @@ class Runner(object): 
     | 
|
| 
       245 
245 
     | 
    
         
             
                    # This ability is made possible by the statement:
         
     | 
| 
       246 
246 
     | 
    
         
             
                    # 'from .metaflow_runner import Runner' in '__init__.py'
         
     | 
| 
       247 
247 
     | 
    
         | 
| 
      
 248 
     | 
    
         
            +
                    from metaflow.parameters import flow_context
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
       248 
250 
     | 
    
         
             
                    if "metaflow.cli" in sys.modules:
         
     | 
| 
       249 
     | 
    
         
            -
                         
     | 
| 
      
 251 
     | 
    
         
            +
                        # Reload the CLI with an "empty" flow -- this will remove any configuration
         
     | 
| 
      
 252 
     | 
    
         
            +
                        # options. They are re-added in from_cli (called below).
         
     | 
| 
      
 253 
     | 
    
         
            +
                        with flow_context(None) as _:
         
     | 
| 
      
 254 
     | 
    
         
            +
                            importlib.reload(sys.modules["metaflow.cli"])
         
     | 
| 
       250 
255 
     | 
    
         
             
                    from metaflow.cli import start
         
     | 
| 
       251 
256 
     | 
    
         
             
                    from metaflow.runner.click_api import MetaflowAPI
         
     | 
| 
       252 
257 
     | 
    
         | 
| 
         @@ -272,6 +277,9 @@ class Runner(object): 
     | 
|
| 
       272 
277 
     | 
    
         | 
| 
       273 
278 
     | 
    
         
             
                def __get_executing_run(self, attribute_file_fd, command_obj):
         
     | 
| 
       274 
279 
     | 
    
         
             
                    content = handle_timeout(attribute_file_fd, command_obj, self.file_read_timeout)
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                    command_obj.sync_wait()
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
       275 
283 
     | 
    
         
             
                    content = json.loads(content)
         
     | 
| 
       276 
284 
     | 
    
         
             
                    pathspec = "%s/%s" % (content.get("flow_name"), content.get("run_id"))
         
     | 
| 
       277 
285 
     | 
    
         | 
    
        metaflow/runner/nbdeploy.py
    CHANGED
    
    | 
         @@ -46,6 +46,8 @@ class NBDeployer(object): 
     | 
|
| 
       46 
46 
     | 
    
         
             
                base_dir : str, optional, default None
         
     | 
| 
       47 
47 
     | 
    
         
             
                    The directory to run the subprocess in; if not specified, the current
         
     | 
| 
       48 
48 
     | 
    
         
             
                    working directory is used.
         
     | 
| 
      
 49 
     | 
    
         
            +
                file_read_timeout : int, default 3600
         
     | 
| 
      
 50 
     | 
    
         
            +
                    The timeout until which we try to read the deployer attribute file (in seconds).
         
     | 
| 
       49 
51 
     | 
    
         
             
                **kwargs : Any
         
     | 
| 
       50 
52 
     | 
    
         
             
                    Additional arguments that you would pass to `python myflow.py` i.e. options
         
     | 
| 
       51 
53 
     | 
    
         
             
                    listed in `python myflow.py --help`
         
     | 
    
        metaflow/runner/nbrun.py
    CHANGED
    
    | 
         @@ -44,7 +44,7 @@ class NBRunner(object): 
     | 
|
| 
       44 
44 
     | 
    
         
             
                    The directory to run the subprocess in; if not specified, the current
         
     | 
| 
       45 
45 
     | 
    
         
             
                    working directory is used.
         
     | 
| 
       46 
46 
     | 
    
         
             
                file_read_timeout : int, default 3600
         
     | 
| 
       47 
     | 
    
         
            -
                    The timeout until which we try to read the runner attribute file.
         
     | 
| 
      
 47 
     | 
    
         
            +
                    The timeout until which we try to read the runner attribute file (in seconds).
         
     | 
| 
       48 
48 
     | 
    
         
             
                **kwargs : Any
         
     | 
| 
       49 
49 
     | 
    
         
             
                    Additional arguments that you would pass to `python myflow.py` before
         
     | 
| 
       50 
50 
     | 
    
         
             
                    the `run` command.
         
     | 
| 
         @@ -120,6 +120,9 @@ class SubprocessManager(object): 
     | 
|
| 
       120 
120 
     | 
    
         
             
                    """
         
     | 
| 
       121 
121 
     | 
    
         
             
                    Run a command synchronously and return its process ID.
         
     | 
| 
       122 
122 
     | 
    
         | 
| 
      
 123 
     | 
    
         
            +
                    Note: in no case does this wait for the process to *finish*. Use sync_wait()
         
     | 
| 
      
 124 
     | 
    
         
            +
                    to wait for the command to finish.
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
       123 
126 
     | 
    
         
             
                    Parameters
         
     | 
| 
       124 
127 
     | 
    
         
             
                    ----------
         
     | 
| 
       125 
128 
     | 
    
         
             
                    command : List[str]
         
     | 
| 
         @@ -145,7 +148,6 @@ class SubprocessManager(object): 
     | 
|
| 
       145 
148 
     | 
    
         
             
                    command_obj = CommandManager(command, env, cwd)
         
     | 
| 
       146 
149 
     | 
    
         
             
                    pid = command_obj.run(show_output=show_output)
         
     | 
| 
       147 
150 
     | 
    
         
             
                    self.commands[pid] = command_obj
         
     | 
| 
       148 
     | 
    
         
            -
                    command_obj.sync_wait()
         
     | 
| 
       149 
151 
     | 
    
         
             
                    return pid
         
     | 
| 
       150 
152 
     | 
    
         | 
| 
       151 
153 
     | 
    
         
             
                async def async_run_command(
         
     | 
    
        metaflow/runner/utils.py
    CHANGED
    
    | 
         @@ -91,7 +91,7 @@ def read_from_fifo_when_ready( 
     | 
|
| 
       91 
91 
     | 
    
         
             
                encoding : str, optional
         
     | 
| 
       92 
92 
     | 
    
         
             
                    Encoding to use while reading the file, by default "utf-8".
         
     | 
| 
       93 
93 
     | 
    
         
             
                timeout : int, optional
         
     | 
| 
       94 
     | 
    
         
            -
                    Timeout for reading the file in  
     | 
| 
      
 94 
     | 
    
         
            +
                    Timeout for reading the file in seconds, by default 3600.
         
     | 
| 
       95 
95 
     | 
    
         | 
| 
       96 
96 
     | 
    
         
             
                Returns
         
     | 
| 
       97 
97 
     | 
    
         
             
                -------
         
     | 
| 
         @@ -107,30 +107,52 @@ def read_from_fifo_when_ready( 
     | 
|
| 
       107 
107 
     | 
    
         
             
                    content to the FIFO.
         
     | 
| 
       108 
108 
     | 
    
         
             
                """
         
     | 
| 
       109 
109 
     | 
    
         
             
                content = bytearray()
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
110 
     | 
    
         
             
                poll = select.poll()
         
     | 
| 
       112 
111 
     | 
    
         
             
                poll.register(fifo_fd, select.POLLIN)
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
      
 112 
     | 
    
         
            +
                max_timeout = 3  # Wait for 10 * 3 = 30 ms after last write
         
     | 
| 
       114 
113 
     | 
    
         
             
                while True:
         
     | 
| 
       115 
     | 
    
         
            -
                     
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
      
 114 
     | 
    
         
            +
                    if check_process_exited(command_obj) and command_obj.process.returncode != 0:
         
     | 
| 
      
 115 
     | 
    
         
            +
                        raise CalledProcessError(
         
     | 
| 
      
 116 
     | 
    
         
            +
                            command_obj.process.returncode, command_obj.command
         
     | 
| 
      
 117 
     | 
    
         
            +
                        )
         
     | 
| 
       118 
118 
     | 
    
         | 
| 
       119 
     | 
    
         
            -
                    if timeout  
     | 
| 
      
 119 
     | 
    
         
            +
                    if timeout < 0:
         
     | 
| 
       120 
120 
     | 
    
         
             
                        raise TimeoutError("Timeout while waiting for the file content")
         
     | 
| 
       121 
121 
     | 
    
         | 
| 
      
 122 
     | 
    
         
            +
                    poll_begin = time.time()
         
     | 
| 
      
 123 
     | 
    
         
            +
                    # We poll for a very short time to be also able to check if the file was closed
         
     | 
| 
      
 124 
     | 
    
         
            +
                    # If the file is closed, we assume that we only have one writer so if we have
         
     | 
| 
      
 125 
     | 
    
         
            +
                    # data, we break out. This is to work around issues in macos
         
     | 
| 
      
 126 
     | 
    
         
            +
                    events = poll.poll(min(10, timeout * 1000))
         
     | 
| 
      
 127 
     | 
    
         
            +
                    timeout -= time.time() - poll_begin
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
       122 
129 
     | 
    
         
             
                    try:
         
     | 
| 
       123 
     | 
    
         
            -
                        data = os.read(fifo_fd,  
     | 
| 
       124 
     | 
    
         
            -
                         
     | 
| 
      
 130 
     | 
    
         
            +
                        data = os.read(fifo_fd, 8192)
         
     | 
| 
      
 131 
     | 
    
         
            +
                        if data:
         
     | 
| 
       125 
132 
     | 
    
         
             
                            content += data
         
     | 
| 
       126 
     | 
    
         
            -
             
     | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
      
 133 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 134 
     | 
    
         
            +
                            if len(events):
         
     | 
| 
      
 135 
     | 
    
         
            +
                                # We read an EOF -- consider the file done
         
     | 
| 
      
 136 
     | 
    
         
            +
                                break
         
     | 
| 
      
 137 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 138 
     | 
    
         
            +
                                # We had no events (just a timeout) and the read didn't return
         
     | 
| 
      
 139 
     | 
    
         
            +
                                # an exception so the file is still open; we continue waiting for data
         
     | 
| 
      
 140 
     | 
    
         
            +
                                # Unfortunately, on MacOS, it seems that even *after* the file is
         
     | 
| 
      
 141 
     | 
    
         
            +
                                # closed on the other end, we still don't get a BlockingIOError so
         
     | 
| 
      
 142 
     | 
    
         
            +
                                # we hack our way and timeout if there is no write in 30ms which is
         
     | 
| 
      
 143 
     | 
    
         
            +
                                # a relative eternity for file writes.
         
     | 
| 
      
 144 
     | 
    
         
            +
                                if content:
         
     | 
| 
      
 145 
     | 
    
         
            +
                                    if max_timeout <= 0:
         
     | 
| 
      
 146 
     | 
    
         
            +
                                        break
         
     | 
| 
      
 147 
     | 
    
         
            +
                                    max_timeout -= 1
         
     | 
| 
      
 148 
     | 
    
         
            +
                                    continue
         
     | 
| 
       131 
149 
     | 
    
         
             
                    except BlockingIOError:
         
     | 
| 
       132 
     | 
    
         
            -
                         
     | 
| 
       133 
     | 
    
         
            -
                         
     | 
| 
      
 150 
     | 
    
         
            +
                        has_blocking_error = True
         
     | 
| 
      
 151 
     | 
    
         
            +
                        if content:
         
     | 
| 
      
 152 
     | 
    
         
            +
                            # The file was closed
         
     | 
| 
      
 153 
     | 
    
         
            +
                            break
         
     | 
| 
      
 154 
     | 
    
         
            +
                        # else, if we have no content, we continue waiting for the file to be open
         
     | 
| 
      
 155 
     | 
    
         
            +
                        # and written to.
         
     | 
| 
       134 
156 
     | 
    
         | 
| 
       135 
157 
     | 
    
         
             
                if not content and check_process_exited(command_obj):
         
     | 
| 
       136 
158 
     | 
    
         
             
                    raise CalledProcessError(command_obj.process.returncode, command_obj.command)
         
     | 
| 
         @@ -156,7 +178,7 @@ async def async_read_from_fifo_when_ready( 
     | 
|
| 
       156 
178 
     | 
    
         
             
                encoding : str, optional
         
     | 
| 
       157 
179 
     | 
    
         
             
                    Encoding to use while reading the file, by default "utf-8".
         
     | 
| 
       158 
180 
     | 
    
         
             
                timeout : int, optional
         
     | 
| 
       159 
     | 
    
         
            -
                    Timeout for reading the file in  
     | 
| 
      
 181 
     | 
    
         
            +
                    Timeout for reading the file in seconds, by default 3600.
         
     | 
| 
       160 
182 
     | 
    
         | 
| 
       161 
183 
     | 
    
         
             
                Returns
         
     | 
| 
       162 
184 
     | 
    
         
             
                -------
         
     | 
| 
         @@ -206,7 +228,7 @@ def handle_timeout( 
     | 
|
| 
       206 
228 
     | 
    
         
             
                command_obj : CommandManager
         
     | 
| 
       207 
229 
     | 
    
         
             
                    Command manager object that encapsulates the running command details.
         
     | 
| 
       208 
230 
     | 
    
         
             
                file_read_timeout : int
         
     | 
| 
       209 
     | 
    
         
            -
                    Timeout for reading the file 
     | 
| 
      
 231 
     | 
    
         
            +
                    Timeout for reading the file, in seconds
         
     | 
| 
       210 
232 
     | 
    
         | 
| 
       211 
233 
     | 
    
         
             
                Returns
         
     | 
| 
       212 
234 
     | 
    
         
             
                -------
         
     | 
| 
         @@ -243,7 +265,7 @@ async def async_handle_timeout( 
     | 
|
| 
       243 
265 
     | 
    
         
             
                command_obj : CommandManager
         
     | 
| 
       244 
266 
     | 
    
         
             
                    Command manager object that encapsulates the running command details.
         
     | 
| 
       245 
267 
     | 
    
         
             
                file_read_timeout : int
         
     | 
| 
       246 
     | 
    
         
            -
                    Timeout for reading the file 
     | 
| 
      
 268 
     | 
    
         
            +
                    Timeout for reading the file, in seconds
         
     | 
| 
       247 
269 
     | 
    
         | 
| 
       248 
270 
     | 
    
         
             
                Returns
         
     | 
| 
       249 
271 
     | 
    
         
             
                -------
         
     |