Content Handlers ================ Content handlers are responsible for preparing request data and evaluating response data based on the content-type of the request and response. A content handler operates as follows: * Structured YAML data provided via the ``data`` attribute is converted to a string or bytes sequence and used as request body. * The response body (a string or sequence of bytes) is transformed into a content-type dependent structure and stored in an internal attribute named ``response_data`` that is: * used when evaluating the response body * used in ``$RESPONSE[]`` :ref:`substitutions ` By default, gabbi provides content handlers for JSON. In that content handler the ``data`` test key is converted from structured YAML into a JSON string. Response bodies are converted from a JSON string into a data structure in ``response_data`` that is used when evaluating ``response_json_paths`` entries in a test or doing JSONPath-based ``$RESPONSE[]`` substitutions. Further content handlers can be added as extensions. Test authors may need these extensions for their own suites, or enterprising developers may wish to create and distribute extensions for others to use. .. note:: One extension that is likely to be useful is a content handler that turns ``data`` into url-encoded form data suitable for POST and turns an HTML response into a DOM object. Extensions ---------- Content handlers are an evolution of the response handler concept in earlier versions gabbi. To preserve backwards compatibility with existing response handlers, old style response handlers are still allowed, but new handlers should implement the content handler interface (described below). .. highlight:: python Registering additional custom handlers is done by passing a subclass of :class:`~gabbi.handlers.base.ContentHandler` to :meth:`~gabbi.driver.build_tests`:: driver.build_tests(test_dir, loader, host=None, intercept=simple_wsgi.SimpleWsgi, content_handlers=[MyContentHandler]) If pytest is being used:: driver.py_test_generator(test_dir, intercept=simple_wsgi.SimpleWsgi, content_handlers=[MyContenHandler]) .. warning:: When there are multiple handlers listed that accept the same content-type, the one that is earliest in the list will be used. With ``gabbi-run``, custom handlers can be loaded via the ``--response-handler`` option -- see :meth:`~gabbi.runner.load_response_handlers` for details. .. note:: The use of the ``--response-handler`` argument is done to preserve backwards compatibility and avoid excessive arguments. Both types of handler may be passed to the argument. Implementation Details ~~~~~~~~~~~~~~~~~~~~~~ Creating a content handler requires subclassing :class:`~gabbi.handlers.base.ContentHandler` and implementing several methods. These methods are described below, but inspecting :class:`~gabbi.handlers.jsonhandler.JSONHandler` will be instructive in highlighting required arguments and techniques. To provide a ``response_`` response-body evaluator a subclass must define: * ``test_key_suffix``: This, along with the prefix ``response_``, forms the key used in the test structure. It is a class level string. * ``test_key_value``: The key's default value, either an empty list (``[]``) or empty dict (``{}``). It is a class level value. * ``action``: An instance method which tests the expected values against the HTTP response - it is invoked for each entry, with the parameters depending on the default value. The arguments to ``action`` are (in order): * ``self``: The current instance. * ``test``: The currently active ``HTTPTestCase`` * ``item``: The current entry if ``test_key_value`` is a list, otherwise the key half of the key/value pair at this entry. * ``value``: ``None`` if ``test_key_value`` is a list, otherwise the value half of the key/value pair at this entry. To translate request or response bodies to or from structured data a subclass must define an ``accepts`` method. This should return ``True`` if this class is willing to translate the provided content-type. During request processing it is given the value of the content-type header that will be sent in the request. During response processing it is given the value of the content-type header of the response. This makes it possible to handle different request and response bodies in the same handler, if desired. For example a handler might accept ``application/x-www-form-urlencoded`` and ``text/html``. If ``accepts`` is defined two additional static methods should be defined: * ``dumps``: Turn structured Python data from the ``data`` key in a test into a string or byte stream. * ``loads``: Turn a string or byte stream in a response into a Python data structure. Gabbi will put this data on the ``response_data`` attribute on the test, where it can be used in the evaluations described above (in the ``action`` method) or in ``$RESPONSE`` handling. An example usage here would be to turn HTML into a DOM. Finally if a ``replacer`` class method is defined, then when a ``$RESPONSE`` substitution is encountered, ``replacer`` will be passed the ``response_data`` of the prior test and the argument within the ``$RESPONSE``. Please see the `JSONHandler source`_ for additional detail. .. _JSONHandler source: https://github.com/cdent/gabbi/blob/master/gabbi/handlers/jsonhandler.py