Skip to content

Commit 93afed6

Browse files
committed
IDEV-2299: Improve implementation that handles positional args as well.
1 parent aa80663 commit 93afed6

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

domaintools/decorators.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import inspect
23

34
from typing import List, Union
45

@@ -24,8 +25,26 @@ def decorator(func):
2425
normalized_methods = [methods] if isinstance(methods, str) else methods
2526
func._api_methods = normalized_methods
2627

28+
# Get the signature of the original function ONCE
29+
sig = inspect.signature(func)
30+
2731
@functools.wraps(func)
2832
def wrapper(self, *args, **kwargs):
33+
34+
try:
35+
bound_args = sig.bind(*args, **kwargs)
36+
except TypeError:
37+
# If arguments don't match signature, let the actual func raise the error
38+
return func(*args, **kwargs)
39+
40+
arguments = bound_args.arguments
41+
42+
# Robustly find 'self' (it's usually the first argument in bound_args)
43+
# We look for the first value in arguments, or try to get 'self' explicitly.
44+
instance = arguments.get("self")
45+
if not instance and args:
46+
instance = args[0]
47+
2948
# Retrieve the Spec from the instance
3049
# We assume 'self' has a .specs attribute (like DocstringPatcher expects)
3150
spec = getattr(self, "specs", {}).get(spec_name)
@@ -43,14 +62,13 @@ def wrapper(self, *args, **kwargs):
4362
spec=spec,
4463
path=path,
4564
method=current_method,
46-
parameters=kwargs,
65+
parameters=arguments,
4766
)
4867
except ValueError as e:
49-
# Optional: Log the error or re-raise custom exception
5068
print(f"[Validation Error] {e}")
5169
raise e
5270

53-
# 5. Proceed with the original function call
71+
# Proceed with the original function call
5472
return func(*args, **kwargs)
5573

5674
# Copy tags to wrapper for the DocstringPatcher to find

tests/test_decorators.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,25 @@ def get_users():
195195

196196
assert result == "Ran Safe"
197197
mock_validate.assert_not_called()
198+
199+
def test_positional_arguments_are_mapped(self, mock_client):
200+
"""
201+
Test that passing arguments positionally (args) instead of via keywords (kwargs)
202+
still triggers validation correctly.
203+
"""
204+
205+
# Define function with explicit parameter names
206+
@api_endpoint(spec_name="v1", path="/users", methods="POST")
207+
def create_user(name=None, body=None):
208+
return "Success"
209+
210+
with patch("domaintools.request_validator.RequestValidator.validate") as mock_validate:
211+
# CALL POSITIONALLY: passing client and body as args
212+
# (Note: we pass mock_client manually because create_user is just a function here)
213+
create_user(mock_client, "test-name")
214+
215+
# Verify validator received the data mapped to 'body_data'
216+
mock_validate.assert_called_once()
217+
call_kwargs = mock_validate.call_args[1]
218+
219+
assert call_kwargs.get("parameters") == {"name": "test-name"}

0 commit comments

Comments
 (0)