From 1129eaf9fc9f4afd1e5628aaa0eda1809d8ea438 Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Tue, 10 Dec 2024 13:35:51 -0800 Subject: [PATCH 1/3] Add MQResponse model to define parameters used in https://github.com/NeonGeckoCom/neon_mq_connector/pull/101 --- neon_data_models/models/base/contexts.py | 4 +++ neon_data_models/models/base/mq.py | 38 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 neon_data_models/models/base/mq.py diff --git a/neon_data_models/models/base/contexts.py b/neon_data_models/models/base/contexts.py index 131cc71f..3836a855 100644 --- a/neon_data_models/models/base/contexts.py +++ b/neon_data_models/models/base/contexts.py @@ -137,6 +137,10 @@ def validate_inputs(cls, values): return values class MQContext(BaseModel): + """ + MQ context information that is included in all MQ message bodies and in + `Message.context['MQ']` for Message objects originating at MQ handlers. + """ routing_key: Optional[str] = None message_id: str = Field(default_factory=lambda: uuid4().hex, description="MQ unique message ID") diff --git a/neon_data_models/models/base/mq.py b/neon_data_models/models/base/mq.py new file mode 100644 index 00000000..4467508c --- /dev/null +++ b/neon_data_models/models/base/mq.py @@ -0,0 +1,38 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2024 Neongecko.com Inc. +# BSD-3 +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from typing import Optional +from pydantic import Field +from neon_data_models.models.base.contexts import MQContext + + +class MQResponse(MQContext): + is_final: bool = Field(default=True, alias="_is_final", + description='If False, another response message is ' + 'expected.') + part: Optional[int] = Field(default=None, alias="_part", + description='Index of this response message in ' + 'a series. (zero-indexed)') From e417d4a2b686884d1918c310809a2d572c94457c Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Mon, 24 Aug 2026 16:46:11 -0700 Subject: [PATCH 2/3] Update new module formatting and field description --- neon_data_models/models/base/mq.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/neon_data_models/models/base/mq.py b/neon_data_models/models/base/mq.py index 4467508c..97c7f4d3 100644 --- a/neon_data_models/models/base/mq.py +++ b/neon_data_models/models/base/mq.py @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System # All trademark and other rights reserved by their respective owners -# Copyright 2008-2024 Neongecko.com Inc. +# Copyright 2008-2026 Neongecko.com Inc. # BSD-3 # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -30,9 +30,14 @@ class MQResponse(MQContext): - is_final: bool = Field(default=True, alias="_is_final", - description='If False, another response message is ' - 'expected.') - part: Optional[int] = Field(default=None, alias="_part", - description='Index of this response message in ' - 'a series. (zero-indexed)') + is_final: bool = Field( + default=True, + alias="_is_final", + description="If False, another response message is expected.", + ) + part: Optional[int] = Field( + default=None, + alias="_part", + description="Index of this response message in a series " + "(zero-indexed). If `None`, message is not multipart.", + ) From c7f6212c7981cd30d697236040734c0e62bf2e84 Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Mon, 24 Aug 2026 17:03:55 -0700 Subject: [PATCH 3/3] Refactor to remove aliases to avoid confusion and add unit tests by Cursor --- neon_data_models/models/base/mq.py | 2 -- tests/models/test_base.py | 40 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/neon_data_models/models/base/mq.py b/neon_data_models/models/base/mq.py index 97c7f4d3..e558e4b0 100644 --- a/neon_data_models/models/base/mq.py +++ b/neon_data_models/models/base/mq.py @@ -32,12 +32,10 @@ class MQResponse(MQContext): is_final: bool = Field( default=True, - alias="_is_final", description="If False, another response message is expected.", ) part: Optional[int] = Field( default=None, - alias="_part", description="Index of this response message in a series " "(zero-indexed). If `None`, message is not multipart.", ) diff --git a/tests/models/test_base.py b/tests/models/test_base.py index f6305da1..14734721 100644 --- a/tests/models/test_base.py +++ b/tests/models/test_base.py @@ -372,6 +372,46 @@ def test_mq_context(self): minimal_ctx = MQContext(message_id="test_message_id_string") self.assertEqual(minimal_ctx, MQContext(**minimal_ctx.model_dump())) + def test_mq_response(self): + from neon_data_models.models.base import BaseModel + from neon_data_models.models.base.contexts import MQContext + from neon_data_models.models.base.mq import MQResponse + + self.assertTrue(issubclass(MQResponse, MQContext)) + self.assertTrue(issubclass(MQResponse, BaseModel)) + + default = MQResponse() + self.assertTrue(default.is_final) + self.assertIsNone(default.part) + self.assertIsInstance(default.message_id, str) + + serialized = default.model_dump() + self.assertEqual(serialized["is_final"], True) + self.assertIn("part", serialized) + self.assertIsNone(serialized["part"]) + self.assertNotIn("_is_final", serialized) + self.assertNotIn("_part", serialized) + + multipart = MQResponse(message_id="test_message_id", + is_final=False, + part=0) + self.assertFalse(multipart.is_final) + self.assertEqual(multipart.part, 0) + self.assertEqual(multipart.model_dump(), + {"message_id": "test_message_id", + "routing_key": None, + "is_final": False, + "part": 0}) + + final_part = MQResponse(message_id="test_message_id", + is_final=True, + part=2) + self.assertEqual(final_part, MQResponse(**final_part.model_dump())) + + json_str = json.dumps(multipart.model_dump()) + deserialized = MQResponse(**json.loads(json_str)) + self.assertEqual(deserialized, multipart) + class TestMessagebus(TestCase): def test_base_model(self):