Skip to content

Commit ddaa79d

Browse files
committed
Handle kwargs when calling methods in the component view.
1 parent 9c6a0b7 commit ddaa79d

File tree

6 files changed

+61
-26
lines changed

6 files changed

+61
-26
lines changed

django_unicorn/call_method_parser.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,28 +141,27 @@ def parse_call_method_name(call_method_name: str) -> Tuple[str, List[Any]]:
141141
Tuple of method_name and a list of arguments.
142142
"""
143143

144-
dollar_func = False
144+
is_special_method = False
145+
args: List[Any] = []
146+
kwargs: Dict[str, Any] = {}
147+
method_name = call_method_name
145148

146149
# Deal with special methods that start with a "$"
147-
if call_method_name.startswith("$"):
148-
dollar_func = True
149-
call_method_name = call_method_name[1:]
150+
if method_name.startswith("$"):
151+
is_special_method = True
152+
method_name = method_name[1:]
150153

151-
tree = ast.parse(call_method_name, "eval")
152-
method_name = call_method_name
153-
args: List[Any] = []
154-
kwargs: Dict[str, Any] = {}
154+
tree = ast.parse(method_name, "eval")
155+
statement = tree.body[0].value
155156

156-
if tree.body and isinstance(tree.body[0].value, ast.Call):
157+
if tree.body and isinstance(statement, ast.Call):
157158
call = tree.body[0].value
158159
method_name = call.func.id
159160
args = [eval_value(arg) for arg in call.args]
160-
161-
# Not returned, but might be usable
162161
kwargs = {kw.arg: eval_value(kw.value) for kw in call.keywords}
163162

164163
# Add "$" back to special functions
165-
if dollar_func:
164+
if is_special_method:
166165
method_name = f"${method_name}"
167166

168167
return (method_name, args, kwargs)

django_unicorn/views.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,21 +225,28 @@ def _get_property_value(component: UnicornView, property_name: str) -> Any:
225225

226226

227227
@timed
228-
def _call_method_name(component: UnicornView, method_name: str, args: List[Any]) -> Any:
228+
def _call_method_name(
229+
component: UnicornView, method_name: str, args: List[Any], kwargs: Dict[str, Any]
230+
) -> Any:
229231
"""
230232
Calls the method name with parameters.
231233
232234
Args:
233235
param component: Component to call method on.
234236
param method_name: Method name to call.
235237
param args: List of arguments for the method.
238+
param kwargs: Dictionary of kwargs for the method.
236239
"""
237240

238241
if method_name is not None and hasattr(component, method_name):
239242
func = getattr(component, method_name)
240243

241-
if args:
242-
return func(*args)
244+
if args and kwargs:
245+
return func(*args, **kwargs)
246+
elif args:
247+
return func(*args, **kwargs)
248+
elif kwargs:
249+
return func(**kwargs)
243250
else:
244251
return func()
245252

@@ -420,7 +427,9 @@ def _process_component_request(
420427
validate_all_fields = True
421428
else:
422429
component.calling(method_name, args)
423-
return_data.value = _call_method_name(component, method_name, args)
430+
return_data.value = _call_method_name(
431+
component, method_name, args, kwargs
432+
)
424433
component.called(method_name, args)
425434
else:
426435
raise UnicornViewError(f"Unknown action_type '{action_type}'")

example/unicorn/templates/unicorn/text-inputs.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@
6161
<button unicorn:click="set_name('')">set_name('')</button>
6262
<button unicorn:click="set_name('blob 2')">set_name('blob 2')</button>
6363
<button unicorn:click="set_name('abc{}.def')">set_name('abc{}.def')</button>
64-
<button unicorn:click="name='human'">name='human'</button>
64+
<button unicorn:click="name='howdy'">name='human'</button>
65+
<button unicorn:click="set_name(name='test kwarg')">set_name(name='test kwarg')</button>
6566

6667
<br /><br />
6768
<button unicorn:click='set_name($event.target.value.trim())' value=' button value '>set_name($event.target.value.trim())</button>

tests/views/fake_components.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@ class FakeComponent(UnicornView):
1818
method_count = 0
1919
check = False
2020
nested = {"check": False, "another": {"bool": False}}
21-
method_param = ""
21+
method_arg = ""
2222

2323
def test_method(self):
2424
self.method_count += 1
2525

26-
def test_method_params(self, count):
26+
def test_method_args(self, count):
2727
self.method_count = count
2828

29-
def test_method_string_param(self, param):
29+
def test_method_string_arg(self, param):
3030
self.method_count += 1
31-
self.method_param = param
31+
self.method_arg = param
32+
33+
def test_method_kwargs(self, count=-1):
34+
self.method_count = count
3235

3336
def test_redirect(self):
3437
return redirect("/something-here")

tests/views/message/test_call_method.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,11 @@ def test_message_call_method_nested_toggle(client):
270270
assert body["data"].get("nested").get("check") == True
271271

272272

273-
def test_message_call_method_params(client):
273+
def test_message_call_method_args(client):
274274
data = {"method_count": 0}
275275
message = {
276276
"actionQueue": [
277-
{"payload": {"name": "test_method_params(3)"}, "type": "callMethod",}
277+
{"payload": {"name": "test_method_args(3)"}, "type": "callMethod",}
278278
],
279279
"data": data,
280280
"checksum": generate_checksum(orjson.dumps(data)),
@@ -293,6 +293,29 @@ def test_message_call_method_params(client):
293293
assert body["data"].get("method_count") == 3
294294

295295

296+
def test_message_call_method_kwargs(client):
297+
data = {"method_count": 0}
298+
message = {
299+
"actionQueue": [
300+
{"payload": {"name": "test_method_kwargs(count=99)"}, "type": "callMethod",}
301+
],
302+
"data": data,
303+
"checksum": generate_checksum(orjson.dumps(data)),
304+
"id": shortuuid.uuid()[:8],
305+
"epoch": time.time(),
306+
}
307+
308+
response = client.post(
309+
"/message/tests.views.fake_components.FakeComponent",
310+
message,
311+
content_type="application/json",
312+
)
313+
314+
body = orjson.loads(response.content)
315+
316+
assert body["data"].get("method_count") == 99
317+
318+
296319
def test_message_call_method_no_validation(client):
297320
data = {}
298321
message = {

tests/views/message/test_set_property.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ def test_nested_setter(client):
5252

5353

5454
def test_equal_sign(client):
55-
data = {"nested": {"check": False}, "method_param": ""}
55+
data = {"nested": {"check": False}, "method_arg": ""}
5656
message = {
5757
"actionQueue": [
5858
{
5959
"type": "callMethod",
60-
"payload": {"name": "test_method_string_param('does=thiswork?')"},
60+
"payload": {"name": "test_method_string_arg('does=thiswork?')"},
6161
},
6262
],
6363
"data": data,
@@ -69,4 +69,4 @@ def test_equal_sign(client):
6969
body = _post_message_and_get_body(client, message)
7070

7171
assert not body["errors"]
72-
assert body["data"]["method_param"] == "does=thiswork?"
72+
assert body["data"]["method_arg"] == "does=thiswork?"

0 commit comments

Comments
 (0)