Repository: encode/django-rest-framework

Issue:
Overriding Field.__deepcopy__ for RegexField
Problem:

`Serializers` are deepcopying a declared `RegexField`[[source]](https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L324), but it'll error out with the message. `TypeError: cannot deepcopy this pattern object`.

![screen shot 2015-02-27 at 2 12 06 pm](https://cloud.githubusercontent.com/assets/1307950/6422194/a50e10bc-be8a-11e4-9c57-bf8a2dc3ae60.png)
![screen shot 2015-02-27 at 2 12 52 pm](https://cloud.githubusercontent.com/assets/1307950/6422201/c24dc80c-be8a-11e4-8bbb-6520de0910b6.png)
![screen shot 2015-02-27 at 2 58 45 pm](https://cloud.githubusercontent.com/assets/1307950/6422766/269eb1a8-be91-11e4-96e1-aad8377bd7fa.png)



Discussion:
I think this needs a test in order to verify that now the RegexField can be deep copied with compiled regex patterns.

Something like:

``` python
# ...
serializer = TestSerializer(data={'regex_field': 'abc'})
try:
  serializer.get_fields()
except TypeError as e:
  if e.msg == 'cannot deepcopy this pattern object' and isinstance(e, TypeError):
    assert False, 'Compiled regex patterns cannot be deep copied.'
  else:
    traceback.print_traceback()
    raise e
```

Hi there,

Wondering if there's been a decision made on this? I'm running into the same deepcopy issue with RegexFields.

Having `regex` [taken out of kwargs](https://github.com/tomchristie/django-rest-framework/blob/3.4.6/rest_framework/fields.py#L734) 

```
    def __init__(self, regex, **kwargs):
        super(RegexField, self).__init__(**kwargs)
        <..snip..>
```

doesn't seem to avoid them [being in `self._kwargs`](https://github.com/tomchristie/django-rest-framework/blob/3.4.6/rest_framework/fields.py#L576)

```
 def __new__(cls, *args, **kwargs):
        """
        When a field is instantiated, we store the arguments that were used,
        so that we can present a helpful representation of the object.
        """
        <..snip..>
        instance._kwargs = kwargs
        return instance
```

and it's `self._kwargs` that actually ends up being `deepcopy()`ed in `__deepcopy__`

```
    def __deepcopy__(self, memo):
        """
        When cloning fields we instantiate using the arguments it was
        originally created with, rather than copying the complete state.
        """
        args = copy.deepcopy(self._args)
        kwargs = dict(self._kwargs)
        <..snip..>
        kwargs = copy.deepcopy(kwargs)
        <..snip..>
```

Bumping up the priority to ensure this gets reviewed sometime over the coming weeks.

To add on, this happens only when I'm using a compiled regex object as the `regex` argument (either as a positional parameter, or as a keyword argument).

Using a pattern string works fine

eg.

```
import re
USERNAME_RE = re.compile(r"[a-z0-9]+")

username = serializers.RegexField(regex=USERNAME_RE)  # This eventually fails

username = seriazliers.RegexField(regex=USERNAME_RE.pattern)  # This works
```

Noted. Thanks!


Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -252,6 +252,8 @@ class SkipField(Exception):
     pass
 
 
+REGEX_TYPE = type(re.compile(''))
+
 NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`'
 NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`'
 NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
@@ -581,16 +583,17 @@ def __deepcopy__(self, memo):
         When cloning fields we instantiate using the arguments it was
         originally created with, rather than copying the complete state.
         """
-        args = copy.deepcopy(self._args)
-        kwargs = dict(self._kwargs)
-        # Bit ugly, but we need to special case 'validators' as Django's
-        # RegexValidator does not support deepcopy.
-        # We treat validator callables as immutable objects.
+        # Treat regexes and validators as immutable.
         # See https://github.com/tomchristie/django-rest-framework/issues/1954
-        validators = kwargs.pop('validators', None)
-        kwargs = copy.deepcopy(kwargs)
-        if validators is not None:
-            kwargs['validators'] = validators
+        # and https://github.com/tomchristie/django-rest-framework/pull/4489
+        args = [
+            copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item
+            for item in self._args
+        ]
+        kwargs = {
+            key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value)
+            for key, value in self._kwargs.items()
+        }
         return self.__class__(*args, **kwargs)
 
     def __repr__(self):


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1,5 +1,6 @@
 import datetime
 import os
+import re
 import uuid
 from decimal import Decimal
 
@@ -590,6 +591,20 @@ class TestRegexField(FieldValues):
     field = serializers.RegexField(regex='[a-z][0-9]')
 
 
+class TestiCompiledRegexField(FieldValues):
+    """
+    Valid and invalid values for `RegexField`.
+    """
+    valid_inputs = {
+        'a9': 'a9',
+    }
+    invalid_inputs = {
+        'A9': ["This value does not match the required pattern."]
+    }
+    outputs = {}
+    field = serializers.RegexField(regex=re.compile('[a-z][0-9]'))
+
+
 class TestSlugField(FieldValues):
     """
     Valid and invalid values for `SlugField`.
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -2,6 +2,7 @@
 from __future__ import unicode_literals
 
 import pickle
+import re
 
 import pytest
 
@@ -337,3 +338,16 @@ def test_default_should_not_be_included_on_partial_update(self):
         assert serializer.is_valid()
         assert serializer.validated_data == {'integer': 456}
         assert serializer.errors == {}
+
+
+class TestSerializerValidationWithCompiledRegexField:
+    def setup(self):
+        class ExampleSerializer(serializers.Serializer):
+            name = serializers.RegexField(re.compile(r'\d'), required=True)
+        self.Serializer = ExampleSerializer
+
+    def test_validation_success(self):
+        serializer = self.Serializer(data={'name': '2'})
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'name': '2'}
+        assert serializer.errors == {}