diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 736fd7c..dade737 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - python-version: [ '3.6', '3.7', '3.8', '3.9', '3.10' ] + python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] steps: - name: Checkout uses: actions/checkout@v2 @@ -32,4 +32,4 @@ jobs: - run: coverage xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 \ No newline at end of file + uses: codecov/codecov-action@v2 diff --git a/decorest/request.py b/decorest/request.py index 4afbfdb..277d665 100644 --- a/decorest/request.py +++ b/decorest/request.py @@ -72,7 +72,7 @@ def __init__(self, func: typing.Callable[..., self.rest_client = args[0] args_dict = dict_from_args(func, *args) - self.req_path = render_path(self.path_template, args_dict) + self.req_path = render_path(self.path_template, args_dict, kwargs) self.session = None if '__session' in self.kwargs: self.session = self.kwargs['__session'] @@ -85,12 +85,13 @@ def __init__(self, func: typing.Callable[..., # Merge query parameters from common values for all method # invocations with arguments provided in the method # arguments - query_parameters = self._merge_args(args_dict, func, 'query') - form_parameters = self._merge_args(args_dict, func, 'form') - multipart_parameters = self._merge_args(args_dict, func, 'multipart') + query_parameters = self._merge_args(args_dict, kwargs, func, 'query') + form_parameters = self._merge_args(args_dict, kwargs, func, 'form') + multipart_parameters = self._merge_args(args_dict, kwargs, func, + 'multipart') header_parameters = CaseInsensitiveDict( merge_dicts(get_header_decor(self.rest_client.__class__), - self._merge_args(args_dict, func, 'header'))) + self._merge_args(args_dict, kwargs, func, 'header'))) # Merge header parameters with default values, treat header # decorators with 2 params as default values only if they @@ -374,7 +375,7 @@ def _validate_decor(self, decor: str, kwargs: ArgsDict, raise TypeError("{} value must be an instance of {}".format( decor, cls.__name__)) - def _merge_args(self, args_dict: ArgsDict, + def _merge_args(self, args_dict: ArgsDict, kwargs: ArgsDict, func: typing.Callable[..., typing.Any], decor: str) \ -> ArgsDict: """ @@ -392,9 +393,14 @@ def _merge_args(self, args_dict: ArgsDict, parameters = {} if args_decor: for arg, value in args_decor.items(): - if (isinstance(value, str)) \ - and arg in args_dict.keys(): - parameters[value] = args_dict[arg] + if (isinstance(value, str)): + if arg in args_dict.keys(): + parameters[value] = args_dict[arg] + if arg in kwargs.keys(): + parameters[value] = kwargs[arg] + # Delete from kwargs so that we don't + # pass them on to requests/httpx + del kwargs[arg] else: parameters[arg] = value return parameters diff --git a/decorest/utils.py b/decorest/utils.py index e8bacda..7d0a1a0 100644 --- a/decorest/utils.py +++ b/decorest/utils.py @@ -82,17 +82,26 @@ def iteritems_lower(self) \ for (lkey, keyval) in self.__original.items()) -def render_path(path: str, args: ArgsDict) -> str: +def render_path(path: str, args: ArgsDict, kwargs: ArgsDict) -> str: """Render REST path from *args.""" - LOG.debug('RENDERING PATH FROM: %s, %s', path, args) + LOG.debug('RENDERING PATH FROM: %s, %s', path, args, kwargs) result = path matches = re.search(r'{([^}.]*)}', result) + used_kwargs = [] while matches: path_token = matches.group(1) - if path_token not in args: + if path_token in kwargs: + result = re.sub('{%s}' % path_token, str(kwargs[path_token]), + result) + matches = re.search(r'{([^}.]*)}', result) + used_kwargs.append(path_token) + elif path_token in args: + result = re.sub('{%s}' % path_token, str(args[path_token]), result) + matches = re.search(r'{([^}.]*)}', result) + else: raise ValueError("Missing argument %s in REST call." % path_token) - result = re.sub('{%s}' % path_token, str(args[path_token]), result) - matches = re.search(r'{([^}.]*)}', result) + for used_kwarg in used_kwargs: + del kwargs[used_kwarg] return result diff --git a/tests/petstore_test.py b/tests/petstore_test.py index 7b76fea..68d6d7d 100644 --- a/tests/petstore_test.py +++ b/tests/petstore_test.py @@ -161,3 +161,49 @@ def test_user_methods(client): with pytest.raises(HTTPErrorWrapper) as e: client.get_user('swagger') + + +@pytest.mark.parametrize("client", pytest_params) +def test_user_methods_with_kwargs_params(client): + + res = client.create_user({ + 'username': 'swagger', + 'firstName': 'Swagger', + 'lastName': 'Petstore', + 'email': 'swagger@example.com', + 'password': 'guess', + 'phone': '001-111-CALL-ME', + "userStatus": 0 + }) + + assert res is True + + # Mixed usage + res = client.login('swagger', password='petstore') + + assert res.decode("utf-8").startswith('logged in user session:') + + res = client.get_user(username='swagger') + + assert res['phone'] == '001-111-CALL-ME' + + client.update_user( + 'swagger', { + 'username': 'swagger', + 'firstName': 'Swagger', + 'lastName': 'Petstore', + 'email': 'swagger@example.com', + 'password': 'guess', + 'phone': '001-111-CALL-ME', + "userStatus": 0 + }) + + res = client.get_user(username='swagger') + + assert res['email'] == 'swagger@example.com' + assert res['password'] == 'guess' + + client.delete_user(username='swagger') + + with pytest.raises(HTTPErrorWrapper) as e: + client.get_user(username='swagger') diff --git a/tests/petstore_test_with_typing.py b/tests/petstore_test_with_typing.py index ca8b63b..bed95cd 100644 --- a/tests/petstore_test_with_typing.py +++ b/tests/petstore_test_with_typing.py @@ -151,3 +151,45 @@ def test_user_methods(client: PetstoreClientWithTyping) -> None: assert res['password'] == 'guess' client.delete_user('swagger') + + +def test_user_methods_with_kwargs_params( + client: PetstoreClientWithTyping) -> None: + + res = client.create_user({ + 'username': 'swagger', + 'firstName': 'Swagger', + 'lastName': 'Petstore', + 'email': 'swagger@example.com', + 'password': 'guess', + 'phone': '001-111-CALL-ME', + "userStatus": 0 + }) + + assert res is True + + res = client.login('swagger', password='petstore') + + assert res.decode("utf-8").startswith('logged in user session:') + + res = client.get_user(username='swagger') + + assert res['phone'] == '001-111-CALL-ME' + + client.update_user( + 123, { + 'username': 'swagger', + 'firstName': 'Swagger', + 'lastName': 'Petstore', + 'email': 'swagger@example.com', + 'password': 'guess', + 'phone': '001-111-CALL-ME', + "userStatus": 0 + }) + + res = client.get_user(username='swagger') + + assert res['email'] == 'swagger@example.com' + assert res['password'] == 'guess' + + client.delete_user(username='swagger')