runnable 0.12.3__py3-none-any.whl → 0.14.0__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.
- runnable/__init__.py +0 -11
 - runnable/catalog.py +27 -5
 - runnable/cli.py +122 -26
 - runnable/datastore.py +71 -35
 - runnable/defaults.py +0 -1
 - runnable/entrypoints.py +107 -32
 - runnable/exceptions.py +6 -2
 - runnable/executor.py +28 -9
 - runnable/graph.py +37 -12
 - runnable/integration.py +7 -2
 - runnable/nodes.py +15 -17
 - runnable/parameters.py +27 -8
 - runnable/pickler.py +1 -1
 - runnable/sdk.py +101 -33
 - runnable/secrets.py +3 -1
 - runnable/tasks.py +246 -34
 - runnable/utils.py +41 -13
 - {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info}/METADATA +25 -31
 - runnable-0.14.0.dist-info/RECORD +24 -0
 - {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info}/WHEEL +1 -1
 - runnable-0.14.0.dist-info/entry_points.txt +40 -0
 - runnable/extensions/__init__.py +0 -0
 - runnable/extensions/catalog/__init__.py +0 -21
 - runnable/extensions/catalog/file_system/__init__.py +0 -0
 - runnable/extensions/catalog/file_system/implementation.py +0 -234
 - runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
 - runnable/extensions/catalog/k8s_pvc/implementation.py +0 -16
 - runnable/extensions/catalog/k8s_pvc/integration.py +0 -59
 - runnable/extensions/executor/__init__.py +0 -649
 - runnable/extensions/executor/argo/__init__.py +0 -0
 - runnable/extensions/executor/argo/implementation.py +0 -1194
 - runnable/extensions/executor/argo/specification.yaml +0 -51
 - runnable/extensions/executor/k8s_job/__init__.py +0 -0
 - runnable/extensions/executor/k8s_job/implementation_FF.py +0 -259
 - runnable/extensions/executor/k8s_job/integration_FF.py +0 -69
 - runnable/extensions/executor/local/__init__.py +0 -0
 - runnable/extensions/executor/local/implementation.py +0 -71
 - runnable/extensions/executor/local_container/__init__.py +0 -0
 - runnable/extensions/executor/local_container/implementation.py +0 -446
 - runnable/extensions/executor/mocked/__init__.py +0 -0
 - runnable/extensions/executor/mocked/implementation.py +0 -154
 - runnable/extensions/executor/retry/__init__.py +0 -0
 - runnable/extensions/executor/retry/implementation.py +0 -168
 - runnable/extensions/nodes.py +0 -855
 - runnable/extensions/run_log_store/__init__.py +0 -0
 - runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
 - runnable/extensions/run_log_store/chunked_file_system/implementation.py +0 -111
 - runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
 - runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +0 -21
 - runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +0 -61
 - runnable/extensions/run_log_store/db/implementation_FF.py +0 -157
 - runnable/extensions/run_log_store/db/integration_FF.py +0 -0
 - runnable/extensions/run_log_store/file_system/__init__.py +0 -0
 - runnable/extensions/run_log_store/file_system/implementation.py +0 -140
 - runnable/extensions/run_log_store/generic_chunked.py +0 -557
 - runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
 - runnable/extensions/run_log_store/k8s_pvc/implementation.py +0 -21
 - runnable/extensions/run_log_store/k8s_pvc/integration.py +0 -56
 - runnable/extensions/secrets/__init__.py +0 -0
 - runnable/extensions/secrets/dotenv/__init__.py +0 -0
 - runnable/extensions/secrets/dotenv/implementation.py +0 -100
 - runnable-0.12.3.dist-info/RECORD +0 -64
 - runnable-0.12.3.dist-info/entry_points.txt +0 -41
 - {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info/licenses}/LICENSE +0 -0
 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         @@ -1,111 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import json
         
     | 
| 
       2 
     | 
    
         
            -
            import logging
         
     | 
| 
       3 
     | 
    
         
            -
            from pathlib import Path
         
     | 
| 
       4 
     | 
    
         
            -
            from string import Template
         
     | 
| 
       5 
     | 
    
         
            -
            from typing import Any, Dict, Optional, Sequence, Union
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
            from runnable import defaults, utils
         
     | 
