runnable 0.50.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 (72) hide show
  1. extensions/README.md +0 -0
  2. extensions/__init__.py +0 -0
  3. extensions/catalog/README.md +0 -0
  4. extensions/catalog/any_path.py +214 -0
  5. extensions/catalog/file_system.py +52 -0
  6. extensions/catalog/minio.py +72 -0
  7. extensions/catalog/pyproject.toml +14 -0
  8. extensions/catalog/s3.py +11 -0
  9. extensions/job_executor/README.md +0 -0
  10. extensions/job_executor/__init__.py +236 -0
  11. extensions/job_executor/emulate.py +70 -0
  12. extensions/job_executor/k8s.py +553 -0
  13. extensions/job_executor/k8s_job_spec.yaml +37 -0
  14. extensions/job_executor/local.py +35 -0
  15. extensions/job_executor/local_container.py +161 -0
  16. extensions/job_executor/pyproject.toml +16 -0
  17. extensions/nodes/README.md +0 -0
  18. extensions/nodes/__init__.py +0 -0
  19. extensions/nodes/conditional.py +301 -0
  20. extensions/nodes/fail.py +78 -0
  21. extensions/nodes/loop.py +394 -0
  22. extensions/nodes/map.py +477 -0
  23. extensions/nodes/parallel.py +281 -0
  24. extensions/nodes/pyproject.toml +15 -0
  25. extensions/nodes/stub.py +93 -0
  26. extensions/nodes/success.py +78 -0
  27. extensions/nodes/task.py +156 -0
  28. extensions/pipeline_executor/README.md +0 -0
  29. extensions/pipeline_executor/__init__.py +871 -0
  30. extensions/pipeline_executor/argo.py +1266 -0
  31. extensions/pipeline_executor/emulate.py +119 -0
  32. extensions/pipeline_executor/local.py +226 -0
  33. extensions/pipeline_executor/local_container.py +369 -0
  34. extensions/pipeline_executor/mocked.py +159 -0
  35. extensions/pipeline_executor/pyproject.toml +16 -0
  36. extensions/run_log_store/README.md +0 -0
  37. extensions/run_log_store/__init__.py +0 -0
  38. extensions/run_log_store/any_path.py +100 -0
  39. extensions/run_log_store/chunked_fs.py +122 -0
  40. extensions/run_log_store/chunked_minio.py +141 -0
  41. extensions/run_log_store/file_system.py +91 -0
  42. extensions/run_log_store/generic_chunked.py +549 -0
  43. extensions/run_log_store/minio.py +114 -0
  44. extensions/run_log_store/pyproject.toml +15 -0
  45. extensions/secrets/README.md +0 -0
  46. extensions/secrets/dotenv.py +62 -0
  47. extensions/secrets/pyproject.toml +15 -0
  48. runnable/__init__.py +108 -0
  49. runnable/catalog.py +141 -0
  50. runnable/cli.py +484 -0
  51. runnable/context.py +730 -0
  52. runnable/datastore.py +1058 -0
  53. runnable/defaults.py +159 -0
  54. runnable/entrypoints.py +390 -0
  55. runnable/exceptions.py +137 -0
  56. runnable/executor.py +561 -0
  57. runnable/gantt.py +1646 -0
  58. runnable/graph.py +501 -0
  59. runnable/names.py +546 -0
  60. runnable/nodes.py +593 -0
  61. runnable/parameters.py +217 -0
  62. runnable/pickler.py +96 -0
  63. runnable/sdk.py +1277 -0
  64. runnable/secrets.py +92 -0
  65. runnable/tasks.py +1268 -0
  66. runnable/telemetry.py +142 -0
  67. runnable/utils.py +423 -0
  68. runnable-0.50.0.dist-info/METADATA +189 -0
  69. runnable-0.50.0.dist-info/RECORD +72 -0
  70. runnable-0.50.0.dist-info/WHEEL +4 -0
  71. runnable-0.50.0.dist-info/entry_points.txt +53 -0
  72. runnable-0.50.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,62 @@
