Repository: mozman/ezdxf

Issue:
RecursionError when pickle dump/loading dxf file
**Describe the bug**
when running pickle load/dump on a dxf object (ezdxf.document.Drawing) an RecursionError: maximum recursion depth exceeded is recived.

**To Reproduce**
Information and data needed to reproduce the error:
read a dxf file and try to dump it into a file using pickle, i saved  the file and read it as gzip.
when trying to load the saved file a RecursionError occures.

**Expected behavior**
the dxf object should be loaded correctly.
**Screenshots**
inorder to replecate the problem i have created a small notebook for it with a random dxf i found online.

[EzdxfRecursionErrorNotebook.zip](https://github.com/user-attachments/files/18505797/EzdxfRecursionErrorNotebook.zip)

Discussion:
@mozman: Pickle protocol is not supported!@mbway: ezdxf.document.Drawing is an in-memory representation of a DXF file, so dxf seems like it would be an appropriate storage format rather than a pickled ezdxf object.

If pickle as a format is a hard requirement you can pickle a string containing the contents of a DXF file?@zassoulin: pickle is not a hard requirement but i need to read alot of dxf files in a short amount of time and reading&parsing a dxf file takes significantly longer than using pickle(the same dxf file will be read multiple times), thus the whole idea of using pickle came to be.
thanks alot for the response and if you have any idea/suggestion about how might i tackle the issue i would love to hear it@mbway: fair enough. I opened a PR that hopefully works for you https://github.com/mozman/ezdxf/pull/1232

Solution patch:
diff --git a/src/ezdxf/entities/dxfns.py b/src/ezdxf/entities/dxfns.py
index e22a84d40..4fde9caa0 100644
--- a/src/ezdxf/entities/dxfns.py
+++ b/src/ezdxf/entities/dxfns.py
@@ -86,6 +86,15 @@ def copy(self, entity: DXFEntity):
     def __deepcopy__(self, memodict: Optional[dict] = None):
         return self.copy(self._entity)
 
+    def __getstate__(self) -> object:
+        return self.__dict__
+
+    def __setstate__(self, state: object) -> None:
+        if not isinstance(state, dict):
+            raise TypeError(f"invalid state: {type(state).__name__}")
+        # bypass __setattr__
+        object.__setattr__(self, "__dict__", state)
+
     def reset_handles(self):
         """Reset handle and owner to None."""
         self.__dict__["handle"] = None


Test patch:
diff --git a/tests/test_10_issues/test_issue_1231_pickling.py b/tests/test_10_issues/test_issue_1231_pickling.py
new file mode 100644
index 000000000..c5e014cab
--- /dev/null
+++ b/tests/test_10_issues/test_issue_1231_pickling.py
@@ -0,0 +1,36 @@
+import io
+import pickle
+from collections.abc import Iterator
+from pathlib import Path
+
+import pytest
+
+import ezdxf
+from ezdxf.document import Drawing
+
+EXAMPLES = Path(__file__).parent.parent.parent / "examples_dxf"
+
+
+def example_dxfs() -> Iterator[Path]:
+    for item in EXAMPLES.iterdir():
+        if item.suffix.lower() == ".dxf":
+            yield item
+
+
+def _to_dxf_str(doc: Drawing) -> str:
+    stream = io.StringIO()
+    doc.write(stream)
+    return stream.getvalue()
+
+
+def test_document_pickle(monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.setattr(ezdxf.options, "write_fixed_meta_data_for_testing", True)
+
+    for item in example_dxfs():
+        print(f"testing pickling of {item}")
+
+        doc = ezdxf.readfile(item)
+        doc_serialized = pickle.dumps(doc)
+        doc_loaded = pickle.loads(doc_serialized)
+
+        assert _to_dxf_str(doc) == _to_dxf_str(doc_loaded)