| 
       8 
     | 
    
         
            -
            from runnable.extensions.run_log_store.generic_chunked import ChunkedRunLogStore
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
            logger = logging.getLogger(defaults.LOGGER_NAME)
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
            T = Union[str, Path]
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
            class ChunkedFileSystemRunLogStore(ChunkedRunLogStore):
         
     | 
| 
       16 
     | 
    
         
            -
                """
         
     | 
| 
       17 
     | 
    
         
            -
                File system run log store but chunks the run log into thread safe chunks.
         
     | 
| 
       18 
     | 
    
         
            -
                This enables executions to be parallel.
         
     | 
| 
       19 
     | 
    
         
            -
                """
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                service_name: str = "chunked-fs"
         
     | 
| 
       22 
     | 
    
         
            -
                log_folder: str = defaults.LOG_LOCATION_FOLDER
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                def get_summary(self) -> Dict[str, Any]:
         
     | 
| 
       25 
     | 
    
         
            -
                    summary = {"Type": self.service_name, "Location": self.log_folder}
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                    return summary
         
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                def get_matches(self, run_id: str, name: str, multiple_allowed: bool = False) -> Optional[Union[Sequence[T], T]]:
         
     | 
| 
       30 
     | 
    
         
            -
                    """
         
     | 
| 
       31 
     | 
    
         
            -
                    Get contents of files matching the pattern name*
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                    Args:
         
     | 
| 
       34 
     | 
    
         
            -
                        run_id (str): The run id
         
     | 
| 
       35 
     | 
    
         
            -
                        name (str): The suffix of the file name to check in the run log store.
         
     | 
| 
       36 
     | 
    
         
            -
                    """
         
     | 
| 
       37 
     | 
    
         
            -
                    log_folder = self.log_folder_with_run_id(run_id=run_id)
         
     | 
| 
       38 
     | 
    
         
            -
                    sub_name = Template(name).safe_substitute({"creation_time": ""})
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                    matches = list(log_folder.glob(f"{sub_name}*"))
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
                    if matches:
         
     | 
| 
       43 
     | 
    
         
            -
                        if not multiple_allowed:
         
     | 
| 
       44 
     | 
    
         
            -
                            if len(matches) > 1:
         
     | 
| 
       45 
     | 
    
         
            -
                                msg = f"Multiple matches found for {name} while multiple is not allowed"
         
     | 
| 
       46 
     | 
    
         
            -
                                raise Exception(msg)
         
     | 
| 
       47 
     | 
    
         
            -
                            return matches[0]
         
     | 
| 
       48 
     | 
    
         
            -
                        return matches
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                    return None
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                def log_folder_with_run_id(self, run_id: str) -> Path:
         
     | 
| 
       53 
     | 
    
         
            -
                    """
         
     | 
| 
       54 
     | 
    
         
            -
                    Utility function to get the log folder for a run id.
         
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
                    Args:
         
     | 
| 
       57 
     | 
    
         
            -
                        run_id (str): The run id
         
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       60 
     | 
    
         
            -
                        Path: The path to the log folder with the run id
         
     | 
| 
       61 
     | 
    
         
            -
                    """
         
     | 
| 
       62 
     | 
    
         
            -
                    return Path(self.log_folder) / run_id
         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
                def safe_suffix_json(self, name: Union[Path, str]) -> str:
         
     | 
| 
       65 
     | 
    
         
            -
                    """
         
     | 
| 
       66 
     | 
    
         
            -
                    Safely attach a suffix to a json file.
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
                    Args:
         
     | 
| 
       69 
     | 
    
         
            -
                        name (Path): The name of the file with or without suffix of json
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       72 
     | 
    
         
            -
                        str : The name of the file with .json
         
     | 
| 
       73 
     | 
    
         
            -
                    """
         
     | 
| 
       74 
     | 
    
         
            -
                    if str(name).endswith("json"):
         
     | 
| 
       75 
     | 
    
         
            -
                        return str(name)
         
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
                    return str(name) + ".json"
         
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
                def _store(self, run_id: str, contents: dict, name: Union[Path, str], insert=False):
         
     | 
