Repository: frappe/frappe

Issue:
AttributeError in _dict
https://github.com/frappe/frappe/blob/5477429aa8dae3fd4c88120d4b51d0c7168d1f72/frappe/__init__.py#L40-L46

Line 45 throws an error when running `bench serve` or `bench migrate` with the VS Code debugger:

```
Exception has occurred: AttributeError
exception: no description
  File "/home/alyf/develop/apps/frappe/frappe/__init__.py", line 45, in __getattr__ (Current frame)
    raise AttributeError()
  File "/home/alyf/develop/apps/frappe/frappe/model/create_new.py", line 22, in get_new_doc
    doc = copy.deepcopy(frappe.local.new_doc_templates[doctype])
  File "/home/alyf/develop/apps/frappe/frappe/__init__.py", line 824, in new_doc
    return get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict)
  File "/home/alyf/develop/apps/frappe/frappe/model/document.py", line 142, in load_from_db
    single_doc = frappe.new_doc(self.doctype).as_dict()
  File "/home/alyf/develop/apps/frappe/frappe/model/document.py", line 110, in __init__
    self.load_from_db()
  File "/home/alyf/develop/apps/frappe/frappe/model/document.py", line 72, in get_doc
    return controller(*args, **kwargs)
  File "/home/alyf/develop/apps/frappe/frappe/__init__.py", line 890, in get_doc
    doc = frappe.model.document.get_doc(*args, **kwargs)
  File "/home/alyf/develop/apps/frappe/frappe/integrations/doctype/ldap_settings/ldap_settings.py", line 99, in get_ldap_client_settings
    ldap = frappe.get_doc("LDAP Settings")
  File "/home/alyf/develop/apps/frappe/frappe/www/login.py", line 64, in get_context
    ldap_settings = LDAPSettings.get_ldap_client_settings()
  File "/home/alyf/develop/apps/frappe/frappe/website/page_renderers/template_page.py", line 194, in run_pymodule_method
    return method(self.context)
  File "/home/alyf/develop/apps/frappe/frappe/website/page_renderers/template_page.py", line 135, in update_context
    data = self.run_pymodule_method('get_context')
  File "/home/alyf/develop/apps/frappe/frappe/website/page_renderers/template_page.py", line 72, in get_html
    self.update_context()
  File "/home/alyf/develop/apps/frappe/frappe/website/utils.py", line 445, in cache_html_decorator
    html = func(*args, **kwargs)
  File "/home/alyf/develop/apps/frappe/frappe/website/page_renderers/template_page.py", line 61, in render
    html = self.get_html()
  File "/home/alyf/develop/apps/frappe/frappe/website/serve.py", line 17, in get_response
    response = renderer_instance.render()
  File "/home/alyf/develop/apps/frappe/frappe/app.py", line 75, in application
    response = get_response()
  File "/home/alyf/develop/apps/frappe/frappe/middlewares.py", line 14, in __call__
    return super(StaticDataMiddleware, self).__call__(environ, start_response)
  File "/home/alyf/develop/apps/frappe/frappe/app.py", line 328, in serve
    threaded=not no_threading)
  File "/home/alyf/develop/apps/frappe/frappe/commands/utils.py", line 754, in serve
    frappe.app.serve(port=port, profile=profile, no_reload=no_reload, no_threading=no_threading, site=site, sites_path='.')
  File "/home/alyf/develop/apps/frappe/frappe/commands/__init__.py", line 26, in _func
    ret = f(frappe._dict(ctx.obj), *args, **kwargs)
  File "/home/alyf/develop/apps/frappe/frappe/utils/bench_helper.py", line 18, in main
    click.Group(commands=commands)(prog_name='bench')
  File "/home/alyf/develop/apps/frappe/frappe/utils/bench_helper.py", line 104, in <module>
    main()
```

This is caused by the method `copy.deepcopy()`, which tries to access the `__deepcopy__` attribute.

https://github.com/frappe/frappe/blob/5477429aa8dae3fd4c88120d4b51d0c7168d1f72/frappe/model/create_new.py#L22

### Proposed solution

a) Don't deepcopy `_dict`(?)
b) Don't throw an error when `__deepcopy__` is accessed:

```py
class _dict(dict):
    """dict like object that exposes keys as attributes"""
    def __getattr__(self, key):
        ret = self.get(key)
        if not ret and key.startswith("__"):
            return None
        return ret

    # ...
```


Discussion:
Same here. I just applied your b. option into my forked frappe if this helps to anyone because I am not sure if it could depend to an specific Python version:
```python
class _dict(dict):
	"""dict like object that exposes keys as attributes"""
	def __getattr__(self, key):
		ret = self.get(key)
		if not ret and key.startswith("__") and key != "__deepcopy__":
			raise AttributeError()
		return ret
```

Resolving patch:
diff --git a/frappe/__init__.py b/frappe/__init__.py
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -46,7 +46,8 @@ class _dict(dict):
 	"""dict like object that exposes keys as attributes"""
 	def __getattr__(self, key):
 		ret = self.get(key)
-		if not ret and key.startswith("__"):
+		# "__deepcopy__" exception added to fix frappe#14833 via DFP
+		if not ret and key.startswith("__") and key != "__deepcopy__":
 			raise AttributeError()
 		return ret
 	def __setattr__(self, key, value):


Test patch: