From e9c621bd320fff088157177b195c684924857eee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 00:11:42 -0700 Subject: [PATCH 1/3] Bump black from 26.1.0 to 26.3.1 (#29) Bumps [black](https://github.com/psf/black) from 26.1.0 to 26.3.1. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/26.1.0...26.3.1) --- updated-dependencies: - dependency-name: black dependency-version: 26.3.1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.development.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.development.txt b/requirements.development.txt index 31b7b1c..14f92da 100644 --- a/requirements.development.txt +++ b/requirements.development.txt @@ -1,5 +1,5 @@ # SemanticPy Dependencies: Development -black==26.1.0 +black==26.3.1 pytest==8.3.0 pytest-cov==4.1.0 pytest-codeblocks==0.17.0 From fb75a998a9a51bac655267303c539af9ec2ac75b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 00:13:46 -0700 Subject: [PATCH 2/3] Bump pytest from 8.3.0 to 9.0.3 (#30) Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.0 to 9.0.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.0...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.development.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.development.txt b/requirements.development.txt index 14f92da..5ad0747 100644 --- a/requirements.development.txt +++ b/requirements.development.txt @@ -1,6 +1,6 @@ # SemanticPy Dependencies: Development black==26.3.1 -pytest==8.3.0 +pytest==9.0.3 pytest-cov==4.1.0 pytest-codeblocks==0.17.0 pyflakes==3.4.0 From 60da61a2c897adaabf0ccf587025ad24a19ee003 Mon Sep 17 00:00:00 2001 From: Daniel Sissman Date: Fri, 8 May 2026 00:15:28 -0700 Subject: [PATCH 3/3] Item Comparison (#31) Added support for comparing items (instances of the `Node` class) based on their properties and property values; this is useful with regards to the appending behaviours added previously to support determining if a comparable item is already in the list or not. Updated the unit testing to check that items are considered both by identity and by equality when the appending behaviour is set to unique item appending. Incremented the SemanticPy library version number to `1.3.0`. --- source/semanticpy/__init__.py | 3 +- source/semanticpy/types/node.py | 52 +++++++++++++++++++--- source/semanticpy/version.txt | 2 +- tests/test_configuration_appending_mode.py | 27 ++++++++--- 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/source/semanticpy/__init__.py b/source/semanticpy/__init__.py index 4f25d0e..ad01271 100644 --- a/source/semanticpy/__init__.py +++ b/source/semanticpy/__init__.py @@ -1125,6 +1125,7 @@ def properties( sorting: list[str] | dict[str, int] = None, callback: callable = None, attribute: str | int = None, + unpack: bool = False, ) -> dict[str, object]: """Support obtaining a dictionary representation of the properties assigned to the current model instance.""" @@ -1142,7 +1143,7 @@ def properties( if context := (self._context or self._profile.get("context")): properties = {**{"@context": context}, **properties} - return properties + return properties.items() if unpack is True else properties def property(self, name: str = None, default: object = None) -> dict | None: """Support obtaining a copy of the value assigned to a model property""" diff --git a/source/semanticpy/types/node.py b/source/semanticpy/types/node.py index 011b538..fb4cb15 100644 --- a/source/semanticpy/types/node.py +++ b/source/semanticpy/types/node.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import copy import json @@ -169,6 +171,29 @@ def __getitem__(self, name: str) -> object | None: def __setitem__(self, name: str, value: object): return self.__setattr__(name, value) + def __eq__(self, other: Node) -> bool: + """Support comparing Node instances for equality.""" + + if not isinstance(other, Node): + return NotImplemented + + equal: bool = False + + properties = self.properties() + + for name, value in other.properties(unpack=True): + if name in properties: + if properties[name] == value: + equal = True + else: + equal = False + break + else: + equal = False + break + + return equal + @property def type(self) -> str: return self._type @@ -301,13 +326,30 @@ def properties( sorting: list[str] | dict[str, int] = None, callback: callable = None, attribute: str = None, - ): - if properties := self._serialize(self.data, sorting=sorting): - if prepend: + unpack: bool = False, + ) -> dict[str, object]: + properties: dict[str, object] = {} + + if isinstance(serialized := self._serialize(self.data, sorting=sorting), dict): + properties = serialized + + if prepend is None: + pass + elif isinstance(prepend, dict) and len(prepend) > 0: properties = {**prepend, **properties} + else: + raise TypeError( + "The 'prepend' argument, if specified, must reference a dictionary!" + ) - if append: + if append is None: + pass + elif isinstance(append, dict) and len(append) > 0: properties = {**properties, **append} + else: + raise TypeError( + "The 'prepend' argument, if specified, must reference a dictionary!" + ) if callable(callback): properties = self.walkthrough( @@ -316,7 +358,7 @@ def properties( container=properties, ) - return properties + return properties.items() if unpack is True else properties def walkthrough( self, diff --git a/source/semanticpy/version.txt b/source/semanticpy/version.txt index 9d4f823..f0bb29e 100644 --- a/source/semanticpy/version.txt +++ b/source/semanticpy/version.txt @@ -1 +1 @@ -1.2.9 +1.3.0 diff --git a/tests/test_configuration_appending_mode.py b/tests/test_configuration_appending_mode.py index 70d4989..1cce611 100644 --- a/tests/test_configuration_appending_mode.py +++ b/tests/test_configuration_appending_mode.py @@ -64,24 +64,39 @@ def test_appending_mode_unique(): assert object.identified_by is None # Create an instance to demonstrate the various multiple-value property appending modes - identifier = model.Identifier(content="123") + identifier1 = model.Identifier(content="123") # Assign the identifier to the `object.identified_by` property - object.identified_by = identifier + object.identified_by = identifier1 # Notice that the list of identifiers has grown to accommodate the new identifier assert isinstance(object.identified_by, list) assert len(object.identified_by) == 1 - assert object.identified_by[0] is identifier + assert object.identified_by[0] is identifier1 - # Attempt to assign the identifier to the `object.identified_by` property again - object.identified_by = identifier + # Attempt to assign the identifier to the `object.identified_by` property again; + # this is prevented by identity-comparison, as the the same instance is assigned + object.identified_by = identifier1 # Notice that the list of identifiers has not grown to accommodate the duplicate # due to the Model currently being configured to only append unique values: assert isinstance(object.identified_by, list) assert len(object.identified_by) == 1 - assert object.identified_by[0] is identifier + assert object.identified_by[0] is identifier1 + + # Create another identifier instance, this time to check via equality comparison + identifier2 = model.Identifier(content="123") + + # Attempt to assign the identifier to the `object.identified_by` property again; + # this is prevented by equality-comparison, for new instances with the same content + object.identified_by = identifier2 + + # Notice that the list of identifiers has not grown to accommodate the duplicate + # due to the Model currently being configured to only append unique values: + assert isinstance(object.identified_by, list) + assert len(object.identified_by) == 1 + assert object.identified_by[0] is identifier1 + assert object.identified_by[0] is not identifier2 # Tear down the model, removing it from the current scope and reset configuration Model.teardown()