| 
       80 
     | 
    
         
            -
                    """
         
     | 
| 
       81 
     | 
    
         
            -
                    Store the contents against the name in the folder.
         
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
                    Args:
         
     | 
| 
       84 
     | 
    
         
            -
                        run_id (str): The run id
         
     | 
| 
       85 
     | 
    
         
            -
                        contents (dict): The dict to store
         
     | 
| 
       86 
     | 
    
         
            -
                        name (str): The name to store as
         
     | 
| 
       87 
     | 
    
         
            -
                    """
         
     | 
| 
       88 
     | 
    
         
            -
                    if insert:
         
     | 
| 
       89 
     | 
    
         
            -
                        name = self.log_folder_with_run_id(run_id=run_id) / name
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
                    utils.safe_make_dir(self.log_folder_with_run_id(run_id=run_id))
         
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
                    with open(self.safe_suffix_json(name), "w") as fw:
         
     | 
| 
       94 
     | 
    
         
            -
                        json.dump(contents, fw, ensure_ascii=True, indent=4)
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
                def _retrieve(self, name: Union[str, Path]) -> dict:
         
     | 
| 
       97 
     | 
    
         
            -
                    """
         
     | 
| 
       98 
     | 
    
         
            -
                    Does the job of retrieving from the folder.
         
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
                    Args:
         
     | 
| 
       101 
     | 
    
         
            -
                        name (str): the name of the file to retrieve
         
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       104 
     | 
    
         
            -
                        dict: The contents
         
     | 
| 
       105 
     | 
    
         
            -
                    """
         
     | 
| 
       106 
     | 
    
         
            -
                    contents: dict = {}
         
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
                    with open(self.safe_suffix_json(name), "r") as fr:
         
     | 
| 
       109 
     | 
    
         
            -
                        contents = json.load(fr)
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
                    return contents
         
     | 
| 
         
            File without changes
         
     | 
| 
         @@ -1,21 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import logging
         
     | 
| 
       2 
     | 
    
         
            -
            from pathlib import Path
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            from runnable import defaults
         
     | 
| 
       5 
     | 
    
         
            -
            from runnable.extensions.run_log_store.chunked_file_system.implementation import ChunkedFileSystemRunLogStore
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
            logger = logging.getLogger(defaults.NAME)
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
            class ChunkedK8PersistentVolumeRunLogstore(ChunkedFileSystemRunLogStore):
         
     | 
| 
       11 
     | 
    
         
            -
                """
         
     | 
| 
       12 
     | 
    
         
            -
                Uses the K8s Persistent Volumes to store run logs.
         
     | 
| 
       13 
     | 
    
         
            -
                """
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                service_name: str = "chunked-k8s-pvc"
         
     | 
| 
       16 
     | 
    
         
            -
                persistent_volume_name: str
         
     | 
| 
       17 
     | 
    
         
            -
                mount_path: str
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                @property
         
     | 
| 
       20 
     | 
    
         
            -
                def log_folder_name(self) -> str:
         
     | 
| 
       21 
     | 
    
         
            -
                    return str(Path(self.mount_path) / self.log_folder)
         
     | 
| 
         @@ -1,61 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import logging
         
     | 
| 
       2 
     | 
    
         
            -
            from typing import cast
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            from runnable import defaults
         
     | 
| 
       5 
     | 
    
         
            -
            from runnable.integration import BaseIntegration
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
            logger = logging.getLogger(defaults.NAME)
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
            class LocalCompute(BaseIntegration):
         
     | 
| 
       11 
     | 
    
         
            -
                """
         
     | 
| 
       12 
     | 
    
         
            -
                Integration between local and k8's pvc
         
     | 
| 
       13 
     | 
    
         
            -
                """
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                executor_type = "local"
         
     | 
| 
       16 
     | 
    
         
            -
                service_type = "run_log_store"  # One of secret, catalog, datastore
         
     | 
| 
       17 
     | 
    
         
            -
                service_provider = "chunked-k8s-pvc"  # The actual implementation of the service
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                def validate(self, **kwargs):
         
     | 
| 
       20 
     | 
    
         
            -
                    msg = "We can't use the local compute k8s pvc store integration."
         
     | 
| 
       21 
     | 
    
         
            -
                    raise Exception(msg)
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
            class LocalContainerCompute(BaseIntegration):
         
     | 
| 
       25 
     | 
    
         
            -
                """
         
     | 
| 
       26 
     | 
    
         
            -
                Integration between local-container and k8's pvc
         
     | 
| 
       27 
     | 
    
         
            -
                """
         
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                executor_type = "local-container"
         
     | 
