From 1b682300276e1580d661246743a3a2bcf64b3e79 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Tue, 20 Jan 2026 17:40:49 +0530 Subject: [PATCH 01/33] feat: Add models for Notification Strategy API --- .../clients/notification/models/__init__.py | 7 +++ .../notification/models/_address_group.py | 47 +++++++++++++++++++ .../models/_dynamic_strategy_request.py | 18 +++++++ .../notification/models/_message_template.py | 41 ++++++++++++++++ .../models/_notification_configuration.py | 20 ++++++++ .../models/_notification_strategy.py | 10 ++++ 6 files changed, 143 insertions(+) create mode 100644 nisystemlink/clients/notification/models/__init__.py create mode 100644 nisystemlink/clients/notification/models/_address_group.py create mode 100644 nisystemlink/clients/notification/models/_dynamic_strategy_request.py create mode 100644 nisystemlink/clients/notification/models/_message_template.py create mode 100644 nisystemlink/clients/notification/models/_notification_configuration.py create mode 100644 nisystemlink/clients/notification/models/_notification_strategy.py diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py new file mode 100644 index 00000000..d2c170f0 --- /dev/null +++ b/nisystemlink/clients/notification/models/__init__.py @@ -0,0 +1,7 @@ +from ._address_group import AddressGroup +from ._dynamic_strategy_request import DynamicStrategyRequest +from ._message_template import MessageTemplate +from ._notification_configuration import NotificationConfiguration +from ._notification_strategy import NotificationStrategy + +# flake8: noqa diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py new file mode 100644 index 00000000..171ff506 --- /dev/null +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -0,0 +1,47 @@ +from typing import Dict, List + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class AddressGroup(JsonModel): + """Model for address group for v1 endpoint.""" + + id: str + """Gets or sets the ID for address group.""" + + interpreting_service_name: str + """Gets or sets the name of the interpreting service. + + Example: "smtp" + """ + + display_name: str + """Gets or sets the address group's display name. + + Example: "name" + """ + + properties: Dict[str, str] + """Gets or sets the address group's properties. + + Example: { "property": "value" } + """ + + fields: Dict[str, List[str]] + """Gets or sets the address group's fields. Requires at least one valid recipient. + + Valid fields: + - toAddresses + - ccAddresses + - bccAddresses + + Example: + { + "toAddresses": [ "address1@example.com" ], + "ccAddresses": [ "address2@example.com" ], + "bccAddresses": [ "address3@example.com" ] + } + """ + + referencing_notification_strategies: List[str] + """Gets or sets the address group's referencing notification strategies.""" diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py new file mode 100644 index 00000000..2cc6d303 --- /dev/null +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -0,0 +1,18 @@ +from typing import Dict + +from ._notification_strategy import NotificationStrategy + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class DynamicStrategyRequest(JsonModel): + """Model for an object describing the properties of the strategy to be applied.""" + + message_template_substitution_fields: Dict[str, str] + """Gets or sets the message template substitution fields. + + Example: { "replacement": "value" } + """ + + notification_strategy: NotificationStrategy + """This record defines the dynamic notification strategy model for the v1 endpoint.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py new file mode 100644 index 00000000..473be258 --- /dev/null +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -0,0 +1,41 @@ +from typing import Dict, List + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class MessageTemplate(JsonModel): + """Model for message template for v1 endpoint.""" + + id: str + """Gets or sets the ID for message template.""" + + interpreting_service_name: str + """Gets or sets the name of the interpreting service. + + Example: "smtp" + """ + + display_name: str + """Gets or sets the message template's display name. + + Example: "name" + """ + + properties: Dict[str, str] + """Gets or sets the message template's properties. + + Example: { "property": "value" } + """ + + fields: Dict[str, str] + """Gets or sets the message template's fields. + + Valid fields: + - subjectTemplate (required) + - bodyTemplate + + Example: { "subjectTemplate": "subject", "bodyTemplate": "body" } + """ + + referencing_notification_strategies: List[str] + """Gets or sets the message template's referencing notification strategies.""" diff --git a/nisystemlink/clients/notification/models/_notification_configuration.py b/nisystemlink/clients/notification/models/_notification_configuration.py new file mode 100644 index 00000000..79caaa97 --- /dev/null +++ b/nisystemlink/clients/notification/models/_notification_configuration.py @@ -0,0 +1,20 @@ +from ._address_group import AddressGroup +from ._message_template import MessageTemplate + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class NotificationConfiguration(JsonModel): + """Model for dynamic notification configuration for the v1 endpoint.""" + + address_group_id: str + """Gets the address group ID""" + + message_template_id: str + """Gets the message template ID""" + + address_group: AddressGroup + """This record defines the address group model for the v1 endpoint.""" + + message_template: MessageTemplate + """This record defines the message template model for the v1 endpoint.""" diff --git a/nisystemlink/clients/notification/models/_notification_strategy.py b/nisystemlink/clients/notification/models/_notification_strategy.py new file mode 100644 index 00000000..1760436b --- /dev/null +++ b/nisystemlink/clients/notification/models/_notification_strategy.py @@ -0,0 +1,10 @@ +from typing import List +from ._notification_configuration import NotificationConfiguration +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class NotificationStrategy(JsonModel): + """Model for the notification strategy to be applied.""" + + notification_configurations: List[NotificationConfiguration] + """Gets the notification configurations associated with this strategy.""" From fdb58a37fcfdf133fb6c6b00add34424c8ebb276 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Tue, 20 Jan 2026 17:41:28 +0530 Subject: [PATCH 02/33] feat: Add Client for Notification API --- nisystemlink/clients/notification/__init__.py | 3 ++ .../notification/_notification_client.py | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 nisystemlink/clients/notification/__init__.py create mode 100644 nisystemlink/clients/notification/_notification_client.py diff --git a/nisystemlink/clients/notification/__init__.py b/nisystemlink/clients/notification/__init__.py new file mode 100644 index 00000000..93aa4ea6 --- /dev/null +++ b/nisystemlink/clients/notification/__init__.py @@ -0,0 +1,3 @@ +from ._notification_client import NotificationClient + +# flake8: noqa diff --git a/nisystemlink/clients/notification/_notification_client.py b/nisystemlink/clients/notification/_notification_client.py new file mode 100644 index 00000000..779ef87e --- /dev/null +++ b/nisystemlink/clients/notification/_notification_client.py @@ -0,0 +1,45 @@ +"""Implementation of Notification Client""" + +from uplink import retry +from nisystemlink.clients import core +from nisystemlink.clients.core._uplink._base_client import BaseClient +from nisystemlink.clients.core._uplink._methods import post + +from . import models + +@retry( + when=retry.when.status(408, 429, 502, 503, 504), + stop=retry.stop.after_attempt(1), + on_exception=retry.CONNECTION_ERROR, +) +class NotificationClient(BaseClient): + def __init__(self, configuration: core.HttpConfiguration | None = None): + """Initialize an instance. + + Args: + configuration: Defines the web server to connect to and information about + how to connect. If not provided, the + :class:`HttpConfigurationManager ` + is used to obtain the configuration. + + Raises: + ApiException: if unable to communicate with the Notification. + """ + if configuration is None: + configuration = core.HttpConfigurationManager.get_configuration() + + super().__init__(configuration, base_path="/ninotification/v1/") + + @post("apply-dynamic-strategy") + def apply_notification_strategy( + self, request: models.DynamicStrategyRequest + ) -> None: + """Applies the notification strategy from the given request. + + Args: + request: The request containing message template substitution fields and notification strategies. + + Returns: + None. + """ + ... From 3796ff100d08f8aac7878692c6e73ce4d1a0fb65 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Tue, 20 Jan 2026 17:41:59 +0530 Subject: [PATCH 03/33] test: Add tests for Notification CLient API --- .../notification/test_notification_client.py | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tests/integration/notification/test_notification_client.py diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py new file mode 100644 index 00000000..8e95e5c8 --- /dev/null +++ b/tests/integration/notification/test_notification_client.py @@ -0,0 +1,154 @@ +from copy import deepcopy + +import pytest + +from nisystemlink.clients.core import ApiException +from nisystemlink.clients.core._http_configuration import HttpConfiguration +from nisystemlink.clients.notification import NotificationClient +from nisystemlink.clients.notification.models import ( + AddressGroup, + DynamicStrategyRequest, + MessageTemplate, + NotificationConfiguration, + NotificationStrategy, +) + + +class GenerateRequest: + """Creates a sample valid request for the notification client.""" + + _address_group = AddressGroup( + id="address_group_id", + interpreting_service_name="smtp", + display_name="name", + properties={"property": "value"}, + fields={ + "toAddresses": ["address1@example.com"], + }, + referencing_notification_strategies=["reference_notification_strategy"], + ) + + _message_template = MessageTemplate( + id="address_group_id", + interpreting_service_name="smtp", + display_name="name", + properties={"property": "value"}, + fields={ + "subjectTemplate": "subject", + "bodyTemplate": "body", + }, + referencing_notification_strategies=["reference_notification_strategy"], + ) + + _notification_configuration = NotificationConfiguration( + address_group_id="address_group_id", + message_template_id="message_template_id", + address_group=_address_group, + message_template=_message_template, + ) + + _notification_strategy = NotificationStrategy( + notification_configurations=[ + _notification_configuration, + ] + ) + + _dynamic_strategy_request = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=_notification_strategy, + ) + + @classmethod + def getRequestBody(self): + """Returns the created request.""" + return self._dynamic_strategy_request + + +@pytest.fixture +def client(enterprise_config: HttpConfiguration) -> NotificationClient: + """Fixture to create a Notification client.""" + return NotificationClient(enterprise_config) + + +@pytest.mark.integration +@pytest.mark.enterprise +class TestNotificationClient: + request = GenerateRequest.getRequestBody() + + def test__apply_strategy_with_correct_request__returns_none( + self, client: NotificationClient + ): + assert client.apply_notification_strategy(request=self.request) is None + + def test__apply_strategy_with_empty_template_substitution_fields__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.message_template_substitution_fields = None + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) + + def test__apply_strategy_with_no_recipient__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.notification_strategy.notification_configurations[ + 0 + ].address_group.fields = {} + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) + + def test__apply_strategy_with_invalid_recipient__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.notification_strategy.notification_configurations[ + 0 + ].address_group.fields = {"toAddresses": ["sample"]} + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) + + def test__apply_strategy_with_no_configurations__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.notification_strategy.notification_configurations = [] + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) + + def test__apply_strategy_with_empty_address_groups__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.notification_strategy.notification_configurations[ + 0 + ].address_group = None + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) + + def test__apply_strategy_with_empty_message_template__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.notification_strategy.notification_configurations[ + 0 + ].message_template = None + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) + + def test__apply_strategy_with_invalid_message_template_fields__raises_exception( + self, client: NotificationClient + ): + fetched_request = deepcopy(self.request) + fetched_request.notification_strategy.notification_configurations[ + 0 + ].message_template.fields = {"bodyTemplate": "body"} + + with pytest.raises(ApiException): + client.apply_notification_strategy(request=fetched_request) From d1d8dc6567aa90a836be073eb607450cd0909e5e Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 21 Jan 2026 11:12:33 +0530 Subject: [PATCH 04/33] docs: Add notification.rst in docs --- docs/api_reference.rst | 1 + docs/api_reference/notification.rst | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 docs/api_reference/notification.rst diff --git a/docs/api_reference.rst b/docs/api_reference.rst index d8ad53b2..0b9768cc 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -22,6 +22,7 @@ API Reference api_reference/work_item api_reference/test_plan api_reference/artifact + api_reference/notification Indices and tables ------------------ diff --git a/docs/api_reference/notification.rst b/docs/api_reference/notification.rst new file mode 100644 index 00000000..a66aecc7 --- /dev/null +++ b/docs/api_reference/notification.rst @@ -0,0 +1,14 @@ +.. _api_notification_page: + +nisystemlink.clients.notification +========================== + +.. autoclass:: nisystemlink.clients.notification.NotificationClient + :exclude-members: __init__ + + .. automethod:: __init__ + .. automethod:: apply_notification_strategy + +.. automodule:: nisystemlink.clients.notification.models + :members: + :imported-members: \ No newline at end of file From 2d0fd9eb57686cf2853cc73d6ccb3b6240562d9c Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 21 Jan 2026 12:47:18 +0530 Subject: [PATCH 05/33] feat: Add example for notification client --- examples/notification/notification.py | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/notification/notification.py diff --git a/examples/notification/notification.py b/examples/notification/notification.py new file mode 100644 index 00000000..45972c23 --- /dev/null +++ b/examples/notification/notification.py @@ -0,0 +1,79 @@ +import uuid + +from nisystemlink.clients.core import ApiException, HttpConfiguration +from nisystemlink.clients.notification import NotificationClient +from nisystemlink.clients.notification.models import ( + AddressGroup, + DynamicStrategyRequest, + MessageTemplate, + NotificationConfiguration, + NotificationStrategy, +) + +# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink +server_configuration: HttpConfiguration | None = None + +# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment +# the following lines and provide your server URI and API key. +# server_configuration = HttpConfiguration( +# server_uri="https://yourserver.yourcompany.com", +# api_key="", +# ) + +client = NotificationClient(configuration=server_configuration) + +# Create a unique address group ID for this example +address_group_id = uuid.uuid1().hex + +# Create a unique message template ID for this example +message_template_id = uuid.uuid1().hex + +# Create address group +address_group = AddressGroup( + id=address_group_id, + interpreting_service_name="smtp", + display_name="Address group name", + properties={"property": "value"}, + fields={"toAddresses": ["address1@example.com"]}, + referencing_notification_strategies=["reference_notification_strategy"], +) + +# Create message template +message_template = MessageTemplate( + id=message_template_id, + interpreting_service_name="smtp", + display_name="Message template name", + properties={"property": "value"}, + fields={"subjectTemplate": "subject", "bodyTemplate": "body"}, + referencing_notification_strategies=["reference_notification_strategy"], +) + +# Create notification configuration +notification_config = NotificationConfiguration( + address_group_id=address_group_id, + message_template_id=message_template_id, + address_group=address_group, + message_template=message_template, +) + +# Create notification strategy +notification_strategy = NotificationStrategy( + notification_configurations=[notification_config] +) + +# Create request for applying strategy +apply_strategy_request = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=notification_strategy, +) + +try: + client.apply_notification_strategy(request=apply_strategy_request) + print("Notification strategy applied successfully") +except ApiException as e: + if e.http_status_code == 400: + print("Bad request body") + elif e.http_status_code == 401: + print("Unauthorized access") + else: + raise From 9569daf07b0709bef29a73c3c33cb55cc082b740 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 22 Jan 2026 10:10:05 +0530 Subject: [PATCH 06/33] refactor: Changes in docstrings, included TypedDict for required fields --- .../notification/_notification_client.py | 3 +- .../clients/notification/models/__init__.py | 4 +- .../notification/models/_address_group.py | 12 ++- .../models/_dynamic_strategy_request.py | 4 +- .../notification/models/_message_template.py | 11 ++- .../models/_notification_configuration.py | 6 +- .../notification/test_notification_client.py | 83 +++++++------------ 7 files changed, 57 insertions(+), 66 deletions(-) diff --git a/nisystemlink/clients/notification/_notification_client.py b/nisystemlink/clients/notification/_notification_client.py index 779ef87e..4f05eeed 100644 --- a/nisystemlink/clients/notification/_notification_client.py +++ b/nisystemlink/clients/notification/_notification_client.py @@ -7,6 +7,7 @@ from . import models + @retry( when=retry.when.status(408, 429, 502, 503, 504), stop=retry.stop.after_attempt(1), @@ -37,7 +38,7 @@ def apply_notification_strategy( """Applies the notification strategy from the given request. Args: - request: The request containing message template substitution fields and notification strategies. + request: Request with message template substitution fields and notification strategies. Returns: None. diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py index d2c170f0..0a555d59 100644 --- a/nisystemlink/clients/notification/models/__init__.py +++ b/nisystemlink/clients/notification/models/__init__.py @@ -1,6 +1,6 @@ -from ._address_group import AddressGroup +from ._address_group import AddressGroup, AddressFields from ._dynamic_strategy_request import DynamicStrategyRequest -from ._message_template import MessageTemplate +from ._message_template import MessageTemplate, MessageFieldTemplates from ._notification_configuration import NotificationConfiguration from ._notification_strategy import NotificationStrategy diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 171ff506..52e49621 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,10 +1,16 @@ -from typing import Dict, List +from typing import Dict, List, TypedDict from nisystemlink.clients.core._uplink._json_model import JsonModel +class AddressFields(TypedDict, total=False): + toAddresses: List[str] + ccAddresses: List[str] + bccAddresses: List[str] + + class AddressGroup(JsonModel): - """Model for address group for v1 endpoint.""" + """Model for address group defining notification recipients.""" id: str """Gets or sets the ID for address group.""" @@ -27,7 +33,7 @@ class AddressGroup(JsonModel): Example: { "property": "value" } """ - fields: Dict[str, List[str]] + fields: AddressFields """Gets or sets the address group's fields. Requires at least one valid recipient. Valid fields: diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py index 2cc6d303..2699b827 100644 --- a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -6,7 +6,7 @@ class DynamicStrategyRequest(JsonModel): - """Model for an object describing the properties of the strategy to be applied.""" + """Request model for applying a notification strategy.""" message_template_substitution_fields: Dict[str, str] """Gets or sets the message template substitution fields. @@ -15,4 +15,4 @@ class DynamicStrategyRequest(JsonModel): """ notification_strategy: NotificationStrategy - """This record defines the dynamic notification strategy model for the v1 endpoint.""" + """Gets or sets the notification strategy containing configurations for address groups and message templates.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index 473be258..95e9fd1e 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,10 +1,15 @@ -from typing import Dict, List +from typing import Dict, List, TypedDict from nisystemlink.clients.core._uplink._json_model import JsonModel +class MessageFieldTemplates(TypedDict, total=False): + subjectTemplate: str + bodyTemplate: str + + class MessageTemplate(JsonModel): - """Model for message template for v1 endpoint.""" + """Model for message template defining notification content structure.""" id: str """Gets or sets the ID for message template.""" @@ -27,7 +32,7 @@ class MessageTemplate(JsonModel): Example: { "property": "value" } """ - fields: Dict[str, str] + fields: MessageFieldTemplates """Gets or sets the message template's fields. Valid fields: diff --git a/nisystemlink/clients/notification/models/_notification_configuration.py b/nisystemlink/clients/notification/models/_notification_configuration.py index 79caaa97..6f8800ed 100644 --- a/nisystemlink/clients/notification/models/_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_notification_configuration.py @@ -5,7 +5,7 @@ class NotificationConfiguration(JsonModel): - """Model for dynamic notification configuration for the v1 endpoint.""" + """Model for notification configuration defining address groups and message template for the notification.""" address_group_id: str """Gets the address group ID""" @@ -14,7 +14,7 @@ class NotificationConfiguration(JsonModel): """Gets the message template ID""" address_group: AddressGroup - """This record defines the address group model for the v1 endpoint.""" + """Gets the address group defining notification recipients.""" message_template: MessageTemplate - """This record defines the message template model for the v1 endpoint.""" + """Gets the message template defining notification content structure""" diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 8e95e5c8..90a7bf23 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -1,22 +1,16 @@ -from copy import deepcopy - import pytest from nisystemlink.clients.core import ApiException from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( - AddressGroup, - DynamicStrategyRequest, - MessageTemplate, - NotificationConfiguration, - NotificationStrategy, -) - + AddressGroup, DynamicStrategyRequest, MessageTemplate, + NotificationConfiguration, NotificationStrategy) -class GenerateRequest: - """Creates a sample valid request for the notification client.""" +@pytest.fixture +def request_model(): + """Returns the created request.""" _address_group = AddressGroup( id="address_group_id", interpreting_service_name="smtp", @@ -33,10 +27,7 @@ class GenerateRequest: interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, - fields={ - "subjectTemplate": "subject", - "bodyTemplate": "body", - }, + fields={"subjectTemplate": "subject", "bodyTemplate": "body"}, referencing_notification_strategies=["reference_notification_strategy"], ) @@ -58,13 +49,10 @@ class GenerateRequest: notification_strategy=_notification_strategy, ) - @classmethod - def getRequestBody(self): - """Returns the created request.""" - return self._dynamic_strategy_request + return _dynamic_strategy_request -@pytest.fixture +@pytest.fixture(scope="class") def client(enterprise_config: HttpConfiguration) -> NotificationClient: """Fixture to create a Notification client.""" return NotificationClient(enterprise_config) @@ -73,82 +61,73 @@ def client(enterprise_config: HttpConfiguration) -> NotificationClient: @pytest.mark.integration @pytest.mark.enterprise class TestNotificationClient: - request = GenerateRequest.getRequestBody() - def test__apply_strategy_with_correct_request__returns_none( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - assert client.apply_notification_strategy(request=self.request) is None + assert client.apply_notification_strategy(request=request_model) is None def test__apply_strategy_with_empty_template_substitution_fields__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.message_template_substitution_fields = None + request_model.message_template_substitution_fields = None with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) def test__apply_strategy_with_no_recipient__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.notification_strategy.notification_configurations[ + request_model.notification_strategy.notification_configurations[ 0 ].address_group.fields = {} with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) def test__apply_strategy_with_invalid_recipient__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.notification_strategy.notification_configurations[ + request_model.notification_strategy.notification_configurations[ 0 ].address_group.fields = {"toAddresses": ["sample"]} with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) def test__apply_strategy_with_no_configurations__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.notification_strategy.notification_configurations = [] + request_model.notification_strategy.notification_configurations = [] with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) def test__apply_strategy_with_empty_address_groups__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.notification_strategy.notification_configurations[ + request_model.notification_strategy.notification_configurations[ 0 ].address_group = None with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) def test__apply_strategy_with_empty_message_template__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.notification_strategy.notification_configurations[ + request_model.notification_strategy.notification_configurations[ 0 ].message_template = None with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) def test__apply_strategy_with_invalid_message_template_fields__raises_exception( - self, client: NotificationClient + self, client: NotificationClient, request_model: DynamicStrategyRequest ): - fetched_request = deepcopy(self.request) - fetched_request.notification_strategy.notification_configurations[ + request_model.notification_strategy.notification_configurations[ 0 ].message_template.fields = {"bodyTemplate": "body"} with pytest.raises(ApiException): - client.apply_notification_strategy(request=fetched_request) + client.apply_notification_strategy(request=request_model) From 63437c7f39d14adf50cba91e5a28955546449097 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 22 Jan 2026 18:08:02 +0530 Subject: [PATCH 07/33] refactor: Minor lint changes, used enum for required fields --- examples/notification/notification.py | 13 ++++++-- .../notification/_notification_client.py | 2 +- .../notification/models/_address_group.py | 26 +++++++++------- .../models/_dynamic_strategy_request.py | 4 +-- .../notification/models/_message_template.py | 16 ++++++---- .../models/_notification_configuration.py | 4 +-- .../models/_notification_strategy.py | 4 ++- .../notification/test_notification_client.py | 30 ++++++++++++------- 8 files changed, 64 insertions(+), 35 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 45972c23..89af8af5 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -3,8 +3,10 @@ from nisystemlink.clients.core import ApiException, HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( + AddressFields, AddressGroup, DynamicStrategyRequest, + MessageFieldTemplates, MessageTemplate, NotificationConfiguration, NotificationStrategy, @@ -34,7 +36,11 @@ interpreting_service_name="smtp", display_name="Address group name", properties={"property": "value"}, - fields={"toAddresses": ["address1@example.com"]}, + fields={ + AddressFields.toAddresses: ["address1@example.com"], + AddressFields.ccAddresses: ["address2@example.com"], + AddressFields.bccAddresses: ["address3@example.com"], + }, referencing_notification_strategies=["reference_notification_strategy"], ) @@ -44,7 +50,10 @@ interpreting_service_name="smtp", display_name="Message template name", properties={"property": "value"}, - fields={"subjectTemplate": "subject", "bodyTemplate": "body"}, + fields={ + MessageFieldTemplates.subjectTemplate: "subject", + MessageFieldTemplates.bodyTemplate: "body", + }, referencing_notification_strategies=["reference_notification_strategy"], ) diff --git a/nisystemlink/clients/notification/_notification_client.py b/nisystemlink/clients/notification/_notification_client.py index 4f05eeed..6d7e91ef 100644 --- a/nisystemlink/clients/notification/_notification_client.py +++ b/nisystemlink/clients/notification/_notification_client.py @@ -1,9 +1,9 @@ """Implementation of Notification Client""" -from uplink import retry from nisystemlink.clients import core from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import post +from uplink import retry from . import models diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 52e49621..fdd9cb3a 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,12 +1,14 @@ -from typing import Dict, List, TypedDict +from enum import Enum +from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -class AddressFields(TypedDict, total=False): - toAddresses: List[str] - ccAddresses: List[str] - bccAddresses: List[str] +class AddressFields(str, Enum): + """Defines valid fields for address group.""" + toAddresses = "toAddresses" + ccAddresses = "ccAddresses" + bccAddresses = "bccAddresses" class AddressGroup(JsonModel): @@ -33,21 +35,23 @@ class AddressGroup(JsonModel): Example: { "property": "value" } """ - fields: AddressFields + fields: Dict[AddressFields, List[str]] """Gets or sets the address group's fields. Requires at least one valid recipient. Valid fields: - toAddresses - ccAddresses - bccAddresses - + Example: - { - "toAddresses": [ "address1@example.com" ], - "ccAddresses": [ "address2@example.com" ], - "bccAddresses": [ "address3@example.com" ] + { + AddressFields.toAddresses: [ "address1@example.com" ], + AddressFields.ccAddresses: [ "address2@example.com" ], + AddressFields.bccAddresses: [ "address3@example.com" ] } """ referencing_notification_strategies: List[str] """Gets or sets the address group's referencing notification strategies.""" + + model_config = {"use_enum_values": True} diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py index 2699b827..5f7c682c 100644 --- a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -1,9 +1,9 @@ from typing import Dict -from ._notification_strategy import NotificationStrategy - from nisystemlink.clients.core._uplink._json_model import JsonModel +from ._notification_strategy import NotificationStrategy + class DynamicStrategyRequest(JsonModel): """Request model for applying a notification strategy.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index 95e9fd1e..e11e4a45 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,11 +1,13 @@ -from typing import Dict, List, TypedDict +from enum import Enum +from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -class MessageFieldTemplates(TypedDict, total=False): - subjectTemplate: str - bodyTemplate: str +class MessageFieldTemplates(str, Enum): + """Defines valid fields for message template.""" + subjectTemplate = "subjectTemplate" + bodyTemplate = "bodyTemplate" class MessageTemplate(JsonModel): @@ -32,15 +34,17 @@ class MessageTemplate(JsonModel): Example: { "property": "value" } """ - fields: MessageFieldTemplates + fields: Dict[MessageFieldTemplates, str] """Gets or sets the message template's fields. Valid fields: - subjectTemplate (required) - bodyTemplate - Example: { "subjectTemplate": "subject", "bodyTemplate": "body" } + Example: { MessageFieldTemplates.subjectTemplate: "subject", MessageFieldTemplates.bodyTemplate: "body" } """ referencing_notification_strategies: List[str] """Gets or sets the message template's referencing notification strategies.""" + + model_config = {"use_enum_values": True} diff --git a/nisystemlink/clients/notification/models/_notification_configuration.py b/nisystemlink/clients/notification/models/_notification_configuration.py index 6f8800ed..b93a46a3 100644 --- a/nisystemlink/clients/notification/models/_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_notification_configuration.py @@ -1,8 +1,8 @@ +from nisystemlink.clients.core._uplink._json_model import JsonModel + from ._address_group import AddressGroup from ._message_template import MessageTemplate -from nisystemlink.clients.core._uplink._json_model import JsonModel - class NotificationConfiguration(JsonModel): """Model for notification configuration defining address groups and message template for the notification.""" diff --git a/nisystemlink/clients/notification/models/_notification_strategy.py b/nisystemlink/clients/notification/models/_notification_strategy.py index 1760436b..419042b7 100644 --- a/nisystemlink/clients/notification/models/_notification_strategy.py +++ b/nisystemlink/clients/notification/models/_notification_strategy.py @@ -1,7 +1,9 @@ from typing import List -from ._notification_configuration import NotificationConfiguration + from nisystemlink.clients.core._uplink._json_model import JsonModel +from ._notification_configuration import NotificationConfiguration + class NotificationStrategy(JsonModel): """Model for the notification strategy to be applied.""" diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 90a7bf23..708b9bb0 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -1,11 +1,16 @@ import pytest - from nisystemlink.clients.core import ApiException from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( - AddressGroup, DynamicStrategyRequest, MessageTemplate, - NotificationConfiguration, NotificationStrategy) + AddressFields, + AddressGroup, + DynamicStrategyRequest, + MessageFieldTemplates, + MessageTemplate, + NotificationConfiguration, + NotificationStrategy, +) @pytest.fixture @@ -17,7 +22,9 @@ def request_model(): display_name="name", properties={"property": "value"}, fields={ - "toAddresses": ["address1@example.com"], + AddressFields.toAddresses: ["address1@example.com"], + AddressFields.ccAddresses: ["address2@example.com"], + AddressFields.bccAddresses: ["address3@example.com"], }, referencing_notification_strategies=["reference_notification_strategy"], ) @@ -27,7 +34,10 @@ def request_model(): interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, - fields={"subjectTemplate": "subject", "bodyTemplate": "body"}, + fields={ + MessageFieldTemplates.subjectTemplate: "subject", + MessageFieldTemplates.bodyTemplate: "body", + }, referencing_notification_strategies=["reference_notification_strategy"], ) @@ -69,7 +79,7 @@ def test__apply_strategy_with_correct_request__returns_none( def test__apply_strategy_with_empty_template_substitution_fields__raises_exception( self, client: NotificationClient, request_model: DynamicStrategyRequest ): - request_model.message_template_substitution_fields = None + request_model.message_template_substitution_fields = None # type: ignore[assignment] with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) @@ -89,7 +99,7 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( ): request_model.notification_strategy.notification_configurations[ 0 - ].address_group.fields = {"toAddresses": ["sample"]} + ].address_group.fields = {AddressFields.toAddresses: ["sample"]} with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) @@ -107,7 +117,7 @@ def test__apply_strategy_with_empty_address_groups__raises_exception( ): request_model.notification_strategy.notification_configurations[ 0 - ].address_group = None + ].address_group = None # type: ignore[assignment] with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) @@ -117,7 +127,7 @@ def test__apply_strategy_with_empty_message_template__raises_exception( ): request_model.notification_strategy.notification_configurations[ 0 - ].message_template = None + ].message_template = None # type: ignore[assignment] with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) @@ -127,7 +137,7 @@ def test__apply_strategy_with_invalid_message_template_fields__raises_exception( ): request_model.notification_strategy.notification_configurations[ 0 - ].message_template.fields = {"bodyTemplate": "body"} + ].message_template.fields = {MessageFieldTemplates.bodyTemplate: "body"} with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) From 2a316916a2e40c6382f09180ec1a7d6c4466879b Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 23 Jan 2026 20:57:41 +0530 Subject: [PATCH 08/33] refactor: Refactored model to handle nullable fields --- examples/notification/notification.py | 35 +++--- .../clients/notification/models/__init__.py | 6 +- .../notification/models/_address_group.py | 37 ++++--- ...=> _dynamic_notification_configuration.py} | 10 +- ...y.py => _dynamic_notification_strategy.py} | 6 +- .../models/_dynamic_strategy_request.py | 8 +- .../notification/models/_message_template.py | 31 +++--- .../notification/test_notification_client.py | 102 +++++++++--------- 8 files changed, 118 insertions(+), 117 deletions(-) rename nisystemlink/clients/notification/models/{_notification_configuration.py => _dynamic_notification_configuration.py} (68%) rename nisystemlink/clients/notification/models/{_notification_strategy.py => _dynamic_notification_strategy.py} (54%) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 89af8af5..aa5c2c7e 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -5,11 +5,11 @@ from nisystemlink.clients.notification.models import ( AddressFields, AddressGroup, + DynamicNotificationConfiguration, + DynamicNotificationStrategy, DynamicStrategyRequest, - MessageFieldTemplates, MessageTemplate, - NotificationConfiguration, - NotificationStrategy, + MessageTemplateFields, ) # Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink @@ -36,11 +36,11 @@ interpreting_service_name="smtp", display_name="Address group name", properties={"property": "value"}, - fields={ - AddressFields.toAddresses: ["address1@example.com"], - AddressFields.ccAddresses: ["address2@example.com"], - AddressFields.bccAddresses: ["address3@example.com"], - }, + fields=AddressFields( + toAddresses=["sample1@example.com"], + ccAddresses=["sample2@example.com"], + bccAddresses=["sample3@example.com"], + ), referencing_notification_strategies=["reference_notification_strategy"], ) @@ -50,15 +50,14 @@ interpreting_service_name="smtp", display_name="Message template name", properties={"property": "value"}, - fields={ - MessageFieldTemplates.subjectTemplate: "subject", - MessageFieldTemplates.bodyTemplate: "body", - }, + fields=MessageTemplateFields( + subject_template="Sample Subject", body_template="Sample Body" + ), referencing_notification_strategies=["reference_notification_strategy"], ) # Create notification configuration -notification_config = NotificationConfiguration( +dynamic_notification_config = DynamicNotificationConfiguration( address_group_id=address_group_id, message_template_id=message_template_id, address_group=address_group, @@ -66,18 +65,18 @@ ) # Create notification strategy -notification_strategy = NotificationStrategy( - notification_configurations=[notification_config] +dynamic_notification_strategy = DynamicNotificationStrategy( + notification_configurations=[dynamic_notification_config] ) # Create request for applying strategy -apply_strategy_request = DynamicStrategyRequest( +dynamic_strategy_request = DynamicStrategyRequest( message_template_substitution_fields={"replacement": "value"}, - notification_strategy=notification_strategy, + notification_strategy=dynamic_notification_strategy, ) try: - client.apply_notification_strategy(request=apply_strategy_request) + client.apply_notification_strategy(request=dynamic_strategy_request) print("Notification strategy applied successfully") except ApiException as e: if e.http_status_code == 400: diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py index 0a555d59..2259fd10 100644 --- a/nisystemlink/clients/notification/models/__init__.py +++ b/nisystemlink/clients/notification/models/__init__.py @@ -1,7 +1,7 @@ from ._address_group import AddressGroup, AddressFields from ._dynamic_strategy_request import DynamicStrategyRequest -from ._message_template import MessageTemplate, MessageFieldTemplates -from ._notification_configuration import NotificationConfiguration -from ._notification_strategy import NotificationStrategy +from ._message_template import MessageTemplate, MessageTemplateFields +from ._dynamic_notification_configuration import DynamicNotificationConfiguration +from ._dynamic_notification_strategy import DynamicNotificationStrategy # flake8: noqa diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index fdd9cb3a..9cb664d0 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,20 +1,25 @@ -from enum import Enum from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -class AddressFields(str, Enum): - """Defines valid fields for address group.""" - toAddresses = "toAddresses" - ccAddresses = "ccAddresses" - bccAddresses = "bccAddresses" +class AddressFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + toAddresses: List[str] | None = None + """List of primary recipient addresses.""" + + ccAddresses: List[str] | None = None + """List of carbon copy recipient addresses.""" + + bccAddresses: List[str] | None = None + """List of blind carbon copy recipient addresses.""" class AddressGroup(JsonModel): - """Model for address group defining notification recipients.""" + """Model defining notification recipients.""" - id: str + id: str | None = None """Gets or sets the ID for address group.""" interpreting_service_name: str @@ -23,19 +28,19 @@ class AddressGroup(JsonModel): Example: "smtp" """ - display_name: str + display_name: str | None = None """Gets or sets the address group's display name. Example: "name" """ - properties: Dict[str, str] + properties: Dict[str, str] | None = None """Gets or sets the address group's properties. Example: { "property": "value" } """ - fields: Dict[AddressFields, List[str]] + fields: AddressFields """Gets or sets the address group's fields. Requires at least one valid recipient. Valid fields: @@ -45,13 +50,11 @@ class AddressGroup(JsonModel): Example: { - AddressFields.toAddresses: [ "address1@example.com" ], - AddressFields.ccAddresses: [ "address2@example.com" ], - AddressFields.bccAddresses: [ "address3@example.com" ] + toAddresses: [ "address1@example.com" ], + ccAddresses: [ "address2@example.com" ], + bccAddresses: [ "address3@example.com" ] } """ - referencing_notification_strategies: List[str] + referencing_notification_strategies: List[str] | None = None """Gets or sets the address group's referencing notification strategies.""" - - model_config = {"use_enum_values": True} diff --git a/nisystemlink/clients/notification/models/_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py similarity index 68% rename from nisystemlink/clients/notification/models/_notification_configuration.py rename to nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index b93a46a3..f964cce9 100644 --- a/nisystemlink/clients/notification/models/_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -4,17 +4,17 @@ from ._message_template import MessageTemplate -class NotificationConfiguration(JsonModel): +class DynamicNotificationConfiguration(JsonModel): """Model for notification configuration defining address groups and message template for the notification.""" - address_group_id: str + address_group_id: str | None = None """Gets the address group ID""" - message_template_id: str + message_template_id: str | None = None """Gets the message template ID""" - address_group: AddressGroup + address_group: AddressGroup | None = None """Gets the address group defining notification recipients.""" - message_template: MessageTemplate + message_template: MessageTemplate | None = None """Gets the message template defining notification content structure""" diff --git a/nisystemlink/clients/notification/models/_notification_strategy.py b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py similarity index 54% rename from nisystemlink/clients/notification/models/_notification_strategy.py rename to nisystemlink/clients/notification/models/_dynamic_notification_strategy.py index 419042b7..e82cff32 100644 --- a/nisystemlink/clients/notification/models/_notification_strategy.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py @@ -2,11 +2,11 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._notification_configuration import NotificationConfiguration +from ._dynamic_notification_configuration import DynamicNotificationConfiguration -class NotificationStrategy(JsonModel): +class DynamicNotificationStrategy(JsonModel): """Model for the notification strategy to be applied.""" - notification_configurations: List[NotificationConfiguration] + notification_configurations: List[DynamicNotificationConfiguration] """Gets the notification configurations associated with this strategy.""" diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py index 5f7c682c..d53f7f9a 100644 --- a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -2,17 +2,17 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._notification_strategy import NotificationStrategy +from ._dynamic_notification_strategy import DynamicNotificationStrategy class DynamicStrategyRequest(JsonModel): - """Request model for applying a notification strategy.""" + """Request model for applying a dynamic notification strategy.""" - message_template_substitution_fields: Dict[str, str] + message_template_substitution_fields: Dict[str, str] | None = None """Gets or sets the message template substitution fields. Example: { "replacement": "value" } """ - notification_strategy: NotificationStrategy + notification_strategy: DynamicNotificationStrategy """Gets or sets the notification strategy containing configurations for address groups and message templates.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index e11e4a45..e2716d7e 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,50 +1,51 @@ -from enum import Enum from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -class MessageFieldTemplates(str, Enum): - """Defines valid fields for message template.""" - subjectTemplate = "subjectTemplate" - bodyTemplate = "bodyTemplate" +class MessageTemplateFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + subject_template: str + """Subject template of the message.""" + + body_template: str | None = None + """Body template of the message.""" class MessageTemplate(JsonModel): - """Model for message template defining notification content structure.""" + """Model defining the notification content structure.""" - id: str + id: str | None = None """Gets or sets the ID for message template.""" - interpreting_service_name: str + interpreting_service_name: str | None = None """Gets or sets the name of the interpreting service. Example: "smtp" """ - display_name: str + display_name: str | None = None """Gets or sets the message template's display name. Example: "name" """ - properties: Dict[str, str] + properties: Dict[str, str] | None = None """Gets or sets the message template's properties. Example: { "property": "value" } """ - fields: Dict[MessageFieldTemplates, str] + fields: MessageTemplateFields """Gets or sets the message template's fields. Valid fields: - subjectTemplate (required) - bodyTemplate - Example: { MessageFieldTemplates.subjectTemplate: "subject", MessageFieldTemplates.bodyTemplate: "body" } + Example: { subjectTemplate: "subject", bodyTemplate: "body" } """ - referencing_notification_strategies: List[str] + referencing_notification_strategies: List[str] | None = None """Gets or sets the message template's referencing notification strategies.""" - - model_config = {"use_enum_values": True} diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 708b9bb0..3bb7b5fa 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -5,11 +5,11 @@ from nisystemlink.clients.notification.models import ( AddressFields, AddressGroup, + DynamicNotificationConfiguration, + DynamicNotificationStrategy, DynamicStrategyRequest, - MessageFieldTemplates, MessageTemplate, - NotificationConfiguration, - NotificationStrategy, + MessageTemplateFields, ) @@ -21,34 +21,29 @@ def request_model(): interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, - fields={ - AddressFields.toAddresses: ["address1@example.com"], - AddressFields.ccAddresses: ["address2@example.com"], - AddressFields.bccAddresses: ["address3@example.com"], - }, + fields=AddressFields( + toAddresses=["address1@example.com"], + ccAddresses=["address2@example.com"], + bccAddresses=["address3@example.com"], + ), referencing_notification_strategies=["reference_notification_strategy"], ) - _message_template = MessageTemplate( - id="address_group_id", + id="message_group_id", interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, - fields={ - MessageFieldTemplates.subjectTemplate: "subject", - MessageFieldTemplates.bodyTemplate: "body", - }, + fields=MessageTemplateFields(subject_template="subject", body_template="body"), referencing_notification_strategies=["reference_notification_strategy"], ) - - _notification_configuration = NotificationConfiguration( + _notification_configuration = DynamicNotificationConfiguration( address_group_id="address_group_id", - message_template_id="message_template_id", + message_template_id="message_group_id", address_group=_address_group, message_template=_message_template, ) - _notification_strategy = NotificationStrategy( + _notification_strategy = DynamicNotificationStrategy( notification_configurations=[ _notification_configuration, ] @@ -76,30 +71,15 @@ def test__apply_strategy_with_correct_request__returns_none( ): assert client.apply_notification_strategy(request=request_model) is None - def test__apply_strategy_with_empty_template_substitution_fields__raises_exception( - self, client: NotificationClient, request_model: DynamicStrategyRequest - ): - request_model.message_template_substitution_fields = None # type: ignore[assignment] - - with pytest.raises(ApiException): - client.apply_notification_strategy(request=request_model) - - def test__apply_strategy_with_no_recipient__raises_exception( - self, client: NotificationClient, request_model: DynamicStrategyRequest - ): - request_model.notification_strategy.notification_configurations[ - 0 - ].address_group.fields = {} - - with pytest.raises(ApiException): - client.apply_notification_strategy(request=request_model) - def test__apply_strategy_with_invalid_recipient__raises_exception( self, client: NotificationClient, request_model: DynamicStrategyRequest ): - request_model.notification_strategy.notification_configurations[ + address_group = request_model.notification_strategy.notification_configurations[ 0 - ].address_group.fields = {AddressFields.toAddresses: ["sample"]} + ].address_group + + assert address_group is not None + address_group.fields = AddressFields(toAddresses=["invalid-email"]) with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) @@ -112,32 +92,50 @@ def test__apply_strategy_with_no_configurations__raises_exception( with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) - def test__apply_strategy_with_empty_address_groups__raises_exception( + def test__apply_strategy_with_empty_subject_template_fields__raises_exception( self, client: NotificationClient, request_model: DynamicStrategyRequest ): - request_model.notification_strategy.notification_configurations[ - 0 - ].address_group = None # type: ignore[assignment] + message_template = ( + request_model.notification_strategy.notification_configurations[ + 0 + ].message_template + ) + assert message_template is not None + message_template.fields.subject_template = "" with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) - def test__apply_strategy_with_empty_message_template__raises_exception( + def test__apply_strategy_with_invalid_interpreting_service_name__raises_exception( self, client: NotificationClient, request_model: DynamicStrategyRequest ): - request_model.notification_strategy.notification_configurations[ + address_group = request_model.notification_strategy.notification_configurations[ 0 - ].message_template = None # type: ignore[assignment] + ].address_group + assert address_group is not None + address_group.interpreting_service_name = "" with pytest.raises(ApiException): client.apply_notification_strategy(request=request_model) - def test__apply_strategy_with_invalid_message_template_fields__raises_exception( + def test__apply_configuration_with_no_address_and_message_template_id__returns_none( self, client: NotificationClient, request_model: DynamicStrategyRequest ): - request_model.notification_strategy.notification_configurations[ - 0 - ].message_template.fields = {MessageFieldTemplates.bodyTemplate: "body"} - - with pytest.raises(ApiException): - client.apply_notification_strategy(request=request_model) + temp_request = request_model + + configuration = DynamicNotificationConfiguration( + address_group=request_model.notification_strategy.notification_configurations[ + 0 + ].address_group, + message_template=request_model.notification_strategy.notification_configurations[ + 0 + ].message_template, + ) + + temp_request = DynamicStrategyRequest( + message_template_substitution_fields=request_model.message_template_substitution_fields, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[configuration] + ), + ) + assert client.apply_notification_strategy(request=temp_request) is None From 035457da02b0103b85107177f347b3d11ee10d35 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Tue, 27 Jan 2026 12:12:42 +0530 Subject: [PATCH 09/33] docs: Change in config docs --- .../models/_dynamic_notification_configuration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index f964cce9..a2c81e49 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -5,7 +5,10 @@ class DynamicNotificationConfiguration(JsonModel): - """Model for notification configuration defining address groups and message template for the notification.""" + """Model for notification configuration defining address groups and message template for the notification. + + Requires at least one of addressGroupId or addressGroup, and one of messageTemplateId or messageTemplate. + """ address_group_id: str | None = None """Gets the address group ID""" From 2aa9201d71ff676c734f469b99e5f05f94ac88ad Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Tue, 27 Jan 2026 14:43:37 +0530 Subject: [PATCH 10/33] docs: Updated getting_started.rst --- docs/getting_started.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 3da67ca4..f501ef35 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -505,3 +505,30 @@ Create, query and delete test plan templates. .. literalinclude:: ../examples/test_plan/test_plan_templates.py :language: python :linenos: + +Notification API +---------------- + +Overview +~~~~~~~~ + +The :class:`.NotificationClient` class is the primary entry point of the Notification API. + +When constructing a :class:`.NotificationClient`, you can pass an +:class:`.HttpConfiguration` (like one retrieved from the +:class:`.HttpConfigurationManager`), or let :class:`.NotificationClient` use the +default connection. The default connection depends on your environment. + +With a :class:`.NotificationClient` object, you can: + +* Apply dynamic notification strategy using :meth:`~.NotificationCLient. +apply_notification_strategy` + +Examples +~~~~~~~~ + +Apply a notification strategy + +.. literalinclude:: ../examples/notification/notification.py + :language: python + :linenos: \ No newline at end of file From 74e2713333b8f1a2bec8c4c1794cfede860186b1 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 11:24:09 +0530 Subject: [PATCH 11/33] refactor: Refactored changes mentioned --- .../notification/_notification_client.py | 5 +++- .../notification/models/_address_group.py | 24 +++------------ .../notification/models/_common_meta_data.py | 29 ++++++++++++++++++ .../notification/models/_message_template.py | 30 ++----------------- 4 files changed, 40 insertions(+), 48 deletions(-) create mode 100644 nisystemlink/clients/notification/models/_common_meta_data.py diff --git a/nisystemlink/clients/notification/_notification_client.py b/nisystemlink/clients/notification/_notification_client.py index 6d7e91ef..046f5756 100644 --- a/nisystemlink/clients/notification/_notification_client.py +++ b/nisystemlink/clients/notification/_notification_client.py @@ -24,7 +24,7 @@ def __init__(self, configuration: core.HttpConfiguration | None = None): is used to obtain the configuration. Raises: - ApiException: if unable to communicate with the Notification. + ApiException: if unable to communicate with the `/ninotification` service. """ if configuration is None: configuration = core.HttpConfigurationManager.get_configuration() @@ -42,5 +42,8 @@ def apply_notification_strategy( Returns: None. + + Raises: + ApiException: if unable to communicate with the `/ninotification` service or provided invalid arguments. """ ... diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 9cb664d0..f059fc90 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,7 +1,9 @@ -from typing import Dict, List +from typing import List from nisystemlink.clients.core._uplink._json_model import JsonModel +from ._common_meta_data import CommonFields + class AddressFields(JsonModel): """Fields representing the subject and body templates of a message.""" @@ -16,30 +18,15 @@ class AddressFields(JsonModel): """List of blind carbon copy recipient addresses.""" -class AddressGroup(JsonModel): +class AddressGroup(CommonFields): """Model defining notification recipients.""" - id: str | None = None - """Gets or sets the ID for address group.""" - interpreting_service_name: str """Gets or sets the name of the interpreting service. Example: "smtp" """ - display_name: str | None = None - """Gets or sets the address group's display name. - - Example: "name" - """ - - properties: Dict[str, str] | None = None - """Gets or sets the address group's properties. - - Example: { "property": "value" } - """ - fields: AddressFields """Gets or sets the address group's fields. Requires at least one valid recipient. @@ -55,6 +42,3 @@ class AddressGroup(JsonModel): bccAddresses: [ "address3@example.com" ] } """ - - referencing_notification_strategies: List[str] | None = None - """Gets or sets the address group's referencing notification strategies.""" diff --git a/nisystemlink/clients/notification/models/_common_meta_data.py b/nisystemlink/clients/notification/models/_common_meta_data.py new file mode 100644 index 00000000..459ca24f --- /dev/null +++ b/nisystemlink/clients/notification/models/_common_meta_data.py @@ -0,0 +1,29 @@ +from typing import Dict, List + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class CommonFields(JsonModel): + id: str | None = None + """Gets or sets the ID""" + + interpreting_service_name: str | None = None + """Gets or sets the name of the interpreting service. + + Example: "smtp" + """ + + display_name: str | None = None + """Gets or sets the display name. + + Example: "name" + """ + + properties: Dict[str, str] | None = None + """Gets or sets the properties. + + Example: { "property": "value" } + """ + + referencing_notification_strategies: List[str] | None = None + """Gets or sets the referencing notification strategies.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index e2716d7e..a0c92bdc 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,7 +1,7 @@ -from typing import Dict, List - from nisystemlink.clients.core._uplink._json_model import JsonModel +from ._common_meta_data import CommonFields + class MessageTemplateFields(JsonModel): """Fields representing the subject and body templates of a message.""" @@ -13,30 +13,9 @@ class MessageTemplateFields(JsonModel): """Body template of the message.""" -class MessageTemplate(JsonModel): +class MessageTemplate(CommonFields): """Model defining the notification content structure.""" - id: str | None = None - """Gets or sets the ID for message template.""" - - interpreting_service_name: str | None = None - """Gets or sets the name of the interpreting service. - - Example: "smtp" - """ - - display_name: str | None = None - """Gets or sets the message template's display name. - - Example: "name" - """ - - properties: Dict[str, str] | None = None - """Gets or sets the message template's properties. - - Example: { "property": "value" } - """ - fields: MessageTemplateFields """Gets or sets the message template's fields. @@ -46,6 +25,3 @@ class MessageTemplate(JsonModel): Example: { subjectTemplate: "subject", bodyTemplate: "body" } """ - - referencing_notification_strategies: List[str] | None = None - """Gets or sets the message template's referencing notification strategies.""" From 5cc4edfa22e0edd086bd2c6d4a02ace6cf1f6b3e Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 11:24:38 +0530 Subject: [PATCH 12/33] refactor: Refactored models used in test --- .../notification/test_notification_client.py | 160 +++++++++++++----- 1 file changed, 113 insertions(+), 47 deletions(-) diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 3bb7b5fa..3ab71c50 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -14,9 +14,9 @@ @pytest.fixture -def request_model(): - """Returns the created request.""" - _address_group = AddressGroup( +def _address_group(): + """Returns the created address group.""" + return AddressGroup( id="address_group_id", interpreting_service_name="smtp", display_name="name", @@ -28,7 +28,12 @@ def request_model(): ), referencing_notification_strategies=["reference_notification_strategy"], ) - _message_template = MessageTemplate( + + +@pytest.fixture +def _message_template(): + """Returns the created message template.""" + return MessageTemplate( id="message_group_id", interpreting_service_name="smtp", display_name="name", @@ -36,26 +41,43 @@ def request_model(): fields=MessageTemplateFields(subject_template="subject", body_template="body"), referencing_notification_strategies=["reference_notification_strategy"], ) - _notification_configuration = DynamicNotificationConfiguration( + + +@pytest.fixture +def _notification_configuration( + _address_group: AddressGroup, _message_template: MessageTemplate +): + """Returns the created notification configuration.""" + return DynamicNotificationConfiguration( address_group_id="address_group_id", message_template_id="message_group_id", address_group=_address_group, message_template=_message_template, ) - _notification_strategy = DynamicNotificationStrategy( + +@pytest.fixture +def _notification_strategy( + _notification_configuration: DynamicNotificationConfiguration, +): + """Returns the created notification strategy.""" + return DynamicNotificationStrategy( notification_configurations=[ _notification_configuration, ] ) - _dynamic_strategy_request = DynamicStrategyRequest( + +@pytest.fixture +def request_model( + _notification_strategy: DynamicNotificationStrategy, +): + """Returns the created request.""" + return DynamicStrategyRequest( message_template_substitution_fields={"replacement": "value"}, notification_strategy=_notification_strategy, ) - return _dynamic_strategy_request - @pytest.fixture(scope="class") def client(enterprise_config: HttpConfiguration) -> NotificationClient: @@ -72,70 +94,114 @@ def test__apply_strategy_with_correct_request__returns_none( assert client.apply_notification_strategy(request=request_model) is None def test__apply_strategy_with_invalid_recipient__raises_exception( - self, client: NotificationClient, request_model: DynamicStrategyRequest + self, + client: NotificationClient, + _message_template: MessageTemplate, ): - address_group = request_model.notification_strategy.notification_configurations[ - 0 - ].address_group + address_group = AddressGroup( + interpreting_service_name="smtp", + fields=AddressFields(toAddresses=["invalid-email"]), + ) - assert address_group is not None - address_group.fields = AddressFields(toAddresses=["invalid-email"]) + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=_message_template, + ) + ] + ), + ) - with pytest.raises(ApiException): + with pytest.raises(ApiException) as exc_info: client.apply_notification_strategy(request=request_model) + assert exc_info.value.http_status_code == 400 + def test__apply_strategy_with_no_configurations__raises_exception( - self, client: NotificationClient, request_model: DynamicStrategyRequest + self, client: NotificationClient ): - request_model.notification_strategy.notification_configurations = [] + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[] + ), + ) - with pytest.raises(ApiException): + with pytest.raises(ApiException) as exc_info: client.apply_notification_strategy(request=request_model) + assert exc_info.value.http_status_code == 400 + def test__apply_strategy_with_empty_subject_template_fields__raises_exception( - self, client: NotificationClient, request_model: DynamicStrategyRequest + self, client: NotificationClient, _address_group: AddressGroup ): - message_template = ( - request_model.notification_strategy.notification_configurations[ - 0 - ].message_template + message_template = MessageTemplate( + interpreting_service_name="smtp", + fields=MessageTemplateFields(subject_template=""), ) - assert message_template is not None - message_template.fields.subject_template = "" - with pytest.raises(ApiException): + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=_address_group, + message_template=message_template, + ) + ] + ), + ) + + with pytest.raises(ApiException) as exc_info: client.apply_notification_strategy(request=request_model) + assert exc_info.value.http_status_code == 400 + def test__apply_strategy_with_invalid_interpreting_service_name__raises_exception( - self, client: NotificationClient, request_model: DynamicStrategyRequest + self, client: NotificationClient, _message_template: MessageTemplate ): - address_group = request_model.notification_strategy.notification_configurations[ - 0 - ].address_group - assert address_group is not None - address_group.interpreting_service_name = "" + address_group = AddressGroup( + interpreting_service_name="invalid_service", + fields=AddressFields(toAddresses=["address1@example.com"]), + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=_message_template, + ) + ] + ), + ) - with pytest.raises(ApiException): + with pytest.raises(ApiException) as exc_info: client.apply_notification_strategy(request=request_model) + assert exc_info.value.http_status_code == 400 + def test__apply_configuration_with_no_address_and_message_template_id__returns_none( - self, client: NotificationClient, request_model: DynamicStrategyRequest + self, + client: NotificationClient, + _address_group: AddressGroup, + _message_template: MessageTemplate, ): - temp_request = request_model - configuration = DynamicNotificationConfiguration( - address_group=request_model.notification_strategy.notification_configurations[ - 0 - ].address_group, - message_template=request_model.notification_strategy.notification_configurations[ - 0 - ].message_template, + address_group=_address_group, + message_template=_message_template, ) - temp_request = DynamicStrategyRequest( - message_template_substitution_fields=request_model.message_template_substitution_fields, + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, notification_strategy=DynamicNotificationStrategy( - notification_configurations=[configuration] + notification_configurations=[ + configuration, + ] ), ) - assert client.apply_notification_strategy(request=temp_request) is None + assert client.apply_notification_strategy(request=request_model) is None From 5c4212729851bb253edf5050d61f67ec5a08815c Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 11:24:52 +0530 Subject: [PATCH 13/33] refactor: Updated example --- examples/notification/notification.py | 167 +++++++++++++++++++------- 1 file changed, 122 insertions(+), 45 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index aa5c2c7e..812e43d2 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -1,6 +1,16 @@ import uuid +from datetime import datetime -from nisystemlink.clients.core import ApiException, HttpConfiguration +# from urllib.parse import urlsplit, urlunsplit + +from nisystemlink.clients.alarm import AlarmClient +from nisystemlink.clients.alarm.models._alarm import Alarm, AlarmSeverityLevel +from nisystemlink.clients.alarm.models._create_or_update_alarm_request import ( + ClearAlarmTransition, + CreateOrUpdateAlarmRequest, + SetAlarmTransition, +) +from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( AddressFields, @@ -13,7 +23,11 @@ ) # Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink -server_configuration: HttpConfiguration | None = None +server_configuration: HttpConfiguration | None = HttpConfiguration( + server_uri="https://test-api.lifecyclesolutions.ni.com", + api_key="TUXSZH0a6WtAVymFBUQZDjBU4lBMW1Kqn1FumfMMg6", +) + # To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment # the following lines and provide your server URI and API key. @@ -22,66 +36,129 @@ # api_key="", # ) -client = NotificationClient(configuration=server_configuration) -# Create a unique address group ID for this example -address_group_id = uuid.uuid1().hex +# Create request for applying strategy +def create_notification_request_for_alarm( + retrieved_alarm: Alarm, + address_group: AddressGroup, + message_template: MessageTemplate, +) -> DynamicStrategyRequest: + """Creates and returns a dynamic strategy request.""" + occurred_at = retrieved_alarm.most_recent_transition_occurred_at + + return DynamicStrategyRequest( + message_template_substitution_fields={ + "alarm_id": retrieved_alarm.alarm_id, + "alarm_condition": retrieved_alarm.condition, + "alarm_description": retrieved_alarm.description, + "alarm_severity": str(retrieved_alarm.current_severity_level), + "alarm_occurred_at": occurred_at.isoformat() if occurred_at else "", + }, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=message_template, + ) + ] + ), + ) + + +# Create clients for Notification and Alarm services +notification_client = NotificationClient(configuration=server_configuration) +alarm_client = AlarmClient(configuration=server_configuration) + +# Create a unique alarm ID for this example +alarm_id = f"example_alarm_{uuid.uuid1().hex}" + +# Create an alarm with a SET transition +create_alarm_request = CreateOrUpdateAlarmRequest( + alarm_id=alarm_id, + transition=SetAlarmTransition( + occurred_at=datetime.now(), + severity_level=AlarmSeverityLevel.HIGH, + value="85", + condition="Greater than 80", + short_text="Temperature is high", + detail_text="Temperature sensor reading is 85°C (higher than the configured threshold of 80°C)", + ), + description="Example alarm for notification", +) +id = alarm_client.create_or_update_alarm(create_alarm_request) +print("Alarm created successfully") + +# Get the alarm by its instance ID (the unique occurrence identifier) +retrieved_alarm = alarm_client.get_alarm(instance_id=id) -# Create a unique message template ID for this example -message_template_id = uuid.uuid1().hex +# Define recipients to notify +recipients = AddressFields(toAddresses=["sample1@example.com"]) # Create address group address_group = AddressGroup( - id=address_group_id, interpreting_service_name="smtp", - display_name="Address group name", - properties={"property": "value"}, - fields=AddressFields( - toAddresses=["sample1@example.com"], - ccAddresses=["sample2@example.com"], - bccAddresses=["sample3@example.com"], - ), - referencing_notification_strategies=["reference_notification_strategy"], + fields=recipients, ) -# Create message template -message_template = MessageTemplate( - id=message_template_id, +# Create mail template for alarm creation notification +alarm_creation_template = MessageTemplate( interpreting_service_name="smtp", - display_name="Message template name", - properties={"property": "value"}, + display_name="Alarm Creation Template", + fields=MessageTemplateFields( + subject_template="Alarm Created: ", + body_template="An alarm with ID has been created.\n" + "Condition: \n" + "Description: \n" + "Current severity: \n" + "Occurred At: ", + ), +) + +# Create mail template for alarm deletion notification +alarm_deletion_template = MessageTemplate( + display_name="Alarm Deletion Template", fields=MessageTemplateFields( - subject_template="Sample Subject", body_template="Sample Body" + subject_template="Alarm Cleared: ", + body_template="The alarm with ID has been cleared.\n" + "Condition: \n" + "Description: ", ), - referencing_notification_strategies=["reference_notification_strategy"], ) -# Create notification configuration -dynamic_notification_config = DynamicNotificationConfiguration( - address_group_id=address_group_id, - message_template_id=message_template_id, + +# Send notification for alarm creation +notification_for_alarm_creation = create_notification_request_for_alarm( + retrieved_alarm=retrieved_alarm, address_group=address_group, - message_template=message_template, + message_template=alarm_creation_template, ) +notification_client.apply_notification_strategy(request=notification_for_alarm_creation) +print("Notification sent for alarm creation") -# Create notification strategy -dynamic_notification_strategy = DynamicNotificationStrategy( - notification_configurations=[dynamic_notification_config] +# Clear the alarm +clear_request = CreateOrUpdateAlarmRequest( + alarm_id=alarm_id, + transition=ClearAlarmTransition( + occurred_at=datetime.now(), + condition="Temperature returned to normal", + ), ) -# Create request for applying strategy -dynamic_strategy_request = DynamicStrategyRequest( - message_template_substitution_fields={"replacement": "value"}, - notification_strategy=dynamic_notification_strategy, +result = alarm_client.create_or_update_alarm(clear_request, ignore_conflict=True) +if result is None: + print("No state change needed (alarm already in requested state)") +else: + print("Alarm cleared successfully") + +alarm_client.delete_alarm(instance_id=id) +print("Alarm deleted successfully") + +# Send notification for alarm deletion +notification_for_alarm_deletion = create_notification_request_for_alarm( + retrieved_alarm=retrieved_alarm, + address_group=address_group, + message_template=alarm_deletion_template, ) -try: - client.apply_notification_strategy(request=dynamic_strategy_request) - print("Notification strategy applied successfully") -except ApiException as e: - if e.http_status_code == 400: - print("Bad request body") - elif e.http_status_code == 401: - print("Unauthorized access") - else: - raise +notification_client.apply_notification_strategy(request=notification_for_alarm_deletion) +print("Notification sent for alarm deletion") From be56bb9241fa35cb4d7fd87a2d23f1b978d4556f Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 11:24:52 +0530 Subject: [PATCH 14/33] refactor: Updated example --- examples/notification/notification.py | 161 +++++++++++++++++++------- 1 file changed, 117 insertions(+), 44 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index aa5c2c7e..83effa93 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -1,6 +1,16 @@ import uuid +from datetime import datetime -from nisystemlink.clients.core import ApiException, HttpConfiguration +# from urllib.parse import urlsplit, urlunsplit + +from nisystemlink.clients.alarm import AlarmClient +from nisystemlink.clients.alarm.models._alarm import Alarm, AlarmSeverityLevel +from nisystemlink.clients.alarm.models._create_or_update_alarm_request import ( + ClearAlarmTransition, + CreateOrUpdateAlarmRequest, + SetAlarmTransition, +) +from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( AddressFields, @@ -22,66 +32,129 @@ # api_key="", # ) -client = NotificationClient(configuration=server_configuration) -# Create a unique address group ID for this example -address_group_id = uuid.uuid1().hex +# Create request for applying strategy +def create_notification_request_for_alarm( + retrieved_alarm: Alarm, + address_group: AddressGroup, + message_template: MessageTemplate, +) -> DynamicStrategyRequest: + """Creates and returns a dynamic strategy request.""" + occurred_at = retrieved_alarm.most_recent_transition_occurred_at + + return DynamicStrategyRequest( + message_template_substitution_fields={ + "alarm_id": retrieved_alarm.alarm_id, + "alarm_condition": retrieved_alarm.condition, + "alarm_description": retrieved_alarm.description, + "alarm_severity": str(retrieved_alarm.current_severity_level), + "alarm_occurred_at": occurred_at.isoformat() if occurred_at else "", + }, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=message_template, + ) + ] + ), + ) + + +# Create clients for Notification and Alarm services +notification_client = NotificationClient(configuration=server_configuration) +alarm_client = AlarmClient(configuration=server_configuration) + +# Create a unique alarm ID for this example +alarm_id = f"example_alarm_{uuid.uuid1().hex}" + +# Create an alarm with a SET transition +create_alarm_request = CreateOrUpdateAlarmRequest( + alarm_id=alarm_id, + transition=SetAlarmTransition( + occurred_at=datetime.now(), + severity_level=AlarmSeverityLevel.HIGH, + value="85", + condition="Greater than 80", + short_text="Temperature is high", + detail_text="Temperature sensor reading is 85°C (higher than the configured threshold of 80°C)", + ), + description="Example alarm for notification", +) +id = alarm_client.create_or_update_alarm(create_alarm_request) +print("Alarm created successfully") + +# Get the alarm by its instance ID (the unique occurrence identifier) +retrieved_alarm = alarm_client.get_alarm(instance_id=id) -# Create a unique message template ID for this example -message_template_id = uuid.uuid1().hex +# Define recipients to notify +recipients = AddressFields(toAddresses=["sample1@example.com"]) # Create address group address_group = AddressGroup( - id=address_group_id, interpreting_service_name="smtp", - display_name="Address group name", - properties={"property": "value"}, - fields=AddressFields( - toAddresses=["sample1@example.com"], - ccAddresses=["sample2@example.com"], - bccAddresses=["sample3@example.com"], - ), - referencing_notification_strategies=["reference_notification_strategy"], + fields=recipients, ) -# Create message template -message_template = MessageTemplate( - id=message_template_id, +# Create mail template for alarm creation notification +alarm_creation_template = MessageTemplate( interpreting_service_name="smtp", - display_name="Message template name", - properties={"property": "value"}, + display_name="Alarm Creation Template", + fields=MessageTemplateFields( + subject_template="Alarm Created: ", + body_template="An alarm with ID has been created.\n" + "Condition: \n" + "Description: \n" + "Current severity: \n" + "Occurred At: ", + ), +) + +# Create mail template for alarm deletion notification +alarm_deletion_template = MessageTemplate( + display_name="Alarm Deletion Template", fields=MessageTemplateFields( - subject_template="Sample Subject", body_template="Sample Body" + subject_template="Alarm Cleared: ", + body_template="The alarm with ID has been cleared.\n" + "Condition: \n" + "Description: ", ), - referencing_notification_strategies=["reference_notification_strategy"], ) -# Create notification configuration -dynamic_notification_config = DynamicNotificationConfiguration( - address_group_id=address_group_id, - message_template_id=message_template_id, + +# Send notification for alarm creation +notification_for_alarm_creation = create_notification_request_for_alarm( + retrieved_alarm=retrieved_alarm, address_group=address_group, - message_template=message_template, + message_template=alarm_creation_template, ) +notification_client.apply_notification_strategy(request=notification_for_alarm_creation) +print("Notification sent for alarm creation") -# Create notification strategy -dynamic_notification_strategy = DynamicNotificationStrategy( - notification_configurations=[dynamic_notification_config] +# Clear the alarm +clear_request = CreateOrUpdateAlarmRequest( + alarm_id=alarm_id, + transition=ClearAlarmTransition( + occurred_at=datetime.now(), + condition="Temperature returned to normal", + ), ) -# Create request for applying strategy -dynamic_strategy_request = DynamicStrategyRequest( - message_template_substitution_fields={"replacement": "value"}, - notification_strategy=dynamic_notification_strategy, +result = alarm_client.create_or_update_alarm(clear_request, ignore_conflict=True) +if result is None: + print("No state change needed (alarm already in requested state)") +else: + print("Alarm cleared successfully") + +alarm_client.delete_alarm(instance_id=id) +print("Alarm deleted successfully") + +# Send notification for alarm deletion +notification_for_alarm_deletion = create_notification_request_for_alarm( + retrieved_alarm=retrieved_alarm, + address_group=address_group, + message_template=alarm_deletion_template, ) -try: - client.apply_notification_strategy(request=dynamic_strategy_request) - print("Notification strategy applied successfully") -except ApiException as e: - if e.http_status_code == 400: - print("Bad request body") - elif e.http_status_code == 401: - print("Unauthorized access") - else: - raise +notification_client.apply_notification_strategy(request=notification_for_alarm_deletion) +print("Notification sent for alarm deletion") From dd6f99f099690c3611a0a3def30d9c9312ef4b91 Mon Sep 17 00:00:00 2001 From: S Rohith Raja Date: Wed, 28 Jan 2026 11:32:16 +0530 Subject: [PATCH 15/33] refactor: Change config --- examples/notification/notification.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 812e43d2..6ea9723b 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -23,10 +23,7 @@ ) # Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink -server_configuration: HttpConfiguration | None = HttpConfiguration( - server_uri="https://test-api.lifecyclesolutions.ni.com", - api_key="TUXSZH0a6WtAVymFBUQZDjBU4lBMW1Kqn1FumfMMg6", -) +server_configuration: HttpConfiguration | None = None # To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment From 1bb60f68995b6af6899f399933608238d7a9cb1d Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 12:37:19 +0530 Subject: [PATCH 16/33] refactor: Minor refactors --- docs/getting_started.rst | 3 +- examples/notification/notification.py | 60 +++---------------- .../notification/models/_address_group.py | 4 +- .../notification/models/_common_meta_data.py | 2 +- .../notification/models/_message_template.py | 4 +- .../notification/test_notification_client.py | 43 ++++++++++++- 6 files changed, 55 insertions(+), 61 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index f501ef35..515da5c7 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -521,8 +521,7 @@ default connection. The default connection depends on your environment. With a :class:`.NotificationClient` object, you can: -* Apply dynamic notification strategy using :meth:`~.NotificationCLient. -apply_notification_strategy` +* Apply dynamic notification strategy using :meth:`~.NotificationClient.apply_notification_strategy` Examples ~~~~~~~~ diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 6ea9723b..c0ff79c1 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -1,12 +1,9 @@ import uuid from datetime import datetime -# from urllib.parse import urlsplit, urlunsplit - from nisystemlink.clients.alarm import AlarmClient from nisystemlink.clients.alarm.models._alarm import Alarm, AlarmSeverityLevel from nisystemlink.clients.alarm.models._create_or_update_alarm_request import ( - ClearAlarmTransition, CreateOrUpdateAlarmRequest, SetAlarmTransition, ) @@ -25,7 +22,6 @@ # Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink server_configuration: HttpConfiguration | None = None - # To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment # the following lines and provide your server URI and API key. # server_configuration = HttpConfiguration( @@ -36,19 +32,19 @@ # Create request for applying strategy def create_notification_request_for_alarm( - retrieved_alarm: Alarm, + alarm: Alarm, address_group: AddressGroup, message_template: MessageTemplate, ) -> DynamicStrategyRequest: """Creates and returns a dynamic strategy request.""" - occurred_at = retrieved_alarm.most_recent_transition_occurred_at + occurred_at = alarm.most_recent_transition_occurred_at return DynamicStrategyRequest( message_template_substitution_fields={ - "alarm_id": retrieved_alarm.alarm_id, - "alarm_condition": retrieved_alarm.condition, - "alarm_description": retrieved_alarm.description, - "alarm_severity": str(retrieved_alarm.current_severity_level), + "alarm_id": alarm.alarm_id, + "alarm_condition": alarm.condition, + "alarm_description": alarm.description, + "alarm_severity": str(alarm.current_severity_level), "alarm_occurred_at": occurred_at.isoformat() if occurred_at else "", }, notification_strategy=DynamicNotificationStrategy( @@ -94,6 +90,8 @@ def create_notification_request_for_alarm( # Create address group address_group = AddressGroup( interpreting_service_name="smtp", + display_name="Alarm Notification Recipients", + properties={"address group": "Alarm"}, fields=recipients, ) @@ -111,51 +109,11 @@ def create_notification_request_for_alarm( ), ) -# Create mail template for alarm deletion notification -alarm_deletion_template = MessageTemplate( - display_name="Alarm Deletion Template", - fields=MessageTemplateFields( - subject_template="Alarm Cleared: ", - body_template="The alarm with ID has been cleared.\n" - "Condition: \n" - "Description: ", - ), -) - - # Send notification for alarm creation notification_for_alarm_creation = create_notification_request_for_alarm( - retrieved_alarm=retrieved_alarm, + alarm=retrieved_alarm, address_group=address_group, message_template=alarm_creation_template, ) notification_client.apply_notification_strategy(request=notification_for_alarm_creation) print("Notification sent for alarm creation") - -# Clear the alarm -clear_request = CreateOrUpdateAlarmRequest( - alarm_id=alarm_id, - transition=ClearAlarmTransition( - occurred_at=datetime.now(), - condition="Temperature returned to normal", - ), -) - -result = alarm_client.create_or_update_alarm(clear_request, ignore_conflict=True) -if result is None: - print("No state change needed (alarm already in requested state)") -else: - print("Alarm cleared successfully") - -alarm_client.delete_alarm(instance_id=id) -print("Alarm deleted successfully") - -# Send notification for alarm deletion -notification_for_alarm_deletion = create_notification_request_for_alarm( - retrieved_alarm=retrieved_alarm, - address_group=address_group, - message_template=alarm_deletion_template, -) - -notification_client.apply_notification_strategy(request=notification_for_alarm_deletion) -print("Notification sent for alarm deletion") diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index f059fc90..6934258f 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -2,7 +2,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._common_meta_data import CommonFields +from ._common_meta_data import BaseNotificationMetadata class AddressFields(JsonModel): @@ -18,7 +18,7 @@ class AddressFields(JsonModel): """List of blind carbon copy recipient addresses.""" -class AddressGroup(CommonFields): +class AddressGroup(BaseNotificationMetadata): """Model defining notification recipients.""" interpreting_service_name: str diff --git a/nisystemlink/clients/notification/models/_common_meta_data.py b/nisystemlink/clients/notification/models/_common_meta_data.py index 459ca24f..e83e1fcf 100644 --- a/nisystemlink/clients/notification/models/_common_meta_data.py +++ b/nisystemlink/clients/notification/models/_common_meta_data.py @@ -3,7 +3,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class CommonFields(JsonModel): +class BaseNotificationMetadata(JsonModel): id: str | None = None """Gets or sets the ID""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index a0c92bdc..d0fc7333 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,6 +1,6 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._common_meta_data import CommonFields +from ._common_meta_data import BaseNotificationMetadata class MessageTemplateFields(JsonModel): @@ -13,7 +13,7 @@ class MessageTemplateFields(JsonModel): """Body template of the message.""" -class MessageTemplate(CommonFields): +class MessageTemplate(BaseNotificationMetadata): """Model defining the notification content structure.""" fields: MessageTemplateFields diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 3ab71c50..8d1e6f29 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -34,7 +34,7 @@ def _address_group(): def _message_template(): """Returns the created message template.""" return MessageTemplate( - id="message_group_id", + id="message_template_id", interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, @@ -50,7 +50,7 @@ def _notification_configuration( """Returns the created notification configuration.""" return DynamicNotificationConfiguration( address_group_id="address_group_id", - message_template_id="message_group_id", + message_template_id="message_template_id", address_group=_address_group, message_template=_message_template, ) @@ -185,7 +185,7 @@ def test__apply_strategy_with_invalid_interpreting_service_name__raises_exceptio assert exc_info.value.http_status_code == 400 - def test__apply_configuration_with_no_address_and_message_template_id__returns_none( + def test__apply_strategy_with_no_address_and_message_template_id__returns_none( self, client: NotificationClient, _address_group: AddressGroup, @@ -205,3 +205,40 @@ def test__apply_configuration_with_no_address_and_message_template_id__returns_n ), ) assert client.apply_notification_strategy(request=request_model) is None + + def test__apply_multiple_notification_configurations__returns_none( + self, + client: NotificationClient, + _address_group: AddressGroup, + ): + first_message_template = MessageTemplate( + fields=MessageTemplateFields( + subject_template="subject1", body_template="body1" + ), + ) + second_message_template = MessageTemplate( + fields=MessageTemplateFields( + subject_template="subject2", body_template="body2" + ), + ) + + configuration1 = DynamicNotificationConfiguration( + address_group=_address_group, + message_template=first_message_template, + ) + + configuration2 = DynamicNotificationConfiguration( + address_group=_address_group, + message_template=second_message_template, + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + configuration1, + configuration2, + ] + ), + ) + assert client.apply_notification_strategy(request=request_model) is None From 1d4bf65db57ea2cc30b1761c1429f3553c76a9bd Mon Sep 17 00:00:00 2001 From: S Rohith Raja Date: Wed, 28 Jan 2026 13:16:28 +0530 Subject: [PATCH 17/33] feat: Add Notification Client Notification Client - Apply Dynamic Strategy --- docs/api_reference.rst | 1 + docs/api_reference/notification.rst | 14 + docs/getting_started.rst | 26 ++ examples/notification/notification.py | 119 +++++++++ nisystemlink/clients/notification/__init__.py | 3 + .../notification/_notification_client.py | 49 ++++ .../clients/notification/models/__init__.py | 7 + .../notification/models/_address_group.py | 44 ++++ .../notification/models/_common_meta_data.py | 29 +++ .../_dynamic_notification_configuration.py | 23 ++ .../models/_dynamic_notification_strategy.py | 12 + .../models/_dynamic_strategy_request.py | 18 ++ .../notification/models/_message_template.py | 27 ++ .../notification/test_notification_client.py | 244 ++++++++++++++++++ 14 files changed, 616 insertions(+) create mode 100644 docs/api_reference/notification.rst create mode 100644 examples/notification/notification.py create mode 100644 nisystemlink/clients/notification/__init__.py create mode 100644 nisystemlink/clients/notification/_notification_client.py create mode 100644 nisystemlink/clients/notification/models/__init__.py create mode 100644 nisystemlink/clients/notification/models/_address_group.py create mode 100644 nisystemlink/clients/notification/models/_common_meta_data.py create mode 100644 nisystemlink/clients/notification/models/_dynamic_notification_configuration.py create mode 100644 nisystemlink/clients/notification/models/_dynamic_notification_strategy.py create mode 100644 nisystemlink/clients/notification/models/_dynamic_strategy_request.py create mode 100644 nisystemlink/clients/notification/models/_message_template.py create mode 100644 tests/integration/notification/test_notification_client.py diff --git a/docs/api_reference.rst b/docs/api_reference.rst index d8ad53b2..0b9768cc 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -22,6 +22,7 @@ API Reference api_reference/work_item api_reference/test_plan api_reference/artifact + api_reference/notification Indices and tables ------------------ diff --git a/docs/api_reference/notification.rst b/docs/api_reference/notification.rst new file mode 100644 index 00000000..a66aecc7 --- /dev/null +++ b/docs/api_reference/notification.rst @@ -0,0 +1,14 @@ +.. _api_notification_page: + +nisystemlink.clients.notification +========================== + +.. autoclass:: nisystemlink.clients.notification.NotificationClient + :exclude-members: __init__ + + .. automethod:: __init__ + .. automethod:: apply_notification_strategy + +.. automodule:: nisystemlink.clients.notification.models + :members: + :imported-members: \ No newline at end of file diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 61a221cd..f35c49f4 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -506,3 +506,29 @@ Create, query and delete test plan templates. .. literalinclude:: ../examples/test_plan/test_plan_templates.py :language: python :linenos: + +Notification API +---------------- + +Overview +~~~~~~~~ + +The :class:`.NotificationClient` class is the primary entry point of the Notification API. + +When constructing a :class:`.NotificationClient`, you can pass an +:class:`.HttpConfiguration` (like one retrieved from the +:class:`.HttpConfigurationManager`), or let :class:`.NotificationClient` use the +default connection. The default connection depends on your environment. + +With a :class:`.NotificationClient` object, you can: + +* Apply dynamic notification strategy using :meth:`~.NotificationClient.apply_notification_strategy` + +Examples +~~~~~~~~ + +Apply a notification strategy + +.. literalinclude:: ../examples/notification/notification.py + :language: python + :linenos: \ No newline at end of file diff --git a/examples/notification/notification.py b/examples/notification/notification.py new file mode 100644 index 00000000..c0ff79c1 --- /dev/null +++ b/examples/notification/notification.py @@ -0,0 +1,119 @@ +import uuid +from datetime import datetime + +from nisystemlink.clients.alarm import AlarmClient +from nisystemlink.clients.alarm.models._alarm import Alarm, AlarmSeverityLevel +from nisystemlink.clients.alarm.models._create_or_update_alarm_request import ( + CreateOrUpdateAlarmRequest, + SetAlarmTransition, +) +from nisystemlink.clients.core import HttpConfiguration +from nisystemlink.clients.notification import NotificationClient +from nisystemlink.clients.notification.models import ( + AddressFields, + AddressGroup, + DynamicNotificationConfiguration, + DynamicNotificationStrategy, + DynamicStrategyRequest, + MessageTemplate, + MessageTemplateFields, +) + +# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink +server_configuration: HttpConfiguration | None = None + +# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment +# the following lines and provide your server URI and API key. +# server_configuration = HttpConfiguration( +# server_uri="https://yourserver.yourcompany.com", +# api_key="", +# ) + + +# Create request for applying strategy +def create_notification_request_for_alarm( + alarm: Alarm, + address_group: AddressGroup, + message_template: MessageTemplate, +) -> DynamicStrategyRequest: + """Creates and returns a dynamic strategy request.""" + occurred_at = alarm.most_recent_transition_occurred_at + + return DynamicStrategyRequest( + message_template_substitution_fields={ + "alarm_id": alarm.alarm_id, + "alarm_condition": alarm.condition, + "alarm_description": alarm.description, + "alarm_severity": str(alarm.current_severity_level), + "alarm_occurred_at": occurred_at.isoformat() if occurred_at else "", + }, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=message_template, + ) + ] + ), + ) + + +# Create clients for Notification and Alarm services +notification_client = NotificationClient(configuration=server_configuration) +alarm_client = AlarmClient(configuration=server_configuration) + +# Create a unique alarm ID for this example +alarm_id = f"example_alarm_{uuid.uuid1().hex}" + +# Create an alarm with a SET transition +create_alarm_request = CreateOrUpdateAlarmRequest( + alarm_id=alarm_id, + transition=SetAlarmTransition( + occurred_at=datetime.now(), + severity_level=AlarmSeverityLevel.HIGH, + value="85", + condition="Greater than 80", + short_text="Temperature is high", + detail_text="Temperature sensor reading is 85°C (higher than the configured threshold of 80°C)", + ), + description="Example alarm for notification", +) +id = alarm_client.create_or_update_alarm(create_alarm_request) +print("Alarm created successfully") + +# Get the alarm by its instance ID (the unique occurrence identifier) +retrieved_alarm = alarm_client.get_alarm(instance_id=id) + +# Define recipients to notify +recipients = AddressFields(toAddresses=["sample1@example.com"]) + +# Create address group +address_group = AddressGroup( + interpreting_service_name="smtp", + display_name="Alarm Notification Recipients", + properties={"address group": "Alarm"}, + fields=recipients, +) + +# Create mail template for alarm creation notification +alarm_creation_template = MessageTemplate( + interpreting_service_name="smtp", + display_name="Alarm Creation Template", + fields=MessageTemplateFields( + subject_template="Alarm Created: ", + body_template="An alarm with ID has been created.\n" + "Condition: \n" + "Description: \n" + "Current severity: \n" + "Occurred At: ", + ), +) + +# Send notification for alarm creation +notification_for_alarm_creation = create_notification_request_for_alarm( + alarm=retrieved_alarm, + address_group=address_group, + message_template=alarm_creation_template, +) +notification_client.apply_notification_strategy(request=notification_for_alarm_creation) +print("Notification sent for alarm creation") diff --git a/nisystemlink/clients/notification/__init__.py b/nisystemlink/clients/notification/__init__.py new file mode 100644 index 00000000..93aa4ea6 --- /dev/null +++ b/nisystemlink/clients/notification/__init__.py @@ -0,0 +1,3 @@ +from ._notification_client import NotificationClient + +# flake8: noqa diff --git a/nisystemlink/clients/notification/_notification_client.py b/nisystemlink/clients/notification/_notification_client.py new file mode 100644 index 00000000..046f5756 --- /dev/null +++ b/nisystemlink/clients/notification/_notification_client.py @@ -0,0 +1,49 @@ +"""Implementation of Notification Client""" + +from nisystemlink.clients import core +from nisystemlink.clients.core._uplink._base_client import BaseClient +from nisystemlink.clients.core._uplink._methods import post +from uplink import retry + +from . import models + + +@retry( + when=retry.when.status(408, 429, 502, 503, 504), + stop=retry.stop.after_attempt(1), + on_exception=retry.CONNECTION_ERROR, +) +class NotificationClient(BaseClient): + def __init__(self, configuration: core.HttpConfiguration | None = None): + """Initialize an instance. + + Args: + configuration: Defines the web server to connect to and information about + how to connect. If not provided, the + :class:`HttpConfigurationManager ` + is used to obtain the configuration. + + Raises: + ApiException: if unable to communicate with the `/ninotification` service. + """ + if configuration is None: + configuration = core.HttpConfigurationManager.get_configuration() + + super().__init__(configuration, base_path="/ninotification/v1/") + + @post("apply-dynamic-strategy") + def apply_notification_strategy( + self, request: models.DynamicStrategyRequest + ) -> None: + """Applies the notification strategy from the given request. + + Args: + request: Request with message template substitution fields and notification strategies. + + Returns: + None. + + Raises: + ApiException: if unable to communicate with the `/ninotification` service or provided invalid arguments. + """ + ... diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py new file mode 100644 index 00000000..2259fd10 --- /dev/null +++ b/nisystemlink/clients/notification/models/__init__.py @@ -0,0 +1,7 @@ +from ._address_group import AddressGroup, AddressFields +from ._dynamic_strategy_request import DynamicStrategyRequest +from ._message_template import MessageTemplate, MessageTemplateFields +from ._dynamic_notification_configuration import DynamicNotificationConfiguration +from ._dynamic_notification_strategy import DynamicNotificationStrategy + +# flake8: noqa diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py new file mode 100644 index 00000000..6934258f --- /dev/null +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -0,0 +1,44 @@ +from typing import List + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._common_meta_data import BaseNotificationMetadata + + +class AddressFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + toAddresses: List[str] | None = None + """List of primary recipient addresses.""" + + ccAddresses: List[str] | None = None + """List of carbon copy recipient addresses.""" + + bccAddresses: List[str] | None = None + """List of blind carbon copy recipient addresses.""" + + +class AddressGroup(BaseNotificationMetadata): + """Model defining notification recipients.""" + + interpreting_service_name: str + """Gets or sets the name of the interpreting service. + + Example: "smtp" + """ + + fields: AddressFields + """Gets or sets the address group's fields. Requires at least one valid recipient. + + Valid fields: + - toAddresses + - ccAddresses + - bccAddresses + + Example: + { + toAddresses: [ "address1@example.com" ], + ccAddresses: [ "address2@example.com" ], + bccAddresses: [ "address3@example.com" ] + } + """ diff --git a/nisystemlink/clients/notification/models/_common_meta_data.py b/nisystemlink/clients/notification/models/_common_meta_data.py new file mode 100644 index 00000000..e83e1fcf --- /dev/null +++ b/nisystemlink/clients/notification/models/_common_meta_data.py @@ -0,0 +1,29 @@ +from typing import Dict, List + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class BaseNotificationMetadata(JsonModel): + id: str | None = None + """Gets or sets the ID""" + + interpreting_service_name: str | None = None + """Gets or sets the name of the interpreting service. + + Example: "smtp" + """ + + display_name: str | None = None + """Gets or sets the display name. + + Example: "name" + """ + + properties: Dict[str, str] | None = None + """Gets or sets the properties. + + Example: { "property": "value" } + """ + + referencing_notification_strategies: List[str] | None = None + """Gets or sets the referencing notification strategies.""" diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py new file mode 100644 index 00000000..a2c81e49 --- /dev/null +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -0,0 +1,23 @@ +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._address_group import AddressGroup +from ._message_template import MessageTemplate + + +class DynamicNotificationConfiguration(JsonModel): + """Model for notification configuration defining address groups and message template for the notification. + + Requires at least one of addressGroupId or addressGroup, and one of messageTemplateId or messageTemplate. + """ + + address_group_id: str | None = None + """Gets the address group ID""" + + message_template_id: str | None = None + """Gets the message template ID""" + + address_group: AddressGroup | None = None + """Gets the address group defining notification recipients.""" + + message_template: MessageTemplate | None = None + """Gets the message template defining notification content structure""" diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py new file mode 100644 index 00000000..e82cff32 --- /dev/null +++ b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py @@ -0,0 +1,12 @@ +from typing import List + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._dynamic_notification_configuration import DynamicNotificationConfiguration + + +class DynamicNotificationStrategy(JsonModel): + """Model for the notification strategy to be applied.""" + + notification_configurations: List[DynamicNotificationConfiguration] + """Gets the notification configurations associated with this strategy.""" diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py new file mode 100644 index 00000000..d53f7f9a --- /dev/null +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -0,0 +1,18 @@ +from typing import Dict + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._dynamic_notification_strategy import DynamicNotificationStrategy + + +class DynamicStrategyRequest(JsonModel): + """Request model for applying a dynamic notification strategy.""" + + message_template_substitution_fields: Dict[str, str] | None = None + """Gets or sets the message template substitution fields. + + Example: { "replacement": "value" } + """ + + notification_strategy: DynamicNotificationStrategy + """Gets or sets the notification strategy containing configurations for address groups and message templates.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py new file mode 100644 index 00000000..d0fc7333 --- /dev/null +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -0,0 +1,27 @@ +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._common_meta_data import BaseNotificationMetadata + + +class MessageTemplateFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + subject_template: str + """Subject template of the message.""" + + body_template: str | None = None + """Body template of the message.""" + + +class MessageTemplate(BaseNotificationMetadata): + """Model defining the notification content structure.""" + + fields: MessageTemplateFields + """Gets or sets the message template's fields. + + Valid fields: + - subjectTemplate (required) + - bodyTemplate + + Example: { subjectTemplate: "subject", bodyTemplate: "body" } + """ diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py new file mode 100644 index 00000000..8d1e6f29 --- /dev/null +++ b/tests/integration/notification/test_notification_client.py @@ -0,0 +1,244 @@ +import pytest +from nisystemlink.clients.core import ApiException +from nisystemlink.clients.core._http_configuration import HttpConfiguration +from nisystemlink.clients.notification import NotificationClient +from nisystemlink.clients.notification.models import ( + AddressFields, + AddressGroup, + DynamicNotificationConfiguration, + DynamicNotificationStrategy, + DynamicStrategyRequest, + MessageTemplate, + MessageTemplateFields, +) + + +@pytest.fixture +def _address_group(): + """Returns the created address group.""" + return AddressGroup( + id="address_group_id", + interpreting_service_name="smtp", + display_name="name", + properties={"property": "value"}, + fields=AddressFields( + toAddresses=["address1@example.com"], + ccAddresses=["address2@example.com"], + bccAddresses=["address3@example.com"], + ), + referencing_notification_strategies=["reference_notification_strategy"], + ) + + +@pytest.fixture +def _message_template(): + """Returns the created message template.""" + return MessageTemplate( + id="message_template_id", + interpreting_service_name="smtp", + display_name="name", + properties={"property": "value"}, + fields=MessageTemplateFields(subject_template="subject", body_template="body"), + referencing_notification_strategies=["reference_notification_strategy"], + ) + + +@pytest.fixture +def _notification_configuration( + _address_group: AddressGroup, _message_template: MessageTemplate +): + """Returns the created notification configuration.""" + return DynamicNotificationConfiguration( + address_group_id="address_group_id", + message_template_id="message_template_id", + address_group=_address_group, + message_template=_message_template, + ) + + +@pytest.fixture +def _notification_strategy( + _notification_configuration: DynamicNotificationConfiguration, +): + """Returns the created notification strategy.""" + return DynamicNotificationStrategy( + notification_configurations=[ + _notification_configuration, + ] + ) + + +@pytest.fixture +def request_model( + _notification_strategy: DynamicNotificationStrategy, +): + """Returns the created request.""" + return DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=_notification_strategy, + ) + + +@pytest.fixture(scope="class") +def client(enterprise_config: HttpConfiguration) -> NotificationClient: + """Fixture to create a Notification client.""" + return NotificationClient(enterprise_config) + + +@pytest.mark.integration +@pytest.mark.enterprise +class TestNotificationClient: + def test__apply_strategy_with_correct_request__returns_none( + self, client: NotificationClient, request_model: DynamicStrategyRequest + ): + assert client.apply_notification_strategy(request=request_model) is None + + def test__apply_strategy_with_invalid_recipient__raises_exception( + self, + client: NotificationClient, + _message_template: MessageTemplate, + ): + address_group = AddressGroup( + interpreting_service_name="smtp", + fields=AddressFields(toAddresses=["invalid-email"]), + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=_message_template, + ) + ] + ), + ) + + with pytest.raises(ApiException) as exc_info: + client.apply_notification_strategy(request=request_model) + + assert exc_info.value.http_status_code == 400 + + def test__apply_strategy_with_no_configurations__raises_exception( + self, client: NotificationClient + ): + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[] + ), + ) + + with pytest.raises(ApiException) as exc_info: + client.apply_notification_strategy(request=request_model) + + assert exc_info.value.http_status_code == 400 + + def test__apply_strategy_with_empty_subject_template_fields__raises_exception( + self, client: NotificationClient, _address_group: AddressGroup + ): + message_template = MessageTemplate( + interpreting_service_name="smtp", + fields=MessageTemplateFields(subject_template=""), + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=_address_group, + message_template=message_template, + ) + ] + ), + ) + + with pytest.raises(ApiException) as exc_info: + client.apply_notification_strategy(request=request_model) + + assert exc_info.value.http_status_code == 400 + + def test__apply_strategy_with_invalid_interpreting_service_name__raises_exception( + self, client: NotificationClient, _message_template: MessageTemplate + ): + address_group = AddressGroup( + interpreting_service_name="invalid_service", + fields=AddressFields(toAddresses=["address1@example.com"]), + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + DynamicNotificationConfiguration( + address_group=address_group, + message_template=_message_template, + ) + ] + ), + ) + + with pytest.raises(ApiException) as exc_info: + client.apply_notification_strategy(request=request_model) + + assert exc_info.value.http_status_code == 400 + + def test__apply_strategy_with_no_address_and_message_template_id__returns_none( + self, + client: NotificationClient, + _address_group: AddressGroup, + _message_template: MessageTemplate, + ): + configuration = DynamicNotificationConfiguration( + address_group=_address_group, + message_template=_message_template, + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + configuration, + ] + ), + ) + assert client.apply_notification_strategy(request=request_model) is None + + def test__apply_multiple_notification_configurations__returns_none( + self, + client: NotificationClient, + _address_group: AddressGroup, + ): + first_message_template = MessageTemplate( + fields=MessageTemplateFields( + subject_template="subject1", body_template="body1" + ), + ) + second_message_template = MessageTemplate( + fields=MessageTemplateFields( + subject_template="subject2", body_template="body2" + ), + ) + + configuration1 = DynamicNotificationConfiguration( + address_group=_address_group, + message_template=first_message_template, + ) + + configuration2 = DynamicNotificationConfiguration( + address_group=_address_group, + message_template=second_message_template, + ) + + request_model = DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[ + configuration1, + configuration2, + ] + ), + ) + assert client.apply_notification_strategy(request=request_model) is None From a77b3a55c9d1b4cdd66df032086df3991cab0412 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 15:15:07 +0530 Subject: [PATCH 18/33] refactor: File name changed --- nisystemlink/clients/notification/models/_address_group.py | 2 +- .../{_common_meta_data.py => _base_notification_metadata.py} | 0 nisystemlink/clients/notification/models/_message_template.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename nisystemlink/clients/notification/models/{_common_meta_data.py => _base_notification_metadata.py} (100%) diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 6934258f..cf3ea49c 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -2,7 +2,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._common_meta_data import BaseNotificationMetadata +from ._base_notification_metadata import BaseNotificationMetadata class AddressFields(JsonModel): diff --git a/nisystemlink/clients/notification/models/_common_meta_data.py b/nisystemlink/clients/notification/models/_base_notification_metadata.py similarity index 100% rename from nisystemlink/clients/notification/models/_common_meta_data.py rename to nisystemlink/clients/notification/models/_base_notification_metadata.py diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index d0fc7333..ed0388eb 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,6 +1,6 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._common_meta_data import BaseNotificationMetadata +from ._base_notification_metadata import BaseNotificationMetadata class MessageTemplateFields(JsonModel): From 78fe479123a689cdeb3b2046f2d4d80cbe6d898d Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Wed, 28 Jan 2026 15:31:40 +0530 Subject: [PATCH 19/33] refactor: File name change --- nisystemlink/clients/notification/models/_address_group.py | 2 +- .../{_common_meta_data.py => _base_notification_metadata.py} | 0 nisystemlink/clients/notification/models/_message_template.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename nisystemlink/clients/notification/models/{_common_meta_data.py => _base_notification_metadata.py} (100%) diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 6934258f..cf3ea49c 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -2,7 +2,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._common_meta_data import BaseNotificationMetadata +from ._base_notification_metadata import BaseNotificationMetadata class AddressFields(JsonModel): diff --git a/nisystemlink/clients/notification/models/_common_meta_data.py b/nisystemlink/clients/notification/models/_base_notification_metadata.py similarity index 100% rename from nisystemlink/clients/notification/models/_common_meta_data.py rename to nisystemlink/clients/notification/models/_base_notification_metadata.py diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index d0fc7333..ed0388eb 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,6 +1,6 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._common_meta_data import BaseNotificationMetadata +from ._base_notification_metadata import BaseNotificationMetadata class MessageTemplateFields(JsonModel): From 61c435842f2e16c05640a9eb1e750f4decf52d45 Mon Sep 17 00:00:00 2001 From: S Rohith Raja Date: Wed, 28 Jan 2026 15:36:37 +0530 Subject: [PATCH 20/33] refactor: Change in model file name From dbcdfaba345417e0981f572a6aa92c021f31f219 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 29 Jan 2026 13:18:09 +0530 Subject: [PATCH 21/33] refactor: Change in config structure --- .../clients/notification/models/__init__.py | 3 +- .../notification/models/_address_group.py | 10 ++---- .../models/_base_notification_metadata.py | 10 +++--- .../_dynamic_notification_configuration.py | 31 ++++++++++++++++--- .../models/_dynamic_notification_strategy.py | 10 ++++-- .../models/_dynamic_strategy_request.py | 4 +-- .../notification/models/_message_template.py | 4 ++- .../models/_smtp_address_group.py | 22 +++++++++++++ .../models/_smtp_message_template.py | 20 ++++++++++++ .../notification/test_notification_client.py | 3 ++ 10 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 nisystemlink/clients/notification/models/_smtp_address_group.py create mode 100644 nisystemlink/clients/notification/models/_smtp_message_template.py diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py index 2259fd10..ac9282da 100644 --- a/nisystemlink/clients/notification/models/__init__.py +++ b/nisystemlink/clients/notification/models/__init__.py @@ -3,5 +3,6 @@ from ._message_template import MessageTemplate, MessageTemplateFields from ._dynamic_notification_configuration import DynamicNotificationConfiguration from ._dynamic_notification_strategy import DynamicNotificationStrategy - +from ._smtp_address_group import SmtpAddressFields +from ._smtp_message_template import SmtpMessageTemplateFields # flake8: noqa diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index cf3ea49c..21ae0fbc 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -21,13 +21,7 @@ class AddressFields(JsonModel): class AddressGroup(BaseNotificationMetadata): """Model defining notification recipients.""" - interpreting_service_name: str - """Gets or sets the name of the interpreting service. - - Example: "smtp" - """ - - fields: AddressFields + fields: Dict[str, List[str]] """Gets or sets the address group's fields. Requires at least one valid recipient. Valid fields: diff --git a/nisystemlink/clients/notification/models/_base_notification_metadata.py b/nisystemlink/clients/notification/models/_base_notification_metadata.py index e83e1fcf..e0ab51e4 100644 --- a/nisystemlink/clients/notification/models/_base_notification_metadata.py +++ b/nisystemlink/clients/notification/models/_base_notification_metadata.py @@ -5,25 +5,25 @@ class BaseNotificationMetadata(JsonModel): id: str | None = None - """Gets or sets the ID""" + """identifier of this notification metadata""" - interpreting_service_name: str | None = None + interpreting_service_name: str """Gets or sets the name of the interpreting service. Example: "smtp" """ display_name: str | None = None - """Gets or sets the display name. + """Display name of the object. Example: "name" """ properties: Dict[str, str] | None = None - """Gets or sets the properties. + """Additional properties for the base metadata. Example: { "property": "value" } """ referencing_notification_strategies: List[str] | None = None - """Gets or sets the referencing notification strategies.""" + """List of strategies used for referencing,""" diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index a2c81e49..4b43ce7a 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -1,4 +1,13 @@ +from nisystemlink.clients.core._api_exception import ApiException from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._smtp_address_group import ( + SmtpAddressGroup, +) +from nisystemlink.clients.notification.models._smtp_message_template import ( + SmtpMessageTemplate, +) +from pydantic import Field, model_validator + from ._address_group import AddressGroup from ._message_template import MessageTemplate @@ -11,13 +20,27 @@ class DynamicNotificationConfiguration(JsonModel): """ address_group_id: str | None = None - """Gets the address group ID""" + """ID referencing the associated address group.""" message_template_id: str | None = None - """Gets the message template ID""" + """ID referencing the associated message template.""" - address_group: AddressGroup | None = None + address_group: SmtpAddressGroup | AddressGroup | None = Field( + default=None, discriminator="interpreting_service_name" + ) """Gets the address group defining notification recipients.""" - message_template: MessageTemplate | None = None + message_template: SmtpMessageTemplate | MessageTemplate | None = Field( + default=None, discriminator="interpreting_service_name" + ) """Gets the message template defining notification content structure""" + + @model_validator(mode="after") + def validate_required_pairs(self): + if self.address_group_id is None and self.address_group is None: + raise ApiException("AddressGroupId is required.") + + if self.message_template_id is None and self.message_template is None: + raise ApiException("MessageTemplateId is required.") + + return self diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py index e82cff32..06cb063f 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py @@ -1,6 +1,8 @@ -from typing import List +from typing import Annotated, List + from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import Field from ._dynamic_notification_configuration import DynamicNotificationConfiguration @@ -8,5 +10,7 @@ class DynamicNotificationStrategy(JsonModel): """Model for the notification strategy to be applied.""" - notification_configurations: List[DynamicNotificationConfiguration] - """Gets the notification configurations associated with this strategy.""" + notification_configurations: Annotated[ + List[DynamicNotificationConfiguration], Field(min_length=1) + ] + """Notification configurations associated with this strategy.""" diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py index d53f7f9a..cec4d7c6 100644 --- a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -9,10 +9,10 @@ class DynamicStrategyRequest(JsonModel): """Request model for applying a dynamic notification strategy.""" message_template_substitution_fields: Dict[str, str] | None = None - """Gets or sets the message template substitution fields. + """Defines the fields used for substituting values in the message template. Example: { "replacement": "value" } """ notification_strategy: DynamicNotificationStrategy - """Gets or sets the notification strategy containing configurations for address groups and message templates.""" + """Notification strategy containing configurations for address groups and message templates.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index ed0388eb..575535a9 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,3 +1,5 @@ +from typing import Dict + from nisystemlink.clients.core._uplink._json_model import JsonModel from ._base_notification_metadata import BaseNotificationMetadata @@ -16,7 +18,7 @@ class MessageTemplateFields(JsonModel): class MessageTemplate(BaseNotificationMetadata): """Model defining the notification content structure.""" - fields: MessageTemplateFields + fields: Dict[str, str] """Gets or sets the message template's fields. Valid fields: diff --git a/nisystemlink/clients/notification/models/_smtp_address_group.py b/nisystemlink/clients/notification/models/_smtp_address_group.py new file mode 100644 index 00000000..87e02f13 --- /dev/null +++ b/nisystemlink/clients/notification/models/_smtp_address_group.py @@ -0,0 +1,22 @@ +from typing import List, Literal + +from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._address_group import AddressGroup + + +class SmtpAddressFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + toAddresses: List[str] | None = None + """List of primary recipient addresses.""" + + ccAddresses: List[str] | None = None + """List of carbon copy recipient addresses.""" + + bccAddresses: List[str] | None = None + """List of blind carbon copy recipient addresses.""" + + +class SmtpAddressGroup(AddressGroup): + interpreting_service_name: Literal["smtp"] + fields: SmtpAddressFields diff --git a/nisystemlink/clients/notification/models/_smtp_message_template.py b/nisystemlink/clients/notification/models/_smtp_message_template.py new file mode 100644 index 00000000..03430d3a --- /dev/null +++ b/nisystemlink/clients/notification/models/_smtp_message_template.py @@ -0,0 +1,20 @@ +from typing import Literal + +from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._message_template import \ + MessageTemplate + + +class SmtpMessageTemplateFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + subject_template: str + """Subject template of the message.""" + + body_template: str | None = None + """Body template of the message.""" + + +class SmtpMessageTemplate(MessageTemplate): + interpreting_service_name: Literal["smtp"] + fields: SmtpMessageTemplateFields diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 8d1e6f29..26321c32 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -120,6 +120,7 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( assert exc_info.value.http_status_code == 400 + # Need to verify def test__apply_strategy_with_no_configurations__raises_exception( self, client: NotificationClient ): @@ -212,11 +213,13 @@ def test__apply_multiple_notification_configurations__returns_none( _address_group: AddressGroup, ): first_message_template = MessageTemplate( + interpreting_service_name="smtp", fields=MessageTemplateFields( subject_template="subject1", body_template="body1" ), ) second_message_template = MessageTemplate( + interpreting_service_name="smtp", fields=MessageTemplateFields( subject_template="subject2", body_template="body2" ), From 9a58956cdff0b9307b2f67450ed187dbd1d83746 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 29 Jan 2026 13:18:09 +0530 Subject: [PATCH 22/33] refactor: Change in config structure --- .../clients/notification/models/__init__.py | 3 +- .../notification/models/_address_group.py | 10 ++---- .../models/_base_notification_metadata.py | 10 +++--- .../_dynamic_notification_configuration.py | 31 ++++++++++++++++--- .../models/_dynamic_notification_strategy.py | 10 ++++-- .../models/_dynamic_strategy_request.py | 4 +-- .../notification/models/_message_template.py | 4 ++- .../models/_smtp_address_group.py | 22 +++++++++++++ .../models/_smtp_message_template.py | 20 ++++++++++++ .../notification/test_notification_client.py | 3 ++ 10 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 nisystemlink/clients/notification/models/_smtp_address_group.py create mode 100644 nisystemlink/clients/notification/models/_smtp_message_template.py diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py index 2259fd10..ac9282da 100644 --- a/nisystemlink/clients/notification/models/__init__.py +++ b/nisystemlink/clients/notification/models/__init__.py @@ -3,5 +3,6 @@ from ._message_template import MessageTemplate, MessageTemplateFields from ._dynamic_notification_configuration import DynamicNotificationConfiguration from ._dynamic_notification_strategy import DynamicNotificationStrategy - +from ._smtp_address_group import SmtpAddressFields +from ._smtp_message_template import SmtpMessageTemplateFields # flake8: noqa diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index cf3ea49c..21ae0fbc 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -21,13 +21,7 @@ class AddressFields(JsonModel): class AddressGroup(BaseNotificationMetadata): """Model defining notification recipients.""" - interpreting_service_name: str - """Gets or sets the name of the interpreting service. - - Example: "smtp" - """ - - fields: AddressFields + fields: Dict[str, List[str]] """Gets or sets the address group's fields. Requires at least one valid recipient. Valid fields: diff --git a/nisystemlink/clients/notification/models/_base_notification_metadata.py b/nisystemlink/clients/notification/models/_base_notification_metadata.py index e83e1fcf..e0ab51e4 100644 --- a/nisystemlink/clients/notification/models/_base_notification_metadata.py +++ b/nisystemlink/clients/notification/models/_base_notification_metadata.py @@ -5,25 +5,25 @@ class BaseNotificationMetadata(JsonModel): id: str | None = None - """Gets or sets the ID""" + """identifier of this notification metadata""" - interpreting_service_name: str | None = None + interpreting_service_name: str """Gets or sets the name of the interpreting service. Example: "smtp" """ display_name: str | None = None - """Gets or sets the display name. + """Display name of the object. Example: "name" """ properties: Dict[str, str] | None = None - """Gets or sets the properties. + """Additional properties for the base metadata. Example: { "property": "value" } """ referencing_notification_strategies: List[str] | None = None - """Gets or sets the referencing notification strategies.""" + """List of strategies used for referencing,""" diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index a2c81e49..4b43ce7a 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -1,4 +1,13 @@ +from nisystemlink.clients.core._api_exception import ApiException from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._smtp_address_group import ( + SmtpAddressGroup, +) +from nisystemlink.clients.notification.models._smtp_message_template import ( + SmtpMessageTemplate, +) +from pydantic import Field, model_validator + from ._address_group import AddressGroup from ._message_template import MessageTemplate @@ -11,13 +20,27 @@ class DynamicNotificationConfiguration(JsonModel): """ address_group_id: str | None = None - """Gets the address group ID""" + """ID referencing the associated address group.""" message_template_id: str | None = None - """Gets the message template ID""" + """ID referencing the associated message template.""" - address_group: AddressGroup | None = None + address_group: SmtpAddressGroup | AddressGroup | None = Field( + default=None, discriminator="interpreting_service_name" + ) """Gets the address group defining notification recipients.""" - message_template: MessageTemplate | None = None + message_template: SmtpMessageTemplate | MessageTemplate | None = Field( + default=None, discriminator="interpreting_service_name" + ) """Gets the message template defining notification content structure""" + + @model_validator(mode="after") + def validate_required_pairs(self): + if self.address_group_id is None and self.address_group is None: + raise ApiException("AddressGroupId is required.") + + if self.message_template_id is None and self.message_template is None: + raise ApiException("MessageTemplateId is required.") + + return self diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py index e82cff32..06cb063f 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_strategy.py @@ -1,6 +1,8 @@ -from typing import List +from typing import Annotated, List + from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import Field from ._dynamic_notification_configuration import DynamicNotificationConfiguration @@ -8,5 +10,7 @@ class DynamicNotificationStrategy(JsonModel): """Model for the notification strategy to be applied.""" - notification_configurations: List[DynamicNotificationConfiguration] - """Gets the notification configurations associated with this strategy.""" + notification_configurations: Annotated[ + List[DynamicNotificationConfiguration], Field(min_length=1) + ] + """Notification configurations associated with this strategy.""" diff --git a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py index d53f7f9a..cec4d7c6 100644 --- a/nisystemlink/clients/notification/models/_dynamic_strategy_request.py +++ b/nisystemlink/clients/notification/models/_dynamic_strategy_request.py @@ -9,10 +9,10 @@ class DynamicStrategyRequest(JsonModel): """Request model for applying a dynamic notification strategy.""" message_template_substitution_fields: Dict[str, str] | None = None - """Gets or sets the message template substitution fields. + """Defines the fields used for substituting values in the message template. Example: { "replacement": "value" } """ notification_strategy: DynamicNotificationStrategy - """Gets or sets the notification strategy containing configurations for address groups and message templates.""" + """Notification strategy containing configurations for address groups and message templates.""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index ed0388eb..575535a9 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,3 +1,5 @@ +from typing import Dict + from nisystemlink.clients.core._uplink._json_model import JsonModel from ._base_notification_metadata import BaseNotificationMetadata @@ -16,7 +18,7 @@ class MessageTemplateFields(JsonModel): class MessageTemplate(BaseNotificationMetadata): """Model defining the notification content structure.""" - fields: MessageTemplateFields + fields: Dict[str, str] """Gets or sets the message template's fields. Valid fields: diff --git a/nisystemlink/clients/notification/models/_smtp_address_group.py b/nisystemlink/clients/notification/models/_smtp_address_group.py new file mode 100644 index 00000000..87e02f13 --- /dev/null +++ b/nisystemlink/clients/notification/models/_smtp_address_group.py @@ -0,0 +1,22 @@ +from typing import List, Literal + +from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._address_group import AddressGroup + + +class SmtpAddressFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + toAddresses: List[str] | None = None + """List of primary recipient addresses.""" + + ccAddresses: List[str] | None = None + """List of carbon copy recipient addresses.""" + + bccAddresses: List[str] | None = None + """List of blind carbon copy recipient addresses.""" + + +class SmtpAddressGroup(AddressGroup): + interpreting_service_name: Literal["smtp"] + fields: SmtpAddressFields diff --git a/nisystemlink/clients/notification/models/_smtp_message_template.py b/nisystemlink/clients/notification/models/_smtp_message_template.py new file mode 100644 index 00000000..03430d3a --- /dev/null +++ b/nisystemlink/clients/notification/models/_smtp_message_template.py @@ -0,0 +1,20 @@ +from typing import Literal + +from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._message_template import \ + MessageTemplate + + +class SmtpMessageTemplateFields(JsonModel): + """Fields representing the subject and body templates of a message.""" + + subject_template: str + """Subject template of the message.""" + + body_template: str | None = None + """Body template of the message.""" + + +class SmtpMessageTemplate(MessageTemplate): + interpreting_service_name: Literal["smtp"] + fields: SmtpMessageTemplateFields diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 8d1e6f29..26321c32 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -120,6 +120,7 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( assert exc_info.value.http_status_code == 400 + # Need to verify def test__apply_strategy_with_no_configurations__raises_exception( self, client: NotificationClient ): @@ -212,11 +213,13 @@ def test__apply_multiple_notification_configurations__returns_none( _address_group: AddressGroup, ): first_message_template = MessageTemplate( + interpreting_service_name="smtp", fields=MessageTemplateFields( subject_template="subject1", body_template="body1" ), ) second_message_template = MessageTemplate( + interpreting_service_name="smtp", fields=MessageTemplateFields( subject_template="subject2", body_template="body2" ), From 0b44d482a2ed80e956f92a7f12f1dddb617c86de Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 29 Jan 2026 18:57:39 +0530 Subject: [PATCH 23/33] refactor: Change in model structure --- examples/notification/notification.py | 20 ++-- .../clients/notification/models/__init__.py | 9 +- .../notification/models/_address_group.py | 35 +------ .../models/_base_notification_metadata.py | 6 -- .../_dynamic_notification_configuration.py | 32 +++--- .../notification/models/_message_template.py | 24 +---- .../models/_smtp_address_group.py | 26 ++++- .../models/_smtp_message_template.py | 14 ++- .../notification/test_notification_client.py | 98 ++++++++----------- 9 files changed, 112 insertions(+), 152 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index c0ff79c1..044b7686 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -10,13 +10,13 @@ from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( - AddressFields, - AddressGroup, DynamicNotificationConfiguration, DynamicNotificationStrategy, DynamicStrategyRequest, - MessageTemplate, - MessageTemplateFields, + SmtpAddressFields, + SmtpAddressGroup, + SmtpMessageTemplate, + SmtpMessageTemplateFields, ) # Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink @@ -33,8 +33,8 @@ # Create request for applying strategy def create_notification_request_for_alarm( alarm: Alarm, - address_group: AddressGroup, - message_template: MessageTemplate, + address_group: SmtpAddressGroup, + message_template: SmtpMessageTemplate, ) -> DynamicStrategyRequest: """Creates and returns a dynamic strategy request.""" occurred_at = alarm.most_recent_transition_occurred_at @@ -85,10 +85,10 @@ def create_notification_request_for_alarm( retrieved_alarm = alarm_client.get_alarm(instance_id=id) # Define recipients to notify -recipients = AddressFields(toAddresses=["sample1@example.com"]) +recipients = SmtpAddressFields(toAddresses=["sample1@example.com"]) # Create address group -address_group = AddressGroup( +address_group = SmtpAddressGroup( interpreting_service_name="smtp", display_name="Alarm Notification Recipients", properties={"address group": "Alarm"}, @@ -96,10 +96,10 @@ def create_notification_request_for_alarm( ) # Create mail template for alarm creation notification -alarm_creation_template = MessageTemplate( +alarm_creation_template = SmtpMessageTemplate( interpreting_service_name="smtp", display_name="Alarm Creation Template", - fields=MessageTemplateFields( + fields=SmtpMessageTemplateFields( subject_template="Alarm Created: ", body_template="An alarm with ID has been created.\n" "Condition: \n" diff --git a/nisystemlink/clients/notification/models/__init__.py b/nisystemlink/clients/notification/models/__init__.py index ac9282da..715d9a17 100644 --- a/nisystemlink/clients/notification/models/__init__.py +++ b/nisystemlink/clients/notification/models/__init__.py @@ -1,8 +1,9 @@ -from ._address_group import AddressGroup, AddressFields +from ._address_group import AddressGroup from ._dynamic_strategy_request import DynamicStrategyRequest -from ._message_template import MessageTemplate, MessageTemplateFields +from ._message_template import MessageTemplate from ._dynamic_notification_configuration import DynamicNotificationConfiguration from ._dynamic_notification_strategy import DynamicNotificationStrategy -from ._smtp_address_group import SmtpAddressFields -from ._smtp_message_template import SmtpMessageTemplateFields +from ._smtp_address_group import SmtpAddressFields, SmtpAddressGroup +from ._smtp_message_template import SmtpMessageTemplateFields, SmtpMessageTemplate + # flake8: noqa diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 21ae0fbc..45bb9025 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -1,38 +1,13 @@ from typing import Dict, List -from nisystemlink.clients.core._uplink._json_model import JsonModel - from ._base_notification_metadata import BaseNotificationMetadata -class AddressFields(JsonModel): - """Fields representing the subject and body templates of a message.""" - - toAddresses: List[str] | None = None - """List of primary recipient addresses.""" - - ccAddresses: List[str] | None = None - """List of carbon copy recipient addresses.""" - - bccAddresses: List[str] | None = None - """List of blind carbon copy recipient addresses.""" - - class AddressGroup(BaseNotificationMetadata): - """Model defining notification recipients.""" - - fields: Dict[str, List[str]] - """Gets or sets the address group's fields. Requires at least one valid recipient. + """Model defining notification recipients for generic service.""" - Valid fields: - - toAddresses - - ccAddresses - - bccAddresses + interpreting_service_name: str + """Service identifier for generic interpretation.""" - Example: - { - toAddresses: [ "address1@example.com" ], - ccAddresses: [ "address2@example.com" ], - bccAddresses: [ "address3@example.com" ] - } - """ + fields: Dict[str, List[str]] + """Address group's fields. Requires at least one valid recipient.""" diff --git a/nisystemlink/clients/notification/models/_base_notification_metadata.py b/nisystemlink/clients/notification/models/_base_notification_metadata.py index e0ab51e4..9bc27db5 100644 --- a/nisystemlink/clients/notification/models/_base_notification_metadata.py +++ b/nisystemlink/clients/notification/models/_base_notification_metadata.py @@ -7,12 +7,6 @@ class BaseNotificationMetadata(JsonModel): id: str | None = None """identifier of this notification metadata""" - interpreting_service_name: str - """Gets or sets the name of the interpreting service. - - Example: "smtp" - """ - display_name: str | None = None """Display name of the object. diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index 4b43ce7a..0bcc0e4e 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -1,16 +1,15 @@ -from nisystemlink.clients.core._api_exception import ApiException +from typing import Self + from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.notification.models._address_group import AddressGroup +from nisystemlink.clients.notification.models._message_template import MessageTemplate from nisystemlink.clients.notification.models._smtp_address_group import ( SmtpAddressGroup, ) from nisystemlink.clients.notification.models._smtp_message_template import ( SmtpMessageTemplate, ) -from pydantic import Field, model_validator - - -from ._address_group import AddressGroup -from ._message_template import MessageTemplate +from pydantic import Field, model_validator, ValidationError class DynamicNotificationConfiguration(JsonModel): @@ -25,22 +24,21 @@ class DynamicNotificationConfiguration(JsonModel): message_template_id: str | None = None """ID referencing the associated message template.""" - address_group: SmtpAddressGroup | AddressGroup | None = Field( - default=None, discriminator="interpreting_service_name" - ) - """Gets the address group defining notification recipients.""" + address_group: AddressGroup | SmtpAddressGroup | None = Field(default=None) + """Address group defining notification recipients.""" - message_template: SmtpMessageTemplate | MessageTemplate | None = Field( - default=None, discriminator="interpreting_service_name" - ) - """Gets the message template defining notification content structure""" + message_template: MessageTemplate | SmtpMessageTemplate | None = Field(default=None) + """Message template defining notification content structure""" @model_validator(mode="after") - def validate_required_pairs(self): + def validate_required_pairs(self) -> Self: + """Validator to check at least one of address_group_id or address_group, and + one of message_template_id or message_template is present. + """ if self.address_group_id is None and self.address_group is None: - raise ApiException("AddressGroupId is required.") + raise ValidationError("AddressGroupId is required.") if self.message_template_id is None and self.message_template is None: - raise ApiException("MessageTemplateId is required.") + raise ValidationError("MessageTemplateId is required.") return self diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index 575535a9..3d3d9f96 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -1,29 +1,13 @@ from typing import Dict -from nisystemlink.clients.core._uplink._json_model import JsonModel - from ._base_notification_metadata import BaseNotificationMetadata -class MessageTemplateFields(JsonModel): - """Fields representing the subject and body templates of a message.""" - - subject_template: str - """Subject template of the message.""" - - body_template: str | None = None - """Body template of the message.""" - - class MessageTemplate(BaseNotificationMetadata): """Model defining the notification content structure.""" - fields: Dict[str, str] - """Gets or sets the message template's fields. - - Valid fields: - - subjectTemplate (required) - - bodyTemplate + interpreting_service_name: str + """Service identifier for generic interpretation.""" - Example: { subjectTemplate: "subject", bodyTemplate: "body" } - """ + fields: Dict[str, str] + """Template fields for message.""" diff --git a/nisystemlink/clients/notification/models/_smtp_address_group.py b/nisystemlink/clients/notification/models/_smtp_address_group.py index 87e02f13..32f73a5c 100644 --- a/nisystemlink/clients/notification/models/_smtp_address_group.py +++ b/nisystemlink/clients/notification/models/_smtp_address_group.py @@ -1,11 +1,13 @@ from typing import List, Literal from nisystemlink.clients.core._uplink._json_model import JsonModel -from nisystemlink.clients.notification.models._address_group import AddressGroup +from nisystemlink.clients.notification.models._base_notification_metadata import ( + BaseNotificationMetadata, +) class SmtpAddressFields(JsonModel): - """Fields representing the subject and body templates of a message.""" + """Recipient address fields used in SMTP messaging.""" toAddresses: List[str] | None = None """List of primary recipient addresses.""" @@ -17,6 +19,24 @@ class SmtpAddressFields(JsonModel): """List of blind carbon copy recipient addresses.""" -class SmtpAddressGroup(AddressGroup): +class SmtpAddressGroup(BaseNotificationMetadata): + """Model defining notification recipients for SMTP service.""" + interpreting_service_name: Literal["smtp"] + """Service name for SMTP-based interpretation.""" + fields: SmtpAddressFields + """Recipient address fields used for SMTP notifications. + + Valid fields: + - toAddresses + - ccAddresses + - bccAddresses + + Example: + { + toAddresses: [ "address1@example.com" ], + ccAddresses: [ "address2@example.com" ], + bccAddresses: [ "address3@example.com" ] + } + """ diff --git a/nisystemlink/clients/notification/models/_smtp_message_template.py b/nisystemlink/clients/notification/models/_smtp_message_template.py index 03430d3a..eebc2bde 100644 --- a/nisystemlink/clients/notification/models/_smtp_message_template.py +++ b/nisystemlink/clients/notification/models/_smtp_message_template.py @@ -1,12 +1,13 @@ from typing import Literal from nisystemlink.clients.core._uplink._json_model import JsonModel -from nisystemlink.clients.notification.models._message_template import \ - MessageTemplate +from nisystemlink.clients.notification.models._base_notification_metadata import ( + BaseNotificationMetadata, +) class SmtpMessageTemplateFields(JsonModel): - """Fields representing the subject and body templates of a message.""" + """Template fields to construct an SMTP message.""" subject_template: str """Subject template of the message.""" @@ -15,6 +16,11 @@ class SmtpMessageTemplateFields(JsonModel): """Body template of the message.""" -class SmtpMessageTemplate(MessageTemplate): +class SmtpMessageTemplate(BaseNotificationMetadata): + """Model defining message template for SMTP service""" + interpreting_service_name: Literal["smtp"] + """Service name for SMTP-based interpretation.""" + fields: SmtpMessageTemplateFields + """Subject and body template fields for SMTP messages.""" diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 26321c32..d574dd20 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -3,25 +3,26 @@ from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.notification import NotificationClient from nisystemlink.clients.notification.models import ( - AddressFields, - AddressGroup, DynamicNotificationConfiguration, DynamicNotificationStrategy, DynamicStrategyRequest, - MessageTemplate, - MessageTemplateFields, + SmtpAddressFields, + SmtpAddressGroup, + SmtpMessageTemplate, + SmtpMessageTemplateFields, ) +from pydantic import ValidationError @pytest.fixture def _address_group(): """Returns the created address group.""" - return AddressGroup( + return SmtpAddressGroup( id="address_group_id", interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, - fields=AddressFields( + fields=SmtpAddressFields( toAddresses=["address1@example.com"], ccAddresses=["address2@example.com"], bccAddresses=["address3@example.com"], @@ -33,19 +34,21 @@ def _address_group(): @pytest.fixture def _message_template(): """Returns the created message template.""" - return MessageTemplate( + return SmtpMessageTemplate( id="message_template_id", interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, - fields=MessageTemplateFields(subject_template="subject", body_template="body"), + fields=SmtpMessageTemplateFields( + subject_template="subject", body_template="body" + ), referencing_notification_strategies=["reference_notification_strategy"], ) @pytest.fixture def _notification_configuration( - _address_group: AddressGroup, _message_template: MessageTemplate + _address_group: SmtpAddressGroup, _message_template: SmtpMessageTemplate ): """Returns the created notification configuration.""" return DynamicNotificationConfiguration( @@ -96,11 +99,11 @@ def test__apply_strategy_with_correct_request__returns_none( def test__apply_strategy_with_invalid_recipient__raises_exception( self, client: NotificationClient, - _message_template: MessageTemplate, + _message_template: SmtpMessageTemplate, ): - address_group = AddressGroup( + address_group = SmtpAddressGroup( interpreting_service_name="smtp", - fields=AddressFields(toAddresses=["invalid-email"]), + fields=SmtpAddressFields(toAddresses=["invalid-email"]), ) request_model = DynamicStrategyRequest( @@ -120,28 +123,23 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( assert exc_info.value.http_status_code == 400 - # Need to verify - def test__apply_strategy_with_no_configurations__raises_exception( + def test__create_strategy_with_no_configurations__raises_exception( self, client: NotificationClient ): - request_model = DynamicStrategyRequest( - message_template_substitution_fields={"replacement": "value"}, - notification_strategy=DynamicNotificationStrategy( - notification_configurations=[] - ), - ) - - with pytest.raises(ApiException) as exc_info: - client.apply_notification_strategy(request=request_model) - - assert exc_info.value.http_status_code == 400 + with pytest.raises(ValidationError): + DynamicStrategyRequest( + message_template_substitution_fields={"replacement": "value"}, + notification_strategy=DynamicNotificationStrategy( + notification_configurations=[] + ), + ) def test__apply_strategy_with_empty_subject_template_fields__raises_exception( - self, client: NotificationClient, _address_group: AddressGroup + self, client: NotificationClient, _address_group: SmtpAddressGroup ): - message_template = MessageTemplate( + message_template = SmtpMessageTemplate( interpreting_service_name="smtp", - fields=MessageTemplateFields(subject_template=""), + fields=SmtpMessageTemplateFields(subject_template=""), ) request_model = DynamicStrategyRequest( @@ -161,36 +159,20 @@ def test__apply_strategy_with_empty_subject_template_fields__raises_exception( assert exc_info.value.http_status_code == 400 - def test__apply_strategy_with_invalid_interpreting_service_name__raises_exception( - self, client: NotificationClient, _message_template: MessageTemplate + def test__create_smtp_address_group_with_invalid_interpreting_service__raises_exception( + self, ): - address_group = AddressGroup( - interpreting_service_name="invalid_service", - fields=AddressFields(toAddresses=["address1@example.com"]), - ) - - request_model = DynamicStrategyRequest( - message_template_substitution_fields={"replacement": "value"}, - notification_strategy=DynamicNotificationStrategy( - notification_configurations=[ - DynamicNotificationConfiguration( - address_group=address_group, - message_template=_message_template, - ) - ] - ), - ) - - with pytest.raises(ApiException) as exc_info: - client.apply_notification_strategy(request=request_model) - - assert exc_info.value.http_status_code == 400 + with pytest.raises(ValidationError): + SmtpAddressGroup( + interpreting_service_name="invalid_service", + fields=SmtpAddressFields(toAddresses=["address1@example.com"]), + ) def test__apply_strategy_with_no_address_and_message_template_id__returns_none( self, client: NotificationClient, - _address_group: AddressGroup, - _message_template: MessageTemplate, + _address_group: SmtpAddressGroup, + _message_template: SmtpMessageTemplate, ): configuration = DynamicNotificationConfiguration( address_group=_address_group, @@ -210,17 +192,17 @@ def test__apply_strategy_with_no_address_and_message_template_id__returns_none( def test__apply_multiple_notification_configurations__returns_none( self, client: NotificationClient, - _address_group: AddressGroup, + _address_group: SmtpAddressGroup, ): - first_message_template = MessageTemplate( + first_message_template = SmtpMessageTemplate( interpreting_service_name="smtp", - fields=MessageTemplateFields( + fields=SmtpMessageTemplateFields( subject_template="subject1", body_template="body1" ), ) - second_message_template = MessageTemplate( + second_message_template = SmtpMessageTemplate( interpreting_service_name="smtp", - fields=MessageTemplateFields( + fields=SmtpMessageTemplateFields( subject_template="subject2", body_template="body2" ), ) From 4ea0288a2690f76800810a17e3e80569b967c7c0 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 29 Jan 2026 19:15:13 +0530 Subject: [PATCH 24/33] fix: Import error in pipeline --- .../models/_dynamic_notification_configuration.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index 0bcc0e4e..872274b2 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import TypeVar from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.notification.models._address_group import AddressGroup @@ -12,6 +12,9 @@ from pydantic import Field, model_validator, ValidationError +T = TypeVar("T", bound="MessageTemplate") + + class DynamicNotificationConfiguration(JsonModel): """Model for notification configuration defining address groups and message template for the notification. @@ -31,7 +34,7 @@ class DynamicNotificationConfiguration(JsonModel): """Message template defining notification content structure""" @model_validator(mode="after") - def validate_required_pairs(self) -> Self: + def validate_required_pairs(self: T) -> T: """Validator to check at least one of address_group_id or address_group, and one of message_template_id or message_template is present. """ From 4e71b5a1b3b80c4b042ce1c69402d626a280db91 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Thu, 29 Jan 2026 19:23:02 +0530 Subject: [PATCH 25/33] fix: Type annotation issue --- .../models/_dynamic_notification_configuration.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index 872274b2..80437a0d 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -1,4 +1,4 @@ -from typing import TypeVar +from __future__ import annotations from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.notification.models._address_group import AddressGroup @@ -12,9 +12,6 @@ from pydantic import Field, model_validator, ValidationError -T = TypeVar("T", bound="MessageTemplate") - - class DynamicNotificationConfiguration(JsonModel): """Model for notification configuration defining address groups and message template for the notification. @@ -34,7 +31,7 @@ class DynamicNotificationConfiguration(JsonModel): """Message template defining notification content structure""" @model_validator(mode="after") - def validate_required_pairs(self: T) -> T: + def validate_required_pairs(self) -> DynamicNotificationConfiguration: """Validator to check at least one of address_group_id or address_group, and one of message_template_id or message_template is present. """ From b6ceec40123fdfe9bb7c63f8de3feb5dc02c0c1d Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 30 Jan 2026 10:45:09 +0530 Subject: [PATCH 26/33] refactor: Minor changes including change in function name --- examples/notification/notification.py | 2 +- .../clients/notification/_notification_client.py | 2 +- .../models/_dynamic_notification_configuration.py | 4 ++-- .../clients/notification/models/_smtp_address_group.py | 2 +- .../notification/models/_smtp_message_template.py | 2 +- .../notification/test_notification_client.py | 10 +++++----- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 044b7686..0dd31ca2 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -115,5 +115,5 @@ def create_notification_request_for_alarm( address_group=address_group, message_template=alarm_creation_template, ) -notification_client.apply_notification_strategy(request=notification_for_alarm_creation) +notification_client.apply_dynamic_notification_strategy(request=notification_for_alarm_creation) print("Notification sent for alarm creation") diff --git a/nisystemlink/clients/notification/_notification_client.py b/nisystemlink/clients/notification/_notification_client.py index 046f5756..a373f8f3 100644 --- a/nisystemlink/clients/notification/_notification_client.py +++ b/nisystemlink/clients/notification/_notification_client.py @@ -32,7 +32,7 @@ def __init__(self, configuration: core.HttpConfiguration | None = None): super().__init__(configuration, base_path="/ninotification/v1/") @post("apply-dynamic-strategy") - def apply_notification_strategy( + def apply_dynamic_notification_strategy( self, request: models.DynamicStrategyRequest ) -> None: """Applies the notification strategy from the given request. diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index 80437a0d..752d9b51 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -36,9 +36,9 @@ def validate_required_pairs(self) -> DynamicNotificationConfiguration: one of message_template_id or message_template is present. """ if self.address_group_id is None and self.address_group is None: - raise ValidationError("AddressGroupId is required.") + raise ValidationError("One of either AddressGroupId or AddressGroup is required.") if self.message_template_id is None and self.message_template is None: - raise ValidationError("MessageTemplateId is required.") + raise ValidationError("One of either MessageTemplateId or MessageTemplate is required.") return self diff --git a/nisystemlink/clients/notification/models/_smtp_address_group.py b/nisystemlink/clients/notification/models/_smtp_address_group.py index 32f73a5c..f4d53c61 100644 --- a/nisystemlink/clients/notification/models/_smtp_address_group.py +++ b/nisystemlink/clients/notification/models/_smtp_address_group.py @@ -22,7 +22,7 @@ class SmtpAddressFields(JsonModel): class SmtpAddressGroup(BaseNotificationMetadata): """Model defining notification recipients for SMTP service.""" - interpreting_service_name: Literal["smtp"] + interpreting_service_name: Literal["smtp"] = "smtp" """Service name for SMTP-based interpretation.""" fields: SmtpAddressFields diff --git a/nisystemlink/clients/notification/models/_smtp_message_template.py b/nisystemlink/clients/notification/models/_smtp_message_template.py index eebc2bde..521a0c4d 100644 --- a/nisystemlink/clients/notification/models/_smtp_message_template.py +++ b/nisystemlink/clients/notification/models/_smtp_message_template.py @@ -19,7 +19,7 @@ class SmtpMessageTemplateFields(JsonModel): class SmtpMessageTemplate(BaseNotificationMetadata): """Model defining message template for SMTP service""" - interpreting_service_name: Literal["smtp"] + interpreting_service_name: Literal["smtp"] = "smtp" """Service name for SMTP-based interpretation.""" fields: SmtpMessageTemplateFields diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index d574dd20..791848f8 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -94,7 +94,7 @@ class TestNotificationClient: def test__apply_strategy_with_correct_request__returns_none( self, client: NotificationClient, request_model: DynamicStrategyRequest ): - assert client.apply_notification_strategy(request=request_model) is None + assert client.apply_dynamic_notification_strategy(request=request_model) is None def test__apply_strategy_with_invalid_recipient__raises_exception( self, @@ -119,7 +119,7 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( ) with pytest.raises(ApiException) as exc_info: - client.apply_notification_strategy(request=request_model) + client.apply_dynamic_notification_strategy(request=request_model) assert exc_info.value.http_status_code == 400 @@ -155,7 +155,7 @@ def test__apply_strategy_with_empty_subject_template_fields__raises_exception( ) with pytest.raises(ApiException) as exc_info: - client.apply_notification_strategy(request=request_model) + client.apply_dynamic_notification_strategy(request=request_model) assert exc_info.value.http_status_code == 400 @@ -187,7 +187,7 @@ def test__apply_strategy_with_no_address_and_message_template_id__returns_none( ] ), ) - assert client.apply_notification_strategy(request=request_model) is None + assert client.apply_dynamic_notification_strategy(request=request_model) is None def test__apply_multiple_notification_configurations__returns_none( self, @@ -226,4 +226,4 @@ def test__apply_multiple_notification_configurations__returns_none( ] ), ) - assert client.apply_notification_strategy(request=request_model) is None + assert client.apply_dynamic_notification_strategy(request=request_model) is None From 9b29bb5a3ba4e24a251c1b53f6b894f75d08df7b Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 30 Jan 2026 12:17:23 +0530 Subject: [PATCH 27/33] fix: Faked http calls for test --- examples/notification/notification.py | 4 ++- .../_dynamic_notification_configuration.py | 8 +++-- .../notification/test_notification_client.py | 33 ++++++++++++++----- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 0dd31ca2..13c26b77 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -115,5 +115,7 @@ def create_notification_request_for_alarm( address_group=address_group, message_template=alarm_creation_template, ) -notification_client.apply_dynamic_notification_strategy(request=notification_for_alarm_creation) +notification_client.apply_dynamic_notification_strategy( + request=notification_for_alarm_creation +) print("Notification sent for alarm creation") diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index 752d9b51..252350d2 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -36,9 +36,13 @@ def validate_required_pairs(self) -> DynamicNotificationConfiguration: one of message_template_id or message_template is present. """ if self.address_group_id is None and self.address_group is None: - raise ValidationError("One of either AddressGroupId or AddressGroup is required.") + raise ValidationError( + "One of either AddressGroupId or AddressGroup is required." + ) if self.message_template_id is None and self.message_template is None: - raise ValidationError("One of either MessageTemplateId or MessageTemplate is required.") + raise ValidationError( + "One of either MessageTemplateId or MessageTemplate is required." + ) return self diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 791848f8..3bed33ac 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -1,4 +1,5 @@ import pytest +import responses from nisystemlink.clients.core import ApiException from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.notification import NotificationClient @@ -13,6 +14,8 @@ ) from pydantic import ValidationError +BASE_URL = "https://test-api.lifecyclesolutions.ni.com" + @pytest.fixture def _address_group(): @@ -91,9 +94,15 @@ def client(enterprise_config: HttpConfiguration) -> NotificationClient: @pytest.mark.integration @pytest.mark.enterprise class TestNotificationClient: + @responses.activate def test__apply_strategy_with_correct_request__returns_none( self, client: NotificationClient, request_model: DynamicStrategyRequest ): + responses.add( + responses.POST, + f"{BASE_URL}/ninotification/v1/apply-dynamic-strategy", + status=204, + ) assert client.apply_dynamic_notification_strategy(request=request_model) is None def test__apply_strategy_with_invalid_recipient__raises_exception( @@ -118,14 +127,12 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( ), ) - with pytest.raises(ApiException) as exc_info: + with pytest.raises(ApiException, match="Bad Request") as exc_info: client.apply_dynamic_notification_strategy(request=request_model) assert exc_info.value.http_status_code == 400 - def test__create_strategy_with_no_configurations__raises_exception( - self, client: NotificationClient - ): + def test__create_strategy_with_no_configurations__raises_exception(self): with pytest.raises(ValidationError): DynamicStrategyRequest( message_template_substitution_fields={"replacement": "value"}, @@ -154,26 +161,30 @@ def test__apply_strategy_with_empty_subject_template_fields__raises_exception( ), ) - with pytest.raises(ApiException) as exc_info: + with pytest.raises(ApiException, match="Bad Request") as exc_info: client.apply_dynamic_notification_strategy(request=request_model) assert exc_info.value.http_status_code == 400 - def test__create_smtp_address_group_with_invalid_interpreting_service__raises_exception( - self, - ): + def test__create_smtp_address_group_with_invalid_interpreting_service__raises_exception(self): with pytest.raises(ValidationError): SmtpAddressGroup( interpreting_service_name="invalid_service", fields=SmtpAddressFields(toAddresses=["address1@example.com"]), ) + @responses.activate def test__apply_strategy_with_no_address_and_message_template_id__returns_none( self, client: NotificationClient, _address_group: SmtpAddressGroup, _message_template: SmtpMessageTemplate, ): + responses.add( + responses.POST, + f"{BASE_URL}/ninotification/v1/apply-dynamic-strategy", + status=204, + ) configuration = DynamicNotificationConfiguration( address_group=_address_group, message_template=_message_template, @@ -189,11 +200,17 @@ def test__apply_strategy_with_no_address_and_message_template_id__returns_none( ) assert client.apply_dynamic_notification_strategy(request=request_model) is None + @responses.activate def test__apply_multiple_notification_configurations__returns_none( self, client: NotificationClient, _address_group: SmtpAddressGroup, ): + responses.add( + responses.POST, + f"{BASE_URL}/ninotification/v1/apply-dynamic-strategy", + status=204, + ) first_message_template = SmtpMessageTemplate( interpreting_service_name="smtp", fields=SmtpMessageTemplateFields( From 109fcb563039a8aac32f96d21859f66c3e14f547 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 30 Jan 2026 12:19:27 +0530 Subject: [PATCH 28/33] refactor: Linting issue --- tests/integration/notification/test_notification_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 3bed33ac..55169fea 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -166,7 +166,9 @@ def test__apply_strategy_with_empty_subject_template_fields__raises_exception( assert exc_info.value.http_status_code == 400 - def test__create_smtp_address_group_with_invalid_interpreting_service__raises_exception(self): + def test__create_smtp_address_group_with_invalid_interpreting_service__raises_exception( + self, + ): with pytest.raises(ValidationError): SmtpAddressGroup( interpreting_service_name="invalid_service", From e3114e0cc3a7f700e50446b1429116d9037e90c0 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 30 Jan 2026 17:36:40 +0530 Subject: [PATCH 29/33] refactor: Change in docstring and variable names --- docs/api_reference/notification.rst | 2 +- docs/getting_started.rst | 2 +- .../notification/models/_address_group.py | 2 +- .../models/_base_notification_metadata.py | 4 +- .../notification/models/_message_template.py | 2 +- .../notification/test_notification_client.py | 40 +++++++++---------- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/api_reference/notification.rst b/docs/api_reference/notification.rst index a66aecc7..d4885590 100644 --- a/docs/api_reference/notification.rst +++ b/docs/api_reference/notification.rst @@ -7,7 +7,7 @@ nisystemlink.clients.notification :exclude-members: __init__ .. automethod:: __init__ - .. automethod:: apply_notification_strategy + .. automethod:: apply_dynamic_notification_strategy .. automodule:: nisystemlink.clients.notification.models :members: diff --git a/docs/getting_started.rst b/docs/getting_started.rst index f35c49f4..c6962edc 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -522,7 +522,7 @@ default connection. The default connection depends on your environment. With a :class:`.NotificationClient` object, you can: -* Apply dynamic notification strategy using :meth:`~.NotificationClient.apply_notification_strategy` +* Apply dynamic notification strategy using :meth:`~.NotificationClient.apply_dynamic_notification_strategy` Examples ~~~~~~~~ diff --git a/nisystemlink/clients/notification/models/_address_group.py b/nisystemlink/clients/notification/models/_address_group.py index 45bb9025..dec29115 100644 --- a/nisystemlink/clients/notification/models/_address_group.py +++ b/nisystemlink/clients/notification/models/_address_group.py @@ -7,7 +7,7 @@ class AddressGroup(BaseNotificationMetadata): """Model defining notification recipients for generic service.""" interpreting_service_name: str - """Service identifier for generic interpretation.""" + """Name of the interpreting service.""" fields: Dict[str, List[str]] """Address group's fields. Requires at least one valid recipient.""" diff --git a/nisystemlink/clients/notification/models/_base_notification_metadata.py b/nisystemlink/clients/notification/models/_base_notification_metadata.py index 9bc27db5..ae2f4922 100644 --- a/nisystemlink/clients/notification/models/_base_notification_metadata.py +++ b/nisystemlink/clients/notification/models/_base_notification_metadata.py @@ -5,7 +5,7 @@ class BaseNotificationMetadata(JsonModel): id: str | None = None - """identifier of this notification metadata""" + """Identifier of the notification metadata""" display_name: str | None = None """Display name of the object. @@ -20,4 +20,4 @@ class BaseNotificationMetadata(JsonModel): """ referencing_notification_strategies: List[str] | None = None - """List of strategies used for referencing,""" + """List of notification strategies referencing the notification metadata""" diff --git a/nisystemlink/clients/notification/models/_message_template.py b/nisystemlink/clients/notification/models/_message_template.py index 3d3d9f96..c8556be6 100644 --- a/nisystemlink/clients/notification/models/_message_template.py +++ b/nisystemlink/clients/notification/models/_message_template.py @@ -7,7 +7,7 @@ class MessageTemplate(BaseNotificationMetadata): """Model defining the notification content structure.""" interpreting_service_name: str - """Service identifier for generic interpretation.""" + """Name of the interpreting service""" fields: Dict[str, str] """Template fields for message.""" diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 55169fea..39c82a85 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -18,7 +18,7 @@ @pytest.fixture -def _address_group(): +def _smtp_address_group(): """Returns the created address group.""" return SmtpAddressGroup( id="address_group_id", @@ -35,7 +35,7 @@ def _address_group(): @pytest.fixture -def _message_template(): +def _smtp_message_template(): """Returns the created message template.""" return SmtpMessageTemplate( id="message_template_id", @@ -51,14 +51,14 @@ def _message_template(): @pytest.fixture def _notification_configuration( - _address_group: SmtpAddressGroup, _message_template: SmtpMessageTemplate + _smtp_address_group: SmtpAddressGroup, _smtp_message_template: SmtpMessageTemplate ): """Returns the created notification configuration.""" return DynamicNotificationConfiguration( address_group_id="address_group_id", message_template_id="message_template_id", - address_group=_address_group, - message_template=_message_template, + address_group=_smtp_address_group, + message_template=_smtp_message_template, ) @@ -105,10 +105,10 @@ def test__apply_strategy_with_correct_request__returns_none( ) assert client.apply_dynamic_notification_strategy(request=request_model) is None - def test__apply_strategy_with_invalid_recipient__raises_exception( + def test__apply_strategy_with_invalid_recipient_for_smtp_service__raises_exception( self, client: NotificationClient, - _message_template: SmtpMessageTemplate, + _smtp_message_template: SmtpMessageTemplate, ): address_group = SmtpAddressGroup( interpreting_service_name="smtp", @@ -121,7 +121,7 @@ def test__apply_strategy_with_invalid_recipient__raises_exception( notification_configurations=[ DynamicNotificationConfiguration( address_group=address_group, - message_template=_message_template, + message_template=_smtp_message_template, ) ] ), @@ -141,8 +141,8 @@ def test__create_strategy_with_no_configurations__raises_exception(self): ), ) - def test__apply_strategy_with_empty_subject_template_fields__raises_exception( - self, client: NotificationClient, _address_group: SmtpAddressGroup + def test__apply_strategy_with_empty_subject_for_smtp_message_template__raises_exception( + self, client: NotificationClient, _smtp_address_group: SmtpAddressGroup ): message_template = SmtpMessageTemplate( interpreting_service_name="smtp", @@ -154,7 +154,7 @@ def test__apply_strategy_with_empty_subject_template_fields__raises_exception( notification_strategy=DynamicNotificationStrategy( notification_configurations=[ DynamicNotificationConfiguration( - address_group=_address_group, + address_group=_smtp_address_group, message_template=message_template, ) ] @@ -166,7 +166,7 @@ def test__apply_strategy_with_empty_subject_template_fields__raises_exception( assert exc_info.value.http_status_code == 400 - def test__create_smtp_address_group_with_invalid_interpreting_service__raises_exception( + def test__create_address_group_with_invalid_interpreting_service_name__raises_exception( self, ): with pytest.raises(ValidationError): @@ -176,11 +176,11 @@ def test__create_smtp_address_group_with_invalid_interpreting_service__raises_ex ) @responses.activate - def test__apply_strategy_with_no_address_and_message_template_id__returns_none( + def test__apply_strategy_with_no_address_group_id_and_message_template_id__returns_none( self, client: NotificationClient, - _address_group: SmtpAddressGroup, - _message_template: SmtpMessageTemplate, + _smtp_address_group: SmtpAddressGroup, + _smtp_message_template: SmtpMessageTemplate, ): responses.add( responses.POST, @@ -188,8 +188,8 @@ def test__apply_strategy_with_no_address_and_message_template_id__returns_none( status=204, ) configuration = DynamicNotificationConfiguration( - address_group=_address_group, - message_template=_message_template, + address_group=_smtp_address_group, + message_template=_smtp_message_template, ) request_model = DynamicStrategyRequest( @@ -206,7 +206,7 @@ def test__apply_strategy_with_no_address_and_message_template_id__returns_none( def test__apply_multiple_notification_configurations__returns_none( self, client: NotificationClient, - _address_group: SmtpAddressGroup, + _smtp_address_group: SmtpAddressGroup, ): responses.add( responses.POST, @@ -227,12 +227,12 @@ def test__apply_multiple_notification_configurations__returns_none( ) configuration1 = DynamicNotificationConfiguration( - address_group=_address_group, + address_group=_smtp_address_group, message_template=first_message_template, ) configuration2 = DynamicNotificationConfiguration( - address_group=_address_group, + address_group=_smtp_address_group, message_template=second_message_template, ) From 4554a1aaa95693bcd6f70e9a35311f28ebfc1593 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 30 Jan 2026 17:56:46 +0530 Subject: [PATCH 30/33] refactor: Change in test names --- .../models/_dynamic_notification_configuration.py | 6 +++--- .../notification/test_notification_client.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py index 252350d2..4e092ede 100644 --- a/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py +++ b/nisystemlink/clients/notification/models/_dynamic_notification_configuration.py @@ -9,7 +9,7 @@ from nisystemlink.clients.notification.models._smtp_message_template import ( SmtpMessageTemplate, ) -from pydantic import Field, model_validator, ValidationError +from pydantic import Field, model_validator class DynamicNotificationConfiguration(JsonModel): @@ -36,12 +36,12 @@ def validate_required_pairs(self) -> DynamicNotificationConfiguration: one of message_template_id or message_template is present. """ if self.address_group_id is None and self.address_group is None: - raise ValidationError( + raise ValueError( "One of either AddressGroupId or AddressGroup is required." ) if self.message_template_id is None and self.message_template is None: - raise ValidationError( + raise ValueError( "One of either MessageTemplateId or MessageTemplate is required." ) diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 39c82a85..914d7e22 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -95,7 +95,7 @@ def client(enterprise_config: HttpConfiguration) -> NotificationClient: @pytest.mark.enterprise class TestNotificationClient: @responses.activate - def test__apply_strategy_with_correct_request__returns_none( + def test__apply_dynamic_strategy_with_correct_request__returns_none( self, client: NotificationClient, request_model: DynamicStrategyRequest ): responses.add( @@ -105,7 +105,7 @@ def test__apply_strategy_with_correct_request__returns_none( ) assert client.apply_dynamic_notification_strategy(request=request_model) is None - def test__apply_strategy_with_invalid_recipient_for_smtp_service__raises_exception( + def test__apply_dynamic_strategy_with_invalid_recipient_for_smtp_service__raises_exception( self, client: NotificationClient, _smtp_message_template: SmtpMessageTemplate, @@ -141,7 +141,7 @@ def test__create_strategy_with_no_configurations__raises_exception(self): ), ) - def test__apply_strategy_with_empty_subject_for_smtp_message_template__raises_exception( + def test__apply_dynamic_strategy_with_empty_subject_for_smtp_message_template__raises_exception( self, client: NotificationClient, _smtp_address_group: SmtpAddressGroup ): message_template = SmtpMessageTemplate( @@ -176,7 +176,7 @@ def test__create_address_group_with_invalid_interpreting_service_name__raises_ex ) @responses.activate - def test__apply_strategy_with_no_address_group_id_and_message_template_id__returns_none( + def test__apply_dynamic_strategy_with_no_address_group_id_and_message_template_id_in_config__returns_none( self, client: NotificationClient, _smtp_address_group: SmtpAddressGroup, @@ -203,7 +203,7 @@ def test__apply_strategy_with_no_address_group_id_and_message_template_id__retur assert client.apply_dynamic_notification_strategy(request=request_model) is None @responses.activate - def test__apply_multiple_notification_configurations__returns_none( + def test__apply_dynamic_strategy_with_multiple_notification_configurations__returns_none( self, client: NotificationClient, _smtp_address_group: SmtpAddressGroup, From 6ae03427437ac19e18384a438cf7ef53e4b0463c Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Fri, 30 Jan 2026 18:00:19 +0530 Subject: [PATCH 31/33] tests: Include cases to test configuration validator --- .../notification/test_notification_client.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index 914d7e22..d0e00c4c 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -246,3 +246,27 @@ def test__apply_dynamic_strategy_with_multiple_notification_configurations__retu ), ) assert client.apply_dynamic_notification_strategy(request=request_model) is None + + def test__create_configuration_with_no_address_group_id_and_address_group__raise_exception( + self, _smtp_message_template: SmtpMessageTemplate + ): + with pytest.raises( + ValidationError, + match="One of either AddressGroupId or AddressGroup is required.", + ): + DynamicNotificationConfiguration( + message_template_id="message_template_id", + message_template=_smtp_message_template, + ) + + def test__create_configuration_with_no_message_template_id_and_message_template__raise_exception( + self, _smtp_address_group: SmtpAddressGroup + ): + with pytest.raises( + ValidationError, + match="One of either MessageTemplateId or MessageTemplate is required.", + ): + DynamicNotificationConfiguration( + address_group_id="address_group_id", + address_group=_smtp_address_group, + ) From 4b6b28eed94b19b0b4c301d47ee31a31cc23cea0 Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Mon, 2 Feb 2026 10:55:39 +0530 Subject: [PATCH 32/33] refactor: Included model validator to set service name on smtp models --- examples/notification/notification.py | 2 -- .../clients/notification/models/_smtp_address_group.py | 8 ++++++++ .../notification/models/_smtp_message_template.py | 10 ++++++++++ .../notification/test_notification_client.py | 6 ------ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/examples/notification/notification.py b/examples/notification/notification.py index 13c26b77..0b65d695 100644 --- a/examples/notification/notification.py +++ b/examples/notification/notification.py @@ -89,7 +89,6 @@ def create_notification_request_for_alarm( # Create address group address_group = SmtpAddressGroup( - interpreting_service_name="smtp", display_name="Alarm Notification Recipients", properties={"address group": "Alarm"}, fields=recipients, @@ -97,7 +96,6 @@ def create_notification_request_for_alarm( # Create mail template for alarm creation notification alarm_creation_template = SmtpMessageTemplate( - interpreting_service_name="smtp", display_name="Alarm Creation Template", fields=SmtpMessageTemplateFields( subject_template="Alarm Created: ", diff --git a/nisystemlink/clients/notification/models/_smtp_address_group.py b/nisystemlink/clients/notification/models/_smtp_address_group.py index f4d53c61..c544116d 100644 --- a/nisystemlink/clients/notification/models/_smtp_address_group.py +++ b/nisystemlink/clients/notification/models/_smtp_address_group.py @@ -4,6 +4,7 @@ from nisystemlink.clients.notification.models._base_notification_metadata import ( BaseNotificationMetadata, ) +from pydantic import model_validator class SmtpAddressFields(JsonModel): @@ -40,3 +41,10 @@ class SmtpAddressGroup(BaseNotificationMetadata): bccAddresses: [ "address3@example.com" ] } """ + + @model_validator(mode="before") + @classmethod + def set_interpreting_service_name(cls, data: "SmtpAddressGroup") -> "SmtpAddressGroup": + if isinstance(data, dict) and "interpreting_service_name" not in data: + data["interpreting_service_name"] = "smtp" + return data diff --git a/nisystemlink/clients/notification/models/_smtp_message_template.py b/nisystemlink/clients/notification/models/_smtp_message_template.py index 521a0c4d..d1931173 100644 --- a/nisystemlink/clients/notification/models/_smtp_message_template.py +++ b/nisystemlink/clients/notification/models/_smtp_message_template.py @@ -4,6 +4,7 @@ from nisystemlink.clients.notification.models._base_notification_metadata import ( BaseNotificationMetadata, ) +from pydantic import model_validator class SmtpMessageTemplateFields(JsonModel): @@ -24,3 +25,12 @@ class SmtpMessageTemplate(BaseNotificationMetadata): fields: SmtpMessageTemplateFields """Subject and body template fields for SMTP messages.""" + + @model_validator(mode="before") + @classmethod + def set_interpreting_service_name( + cls, data: "SmtpMessageTemplate" + ) -> "SmtpMessageTemplate": + if isinstance(data, dict) and "interpreting_service_name" not in data: + data["interpreting_service_name"] = "smtp" + return data diff --git a/tests/integration/notification/test_notification_client.py b/tests/integration/notification/test_notification_client.py index d0e00c4c..266ea77d 100644 --- a/tests/integration/notification/test_notification_client.py +++ b/tests/integration/notification/test_notification_client.py @@ -22,7 +22,6 @@ def _smtp_address_group(): """Returns the created address group.""" return SmtpAddressGroup( id="address_group_id", - interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, fields=SmtpAddressFields( @@ -39,7 +38,6 @@ def _smtp_message_template(): """Returns the created message template.""" return SmtpMessageTemplate( id="message_template_id", - interpreting_service_name="smtp", display_name="name", properties={"property": "value"}, fields=SmtpMessageTemplateFields( @@ -111,7 +109,6 @@ def test__apply_dynamic_strategy_with_invalid_recipient_for_smtp_service__raises _smtp_message_template: SmtpMessageTemplate, ): address_group = SmtpAddressGroup( - interpreting_service_name="smtp", fields=SmtpAddressFields(toAddresses=["invalid-email"]), ) @@ -145,7 +142,6 @@ def test__apply_dynamic_strategy_with_empty_subject_for_smtp_message_template__r self, client: NotificationClient, _smtp_address_group: SmtpAddressGroup ): message_template = SmtpMessageTemplate( - interpreting_service_name="smtp", fields=SmtpMessageTemplateFields(subject_template=""), ) @@ -214,13 +210,11 @@ def test__apply_dynamic_strategy_with_multiple_notification_configurations__retu status=204, ) first_message_template = SmtpMessageTemplate( - interpreting_service_name="smtp", fields=SmtpMessageTemplateFields( subject_template="subject1", body_template="body1" ), ) second_message_template = SmtpMessageTemplate( - interpreting_service_name="smtp", fields=SmtpMessageTemplateFields( subject_template="subject2", body_template="body2" ), From cb1321c56a5322883e24f7abedf4e4bdcd5bbc6c Mon Sep 17 00:00:00 2001 From: Rohith Raja Sakthivel Date: Mon, 2 Feb 2026 10:57:33 +0530 Subject: [PATCH 33/33] refactor: Linting --- .../clients/notification/models/_smtp_address_group.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nisystemlink/clients/notification/models/_smtp_address_group.py b/nisystemlink/clients/notification/models/_smtp_address_group.py index c544116d..55fdcd05 100644 --- a/nisystemlink/clients/notification/models/_smtp_address_group.py +++ b/nisystemlink/clients/notification/models/_smtp_address_group.py @@ -44,7 +44,9 @@ class SmtpAddressGroup(BaseNotificationMetadata): @model_validator(mode="before") @classmethod - def set_interpreting_service_name(cls, data: "SmtpAddressGroup") -> "SmtpAddressGroup": + def set_interpreting_service_name( + cls, data: "SmtpAddressGroup" + ) -> "SmtpAddressGroup": if isinstance(data, dict) and "interpreting_service_name" not in data: data["interpreting_service_name"] = "smtp" return data