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.
Files changed (64) hide show
  1. runnable/__init__.py +0 -11
  2. runnable/catalog.py +27 -5
  3. runnable/cli.py +122 -26
  4. runnable/datastore.py +71 -35
  5. runnable/defaults.py +0 -1
  6. runnable/entrypoints.py +107 -32
  7. runnable/exceptions.py +6 -2
  8. runnable/executor.py +28 -9
  9. runnable/graph.py +37 -12
  10. runnable/integration.py +7 -2
  11. runnable/nodes.py +15 -17
  12. runnable/parameters.py +27 -8
  13. runnable/pickler.py +1 -1
  14. runnable/sdk.py +101 -33
  15. runnable/secrets.py +3 -1
  16. runnable/tasks.py +246 -34
  17. runnable/utils.py +41 -13
  18. {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info}/METADATA +25 -31
  19. runnable-0.14.0.dist-info/RECORD +24 -0
  20. {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info}/WHEEL +1 -1
  21. runnable-0.14.0.dist-info/entry_points.txt +40 -0
  22. runnable/extensions/__init__.py +0 -0
  23. runnable/extensions/catalog/__init__.py +0 -21
  24. runnable/extensions/catalog/file_system/__init__.py +0 -0
  25. runnable/extensions/catalog/file_system/implementation.py +0 -234
  26. runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
  27. runnable/extensions/catalog/k8s_pvc/implementation.py +0 -16
  28. runnable/extensions/catalog/k8s_pvc/integration.py +0 -59
  29. runnable/extensions/executor/__init__.py +0 -649
  30. runnable/extensions/executor/argo/__init__.py +0 -0
  31. runnable/extensions/executor/argo/implementation.py +0 -1194
  32. runnable/extensions/executor/argo/specification.yaml +0 -51
  33. runnable/extensions/executor/k8s_job/__init__.py +0 -0
  34. runnable/extensions/executor/k8s_job/implementation_FF.py +0 -259
  35. runnable/extensions/executor/k8s_job/integration_FF.py +0 -69
  36. runnable/extensions/executor/local/__init__.py +0 -0
  37. runnable/extensions/executor/local/implementation.py +0 -71
  38. runnable/extensions/executor/local_container/__init__.py +0 -0
  39. runnable/extensions/executor/local_container/implementation.py +0 -446
  40. runnable/extensions/executor/mocked/__init__.py +0 -0
  41. runnable/extensions/executor/mocked/implementation.py +0 -154
  42. runnable/extensions/executor/retry/__init__.py +0 -0
  43. runnable/extensions/executor/retry/implementation.py +0 -168
  44. runnable/extensions/nodes.py +0 -855
  45. runnable/extensions/run_log_store/__init__.py +0 -0
  46. runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
  47. runnable/extensions/run_log_store/chunked_file_system/implementation.py +0 -111
  48. runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
  49. runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +0 -21
  50. runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +0 -61
  51. runnable/extensions/run_log_store/db/implementation_FF.py +0 -157
  52. runnable/extensions/run_log_store/db/integration_FF.py +0 -0
  53. runnable/extensions/run_log_store/file_system/__init__.py +0 -0
  54. runnable/extensions/run_log_store/file_system/implementation.py +0 -140
  55. runnable/extensions/run_log_store/generic_chunked.py +0 -557
  56. runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
  57. runnable/extensions/run_log_store/k8s_pvc/implementation.py +0 -21
  58. runnable/extensions/run_log_store/k8s_pvc/integration.py +0 -56
  59. runnable/extensions/secrets/__init__.py +0 -0
  60. runnable/extensions/secrets/dotenv/__init__.py +0 -0
  61. runnable/extensions/secrets/dotenv/implementation.py +0 -100
  62. runnable-0.12.3.dist-info/RECORD +0 -64
  63. runnable-0.12.3.dist-info/entry_points.txt +0 -41
  64. {runnable-0.12.3.dist-info → runnable-0.14.0.dist-info/licenses}/LICENSE +0 -0
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
@@ -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)