| 
       30 
     | 
    
         
            -
                service_type = "run_log_store"  # One of secret, catalog, datastore
         
     | 
| 
       31 
     | 
    
         
            -
                service_provider = "chunked-k8s-pvc"  # The actual implementation of the service
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                def validate(self, **kwargs):
         
     | 
| 
       34 
     | 
    
         
            -
                    msg = "We can't use the local-container compute k8s pvc store integration."
         
     | 
| 
       35 
     | 
    
         
            -
                    raise Exception(msg)
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
            class ArgoCompute(BaseIntegration):
         
     | 
| 
       39 
     | 
    
         
            -
                """
         
     | 
| 
       40 
     | 
    
         
            -
                Integration between argo and k8's pvc
         
     | 
| 
       41 
     | 
    
         
            -
                """
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                executor_type = "argo"
         
     | 
| 
       44 
     | 
    
         
            -
                service_type = "run_log_store"  # One of secret, catalog, datastore
         
     | 
| 
       45 
     | 
    
         
            -
                service_provider = "chunked-k8s-pvc"  # The actual implementation of the service
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                def configure_for_traversal(self, **kwargs):
         
     | 
| 
       48 
     | 
    
         
            -
                    from runnable.extensions.executor.argo.implementation import ArgoExecutor, UserVolumeMounts
         
     | 
| 
       49 
     | 
    
         
            -
                    from runnable.extensions.run_log_store.chunked_k8s_pvc.implementation import (
         
     | 
| 
       50 
     | 
    
         
            -
                        ChunkedK8PersistentVolumeRunLogstore,
         
     | 
| 
       51 
     | 
    
         
            -
                    )
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
                    self.executor = cast(ArgoExecutor, self.executor)
         
     | 
| 
       54 
     | 
    
         
            -
                    self.service = cast(ChunkedK8PersistentVolumeRunLogstore, self.service)
         
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
                    volume_mount = UserVolumeMounts(
         
     | 
| 
       57 
     | 
    
         
            -
                        name=self.service.persistent_volume_name,
         
     | 
| 
       58 
     | 
    
         
            -
                        mount_path=self.service.mount_path,
         
     | 
| 
       59 
     | 
    
         
            -
                    )
         
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
                    self.executor.persistent_volumes.append(volume_mount)
         
     | 
| 
         @@ -1,157 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import datetime
         
     | 
| 
       2 
     | 
    
         
            -
            import json
         
     | 
| 
       3 
     | 
    
         
            -
            import logging
         
     | 
| 
       4 
     | 
    
         
            -
            from pathlib import Path
         
     | 
| 
       5 
     | 
    
         
            -
            from string import Template
         
     | 
| 
       6 
     | 
    
         
            -
            from typing import Any, Dict, List, Optional, Union, cast
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
            from runnable import defaults, utils
         
     | 
| 
       9 
     | 
    
         
            -
            from runnable.extensions.run_log_store.generic_chunked import ChunkedRunLogStore
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
            logger = logging.getLogger(defaults.LOGGER_NAME)
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
            class DBRunLogStore(ChunkedRunLogStore):
         
     | 
| 
       15 
     | 
    
         
            -
                """
         
     | 
| 
       16 
     | 
    
         
            -
                File system run log store but chunks the run log into thread safe chunks.
         
     | 
| 
       17 
     | 
    
         
            -
                This enables executions to be parallel.
         
     | 
| 
       18 
     | 
    
         
            -
                """
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                service_name: str = "chunked-fs"
         
     | 
| 
       21 
     | 
    
         
            -
                connection_string: str
         
     | 
| 
       22 
     | 
    
         
            -
                db_name: str
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                _DB_LOG: Any = None
         
     | 
| 
       25 
     | 
    
         
            -
                _engine: Any = None
         
     | 
| 
       26 
     | 
    
         
            -
                _session: Any = None
         
     | 
| 
       27 
     | 
    
         
            -
                _connection_string: str = ""
         
     | 
| 
       28 
     | 
    
         
            -
                _base: Any = None
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                def model_post_init(self, _: Any) -> None:
         
     | 
| 
       31 
     | 
    
         
            -
                    run_context = self._context
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                    secrets = cast(Dict[str, str], run_context.secrets_handler.get())
         
     | 
