Skip to content

Commit

Permalink
feat(request,response): change context type to a subclass of dict (fa…
Browse files Browse the repository at this point in the history
  • Loading branch information
vytas7 authored Feb 7, 2019
1 parent 09e643c commit 14b482a
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 49 deletions.
32 changes: 32 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Breaking Changes
``False``.
- ``RequestOptions.auto_parse_qs_csv`` now defaults to ``False`` instead of
``True``.
- ``Request.context_type`` was changed from dict to a subclass of dict.
- ``Response.context_type`` was changed from dict to a subclass of dict.
- ``JSONHandler`` and ``HTTPError`` no longer use
`ujson` in lieu of the standard `json` library (when `ujson` is available in
the environment). Instead, ``JSONHandler`` can now be configured
Expand All @@ -31,6 +33,36 @@ New & Improved

- Added a new ``headers`` property to the ``Response`` class.
- Removed ``six`` as a dependency.
- ``Request.context_type`` now defaults to a bare class allowing to set
attributes on the request context object::

# Before
req.context['role'] = 'trial'
req.context['user'] = 'guest'

# Falcon 2.0
req.context.role = 'trial'
req.context.user = 'guest'

To ease the migration path, the previous behavior is supported by subclassing
dict, however, as of Falcon 2.0, the dict context interface is considered
deprecated, and may be removed in a future release. It is also noteworthy
that object attributes and dict items are not automagically linked in any
special way, and setting one does not affect the other.
- ``Response.context_type`` now defaults to a bare class allowing
to set attributes on the response context object::

# Before
resp.context['cache_strategy'] = 'lru'

# Falcon 2.0
resp.context.cache_strategy = 'lru'

To ease the migration path, the previous behavior is supported by subclassing
dict, however, as of Falcon 2.0, the dict context interface is considered
deprecated, and may be removed in a future release. It is also noteworthy
that object attributes and dict items are not automagically linked in any
special way, and setting one does not affect the other.
- ``JSONHandler`` can now be configured to use arbitrary
``dumps()`` and ``loads()`` functions. This enables support not only for
using any of a number of third-party JSON libraries, but also for
Expand Down
12 changes: 6 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ bodies.
'A valid JSON document is required.')
try:
req.context['doc'] = json.loads(body.decode('utf-8'))
req.context.doc = json.loads(body.decode('utf-8'))
except (ValueError, UnicodeDecodeError):
raise falcon.HTTPError(falcon.HTTP_753,
Expand All @@ -542,10 +542,10 @@ bodies.
'UTF-8.')
def process_response(self, req, resp, resource):
if 'result' not in resp.context:
if not hasattr(resp.context, 'result'):
return
resp.body = json.dumps(resp.context['result'])
resp.body = json.dumps(resp.context.result)
def max_body(limit):
Expand Down Expand Up @@ -593,7 +593,7 @@ bodies.
#
# NOTE: Starting with Falcon 1.3, you can simply
# use resp.media for this instead.
resp.context['result'] = result
resp.context.result = result
resp.set_header('Powered-By', 'Falcon')
resp.status = falcon.HTTP_200
Expand All @@ -603,8 +603,8 @@ bodies.
try:
# NOTE: Starting with Falcon 1.3, you can simply
# use req.media for this instead.
doc = req.context['doc']
except KeyError:
doc = req.context.doc
except AttributeError:
raise falcon.HTTPBadRequest(
'Missing thing',
'A thing must be submitted in the request body.')
Expand Down
6 changes: 3 additions & 3 deletions docs/api/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ requests.

.. Tip::
In order to pass data from a hook function to a resource function
use the ``req.context`` and ``resp.context`` dictionaries. These context
dictionaries are intended to hold request and response data specific to
your app as it passes through the framework.
use the ``req.context`` and ``resp.context`` objects. These context objects
are intended to hold request and response data specific to your app as it
passes through the framework.

.. automodule:: falcon
:members: before, after
Expand Down
6 changes: 3 additions & 3 deletions docs/api/middleware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ Falcon's middleware interface is defined as follows:

.. Tip::
In order to pass data from a middleware function to a resource function
use the ``req.context`` and ``resp.context`` dictionaries. These context
dictionaries are intended to hold request and response data specific to
your app as it passes through the framework.
use the ``req.context`` and ``resp.context`` objects. These context objects
are intended to hold request and response data specific to your app as it
passes through the framework.

