Repository: falconry/falcon
Pull request #636 (2015-10-19 20:10:36)

fix(Request): context_type function raises AttributeError
Moving the instantiation of Request context to the end of Request.__init__, so that all Request properties are set up and accessible from within a context_type function.


Patch:
diff --git a/AUTHORS b/AUTHORS
index 8af5f77..374568a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -53,3 +53,4 @@ below in order of date of first contribution:
 * Adam Yala (adamyala)
 * Grigory Bakunov (bobuk)
 * Vincent Raiken (Freezerburn)
+* hooblei
diff --git a/falcon/request.py b/falcon/request.py
index fecb8d4..0a2eb8d 100644
--- 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 @@ class Request(object):
         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 @@ class Request(object):
                 '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
index 242d71d..d9013dd 100644
--- a/tests/test_request_context.py
+++ b/tests/test_request_context.py
@@ -31,3 +31,17 @@ class TestRequestContext(testing.TestBase):
 
         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)