| 
       34 
     | 
    
         
            -
                    connection_string = Template(self.connection_string).safe_substitute(**secrets)
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                    try:
         
     | 
| 
       37 
     | 
    
         
            -
                        import sqlalchemy
         
     | 
| 
       38 
     | 
    
         
            -
                        from sqlalchemy import Column, DateTime, Integer, Sequence, Text
         
     | 
| 
       39 
     | 
    
         
            -
                        from sqlalchemy.orm import declarative_base, sessionmaker
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                        Base = declarative_base()
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                        class DBLog(Base):
         
     | 
| 
       44 
     | 
    
         
            -
                            """
         
     | 
| 
       45 
     | 
    
         
            -
                            Base table for storing run logs in database.
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                            In this model, we fragment the run log into logical units that are concurrent safe.
         
     | 
| 
       48 
     | 
    
         
            -
                            """
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                            __tablename__ = self.db_name
         
     | 
| 
       51 
     | 
    
         
            -
                            pk = Column(Integer, Sequence("id_seq"), primary_key=True)
         
     | 
| 
       52 
     | 
    
         
            -
                            run_id = Column(Text, index=True)
         
     | 
| 
       53 
     | 
    
         
            -
                            attribute_key = Column(Text)  # run_log, step_internal_name, parameter_key etc
         
     | 
| 
       54 
     | 
    
         
            -
                            attribute_type = Column(Text)  # RunLog, Step, Branch, Parameter
         
     | 
| 
       55 
     | 
    
         
            -
                            attribute_value = Column(Text)  # The JSON string
         
     | 
| 
       56 
     | 
    
         
            -
                            created_at = Column(DateTime, default=datetime.datetime.utcnow)
         
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                        self._engine = sqlalchemy.create_engine(connection_string, pool_pre_ping=True)
         
     | 
| 
       59 
     | 
    
         
            -
                        self._session = sessionmaker(bind=self._engine)
         
     | 
| 
       60 
     | 
    
         
            -
                        self._DB_LOG = DBLog
         
     | 
| 
       61 
     | 
    
         
            -
                        self._connection_string = connection_string
         
     | 
| 
       62 
     | 
    
         
            -
                        self._base = Base
         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
                    except ImportError as _e:
         
     | 
| 
       65 
     | 
    
         
            -
                        logger.exception("Unable to import SQLalchemy, is it installed?")
         
     | 
| 
       66 
     | 
    
         
            -
                        msg = "SQLAlchemy is required for this extension. Please install it"
         
     | 
| 
       67 
     | 
    
         
            -
                        raise Exception(msg) from _e
         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
                def create_tables(self):
         
     | 
| 
       70 
     | 
    
         
            -
                    import sqlalchemy
         
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
                    engine = sqlalchemy.create_engine(self._connection_string)
         
     | 
| 
       73 
     | 
    
         
            -
                    self._base.metadata.create_all(engine)
         
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
                def get_matches(self, run_id: str, name: str, multiple_allowed: bool = False) -> Optional[Union[List[Path], Path]]:
         
     | 
| 
       76 
     | 
    
         
            -
                    """
         
     | 
| 
       77 
     | 
    
         
            -
                    Get contents of files matching the pattern name*
         
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
                    Args:
         
     | 
| 
       80 
     | 
    
         
            -
                        run_id (str): The run id
         
     | 
| 
       81 
     | 
    
         
            -
                        name (str): The suffix of the file name to check in the run log store.
         
     | 
| 
       82 
     | 
    
         
            -
                    """
         
     | 
| 
       83 
     | 
    
         
            -
                    log_folder = self.log_folder_with_run_id(run_id=run_id)
         
     | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
       85 
     | 
    
         
            -
                    sub_name = Template(name).safe_substitute({"creation_time": ""})
         
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
                    matches = list(log_folder.glob(f"{sub_name}*"))
         
     | 
| 
       88 
     | 
    
         
            -
                    if matches:
         
     | 
| 
       89 
     | 
    
         
            -
                        if not multiple_allowed:
         
     | 
| 
       90 
     | 
    
         
            -
                            if len(matches) > 1:
         
     | 
| 
       91 
     | 
    
         
            -
                                msg = f"Multiple matches found for {name} while multiple is not allowed"
         
     | 
