"Fossies" - the Fresh Open Source Software Archive

Member "zaqar-10.0.0/zaqar/transport/wsgi/utils.py" (13 May 2020, 8333 Bytes) of package /linux/misc/openstack/zaqar-10.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "utils.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 9.0.0_vs_10.0.0.

    1 # Copyright (c) 2013 Rackspace, Inc.
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
    4 # use this file except in compliance with the License.  You may obtain a copy
    5 # of the License at
    6 #
    7 #    http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 # License for the specific language governing permissions and limitations under
   13 # the License.
   14 
   15 import falcon
   16 import jsonschema
   17 
   18 from oslo_log import log as logging
   19 import six
   20 
   21 from zaqar.i18n import _
   22 from zaqar.transport import utils
   23 from zaqar.transport.wsgi import errors
   24 
   25 JSONObject = dict
   26 """Represents a JSON object in Python."""
   27 
   28 JSONArray = list
   29 """Represents a JSON array in Python."""
   30 
   31 LOG = logging.getLogger(__name__)
   32 
   33 
   34 #
   35 # TODO(kgriffs): Create Falcon "before" hooks adapters for these functions
   36 #
   37 
   38 
   39 def deserialize(stream, len):
   40     """Deserializes JSON from a file-like stream.
   41 
   42     This function deserializes JSON from a stream, including
   43     translating read and parsing errors to HTTP error types.
   44 
   45     :param stream: file-like object from which to read an object or
   46         array of objects.
   47     :param len: number of bytes to read from stream
   48     :raises HTTPBadRequest: if the request is invalid
   49     :raises HTTPServiceUnavailable: if the http service is unavailable
   50     """
   51 
   52     if len is None:
   53         description = _(u'Request body can not be empty')
   54         raise errors.HTTPBadRequestBody(description)
   55 
   56     try:
   57         # TODO(kgriffs): read_json should stream the resulting list
   58         # of messages, returning a generator rather than buffering
   59         # everything in memory (bp/streaming-serialization).
   60         return utils.read_json(stream, len)
   61 
   62     except utils.MalformedJSON as ex:
   63         LOG.debug(ex)
   64         description = _(u'Request body could not be parsed.')
   65         raise errors.HTTPBadRequestBody(description)
   66 
   67     except utils.OverflowedJSONInteger as ex:
   68         LOG.debug(ex)
   69         description = _(u'JSON contains integer that is too large.')
   70         raise errors.HTTPBadRequestBody(description)
   71 
   72     except Exception:
   73         # Error while reading from the network/server
   74         description = _(u'Request body could not be read.')
   75         LOG.exception(description)
   76         raise errors.HTTPServiceUnavailable(description)
   77 
   78 
   79 def sanitize(document, spec=None, doctype=JSONObject):
   80     """Validates a document and drops undesired fields.
   81 
   82     :param document: A dict to verify according to `spec`.
   83     :param spec: (Default None) Iterable describing expected fields,
   84         yielding tuples with the form of:
   85 
   86             (field_name, value_type, default_value)
   87 
   88         Note that value_type may either be a Python type, or the
   89         special string '*' to accept any type. default_value is the
   90         default to give the field if it is missing, or None to require
   91         that the field be present.
   92 
   93         If spec is None, the incoming documents will not be validated.
   94     :param doctype: type of document to expect; must be either
   95         JSONObject or JSONArray.
   96     :raises HTTPBadRequestBody: if the request is invalid
   97     :returns: A sanitized, filtered version of the document. If the
   98         document is a list of objects, each object will be filtered
   99         and returned in a new list. If, on the other hand, the document
  100         is expected to contain a single object, that object's fields will
  101         be filtered and the resulting object will be returned.
  102     """
  103 
  104     if doctype is JSONObject:
  105         if not isinstance(document, JSONObject):
  106             raise errors.HTTPDocumentTypeNotSupported()
  107 
  108         return document if spec is None else filter(document, spec)
  109 
  110     if doctype is JSONArray:
  111         if not isinstance(document, JSONArray):
  112             raise errors.HTTPDocumentTypeNotSupported()
  113 
  114         if spec is None:
  115             return document
  116 
  117         return [filter(obj, spec) for obj in document]
  118 
  119     raise TypeError('doctype must be either a JSONObject or JSONArray')
  120 
  121 
  122 def filter(document, spec):
  123     """Validates and retrieves typed fields from a single document.
  124 
  125     Sanitizes a dict-like document by checking it against a
  126     list of field spec, and returning only those fields
  127     specified.
  128 
  129     :param document: dict-like object
  130     :param spec: iterable describing expected fields, yielding
  131         tuples with the form of: (field_name, value_type). Note that
  132         value_type may either be a Python type, or the special
  133         string '*' to accept any type.
  134     :raises HTTPBadRequest: if any field is missing or not an
  135         instance of the specified type
  136     :returns: A filtered dict containing only the fields
  137         listed in the spec
  138     """
  139 
  140     filtered = {}
  141     for name, value_type, default_value in spec:
  142         filtered[name] = get_checked_field(document, name,
  143                                            value_type, default_value)
  144 
  145     return filtered
  146 
  147 
  148 def get_checked_field(document, name, value_type, default_value):
  149     """Validates and retrieves a typed field from a document.
  150 
  151     This function attempts to look up doc[name], and raises
  152     appropriate HTTP errors if the field is missing or not an
  153     instance of the given type.
  154 
  155     :param document: dict-like object
  156     :param name: field name
  157     :param value_type: expected value type, or '*' to accept any type
  158     :param default_value: Default value to use if the value is missing,
  159         or None to make the value required.
  160     :raises HTTPBadRequest: if the field is missing or not an
  161         instance of value_type
  162     :returns: value obtained from doc[name]
  163     """
  164 
  165     try:
  166         value = document[name]
  167     except KeyError:
  168         if default_value is not None:
  169             value = default_value
  170         else:
  171             description = _(u'Missing "{name}" field.').format(name=name)
  172             raise errors.HTTPBadRequestBody(description)
  173 
  174     # PERF(kgriffs): We do our own little spec thing because it is way
  175     # faster than jsonschema.
  176     if value_type == '*' or isinstance(value, value_type):
  177         return value
  178 
  179     description = _(u'The value of the "{name}" field must be a {vtype}.')
  180     description = description.format(name=name, vtype=value_type.__name__)
  181     raise errors.HTTPBadRequestBody(description)
  182 
  183 
  184 def load(req):
  185     """Reads request body, raising an exception if it is not JSON.
  186 
  187     :param req: The request object to read from
  188     :type req: falcon.Request
  189     :return: a dictionary decoded from the JSON stream
  190     :rtype: dict
  191     :raises HTTPBadRequestBody: if JSON could not be parsed
  192     """
  193     try:
  194         return utils.read_json(req.stream, req.content_length)
  195     except (utils.MalformedJSON, utils.OverflowedJSONInteger):
  196         message = 'JSON could not be parsed.'
  197         LOG.exception(message)
  198         raise errors.HTTPBadRequestBody(message)
  199 
  200 
  201 # TODO(cpp-cabrera): generalize this
  202 def validate(validator, document):
  203     """Verifies a document against a schema.
  204 
  205     :param validator: a validator to use to check validity
  206     :type validator: jsonschema.Draft4Validator
  207     :param document: document to check
  208     :type document: dict
  209     :raises HTTPBadRequestBody: if the request is invalid
  210     """
  211     try:
  212         validator.validate(document)
  213     except jsonschema.ValidationError as ex:
  214         raise errors.HTTPBadRequestBody(
  215             '{0}: {1}'.format(ex.args, six.text_type(ex))
  216         )
  217 
  218 
  219 def message_url(message, base_path, claim_id=None):
  220     path = "/".join([base_path, 'messages', message['id']])
  221     if claim_id:
  222         path += falcon.to_query_str({'claim_id': claim_id})
  223     return path
  224 
  225 
  226 def format_message_v1(message, base_path, claim_id=None):
  227     return {
  228         'href': message_url(message, base_path, claim_id),
  229         'ttl': message['ttl'],
  230         'age': message['age'],
  231         'body': message['body'],
  232     }
  233 
  234 
  235 def format_message_v1_1(message, base_path, claim_id=None):
  236     url = message_url(message, base_path, claim_id)
  237     res = {
  238         'id': message['id'],
  239         'href': url,
  240         'ttl': message['ttl'],
  241         'age': message['age'],
  242         'body': message['body']
  243     }
  244     if message.get('checksum'):
  245         res['checksum'] = message.get('checksum')
  246     return res