Repository: joke2k/faker

Issue:
Raises RecursionError under certain conditions
* Faker version: 8.4.0
* OS: Arch Linux with kernel 5.12.6

Certain conditions can trigger a `RecursionError: maximum recursion depth exceeded`. I encountered this error in a django project, see below the steps to reproduce this error.

### Steps to reproduce

1. `pip install django faker`
1. `django-admin startproject mysite`
1. In the app mysite create a file called _test_example.py_, the source code is listed below
1. And in the app mysite create a file called _my_faker.py_, the source code is listed below

```python
# test_example.py
from django.test import TestCase
from faker import Faker

from .my_faker import MyFaker


class ExampleTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.faker_example = MyFaker()

    def setUp(self):
        self.fake = Faker()

        self.example = self.faker_example.example()

    def test_example(self):
        self.assertEqual(1, 1)
```

```python
# my_faker.py
from faker import Faker


class MyFaker:
    def __init__(self):
        self.fake = Faker()

    def example(self):
        pass
```


### Expected behavior

No RecursionError, it worked before. Last time about 2 month ago with `faker==5.6.5`, however this version does not work on updated systems as of the `AttributeError: 'PosixPath' object has no attribute 'startswith'`error but which was fixed recently.

### Actual behavior
```
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_example (mysite.test_example.ExampleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/username/Downloads/mysite/mysite/test_example.py", line 15, in setUp
    self.example = self.faker_example.example()
  File "/home/username/.virtualenvs/faker-test/lib/python3.9/site-packages/django/test/testcases.py", line 1124, in __get__
    data = deepcopy(self.data, memo)
  File "/usr/lib/python3.9/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.9/copy.py", line 270, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.9/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.9/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.9/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.9/copy.py", line 270, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.9/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.9/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.9/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.9/copy.py", line 271, in _reconstruct
    if hasattr(y, '__setstate__'):
  File "/home/username/.virtualenvs/faker-test/lib/python3.9/site-packages/faker/proxy.py", line 263, in __getattr__
    obj = getattr(self._proxy, name)
  File "/home/username/.virtualenvs/faker-test/lib/python3.9/site-packages/faker/proxy.py", line 263, in __getattr__
    obj = getattr(self._proxy, name)
  File "/home/username/.virtualenvs/faker-test/lib/python3.9/site-packages/faker/proxy.py", line 263, in __getattr__
    obj = getattr(self._proxy, name)
  [Previous line repeated 964 more times]
RecursionError: maximum recursion depth exceeded
```



Discussion:

Resolving patch:
diff --git a/faker/proxy.py b/faker/proxy.py
--- a/faker/proxy.py
+++ b/faker/proxy.py
@@ -1,3 +1,4 @@
+import copy
 import functools
 import random
 import re
@@ -100,7 +101,6 @@ def __getattr__(self, attr):
         :param attr: attribute name
         :return: the appropriate attribute
         """
-
         if len(self._factories) == 1:
             return getattr(self._factories[0], attr)
         elif attr in self.generator_attrs:
@@ -113,6 +113,20 @@ def __getattr__(self, attr):
             factory = self._select_factory(attr)
             return getattr(factory, attr)
 
+    def __deepcopy__(self, memodict={}):
+        cls = self.__class__
+        result = cls.__new__(cls)
+        result._locales = copy.deepcopy(self._locales)
+        result._factories = copy.deepcopy(self._factories)
+        result._factory_map = copy.deepcopy(self._factory_map)
+        result._weights = copy.deepcopy(self._weights)
+        result._unique_proxy = UniqueProxy(self)
+        result._unique_proxy._seen = {
+            k: {result._unique_proxy._sentinel}
+            for k in self._unique_proxy._seen.keys()
+        }
+        return result
+
     @property
     def unique(self):
         return self._unique_proxy


Test patch:
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
--- a/tests/test_proxy.py
+++ b/tests/test_proxy.py
@@ -1,3 +1,4 @@
+import copy
 import random
 
 from collections import OrderedDict
@@ -389,3 +390,9 @@ def test_dir_include_all_providers_attribute_in_list(self):
         expected = sorted(expected)
         attributes = dir(fake)
         assert attributes == expected
+
+    def test_copy(self):
+        fake = Faker("it_IT")
+        fake2 = copy.deepcopy(fake)
+        assert fake.locales == fake2.locales
+        assert fake.locales is not fake2.locales