| 
       92 
     | 
    
         
            -
                                raise Exception(msg)
         
     | 
| 
       93 
     | 
    
         
            -
                            return matches[0]
         
     | 
| 
       94 
     | 
    
         
            -
                        return matches
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
                    return None
         
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
                def log_folder_with_run_id(self, run_id: str) -> Path:
         
     | 
| 
       99 
     | 
    
         
            -
                    """
         
     | 
| 
       100 
     | 
    
         
            -
                    Utility function to get the log folder for a run id.
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                    Args:
         
     | 
| 
       103 
     | 
    
         
            -
                        run_id (str): The run id
         
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       106 
     | 
    
         
            -
                        Path: The path to the log folder with the run id
         
     | 
| 
       107 
     | 
    
         
            -
                    """
         
     | 
| 
       108 
     | 
    
         
            -
                    return Path(self.log_folder) / run_id
         
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
       110 
     | 
    
         
            -
                def safe_suffix_json(self, name: Union[Path, str]) -> str:
         
     | 
| 
       111 
     | 
    
         
            -
                    """
         
     | 
| 
       112 
     | 
    
         
            -
                    Safely attach a suffix to a json file.
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
     | 
    
         
            -
                    Args:
         
     | 
| 
       115 
     | 
    
         
            -
                        name (Path): The name of the file with or without suffix of json
         
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       118 
     | 
    
         
            -
                        str : The name of the file with .json
         
     | 
| 
       119 
     | 
    
         
            -
                    """
         
     | 
| 
       120 
     | 
    
         
            -
                    if str(name).endswith("json"):
         
     | 
| 
       121 
     | 
    
         
            -
                        return str(name)
         
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
       123 
     | 
    
         
            -
                    return str(name) + ".json"
         
     | 
| 
       124 
     | 
    
         
            -
             
     | 
| 
       125 
     | 
    
         
            -
                def _store(self, run_id: str, contents: dict, name: Union[Path, str], insert=False):
         
     | 
| 
       126 
     | 
    
         
            -
                    """
         
     | 
| 
       127 
     | 
    
         
            -
                    Store the contents against the name in the folder.
         
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
                    Args:
         
     | 
| 
       130 
     | 
    
         
            -
                        run_id (str): The run id
         
     | 
| 
       131 
     | 
    
         
            -
                        contents (dict): The dict to store
         
     | 
| 
       132 
     | 
    
         
            -
                        name (str): The name to store as
         
     | 
| 
       133 
     | 
    
         
            -
                    """
         
     | 
| 
       134 
     | 
    
         
            -
                    if insert:
         
     | 
| 
       135 
     | 
    
         
            -
                        name = self.log_folder_with_run_id(run_id=run_id) / name
         
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
       137 
     | 
    
         
            -
                    utils.safe_make_dir(self.log_folder_with_run_id(run_id=run_id))
         
     | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
                    with open(self.safe_suffix_json(name), "w") as fw:
         
     | 
| 
       140 
     | 
    
         
            -
                        json.dump(contents, fw, ensure_ascii=True, indent=4)
         
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
                def _retrieve(self, name: Path) -> dict:
         
     | 
| 
       143 
     | 
    
         
            -
                    """
         
     | 
| 
       144 
     | 
    
         
            -
                    Does the job of retrieving from the folder.
         
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
                    Args:
         
     | 
| 
       147 
     | 
    
         
            -
                        name (str): the name of the file to retrieve
         
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       150 
     | 
    
         
            -
                        dict: The contents
         
     | 
| 
       151 
     | 
    
         
            -
                    """
         
     | 
| 
       152 
     | 
    
         
            -
                    contents: dict = {}
         
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
                    with open(self.safe_suffix_json(name), "r") as fr:
         
     | 
| 
       155 
     | 
    
         
            -
                        contents = json.load(fr)
         
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
                    return contents
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         @@ -1,140 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import json
         
     | 
| 
       2 
     | 
    
         
            -
            import logging
         
     | 
| 
       3 
     | 
    
         
            -
            from pathlib import Path
         
     | 
| 
       4 
     | 
    
         
            -
            from typing import Any, Dict
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
            from runnable import defaults, exceptions, utils
         
     | 
