runnable 0.10.0__py3-none-any.whl → 0.11.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|