Each component's *process_request*, *process_resource*, and
*process_response* methods are executed hierarchically, as a stack, following
Expand Down
41 changes: 41 additions & 0 deletions docs/changes/2.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Breaking Changes
instead of ``False``.
- :attr:`~.RequestOptions.auto_parse_qs_csv` now defaults to ``False``
instead of ``True``.
- Request :attr:`~.Request.context_type` was changed from dict to a subclass of
dict.
- Response :attr:`~.Response.context_type` was changed from dict to a subclass
of dict.
- :class:`~.media.JSONHandler` and :class:`~.HTTPError` no longer use
`ujson` in lieu of the standard `json` library (when `ujson` is available in
the environment). Instead, :class:`~.media.JSONHandler` can now be configured
Expand All @@ -31,6 +35,43 @@ New & Improved

- Added a new :attr:`~.Response.headers` property to the :class:`~.Response` class.
- Removed :py:mod:`six` from deps.
- Request :attr:`~.Request.context_type` now defaults to a bare class allowing
to set attributes on the request context object::

# Before
req.context['role'] = 'trial'
req.context['user'] = 'guest'

# Falcon 2.0
req.context.role = 'trial'
req.context.user = 'guest'

To ease the migration path, the previous behavior is supported by subclassing
dict, however, as of Falcon 2.0, the dict context interface is considered
deprecated, and may be removed in a future release. It is also noteworthy
that object attributes and dict items are not automagically linked in any
special way, and setting one does not affect the other.

Applications can work around this change by explicitly overriding
:attr:`~.Request.context_type` to dict.

- Response :attr:`~.Response.context_type` now defaults to a bare class allowing
to set attributes on the response context object::

# Before
resp.context['cache_strategy'] = 'lru'

# Falcon 2.0
resp.context.cache_strategy = 'lru'

To ease the migration path, the previous behavior is supported by subclassing
dict, however, as of Falcon 2.0, the dict context interface is considered
deprecated, and may be removed in a future release. It is also noteworthy
that object attributes and dict items are not automagically linked in any
special way, and setting one does not affect the other.

Applications can work around this change by explicitly overriding
:attr:`~.Response.context_type` to dict.
- :class:`~.media.JSONHandler` can now be configured to use arbitrary
``dumps()`` and ``loads()`` functions. This enables support not only for
using any of a number of third-party JSON libraries, but also for
Expand Down
72 changes: 68 additions & 4 deletions docs/user/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ implementation-first approach), while other developers prefer to use the API
spec itself as the contract, implementing and testing the API against that spec
(taking a design-first approach).

At the risk of erring on the side of flexiblity, Falcon does not provide API
At the risk of erring on the side of flexibility, Falcon does not provide API
spec support out of the box. However, there are several community projects
available in this vein. Our
`Add on Catalog <https://github.com/falconry/falcon/wiki/Add-on-Catalog>`_ lists
Expand Down Expand Up @@ -177,7 +177,7 @@ For more sophisticated use cases, have a look at Falcon add-ons from the
community, such as `falcon-cors <https://github.com/lwcolton/falcon-cors>`_, or
try one of the generic
`WSGI CORS libraries available on PyPI <https://pypi.python.org/pypi?%3Aaction=search&term=cors&submit=search>`_.
If you use an API gateway, you might also look into what CORS functionaly
If you use an API gateway, you might also look into what CORS functionality
it provides at that level.