| 
       7 
     | 
    
         
            -
            from runnable.datastore import BaseRunLogStore, RunLog
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
            logger = logging.getLogger(defaults.LOGGER_NAME)
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
            class FileSystemRunLogstore(BaseRunLogStore):
         
     | 
| 
       13 
     | 
    
         
            -
                """
         
     | 
| 
       14 
     | 
    
         
            -
                In this type of Run Log store, we use a file system to store the JSON run log.
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                Every single run is stored as a different file which makes it compatible across other store types.
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                When to use:
         
     | 
| 
       19 
     | 
    
         
            -
                    When locally testing a pipeline and have the need to compare across runs.
         
     | 
| 
       20 
     | 
    
         
            -
                    Its fully featured and perfectly fine if your local environment is where you would do everything.
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                Do not use:
         
     | 
| 
       23 
     | 
    
         
            -
                    If you need parallelization on local, this run log would not support it.
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                Example config:
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                run_log:
         
     | 
| 
       28 
     | 
    
         
            -
                  type: file-system
         
     | 
| 
       29 
     | 
    
         
            -
                  config:
         
     | 
| 
       30 
     | 
    
         
            -
                    log_folder: The folder to out the logs. Defaults to .run_log_store
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                """
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                service_name: str = "file-system"
         
     | 
| 
       35 
     | 
    
         
            -
                log_folder: str = defaults.LOG_LOCATION_FOLDER
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                @property
         
     | 
| 
       38 
     | 
    
         
            -
                def log_folder_name(self):
         
     | 
| 
       39 
     | 
    
         
            -
                    return self.log_folder
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                def get_summary(self) -> Dict[str, Any]:
         
     | 
| 
       42 
     | 
    
         
            -
                    summary = {"Type": self.service_name, "Location": self.log_folder}
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                    return summary
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                def write_to_folder(self, run_log: RunLog):
         
     | 
| 
       47 
     | 
    
         
            -
                    """
         
     | 
| 
       48 
     | 
    
         
            -
                    Write the run log to the folder
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                    Args:
         
     | 
| 
       51 
     | 
    
         
            -
                        run_log (RunLog): The run log to be added to the database
         
     | 
| 
       52 
     | 
    
         
            -
                    """
         
     | 
| 
       53 
     | 
    
         
            -
                    write_to = self.log_folder_name
         
     | 
| 
       54 
     | 
    
         
            -
                    utils.safe_make_dir(write_to)
         
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
                    write_to_path = Path(write_to)
         
     | 
| 
       57 
     | 
    
         
            -
                    run_id = run_log.run_id
         
     | 
| 
       58 
     | 
    
         
            -
                    json_file_path = write_to_path / f"{run_id}.json"
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                    with json_file_path.open("w") as fw:
         
     | 
| 
       61 
     | 
    
         
            -
                        json.dump(run_log.model_dump(), fw, ensure_ascii=True, indent=4)  # pylint: disable=no-member
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
                def get_from_folder(self, run_id: str) -> RunLog:
         
     | 
| 
       64 
     | 
    
         
            -
                    """
         
     | 
| 
       65 
     | 
    
         
            -
                    Look into the run log folder for the run log for the run id.
         
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
                    If the run log does not exist, raise an exception. If it does, decode it
         
     | 
| 
       68 
     | 
    
         
            -
                    as a RunLog and return it
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
                    Args:
         
     | 
| 
       71 
     | 
    
         
            -
                        run_id (str): The requested run id to retrieve the run log store
         
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
                    Raises:
         
     | 
| 
       74 
     | 
    
         
            -
                        FileNotFoundError: If the Run Log has not been found.
         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       77 
     | 
    
         
            -
                        RunLog: The decoded Run log
         
     | 
| 
       78 
     | 
    
         
            -
                    """
         
     | 
| 
       79 
     | 
    
         
            -
                    write_to = self.log_folder_name
         
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
                    read_from_path = Path(write_to)
         
     | 
| 
       82 
     | 
    
         
            -
                    json_file_path = read_from_path / f"{run_id}.json"
         
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
     | 
    
         
            -
                    if not json_file_path.exists():
         
     | 
| 
       85 
     | 
    
         
            -
                        raise FileNotFoundError(f"Expected {json_file_path} is not present")
         
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
                    with json_file_path.open("r") as fr:
         
     | 
| 
       88 
     | 
    
         
            -
                        json_str = json.load(fr)
         
     | 
