From 6db9587c2810cc33e0ba37be815be212d212b65a Mon Sep 17 00:00:00 2001
From: konstin <konstin@mailbox.org>
Date: Wed, 5 Jun 2019 11:32:46 +0200
Subject: [PATCH] Use existing fields and methods before calling custom
 __getattr__

Previously, defining `__getattr__` would override all existing fields and methods. This changes it to behave like a `__getattr__` method defined in python, i.e. the custom method is only called if there isn't a field or method of that name
---
 CHANGELOG.md       |  1 +
 src/class/basic.rs | 37 +++++++++++++++++++++++++++++++------
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index beb3e35d02d..fe366d975dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 ### Added
 
  * `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499)
+ * Use existing fields and methods before calling custom __getattr__.
 
 
 ## [0.7.0] - 2018-05-26
diff --git a/src/class/basic.rs b/src/class/basic.rs
index ed71e2d9d73..a23c271801a 100644
--- a/src/class/basic.rs
+++ b/src/class/basic.rs
@@ -207,12 +207,37 @@ where
     T: for<'p> PyObjectGetAttrProtocol<'p>,
 {
     fn tp_getattro() -> Option<ffi::binaryfunc> {
-        py_binary_func!(
-            PyObjectGetAttrProtocol,
-            T::__getattr__,
-            T::Success,
-            PyObjectCallbackConverter
-        )
+        #[allow(unused_mut)]
+        unsafe extern "C" fn wrap<T>(
+            slf: *mut ffi::PyObject,
+            arg: *mut ffi::PyObject,
+        ) -> *mut ffi::PyObject
+        where
+            T: for<'p> PyObjectGetAttrProtocol<'p>,
+        {
+            let _pool = crate::GILPool::new();
+            let py = Python::assume_gil_acquired();
+
+            // Behave like python's __getattr__ (as opposed to __getattribute__) and check
+            // for existing fields and methods first
+            let existing = ffi::PyObject_GenericGetAttr(slf, arg);
+            if existing == std::ptr::null_mut() {
+                // PyObject_HasAttr also tries to get an object and clears the error if it fails
+                ffi::PyErr_Clear();
+            } else {
+                return existing;
+            }
+
+            let slf = py.mut_from_borrowed_ptr::<T>(slf);
+            let arg = py.from_borrowed_ptr::<crate::types::PyAny>(arg);
+
+            let result = match arg.extract() {
+                Ok(arg) => slf.__getattr__(arg).into(),
+                Err(e) => Err(e.into()),
+            };
+            crate::callback::cb_convert(PyObjectCallbackConverter, py, result)
+        }
+        Some(wrap::<T>)
     }
 }