diff --git a/amqp/adapter/cleverthis_service_adapter.py b/amqp/adapter/cleverthis_service_adapter.py index 9eab27c..6c43f37 100644 --- a/amqp/adapter/cleverthis_service_adapter.py +++ b/amqp/adapter/cleverthis_service_adapter.py @@ -495,12 +495,11 @@ class CleverThisServiceAdapter: if self.authenticated_user_arg_name in _signature_args.keys(): _args[self.authenticated_user_arg_name] = auth_user # If the path matches, call the corresponding function - return await CleverThisServiceAdapter.call_cleverthis_api( + return await self.call_cleverthis_api( message, _args, api_method=_route.endpoint, success_code=_route.status_code, - loop=self.loop, ) return DataResponseFactory.of( @@ -566,12 +565,11 @@ class CleverThisServiceAdapter: if self.authenticated_user_arg_name in _signature_args.keys(): _verified_args[self.authenticated_user_arg_name] = auth_user - return await CleverThisServiceAdapter.call_cleverthis_api( + return await self.call_cleverthis_api( message, _verified_args, api_method=_route.endpoint, success_code=_route.status_code if _route.status_code else 200, - loop=self.loop, ) return DataResponseFactory.of( @@ -582,28 +580,21 @@ class CleverThisServiceAdapter: message.trace_info(), ) - @staticmethod - async def call_cleverthis_api( - message: AMQMessage, + def _verify_and_instantiate_arguments( + self, args: Any, api_method: Callable[[Any, Any], Coroutine], - success_code: int, - loop: AbstractEventLoop | None = None, - ) -> DataResponse: + ) -> dict: """ - Handles POST and PUT requests, which typically contain possibly complex payload data, - passed as a dictionary. Invokes the provided API method with the given arguments and - authenticated user, and returns the response as DataResponse. + Check the provided arguments against the API method signature and convert the incoming string value + into correct expected input object. + The logic used FastAPI annotation and would use the value from 'default_factory' invocation (where defined) + or 'default' value, as defined in the FastAPI annotation, to create default value in case the object is not in + the input values provided by the caller. - :param message: DataMessage containing the details of the call and the payload - :param args: map of the arguments to pass to the API method - :param api_method: Callable - the method to invoke. Last parameter is the authenticated user - :param success_code: HTTP code to be returned when operation suceeds - - :return: DataResponse class """ + _verified_args = {} try: - _verified_args = {} _sig = inspect.signature(api_method) _required_args = list(_sig.parameters.values()) # _required_args = getattr(api_method, '__annotations__', {}) @@ -612,7 +603,9 @@ class CleverThisServiceAdapter: _annotation = arg.annotation _str_annotation = str(_annotation) # convert to another type_string if arg is not a str - if _annotation == int: + if arg.name == self.authenticated_user_arg_name: + _verified_args[arg.name] = args.get(arg.name) + elif _annotation == int: _verified_args[arg.name] = int(args.get(arg.name)) elif _annotation == float: _verified_args[arg.name] = float(args.get(arg.name)) @@ -656,7 +649,7 @@ class CleverThisServiceAdapter: else: if hasattr(arg, "default"): _temp = arg.default - if hasattr(_temp, "default_factory"): + if hasattr(_temp, "default_factory") and _temp.default_factory is not None: _temp = _temp.default_factory() elif hasattr(_temp, "default"): _temp = _temp.default @@ -665,6 +658,33 @@ class CleverThisServiceAdapter: logging_warning( f"{api_method.__name__}: Missing required argument: {arg.name}" ) + except Exception as unchecked: + logging_error("Failed to validate and instantiate input args", unchecked) + return _verified_args + + async def call_cleverthis_api( + self, + message: AMQMessage, + args: Any, + api_method: Callable[[Any, Any], Coroutine], + success_code: int, + ) -> DataResponse: + """ + Handles POST and PUT requests, which typically contain possibly complex payload data, + passed as a dictionary. Invokes the provided API method with the given arguments and + authenticated user, and returns the response as DataResponse. + + :param message: DataMessage containing the details of the call and the payload + :param args: map of the arguments to pass to the API method + :param api_method: Callable - the method to invoke. Last parameter is the authenticated user + :param success_code: HTTP code to be returned when operation suceeds + + :return: DataResponse class + """ + try: + _verified_args = self._verify_and_instantiate_arguments( + args=args, api_method=api_method + ) logging_info( "INVOKE ENDPOINT: {%s}, ARGS: {%s}", api_method.__name__, @@ -679,7 +699,7 @@ class CleverThisServiceAdapter: _result, message.connection, file_handler=StreamingFileHandler(), - loop=loop, + loop=self.loop, ), "application/octet-stream", ) diff --git a/amqp/adapter/data_parser.py b/amqp/adapter/data_parser.py index 8ae28fd..c1a8be4 100644 --- a/amqp/adapter/data_parser.py +++ b/amqp/adapter/data_parser.py @@ -149,7 +149,7 @@ class AMQDataParser: # Parse JSON if content_type == "application/json": try: - return json.loads(message.body()) + return json.loads(message.body() or "{}") except json.JSONDecodeError as e: logging_error(f"Invalid JSON: {e}") raise ValueError(f"Invalid JSON: {e}") diff --git a/pyproject.toml b/pyproject.toml index 36d8b7f..79de392 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "amq_adapter" -version = "0.2.32" +version = "0.2.33" description = "The Python implementation of the AMQ Adapter for CleverMicro" readme = "README.md" authors = [