runnable 0.37.0__py3-none-any.whl → 0.38.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/graph.py +1 -1
- runnable/parameters.py +119 -48
- {runnable-0.37.0.dist-info → runnable-0.38.0.dist-info}/METADATA +2 -4
- {runnable-0.37.0.dist-info → runnable-0.38.0.dist-info}/RECORD +7 -7
- {runnable-0.37.0.dist-info → runnable-0.38.0.dist-info}/WHEEL +0 -0
- {runnable-0.37.0.dist-info → runnable-0.38.0.dist-info}/entry_points.txt +0 -0
- {runnable-0.37.0.dist-info → runnable-0.38.0.dist-info}/licenses/LICENSE +0 -0
runnable/graph.py
CHANGED
@@ -329,7 +329,7 @@ def create_graph(dag_config: Dict[str, Any], internal_branch_name: str = "") ->
|
|
329
329
|
Returns:
|
330
330
|
Graph: The created graph object
|
331
331
|
"""
|
332
|
-
description: str = dag_config.get("description", None)
|
332
|
+
description: str | None = dag_config.get("description", None)
|
333
333
|
start_at: str = cast(
|
334
334
|
str, dag_config.get("start_at")
|
335
335
|
) # Let the start_at be relative to the graph
|
runnable/parameters.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
import argparse
|
1
2
|
import inspect
|
2
3
|
import json
|
3
4
|
import logging
|
4
5
|
import os
|
5
6
|
from typing import Any, Dict, Type
|
6
7
|
|
7
|
-
import pydantic
|
8
8
|
from pydantic import BaseModel, ConfigDict
|
9
9
|
from typing_extensions import Callable
|
10
10
|
|
@@ -48,6 +48,25 @@ def get_user_set_parameters(remove: bool = False) -> Dict[str, JsonParameter]:
|
|
48
48
|
return parameters
|
49
49
|
|
50
50
|
|
51
|
+
def return_json_parameters(params: Dict[str, Any]) -> Dict[str, Any]:
|
52
|
+
"""
|
53
|
+
Returns the parameters as a JSON serializable dictionary.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
params (dict): The parameters to serialize.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
dict: The JSON serializable dictionary.
|
60
|
+
"""
|
61
|
+
return_params = {}
|
62
|
+
for key, value in params.items():
|
63
|
+
if isinstance(value, ObjectParameter):
|
64
|
+
continue
|
65
|
+
|
66
|
+
return_params[key] = value.get_value()
|
67
|
+
return return_params
|
68
|
+
|
69
|
+
|
51
70
|
def filter_arguments_for_func(
|
52
71
|
func: Callable[..., Any],
|
53
72
|
params: Dict[str, Any],
|
@@ -55,8 +74,14 @@ def filter_arguments_for_func(
|
|
55
74
|
) -> Dict[str, Any]:
|
56
75
|
"""
|
57
76
|
Inspects the function to be called as part of the pipeline to find the arguments of the function.
|
58
|
-
Matches the function arguments to the parameters available either by
|
77
|
+
Matches the function arguments to the parameters available either by static parameters or by up stream steps.
|
59
78
|
|
79
|
+
The function "func" signature could be:
|
80
|
+
- def my_function(arg1: int, arg2: str, arg3: float):
|
81
|
+
- def my_function(arg1: int, arg2: str, arg3: float, **kwargs):
|
82
|
+
in this case, we would need to send in remaining keyword arguments as a dictionary.
|
83
|
+
- def my_function(arg1: int, arg2: str, arg3: float, args: argparse.Namespace):
|
84
|
+
In this case, we need to send the rest of the parameters as attributes of the args object.
|
60
85
|
|
61
86
|
Args:
|
62
87
|
func (Callable): The function to inspect
|
@@ -72,63 +97,109 @@ def filter_arguments_for_func(
|
|
72
97
|
params[key] = JsonParameter(kind="json", value=v)
|
73
98
|
|
74
99
|
bound_args = {}
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
for name, value in function_args.items():
|
79
|
-
if value.kind != inspect.Parameter.VAR_KEYWORD:
|
80
|
-
continue
|
81
|
-
# Found VAR_KEYWORD, we send back everything as found
|
82
|
-
for key, value in params.items():
|
83
|
-
bound_args[key] = params[key].get_value()
|
100
|
+
missing_required_args: list[str] = []
|
101
|
+
var_keyword_param = None
|
102
|
+
namespace_param = None
|
84
103
|
|
85
|
-
|
86
|
-
|
87
|
-
# Lets return what is asked for then!!
|
104
|
+
# First pass: Handle regular parameters and identify special parameters
|
88
105
|
for name, value in function_args.items():
|
89
106
|
# Ignore any *args
|
90
107
|
if value.kind == inspect.Parameter.VAR_POSITIONAL:
|
91
108
|
logger.warning(f"Ignoring parameter {name} as it is VAR_POSITIONAL")
|
92
109
|
continue
|
93
110
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# No default value is given in the function signature. error as parameter is required.
|
98
|
-
raise ValueError(
|
99
|
-
f"Parameter {name} is required for {func.__name__} but not provided"
|
100
|
-
)
|
101
|
-
# default value is given in the function signature, nothing further to do.
|
111
|
+
# Check for **kwargs parameter
|
112
|
+
if value.kind == inspect.Parameter.VAR_KEYWORD:
|
113
|
+
var_keyword_param = name
|
102
114
|
continue
|
103
115
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
pydantic._internal._model_construction.ModelMetaclass,
|
109
|
-
] and not isinstance(param_value, ObjectParameter):
|
110
|
-
# Even if the annotation is a pydantic model, it can be passed as an object parameter
|
111
|
-
# We try to cast it as a pydantic model if asked
|
112
|
-
named_param = params[name].get_value()
|
113
|
-
|
114
|
-
if not isinstance(named_param, dict):
|
115
|
-
# A case where the parameter is a one attribute model
|
116
|
-
named_param = {name: named_param}
|
117
|
-
|
118
|
-
bound_model = bind_args_for_pydantic_model(named_param, value.annotation)
|
119
|
-
bound_args[name] = bound_model
|
116
|
+
# Check for argparse.Namespace parameter
|
117
|
+
if value.annotation == argparse.Namespace:
|
118
|
+
namespace_param = name
|
119
|
+
continue
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
# Handle regular parameters
|
122
|
+
if name not in params:
|
123
|
+
if value.default != inspect.Parameter.empty:
|
124
|
+
# Default value is given in the function signature, we can use it
|
125
|
+
bound_args[name] = value.default
|
126
|
+
else:
|
127
|
+
# This is a required parameter that's missing
|
128
|
+
missing_required_args.append(name)
|
124
129
|
else:
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
130
|
+
# We have a parameter of this name, lets bind it
|
131
|
+
param_value = params[name]
|
132
|
+
|
133
|
+
if (
|
134
|
+
inspect.isclass(value.annotation)
|
135
|
+
and issubclass(value.annotation, BaseModel)
|
136
|
+
) and not isinstance(param_value, ObjectParameter):
|
137
|
+
# Even if the annotation is a pydantic model, it can be passed as an object parameter
|
138
|
+
# We try to cast it as a pydantic model if asked
|
139
|
+
named_param = params[name].get_value()
|
140
|
+
|
141
|
+
if not isinstance(named_param, dict):
|
142
|
+
# A case where the parameter is a one attribute model
|
143
|
+
named_param = {name: named_param}
|
144
|
+
|
145
|
+
bound_model = bind_args_for_pydantic_model(
|
146
|
+
named_param, value.annotation
|
147
|
+
)
|
148
|
+
bound_args[name] = bound_model
|
149
|
+
|
150
|
+
elif value.annotation in [str, int, float, bool] and callable(
|
151
|
+
value.annotation
|
152
|
+
):
|
153
|
+
# Cast it if its a primitive type. Ensure the type matches the annotation.
|
154
|
+
try:
|
155
|
+
bound_args[name] = value.annotation(params[name].get_value())
|
156
|
+
except (ValueError, TypeError) as e:
|
157
|
+
raise ValueError(
|
158
|
+
f"Cannot cast parameter '{name}' to {value.annotation.__name__}: {e}"
|
159
|
+
)
|
160
|
+
else:
|
161
|
+
# We do not know type of parameter, we send the value as found
|
162
|
+
bound_args[name] = params[name].get_value()
|
163
|
+
|
164
|
+
# Find extra parameters (parameters in params but not consumed by regular function parameters)
|
165
|
+
consumed_param_names = set(bound_args.keys()) | set(missing_required_args)
|
166
|
+
extra_params = {k: v for k, v in params.items() if k not in consumed_param_names}
|
167
|
+
|
168
|
+
# Second pass: Handle **kwargs and argparse.Namespace parameters
|
169
|
+
if var_keyword_param is not None:
|
170
|
+
# Function accepts **kwargs - add all extra parameters directly to bound_args
|
171
|
+
for param_name, param_value in extra_params.items():
|
172
|
+
bound_args[param_name] = param_value.get_value()
|
173
|
+
elif namespace_param is not None:
|
174
|
+
# Function accepts argparse.Namespace - create namespace with extra parameters
|
175
|
+
args_namespace = argparse.Namespace()
|
176
|
+
for param_name, param_value in extra_params.items():
|
177
|
+
setattr(args_namespace, param_name, param_value.get_value())
|
178
|
+
bound_args[namespace_param] = args_namespace
|
179
|
+
elif extra_params:
|
180
|
+
# Function doesn't accept **kwargs or namespace, but we have extra parameters
|
181
|
+
# This should only be an error if we also have missing required parameters
|
182
|
+
# or if the function truly can't handle the extra parameters
|
183
|
+
if missing_required_args:
|
184
|
+
# We have both missing required and extra parameters - this is an error
|
185
|
+
raise ValueError(
|
186
|
+
f"Function {func.__name__} has parameters {missing_required_args} that are not present in the parameters"
|
187
|
+
)
|
188
|
+
# If we only have extra parameters and no missing required ones, we just ignore the extras
|
189
|
+
# This allows for more flexible parameter passing
|
190
|
+
|
191
|
+
# Check for missing required parameters
|
192
|
+
if missing_required_args:
|
193
|
+
if var_keyword_param is None and namespace_param is None:
|
194
|
+
# No way to handle missing parameters
|
195
|
+
raise ValueError(
|
196
|
+
f"Function {func.__name__} has parameters {missing_required_args} that are not present in the parameters"
|
197
|
+
)
|
198
|
+
# If we have **kwargs or namespace, missing parameters might be handled there
|
199
|
+
# But if they're truly required (no default), we should still error
|
200
|
+
raise ValueError(
|
201
|
+
f"Function {func.__name__} has parameters {missing_required_args} that are not present in the parameters"
|
202
|
+
)
|
132
203
|
|
133
204
|
return bound_args
|
134
205
|
|
@@ -1,12 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: runnable
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.38.0
|
4
4
|
Summary: Add your description here
|
5
5
|
Author-email: "Vammi, Vijay" <vijay.vammi@astrazeneca.com>
|
6
6
|
License-File: LICENSE
|
7
7
|
Requires-Python: >=3.10
|
8
|
-
Requires-Dist: click-plugins>=1.1.1
|
9
|
-
Requires-Dist: click<=8.1.3
|
10
8
|
Requires-Dist: cloudpathlib>=0.20.0
|
11
9
|
Requires-Dist: dill>=0.3.9
|
12
10
|
Requires-Dist: pydantic>=2.10.3
|
@@ -15,7 +13,7 @@ Requires-Dist: rich>=13.9.4
|
|
15
13
|
Requires-Dist: ruamel-yaml>=0.18.6
|
16
14
|
Requires-Dist: setuptools>=75.6.0
|
17
15
|
Requires-Dist: stevedore>=5.4.0
|
18
|
-
Requires-Dist: typer>=0.
|
16
|
+
Requires-Dist: typer>=0.17.3
|
19
17
|
Provides-Extra: docker
|
20
18
|
Requires-Dist: docker>=7.1.0; extra == 'docker'
|
21
19
|
Provides-Extra: examples
|
@@ -56,17 +56,17 @@ runnable/defaults.py,sha256=4UYuShnjEyWP529UlFnubvkBpOcczKIdE4jEOhPBwl4,3076
|
|
56
56
|
runnable/entrypoints.py,sha256=46prgr3_FYtBMlRbUXIDSpgZUBgaxcdJAekXhgEIj7M,6578
|
57
57
|
runnable/exceptions.py,sha256=t5tSlYqe_EjU5liXu32yLLh_yrnXeFL93BuXfmQzV98,3268
|
58
58
|
runnable/executor.py,sha256=CwzHkeGVpocACZLzfFS94TzKeiaPLv4NtXtvT3eoocY,15222
|
59
|
-
runnable/graph.py,sha256=
|
59
|
+
runnable/graph.py,sha256=ukJo_sqtBRD_ZX7ULbd2GnvpToAwGHcAowXPcqKjC4Q,16543
|
60
60
|
runnable/names.py,sha256=A9ldUyULXuWjJ1MoXihHqlg-xeTVX-oWYTO5Ah0trmo,8128
|
61
61
|
runnable/nodes.py,sha256=JHBxJib7SSQXY51bLHBXUvb0DlNSLNvyqz3JNEDLt8c,16926
|
62
|
-
runnable/parameters.py,sha256=
|
62
|
+
runnable/parameters.py,sha256=HZW0bhAYxgMyvRZzUlwp29MVxmzoFU5ZoVJMJHnOcX8,8734
|
63
63
|
runnable/pickler.py,sha256=ydJ_eti_U1F4l-YacFp7BWm6g5vTn04UXye25S1HVok,2684
|
64
64
|
runnable/sdk.py,sha256=blLBWzXV2x7jxKQXWpjmeJ9k22jt5CKBQBqQpnt4agk,32587
|
65
65
|
runnable/secrets.py,sha256=4L_dBFxTgr8r_hHUD6RlZEtqaOHDRsFG5PXO5wlvMI0,2324
|
66
66
|
runnable/tasks.py,sha256=7yuoeG4ZqfxFUmN4mPS4i6kbQmzEpAwbPQweAUWY-ic,31366
|
67
67
|
runnable/utils.py,sha256=amHW3KR_NGTDysGHcSafhh5WJUX7GPBSxqdPyzAIhao,11350
|
68
|
-
runnable-0.
|
69
|
-
runnable-0.
|
70
|
-
runnable-0.
|
71
|
-
runnable-0.
|
72
|
-
runnable-0.
|
68
|
+
runnable-0.38.0.dist-info/METADATA,sha256=HbM0-0hNd9E3aI5BFrzlQiEdkxSdxkfLaTwhvAIkmd0,9983
|
69
|
+
runnable-0.38.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
70
|
+
runnable-0.38.0.dist-info/entry_points.txt,sha256=KkxihZ0LLEiwvFl7RquyqZ0tp2fJDIs7DgzHYDlmc3U,2018
|
71
|
+
runnable-0.38.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
72
|
+
runnable-0.38.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|