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.
- extensions/README.md +0 -0
- extensions/__init__.py +0 -0
- extensions/catalog/README.md +0 -0
- extensions/catalog/any_path.py +214 -0
- extensions/catalog/file_system.py +52 -0
- extensions/catalog/minio.py +72 -0
- extensions/catalog/pyproject.toml +14 -0
- extensions/catalog/s3.py +11 -0
- extensions/job_executor/README.md +0 -0
- extensions/job_executor/__init__.py +236 -0
- extensions/job_executor/emulate.py +70 -0
- extensions/job_executor/k8s.py +553 -0
- extensions/job_executor/k8s_job_spec.yaml +37 -0
- extensions/job_executor/local.py +35 -0
- extensions/job_executor/local_container.py +161 -0
- extensions/job_executor/pyproject.toml +16 -0
- extensions/nodes/README.md +0 -0
- extensions/nodes/__init__.py +0 -0
- extensions/nodes/conditional.py +301 -0
- extensions/nodes/fail.py +78 -0
- extensions/nodes/loop.py +394 -0
- extensions/nodes/map.py +477 -0
- extensions/nodes/parallel.py +281 -0
- extensions/nodes/pyproject.toml +15 -0
- extensions/nodes/stub.py +93 -0
- extensions/nodes/success.py +78 -0
- extensions/nodes/task.py +156 -0
- extensions/pipeline_executor/README.md +0 -0
- extensions/pipeline_executor/__init__.py +871 -0
- extensions/pipeline_executor/argo.py +1266 -0
- extensions/pipeline_executor/emulate.py +119 -0
- extensions/pipeline_executor/local.py +226 -0
- extensions/pipeline_executor/local_container.py +369 -0
- extensions/pipeline_executor/mocked.py +159 -0
- extensions/pipeline_executor/pyproject.toml +16 -0
- extensions/run_log_store/README.md +0 -0
- extensions/run_log_store/__init__.py +0 -0
- extensions/run_log_store/any_path.py +100 -0
- extensions/run_log_store/chunked_fs.py +122 -0
- extensions/run_log_store/chunked_minio.py +141 -0
- extensions/run_log_store/file_system.py +91 -0
- extensions/run_log_store/generic_chunked.py +549 -0
- extensions/run_log_store/minio.py +114 -0
- extensions/run_log_store/pyproject.toml +15 -0
- extensions/secrets/README.md +0 -0
- extensions/secrets/dotenv.py +62 -0
- extensions/secrets/pyproject.toml +15 -0
- runnable/__init__.py +108 -0
- runnable/catalog.py +141 -0
- runnable/cli.py +484 -0
- runnable/context.py +730 -0
- runnable/datastore.py +1058 -0
- runnable/defaults.py +159 -0
- runnable/entrypoints.py +390 -0
- runnable/exceptions.py +137 -0
- runnable/executor.py +561 -0
- runnable/gantt.py +1646 -0
- runnable/graph.py +501 -0
- runnable/names.py +546 -0
- runnable/nodes.py +593 -0
- runnable/parameters.py +217 -0
- runnable/pickler.py +96 -0
- runnable/sdk.py +1277 -0
- runnable/secrets.py +92 -0
- runnable/tasks.py +1268 -0
- runnable/telemetry.py +142 -0
- runnable/utils.py +423 -0
- runnable-0.50.0.dist-info/METADATA +189 -0
- runnable-0.50.0.dist-info/RECORD +72 -0
- runnable-0.50.0.dist-info/WHEEL +4 -0
- runnable-0.50.0.dist-info/entry_points.txt +53 -0
- 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")
|