Repository: falconry/falcon

Issue:
AttributeError when accessing request uri/url
Hi,

I'm getting this AttributeError when accessing Request uri or url using falcon 0.3.0

```
Traceback (most recent call last):
  File "falcon/api.py", line 154, in falcon.api.API.__call__ (falcon/api.c:1809)
  File "falcon/request.py", line 214, in falcon.request.Request.__init__ (falcon/request.c:1629)
  File "./dkuim/api.py", line 29, in context
    print("Create CONTEXT", req.url)
  File "falcon/request.py", line 399, in falcon.request.Request.uri (falcon/request.c:4006)
AttributeError: _cached_uri
```

I had a short look in request.py but yet no clue.



Discussion:
I wonder if this is a Cython bug... Have you tried uninstalling/reinstalling falcon to get a fresh compile? Also, you might try installing without Cython (or simply deleting the .so files).

Hi, don't think it's a Cython bug. I have a custom conext func (context_type) wich is called like a request class method. When accessing a property within this function from request (self) the property doesn't exist since context is instantiated before self._cached_uri = None ... hm.

Would it be possible to move context at the end of __init__? 

Ah, OK.

> Would it be possible to move context at the end of init?

That should be fine. If you want to fork the project and try it out to be sure it solves your problem, I'd be happy to review a PR for this change.


Resolving patch:
diff --git a/falcon/request.py b/falcon/request.py
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -95,6 +95,13 @@ class Request(object):
             by creating a custom child class of ``falcon.Request``, and
             then passing that new class to `falcon.API()` by way of the
             latter's `request_type` parameter.
+
+            Note:
+                When overriding `context_type` with a factory function (as
+                opposed to a class), the function is called like a method of
+                the current Request instance. Therefore the first argument is
+                the Request instance itself (self).
+
         uri (str): The fully-qualified URI for the request.
         url (str): alias for `uri`.
         relative_uri (str): The path + query string portion of the full URI.
@@ -209,14 +216,6 @@ def __init__(self, env, options=None):
         self.env = env
         self.options = options if options else RequestOptions()
 
-        if self.context_type is None:
-            # Literal syntax is more efficient than using dict()
-            self.context = {}
-        else:
-            # pylint will detect this as not-callable because it only sees the
-            # declaration of None, not whatever type a subclass may have set.
-            self.context = self.context_type()  # pylint: disable=not-callable
-
         self._wsgierrors = env['wsgi.errors']
         self.stream = env['wsgi.input']
         self.method = env['REQUEST_METHOD']
@@ -284,6 +283,14 @@ def __init__(self, env, options=None):
                 'application/x-www-form-urlencoded' in self.content_type):
             self._parse_form_urlencoded()
 
+        if self.context_type is None:
+            # Literal syntax is more efficient than using dict()
+            self.context = {}
+        else:
+            # pylint will detect this as not-callable because it only sees the
+            # declaration of None, not whatever type a subclass may have set.
+            self.context = self.context_type()  # pylint: disable=not-callable
+
     # ------------------------------------------------------------------------
     # Properties
     # ------------------------------------------------------------------------


Test patch:
diff --git a/tests/test_request_context.py b/tests/test_request_context.py
--- a/tests/test_request_context.py
+++ b/tests/test_request_context.py
@@ -31,3 +31,17 @@ class MyCustomRequest(Request):
 
         env = testing.create_environ()
         self.assertRaises(TypeError, MyCustomRequest, env)
+
+    def test_custom_request_context_request_access(self):
+
+        def create_context(req):
+            return {'uri': req.uri}
+
+        # Define a Request-alike with a custom context type
+        class MyCustomRequest(Request):
+            context_type = create_context
+
+        env = testing.create_environ()
+        req = MyCustomRequest(env)
+        self.assertIsInstance(req.context, dict)
+        self.assertEqual(req.context['uri'], req.uri)