runnable 0.13.0__py3-none-any.whl → 0.16.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. runnable/__init__.py +1 -12
  2. runnable/catalog.py +29 -5
  3. runnable/cli.py +268 -215
  4. runnable/context.py +10 -3
  5. runnable/datastore.py +212 -53
  6. runnable/defaults.py +13 -55
  7. runnable/entrypoints.py +270 -183
  8. runnable/exceptions.py +28 -2
  9. runnable/executor.py +133 -86
  10. runnable/graph.py +37 -13
  11. runnable/nodes.py +50 -22
  12. runnable/parameters.py +27 -8
  13. runnable/pickler.py +1 -1
  14. runnable/sdk.py +230 -66
  15. runnable/secrets.py +3 -1
  16. runnable/tasks.py +99 -41
  17. runnable/utils.py +59 -39
  18. {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info}/METADATA +28 -31
  19. runnable-0.16.0.dist-info/RECORD +23 -0
  20. {runnable-0.13.0.dist-info → runnable-0.16.0.dist-info}/WHEEL +1 -1
  21. runnable-0.16.0.dist-info/entry_points.txt +45 -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.py +0 -69
  37. runnable/extensions/executor/local_container/__init__.py +0 -0
  38. runnable/extensions/executor/local_container/implementation.py +0 -446
  39. runnable/extensions/executor/mocked/__init__.py +0 -0
  40. runnable/extensions/executor/mocked/implementation.py +0 -154
  41. runnable/extensions/executor/retry/__init__.py +0 -0
  42. runnable/extensions/executor/retry/implementation.py +0 -168
  43. runnable/extensions/nodes.py +0 -870
  44. runnable/extensions/run_log_store/__init__.py +0 -0
  45. runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
  46. runnable/extensions/run_log_store/chunked_file_system/implementation.py +0 -111
  47. runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
  48. runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +0 -21
  49. runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +0 -61
  50. runnable/extensions/run_log_store/db/implementation_FF.py +0 -157
  51. runnable/extensions/run_log_store/db/integration_FF.py +0 -0
  52. runnable/extensions/run_log_store/file_system/__init__.py +0 -0
  53. runnable/extensions/run_log_store/file_system/implementation.py +0 -140
  54. runnable/extensions/run_log_store/generic_chunked.py +0 -557
  55. runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
  56. runnable/extensions/run_log_store/k8s_pvc/implementation.py +0 -21
  57. runnable/extensions/run_log_store/k8s_pvc/integration.py +0 -56
  58. runnable/extensions/secrets/__init__.py +0 -0
  59. runnable/extensions/secrets/dotenv/__init__.py +0 -0
  60. runnable/extensions/secrets/dotenv/implementation.py +0 -100
  61. runnable/integration.py +0 -192
  62. runnable-0.13.0.dist-info/RECORD +0 -63
  63. runnable-0.13.0.dist-info/entry_points.txt +0 -41
  64. {runnable-0.13.0.dist-info → runnable-0.16.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)