How do I implement redirects within Falcon?
Expand Down Expand Up @@ -416,8 +416,26 @@ See also the `WSGI middleware example <https://www.python.org/dev/peps/pep-3333/
How can I pass data from a hook to a responder, and between hooks?
------------------------------------------------------------------
You can inject extra responder kwargs from a hook by adding them
to the *params* dict passed into the hook. You can also add custom data to
the ``req.context`` dict, as a way of passing contextual information around.
to the *params* dict passed into the hook. You can also set custom attributes
on the ``req.context`` object, as a way of passing contextual information
around:

.. code:: python
def authorize(req, resp, resource, params):
# Check authentication/authorization
# ...
req.context.role = 'root'
req.context.scopes = ('storage', 'things')
req.context.uid = 0
# ...
@falcon.before(authorize)
def on_post(self, req, resp):
pass
How can I write a custom handler for 404 and 500 pages in falcon?
------------------------------------------------------------------
Expand Down Expand Up @@ -584,6 +602,52 @@ In the meantime, the workaround is to percent-encode the forward slash. If you
don’t control the clients and can't enforce this, you can implement a Falcon
middleware component to rewrite the path before it is routed.

How do I adapt my code to default context type changes in Falcon 2.0?
---------------------------------------------------------------------

The default request/response context type has been changed from dict to a class
subclassing dict in Falcon 2.0. Instead of setting dictionary items, you can
now simply set attributes on the object:

.. code:: python
# Before Falcon 2.0
req.context['cache_backend'] = MyUltraFastCache.connect()
# Falcon 2.0
req.context.cache_backend = MyUltraFastCache.connect()
Since the default context type derives from dict, the existing code will work
unmodified with Falcon 2.0. Nevertheless, it is recommended to change usage
as outlined above since the ability to use context as dictionary may be
removed in a future release.

.. warning::
Context attributes are not linked to dict items in any special way,
i.e. setting an object attribute does not set the corresponding dict item,
nor vice versa.

Furthermore, if you need to mix-and-match both approaches under migration,
beware that setting attributes such as *items* or *values* would obviously
shadow the corresponding dict functions.

If an existing project is making extensive use of dictionary contexts, the type
can be explicitly overridden back to dict by employing custom request/response
types:

.. code:: python
class RequestWithDictContext(falcon.Request):
context_type = dict
class ResponseWithDictContext(falcon.Response):
context_type = dict
# ...
api = falcon.API(request_type=RequestWithDictContext,
response_type=ResponseWithDictContext)
Response Handling
~~~~~~~~~~~~~~~~~

Expand Down
12 changes: 6 additions & 6 deletions docs/user/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ parameters, handling errors, and working with request and response bodies.
'A valid JSON document is required.')
try:
req.context['doc'] = json.loads(body.decode('utf-8'))
req.context.doc = json.loads(body.decode('utf-8'))
except (ValueError, UnicodeDecodeError):
raise falcon.HTTPError(falcon.HTTP_753,
Expand All @@ -203,10 +203,10 @@ parameters, handling errors, and working with request and response bodies.
'UTF-8.')
def process_response(self, req, resp, resource):
if 'result' not in resp.context:
if not hasattr(resp.context, 'result'):
return
resp.body = json.dumps(resp.context['result'])
resp.body = json.dumps(resp.context.result)
def max_body(limit):
Expand Down Expand Up @@ -254,16 +254,16 @@ parameters, handling errors, and working with request and response bodies.
#
# NOTE: Starting with Falcon 1.3, you can simply
# use resp.media for this instead.
resp.context['result'] = result
resp.context.result = result
resp.set_header('Powered-By', 'Falcon')
resp.status = falcon.HTTP_200
@falcon.before(max_body(64 * 1024))
def on_post(self, req, resp, user_id):
try:
doc = req.context['doc']
except KeyError:
doc = req.context.doc
except AttributeError:
raise falcon.HTTPBadRequest(
'Missing thing',
'A thing must be submitted in the request body.')
Expand Down
4 changes: 2 additions & 2 deletions falcon/bench/queues/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

class RequestIDComponent(object):
def process_request(self, req, resp):
req.context['request_id'] = '<generate ID>'
req.context.request_id = '<generate ID>'

def process_response(self, req, resp, resource):
resp.set_header('X-Request-ID', req.context['request_id'])
resp.set_header('X-Request-ID', req.context.request_id)


class CannedResponseComponent(object):
Expand Down
28 changes: 20 additions & 8 deletions falcon/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,27 @@ class Request(object):
Attributes:
env (dict): Reference to the WSGI environ ``dict`` passed in from the
server. (See also PEP-3333.)
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 (object): Empty object to hold any data (in its attributes)
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.
Note:
**New in 2.0:** the default `context_type` (see below) was
changed from dict to a bare class, and the preferred way to
pass request-specific data is now to set attributes directly on
the `context` object, for example::
req.context.role = 'trial'
req.context.user = 'guest'
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.
the framework will instantiate bare objects (instances of the bare
RequestContext class). 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
Expand Down Expand Up @@ -412,7 +424,7 @@ class Request(object):
)

# Child classes may override this
context_type = dict
context_type = type('RequestContext', (dict,), {})

_wsgi_input_type_known = False
_always_wrap_wsgi_input = False
Expand Down
Loading

0 comments on commit 14b482a

Please sign in to comment.