From 3887fbc2abb42f323a08c1cd00926c7d8f2b4732 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:30:22 -0400 Subject: [PATCH 01/10] Base Model --- brewtils/models.py | 22 +++++++++++ brewtils/schema_parser.py | 74 +++++++++++++++++++++++++++++++++++++ brewtils/schemas.py | 10 +++++ brewtils/test/comparable.py | 40 +++++++++++++++++++- brewtils/test/fixtures.py | 62 ++++++++++++++++++++++++------- 5 files changed, 193 insertions(+), 15 deletions(-) diff --git a/brewtils/models.py b/brewtils/models.py index 7bfbcbef..ac9db44e 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -4,6 +4,7 @@ import pytz # noqa # not in requirements file import six # noqa # not in requirements file +from datetime import datetime from brewtils.errors import ModelError, _deprecate @@ -423,6 +424,27 @@ def is_different(self, other): return False +class StatusHistory(BaseModel): + schema = "StatusHistorySchema" + + def __init__(self, status=None, heartbeat=None): + self.status = status + self.heartbeat = heartbeat + + +class StatusInfo(BaseModel): + schema = "StatusInfoSchema" + + def __init__(self, heartbeat=None, history=None): + self.heartbeat = heartbeat + self.history = history or [] + + def set_status_heartbeat(self, status): + + self.heartbeat = datetime.utcnow() + self.history.append(StatusHistory(status=status, heartbeat=self.heartbeat)) + + class RequestFile(BaseModel): schema = "RequestFileSchema" diff --git a/brewtils/schema_parser.py b/brewtils/schema_parser.py index cdbc21f4..3041e94e 100644 --- a/brewtils/schema_parser.py +++ b/brewtils/schema_parser.py @@ -52,6 +52,8 @@ class SchemaParser(object): "ResolvableSchema": brewtils.models.Resolvable, "SubscriberSchema": brewtils.models.Subscriber, "TopicSchema": brewtils.models.Topic, + "StatusInfoSchema": brewtils.models.StatusInfo, + "StatusHistorySchema": brewtils.models.StatusHistory, } logger = logging.getLogger(__name__) @@ -436,6 +438,38 @@ def parse_topic(cls, topic, from_string=False, **kwargs): return cls.parse( topic, brewtils.models.Topic, from_string=from_string, **kwargs ) + + @classmethod + def parse_status_info(cls, status_info, from_string=False, **kwargs): + """Convert raw JSON string or dictionary to a status info model object + + Args: + status_info: The raw input + from_string: True if input is a JSON string, False if a dictionary + **kwargs: Additional parameters to be passed to the Schema (e.g. many=True) + + Returns: + A StatusInfo object + """ + return cls.parse( + status_info, brewtils.models.StatusInfo, from_string=from_string, **kwargs + ) + + @classmethod + def parse_status_history(cls, status_history, from_string=False, **kwargs): + """Convert raw JSON string or dictionary to a status history model object + + Args: + status_history: The raw input + from_string: True if input is a JSON string, False if a dictionary + **kwargs: Additional parameters to be passed to the Schema (e.g. many=True) + + Returns: + A StatusHistory object + """ + return cls.parse( + status_history, brewtils.models.StatusHistory, from_string=from_string, **kwargs + ) @classmethod def parse( @@ -942,6 +976,46 @@ def serialize_topic(cls, topic, to_string=True, **kwargs): schema_name=brewtils.models.Topic.schema, **kwargs ) + + @classmethod + def serialize_status_info(cls, status_info, to_string=True, **kwargs): + """Convert a status info model into serialized form + + Args: + status_info: The status info object(s) to be serialized + to_string: True to generate a JSON-formatted string, False to generate a + dictionary + **kwargs: Additional parameters to be passed to the Schema (e.g. many=True) + + Returns: + Serialized representation of status_info + """ + return cls.serialize( + status_info, + to_string=to_string, + schema_name=brewtils.models.StatusInfo.schema, + **kwargs + ) + + @classmethod + def serialize_status_history(cls, status_history, to_string=True, **kwargs): + """Convert a status history model into serialized form + + Args: + status_history: The status history object(s) to be serialized + to_string: True to generate a JSON-formatted string, False to generate a + dictionary + **kwargs: Additional parameters to be passed to the Schema (e.g. many=True) + + Returns: + Serialized representation of status_history + """ + return cls.serialize( + status_history, + to_string=to_string, + schema_name=brewtils.models.StatusHistory.schema, + **kwargs + ) @classmethod def serialize( diff --git a/brewtils/schemas.py b/brewtils/schemas.py index 6bfe08c1..f3f0800b 100644 --- a/brewtils/schemas.py +++ b/brewtils/schemas.py @@ -48,6 +48,8 @@ "SystemDomainIdentifierSchema", "SubscriberSchema", "TopicSchema", + "StatusInfoSchema", + "StatusHistorySchema", ] # This will be updated after all the schema classes are defined @@ -355,8 +357,14 @@ class RequestSchema(RequestTemplateSchema): target_garden = fields.String(allow_none=True) +class StatusHistorySchema(BaseSchema): + heartbeat = DateTime(allow_none=True, format="epoch", example="1500065932000") + status = fields.Str(allow_none=True) + + class StatusInfoSchema(BaseSchema): heartbeat = DateTime(allow_none=True, format="epoch", example="1500065932000") + history = fields.Nested("StatusHistorySchema", many=True, allow_none=True) class PatchSchema(BaseSchema): @@ -690,6 +698,8 @@ class UserListSchema(BaseSchema): "Resolvable": ResolvableSchema, "Subscriber": SubscriberSchema, "Topic": TopicSchema, + "StatusInfo": StatusInfoSchema, + "StatusHistory": StatusHistorySchema, # Compatibility for the Job trigger types "interval": IntervalTriggerSchema, "date": DateTriggerSchema, diff --git a/brewtils/test/comparable.py b/brewtils/test/comparable.py index 1dda4a73..d26bfdc7 100644 --- a/brewtils/test/comparable.py +++ b/brewtils/test/comparable.py @@ -39,6 +39,8 @@ System, Subscriber, Topic, + StatusHistory, + StatusInfo ) __all__ = [ @@ -62,6 +64,8 @@ "assert_runner_equal", "assert_subscriber_equal", "assert_topic_equal", + "assert_status_info_equal", + "assert_status_history_equal", ] @@ -184,7 +188,6 @@ def _assert_wrapper(obj1, obj2, expected_type=None, do_raise=False, **kwargs): # These are the 'simple' models - they don't have any nested models as fields -assert_instance_equal = partial(_assert_wrapper, expected_type=Instance) assert_choices_equal = partial(_assert_wrapper, expected_type=Choices) assert_patch_equal = partial(_assert_wrapper, expected_type=PatchOperation) assert_logging_config_equal = partial(_assert_wrapper, expected_type=LoggingConfig) @@ -196,8 +199,37 @@ def _assert_wrapper(obj1, obj2, expected_type=None, do_raise=False, **kwargs): assert_request_file_equal = partial(_assert_wrapper, expected_type=RequestFile) assert_runner_equal = partial(_assert_wrapper, expected_type=Runner) assert_resolvable_equal = partial(_assert_wrapper, expected_type=Resolvable) -assert_connection_equal = partial(_assert_wrapper, expected_type=Connection) assert_subscriber_equal = partial(_assert_wrapper, expected_type=Subscriber) +assert_status_history_equal = partial(_assert_wrapper, expected_type=StatusHistory) + +def assert_status_info_equal(obj1, obj2, do_raise=False): + return _assert_wrapper( + obj1, + obj2, + expected_type=StatusInfo, + deep_fields={ + "status_history": partial(assert_status_history_equal, do_raise=True), + }, + do_raise=do_raise, + ) + +def assert_instance_equal(obj1, obj2, do_raise=False): + return _assert_wrapper( + obj1, + obj2, + expected_type=Instance, + deep_fields={"status_info": partial(assert_status_info_equal, do_raise=True)}, + do_raise=do_raise, + ) + +def assert_connection_equal(obj1, obj2, do_raise=False): + return _assert_wrapper( + obj1, + obj2, + expected_type=Connection, + deep_fields={"status_info": partial(assert_status_info_equal, do_raise=True)}, + do_raise=do_raise, + ) def assert_command_equal(obj1, obj2, do_raise=False): @@ -390,6 +422,7 @@ def assert_garden_equal(obj1, obj2, do_raise=False): "systems": partial(assert_system_equal, do_raise=True), "receiving_connections": partial(assert_connection_equal, do_raise=True), "publishing_connections": partial(assert_connection_equal, do_raise=True), + "status_info": partial(assert_status_info_equal, do_raise=True), }, do_raise=do_raise, ) @@ -405,3 +438,6 @@ def assert_topic_equal(obj1, obj2, do_raise=False): }, do_raise=do_raise, ) + + + diff --git a/brewtils/test/fixtures.py b/brewtils/test/fixtures.py index 6cb362fc..609d6b95 100644 --- a/brewtils/test/fixtures.py +++ b/brewtils/test/fixtures.py @@ -32,6 +32,8 @@ System, Subscriber, Topic, + StatusHistory, + StatusInfo, ) @@ -204,7 +206,7 @@ def bg_command_2(command_dict_2, bg_parameter, system_id): @pytest.fixture -def instance_dict(ts_epoch): +def instance_dict(status_info_dict): """An instance represented as a dictionary.""" return { "id": "584f11af55a38e64799fd1d4", @@ -231,16 +233,16 @@ def instance_dict(ts_epoch): }, "url": "amqp://guest:guest@localhost:5672", }, - "status_info": {"heartbeat": ts_epoch}, + "status_info": status_info_dict, "metadata": {"meta": "data"}, } @pytest.fixture -def bg_instance(instance_dict, ts_dt): +def bg_instance(instance_dict, bg_status_info): """An instance as a model.""" dict_copy = copy.deepcopy(instance_dict) - dict_copy["status_info"]["heartbeat"] = ts_dt + dict_copy["status_info"] = bg_status_info return Instance(**dict_copy) @@ -763,52 +765,85 @@ def bg_request_file(request_file_dict): @pytest.fixture -def connection_dict(): +def connection_dict(status_info_dict): """A connection as a dictionary.""" return { "api": "HTTP", "config": {}, "status": "RECEIVING", - "status_info": {}, + "status_info": status_info_dict, } @pytest.fixture -def connection_publishing_dict(): +def connection_publishing_dict(status_info_dict): """A connection as a dictionary.""" return { "api": "HTTP", "config": {}, "status": "PUBLISHING", - "status_info": {}, + "status_info": status_info_dict, } @pytest.fixture -def bg_connection(connection_dict): +def bg_connection(connection_dict, bg_status_info): """An connection as a model.""" dict_copy = copy.deepcopy(connection_dict) + dict_copy["status_info"] = bg_status_info return Connection(**dict_copy) @pytest.fixture def bg_connection_publishing(connection_publishing_dict): """An connection as a model.""" - dict_copy = copy.deepcopy(connection_publishing_dict) + dict_copy = copy.deepcopy(connection_publishing_dict, bg_status_info) + dict_copy["status_info"] = bg_status_info return Connection(**dict_copy) +@pytest.fixture +def status_history_dict(ts_epoch): + """A status history as a dictionary""" + + return { + "status": "RUNNING", + "heartbeat": ts_epoch, + } + +@pytest.fixture +def bg_status_history(status_history_dict, ts_dt): + dict_copy = copy.deepcopy(status_history_dict) + dict_copy["heartbeat"] = ts_dt + return StatusHistory(**dict_copy) + + +@pytest.fixture +def status_info_dict(ts_epoch, status_history_dict): + """A status info as a dictionary""" + + return { + "heartbeat": ts_epoch, + "history": [status_history_dict] + } + +@pytest.fixture +def bg_status_info(status_info_dict, ts_dt, bg_status_history): + dict_copy = copy.deepcopy(status_info_dict) + dict_copy["history"] = [bg_status_history] + dict_copy["heartbeat"] = ts_dt + return StatusInfo(**dict_copy) @pytest.fixture -def garden_dict(ts_epoch, system_dict, connection_dict, connection_publishing_dict): +def garden_dict(ts_epoch, system_dict, connection_dict, connection_publishing_dict, status_info_dict): """A garden as a dictionary.""" return { "id": "123f11af55a38e64799fa1c1", "name": "garden", "status": "RUNNING", - "status_info": {}, + "status_info": status_info_dict, "namespaces": [system_dict["namespace"]], "systems": [system_dict], "connection_type": "http", @@ -822,12 +857,13 @@ def garden_dict(ts_epoch, system_dict, connection_dict, connection_publishing_di @pytest.fixture -def bg_garden(garden_dict, bg_system, bg_connection, bg_connection_publishing): +def bg_garden(garden_dict, bg_system, bg_connection, bg_connection_publishing, bg_status_info): """An operation as a model.""" dict_copy = copy.deepcopy(garden_dict) dict_copy["systems"] = [bg_system] dict_copy["receiving_connections"] = [bg_connection] dict_copy["publishing_connections"] = [bg_connection_publishing] + dict_copy["status_info"] = [bg_status_info] return Garden(**dict_copy) From d24476c0996fef951f4344b7f1290fcc34108de7 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:32:36 +0000 Subject: [PATCH 02/10] model cleanup --- brewtils/models.py | 26 +++++++++++++++++++++++--- brewtils/test/comparable.py | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/brewtils/models.py b/brewtils/models.py index 7f774e24..2107847c 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -247,7 +247,7 @@ def __init__( self.description = description self.id = id self.status = status.upper() if status else None - self.status_info = status_info or {} + self.status_info = status_info self.queue_type = queue_type self.queue_info = queue_info or {} self.icon_name = icon_name @@ -431,6 +431,18 @@ def __init__(self, status=None, heartbeat=None): self.status = status self.heartbeat = heartbeat + def __str__(self): + return "%s:%s" % ( + self.status, + self.heartbeat, + ) + + def __repr__(self): + return "" % ( + self.status, + self.heartbeat, + ) + class StatusInfo(BaseModel): schema = "StatusInfoSchema" @@ -444,6 +456,14 @@ def set_status_heartbeat(self, status): self.heartbeat = datetime.utcnow() self.history.append(StatusHistory(status=status, heartbeat=self.heartbeat)) + def __str__(self): + return self.heartbeat + + def __repr__(self): + return "" % ( + self.heartbeat, + self.history, + ) class RequestFile(BaseModel): schema = "RequestFileSchema" @@ -1497,7 +1517,7 @@ def __init__( self.id = id self.name = name self.status = status.upper() if status else None - self.status_info = status_info or {} + self.status_info = status_info self.namespaces = namespaces or [] self.systems = systems or [] @@ -1554,7 +1574,7 @@ def __init__( ): self.api = api self.status = status - self.status_info = status_info or {} + self.status_info = status_info self.config = config or {} def __str__(self): diff --git a/brewtils/test/comparable.py b/brewtils/test/comparable.py index d26bfdc7..07dbd19c 100644 --- a/brewtils/test/comparable.py +++ b/brewtils/test/comparable.py @@ -208,7 +208,7 @@ def assert_status_info_equal(obj1, obj2, do_raise=False): obj2, expected_type=StatusInfo, deep_fields={ - "status_history": partial(assert_status_history_equal, do_raise=True), + "history": partial(assert_status_history_equal, do_raise=True), }, do_raise=do_raise, ) From 0ea6750891a1e4e59f64f2ce28d85b09f9b69816 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:19:06 +0000 Subject: [PATCH 03/10] code cleanup --- brewtils/models.py | 3 +- brewtils/schema_parser.py | 13 +++--- brewtils/test/comparable.py | 12 +++--- brewtils/test/fixtures.py | 27 +++++++----- test/schema_parser_test.py | 83 +++++++++++++++++++++++++++++++++++-- 5 files changed, 112 insertions(+), 26 deletions(-) diff --git a/brewtils/models.py b/brewtils/models.py index 2107847c..7a74afd4 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- +from datetime import datetime from enum import Enum import pytz # noqa # not in requirements file import six # noqa # not in requirements file -from datetime import datetime from brewtils.errors import ModelError, _deprecate @@ -465,6 +465,7 @@ def __repr__(self): self.history, ) + class RequestFile(BaseModel): schema = "RequestFileSchema" diff --git a/brewtils/schema_parser.py b/brewtils/schema_parser.py index 3041e94e..f023e97c 100644 --- a/brewtils/schema_parser.py +++ b/brewtils/schema_parser.py @@ -438,7 +438,7 @@ def parse_topic(cls, topic, from_string=False, **kwargs): return cls.parse( topic, brewtils.models.Topic, from_string=from_string, **kwargs ) - + @classmethod def parse_status_info(cls, status_info, from_string=False, **kwargs): """Convert raw JSON string or dictionary to a status info model object @@ -454,7 +454,7 @@ def parse_status_info(cls, status_info, from_string=False, **kwargs): return cls.parse( status_info, brewtils.models.StatusInfo, from_string=from_string, **kwargs ) - + @classmethod def parse_status_history(cls, status_history, from_string=False, **kwargs): """Convert raw JSON string or dictionary to a status history model object @@ -468,7 +468,10 @@ def parse_status_history(cls, status_history, from_string=False, **kwargs): A StatusHistory object """ return cls.parse( - status_history, brewtils.models.StatusHistory, from_string=from_string, **kwargs + status_history, + brewtils.models.StatusHistory, + from_string=from_string, + **kwargs ) @classmethod @@ -976,7 +979,7 @@ def serialize_topic(cls, topic, to_string=True, **kwargs): schema_name=brewtils.models.Topic.schema, **kwargs ) - + @classmethod def serialize_status_info(cls, status_info, to_string=True, **kwargs): """Convert a status info model into serialized form @@ -996,7 +999,7 @@ def serialize_status_info(cls, status_info, to_string=True, **kwargs): schema_name=brewtils.models.StatusInfo.schema, **kwargs ) - + @classmethod def serialize_status_history(cls, status_history, to_string=True, **kwargs): """Convert a status history model into serialized form diff --git a/brewtils/test/comparable.py b/brewtils/test/comparable.py index 07dbd19c..dd22e7b3 100644 --- a/brewtils/test/comparable.py +++ b/brewtils/test/comparable.py @@ -36,11 +36,11 @@ RequestTemplate, Resolvable, Runner, - System, + StatusHistory, + StatusInfo, Subscriber, + System, Topic, - StatusHistory, - StatusInfo ) __all__ = [ @@ -202,6 +202,7 @@ def _assert_wrapper(obj1, obj2, expected_type=None, do_raise=False, **kwargs): assert_subscriber_equal = partial(_assert_wrapper, expected_type=Subscriber) assert_status_history_equal = partial(_assert_wrapper, expected_type=StatusHistory) + def assert_status_info_equal(obj1, obj2, do_raise=False): return _assert_wrapper( obj1, @@ -213,6 +214,7 @@ def assert_status_info_equal(obj1, obj2, do_raise=False): do_raise=do_raise, ) + def assert_instance_equal(obj1, obj2, do_raise=False): return _assert_wrapper( obj1, @@ -222,6 +224,7 @@ def assert_instance_equal(obj1, obj2, do_raise=False): do_raise=do_raise, ) + def assert_connection_equal(obj1, obj2, do_raise=False): return _assert_wrapper( obj1, @@ -438,6 +441,3 @@ def assert_topic_equal(obj1, obj2, do_raise=False): }, do_raise=do_raise, ) - - - diff --git a/brewtils/test/fixtures.py b/brewtils/test/fixtures.py index ac0cc861..7946b193 100644 --- a/brewtils/test/fixtures.py +++ b/brewtils/test/fixtures.py @@ -29,11 +29,11 @@ RequestTemplate, Resolvable, Runner, + StatusHistory, + StatusInfo, Subscriber, System, Topic, - StatusHistory, - StatusInfo, ) @@ -798,12 +798,13 @@ def bg_connection(connection_dict, bg_status_info): @pytest.fixture -def bg_connection_publishing(connection_publishing_dict): +def bg_connection_publishing(connection_publishing_dict, bg_status_info): """An connection as a model.""" - dict_copy = copy.deepcopy(connection_publishing_dict, bg_status_info) + dict_copy = copy.deepcopy(connection_publishing_dict) dict_copy["status_info"] = bg_status_info return Connection(**dict_copy) + @pytest.fixture def status_history_dict(ts_epoch): """A status history as a dictionary""" @@ -813,6 +814,7 @@ def status_history_dict(ts_epoch): "heartbeat": ts_epoch, } + @pytest.fixture def bg_status_history(status_history_dict, ts_dt): dict_copy = copy.deepcopy(status_history_dict) @@ -824,10 +826,8 @@ def bg_status_history(status_history_dict, ts_dt): def status_info_dict(ts_epoch, status_history_dict): """A status info as a dictionary""" - return { - "heartbeat": ts_epoch, - "history": [status_history_dict] - } + return {"heartbeat": ts_epoch, "history": [status_history_dict]} + @pytest.fixture def bg_status_info(status_info_dict, ts_dt, bg_status_history): @@ -836,8 +836,11 @@ def bg_status_info(status_info_dict, ts_dt, bg_status_history): dict_copy["heartbeat"] = ts_dt return StatusInfo(**dict_copy) + @pytest.fixture -def garden_dict(ts_epoch, system_dict, connection_dict, connection_publishing_dict, status_info_dict): +def garden_dict( + ts_epoch, system_dict, connection_dict, connection_publishing_dict, status_info_dict +): """A garden as a dictionary.""" return { @@ -858,13 +861,15 @@ def garden_dict(ts_epoch, system_dict, connection_dict, connection_publishing_di @pytest.fixture -def bg_garden(garden_dict, bg_system, bg_connection, bg_connection_publishing, bg_status_info): +def bg_garden( + garden_dict, bg_system, bg_connection, bg_connection_publishing, bg_status_info +): """An operation as a model.""" dict_copy = copy.deepcopy(garden_dict) dict_copy["systems"] = [bg_system] dict_copy["receiving_connections"] = [bg_connection] dict_copy["publishing_connections"] = [bg_connection_publishing] - dict_copy["status_info"] = [bg_status_info] + dict_copy["status_info"] = bg_status_info return Garden(**dict_copy) diff --git a/test/schema_parser_test.py b/test/schema_parser_test.py index 9246dba2..da2205d5 100644 --- a/test/schema_parser_test.py +++ b/test/schema_parser_test.py @@ -5,8 +5,11 @@ import copy -import brewtils.models import pytest +from marshmallow.exceptions import MarshmallowError +from pytest_lazyfixture import lazy_fixture + +import brewtils.models from brewtils.models import System from brewtils.schema_parser import SchemaParser from brewtils.test.comparable import ( @@ -28,12 +31,12 @@ assert_resolvable_equal, assert_role_equal, assert_runner_equal, + assert_status_history_equal, + assert_status_info_equal, assert_subscriber_equal, assert_system_equal, assert_topic_equal, ) -from marshmallow.exceptions import MarshmallowError -from pytest_lazyfixture import lazy_fixture class TestParse(object): @@ -194,6 +197,18 @@ def test_no_modify(self, system_dict): assert_topic_equal, lazy_fixture("bg_topic"), ), + ( + brewtils.models.StatusInfo, + lazy_fixture("status_info_dict"), + assert_status_info_equal, + lazy_fixture("bg_status_info"), + ), + ( + brewtils.models.StatusHistory, + lazy_fixture("status_history_dict"), + assert_status_history_equal, + lazy_fixture("bg_status_history"), + ), ], ) def test_single(self, model, data, assertion, expected): @@ -340,6 +355,18 @@ def test_single_from_string(self): assert_topic_equal, lazy_fixture("bg_topic"), ), + ( + "parse_status_info", + lazy_fixture("status_info_dict"), + assert_status_info_equal, + lazy_fixture("bg_status_info"), + ), + ( + "parse_status_history", + lazy_fixture("status_history_dict"), + assert_status_history_equal, + lazy_fixture("bg_status_history"), + ), ], ) def test_single_specific(self, method, data, assertion, expected): @@ -479,6 +506,18 @@ def test_single_specific_from_string(self): assert_topic_equal, lazy_fixture("bg_topic"), ), + ( + brewtils.models.StatusInfo, + lazy_fixture("status_info_dict"), + assert_status_info_equal, + lazy_fixture("bg_status_info"), + ), + ( + brewtils.models.StatusHistory, + lazy_fixture("status_history_dict"), + assert_status_history_equal, + lazy_fixture("bg_status_history"), + ), ], ) def test_many(self, model, data, assertion, expected): @@ -611,6 +650,18 @@ def test_many(self, model, data, assertion, expected): assert_topic_equal, lazy_fixture("bg_topic"), ), + ( + "parse_status_info", + lazy_fixture("status_info_dict"), + assert_status_info_equal, + lazy_fixture("bg_status_info"), + ), + ( + "parse_status_history", + lazy_fixture("status_history_dict"), + assert_status_history_equal, + lazy_fixture("bg_status_history"), + ), ], ) def test_many_specific(self, method, data, assertion, expected): @@ -666,6 +717,8 @@ class TestSerialize(object): (lazy_fixture("bg_resolvable"), lazy_fixture("resolvable_dict")), (lazy_fixture("bg_subscriber"), lazy_fixture("subscriber_dict")), (lazy_fixture("bg_topic"), lazy_fixture("topic_dict")), + (lazy_fixture("bg_status_info"), lazy_fixture("status_info_dict")), + (lazy_fixture("bg_status_history"), lazy_fixture("status_history_dict")), ], ) def test_single(self, model, expected): @@ -777,6 +830,16 @@ def test_single(self, model, expected): lazy_fixture("bg_topic"), lazy_fixture("topic_dict"), ), + ( + "serialize_status_info", + lazy_fixture("bg_status_info"), + lazy_fixture("status_info_dict"), + ), + ( + "serialize_status_history", + lazy_fixture("bg_status_history"), + lazy_fixture("status_history_dict"), + ), ], ) def test_single_specific(self, method, data, expected): @@ -807,6 +870,8 @@ def test_single_specific(self, method, data, expected): (lazy_fixture("bg_resolvable"), lazy_fixture("resolvable_dict")), (lazy_fixture("bg_subscriber"), lazy_fixture("subscriber_dict")), (lazy_fixture("bg_topic"), lazy_fixture("topic_dict")), + (lazy_fixture("bg_status_info"), lazy_fixture("status_info_dict")), + (lazy_fixture("bg_status_history"), lazy_fixture("status_history_dict")), ], ) def test_many(self, model, expected): @@ -901,6 +966,16 @@ class TestRoundTrip(object): lazy_fixture("bg_subscriber"), ), (brewtils.models.Topic, assert_topic_equal, lazy_fixture("bg_topic")), + ( + brewtils.models.StatusInfo, + assert_status_info_equal, + lazy_fixture("bg_status_info"), + ), + ( + brewtils.models.StatusHistory, + assert_status_history_equal, + lazy_fixture("bg_status_history"), + ), ], ) def test_parsed_start(self, model, assertion, data): @@ -932,6 +1007,8 @@ def test_parsed_start(self, model, assertion, data): (brewtils.models.Operation, lazy_fixture("operation_dict")), (brewtils.models.Runner, lazy_fixture("runner_dict")), (brewtils.models.Resolvable, lazy_fixture("resolvable_dict")), + (brewtils.models.StatusInfo, lazy_fixture("status_info_dict")), + (brewtils.models.StatusHistory, lazy_fixture("status_history_dict")), ], ) def test_serialized_start(self, model, data): From 3e285983b6b1002af2900c2bc83ee4742b08cf30 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:36:02 -0400 Subject: [PATCH 04/10] Update models.py --- brewtils/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/brewtils/models.py b/brewtils/models.py index 7a74afd4..f9c737f9 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -247,7 +247,7 @@ def __init__( self.description = description self.id = id self.status = status.upper() if status else None - self.status_info = status_info + self.status_info = status_info if status_info else StatusInfo() self.queue_type = queue_type self.queue_info = queue_info or {} self.icon_name = icon_name @@ -1518,7 +1518,7 @@ def __init__( self.id = id self.name = name self.status = status.upper() if status else None - self.status_info = status_info + self.status_info = status_info if status_info else StatusInfo() self.namespaces = namespaces or [] self.systems = systems or [] @@ -1575,7 +1575,7 @@ def __init__( ): self.api = api self.status = status - self.status_info = status_info + self.status_info = status_info if status_info else StatusInfo() self.config = config or {} def __str__(self): From 3771ee4df461cc2af29450def97fbcc0bc7ad344 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:38:01 -0400 Subject: [PATCH 05/10] Update models.py --- brewtils/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/brewtils/models.py b/brewtils/models.py index f9c737f9..f9a1ce6f 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import copy from datetime import datetime from enum import Enum @@ -454,7 +455,7 @@ def __init__(self, heartbeat=None, history=None): def set_status_heartbeat(self, status): self.heartbeat = datetime.utcnow() - self.history.append(StatusHistory(status=status, heartbeat=self.heartbeat)) + self.history.append(StatusHistory(status=copy.deepcopy(status), heartbeat=self.heartbeat)) def __str__(self): return self.heartbeat From 066dc47bd11a95b9acd8c88e7bf776af3ffe14f3 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:01:07 -0400 Subject: [PATCH 06/10] deep copy status info in fixtures --- brewtils/test/fixtures.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/brewtils/test/fixtures.py b/brewtils/test/fixtures.py index 7946b193..132b33c5 100644 --- a/brewtils/test/fixtures.py +++ b/brewtils/test/fixtures.py @@ -242,7 +242,7 @@ def instance_dict(status_info_dict): def bg_instance(instance_dict, bg_status_info): """An instance as a model.""" dict_copy = copy.deepcopy(instance_dict) - dict_copy["status_info"] = bg_status_info + dict_copy["status_info"] = copy.deepcopy(bg_status_info) return Instance(**dict_copy) @@ -793,7 +793,7 @@ def connection_publishing_dict(status_info_dict): def bg_connection(connection_dict, bg_status_info): """An connection as a model.""" dict_copy = copy.deepcopy(connection_dict) - dict_copy["status_info"] = bg_status_info + dict_copy["status_info"] = copy.deepcopy(bg_status_info) return Connection(**dict_copy) @@ -801,7 +801,7 @@ def bg_connection(connection_dict, bg_status_info): def bg_connection_publishing(connection_publishing_dict, bg_status_info): """An connection as a model.""" dict_copy = copy.deepcopy(connection_publishing_dict) - dict_copy["status_info"] = bg_status_info + dict_copy["status_info"] = copy.deepcopy(bg_status_info) return Connection(**dict_copy) @@ -832,7 +832,7 @@ def status_info_dict(ts_epoch, status_history_dict): @pytest.fixture def bg_status_info(status_info_dict, ts_dt, bg_status_history): dict_copy = copy.deepcopy(status_info_dict) - dict_copy["history"] = [bg_status_history] + dict_copy["history"] = [copy.deepcopy(bg_status_history)] dict_copy["heartbeat"] = ts_dt return StatusInfo(**dict_copy) @@ -869,7 +869,7 @@ def bg_garden( dict_copy["systems"] = [bg_system] dict_copy["receiving_connections"] = [bg_connection] dict_copy["publishing_connections"] = [bg_connection_publishing] - dict_copy["status_info"] = bg_status_info + dict_copy["status_info"] = copy.deepcopy(bg_status_info) return Garden(**dict_copy) From 1102e428568d1f8ef57a775f4d1c8a2bbe78a7e5 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:25:31 -0400 Subject: [PATCH 07/10] Add max history length --- brewtils/models.py | 5 ++++- test/models_test.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/brewtils/models.py b/brewtils/models.py index f9a1ce6f..5c2aa1f6 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -452,11 +452,14 @@ def __init__(self, heartbeat=None, history=None): self.heartbeat = heartbeat self.history = history or [] - def set_status_heartbeat(self, status): + def set_status_heartbeat(self, status, max_history=None): self.heartbeat = datetime.utcnow() self.history.append(StatusHistory(status=copy.deepcopy(status), heartbeat=self.heartbeat)) + if max_history and len(self.history) > max_history: + self.history = self.history[(max_history * -1):] + def __str__(self): return self.heartbeat diff --git a/test/models_test.py b/test/models_test.py index ec3f40dc..0946f559 100644 --- a/test/models_test.py +++ b/test/models_test.py @@ -20,6 +20,7 @@ RequestTemplate, LegacyRole, Subscriber, + StatusInfo, Topic, ) from pytest_lazyfixture import lazy_fixture @@ -713,3 +714,24 @@ def test_repr(self, topic1, subscriber1): topic1.name, [subscriber1], ) + + +class TestStatusInfo: + + def test_max_history(self): + status_info = StatusInfo() + + max_length = 5 + + for _ in range(10): + status_info.set_status_heartbeat("RUNNING", max_history=max_length) + + assert len(status_info.history) == max_length + + def test_history(self): + status_info = StatusInfo() + + for _ in range(10): + status_info.set_status_heartbeat("RUNNING") + + assert len(status_info.history) == 10 From 010a3c5ec4e749a48c0af312be0bf3bea7e9e412 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:38:40 -0400 Subject: [PATCH 08/10] allow negative checks --- brewtils/models.py | 2 +- test/models_test.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/brewtils/models.py b/brewtils/models.py index 5c2aa1f6..302e5adb 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -457,7 +457,7 @@ def set_status_heartbeat(self, status, max_history=None): self.heartbeat = datetime.utcnow() self.history.append(StatusHistory(status=copy.deepcopy(status), heartbeat=self.heartbeat)) - if max_history and len(self.history) > max_history: + if max_history and max_history > 0 and len(self.history) > max_history: self.history = self.history[(max_history * -1):] def __str__(self): diff --git a/test/models_test.py b/test/models_test.py index 0946f559..6d3e84f3 100644 --- a/test/models_test.py +++ b/test/models_test.py @@ -735,3 +735,11 @@ def test_history(self): status_info.set_status_heartbeat("RUNNING") assert len(status_info.history) == 10 + + def test_negative_history(self): + status_info = StatusInfo() + + for _ in range(10): + status_info.set_status_heartbeat("RUNNING", max_history=-1) + + assert len(status_info.history) == 10 From 36f423c4930668702d667e68bf933f02128cc567 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Mon, 15 Jul 2024 06:35:17 -0400 Subject: [PATCH 09/10] formatting --- brewtils/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/brewtils/models.py b/brewtils/models.py index 302e5adb..c48f33b2 100644 --- a/brewtils/models.py +++ b/brewtils/models.py @@ -455,10 +455,12 @@ def __init__(self, heartbeat=None, history=None): def set_status_heartbeat(self, status, max_history=None): self.heartbeat = datetime.utcnow() - self.history.append(StatusHistory(status=copy.deepcopy(status), heartbeat=self.heartbeat)) + self.history.append( + StatusHistory(status=copy.deepcopy(status), heartbeat=self.heartbeat) + ) if max_history and max_history > 0 and len(self.history) > max_history: - self.history = self.history[(max_history * -1):] + self.history = self.history[(max_history * -1) :] def __str__(self): return self.heartbeat From 243ff22ea4afc31f8aa0bd102fe157644f98def1 Mon Sep 17 00:00:00 2001 From: TheBurchLog <5104941+TheBurchLog@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:22:35 -0400 Subject: [PATCH 10/10] change log updates --- CHANGELOG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 261d77d5..d5377416 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ Brewtils Changelog ================== +3.27.0 +------ +TBD + +- Formalized Status Info model and added helper features to track the history of the status changes. + + 3.26.4 ------ 7/12/24