| 
       89 
     | 
    
         
            -
                        run_log = RunLog(**json_str)  # pylint: disable=no-member
         
     | 
| 
       90 
     | 
    
         
            -
                    return run_log
         
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
                def create_run_log(
         
     | 
| 
       93 
     | 
    
         
            -
                    self,
         
     | 
| 
       94 
     | 
    
         
            -
                    run_id: str,
         
     | 
| 
       95 
     | 
    
         
            -
                    dag_hash: str = "",
         
     | 
| 
       96 
     | 
    
         
            -
                    use_cached: bool = False,
         
     | 
| 
       97 
     | 
    
         
            -
                    tag: str = "",
         
     | 
| 
       98 
     | 
    
         
            -
                    original_run_id: str = "",
         
     | 
| 
       99 
     | 
    
         
            -
                    status: str = defaults.CREATED,
         
     | 
| 
       100 
     | 
    
         
            -
                    **kwargs,
         
     | 
| 
       101 
     | 
    
         
            -
                ) -> RunLog:
         
     | 
| 
       102 
     | 
    
         
            -
                    """
         
     | 
| 
       103 
     | 
    
         
            -
                    # Creates a Run log
         
     | 
| 
       104 
     | 
    
         
            -
                    # Adds it to the db
         
     | 
| 
       105 
     | 
    
         
            -
                    """
         
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
                    try:
         
     | 
| 
       108 
     | 
    
         
            -
                        self.get_run_log_by_id(run_id=run_id, full=False)
         
     | 
| 
       109 
     | 
    
         
            -
                        raise exceptions.RunLogExistsError(run_id=run_id)
         
     | 
| 
       110 
     | 
    
         
            -
                    except exceptions.RunLogNotFoundError:
         
     | 
| 
       111 
     | 
    
         
            -
                        pass
         
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
                    logger.info(f"{self.service_name} Creating a Run Log for : {run_id}")
         
     | 
| 
       114 
     | 
    
         
            -
                    run_log = RunLog(
         
     | 
| 
       115 
     | 
    
         
            -
                        run_id=run_id,
         
     | 
| 
       116 
     | 
    
         
            -
                        dag_hash=dag_hash,
         
     | 
| 
       117 
     | 
    
         
            -
                        tag=tag,
         
     | 
| 
       118 
     | 
    
         
            -
                        status=status,
         
     | 
| 
       119 
     | 
    
         
            -
                    )
         
     | 
| 
       120 
     | 
    
         
            -
                    self.write_to_folder(run_log)
         
     | 
| 
       121 
     | 
    
         
            -
                    return run_log
         
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
       123 
     | 
    
         
            -
                def get_run_log_by_id(self, run_id: str, full: bool = False, **kwargs) -> RunLog:
         
     | 
| 
       124 
     | 
    
         
            -
                    """
         
     | 
| 
       125 
     | 
    
         
            -
                    # Returns the run_log defined by id
         
     | 
| 
       126 
     | 
    
         
            -
                    # Raises Exception if not found
         
     | 
| 
       127 
     | 
    
         
            -
                    """
         
     | 
| 
       128 
     | 
    
         
            -
                    try:
         
     | 
| 
       129 
     | 
    
         
            -
                        logger.info(f"{self.service_name} Getting a Run Log for : {run_id}")
         
     | 
| 
       130 
     | 
    
         
            -
                        run_log = self.get_from_folder(run_id)
         
     | 
| 
       131 
     | 
    
         
            -
                        return run_log
         
     | 
| 
       132 
     | 
    
         
            -
                    except FileNotFoundError as e:
         
     | 
| 
       133 
     | 
    
         
            -
                        raise exceptions.RunLogNotFoundError(run_id) from e
         
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
     | 
    
         
            -
                def put_run_log(self, run_log: RunLog, **kwargs):
         
     | 
| 
       136 
     | 
    
         
            -
                    """
         
     | 
| 
       137 
     | 
    
         
            -
                    # Puts the run_log into the database
         
     | 
| 
       138 
     | 
    
         
            -
                    """
         
     | 
| 
       139 
     | 
    
         
            -
                    logger.info(f"{self.service_name} Putting the run log in the DB: {run_log.run_id}")
         
     | 
| 
       140 
     | 
    
         
            -
                    self.write_to_folder(run_log)
         
     |