Repository: falconry/falcon
Pull request #821 (2016-06-09 22:24:31)

feat(Response): Add context property to the Response class
Add a context property to the Response class, to mirror the context property that is already available on the Request class.

codecov-io: ## [Current coverage][cc-pull] is **100%**
> Merging [#821][cc-pull] into [master][cc-base-branch] will not change coverage

```diff
@@             master       #821   diff @@
==========================================
  Files            29         29          
  Lines          1789       1793     +4   
  Methods           0          0          
  Messages          0          0          
  Branches        299        300     +1   
==========================================
+ Hits           1789       1793     +4   
  Misses            0          0          
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last updated by [cf3cb50...ff7f2ab][cc-compare]
[cc-base-branch]: https://codecov.io/gh/falconry/falcon/branch/master?src=pr
[cc-compare]: https://codecov.io/gh/falconry/falcon/compare/cf3cb5029a51d4b7c980c7851328a02046db9b3e...ff7f2abe01f2f11ff2bb28b17c116b07826a8a55
[cc-pull]: https://codecov.io/gh/falconry/falcon/pull/821?src=pr
fxfitz: LGTM

Patch:
diff --git a/falcon/request.py b/falcon/request.py
index 7359991..3596462 100644
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -126,14 +126,12 @@ class Request(object):
         context (dict): Dictionary to hold any data about the request which is
             specific to your app (e.g. session object). Falcon itself will
             not interact with this attribute after it has been initialized.
-        context_type (class): Class variable that determines the
-            factory or type to use for initializing the
-            `context` attribute. By default, the framework will
-            instantiate standard
-            ``dict`` objects. However, You may override this behavior
-            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.
+        context_type (class): Class variable that determines the factory or
+            type to use for initializing the `context` attribute. By default,
+            the framework will instantiate standard ``dict`` objects. However,
+            you may override this behavior 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
@@ -245,7 +243,7 @@ class Request(object):
         '_cached_access_route',
     )
 
-    # Allow child classes to override this
+    # Child classes may override this
     context_type = None
 
     def __init__(self, env, options=None):
diff --git a/falcon/response.py b/falcon/response.py
index 452432d..cf7d9af 100644
--- a/falcon/response.py
+++ b/falcon/response.py
@@ -83,12 +83,26 @@ class Response(object):
             blocks as byte strings. Falcon will use *wsgi.file_wrapper*, if
             provided by the WSGI server, in order to efficiently serve
             file-like objects.
-
         stream_len (int): Expected length of `stream`. If `stream` is set,
             but `stream_len` is not, Falcon will not supply a
             Content-Length header to the WSGI server. Consequently, the
             server may choose to use chunked encoding or one of the
             other strategies suggested by PEP-3333.
+        context (dict): Dictionary to hold any data about the response which is
+            specific to your app. Falcon itself will not interact with this
+            attribute after it has been initialized.
+        context_type (class): Class variable that determines the factory or
+            type to use for initializing the `context` attribute. By default,
+            the framework will instantiate standard ``dict`` objects. However,
+            you may override this behavior by creating a custom child class of
+            ``falcon.Response``, and then passing that new class to
+            `falcon.API()` by way of the latter's `response_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 Response instance. Therefore the first argument is
+                the Response instance itself (self).
     """
 
     __slots__ = (
@@ -98,9 +112,13 @@ class Response(object):
         '_cookies',
         'status',
         'stream',
-        'stream_len'
+        'stream_len',
+        'context',
     )
 
+    # Child classes may override this
+    context_type = None
+
     def __init__(self):
         self.status = '200 OK'
         self._headers = {}
@@ -114,6 +132,12 @@ class Response(object):
         self.stream = None
         self.stream_len = None
 
+        if self.context_type is None:
+            # PERF(kgriffs): The literal syntax is more efficient than dict().
+            self.context = {}
+        else:
+            self.context = self.context_type()
+
     def set_stream(self, stream, stream_len):
         """Convenience method for setting both `stream` and `stream_len`.
 


Test patch:
diff --git a/tests/test_response_context.py b/tests/test_response_context.py
new file mode 100644
index 0000000..abe9405
--- /dev/null
+++ b/tests/test_response_context.py
@@ -0,0 +1,39 @@
+from falcon import Response
+import falcon.testing as testing
+
+
+class TestRequestContext(testing.TestBase):
+
+    def test_default_response_context(self):
+        resp = Response()
+        self.assertIsInstance(resp.context, dict)
+
+    def test_custom_response_context(self):
+
+        class MyCustomContextType(object):
+            pass
+
+        class MyCustomResponse(Response):
+            context_type = MyCustomContextType
+
+        resp = MyCustomResponse()
+        self.assertIsInstance(resp.context, MyCustomContextType)
+
+    def test_custom_response_context_failure(self):
+
+        class MyCustomResponse(Response):
+            context_type = False
+
+        self.assertRaises(TypeError, MyCustomResponse)
+
+    def test_custom_response_context_factory(self):
+
+        def create_context(resp):
+            return {'resp': resp}
+
+        class MyCustomResponse(Response):
+            context_type = create_context
+
+        resp = MyCustomResponse()
+        self.assertIsInstance(resp.context, dict)
+        self.assertIs(resp.context['resp'], resp)