runnable 0.10.0__py3-none-any.whl → 0.11.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.
- runnable/__init__.py +13 -9
- runnable/catalog.py +8 -1
- runnable/cli.py +1 -0
- runnable/context.py +5 -3
- runnable/datastore.py +96 -12
- runnable/defaults.py +9 -9
- runnable/entrypoints.py +38 -24
- runnable/exceptions.py +4 -0
- runnable/extensions/catalog/file_system/implementation.py +8 -1
- runnable/extensions/executor/__init__.py +85 -29
- runnable/extensions/executor/argo/implementation.py +8 -4
- runnable/extensions/executor/local/implementation.py +1 -0
- runnable/extensions/nodes.py +90 -13
- runnable/extensions/run_log_store/chunked_file_system/implementation.py +6 -1
- runnable/extensions/run_log_store/file_system/implementation.py +6 -0
- runnable/graph.py +11 -0
- runnable/integration.py +4 -17
- runnable/nodes.py +9 -0
- runnable/parameters.py +3 -1
- runnable/sdk.py +29 -4
- runnable/tasks.py +45 -15
- runnable/utils.py +2 -1
- {runnable-0.10.0.dist-info → runnable-0.11.0.dist-info}/METADATA +1 -1
- {runnable-0.10.0.dist-info → runnable-0.11.0.dist-info}/RECORD +27 -31
- {runnable-0.10.0.dist-info → runnable-0.11.0.dist-info}/entry_points.txt +0 -4
- runnable/experiment_tracker.py +0 -139
- runnable/extensions/experiment_tracker/__init__.py +0 -0
- runnable/extensions/experiment_tracker/mlflow/__init__.py +0 -0
- runnable/extensions/experiment_tracker/mlflow/implementation.py +0 -94
- {runnable-0.10.0.dist-info → runnable-0.11.0.dist-info}/LICENSE +0 -0
- {runnable-0.10.0.dist-info → runnable-0.11.0.dist-info}/WHEEL +0 -0
runnable/sdk.py
CHANGED
@@ -16,9 +16,11 @@ from pydantic import (
|
|
16
16
|
model_validator,
|
17
17
|
)
|
18
18
|
from rich import print
|
19
|
+
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn
|
20
|
+
from rich.table import Column
|
19
21
|
from typing_extensions import Self
|
20
22
|
|
21
|
-
from runnable import defaults, entrypoints, graph, utils
|
23
|
+
from runnable import console, defaults, entrypoints, graph, utils
|
22
24
|
from runnable.extensions.nodes import (
|
23
25
|
FailNode,
|
24
26
|
MapNode,
|
@@ -39,6 +41,10 @@ def pickled(name: str) -> TaskReturns:
|
|
39
41
|
return TaskReturns(name=name, kind="object")
|
40
42
|
|
41
43
|
|
44
|
+
def metric(name: str) -> TaskReturns:
|
45
|
+
return TaskReturns(name=name, kind="metric")
|
46
|
+
|
47
|
+
|
42
48
|
class Catalog(BaseModel):
|
43
49
|
"""
|
44
50
|
Use to instruct a task to sync data from/to the central catalog.
|
@@ -360,7 +366,7 @@ class Stub(BaseTraversal):
|
|
360
366
|
|
361
367
|
"""
|
362
368
|
|
363
|
-
model_config = ConfigDict(extra="
|
369
|
+
model_config = ConfigDict(extra="ignore")
|
364
370
|
catalog: Optional[Catalog] = Field(default=None, alias="catalog")
|
365
371
|
|
366
372
|
def create_node(self) -> StubNode:
|
@@ -686,8 +692,27 @@ class Pipeline(BaseModel):
|
|
686
692
|
# Prepare for graph execution
|
687
693
|
run_context.executor.prepare_for_graph_execution()
|
688
694
|
|
689
|
-
|
690
|
-
|
695
|
+
with Progress(
|
696
|
+
TextColumn("[progress.description]{task.description}", table_column=Column(ratio=2)),
|
697
|
+
BarColumn(table_column=Column(ratio=1), style="dark_orange"),
|
698
|
+
TimeElapsedColumn(table_column=Column(ratio=1)),
|
699
|
+
console=console,
|
700
|
+
expand=True,
|
701
|
+
) as progress:
|
702
|
+
try:
|
703
|
+
run_context.progress = progress
|
704
|
+
pipeline_execution_task = progress.add_task("[dark_orange] Starting execution .. ", total=1)
|
705
|
+
run_context.executor.execute_graph(dag=run_context.dag)
|
706
|
+
|
707
|
+
run_log = run_context.run_log_store.get_run_log_by_id(run_id=run_context.run_id, full=False)
|
708
|
+
|
709
|
+
if run_log.status == defaults.SUCCESS:
|
710
|
+
progress.update(pipeline_execution_task, description="[green] Success", completed=True)
|
711
|
+
else:
|
712
|
+
progress.update(pipeline_execution_task, description="[red] Failed", completed=True)
|
713
|
+
except Exception as e: # noqa: E722
|
714
|
+
console.print(e, style=defaults.error_style)
|
715
|
+
progress.update(pipeline_execution_task, description="[red] Errored execution", completed=True)
|
691
716
|
|
692
717
|
if run_context.executor._local:
|
693
718
|
return run_context.run_log_store.get_run_log_by_id(run_id=run_context.run_id)
|
runnable/tasks.py
CHANGED
@@ -15,8 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validat
|
|
15
15
|
from stevedore import driver
|
16
16
|
|
17
17
|
import runnable.context as context
|
18
|
-
from runnable import defaults, parameters, utils
|
19
|
-
from runnable.datastore import
|
18
|
+
from runnable import console, defaults, exceptions, parameters, utils
|
19
|
+
from runnable.datastore import (
|
20
|
+
JsonParameter,
|
21
|
+
MetricParameter,
|
22
|
+
ObjectParameter,
|
23
|
+
Parameter,
|
24
|
+
StepAttempt,
|
25
|
+
)
|
20
26
|
from runnable.defaults import TypeMapVariable
|
21
27
|
|
22
28
|
logger = logging.getLogger(defaults.LOGGER_NAME)
|
@@ -28,7 +34,7 @@ logging.getLogger("stevedore").setLevel(logging.CRITICAL)
|
|
28
34
|
|
29
35
|
class TaskReturns(BaseModel):
|
30
36
|
name: str
|
31
|
-
kind: Literal["json", "object"] = Field(default="json")
|
37
|
+
kind: Literal["json", "object", "metric"] = Field(default="json")
|
32
38
|
|
33
39
|
|
34
40
|
class BaseTaskType(BaseModel):
|
@@ -41,6 +47,9 @@ class BaseTaskType(BaseModel):
|
|
41
47
|
|
42
48
|
model_config = ConfigDict(extra="forbid")
|
43
49
|
|
50
|
+
def get_summary(self) -> Dict[str, Any]:
|
51
|
+
return self.model_dump(by_alias=True, exclude_none=True)
|
52
|
+
|
44
53
|
@property
|
45
54
|
def _context(self):
|
46
55
|
return context.run_context
|
@@ -98,12 +107,15 @@ class BaseTaskType(BaseModel):
|
|
98
107
|
self.set_secrets_as_env_variables()
|
99
108
|
try:
|
100
109
|
yield
|
110
|
+
except Exception as e: # pylint: disable=broad-except
|
111
|
+
logger.exception(e)
|
101
112
|
finally:
|
102
113
|
self.delete_secrets_from_env_variables()
|
103
114
|
|
104
115
|
@contextlib.contextmanager
|
105
116
|
def execution_context(self, map_variable: TypeMapVariable = None, allow_complex: bool = True):
|
106
117
|
params = self._context.run_log_store.get_parameters(run_id=self._context.run_id).copy()
|
118
|
+
logger.info(f"Parameters available for the execution: {params}")
|
107
119
|
|
108
120
|
for param_name, param in params.items():
|
109
121
|
# Any access to unreduced param should be replaced.
|
@@ -118,6 +130,8 @@ class BaseTaskType(BaseModel):
|
|
118
130
|
if context_param in params:
|
119
131
|
params[param_name].value = params[context_param].value
|
120
132
|
|
133
|
+
logger.debug(f"Resolved parameters: {params}")
|
134
|
+
|
121
135
|
if not allow_complex:
|
122
136
|
params = {key: value for key, value in params.items() if isinstance(value, JsonParameter)}
|
123
137
|
|
@@ -132,6 +146,8 @@ class BaseTaskType(BaseModel):
|
|
132
146
|
try:
|
133
147
|
with contextlib.redirect_stdout(f):
|
134
148
|
yield params
|
149
|
+
except Exception as e: # pylint: disable=broad-except
|
150
|
+
logger.exception(e)
|
135
151
|
finally:
|
136
152
|
print(f.getvalue()) # print to console
|
137
153
|
log_file.write(f.getvalue()) # Print to file
|
@@ -140,15 +156,12 @@ class BaseTaskType(BaseModel):
|
|
140
156
|
log_file.close()
|
141
157
|
|
142
158
|
# Put the log file in the catalog
|
143
|
-
catalog_handler =
|
144
|
-
catalog_handler.put(name=log_file.name, run_id=context.run_context.run_id)
|
159
|
+
# self._context.catalog_handler.put(name=log_file.name, run_id=context.run_context.run_id)
|
145
160
|
os.remove(log_file.name)
|
146
161
|
|
147
162
|
# Update parameters
|
148
163
|
self._context.run_log_store.set_parameters(parameters=params, run_id=self._context.run_id)
|
149
164
|
|
150
|
-
return True # To suppress exceptions
|
151
|
-
|
152
165
|
|
153
166
|
def task_return_to_parameter(task_return: TaskReturns, value: Any) -> Parameter:
|
154
167
|
# implicit support for pydantic models
|
@@ -161,6 +174,9 @@ def task_return_to_parameter(task_return: TaskReturns, value: Any) -> Parameter:
|
|
161
174
|
if task_return.kind == "json":
|
162
175
|
return JsonParameter(kind="json", value=value)
|
163
176
|
|
177
|
+
if task_return.kind == "metric":
|
178
|
+
return MetricParameter(kind="metric", value=value)
|
179
|
+
|
164
180
|
if task_return.kind == "object":
|
165
181
|
obj = ObjectParameter(value=task_return.name, kind="object")
|
166
182
|
obj.put_object(data=value)
|
@@ -197,13 +213,23 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
|
|
197
213
|
imported_module = importlib.import_module(module)
|
198
214
|
f = getattr(imported_module, func)
|
199
215
|
|
200
|
-
filtered_parameters = parameters.filter_arguments_for_func(f, params.copy(), map_variable)
|
201
|
-
logger.info(f"Calling {func} from {module} with {filtered_parameters}")
|
202
|
-
|
203
216
|
try:
|
204
|
-
|
217
|
+
try:
|
218
|
+
filtered_parameters = parameters.filter_arguments_for_func(f, params.copy(), map_variable)
|
219
|
+
logger.info(f"Calling {func} from {module} with {filtered_parameters}")
|
220
|
+
user_set_parameters = f(**filtered_parameters) # This is a tuple or single value
|
221
|
+
except Exception as e:
|
222
|
+
logger.exception(e)
|
223
|
+
console.print(e, style=defaults.error_style)
|
224
|
+
raise exceptions.CommandCallError(f"Function call: {self.command} did not succeed.\n") from e
|
225
|
+
|
205
226
|
attempt_log.input_parameters = params.copy()
|
206
227
|
|
228
|
+
if map_variable:
|
229
|
+
attempt_log.input_parameters.update(
|
230
|
+
{k: JsonParameter(value=v, kind="json") for k, v in map_variable.items()}
|
231
|
+
)
|
232
|
+
|
207
233
|
if self.returns:
|
208
234
|
if not isinstance(user_set_parameters, tuple): # make it a tuple
|
209
235
|
user_set_parameters = (user_set_parameters,)
|
@@ -212,6 +238,7 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
|
|
212
238
|
raise ValueError("Returns task signature does not match the function returns")
|
213
239
|
|
214
240
|
output_parameters: Dict[str, Parameter] = {}
|
241
|
+
metrics: Dict[str, Parameter] = {}
|
215
242
|
|
216
243
|
for i, task_return in enumerate(self.returns):
|
217
244
|
output_parameter = task_return_to_parameter(
|
@@ -219,6 +246,9 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
|
|
219
246
|
value=user_set_parameters[i],
|
220
247
|
)
|
221
248
|
|
249
|
+
if task_return.kind == "metric":
|
250
|
+
metrics[task_return.name] = output_parameter
|
251
|
+
|
222
252
|
param_name = task_return.name
|
223
253
|
if map_variable:
|
224
254
|
for _, v in map_variable.items():
|
@@ -227,14 +257,15 @@ class PythonTaskType(BaseTaskType): # pylint: disable=too-few-public-methods
|
|
227
257
|
output_parameters[param_name] = output_parameter
|
228
258
|
|
229
259
|
attempt_log.output_parameters = output_parameters
|
260
|
+
attempt_log.user_defined_metrics = metrics
|
230
261
|
params.update(output_parameters)
|
231
262
|
|
232
263
|
attempt_log.status = defaults.SUCCESS
|
233
264
|
except Exception as _e:
|
234
|
-
msg = f"Call to the function {self.command}
|
235
|
-
logger.exception(msg)
|
265
|
+
msg = f"Call to the function {self.command} did not succeed.\n"
|
236
266
|
logger.exception(_e)
|
237
|
-
attempt_log.
|
267
|
+
attempt_log.message = msg
|
268
|
+
console.print(_e, style=defaults.error_style)
|
238
269
|
|
239
270
|
attempt_log.end_time = str(datetime.now())
|
240
271
|
|
@@ -296,7 +327,6 @@ class NotebookTaskType(BaseTaskType):
|
|
296
327
|
if map_variable:
|
297
328
|
for key, value in map_variable.items():
|
298
329
|
notebook_output_path += "_" + str(value)
|
299
|
-
|
300
330
|
params[key] = value
|
301
331
|
|
302
332
|
notebook_params = {k: v.get_value() for k, v in params.items()}
|
runnable/utils.py
CHANGED
@@ -538,7 +538,7 @@ def get_provider_by_name_and_type(service_type: str, service_details: defaults.S
|
|
538
538
|
if "config" in service_details:
|
539
539
|
service_config = service_details.get("config", {})
|
540
540
|
|
541
|
-
logger.
|
541
|
+
logger.debug(f"Trying to get a service of {service_type} of the name {service_name} with config: {service_config}")
|
542
542
|
try:
|
543
543
|
mgr = driver.DriverManager(
|
544
544
|
namespace=namespace,
|
@@ -548,6 +548,7 @@ def get_provider_by_name_and_type(service_type: str, service_details: defaults.S
|
|
548
548
|
)
|
549
549
|
return mgr.driver
|
550
550
|
except Exception as _e:
|
551
|
+
logger.exception(f"Could not find the service of type: {service_type} with config: {service_details}")
|
551
552
|
raise Exception(f"Could not find the service of type: {service_type} with config: {service_details}") from _e
|
552
553
|
|
553
554
|
|
@@ -1,49 +1,45 @@
|
|
1
|
-
runnable/__init__.py,sha256=
|
2
|
-
runnable/catalog.py,sha256=
|
3
|
-
runnable/cli.py,sha256=
|
4
|
-
runnable/context.py,sha256=
|
5
|
-
runnable/datastore.py,sha256=
|
6
|
-
runnable/defaults.py,sha256=
|
7
|
-
runnable/entrypoints.py,sha256
|
8
|
-
runnable/exceptions.py,sha256=
|
1
|
+
runnable/__init__.py,sha256=BzuufxKGqgYvd-v4fwhH7lgYGCzOnwY7ca5pZZasgx8,720
|
2
|
+
runnable/catalog.py,sha256=22OECi5TrpHErxYIhfx-lJ2vgBUi4-5V9CaYEVm98hE,4138
|
3
|
+
runnable/cli.py,sha256=gDKk-eg1Vlf0NAWEFqdFEUfcDJdBawRPgSMpNqpbsOc,9590
|
4
|
+
runnable/context.py,sha256=QhiXJHRcEBfSKB1ijvL5yB9w44x0HCe7VEiwK1cUJ9U,1124
|
5
|
+
runnable/datastore.py,sha256=EgKi4_b5g6KbInpjMyw8Xwr-EgcSGi1Lx2u5vp4amSQ,27672
|
6
|
+
runnable/defaults.py,sha256=MOX7I2S6yO4FphZaZREFQca94a20oO8uvzXLd6GLKQs,4703
|
7
|
+
runnable/entrypoints.py,sha256=-GbwFCUPkLfXpvslD1abXtlfwZevSocqc6QK8TPL40Q,16197
|
8
|
+
runnable/exceptions.py,sha256=6NIYoTAzdKyGQ9PvW1Hu7b80OS746395KiGDhM7ThH8,2526
|
9
9
|
runnable/executor.py,sha256=xfBighQ5t_vejohip000XfxLwsgechUE1ZMIJWrZbUA,14484
|
10
|
-
runnable/experiment_tracker.py,sha256=bX2Vr73f3bsdnWqxjMSSiKA-WwqkUHfUzJQqZoQBpvY,3668
|
11
10
|
runnable/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
11
|
runnable/extensions/catalog/__init__.py,sha256=uXZ6D-Myr_J4HnBA4F5Hd7LZ0IAjQiFQYxRhMzejhQc,761
|
13
12
|
runnable/extensions/catalog/file_system/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
runnable/extensions/catalog/file_system/implementation.py,sha256=
|
13
|
+
runnable/extensions/catalog/file_system/implementation.py,sha256=9j920o9SULdcVp1Mr8FgeuV-Sv5bR3w5tcohChxHnak,9130
|
15
14
|
runnable/extensions/catalog/k8s_pvc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
15
|
runnable/extensions/catalog/k8s_pvc/implementation.py,sha256=oJDDI0APT7lrtjWmzYJRDHLGn3Vhbn2MdFSRYvFBUpY,436
|
17
16
|
runnable/extensions/catalog/k8s_pvc/integration.py,sha256=OfrHbNFN8sR-wsVa4os3ajmWJFSd5H4KOHGVAmjRZTQ,1850
|
18
|
-
runnable/extensions/executor/__init__.py,sha256=
|
17
|
+
runnable/extensions/executor/__init__.py,sha256=FTXtI_etk_eyKRIDpCiTrYfV5yCkfJMl-cuXVDzEqY8,26649
|
19
18
|
runnable/extensions/executor/argo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
-
runnable/extensions/executor/argo/implementation.py,sha256=
|
19
|
+
runnable/extensions/executor/argo/implementation.py,sha256=_BfxCe742S6uV-7PuQ53KjzwY-8Rq-5y9txOXMYf20U,43670
|
21
20
|
runnable/extensions/executor/argo/specification.yaml,sha256=wXQcm2gOQYqy-IOQIhucohS32ZrHKCfGA5zZ0RraPYc,1276
|
22
21
|
runnable/extensions/executor/k8s_job/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
22
|
runnable/extensions/executor/k8s_job/implementation_FF.py,sha256=1IfVG1GRcJcVFzQ-WhkJsmzdJuj51QMxXylY9UrWM0U,10259
|
24
23
|
runnable/extensions/executor/k8s_job/integration_FF.py,sha256=pG6HKhPMgCRIgu1PAnBvsfJQE1FxcjuSiC2I-Hn5sWo,2165
|
25
24
|
runnable/extensions/executor/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
|
-
runnable/extensions/executor/local/implementation.py,sha256=
|
25
|
+
runnable/extensions/executor/local/implementation.py,sha256=e8Tzv-FgQmJeUXVut96jeNERTR83JVG_zkQZMEjCVAs,2469
|
27
26
|
runnable/extensions/executor/local_container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
27
|
runnable/extensions/executor/local_container/implementation.py,sha256=6kYMgdgE5JxZkVAidxsBSpqkHvyKMfEctgZWSZQEpXA,13979
|
29
28
|
runnable/extensions/executor/mocked/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
29
|
runnable/extensions/executor/mocked/implementation.py,sha256=ChdUyUsiXXjG_v80d0uLp76Nz4jqqGEry36gs9gNn9k,5082
|
31
30
|
runnable/extensions/executor/retry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
31
|
runnable/extensions/executor/retry/implementation.py,sha256=ZBSYpxSiAIt-SXPD-qIPP-MMo8b7sQ6UKOTJemAjXlI,6625
|
33
|
-
runnable/extensions/
|
34
|
-
runnable/extensions/experiment_tracker/mlflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
|
-
runnable/extensions/experiment_tracker/mlflow/implementation.py,sha256=sc1Wm1LCf7wBX0BYVx3YVdwsR72AE0qIrzl7cEfIl58,3045
|
36
|
-
runnable/extensions/nodes.py,sha256=Em-vs21ZfhyvFh-s6NZVhcUydSrX_mY8mb8-NUAO_w8,29883
|
32
|
+
runnable/extensions/nodes.py,sha256=5soHRhfT8FY2vnQa4kvRqeVphTq_t-GSw-ExNZfgB30,31965
|
37
33
|
runnable/extensions/run_log_store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
34
|
runnable/extensions/run_log_store/chunked_file_system/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
-
runnable/extensions/run_log_store/chunked_file_system/implementation.py,sha256=
|
35
|
+
runnable/extensions/run_log_store/chunked_file_system/implementation.py,sha256=wtOeREr9QyIuMHLCT7o_eDCJVCDsBvwmk89kos3dhfQ,3326
|
40
36
|
runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
37
|
runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py,sha256=iGzy-s1eT_kAJP7XgzDLmEMOGrBLvACIiGE_wM62jGE,579
|
42
38
|
runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py,sha256=atzdTy5HJ-bZsd6AzDP8kYRI1TshKxviBKeqY359TUs,1979
|
43
39
|
runnable/extensions/run_log_store/db/implementation_FF.py,sha256=oEiG5ASWYYbwlBbnryKarQENB-L_yOsnZahbj2U0GdQ,5155
|
44
40
|
runnable/extensions/run_log_store/db/integration_FF.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
41
|
runnable/extensions/run_log_store/file_system/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
-
runnable/extensions/run_log_store/file_system/implementation.py,sha256=
|
42
|
+
runnable/extensions/run_log_store/file_system/implementation.py,sha256=WxxfGCaDAB5zHMM3zv9aeDwXZ4DhtyzjXOjfjvyDoZ4,4288
|
47
43
|
runnable/extensions/run_log_store/generic_chunked.py,sha256=rcY5f-MIYUUiM5iQnDHICOh7cKiOUSCeaxcBG9_fz-U,19390
|
48
44
|
runnable/extensions/run_log_store/k8s_pvc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
45
|
runnable/extensions/run_log_store/k8s_pvc/implementation.py,sha256=tLgXy9HUB_vlFVQ0Itk6PpNU3GlCOILN4vA3fm80jXI,542
|
@@ -53,18 +49,18 @@ runnable/extensions/secrets/dotenv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
|
|
53
49
|
runnable/extensions/secrets/dotenv/implementation.py,sha256=3J5pofWahdZbnwnETwpspE5-PKyvmZF_vkfwA1X_bkA,3365
|
54
50
|
runnable/extensions/secrets/env_secrets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
51
|
runnable/extensions/secrets/env_secrets/implementation.py,sha256=5XiHdJvIr0-jkl4fGfEf26UsgE5Q2Z4oCc0RwjlJdJA,1236
|
56
|
-
runnable/graph.py,sha256=
|
57
|
-
runnable/integration.py,sha256=
|
52
|
+
runnable/graph.py,sha256=18IpGYw5kgHP32m12WwXscx-kG5Kx-AuWS1LFbMfBLg,16202
|
53
|
+
runnable/integration.py,sha256=eb9qJVZR7Ehg0N1UnGPuyjJvoA-xQ1-xP7AlZHUXHqM,6705
|
58
54
|
runnable/names.py,sha256=vn92Kv9ANROYSZX6Z4z1v_WA3WiEdIYmG6KEStBFZug,8134
|
59
|
-
runnable/nodes.py,sha256
|
60
|
-
runnable/parameters.py,sha256
|
55
|
+
runnable/nodes.py,sha256=UqR-bJx0Hi7uLSUw_saB7VsNdFh3POKtdgsEPsasHfE,16576
|
56
|
+
runnable/parameters.py,sha256=KGGW8_uoIK2hd3EwzzBmoHBOrai3fh-SESNPpJRTfj4,5161
|
61
57
|
runnable/pickler.py,sha256=5SDNf0miMUJ3ZauhQdzwk8_t-9jeOqaTjP5bvRnu9sU,2685
|
62
|
-
runnable/sdk.py,sha256=
|
58
|
+
runnable/sdk.py,sha256=hx29PEDYjJIWaTZp3ZhyLDwuulG1HIQ2q7A4HVE1WkM,26998
|
63
59
|
runnable/secrets.py,sha256=dakb7WRloWVo-KpQp6Vy4rwFdGi58BTlT4OifQY106I,2324
|
64
|
-
runnable/tasks.py,sha256=
|
65
|
-
runnable/utils.py,sha256=
|
66
|
-
runnable-0.
|
67
|
-
runnable-0.
|
68
|
-
runnable-0.
|
69
|
-
runnable-0.
|
70
|
-
runnable-0.
|
60
|
+
runnable/tasks.py,sha256=T8vVLo-yWHanrXKHFJvNdjQXKuWmJ13_-lxZzmf1mQM,18908
|
61
|
+
runnable/utils.py,sha256=okZFGbJWqStl5Rq5vLhNUQZDv_vhcT58bq9MDrTVxhc,19449
|
62
|
+
runnable-0.11.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
63
|
+
runnable-0.11.0.dist-info/METADATA,sha256=M7YR0Y_j7zMnXCKHFWGk4mlWKYZTdkOyZa3M0v3NokI,17063
|
64
|
+
runnable-0.11.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
65
|
+
runnable-0.11.0.dist-info/entry_points.txt,sha256=Wy-dimdD2REO2a36Ri84fqGqA5iwGy2RIbdgRNtCNdM,1540
|
66
|
+
runnable-0.11.0.dist-info/RECORD,,
|
@@ -12,10 +12,6 @@ local-container=runnable.extensions.executor.local_container.implementation:Loca
|
|
12
12
|
mocked=runnable.extensions.executor.mocked.implementation:MockedExecutor
|
13
13
|
retry=runnable.extensions.executor.retry.implementation:RetryExecutor
|
14
14
|
|
15
|
-
[experiment_tracker]
|
16
|
-
do-nothing=runnable.experiment_tracker:DoNothingTracker
|
17
|
-
mlflow=runnable.extensions.experiment_tracker.mlflow.implementation:MLFlowExperimentTracker
|
18
|
-
|
19
15
|
[nodes]
|
20
16
|
dag=runnable.extensions.nodes:DagNode
|
21
17
|
fail=runnable.extensions.nodes:FailNode
|
runnable/experiment_tracker.py
DELETED
@@ -1,139 +0,0 @@
|
|
1
|
-
import contextlib
|
2
|
-
import json
|
3
|
-
import logging
|
4
|
-
import os
|
5
|
-
from abc import ABC, abstractmethod
|
6
|
-
from collections import defaultdict
|
7
|
-
from typing import Any, ContextManager, Dict, Tuple, Union
|
8
|
-
|
9
|
-
from pydantic import BaseModel, ConfigDict
|
10
|
-
|
11
|
-
import runnable.context as context
|
12
|
-
from runnable import defaults
|
13
|
-
from runnable.utils import remove_prefix
|
14
|
-
|
15
|
-
logger = logging.getLogger(defaults.LOGGER_NAME)
|
16
|
-
|
17
|
-
|
18
|
-
def retrieve_step_details(key: str) -> Tuple[str, int]:
|
19
|
-
key = remove_prefix(key, defaults.TRACK_PREFIX)
|
20
|
-
data = key.split(defaults.STEP_INDICATOR)
|
21
|
-
|
22
|
-
key = data[0].lower()
|
23
|
-
step = 0
|
24
|
-
|
25
|
-
if len(data) > 1:
|
26
|
-
step = int(data[1])
|
27
|
-
|
28
|
-
return key, step
|
29
|
-
|
30
|
-
|
31
|
-
def get_tracked_data() -> Dict[str, Any]:
|
32
|
-
tracked_data: Dict[str, Any] = defaultdict(dict)
|
33
|
-
for env_var, value in os.environ.items():
|
34
|
-
if env_var.startswith(defaults.TRACK_PREFIX):
|
35
|
-
key, step = retrieve_step_details(env_var)
|
36
|
-
|
37
|
-
# print(value, type(value))
|
38
|
-
try:
|
39
|
-
value = json.loads(value)
|
40
|
-
except json.decoder.JSONDecodeError:
|
41
|
-
logger.warning(f"Tracker {key} could not be JSON decoded, adding the literal value")
|
42
|
-
|
43
|
-
tracked_data[key][step] = value
|
44
|
-
del os.environ[env_var]
|
45
|
-
|
46
|
-
for key, value in tracked_data.items():
|
47
|
-
if len(value) == 1:
|
48
|
-
tracked_data[key] = value[0]
|
49
|
-
|
50
|
-
return tracked_data
|
51
|
-
|
52
|
-
|
53
|
-
# --8<-- [start:docs]
|
54
|
-
|
55
|
-
|
56
|
-
class BaseExperimentTracker(ABC, BaseModel):
|
57
|
-
"""
|
58
|
-
Base Experiment tracker class definition.
|
59
|
-
"""
|
60
|
-
|
61
|
-
service_name: str = ""
|
62
|
-
service_type: str = "experiment_tracker"
|
63
|
-
|
64
|
-
@property
|
65
|
-
def _context(self):
|
66
|
-
return context.run_context
|
67
|
-
|
68
|
-
model_config = ConfigDict(extra="forbid")
|
69
|
-
|
70
|
-
@property
|
71
|
-
def client_context(self) -> ContextManager:
|
72
|
-
"""
|
73
|
-
Returns the client context.
|
74
|
-
"""
|
75
|
-
return contextlib.nullcontext()
|
76
|
-
|
77
|
-
def publish_data(self, tracked_data: Dict[str, Any]):
|
78
|
-
for key, value in tracked_data.items():
|
79
|
-
if isinstance(value, dict):
|
80
|
-
for key2, value2 in value.items():
|
81
|
-
self.log_metric(key, value2, step=key2)
|
82
|
-
continue
|
83
|
-
self.log_metric(key, value)
|
84
|
-
|
85
|
-
@abstractmethod
|
86
|
-
def log_metric(self, key: str, value: Union[int, float], step: int = 0):
|
87
|
-
"""
|
88
|
-
Sets the metric in the experiment tracking.
|
89
|
-
|
90
|
-
Args:
|
91
|
-
key (str): The key against you want to store the value
|
92
|
-
value (float): The value of the metric
|
93
|
-
step (int): Optional step at which it was recorded
|
94
|
-
|
95
|
-
Raises:
|
96
|
-
NotImplementedError: Base class, hence not implemented
|
97
|
-
"""
|
98
|
-
raise NotImplementedError
|
99
|
-
|
100
|
-
@abstractmethod
|
101
|
-
def log_parameter(self, key: str, value: Any):
|
102
|
-
"""
|
103
|
-
Logs a parameter in the experiment tracking.
|
104
|
-
|
105
|
-
Args:
|
106
|
-
key (str): The key against you want to store the value
|
107
|
-
value (any): The value of the metric
|
108
|
-
|
109
|
-
Raises:
|
110
|
-
NotImplementedError: Base class, hence not implemented
|
111
|
-
"""
|
112
|
-
pass
|
113
|
-
|
114
|
-
|
115
|
-
# --8<-- [end:docs]
|
116
|
-
|
117
|
-
|
118
|
-
class DoNothingTracker(BaseExperimentTracker):
|
119
|
-
"""
|
120
|
-
A Do nothing tracker
|
121
|
-
"""
|
122
|
-
|
123
|
-
service_name: str = "do-nothing"
|
124
|
-
|
125
|
-
def log_metric(self, key: str, value: Union[int, float], step: int = 0):
|
126
|
-
"""
|
127
|
-
Sets the metric in the experiment tracking.
|
128
|
-
|
129
|
-
Args:
|
130
|
-
key (str): The key against you want to store the value
|
131
|
-
value (float): The value of the metric
|
132
|
-
"""
|
133
|
-
...
|
134
|
-
|
135
|
-
def log_parameter(self, key: str, value: Any):
|
136
|
-
"""
|
137
|
-
Since this is a Do nothing tracker, we don't need to log anything.
|
138
|
-
"""
|
139
|
-
...
|
File without changes
|
File without changes
|
@@ -1,94 +0,0 @@
|
|
1
|
-
import functools
|
2
|
-
import logging
|
3
|
-
from typing import Any, Union
|
4
|
-
|
5
|
-
from pydantic import ConfigDict, PrivateAttr
|
6
|
-
|
7
|
-
from runnable import defaults
|
8
|
-
from runnable.experiment_tracker import BaseExperimentTracker
|
9
|
-
|
10
|
-
logger = logging.getLogger(defaults.NAME)
|
11
|
-
|
12
|
-
|
13
|
-
class MLFlowExperimentTracker(BaseExperimentTracker):
|
14
|
-
"""
|
15
|
-
A MLFlow experiment tracker.
|
16
|
-
|
17
|
-
TODO: Need to set up credentials from secrets
|
18
|
-
"""
|
19
|
-
|
20
|
-
service_name: str = "mlflow"
|
21
|
-
|
22
|
-
server_url: str
|
23
|
-
autolog: bool = False
|
24
|
-
|
25
|
-
_default_experiment_name: str = PrivateAttr(default="Default")
|
26
|
-
_active_run_id: str = PrivateAttr(default="")
|
27
|
-
_client: Any = PrivateAttr(default=None)
|
28
|
-
|
29
|
-
model_config = ConfigDict(extra="forbid")
|
30
|
-
|
31
|
-
def model_post_init(self, __context: Any) -> None:
|
32
|
-
try:
|
33
|
-
import mlflow
|
34
|
-
except ImportError:
|
35
|
-
raise Exception("You need to install mlflow to use MLFlowExperimentTracker.")
|
36
|
-
|
37
|
-
self._client = mlflow
|
38
|
-
|
39
|
-
self._client.set_tracking_uri(self.server_url)
|
40
|
-
|
41
|
-
if self.autolog:
|
42
|
-
self._client.autolog(log_models=False)
|
43
|
-
|
44
|
-
@functools.cached_property
|
45
|
-
def experiment_id(self):
|
46
|
-
experiment_name = self._default_experiment_name
|
47
|
-
|
48
|
-
# If a tag is provided, we should create that as our experiment
|
49
|
-
if self._context.tag:
|
50
|
-
experiment_name = self._context.tag
|
51
|
-
|
52
|
-
experiment = self._client.get_experiment_by_name(experiment_name)
|
53
|
-
if not experiment:
|
54
|
-
# Create the experiment and get it.
|
55
|
-
experiment = self._client.create_experiment(experiment_name)
|
56
|
-
experiment = self._client.get_experiment(experiment)
|
57
|
-
|
58
|
-
return experiment.experiment_id
|
59
|
-
|
60
|
-
@functools.cached_property
|
61
|
-
def run_name(self):
|
62
|
-
return self._context.run_id
|
63
|
-
|
64
|
-
@property
|
65
|
-
def client_context(self):
|
66
|
-
if self._active_run_id:
|
67
|
-
return self._client.start_run(
|
68
|
-
run_id=self._active_run_id, experiment_id=self.experiment_id, run_name=self.run_name
|
69
|
-
)
|
70
|
-
|
71
|
-
active_run = self._client.start_run(run_name=self.run_name, experiment_id=self.experiment_id)
|
72
|
-
self._active_run_id = active_run.info.run_id
|
73
|
-
return active_run
|
74
|
-
|
75
|
-
def log_metric(self, key: str, value: Union[int, float], step: int = 0):
|
76
|
-
"""
|
77
|
-
Sets the metric in the experiment tracking.
|
78
|
-
|
79
|
-
Args:
|
80
|
-
key (str): The key against you want to store the value
|
81
|
-
value (Any): The value of the metric
|
82
|
-
"""
|
83
|
-
if not isinstance(value, float) or isinstance(value, int):
|
84
|
-
msg = f"Only float/int values are accepted as metrics. Setting the metric {key} as parameter {key}_{step}"
|
85
|
-
logger.warning(msg)
|
86
|
-
self.log_parameter(key=key, value=value, step=step)
|
87
|
-
return
|
88
|
-
|
89
|
-
with self.client_context as _:
|
90
|
-
self._client.log_metric(key, float(value), step=step or None)
|
91
|
-
|
92
|
-
def log_parameter(self, key: str, value: Any, step: int = 0):
|
93
|
-
with self.client_context as _:
|
94
|
-
self._client.log_param(key + f"_{str(step)}", value)
|
File without changes
|
File without changes
|