Repository: DHI/mikeio
Pull request #662 (2024-03-03 15:43:51)

Fix PfsDocument.copy
Inheritance is tricky...


Patch:
diff --git a/mikeio/pfs/_pfsdocument.py b/mikeio/pfs/_pfsdocument.py
index 973bcc50..0b55a998 100644
--- a/mikeio/pfs/_pfsdocument.py
+++ b/mikeio/pfs/_pfsdocument.py
@@ -2,9 +2,10 @@ from __future__ import annotations
 import re
 import warnings
 from collections import Counter
+from collections.abc import Mapping, Sequence
 from datetime import datetime
 from pathlib import Path
-from typing import Callable, Dict, List, Mapping, Sequence, TextIO, Tuple
+from typing import Callable, Dict, List, TextIO, Tuple, overload
 
 import yaml
 
@@ -84,10 +85,10 @@ class PfsDocument(PfsSection):
         self,
         data: TextIO | PfsSection | Dict | str | Path,
         *,
-        encoding: str = "cp1252",
-        names: Sequence[str] | None = None,
-        unique_keywords: bool = False,
-    ) -> None:
+        encoding="cp1252",
+        names=None,
+        unique_keywords=False,
+    ):
 
         if isinstance(data, (str, Path)) or hasattr(data, "read"):
             if names is not None:
@@ -103,6 +104,14 @@ class PfsDocument(PfsSection):
         if self._is_FM_engine:
             self._add_all_FM_aliases()
 
+    @staticmethod
+    def from_text(text: str) -> PfsDocument:
+        """Create a PfsDocument from a string"""
+        from io import StringIO
+
+        f = StringIO(text)
+        return PfsDocument(f)
+
     @staticmethod
     def _to_nonunique_key_dict(keys, vals):
         key_count = Counter(keys)
@@ -164,6 +173,14 @@ class PfsDocument(PfsSection):
         rkeys, _ = self._unravel_items(self.items)
         return rkeys
 
+    def copy(self) -> PfsDocument:
+        """Return a deep copy of the PfsDocument"""
+
+        lines = self.write()
+        text = "\n".join(lines)
+
+        return PfsDocument.from_text(text)
+
     def _read_pfs_file(self, filename, encoding, unique_keywords=False):
         try:
             yml = self._pfs2yaml(filename, encoding)
@@ -356,7 +373,13 @@ class PfsDocument(PfsSection):
 
         return s
 
-    def write(self, filename=None):
+    @overload
+    def write(self) -> list[str]: ...
+
+    @overload
+    def write(self, filename: str) -> None: ...
+
+    def write(self, filename: str | None = None) -> list[str] | None:
         """Write object to a pfs file
 
         Parameters
@@ -380,3 +403,8 @@ class PfsDocument(PfsSection):
             f.write("\n\n")
 
             self._write_with_func(f.write, level=0)
+        return None
+
+
+# TODO remove this alias
+Pfs = PfsDocument
diff --git a/mikeio/pfs/_pfssection.py b/mikeio/pfs/_pfssection.py
index 8f69b569..800a5e82 100644
--- a/mikeio/pfs/_pfssection.py
+++ b/mikeio/pfs/_pfssection.py
@@ -281,12 +281,7 @@ class PfsSection(SimpleNamespace, MutableMapping):
 
     def copy(self) -> "PfsSection":
         """Return a copy of the PfsSection."""
-        # is all this necessary???
-        d = self.__dict__.copy()
-        for key, value in d.items():
-            if isinstance(value, PfsSection):
-                d[key] = value.to_dict().copy()
-        return self.__class__(d)
+        return PfsSection(self.to_dict())
 
     def _to_txt_lines(self) -> List[str]:
         lines: List[str] = []


Test patch:
diff --git a/tests/test_pfs.py b/tests/test_pfs.py
index c31886b4..ea92a7fa 100644
--- a/tests/test_pfs.py
+++ b/tests/test_pfs.py
@@ -283,6 +283,16 @@ def test_basic():
     assert data.POINT_1.y == 50
 
 
+def test_pfsdocument_copy():
+
+    pfs = mikeio.PfsDocument("tests/testdata/pfs/simple.pfs")
+    pfs2 = pfs.copy()
+    data = pfs.targets[0]
+    data.z_min = -4000
+
+    assert pfs2.BoundaryExtractor.z_min == -3000
+
+
 def test_ecolab():
     pfs = mikeio.PfsDocument("tests/testdata/pfs/minimal.ecolab")
     assert pfs.ECO_LAB_SETUP.MISC.DESCRIPTION == "Miscellaneous Description"