From cfbb93e258086c33017a4b5b189ff2a100defdc8 Mon Sep 17 00:00:00 2001
From: Benedikt Seeger <benedikt.seeger@ptb.de>
Date: Tue, 25 Mar 2025 14:27:55 +0100
Subject: [PATCH] constructor now returns dsiunit if constructed from dsiunit

---
 pyproject.toml         |  2 +-
 src/dsiUnits.py        | 27 +++++++++++----------------
 tests/test_dsiUnits.py | 27 +++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 17 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 4fd46ce..9f51898 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "dsiunits"  # Ensure this is correctly specified
-version = "2.4.1"
+version = "2.4.2"
 description = "This is a Python module for handling the SI units as objects in Python, parsing them from strings and converting them to Latex and Unicode, as well as performing math operations and calculating scale factors."
 authors = [
     { name="Benedikt Seeger", email="benedikt.seeger@ptb.de" },
diff --git a/src/dsiUnits.py b/src/dsiUnits.py
index 8af756b..0cbc5bf 100644
--- a/src/dsiUnits.py
+++ b/src/dsiUnits.py
@@ -41,11 +41,17 @@ dsiParserInstance = dsiParser()
 
 
 class dsiUnit:
+    def __new__(cls, dsiString=None):
+        # If the argument is already a dsiUnit instance, return it directly.
+        if isinstance(dsiString, cls):
+            return dsiString
+        return super().__new__(cls)
+
     def __init__(self, dsiString: str):
-        """
-        Args:
-            dsiString (str): the D-SI unit string to be parsed
-        """
+        # Prevent reinitialization if this instance was already created.
+        if hasattr(self, '_initialized') and self._initialized:
+            return
+        self._initialized = True
         try:
             parsedDsiUnit = dsiParserInstance.parse(dsiString)
             self.dsiString, self.tree, self.warnings, self.nonDsiUnit = parsedDsiUnit
@@ -68,18 +74,6 @@ class dsiUnit:
         nonDsiUnit=False,
         scaleFactor=1.0,
     ):
-        """
-        Class method to create an instance from a D-SI tree and other optional arguments.
-
-        Args:
-            dsiString (str): the D-SI unit string
-            dsiTree (list): List of lists of nodes as tuples containing (prefix: str,unit: str,exponent: Fraction=Fraction(1),scaleFactor: float = 1.0)
-            warningMessages (list): List of warning messages
-            nonDsiUnit (bool): Flag indicating if it's a non-D-SI unit
-
-        Returns:
-            DsiUnit: An instance of DsiUnit
-        """
         instance = cls.__new__(cls)
         if nonDsiUnit:
             dsiTree = [dsiString]
@@ -91,6 +85,7 @@ class dsiUnit:
         instance.nonDsiUnit = nonDsiUnit
         instance.valid = len(warningMessages) == 0
         instance.scaleFactor = scaleFactor
+        instance._initialized = True
         return instance
 
     def toLatex(self, wrapper=None, prefix=None, suffix=None):
diff --git a/tests/test_dsiUnits.py b/tests/test_dsiUnits.py
index e1c1a5c..2209883 100644
--- a/tests/test_dsiUnits.py
+++ b/tests/test_dsiUnits.py
@@ -626,3 +626,30 @@ def test_hash():
     Hm2_2=hash(dsiUnit(r'\metre\tothe{2}'))
     assert hash(Hmm2) != hash(Hkm2) != hash(Hm2)
     assert hash(Hm2) == hash(Hm2_2)
+
+
+def test_constructor_idempotency():
+    # Create an initial dsiUnit instance from a valid D-SI string.
+    unit_str = r'\metre'
+    unit1 = dsiUnit(unit_str)
+
+    # When passing a dsiUnit instance to the constructor, it should return the same instance.
+    unit2 = dsiUnit(unit1)
+    assert unit2 is unit1, "Constructor did not return the same instance when a dsiUnit was passed."
+
+
+def test_constructor_preserves_existing_state():
+    # Create an initial dsiUnit instance.
+    unit_str = r'\metre'
+    unit1 = dsiUnit(unit_str)
+
+    # Set an extra attribute to check for preservation.
+    unit1.extra_attribute = "unchanged"
+
+    # Pass the instance to the constructor again.
+    unit2 = dsiUnit(unit1)
+
+    # The returned instance should be the same and preserve any already set attributes.
+    assert unit2 is unit1, "Constructor did not return the same instance."
+    assert hasattr(unit2, "extra_attribute"), "Extra attribute was lost on reinitialization."
+    assert unit2.extra_attribute == "unchanged", "Extra attribute value was altered."
-- 
GitLab