runnable 0.1.0__py3-none-any.whl → 0.2.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 (71) hide show
  1. runnable/__init__.py +34 -0
  2. runnable/catalog.py +141 -0
  3. runnable/cli.py +272 -0
  4. runnable/context.py +34 -0
  5. runnable/datastore.py +686 -0
  6. runnable/defaults.py +179 -0
  7. runnable/entrypoints.py +484 -0
  8. runnable/exceptions.py +94 -0
  9. runnable/executor.py +431 -0
  10. runnable/experiment_tracker.py +139 -0
  11. runnable/extensions/catalog/__init__.py +21 -0
  12. runnable/extensions/catalog/file_system/__init__.py +0 -0
  13. runnable/extensions/catalog/file_system/implementation.py +226 -0
  14. runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
  15. runnable/extensions/catalog/k8s_pvc/implementation.py +16 -0
  16. runnable/extensions/catalog/k8s_pvc/integration.py +59 -0
  17. runnable/extensions/executor/__init__.py +714 -0
  18. runnable/extensions/executor/argo/__init__.py +0 -0
  19. runnable/extensions/executor/argo/implementation.py +1182 -0
  20. runnable/extensions/executor/argo/specification.yaml +51 -0
  21. runnable/extensions/executor/k8s_job/__init__.py +0 -0
  22. runnable/extensions/executor/k8s_job/implementation_FF.py +259 -0
  23. runnable/extensions/executor/k8s_job/integration_FF.py +69 -0
  24. runnable/extensions/executor/local/__init__.py +0 -0
  25. runnable/extensions/executor/local/implementation.py +69 -0
  26. runnable/extensions/executor/local_container/__init__.py +0 -0
  27. runnable/extensions/executor/local_container/implementation.py +367 -0
  28. runnable/extensions/executor/mocked/__init__.py +0 -0
  29. runnable/extensions/executor/mocked/implementation.py +220 -0
  30. runnable/extensions/experiment_tracker/__init__.py +0 -0
  31. runnable/extensions/experiment_tracker/mlflow/__init__.py +0 -0
  32. runnable/extensions/experiment_tracker/mlflow/implementation.py +94 -0
  33. runnable/extensions/nodes.py +675 -0
  34. runnable/extensions/run_log_store/__init__.py +0 -0
  35. runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
  36. runnable/extensions/run_log_store/chunked_file_system/implementation.py +106 -0
  37. runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
  38. runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +21 -0
  39. runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +61 -0
  40. runnable/extensions/run_log_store/db/implementation_FF.py +157 -0
  41. runnable/extensions/run_log_store/db/integration_FF.py +0 -0
  42. runnable/extensions/run_log_store/file_system/__init__.py +0 -0
  43. runnable/extensions/run_log_store/file_system/implementation.py +136 -0
  44. runnable/extensions/run_log_store/generic_chunked.py +541 -0
  45. runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
  46. runnable/extensions/run_log_store/k8s_pvc/implementation.py +21 -0
  47. runnable/extensions/run_log_store/k8s_pvc/integration.py +56 -0
  48. runnable/extensions/secrets/__init__.py +0 -0
  49. runnable/extensions/secrets/dotenv/__init__.py +0 -0
  50. runnable/extensions/secrets/dotenv/implementation.py +100 -0
  51. runnable/extensions/secrets/env_secrets/__init__.py +0 -0
  52. runnable/extensions/secrets/env_secrets/implementation.py +42 -0
  53. runnable/graph.py +464 -0
  54. runnable/integration.py +205 -0
  55. runnable/interaction.py +399 -0
  56. runnable/names.py +546 -0
  57. runnable/nodes.py +489 -0
  58. runnable/parameters.py +183 -0
  59. runnable/pickler.py +102 -0
  60. runnable/sdk.py +470 -0
  61. runnable/secrets.py +95 -0
  62. runnable/tasks.py +392 -0
  63. runnable/utils.py +630 -0
  64. runnable-0.2.0.dist-info/METADATA +437 -0
  65. runnable-0.2.0.dist-info/RECORD +69 -0
  66. runnable-0.2.0.dist-info/entry_points.txt +44 -0
  67. runnable-0.1.0.dist-info/METADATA +0 -16
  68. runnable-0.1.0.dist-info/RECORD +0 -6
  69. /runnable/{.gitkeep → extensions/__init__.py} +0 -0
  70. {runnable-0.1.0.dist-info → runnable-0.2.0.dist-info}/LICENSE +0 -0
  71. {runnable-0.1.0.dist-info → runnable-0.2.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,541 @@
1
+ import logging
2
+ import time
3
+ from abc import abstractmethod
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from string import Template
7
+ from typing import Any, Dict, Optional, Sequence, Union
8
+
9
+ from runnable import defaults, exceptions
10
+ from runnable.datastore import BaseRunLogStore, BranchLog, RunLog, StepLog
11
+
12
+ logger = logging.getLogger(defaults.LOGGER_NAME)
13
+
14
+
15
+ T = Union[str, Path] # Holds str, path
16
+
17
+
18
+ class EntityNotFoundError(Exception):
19
+ pass
20
+
21
+
22
+ class ChunkedRunLogStore(BaseRunLogStore):
23
+ """
24
+ A generic implementation of a RunLogStore that stores RunLogs in chunks.
25
+ """
26
+
27
+ service_name: str = ""
28
+
29
+ class LogTypes(Enum):
30
+ RUN_LOG = "RunLog"
31
+ PARAMETER = "Parameter"
32
+ STEP_LOG = "StepLog"
33
+ BRANCH_LOG = "BranchLog"
34
+
35
+ class ModelTypes(Enum):
36
+ RUN_LOG = RunLog
37
+ PARAMETER = dict
38
+ STEP_LOG = StepLog
39
+ BRANCH_LOG = BranchLog
40
+
41
+ def naming_pattern(self, log_type: LogTypes, name: str = "") -> str:
42
+ """
43
+ Naming pattern to store RunLog, Parameter, StepLog or BranchLog.
44
+
45
+ The reasoning for name to be defaulted to empty string:
46
+ Its actually conditionally empty. For RunLog and Parameter it is empty.
47
+ For StepLog and BranchLog it should be provided.
48
+
49
+ Args:
50
+ log_type (LogTypes): One of RunLog, Parameter, StepLog or BranchLog
51
+ name (str, optional): The name to be included or left. Defaults to ''.
52
+
53
+ Raises:
54
+ Exception: If log_type is not recognized
55
+
56
+ Returns:
57
+ str: The naming pattern
58
+ """
59
+ if log_type == self.LogTypes.RUN_LOG:
60
+ return f"{self.LogTypes.RUN_LOG.value}"
61
+
62
+ if log_type == self.LogTypes.PARAMETER:
63
+ return "-".join([self.LogTypes.PARAMETER.value, name])
64
+
65
+ if not name:
66
+ raise Exception(f"Name should be provided for naming pattern for {log_type}")
67
+
68
+ if log_type == self.LogTypes.STEP_LOG:
69
+ return "-".join([self.LogTypes.STEP_LOG.value, name, "${creation_time}"])
70
+
71
+ if log_type == self.LogTypes.BRANCH_LOG:
72
+ return "-".join([self.LogTypes.BRANCH_LOG.value, name, "${creation_time}"])
73
+
74
+ raise Exception("Unexpected log type")
75
+
76
+ @abstractmethod
77
+ def get_matches(self, run_id: str, name: str, multiple_allowed: bool = False) -> Optional[Union[Sequence[T], T]]:
78
+ """
79
+ Get contents of persistence layer matching the pattern name*
80
+
81
+ Args:
82
+ run_id (str): The run id
83
+ name (str): The suffix of the entity name to check in the run log store.
84
+ """
85
+ ...
86
+
87
+ @abstractmethod
88
+ def _store(self, run_id: str, contents: dict, name: T, insert: bool = False):
89
+ """
90
+ Store the contents against the name in the persistence layer.
91
+
92
+ Args:
93
+ run_id (str): The run id
94
+ contents (dict): The dict to store
95
+ name (str): The name to store as
96
+ """
97
+ ...
98
+
99
+ @abstractmethod
100
+ def _retrieve(self, name: T) -> dict:
101
+ """
102
+ Does the job of retrieving from the persistent layer.
103
+
104
+ Args:
105
+ name (str): the name of the file to retrieve
106
+
107
+ Returns:
108
+ dict: The contents
109
+ """
110
+ ...
111
+
112
+ def store(self, run_id: str, log_type: LogTypes, contents: dict, name: str = ""):
113
+ """Store a SINGLE log type in the file system
114
+
115
+ Args:
116
+ run_id (str): The run id to store against
117
+ log_type (LogTypes): The type of log to store
118
+ contents (dict): The dict of contents to store
119
+ name (str, optional): The name against the contents have to be stored. Defaults to ''.
120
+ """
121
+ naming_pattern = self.naming_pattern(log_type=log_type, name=name)
122
+ match = self.get_matches(run_id=run_id, name=naming_pattern, multiple_allowed=False)
123
+ # The boolean multiple allowed confuses mypy a lot!
124
+ name_to_give: str = ""
125
+ insert = False
126
+
127
+ if match:
128
+ existing_contents = self._retrieve(name=match) # type: ignore
129
+ contents = dict(existing_contents, **contents)
130
+ name_to_give = match # type: ignore
131
+ else:
132
+ name_to_give = Template(naming_pattern).safe_substitute({"creation_time": str(int(time.time_ns()))})
133
+ insert = True
134
+
135
+ self._store(run_id=run_id, contents=contents, name=name_to_give, insert=insert)
136
+
137
+ def retrieve(self, run_id: str, log_type: LogTypes, name: str = "", multiple_allowed=False) -> Any:
138
+ """
139
+ Retrieve the model given a log_type and a name.
140
+ Use multiple_allowed to control if you are expecting multiple of them.
141
+ eg: There could be multiple of Parameters- but only one of StepLog-stepname
142
+
143
+ The reasoning for name to be defaulted to empty string:
144
+ Its actually conditionally empty. For RunLog and Parameter it is empty.
145
+ For StepLog and BranchLog it should be provided.
146
+
147
+ Args:
148
+ run_id (str): The run id
149
+ log_type (LogTypes): One of RunLog, Parameter, StepLog, BranchLog
150
+ name (str, optional): The name to match. Defaults to ''.
151
+ multiple_allowed (bool, optional): Are multiple allowed. Defaults to False.
152
+
153
+ Raises:
154
+ FileNotFoundError: If there is no match found
155
+
156
+ Returns:
157
+ Any: One of StepLog, BranchLog, Parameter or RunLog
158
+ """
159
+ # The reason of any is it could be one of Logs or dict or list of the
160
+ if not name and log_type not in [
161
+ self.LogTypes.RUN_LOG,
162
+ self.LogTypes.PARAMETER,
163
+ ]:
164
+ raise Exception(f"Name is required during retrieval for {log_type}")
165
+
166
+ naming_pattern = self.naming_pattern(log_type=log_type, name=name)
167
+ matches = self.get_matches(run_id=run_id, name=naming_pattern, multiple_allowed=multiple_allowed)
168
+ if matches:
169
+ if not multiple_allowed:
170
+ contents = self._retrieve(name=matches) # type: ignore
171
+ model = self.ModelTypes[log_type.name].value
172
+ return model(**contents)
173
+
174
+ models = []
175
+ for match in matches: # type: ignore
176
+ contents = self._retrieve(name=match)
177
+ model = self.ModelTypes[log_type.name].value
178
+ models.append(model(**contents))
179
+ return models
180
+
181
+ raise EntityNotFoundError()
182
+
183
+ def orderly_retrieve(self, run_id: str, log_type: LogTypes) -> Dict[str, Union[StepLog, BranchLog]]:
184
+ """Should only be used by prepare full run log.
185
+
186
+ Retrieves the StepLog or BranchLog sorted according to creation time.
187
+
188
+ Args:
189
+ run_id (str): _description_
190
+ log_type (LogTypes): _description_
191
+ """
192
+ prefix: str = self.LogTypes.STEP_LOG.value
193
+
194
+ if log_type == self.LogTypes.BRANCH_LOG:
195
+ prefix = self.LogTypes.BRANCH_LOG.value
196
+
197
+ matches = self.get_matches(run_id=run_id, name=prefix, multiple_allowed=True)
198
+
199
+ if log_type == self.LogTypes.BRANCH_LOG and not matches:
200
+ # No branch logs are found
201
+ return {}
202
+ # Forcing get_matches to always return a list is a better design
203
+ epoch_created = [str(match).split("-")[-1] for match in matches] # type: ignore
204
+
205
+ # sort matches by epoch created
206
+ epoch_created, matches = zip(*sorted(zip(epoch_created, matches))) # type: ignore
207
+
208
+ logs: Dict[str, Union[StepLog, BranchLog]] = {}
209
+
210
+ for match in matches:
211
+ model = self.ModelTypes[log_type.name].value
212
+ log_model = model(**self._retrieve(match))
213
+ logs[log_model.internal_name] = log_model # type: ignore
214
+
215
+ return logs
216
+
217
+ def _get_parent_branch(self, name: str) -> Union[str, None]:
218
+ """
219
+ Returns the name of the parent branch.
220
+ If the step is part of main dag, return None.
221
+
222
+ Args:
223
+ name (str): The name of the step.
224
+
225
+ Returns:
226
+ str: The name of the branch containing the step.
227
+ """
228
+ dot_path = name.split(".")
229
+
230
+ if len(dot_path) == 1:
231
+ return None
232
+ # Ignore the step name
233
+ return ".".join(dot_path[:-1])
234
+
235
+ def _get_parent_step(self, name: str) -> Union[str, None]:
236
+ """
237
+ Returns the step containing the step, useful when we have steps within a branch.
238
+ Returns None, if the step belongs to parent dag.
239
+
240
+ Args:
241
+ name (str): The name of the step to find the parent step it belongs to.
242
+
243
+ Returns:
244
+ str: The parent step the step belongs to, None if the step belongs to parent dag.
245
+ """
246
+ dot_path = name.split(".")
247
+
248
+ if len(dot_path) == 1:
249
+ return None
250
+ # Ignore the branch.step_name
251
+ return ".".join(dot_path[:-2])
252
+
253
+ def _prepare_full_run_log(self, run_log: RunLog):
254
+ """
255
+ Populates the run log with the branches and steps.
256
+
257
+ Args:
258
+ run_log (RunLog): The partial run log containing empty step logs
259
+ """
260
+ run_id = run_log.run_id
261
+ run_log.parameters = self.get_parameters(run_id=run_id)
262
+
263
+ ordered_steps = self.orderly_retrieve(run_id=run_id, log_type=self.LogTypes.STEP_LOG)
264
+ ordered_branches = self.orderly_retrieve(run_id=run_id, log_type=self.LogTypes.BRANCH_LOG)
265
+
266
+ current_branch: Any = None # It could be str, None, RunLog
267
+ for step_internal_name in ordered_steps:
268
+ current_branch = self._get_parent_branch(step_internal_name)
269
+ step_to_add_branch = self._get_parent_step(step_internal_name)
270
+
271
+ if not current_branch:
272
+ current_branch = run_log
273
+ else:
274
+ current_branch = ordered_branches[current_branch]
275
+ step_to_add_branch = ordered_steps[step_to_add_branch] # type: ignore
276
+ step_to_add_branch.branches[current_branch.internal_name] = current_branch # type: ignore
277
+
278
+ current_branch.steps[step_internal_name] = ordered_steps[step_internal_name]
279
+
280
+ def create_run_log(
281
+ self,
282
+ run_id: str,
283
+ dag_hash: str = "",
284
+ use_cached: bool = False,
285
+ tag: str = "",
286
+ original_run_id: str = "",
287
+ status: str = defaults.CREATED,
288
+ **kwargs,
289
+ ):
290
+ """
291
+ Creates a Run Log object by using the config
292
+
293
+ Logically the method should do the following:
294
+ * Creates a Run log
295
+ * Adds it to the db
296
+ * Return the log
297
+ """
298
+ try:
299
+ self.get_run_log_by_id(run_id=run_id, full=False)
300
+ raise exceptions.RunLogExistsError(run_id=run_id)
301
+ except exceptions.RunLogNotFoundError:
302
+ pass
303
+
304
+ logger.info(f"{self.service_name} Creating a Run Log for : {run_id}")
305
+ run_log = RunLog(
306
+ run_id=run_id,
307
+ dag_hash=dag_hash,
308
+ use_cached=use_cached,
309
+ tag=tag,
310
+ original_run_id=original_run_id,
311
+ status=status,
312
+ )
313
+
314
+ self.store(run_id=run_id, contents=run_log.model_dump(), log_type=self.LogTypes.RUN_LOG)
315
+ return run_log
316
+
317
+ def get_run_log_by_id(self, run_id: str, full: bool = False, **kwargs) -> RunLog:
318
+ """
319
+ Retrieves a Run log from the database using the config and the run_id
320
+
321
+ Args:
322
+ run_id (str): The run_id of the run
323
+ full (bool): return the full run log store or only the RunLog object
324
+
325
+ Returns:
326
+ RunLog: The RunLog object identified by the run_id
327
+
328
+ Logically the method should:
329
+ * Returns the run_log defined by id from the data store defined by the config
330
+
331
+ """
332
+ try:
333
+ logger.info(f"{self.service_name} Getting a Run Log for : {run_id}")
334
+ run_log = self.retrieve(run_id=run_id, log_type=self.LogTypes.RUN_LOG, multiple_allowed=False)
335
+
336
+ if full:
337
+ self._prepare_full_run_log(run_log=run_log)
338
+
339
+ return run_log
340
+ except EntityNotFoundError as e:
341
+ raise exceptions.RunLogNotFoundError(run_id) from e
342
+
343
+ def put_run_log(self, run_log: RunLog, **kwargs):
344
+ """
345
+ Puts the Run Log in the database as defined by the config
346
+
347
+ Args:
348
+ run_log (RunLog): The Run log of the run
349
+
350
+ Logically the method should:
351
+ Puts the run_log into the database
352
+
353
+ Raises:
354
+ NotImplementedError: This is a base class and therefore has no default implementation
355
+ """
356
+ run_id = run_log.run_id
357
+ self.store(run_id=run_id, contents=run_log.model_dump(), log_type=self.LogTypes.RUN_LOG)
358
+
359
+ def get_parameters(self, run_id: str, **kwargs) -> dict:
360
+ """
361
+ Get the parameters from the Run log defined by the run_id
362
+
363
+ Args:
364
+ run_id (str): The run_id of the run
365
+
366
+ The method should:
367
+ * Call get_run_log_by_id(run_id) to retrieve the run_log
368
+ * Return the parameters as identified in the run_log
369
+
370
+ Returns:
371
+ dict: A dictionary of the run_log parameters
372
+ Raises:
373
+ RunLogNotFoundError: If the run log for run_id is not found in the datastore
374
+ """
375
+ parameters = {}
376
+ try:
377
+ parameters_list = self.retrieve(run_id=run_id, log_type=self.LogTypes.PARAMETER, multiple_allowed=True)
378
+ parameters = {key: value for param in parameters_list for key, value in param.items()}
379
+ except EntityNotFoundError:
380
+ # No parameters are set
381
+ pass
382
+
383
+ return parameters
384
+
385
+ def set_parameters(self, run_id: str, parameters: dict, **kwargs):
386
+ """
387
+ Update the parameters of the Run log with the new parameters
388
+
389
+ This method would over-write the parameters, if the parameter exists in the run log already
390
+
391
+ The method should:
392
+ * Call get_run_log_by_id(run_id) to retrieve the run_log
393
+ * Update the parameters of the run_log
394
+ * Call put_run_log(run_log) to put the run_log in the datastore
395
+
396
+ Args:
397
+ run_id (str): The run_id of the run
398
+ parameters (dict): The parameters to update in the run log
399
+ Raises:
400
+ RunLogNotFoundError: If the run log for run_id is not found in the datastore
401
+ """
402
+ for key, value in parameters.items():
403
+ self.store(
404
+ run_id=run_id,
405
+ log_type=self.LogTypes.PARAMETER,
406
+ contents={key: value},
407
+ name=key,
408
+ )
409
+
410
+ def get_run_config(self, run_id: str, **kwargs) -> dict:
411
+ """
412
+ Given a run_id, return the run_config used to perform the run.
413
+
414
+ Args:
415
+ run_id (str): The run_id of the run
416
+
417
+ Returns:
418
+ dict: The run config used for the run
419
+ """
420
+
421
+ run_log = self.get_run_log_by_id(run_id=run_id)
422
+ return run_log.run_config
423
+
424
+ def set_run_config(self, run_id: str, run_config: dict, **kwargs):
425
+ """Set the run config used to run the run_id
426
+
427
+ Args:
428
+ run_id (str): The run_id of the run
429
+ run_config (dict): The run_config of the run
430
+ """
431
+
432
+ run_log = self.get_run_log_by_id(run_id=run_id)
433
+ run_log.run_config.update(run_config)
434
+ self.put_run_log(run_log=run_log)
435
+
436
+ def get_step_log(self, internal_name: str, run_id: str, **kwargs) -> StepLog:
437
+ """
438
+ Get a step log from the datastore for run_id and the internal naming of the step log
439
+
440
+ The internal naming of the step log is a dot path convention.
441
+
442
+ The method should:
443
+ * Call get_run_log_by_id(run_id) to retrieve the run_log
444
+ * Identify the step location by decoding the internal naming
445
+ * Return the step log
446
+
447
+ Args:
448
+ internal_name (str): The internal name of the step log
449
+ run_id (str): The run_id of the run
450
+
451
+ Returns:
452
+ StepLog: The step log object for the step defined by the internal naming and run_id
453
+
454
+ Raises:
455
+ RunLogNotFoundError: If the run log for run_id is not found in the datastore
456
+ StepLogNotFoundError: If the step log for internal_name is not found in the datastore for run_id
457
+ """
458
+ logger.info(f"{self.service_name} Getting the step log: {internal_name} of {run_id}")
459
+
460
+ step_log = self.retrieve(
461
+ run_id=run_id,
462
+ log_type=self.LogTypes.STEP_LOG,
463
+ name=internal_name,
464
+ multiple_allowed=False,
465
+ )
466
+
467
+ return step_log
468
+
469
+ def add_step_log(self, step_log: StepLog, run_id: str, **kwargs):
470
+ """
471
+ Add the step log in the run log as identified by the run_id in the datastore
472
+
473
+ The method should:
474
+ * Call get_run_log_by_id(run_id) to retrieve the run_log
475
+ * Identify the branch to add the step by decoding the step_logs internal name
476
+ * Add the step log to the identified branch log
477
+ * Call put_run_log(run_log) to put the run_log in the datastore
478
+
479
+ Args:
480
+ step_log (StepLog): The Step log to add to the database
481
+ run_id (str): The run id of the run
482
+
483
+ Raises:
484
+ RunLogNotFoundError: If the run log for run_id is not found in the datastore
485
+ BranchLogNotFoundError: If the branch of the step log for internal_name is not found in the datastore
486
+ for run_id
487
+ """
488
+ logger.info(f"{self.service_name} Adding the step log to DB: {step_log.internal_name}")
489
+
490
+ self.store(
491
+ run_id=run_id,
492
+ log_type=self.LogTypes.STEP_LOG,
493
+ contents=step_log.model_dump(),
494
+ name=step_log.internal_name,
495
+ )
496
+
497
+ def get_branch_log(self, internal_branch_name: str, run_id: str, **kwargs) -> Union[BranchLog, RunLog]:
498
+ """
499
+ Returns the branch log by the internal branch name for the run id
500
+
501
+ If the internal branch name is none, returns the run log
502
+
503
+ Args:
504
+ internal_branch_name (str): The internal branch name to retrieve.
505
+ run_id (str): The run id of interest
506
+
507
+ Returns:
508
+ BranchLog: The branch log or the run log as requested.
509
+ """
510
+ if not internal_branch_name:
511
+ return self.get_run_log_by_id(run_id=run_id)
512
+ branch = self.retrieve(run_id=run_id, log_type=self.LogTypes.BRANCH_LOG, name=internal_branch_name)
513
+ return branch
514
+
515
+ def add_branch_log(self, branch_log: Union[BranchLog, RunLog], run_id: str, **kwargs):
516
+ """
517
+ The method should:
518
+ # Get the run log
519
+ # Get the branch and step containing the branch
520
+ # Add the branch to the step
521
+ # Write the run_log
522
+
523
+ The branch log could some times be a Run log and should be handled appropriately
524
+
525
+ Args:
526
+ branch_log (BranchLog): The branch log/run log to add to the database
527
+ run_id (str): The run id to which the branch/run log is added
528
+ """
529
+ if not isinstance(branch_log, BranchLog):
530
+ self.put_run_log(branch_log)
531
+ return
532
+
533
+ internal_branch_name = branch_log.internal_name
534
+
535
+ logger.info(f"{self.service_name} Adding the branch log to DB: {branch_log.internal_name}")
536
+ self.store(
537
+ run_id=run_id,
538
+ log_type=self.LogTypes.BRANCH_LOG,
539
+ contents=branch_log.model_dump(),
540
+ name=internal_branch_name,
541
+ )
File without changes
@@ -0,0 +1,21 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from runnable import defaults
5
+ from runnable.extensions.run_log_store.file_system.implementation import FileSystemRunLogstore
6
+
7
+ logger = logging.getLogger(defaults.NAME)
8
+
9
+
10
+ class K8PersistentVolumeRunLogstore(FileSystemRunLogstore):
11
+ """
12
+ Uses the K8s Persistent Volumes to store run logs.
13
+ """
14
+
15
+ service_name: str = "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)
@@ -0,0 +1,56 @@
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 = "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 = "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 = "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.k8s_pvc.implementation import K8PersistentVolumeRunLogstore
50
+
51
+ self.executor = cast(ArgoExecutor, self.executor)
52
+ self.service = cast(K8PersistentVolumeRunLogstore, self.service)
53
+
54
+ volume_mount = UserVolumeMounts(name=self.service.persistent_volume_name, mount_path=self.service.mount_path)
55
+
56
+ self.executor.persistent_volumes.append(volume_mount)
File without changes
File without changes