Source code for flask_resources.parsers.base

# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021 CERN.
# Copyright (C) 2020-2021 Northwestern University.
#
# Flask-Resources is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Request parser for extracting URL args, headers and view args.

The request parser uses a declarative way to extract and validate request
parameters. The parser can parse data in three different locations:

- ``args``: URL query string (i.e. ``request.args``)
- ``headers``: Request headers (i.e. ``request.headers``)
- ``view_args``: Request view args (i.e. ``request.view_args``)

The parser is not meant to parse the request body. For that you should use the
``RequestBodyParser``.

The request parser can accept both a schema or a dictionary. Using the schema
enables you to do further pre/post-processing of values, while the dict version
can be more compact.

Example with schema:

.. code-block:: python

    class MyHeaders(ma.Schema):
        content_type = ma.fields.String()

    parser = RequestParser(MyHeaders, location='headers')
    parser.parse()

Same example with dict:

.. code-block:: python

    parser = RequestParser({
        'content_type': ma.fields.String()
    }, location='headers')
    parser.parse()

**URL args parsing**

If you are parsing URL args, be aware that a query string can have repeated
variables (e.g. in ``?type=a&type=b`` the value ``type`` is repeated).

Thus if you build your own schema for URL args, you should inherit from
``MultiDictSchema``. If you don't have repeated keys you can use a normal
Marshmallow schema.

**Unknown values**

If you pass a dict for the schema, you can control what to do with unknown
values:

.. code-block:: python

    parser = RequestParser({
        'id': ma.fields.String()
    }, location='args', unknown=ma.RAISE)
    parser.parse()

If you build your own schema, the same can be achieved with by providing the
meta class:

.. code-block:: python

    class MyArgs(ma.Schema):
        id = ma.fields.String()

        class Meta:
            unknown = ma.INCLUDE
"""

import marshmallow as ma
from flask import request

from .schema import MultiDictSchema


[docs]class RequestParser: """Request parser.""" def __init__(self, schema_or_dict, location, unknown=ma.EXCLUDE): """Constructor. :param schema_or_dict: A marshmallow schema class or a mapping from keys to fields. :param location: Location where to load data from. Possible values: (``args``, ``headers``, or ``view_args``). :param unknown: Determines how to handle unknown values. Possible values: ``ma.EXCLUDE``, ``ma.INCLUDE``, ``ma.RAISE``. Only used if the schema is a dict. """ assert location in ["args", "headers", "view_args"] self._location = location self._unknown = unknown if isinstance(schema_or_dict, dict): self._schema = self.schema_from_dict(schema_or_dict) else: self._schema = schema_or_dict @property def location(self): """The request location for this request parser.""" return self._location @property def default_schema_cls(self): """Get the base schema class when dynamically creating the schema. By default, ``request.args`` is a MultiDict which a normal Marshmallow schema does not know how to handle, we therefore change the schema only for request args parsing. """ if self._location == "args": return MultiDictSchema else: return ma.Schema
[docs] def schema_from_dict(self, schema_dict): """Construct a schema from a dict.""" cls_ = self.default_schema_cls class BaseSchema(cls_): class Meta: unknown = self._unknown return BaseSchema.from_dict(schema_dict)
@property def schema(self): """Build the schema class.""" return self._schema()
[docs] def load_data(self): """Load data from request.""" if self._location == "args": if issubclass(self._schema, MultiDictSchema): return request.args else: return request.args.to_dict(flat=False) elif self._location == "headers": return { k.lower().replace("-", "_"): v for (k, v) in request.headers.items() } elif self._location == "view_args": return request.view_args raise RuntimeError(f"Unknown request location: {self._location}.")
[docs] def parse(self): """Parse the request data.""" return self.schema.load(self.load_data())