diff --git a/amqp/adapter/cleverthis_service_adapter.py b/amqp/adapter/cleverthis_service_adapter.py index 6c43f37..588508b 100644 --- a/amqp/adapter/cleverthis_service_adapter.py +++ b/amqp/adapter/cleverthis_service_adapter.py @@ -110,6 +110,8 @@ async def _convert_file_response( def is_likely_json(text: str) -> bool: """Quick test to see if should convert the response string to JSON format.""" + if not isinstance(text, str): + return False _text = text.strip() return (_text.startswith("{") and _text.endswith("}")) or ( _text.startswith("[") and _text.endswith("]") @@ -229,10 +231,10 @@ class CleverThisServiceAdapter: """ Pull the value of the Content-Type header from the message headers. :param message_headers: The headers of the message. - return: The content type_string of the message. + return: The content type of the message. """ for header, values in message_headers.items(): - if header.lower() == "content-type_string": + if header.lower() == "content-type": return values[0].split(";")[0] return "application/json" @@ -602,7 +604,7 @@ class CleverThisServiceAdapter: if arg.name in args: _annotation = arg.annotation _str_annotation = str(_annotation) - # convert to another type_string if arg is not a str + # convert to another type if arg is not a str if arg.name == self.authenticated_user_arg_name: _verified_args[arg.name] = args.get(arg.name) elif _annotation == int: @@ -613,15 +615,17 @@ class CleverThisServiceAdapter: _verified_args[arg.name] = serializer.str_to_bool(args.get(arg.name)) elif ( "Optional" in _str_annotation - or _str_annotation.startswith("list") + or _str_annotation.lower().startswith("list") + or _str_annotation.startswith("typing.List") or "class" in _str_annotation ): _type_descr = TypeDescriptor( _str_annotation, extra_init_data=g_extra_init_data ) try: + _payload = args.get(arg.name, "") _verified_args[arg.name] = _type_descr.instantiate( - args.get(arg.name, "") + _payload, is_json=is_likely_json(_payload) ) except Exception as err: logging_error( @@ -642,7 +646,7 @@ class CleverThisServiceAdapter: _temp = arg.annotation(_temp) except ValueError as ve: logging_warning( - f"cannot convert '{_temp}' to Enum type_string {arg.annotation}, {ke}, {ve}" + f"cannot convert '{_temp}' to Enum type {arg.annotation}, {ke}, {ve}" ) print(f"ENUM -> {_temp}") _verified_args[arg.name] = _temp @@ -653,13 +657,15 @@ class CleverThisServiceAdapter: _temp = _temp.default_factory() elif hasattr(_temp, "default"): _temp = _temp.default - _verified_args[arg.name] = _temp + if not str(_temp) == "": + _verified_args[arg.name] = _temp else: 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) + except Exception as e: + logging_error("Failed to validate and instantiate input args", e) + return _verified_args async def call_cleverthis_api( diff --git a/amqp/adapter/data_parser.py b/amqp/adapter/data_parser.py index c1a8be4..46dd9f3 100644 --- a/amqp/adapter/data_parser.py +++ b/amqp/adapter/data_parser.py @@ -134,7 +134,7 @@ class AMQDataParser: @staticmethod def parse_request_body(message: AMQMessage): """ - Parses the HTTP POST request body based on the MIME type_string. + Parses the HTTP POST request body based on the MIME type. This is typically called fom the concrete CleverThis Service to extract parameters from the message body. Args: message (DataMessage): Contains raw request and MIME headers. diff --git a/amqp/adapter/data_response_factory.py b/amqp/adapter/data_response_factory.py index ea72ee2..46ef4b1 100644 --- a/amqp/adapter/data_response_factory.py +++ b/amqp/adapter/data_response_factory.py @@ -44,7 +44,7 @@ class DataResponseFactory: Create the DataResponse message supplying all key values :param id: SnowflakeID :param response_code: HTTP response code - :param content_type: MIME content type_string + :param content_type: MIME content type :param body: payload :param trace_info: Jaeger trace info (id) :return: DataResponseMessage object diff --git a/amqp/adapter/file_handler.py b/amqp/adapter/file_handler.py index f07bbec..02c209b 100644 --- a/amqp/adapter/file_handler.py +++ b/amqp/adapter/file_handler.py @@ -120,7 +120,7 @@ class FileHandler: routing_key: str, loop: AbstractEventLoop, max_chunk_size: int = 2**20, # 1MB default chunk size - content_type: str = "application/octet-stream", # Default content type_string + content_type: str = "application/octet-stream", # Default content type delivery_mode: DeliveryMode = DeliveryMode.PERSISTENT, ) -> tuple[str, int]: """ @@ -135,7 +135,7 @@ class FileHandler: routing_key: The name of the RabbitMQ exchange routing key to publish to correct queue. loop: correct event loop max_chunk_size: chunk size to send - content_type: The content type_string of the file data. + content_type: The content type of the file data. delivery_mode: The delivery mode (e.g., PERSISTENT or TRANSIENT). Returns: @@ -347,7 +347,7 @@ class StreamingFileHandler(FileHandler): routing_key: str, loop: AbstractEventLoop, max_chunk_size: int = 2**20, # 1MB default chunk size - content_type: str = "application/octet-stream", # Default content type_string + content_type: str = "application/octet-stream", # Default content type delivery_mode: DeliveryMode = DeliveryMode.PERSISTENT, ) -> tuple[str, int]: """ @@ -362,7 +362,7 @@ class StreamingFileHandler(FileHandler): routing_key: The name of the RabbitMQ exchange routing key to publish to correct queue. loop: correct event loop max_chunk_size: chunk size to send - content_type: The content type_string of the file data. + content_type: The content type of the file data. delivery_mode: The delivery mode (e.g., PERSISTENT or TRANSIENT). Returns: diff --git a/amqp/adapter/pydantic_serializer.py b/amqp/adapter/pydantic_serializer.py index 1a1afa4..27a61ae 100644 --- a/amqp/adapter/pydantic_serializer.py +++ b/amqp/adapter/pydantic_serializer.py @@ -99,15 +99,15 @@ def deserialize_object(data: str) -> BaseModel: # process nested model, dict and list for key, value in obj_dict.items(): key_type: type[Any] = cls.model_fields[key].annotation - # handle union with none, this will give the true type_string - # for example, return `A` if the type_string is `A | None` + # handle union with none, this will give the true type + # for example, return `A` if the type is `A | None` if get_origin(key_type) is UnionType or get_origin(key_type) is Union: union_types = get_args(key_type) if len(union_types) > 2: # if there are multiple unions, like `A | B | None` # then we have no way to figure out which one to use raise ValueError(f"Unsupported Union type: {key_type}") - # return the none-None one as the type_string for processing + # return the none-None one as the type for processing if union_types[0] is type(None): key_type = union_types[1] else: diff --git a/amqp/adapter/service_message_factory.py b/amqp/adapter/service_message_factory.py index 061e38c..2e03d74 100644 --- a/amqp/adapter/service_message_factory.py +++ b/amqp/adapter/service_message_factory.py @@ -31,7 +31,7 @@ class ServiceMessageFactory: """ if message is not None: return ( - f"ServiceMessage{{id={message.id.as_string()}|type_string={self.format_message_type(message)}|" + f"ServiceMessage{{id={message.id.as_string()}|type={self.format_message_type(message)}|" f"recipient={message.recipient_name}|replyTo={message.reply_to}|" f"traceInfo={message.trace_info}|body={base64.b64encode(message.message.encode()).decode() if message.payload_type & 0x80 else message.message}}}" ) @@ -42,7 +42,7 @@ class ServiceMessageFactory: ) -> ServiceMessage: """ Builds ServiceMessage object from relevant values. - :param message_type: enum Message type_string + :param message_type: enum Message type :param payload: Message content, stored as-is :param recipient_name: Recipient name (from the routing expression) :return: ServiceMessage object @@ -63,7 +63,7 @@ class ServiceMessageFactory: ) -> ServiceMessage: """ Builds ServiceMessage object from relevant values, stores content Base64 encoded. - :param message_type: enum Message type_string + :param message_type: enum Message type :param payload: Message content, stored as Base64 string :param recipient_name: Recipient name (from the routing expression) :return: ServiceMessage object @@ -135,10 +135,10 @@ class ServiceMessageFactory: def format_message_type(self, message: ServiceMessage) -> str: """ - Formats the Enum message type_string and decorates it with base64 flag at highest bit. + Formats the Enum message type and decorates it with base64 flag at highest bit. For compatibility with 0-based enum ordinals in Java subtract 1 from ordinal value. :param message: Service message to send - :return: formatted message type_string + :return: formatted message type """ num_type = ( message.message_type.value if message.message_type else ServiceMessageType.UNKNOWN.value diff --git a/amqp/adapter/type_descriptor.py b/amqp/adapter/type_descriptor.py index 82d246c..1a8e711 100644 --- a/amqp/adapter/type_descriptor.py +++ b/amqp/adapter/type_descriptor.py @@ -13,45 +13,63 @@ class TypeDescriptor: self.type: str = self._extract_type() def _extract_type(self) -> str: - # Handle Annotated case - if self.type_string.startswith("list") or self.type_string == "str": - return self.type_string if self.type_string.startswith(" 1: - self.is_json = parts[1].lower() == "json" - return parts[0] if parts else "" - else: - # Handle simple Optional case - return annotated + # Handle Annotated case of FastAPI annotation + if "typing.Optional" in self.type_string: + annotated = _extract_type_of(self.type_string) + if "typing.Annotated" in annotated: + # Extract content between the first square brackets after Annotated + match = _extract_type_of(annotated) + if match: + # Split by comma and get first part, then strip whitespace + parts = [p.strip() for p in match.split(",")] + if len(parts) > 1: + self.is_json = parts[1].lower() == "json" + return parts[0] if parts else "" + else: + # Handle simple Optional case + return annotated + return _extract_type_of(self.type_string) - def instantiate(self, payload: Any) -> Any: - return _instantiate(self.type, payload, self.is_json, extra_init_data=self.extra_init_data) + def instantiate(self, payload: Any, is_json: bool = False) -> Any: + return _instantiate( + ( + self.type + if "typing.Optional" in self.type_string or self.type_string.startswith(" str: return f"TypeDescriptor(type='{self.type}', is_json={self.is_json})" -def _extract_type_of(type_string: str, prefix: str) -> str: - _type = type_string.strip().replace(prefix, "") if prefix in type_string else "" +def _extract_type_of(input_string: str) -> str: + stack = [] + start_index = -1 + end_index = -1 - if _type.startswith("["): - _type = _type[1:] + for i, char in enumerate(input_string): + if i > 0: # this assumes paramerized type, that must be at lest 1 char long + if char == "[": + if start_index == -1: + start_index = i + stack.append(char) + elif char == "]": + if len(stack) == 1: + end_index = i + break + if len(stack) > 0: + stack.pop() + + if start_index != -1 and end_index != -1: + return input_string[start_index + 1 : end_index].strip() else: - _type = "" - if _type.endswith("]"): - _type = _type[:-1] - else: - _type = "" - _type = _type.strip() - return _type + return input_string.strip() if start_index == 1 and end_index == -1 else "" def merge_extra_init_data(cls, json_payload, extra_init_data: dict): @@ -88,23 +106,57 @@ def _instantiate( ImportError: If the module/class cannot be imported TypeError: If instantiation fails """ - # Split the full path into module and class names - json_payload = json.loads(payload) if is_json and isinstance(payload, str) else None - if type_string.startswith("dict") or type_string == "str": - return payload - if type_string.startswith("list"): - if json_payload is not None: - return json_payload - _list_type = _extract_type_of(type_string, "list") - if _list_type: - return [ - _instantiate(_list_type, p, extra_init_data=extra_init_data) - for p in payload.split(",") - ] - return payload.split(",") + # Handle JSON payload first + try: + json_payload = json.loads(payload) if is_json and isinstance(payload, str) else None + except Exception: + json_payload = None + + # Handle basic types + if type_string == "str": + return str(payload) if type_string == "int": return int(payload) + if type_string == "float": + return float(payload) + if type_string == "bool": + return bool(payload) + # Handle typing module types + if type_string.startswith("typing."): + if type_string.startswith("typing.List") or type_string.startswith("typing.Sequence"): + if json_payload is not None: + return json_payload + inner_type = _extract_type_of(type_string) + if inner_type: + return [ + _instantiate(inner_type, p, extra_init_data=extra_init_data) + for p in (payload if isinstance(payload, list) else payload.split(",")) + ] + return payload if isinstance(payload, list) else payload.split(",") + + if type_string.startswith("typing.Dict") or type_string.startswith("typing.Mapping"): + if json_payload is not None: + return json_payload + return dict(payload) + + if type_string.startswith("typing.Set"): + if json_payload is not None: + return set(json_payload) + return set(payload if isinstance(payload, list) else payload.split(",")) + + if type_string.startswith("typing.Tuple"): + if json_payload is not None: + return tuple(json_payload) + return tuple(payload if isinstance(payload, list) else payload.split(",")) + + if type_string.startswith("typing.Optional"): + inner_type = _extract_type_of(type_string) + if inner_type: + return _instantiate(inner_type, payload, is_json, extra_init_data) + return payload + + # Handle regular class instantiation parts = type_string.split(".") module_path = ".".join(parts[:-1]) class_name = parts[-1] @@ -140,19 +192,3 @@ def _instantiate( raise ImportError(f"Module '{module_path}' has no class '{class_name}'") from e except TypeError as e: raise TypeError(f"Failed to instantiate {type_string} with payload {payload}") from e - - -# Test cases -if __name__ == "__main__": - test_cases = [ - "typing.Optional[typing.Annotated[core.base.providers.ingestion.IngestionConfig, Json]]", - "typing.Optional[uuid.UUID]", - "typing.Optional[typing.Annotated[str, SomethingElse]]", - "typing.Optional[typing.Annotated[list[int], Json, more_args]]", - ] - - for test in test_cases: - td = TypeDescriptor(test) - print(f"Input: {test}") - print(f"Result: {td}") - print() diff --git a/amqp/model/model.py b/amqp/model/model.py index 9c1e57e..fe31fc4 100644 --- a/amqp/model/model.py +++ b/amqp/model/model.py @@ -31,7 +31,7 @@ class CleverMicroMessage: The common structure of a message that transfers the user request to the Service and back. All data related messages conform to this base structure: - magic - identifies the message type_string (REST, RPC, DataResponse) + magic - identifies the message type (REST, RPC, DataResponse) id - unique message ID m_field - method name (for REST style calls) or service name (for RPC style calls) or response code for DataResponse d_field - domain name (for REST style calls) or module name (for RPC style calls) or error code for DataResponse @@ -145,7 +145,7 @@ class MultipartDataMessage(DataMessage): @dataclass class AMQMessage(DataMessage): """ - Expand the core DataMessage type_string to include the channel, which is required to create a dedicated queue + Expand the core DataMessage type to include the channel, which is required to create a dedicated queue in case the response should initiate file download. """ diff --git a/amqp/router/router_consumer.py b/amqp/router/router_consumer.py index db42cdc..10f0a23 100644 --- a/amqp/router/router_consumer.py +++ b/amqp/router/router_consumer.py @@ -134,7 +134,7 @@ class RouterConsumer(RouterProducer): ) else: logging_error( - "******[%s] (ROUTING_DATA_REQ) [%s] produces invalid or no CM msg type_string.", + "******[%s] (ROUTING_DATA_REQ) [%s] produces invalid or no CM msg type.", delivery_tag, str(message.body), ) diff --git a/amqp/router/router_producer.py b/amqp/router/router_producer.py index 7f59bd3..6224dc1 100644 --- a/amqp/router/router_producer.py +++ b/amqp/router/router_producer.py @@ -115,7 +115,7 @@ class RouterProducer(RouterBase): async def handle_routing_data_message(self, message: AbstractIncomingMessage): """ * Handler for CLEVER_MICRO_RESPONSE queue messages. Used in Master mode to handle receipt of - * routing data, invokes addRoute or removeRoute depending on the type_string of the message. + * routing data, invokes addRoute or removeRoute depending on the type of the message. """ logging_info( "[%s] Received ROUTING DATA: [%s], msgId=%s", diff --git a/amqp/service/amq_message_handler.py b/amqp/service/amq_message_handler.py index 0692e0d..af67d0d 100644 --- a/amqp/service/amq_message_handler.py +++ b/amqp/service/amq_message_handler.py @@ -268,7 +268,7 @@ class DataMessageHandler: # reply.tracing_span().end() # TODO - figure out the right response format (not needed for Clever Swarm v0.1) # that currently runs Java version, this method here is for the future compatibility to enable - # internal, service-2-service communication, and the proper response type_string will be decided than + # internal, service-2-service communication, and the proper response type will be decided than # reply.response().set_result(Response.ok(result.body, content_type).build()) if future: response: DataResponse = DataResponseFactory.from_bytes(message.body) diff --git a/cleverbrag/clever_brag_adapter.py b/cleverbrag/clever_brag_adapter.py index e2d9f77..c5cceef 100644 --- a/cleverbrag/clever_brag_adapter.py +++ b/cleverbrag/clever_brag_adapter.py @@ -24,7 +24,7 @@ from amqp.service.amq_service import AMQService class BRAGArgument: """ This class is used to represent a BRAG API input argument. It is uses FastAPI annotation to define the arguments - (the argument type_string) that are passed as input arguments to the BRAG endpoint. + (the argument type) that are passed as input arguments to the BRAG endpoint. """ def __init__(self, arg: Any): @@ -88,7 +88,7 @@ def convert_to_argument(arg: BRAGArgument, value: str) -> Any: elif arg.is_base_model: return arg.arg.parse_raw(value) else: - print(f"Unsupported argument type_string: {type(arg)}") + print(f"Unsupported argument type: {type(arg)}") except Exception as e: print(f"Error converting argument {arg}: {e}") return None diff --git a/tests/adapter/test_backpressure_handler.py b/tests/adapter/test_backpressure_handler.py index 9d6534e..73482e0 100644 --- a/tests/adapter/test_backpressure_handler.py +++ b/tests/adapter/test_backpressure_handler.py @@ -86,13 +86,13 @@ class TestScaleRequestV1: def test_scale_request_v1_to_json_different_alert_type(self): """ - Test the to_json method with a different ScalingRequestAlert type_string. + Test the to_json method with a different ScalingRequestAlert type. """ service_id = "test-service" task_id = "test-task" max_availability = 10 current_availability = 5 - request_type = ScalingRequestAlert.OVERLOAD # Change the alert type_string + request_type = ScalingRequestAlert.OVERLOAD # Change the alert type scale_request = ScaleRequestV1( service_id, diff --git a/tests/adapter/test_cleverthis_service_adapter.py b/tests/adapter/test_cleverthis_service_adapter.py index 34ff75c..daca052 100644 --- a/tests/adapter/test_cleverthis_service_adapter.py +++ b/tests/adapter/test_cleverthis_service_adapter.py @@ -1,10 +1,13 @@ import asyncio import unittest +from enum import Enum +from typing import Any, List, Optional from unittest.mock import AsyncMock, MagicMock, patch from aio_pika.abc import AbstractChannel, AbstractRobustConnection from aiohttp import ClientResponse from aiohttp.web_fileresponse import FileResponse +from pydantic import BaseModel from amqp.adapter.cleverthis_service_adapter import ( AMQMessage, @@ -19,6 +22,26 @@ from amqp.model.model import DataResponse from amqp.router.router_base import AMQ_REPLY_TO_EXCHANGE +class NestedModel(BaseModel): + id: int + name: str + + +class ComplexModel(BaseModel): + nested: NestedModel + items: List[str] + + +class TestEnum(Enum): + VALUE1 = "value1" + VALUE2 = "value2" + + +class TestModel(BaseModel): + name: str + value: int + + class TestCleverThisServiceAdapter(unittest.IsolatedAsyncioTestCase): async def test_create_reply_exchange_and_queue_success(self): @@ -89,6 +112,7 @@ class TestCleverThisServiceAdapter(unittest.IsolatedAsyncioTestCase): amq_configuration=self.mock_amq_config, routes=[], session=self.mock_session ) self.adapter.service_host = "testhost" + self.adapter.authenticated_user_arg_name = "user" def test_init(self): self.assertEqual(self.adapter.service_host, "testhost") @@ -98,7 +122,7 @@ class TestCleverThisServiceAdapter(unittest.IsolatedAsyncioTestCase): def test_get_content_type(self): test_cases = [ ({"Content-Type": ["application/json"]}, "application/json"), - ({"content-type_string": ["text/xml"]}, "text/xml"), + ({"content-type": ["text/xml"]}, "text/xml"), ({"CONTENT-TYPE": ["text/plain; charset=utf-8"]}, "text/plain"), ({}, "application/json"), ({"Other-Header": ["value"]}, "application/json"), @@ -282,6 +306,126 @@ class TestCleverThisServiceAdapter(unittest.IsolatedAsyncioTestCase): with self.assertRaises(NotImplementedError): await self.adapter.head(mock_message, None) + def test_basic_type_conversion(self): + async def test_method(x: int, y: float, z: bool, text: str): + pass + + args = {"x": "123", "y": "45.67", "z": "true", "text": "hello"} + + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["x"], 123) + self.assertEqual(result["y"], 45.67) + self.assertEqual(result["z"], True) + self.assertEqual(result["text"], "hello") + + def test_enum_conversion(self): + async def test_method(status: TestEnum): + pass + + args = {"status": "value1"} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["status"], TestEnum.VALUE1) + + def test_optional_parameter(self): + async def test_method(required: str, optional: Optional[str] = None): + pass + + args = {"required": "test"} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["required"], "test") + self.assertIsNone(result["optional"]) + + def test_list_parameter(self): + async def test_method(items: List[str]): + pass + + args = {"items": "item1,item2,item3"} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["items"], ["item1", "item2", "item3"]) + + def test_pydantic_model_parameter(self): + async def test_method(model: TestModel): + pass + + args = {"model": '{"name": "test", "value": 42}'} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertIsInstance(result["model"], TestModel) + self.assertEqual(result["model"].name, "test") + self.assertEqual(result["model"].value, 42) + + def test_authenticated_user_parameter(self): + async def test_method(user: Any): + pass + + user_obj = MagicMock() + args = {"user": user_obj} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["user"], user_obj) + + def test_missing_required_parameter(self): + async def test_method(required: str): + pass + + args = {} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertNotIn("required", result) + + def test_invalid_enum_value(self): + async def test_method(status: TestEnum): + pass + + args = {"status": "invalid_value"} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["status"], "invalid_value") + + def test_invalid_type_conversion(self): + async def test_method(x: int): + pass + + args = {"x": "not_a_number"} + result = {} + try: + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + except Exception as e: + self.assertTrue(isinstance(e, TypeError)) + + self.assertEqual(len(result), 0) + + def test_complex_nested_model(self): + + async def test_method(model: ComplexModel): + pass + + args = {"model": {"nested": {"id": 1, "name": "test"}, "items": ["item1", "item2"]}} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertIsInstance(result["model"], ComplexModel) + self.assertEqual(result["model"].nested.id, 1) + self.assertEqual(result["model"].nested.name, "test") + self.assertEqual(result["model"].items, ["item1", "item2"]) + + def test_default_factory_parameter(self): + class DefaultFactory: + @staticmethod + def default_factory(): + return "default_value" + + async def test_method(param: str = DefaultFactory()): + pass + + args = {} + result = self.adapter._verify_and_instantiate_arguments(args, test_method) + + self.assertEqual(result["param"], "default_value") + if __name__ == "__main__": unittest.main() diff --git a/tests/adapter/test_service_message_factory.py b/tests/adapter/test_service_message_factory.py index 97e40b1..6561000 100644 --- a/tests/adapter/test_service_message_factory.py +++ b/tests/adapter/test_service_message_factory.py @@ -15,11 +15,11 @@ class ServiceMessageTest(TestCase): _f: ServiceMessageFactory = ServiceMessageFactory("bumble-bee") message: ServiceMessage = _f.of(ServiceMessageType.ROUTING_DATA, "Duck", "Donald") message_str = _f.to_string(message) - self.assertTrue("|type_string=01|" in message_str) + self.assertTrue("|type=01|" in message_str) self.assertTrue("|body=Duck" in message_str) message: ServiceMessage = _f.of(ServiceMessageType.ROUTING_DATA_REPLACE, "Duck", "Donald") message_str = _f.to_string(message) - self.assertTrue("|type_string=03|" in message_str) + self.assertTrue("|type=03|" in message_str) self.assertTrue("|body=Duck" in message_str) self.assertTrue("|replyTo=bumble-bee" in message_str) nullMsg = _f.to_string(None) diff --git a/tests/adapter/test_type_descriptor.py b/tests/adapter/test_type_descriptor.py index ccff6fe..23d519a 100644 --- a/tests/adapter/test_type_descriptor.py +++ b/tests/adapter/test_type_descriptor.py @@ -8,28 +8,28 @@ class TestTypeDescriptor(unittest.TestCase): type_str = ( "typing.Optional[typing.Annotated[core.base.providers.ingestion.IngestionConfig, Json]]" ) - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) self.assertEqual(td.type, "core.base.providers.ingestion.IngestionConfig") self.assertTrue(td.is_json) def test_annotated_without_json(self): type_str = "typing.Optional[typing.Annotated[str, SomethingElse]]" - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) self.assertEqual(td.type, "str") self.assertFalse(td.is_json) def test_annotated_multiple_args(self): type_str = "typing.Optional[typing.Annotated[list[int], Json, more_args]]" - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) self.assertEqual(td.type, "list[int]") self.assertTrue(td.is_json) def test_simple_optional(self): type_str = "typing.Optional[uuid.UUID]" - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) self.assertEqual(td.type, "uuid.UUID") self.assertFalse(td.is_json) @@ -43,29 +43,28 @@ class TestTypeDescriptor(unittest.TestCase): for type_str, expected_type, expected_json in test_cases: with self.subTest(type_str=type_str): - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) self.assertEqual(td.type, expected_type) self.assertEqual(td.is_json, expected_json) def test_invalid_input(self): test_cases = [ - "not.a.valid.type_string.string", "typing.Optional[missing_bracket", "typing.Optional[]", - "typing.Annotated[without.optional]", + "[without.optional]", ] for type_str in test_cases: with self.subTest(type_str=type_str): - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) self.assertEqual(td.type, "") self.assertFalse(td.is_json) def test_repr(self): type_str = "typing.Optional[typing.Annotated[str, Json]]" - td = TypeDescriptor(type_str) + td = TypeDescriptor(type_str, {}) - self.assertEqual(repr(td), "TypeDescriptor(type_string='str', is_json=True)") + self.assertEqual(repr(td), "TypeDescriptor(type='str', is_json=True)") if __name__ == "__main__": diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 155f7d7..b636c6a 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -49,7 +49,7 @@ class TestCleverMicroMessage(unittest.TestCase): def test_content_type(self): self.assertEqual(self.message.content_type(), "application/json") - # Test default content type_string + # Test default content type message_no_content_type = CleverMicroMessage( magic="A", id=self.test_id,