Files
deb-python-falcon/falcon/response.py
kgriffs 6cf8c6e2b7 doc: Remove module docstrings
The docstrings were only adding clutter to the generated docs on RTD.
2014-04-04 15:40:34 -05:00

250 lines
7.8 KiB
Python

# Copyright 2013 by Rackspace Hosting, 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 six
from falcon.response_helpers import header_property, format_range
from falcon.util import dt_to_http, uri
class Response(object):
"""Represents an HTTP response to a client request
Attributes:
status: HTTP status code, such as "200 OK" (see also falcon.HTTP_*)
body: String representing response content. If Unicode, Falcon will
encode as UTF-8 in the response. If data is already a byte string,
use the data attribute instead (it's faster).
data: Byte string representing response content.
stream: Iterable stream-like object, representing response content.
stream_len: Expected length of stream (e.g., file size).
"""
__slots__ = (
'_body', # Stuff
'_body_encoded', # Stuff
'data',
'_headers',
'status',
'stream',
'stream_len'
)
def __init__(self):
"""Initialize response attributes to default values
Args:
wsgierrors: File-like stream for logging errors
"""
self.status = '200 OK'
self._headers = {}
self._body = None
self._body_encoded = None
self.data = None
self.stream = None
self.stream_len = None
def _get_body(self):
"""Returns the body as-is."""
return self._body
def _set_body(self, value):
"""Sets the body and clears the encoded cache."""
self._body = value
self._body_encoded = None
# NOTE(flaper87): Lets use a property
# for the body in case its content was
# encoded and then modified.
body = property(_get_body, _set_body)
@property
def body_encoded(self):
"""Encode the body and return it
This property will encode `_body` and
cache the result in the `_body_encoded`
attribute.
"""
# NOTE(flaper87): Notice this property
# is not thread-safe. If body is modified
# before this property returns, we might
# end up returning None.
body = self._body
if body and self._body_encoded is None:
# NOTE(flaper87): Assume it is an
# encoded str, then check and encode
# if it isn't.
self._body_encoded = body
if isinstance(body, six.text_type):
self._body_encoded = body.encode('utf-8')
return self._body_encoded
def set_header(self, name, value):
"""Set a header for this response to a given value.
Warning: Overwrites the existing value, if any.
Args:
name: Header name to set (case-insensitive). Must be of type str
or StringType, and only character values 0x00 through 0xFF
may be used on platforms that use wide characters.
value: Value for the header. Must be of type str or StringType, and
only character values 0x00 through 0xFF may be used on
platforms that use wide characters.
"""
# NOTE(kgriffs): normalize name by lowercasing it
self._headers[name.lower()] = value
def set_headers(self, headers):
"""Set several headers at once.
Warning: Overwrites existing values, if any.
Args:
headers: A dict containing header names and values to set, or
list of (name, value) tuples. A list can be read slightly
faster than a dict. Both names and values must be of type
str or StringType, and only character values 0x00 through
0xFF may be used on platforms that use wide characters.
Raises:
ValueError: headers was not a dictionary or list of tuples.
"""
if isinstance(headers, dict):
headers = headers.items()
# NOTE(kgriffs): We can't use dict.update because we have to
# normalize the header names.
_headers = self._headers
for name, value in headers:
_headers[name.lower()] = value
cache_control = header_property(
'Cache-Control',
"""Sets the Cache-Control header.
Used to set a list of cache directives to use as the value of the
Cache-Control header. The list will be joined with ", " to produce
the value for the header.
""",
lambda v: ', '.join(v))
content_location = header_property(
'Content-Location',
'Sets the Content-Location header.',
uri.encode)
content_range = header_property(
'Content-Range',
"""A tuple to use in constructing a value for the Content-Range header.
The tuple has the form (start, end, length), where start and end is
the inclusive byte range, and length is the total number of bytes, or
'*' if unknown.
Note: You only need to use the alternate form, "bytes */1234", for
responses that use the status "416 Range Not Satisfiable". In this
case, raising falcon.HTTPRangeNotSatisfiable will do the right
thing.
See also: http://goo.gl/Iglhp)
""",
format_range)
content_type = header_property(
'Content-Type',
'Sets the Content-Type header.')
etag = header_property(
'ETag',
'Sets the ETag header.')
last_modified = header_property(
'Last-Modified',
"""Sets the Last-Modified header. Set to a datetime (UTC) instance.
Note: Falcon will format the datetime as an HTTP date.
""",
dt_to_http)
location = header_property(
'Location',
'Sets the Location header.',
uri.encode)
retry_after = header_property(
'Retry-After',
"""Sets the Retry-After header.
The expected value is an integral number of seconds to use as the
value for the header. The HTTP-date syntax is not supported.
""",
str)
vary = header_property(
'Vary',
"""Value to use for the Vary header.
From Wikipedia:
"Tells downstream proxies how to match future request headers
to decide whether the cached response can be used rather than
requesting a fresh one from the origin server."
See also: http://goo.gl/NGHdL
Set this property to an iterable of header names. For a single
asterisk or field value, simply pass a single-element list or
tuple.
""",
lambda v: ', '.join(v))
def _wsgi_headers(self, media_type=None):
"""Convert headers into the format expected by WSGI servers.
Args:
media_type: Default media type to use for the Content-Type
header if the header was not set explicitly (default None).
"""
headers = self._headers
# PERF(kgriffs): Using "in" like this is faster than using
# dict.setdefault (tested on py27).
set_content_type = (media_type is not None and
'content-type' not in headers)
if set_content_type:
headers['content-type'] = media_type
if six.PY2: # pragma: no cover
# PERF(kgriffs): Don't create an extra list object if
# it isn't needed.
return headers.items()
return list(headers.items()) # pragma: no cover