1
+ import logging
2
+
3
+ from dotenv import dotenv_values
4
+
5
+ from runnable import defaults, exceptions
6
+ from runnable.secrets import BaseSecrets
7
+
8
+ logger = logging.getLogger(defaults.LOGGER_NAME)
9
+
10
+
11
+ class DotEnvSecrets(BaseSecrets):
12
+ """
13
+ A secret manager which uses .env files for secrets.
14
+
15
+ We recommend this secrets manager only for local development and should not be used for anything close to
16
+ production.
17
+ """
18
+
19
+ service_name: str = "dotenv"
20
+ location: str = defaults.DOTENV_FILE_LOCATION
21
+ secrets: dict = {}
22
+
23
+ @property
24
+ def secrets_location(self):
25
+ """
26
+ Return the location of the .env file.
27
+ If the user has not over-ridden it, it defaults to .env file in the project root.
28
+
29
+ Returns:
30
+ str: The location of the secrets file
31
+ """
32
+ return self.location
33
+
34
+ def _load_secrets(self):
35
+ """
36
+ Use dotenv to load the secrets
37
+ """
38
+ self.secrets = dotenv_values(self.secrets_location)
39
+
40
+ def get(self, name: str = "") -> str:
41
+ """
42
+ Get a secret of name from the secrets file.
43
+
44
+
45
+ Args:
46
+ name (str): The name of the secret to retrieve
47
+
48
+ Raises:
49
+ Exception: If the secret by the name is not found.
50
+
51
+ Returns:
52
+ str: The value of the secret
53
+ """
54
+ if not self.secrets:
55
+ self._load_secrets()
56
+
57
+ if name in self.secrets:
58
+ return self.secrets[name]
59
+
60
+ raise exceptions.SecretNotFoundError(
61
+ secret_name=name, secret_setting=self.secrets_location
62
+ )
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "secrets"
3
+ version = "0.0.0"
4
+ description = "Extension to manage secrets"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = []
8
+
9
+
10
+ [build-system]
11
+ requires = ["hatchling"]
12
+ build-backend = "hatchling.build"
13
+
14
+ [tool.hatch.build.targets.wheel]
15
+ packages = ["."]
runnable/__init__.py ADDED
@@ -0,0 +1,108 @@
1
+ # ruff: noqa
2
+
3
+ import os
4
+
5
+ from rich.console import Console
6
+
7
+ console = Console(record=True)
8
+ console.print(":runner: Lets go!!")
9
+
10
+ task_console = Console(record=True)
11
+
12
+
13
+ def _configure_telemetry():
14
+ """
15
+ Auto-configure logfire/OpenTelemetry from environment variables.
16
+
17
+ This runs at import time to ensure telemetry is configured before
18
+ any task execution, especially for containerized execution where
19
+ the entrypoint is `runnable execute_single_node ...`.
20
+
21
+ Environment variables:
22
+ RUNNABLE_TELEMETRY_CONSOLE: Set to "true" for console output
23
+ OTEL_EXPORTER_OTLP_ENDPOINT: OTLP collector endpoint (e.g., http://localhost:4317)
24
+ LOGFIRE_TOKEN: Logfire cloud token (enables send_to_logfire)
25
+ """
26
+ import logfire_api as logfire
27
+
28
+ console_enabled = os.environ.get("RUNNABLE_TELEMETRY_CONSOLE", "").lower() == "true"
29
+ otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "")
30
+ logfire_token = os.environ.get("LOGFIRE_TOKEN", "")
31
+
32
+ # Skip if no telemetry config is set
33
+ if not (console_enabled or otlp_endpoint or logfire_token):
34
+ return
35
+
36
+ try:
37
+ import logfire
38
+
39
+ config_kwargs = {
40
+ "send_to_logfire": bool(logfire_token),
41
+ }
42
+
43
+ if console_enabled:
44
+ config_kwargs["console"] = logfire.ConsoleOptions(
45
+ colors="auto",
46
+ span_style="indented",
47
+ verbose=True,
48
+ )
49
+
50
+ if otlp_endpoint:
51
+ try:
52
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
53
+ OTLPSpanExporter,
54
+ )
55
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
56
+
57
+ config_kwargs["additional_span_processors"] = [
58
+ BatchSpanProcessor(OTLPSpanExporter(endpoint=otlp_endpoint))
59
+ ]
60
+ except ImportError:
61
+ pass # OTLP exporter not installed
62
+
63
+ logfire.configure(**config_kwargs) # ty: ignore
64
+
65
+ except ImportError:
66
+ pass # logfire not installed, telemetry is no-op
67
+
68
+
69
+ _configure_telemetry()
70
+
71
+
72
+ from runnable.sdk import ( # noqa;
73
+ AsyncPipeline,
74
+ AsyncPythonTask,
75
+ Catalog,
76
+ Conditional,
77
+ Fail,
78
+ Loop,
79
+ Map,
80
+ NotebookJob,
81
+ NotebookTask,
82
+ Parallel,
83
+ Pipeline,
84
+ PythonJob,
85
+ PythonTask,
86
+ ShellJob,
87
+ ShellTask,
88
+ Stub,
89
+ Success,
90
+ json,
91
+ metric,
92
+ pickled,
93
+ )
94
+ from runnable.telemetry import ( # noqa;
95
+ OTEL_AVAILABLE,
96
+ get_stream_queue,
97
+ set_stream_queue,
98
+ truncate_value,
99
+ )
100
+
101
+ # Conditionally export StreamingSpanProcessor
102
+ if OTEL_AVAILABLE:
103
+ from runnable.telemetry import StreamingSpanProcessor # noqa;
104
+ else:
105
+ StreamingSpanProcessor = None # type: ignore
106
+
107
+ # Needed to disable ploomber telemetry
108
+ os.environ["PLOOMBER_STATS_ENABLED"] = "false"
runnable/catalog.py ADDED
@@ -0,0 +1,141 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Dict, List
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ import runnable.context as context
8
+ from runnable import defaults
9
+ from runnable.datastore import DataCatalog
10
+
11
+ logger = logging.getLogger(defaults.LOGGER_NAME)
12
+
13
+
14
+ # --8<-- [start:docs]
15
+
16
+
17
+ class BaseCatalog(ABC, BaseModel):
18
+ """
19
+ Base Catalog class definition.
20
+
21
+ All implementations of the catalog handler should inherit and extend this class.
22
+ """
23
+
24
+ service_name: str = ""
25
+ service_type: str = "catalog"
26
+
27
+ compute_data_folder: str = Field(default=defaults.COMPUTE_DATA_FOLDER)
28
+
29
+ model_config = ConfigDict(extra="forbid")
30
+
31
+ @abstractmethod
32
+ def get_summary(self) -> Dict[str, Any]: ...
33
+
34
+ @property
35
+ def _context(self):
36
+ current_context = context.get_run_context()
37
+ if current_context is None:
38
+ raise RuntimeError("No run context available")
39
+ return current_context
40
+
41
+ @abstractmethod
42
+ def get(self, name: str) -> List[DataCatalog]:
43
+ """
44
+ Get the catalog item by 'name' for the 'run id' and store it in compute data folder.
45
+
46
+ The catalog location should have been created before you can get from it.
47
+
48
+ Args:
49
+ name (str): The name of the catalog item
50
+ run_id (str): The run_id of the run.
51
+ compute_data_folder (str, optional): The compute data folder. Defaults to runnable default (data/)
52
+
53
+ Raises:
54
+ NotImplementedError: Base class, hence not implemented
55
+
56
+ Returns:
57
+ List(object) : A list of catalog objects
58
+ """
59
+ raise NotImplementedError
60
+
61
+ @abstractmethod
62
+ def put(
63
+ self, name: str, allow_file_not_found_exc: bool = False, store_copy: bool = True
64
+ ) -> List[DataCatalog]:
65
+ """
66
+ Put the file by 'name' from the 'compute_data_folder' in the catalog for the run_id.
67
+
68
+ If previous syncing has happened and the file has not been changed, we do not sync again.
69
+
70
+ Args:
71
+ name (str): The name of the catalog item.
72
+ run_id (str): The run_id of the run.
73
+ compute_data_folder (str, optional): The compute data folder. Defaults to runnable default (data/)
74
+ synced_catalogs (dict, optional): Any previously synced catalogs. Defaults to None.
75
+
76
+ Raises:
77
+ NotImplementedError: Base class, hence not implemented
78
+
79
+ Returns:
80
+ List(object) : A list of catalog objects
81
+ """
82
+ raise NotImplementedError
83
+
84
+ @abstractmethod
85
+ def sync_between_runs(self, previous_run_id: str, run_id: str):
86
+ """
87
+ Given run_id of a previous run, sync them to the catalog of the run given by run_id
88
+
89
+ Args:
90
+ previous_run_id (str): The run id of the previous run
91
+ run_id (str): The run_id to which the data catalogs should be synced to.
92
+
93
+ Raises:
94
+ NotImplementedError: Base class, hence not implemented
95
+ """
96
+ raise NotImplementedError
97
+
98
+
99
+ # --8<-- [end:docs]
100
+
101
+
102
+ class DoNothingCatalog(BaseCatalog):
103
+ """
104
+ A Catalog handler that does nothing.
105
+
106
+ Example config:
107
+
108
+ catalog:
109
+ type: do-nothing
110
+
111
+ """
112
+
113
+ service_name: str = "do-nothing"
114
+
115
+ def get_summary(self) -> Dict[str, Any]:
116
+ return {}
117
+
118
+ def get(self, name: str) -> List[DataCatalog]:
119
+ """
120
+ Does nothing
121
+ """
122
+ logger.info("Using a do-nothing catalog, doing nothing in get")
123
+ return []
124
+
125
+ def put(
126
+ self,
127
+ name: str,
128
+ allow_file_not_found_exc: bool = False,
129
+ store_copy: bool = True,
130
+ ) -> List[DataCatalog]:
131
+ """
132
+ Does nothing
133
+ """
134
+ logger.info("Using a do-nothing catalog, doing nothing in put")
135
+ return []
136
+
137
+ def sync_between_runs(self, previous_run_id: str, run_id: str):
138
+ """
139
+ Does nothing
140
+ """
141
+ logger.info("Using a do-nothing catalog, doing nothing while sync between runs")