Repository: cwacek/python-jsonschema-objects

Issue:
deep-copying of instances seems not to work
Here is a minimal viable example

```
import pytest
import fastjsonschema
import python_jsonschema_objects as pjs

from dataclasses import dataclass
import json
import copy
import pprint
pp = pprint.PrettyPrinter(indent=2)

lpid_class_schema = {
    '$id': 'https://example.com/schema-repository/lpid',
    '$schema': 'http://json-schema.org/draft-07/schema/#',
    'type': 'object',
    'title': 'Logical Process ID',
    'properties': {'lpid_str': {'type': 'string',}},
    'required': ['lpid_str'],
    'additionalProperties': True}


@dataclass()
class A:
    a_str: str


def test_pjs_deepcopy():
    # Class building and instantiation work great and round-trip.
    lpid_class_builder = pjs.ObjectBuilder(lpid_class_schema)
    LPID = lpid_class_builder.classes.LogicalProcessId
    lpid = LPID(lpid_str='foobaz')
    instance = json.loads(lpid.serialize())
    fastjsonschema.validate(lpid_class_schema, instance)

    # But deep-copying with objects of generated classes doesn't terminate.
    with pytest.raises(RecursionError) as e_info:
        lpid_2 = copy.deepcopy(lpid)
    pp.pprint(e_info)

    # Deep-copying seems to work with instances of normal popos.
    a = A(a_str='barqux')
    assert a
    a_2 = copy.deepcopy(a)
    assert a_2
    assert a == a_2
```

EDIT: A workaround might be to create a new, deeply copied instance object from the serialized JSON. Not sure whether you have code to do that. Would be a lot of boilerplate to write for a bunch of classes.

Discussion:

Patch:
diff --git a/python_jsonschema_objects/classbuilder.py b/python_jsonschema_objects/classbuilder.py
index 809095e..5524b18 100644
--- a/python_jsonschema_objects/classbuilder.py
+++ b/python_jsonschema_objects/classbuilder.py
@@ -127,6 +127,9 @@ class ProtocolBase(collections.MutableMapping):
         obj.validate()
         return obj
 
+    def __deepcopy__(self, memo):
+        return self.__class__(**self.as_dict())
+
     def __new__(cls, **props):
         """ Overridden to support oneOf, where we need to
         instantiate a different class depending on what
diff --git a/tox.ini b/tox.ini
index dccf043..d1fc67c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,7 @@
 
 [tox]
-envlist = py{27,35,36,37}-jsonschema{23,24,25,26,30}-markdown{2,3}
+envlist = py{35,36,37}-jsonschema{23,24,25,26,30}-markdown{2,3}
+skip_missing_interpreters = true
 
 [testenv]
 ;install_command = pip install {opts} {packages}


Test patch:
diff --git a/test/test_regression_185.py b/test/test_regression_185.py
new file mode 100644
index 0000000..67145f3
--- /dev/null
+++ b/test/test_regression_185.py
@@ -0,0 +1,45 @@
+import pytest
+import python_jsonschema_objects as pjs
+import jsonschema
+
+import json
+import copy
+import pprint
+
+pp = pprint.PrettyPrinter(indent=2)
+
+lpid_class_schema = {
+    "$id": "https://example.com/schema-repository/lpid",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type": "object",
+    "title": "Logical Process ID",
+    "properties": {"lpid_str": {"type": "string"}},
+    "required": ["lpid_str"],
+    "additionalProperties": True,
+}
+
+
+class A(object):
+    a_str = None
+
+    def __init__(self, a_str):
+        self.a_str = a_str
+
+
+def test_regression_185_deepcopy():
+    # Class building and instantiation work great and round-trip.
+    lpid_class_builder = pjs.ObjectBuilder(lpid_class_schema)
+    LPID = lpid_class_builder.classes.LogicalProcessId
+    lpid = LPID(lpid_str="foobaz")
+    instance = json.loads(lpid.serialize())
+    jsonschema.validate(lpid_class_schema, instance)
+
+    # Deep-copying seems to work with instances of normal popos.
+    a = A(a_str="barqux")
+    assert a
+    a_2 = copy.deepcopy(a)
+    assert a_2
+
+    # But deep-copying with objects of generated classes doesn't terminate.
+    lpid_2 = copy.deepcopy(lpid)
+    assert repr(lpid_2) == repr(lpid)