kgriffs baf3d2e372 Added some de-facto style guidelines to HACKING and fixed violations
This patch adds several guidelines:

* Global constants should be ALL_CAPS (cfg => CFG)
* Prefer single-quotes over double-quotes ("foo" => 'foo')
* Place a space before TODO in comments ("#TODO" => "# TODO")

Change-Id: Ib5b5c5916744856eca2ecaa37e949a3cdc4b3bd7
2013-06-17 09:58:30 -04:00

142 lines
5.0 KiB
Python

# Copyright (c) 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
#
# See the License for the specific language governing permissions and
# limitations under the License.
import marconi.openstack.common.log as logging
from marconi.transport import helpers
from marconi.transport.wsgi import exceptions
JSONObject = dict
"""Represents a JSON object in Python."""
JSONArray = list
"""Represents a JSON array in Phython."""
LOG = logging.getLogger(__name__)
# TODO(kgriffs): Consider moving this to Falcon and/or Oslo
def filter_stream(stream, len, spec, doctype=JSONObject):
"""Reads, deserializes, and validates a document from a stream.
:param stream: file-like object from which to read an object or
array of objects.
:param len: number of bytes to read from stream
:param spec: iterable describing expected fields, yielding
tuples with the form of: (field_name, value_type). Note that
value_type may either be a Python type, or the special
string '*' to accept any type.
:param doctype: type of document to expect; must be either
JSONObject or JSONArray.
:raises: HTTPBadRequest, HTTPServiceUnavailable
:returns: A sanitized, filtered version of the document read
from the stream. If the document contains a list of objects,
each object will be filtered and yielded in turn. If, on
the other hand, the document is expected to contain a
single object, that object will be filtered and returned as
a single-element iterable.
"""
try:
# TODO(kgriffs): read_json should stream the resulting list
# of messages, returning a generator rather than buffering
# everything in memory (bp/streaming-serialization).
document = helpers.read_json(stream, len)
except helpers.MalformedJSON as ex:
LOG.exception(ex)
description = _('Body could not be parsed.')
raise exceptions.HTTPBadRequestBody(description)
except Exception as ex:
# Error while reading from the network/server
LOG.exception(ex)
description = _('Request body could not be read.')
raise exceptions.HTTPServiceUnavailable(description)
if doctype is JSONObject:
if not isinstance(document, JSONObject):
raise exceptions.HTTPDocumentTypeNotSupported()
return (filter(document, spec),)
if doctype is JSONArray:
if not isinstance(document, JSONArray):
raise exceptions.HTTPDocumentTypeNotSupported()
# Return as a generator since we plan on doing a
# streaming JSON deserializer (see above.git )
return (filter(obj, spec) for obj in document)
raise ValueError('doctype not in (JSONObject, JSONArray)')
# TODO(kgriffs): Consider moving this to Falcon and/or Oslo
def filter(document, spec):
"""Validates and retrieves typed fields from a single document.
Sanitizes a dict-like document by checking it against a
list of field spec, and returning only those fields
specified.
:param document: dict-like object
:param spec: iterable describing expected fields, yielding
tuples with the form of: (field_name, value_type). Note that
value_type may either be a Python type, or the special
string '*' to accept any type.
:raises: HTTPBadRequest if any field is missing or not an
instance of the specified type
:returns: A filtered dict containing only the fields
listed in the spec
"""
filtered = {}
for name, value_type in spec:
filtered[name] = get_checked_field(document, name, value_type)
return filtered
# TODO(kgriffs): Consider moving this to Falcon and/or Oslo
def get_checked_field(document, name, value_type):
"""Validates and retrieves a typed field from a document.
This function attempts to look up doc[name], and raises
appropriate HTTP errors if the field is missing or not an
instance of the given type.
:param document: dict-like object
:param name: field name
:param value_type: expected value type, or '*' to accept any type
:raises: HTTPBadRequest if the field is missing or not an
instance of value_type
:returns: value obtained from doc[name]
"""
try:
value = document[name]
except KeyError:
description = _('Missing "{name}" field.').format(name=name)
raise exceptions.HTTPBadRequestBody(description)
if value_type == '*' or isinstance(value, value_type):
return value
description = _('The value of the "{name}" field must be a {vtype}.')
description = description.format(name=name, vtype=value_type.__name__)
raise exceptions.HTTPBadRequestBody(description)