diff --git a/doc/_static/css/falcon.css b/doc/_static/css/falcon.css
new file mode 100644
index 0000000..7f429e0
--- /dev/null
+++ b/doc/_static/css/falcon.css
@@ -0,0 +1,7 @@
+.property {
+ margin-left: 1em;
+}
+
+em {
+ margin-right: 0.15em;
+}
\ No newline at end of file
diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html
new file mode 100644
index 0000000..dd2b204
--- /dev/null
+++ b/doc/_templates/layout.html
@@ -0,0 +1,4 @@
+{% extends "!layout.html" %}
+{% block extrahead %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/doc/api/api.rst b/doc/api/api.rst
index 4b42a4b..81bc79e 100644
--- a/doc/api/api.rst
+++ b/doc/api/api.rst
@@ -3,7 +3,7 @@
API Class
=========
-Falcon's API class is a WSGI callable "application" that you can host with any
+Falcon's API class is a WSGI "application" that you can host with any
standard-compliant WSGI server.
.. code:: python
diff --git a/doc/conf.py b/doc/conf.py
index 4e6a171..e27e0be 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -101,9 +101,13 @@ pygments_style = 'sphinx'
# -- Options for HTML output ----------------------------------------------
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
+try:
+ import sphinx_rtd_theme
+
+ html_theme = "sphinx_rtd_theme"
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+except ImportError:
+ html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/index.rst b/doc/index.rst
index 1ed65d5..125eec4 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -8,9 +8,13 @@ Falcon: The Unladen WSGI Framework
Release v\ |version|. (:ref:`Installation `)
-Falcon is a minimalist WSGI library for building speedy web APIs and app backends.
+Falcon is a minimalist WSGI library for building speedy web APIs and app
+backends.
-When it comes to building HTTP APIs, other frameworks weigh you down with tons of dependencies and unnecessary abstractions. Falcon cuts to the chase with a clean design that embraces HTTP. We like to think of Falcon as the Dieter Rams of web frameworks; functional, simple, and elegant.
+When it comes to building HTTP APIs, other frameworks weigh you down with tons
+of dependencies and unnecessary abstractions. Falcon cuts to the chase with a
+clean design that `embraces` HTTP. We like to think of Falcon as the Dieter
+Rams of web frameworks; functional, simple, and elegant.
.. code:: python
diff --git a/doc/user/intro.rst b/doc/user/intro.rst
index e9ec8db..e5338c7 100644
--- a/doc/user/intro.rst
+++ b/doc/user/intro.rst
@@ -3,7 +3,7 @@
Introduction
============
-Falcon is a minimalist, high-performance web framework for building web services and app backends with Python. It's WSGI-based, and works great with Python 2.6, Python 2.7, Python 3.3, and PyPy, giving you a wide variety of deployment options.
+Falcon is a minimalist, high-performance web framework for building web services and app backends with Python. It's WSGI-based, and works great with Python 2.6, Python 2.7, Python 3.3, Python 3.4 and PyPy, giving you a wide variety of deployment options.
Yet Another Framework
diff --git a/falcon/api_helpers.py b/falcon/api_helpers.py
index a69234e..c99598e 100644
--- a/falcon/api_helpers.py
+++ b/falcon/api_helpers.py
@@ -97,10 +97,10 @@ def get_body(resp):
resp: Instance of falcon.Response
Returns:
- * If resp.body is not None, returns [resp.body], encoded as UTF-8 if
+ * If resp.body is not *None*, returns [resp.body], encoded as UTF-8 if
it is a Unicode string. Bytestrings are returned as-is.
- * If resp.data is not None, returns [resp.data]
- * If resp.stream is not None, returns resp.stream
+ * If resp.data is not *None*, returns [resp.data]
+ * If resp.stream is not *None*, returns resp.stream
* Otherwise, returns []
"""
diff --git a/falcon/exceptions.py b/falcon/exceptions.py
index c90e441..288a66a 100644
--- a/falcon/exceptions.py
+++ b/falcon/exceptions.py
@@ -46,7 +46,7 @@ class HTTPUnauthorized(HTTPError):
description (str): Human-friendly description of the error, along with
a helpful suggestion or two.
scheme (str): Authentication scheme to use as the value of the
- WWW-Authenticate header in the response (default None).
+ WWW-Authenticate header in the response (default *None*).
kwargs (optional): Same as for ``HTTPError``.
"""
@@ -92,6 +92,7 @@ class HTTPNotFound(HTTPError):
do not wish to disclose exactly why a request was refused.
"""
+
def __init__(self):
HTTPError.__init__(self, status.HTTP_404, None, None)
@@ -110,6 +111,7 @@ class HTTPMethodNotAllowed(HTTPError):
kwargs (optional): Same as for ``HTTPError``.
"""
+
def __init__(self, allowed_methods, **kwargs):
headers = kwargs.setdefault('headers', {})
headers['Allow'] = ', '.join(allowed_methods)
@@ -134,6 +136,7 @@ class HTTPNotAcceptable(HTTPError):
kwargs (optional): Same as for ``HTTPError``.
"""
+
def __init__(self, description, **kwargs):
HTTPError.__init__(self, status.HTTP_406, 'Media type not acceptable',
description, **kwargs)
@@ -242,7 +245,8 @@ class HTTPRangeNotSatisfiable(HTTPError):
resource_length: The maximum value for the last-byte-pos of a range
request. Used to set the Content-Range header.
media_type: Media type to use as the value of the Content-Type
- header, or None to use the default passed to the API initializer.
+ header, or *None* to use the default passed to the API
+ initializer.
"""
diff --git a/falcon/hooks.py b/falcon/hooks.py
index d9e8601..f67af04 100644
--- a/falcon/hooks.py
+++ b/falcon/hooks.py
@@ -22,19 +22,20 @@ def before(action):
"""Decorator to execute the given action function *before* the responder.
Args:
- action: A function with a similar signature to a resource responder
- method, taking (req, resp, params), where params includes values for
- URI template field names, if any. Hooks may also add pseudo-params
- of their own. For example:
+ action (callable): A function of the form ``func(req, resp, params)``,
+ where params is a dict of URI Template field names, if any,
+ that will be passed into the resource responder as *kwargs*.
- def do_something(req, resp, params):
- try:
- params['id'] = int(params['id'])
- except ValueError:
- raise falcon.HTTPBadRequest('Invalid ID',
- 'ID was not valid.')
+ Hooks may inject extra params as needed. For example::
- params['answer'] = 42
+ def do_something(req, resp, params):
+ try:
+ params['id'] = int(params['id'])
+ except ValueError:
+ raise falcon.HTTPBadRequest('Invalid ID',
+ 'ID was not valid.')
+
+ params['answer'] = 42
"""
@@ -87,8 +88,7 @@ def after(action):
"""Decorator to execute the given action function *after* the responder.
Args:
- action: A function with a similar signature to a resource responder
- method, taking (req, resp).
+ action (callable): A function of the form ``func(req, resp)``
"""
diff --git a/falcon/http_error.py b/falcon/http_error.py
index 7914048..0e1bd05 100644
--- a/falcon/http_error.py
+++ b/falcon/http_error.py
@@ -44,17 +44,17 @@ class HTTPError(Exception):
Args:
status (str): HTTP status code and text, such as "400 Bad Request"
- title (str): Human-friendly error title. Set to None if you wish Falcon
- to return an empty response body (all remaining args will
+ title (str): Human-friendly error title. Set to *None* if you wish
+ Falcon to return an empty response body (all remaining args will
be ignored except for headers.) Do this only when you don't
wish to disclose sensitive information about why a request was
refused, or if the status and headers are self-descriptive.
description (str): Human-friendly description of the error, along with
- a helpful suggestion or two (default None).
+ a helpful suggestion or two (default *None*).
headers (dict): Extra headers to return in the
- response to the client (default None).
+ response to the client (default *None*).
href (str): A URL someone can visit to find out more information
- (default None). Unicode characters are percent-encoded.
+ (default *None*). Unicode characters are percent-encoded.
href_text (str): If href is given, use this as the friendly
title/description for the link (defaults to "API documentation
for this error").
@@ -91,13 +91,9 @@ class HTTPError(Exception):
def json(self):
"""Returns a pretty JSON-encoded version of the exception
- Note:
- Excludes the HTTP status line, since the results of this call
- are meant to be returned in the body of an HTTP response.
-
Returns:
A JSON representation of the exception except the status line, or
- NONE if title was set to None.
+ NONE if title was set to *None*.
"""
diff --git a/falcon/request.py b/falcon/request.py
index ef86282..ac521be 100644
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -55,16 +55,80 @@ class InvalidParamValueError(HTTPBadRequest):
class Request(object):
- """Represents a client's HTTP request
+ """Represents a client's HTTP request.
+
+ Note:
+ Request is not meant to be instantiated directory by responders.
+
+ Args:
+ env (dict): A WSGI environment dict passed in from the server. See
+ also the PEP-3333 spec.
Attributes:
+ protocol (str): Either 'http' or 'https'.
method (str): HTTP method requested (e.g., GET, POST, etc.)
+ user_agent (str): Value of the User-Agent header, or *None* if the
+ header is missing.
+ app (str): Name of the WSGI app (if using WSGI's notion of virtual
+ hosting).
+ env (dict): Reference to the WSGI *environ* dict passed in from the
+ server. See also PEP-3333.
+ uri (str): The fully-qualified URI for the request.
+ url (str): alias for ``uri``.
+ relative_uri (str): The path + query string portion of the full URI.
path (str): Path portion of the request URL (not including query
string).
query_string (str): Query string portion of the request URL, without
the preceding '?' character.
- stream: Stream-like object for reading the body of the request, if any.
+ accept (str): Value of the Accept header, or '*/*' if the header is
+ missing.
+ auth (str): Value of the Authorization header, or *None* if the header
+ is missing.
+ client_accepts_json (bool): True if the Accept header includes JSON,
+ otherwise False.
+ client_accepts_xml (bool): True if the Accept header includes XML,
+ otherwise False.
+ content_type (str): Value of the Content-Type header, or *None* if
+ the header is missing.
+ content_length (int): Value of the Content-Length header converted
+ to an int, or *None* if the header is missing.
+ stream (io): File-like object for reading the body of the request, if
+ any.
+ date (datetime): Value of the Date header, converted to a
+ `datetime.datetime` instance. The header value is assumed to
+ conform to RFC 1123.
+ expect (str): Value of the Expect header, or *None* if the
+ header is missing.
+ range (tuple of int): A 2-member tuple parsed from the value of the
+ Range header.
+ The two members correspond to the first and last byte
+ positions of the requested resource, inclusive. Negative
+ indices indicate offset from the end of the resource,
+ where -1 is the last byte, -2 is the second-to-last byte,
+ and so forth.
+
+ Only continous ranges are supported (e.g., "bytes=0-0,-1" would
+ result in an HTTPBadRequest exception when the attribute is
+ accessed.)
+ if_match (str): Value of the If-Match header, or *None* if the
+ header is missing.
+ if_none_match (str): Value of the If-None-Match header, or *None*
+ if the header is missing.
+ if_modified_since (str): Value of the If-Modified-Since header, or
+ None if the header is missing.
+ if_unmodified_since (str): Value of the If-Unmodified-Sinc header,
+ or *None* if the header is missing.
+ if_range (str): Value of the If-Range header, or *None* if the
+ header is missing.
+
+ headers (dict): Raw HTTP headers from the request with
+ canonical dash-separated names. Parsing all the headers
+ to create this dict is done the first time this attribute
+ is accessed. This parsing can be costly, so unless you
+ need all the headers in this format, you should use the
+ ``get_header`` method or one of the convenience attributes
+ instead, to get a value for a specific header.
"""
__slots__ = (
@@ -81,15 +145,6 @@ class Request(object):
)
def __init__(self, env):
- """Initialize attributes based on a WSGI environment dict
-
- Note: Request is not meant to be instantiated directory by responders.
-
- Args:
- env (dict): A WSGI environment dict passed in from the server. See
- also the PEP-3333 spec.
-
- """
self.env = env
self._wsgierrors = env['wsgi.errors']
@@ -143,100 +198,20 @@ class Request(object):
# covered since the test that does so uses multiprocessing.
self.stream = helpers.Body(self.stream, self.content_length)
- # TODO(kgriffs): Use the nocover pragma only for the six.PY3 if..else
- def log_error(self, message): # pragma: no cover
- """Log an error to wsgi.error
-
- Prepends timestamp and request info to message, and writes the
- result out to the WSGI server's error stream (wsgi.error).
-
- Args:
- message (str): A string describing the problem. If a byte-string
- it is simply written out as-is. Unicode strings will be
- converted to UTF-8.
-
- """
-
- if self.query_string:
- query_string_formatted = '?' + self.query_string
- else:
- query_string_formatted = ''
-
- log_line = (
- DEFAULT_ERROR_LOG_FORMAT.
- format(datetime.now(), self.method, self.path,
- query_string_formatted)
- )
-
- if six.PY3:
- self._wsgierrors.write(log_line + message + '\n')
- else:
- if isinstance(message, unicode):
- message = message.encode('utf-8')
-
- self._wsgierrors.write(log_line.encode('utf-8'))
- self._wsgierrors.write(message + '\n')
+ # ------------------------------------------------------------------------
+ # Properties
+ # ------------------------------------------------------------------------
@property
def client_accepts_json(self):
- """Return True if the Accept header indicates JSON support."""
return self.client_accepts('application/json')
@property
def client_accepts_xml(self):
- """Return True if the Accept header indicates XML support."""
return self.client_accepts('application/xml')
- def client_accepts(self, media_type):
- """Returns the client's preferred media type.
-
- Args:
- media_type (str): Media type to check
-
- Returns:
- bool: True IFF the client has indicated in the Accept header that
- they accept at least one of the specified media types.
- """
-
- accept = self.accept
-
- # PERF(kgriffs): Usually the following will be true, so
- # try it first.
- if (accept == media_type) or (accept == '*/*'):
- return True
-
- # Fall back to full-blown parsing
- try:
- return mimeparse.quality(media_type, accept) != 0.0
- except ValueError:
- return False
-
- def client_prefers(self, media_types):
- """Returns the client's preferred media type given several choices.
-
- Args:
- media_types (iterable): One or more media types from which to
- choose the client's preferred type. This value MUST be an
- iterable collection of strings.
-
- Returns:
- str: The client's preferred media type, based on the Accept header,
- or None if the client does not accept any of the specified
- types.
- """
-
- try:
- # NOTE(kgriffs): best_match will return '' if no match is found
- preferred_type = mimeparse.best_match(media_types, self.accept)
- except ValueError:
- # Value for the accept header was not formatted correctly
- preferred_type = ''
-
- return (preferred_type if preferred_type else None)
-
@property
def accept(self):
- """Value of the Accept header, or */* if not found per RFC."""
accept = self._get_header_by_wsgi_name('HTTP_ACCEPT')
# NOTE(kgriffs): Per RFC, missing accept header is
@@ -244,26 +219,15 @@ class Request(object):
return '*/*' if accept is None else accept
@property
- def app(self):
- """Name of the WSGI app (if using WSGI's notion of virtual hosting)."""
- return self.env['SCRIPT_NAME']
+ def user_agent(self):
+ return self._get_header_by_wsgi_name('HTTP_USER_AGENT')
@property
def auth(self):
- """Value of the Authorization header, or None if not found."""
return self._get_header_by_wsgi_name('HTTP_AUTHORIZATION')
@property
def content_length(self):
- """Value of the Content-Length header
-
- Returns:
- int: Value converted to an int, or None if missing.
-
- Raises:
- HTTPBadRequest: The header had a value, but it wasn't
- formatted correctly or was a negative number.
- """
value = self._get_header_by_wsgi_name('HTTP_CONTENT_LENGTH')
if value:
try:
@@ -284,24 +248,10 @@ class Request(object):
@property
def content_type(self):
- """Value of the Content-Type header, or None if not found."""
return self._get_header_by_wsgi_name('HTTP_CONTENT_TYPE')
@property
def date(self):
- """Value of the Date header, converted to a datetime instance.
-
- Returns:
- datetime.datetime: An instance of datetime.datetime representing
- the value of the Date header, or None if the Date header is
- not present in the request.
-
- Raises:
- HTTPBadRequest: The date value could not be parsed, likely
- because it does not confrom to RFC 1123.
-
- """
-
http_date = self._get_header_by_wsgi_name('HTTP_DATE')
try:
return util.http_date_to_dt(http_date)
@@ -312,59 +262,30 @@ class Request(object):
@property
def expect(self):
- """Value of the Expect header, or None if missing."""
return self._get_header_by_wsgi_name('HTTP_EXPECT')
@property
def if_match(self):
- """Value of the If-Match header, or None if missing."""
return self._get_header_by_wsgi_name('HTTP_IF_MATCH')
@property
def if_none_match(self):
- """Value of the If-None-Match header, or None if missing."""
return self._get_header_by_wsgi_name('HTTP_IF_NONE_MATCH')
@property
def if_modified_since(self):
- """Value of the If-Modified-Since header, or None if missing."""
return self._get_header_by_wsgi_name('HTTP_IF_MODIFIED_SINCE')
@property
def if_unmodified_since(self):
- """Value of the If-Unmodified-Since header, or None if missing."""
return self._get_header_by_wsgi_name('HTTP_IF_UNMODIFIED_SINCE')
@property
def if_range(self):
- """Value of the If-Range header, or None if missing."""
return self._get_header_by_wsgi_name('HTTP_IF_RANGE')
- @property
- def protocol(self):
- """Will be either 'http' or 'https'."""
- return self.env['wsgi.url_scheme']
-
@property
def range(self):
- """A 2-member tuple representing the value of the Range header.
-
- The two members correspond to first and last byte positions of the
- requested resource, inclusive. Negative indices indicate offset
- from the end of the resource, where -1 is the last byte, -2 is the
- second-to-last byte, and so forth.
-
- Only continous ranges are supported (e.g., "bytes=0-0,-1" would
- result in an HTTPBadRequest exception.)
-
- Returns:
- int: Parse range value, or None if the header is not present.
-
- Raises:
- HTTPBadRequest: The header had a value, but it wasn't
- formatted correctly.
- """
-
value = self._get_header_by_wsgi_name('HTTP_RANGE')
if value:
@@ -394,9 +315,15 @@ class Request(object):
return None
@property
- def uri(self):
- """The fully-qualified URI for the request."""
+ def app(self):
+ return self.env['SCRIPT_NAME']
+ @property
+ def protocol(self):
+ return self.env['wsgi.url_scheme']
+
+ @property
+ def uri(self):
if self._cached_uri is None:
# PERF: For small numbers of items, '+' is faster
# than ''.join(...). Concatenation is also generally
@@ -414,12 +341,9 @@ class Request(object):
return self._cached_uri
url = uri
- """Alias for uri"""
@property
def relative_uri(self):
- """The path + query string portion of the full URI."""
-
if self._cached_relative_uri is None:
if self.query_string:
self._cached_relative_uri = (self.app + self.path + '?' +
@@ -429,25 +353,8 @@ class Request(object):
return self._cached_relative_uri
- @property
- def user_agent(self):
- """Value of the User-Agent string, or None if missing."""
- return self._get_header_by_wsgi_name('HTTP_USER_AGENT')
-
@property
def headers(self):
- """Get raw HTTP headers
-
- Build a temporary dictionary of dash-separated HTTP headers,
- which can be used as a whole, like, to perform an HTTP request.
-
- If you want to lookup a header, please use `get_header` instead.
-
- Returns:
- dict: A new dictionary of HTTP headers.
-
- """
-
# NOTE(kgriffs: First time here will cache the dict so all we
# have to do is clone it in the future.
if not self._cached_headers:
@@ -463,17 +370,69 @@ class Request(object):
return self._cached_headers.copy()
+ # ------------------------------------------------------------------------
+ # Methods
+ # ------------------------------------------------------------------------
+
+ def client_accepts(self, media_type):
+ """Determines whether or not the client accepts a given media type.
+
+ Args:
+ media_type (str): An Internet media type to check.
+
+ Returns:
+ bool: True if the client has indicated in the Accept header that
+ it accepts the specified media type. Otherwise, returns
+ False.
+ """
+
+ accept = self.accept
+
+ # PERF(kgriffs): Usually the following will be true, so
+ # try it first.
+ if (accept == media_type) or (accept == '*/*'):
+ return True
+
+ # Fall back to full-blown parsing
+ try:
+ return mimeparse.quality(media_type, accept) != 0.0
+ except ValueError:
+ return False
+
+ def client_prefers(self, media_types):
+ """Returns the client's preferred media type given several choices.
+
+ Args:
+ media_types (iterable of str): One or more Internet media types
+ from which to choose the client's preferred type. This value
+ **must** be an iterable collection of strings.
+
+ Returns:
+ str: The client's preferred media type, based on the Accept
+ header. Returns *None* if the client does not accept any
+ of the given types.
+ """
+
+ try:
+ # NOTE(kgriffs): best_match will return '' if no match is found
+ preferred_type = mimeparse.best_match(media_types, self.accept)
+ except ValueError:
+ # Value for the accept header was not formatted correctly
+ preferred_type = ''
+
+ return (preferred_type if preferred_type else None)
+
def get_header(self, name, required=False):
- """Return a header value as a string
+ """Return a header value as a string.
Args:
name (str): Header name, case-insensitive (e.g., 'Content-Type')
required (bool, optional): Set to True to raise HttpBadRequest
instead of returning gracefully when the header is not found
- (default False)
+ (default False).
Returns:
- str: The value of the specified header if it exists, or None if
+ str: The value of the specified header if it exists, or *None* if
the header is not found and is not required.
Raises:
@@ -496,7 +455,7 @@ class Request(object):
raise HTTPBadRequest('Missing header', description)
def get_param(self, name, required=False, store=None):
- """Return the value of a query string parameter as a string
+ """Return the value of a query string parameter as a string.
Args:
name (str): Parameter name, case-sensitive (e.g., 'sort')
@@ -507,7 +466,7 @@ class Request(object):
value of the param, but only if the param is found.
Returns:
- string: The value of the param as a string, or None if param is
+ string: The value of the param as a string, or *None* if param is
not found and is not required.
Raises:
@@ -534,13 +493,13 @@ class Request(object):
def get_param_as_int(self, name,
required=False, min=None, max=None, store=None):
- """Return the value of a query string parameter as an int
+ """Return the value of a query string parameter as an int.
Args:
name (str): Parameter name, case-sensitive (e.g., 'limit')
required (bool, optional): Set to True to raise HTTPBadRequest
instead of returning gracefully when the parameter is not
- found or is not an integer (default False)
+ found or is not an integer (default False).
min (int, optional): Set to the minimum value allowed for this
param. If the param is found and it is less than min, an
HTTPError is raised.
@@ -549,12 +508,12 @@ class Request(object):
max, an HTTPError is raised.
store (dict, optional): A dict-like object in which to place the
value of the param, but only if the param is found (default
- None)
+ *None*).
Returns:
int: The value of the param if it is found and can be converted to
- an integer. If the param is not found, returns None, unless
- required is True.
+ an integer. If the param is not found, returns *None*, unless
+ ``required`` is True.
Raises
HTTPBadRequest: The param was not found in the request, even though
@@ -601,10 +560,10 @@ class Request(object):
def get_param_as_bool(self, name, required=False, store=None):
"""Return the value of a query string parameter as a boolean
- The following bool-ish strings are supported:
+ The following bool-like strings are supported::
- True: ('true', 'True', 'yes')
- False: ('false', 'False', 'no')
+ TRUE_STRINGS = ('true', 'True', 'yes')
+ FALSE_STRINGS = ('false', 'False', 'no')
Args:
name (str): Parameter name, case-sensitive (e.g., 'limit')
@@ -613,12 +572,12 @@ class Request(object):
found or is not a recognized bool-ish string (default False).
store (dict, optional): A dict-like object in which to place the
value of the param, but only if the param is found (default
- None)
+ *None*).
Returns:
bool: The value of the param if it is found and can be converted
- to a boolean. If the param is not found, returns None unless
- required is True
+ to a boolean. If the param is not found, returns *None* unless
+ required is True.
Raises
HTTPBadRequest: The param was not found in the request, even though
@@ -654,7 +613,7 @@ class Request(object):
def get_param_as_list(self, name,
transform=None, required=False, store=None):
- """Return the value of a query string parameter as a list
+ """Return the value of a query string parameter as a list.
Note that list items must be comma-separated.
@@ -670,24 +629,24 @@ class Request(object):
found or is not an integer (default False)
store (dict, optional): A dict-like object in which to place the
value of the param, but only if the param is found (default
- None)
+ *None*).
Returns:
list: The value of the param if it is found. Otherwise, returns
- None unless required is True. for partial lists, None will be
- returned as a placeholder. For example:
+ *None* unless required is True. for partial lists, *None* will be
+ returned as a placeholder. For example::
things=1,,3
- would be returned as:
+ would be returned as::
['1', None, '3']
- while this:
+ while this::
things=,,,
- would just be retured as:
+ would just be retured as::
[None, None, None, None]
@@ -729,9 +688,43 @@ class Request(object):
raise HTTPBadRequest('Missing query parameter',
'The "' + name + '" query parameter is required.')
- # -------------------------------------------------------------------------
+ # TODO(kgriffs): Use the nocover pragma only for the six.PY3 if..else
+ def log_error(self, message): # pragma: no cover
+ """Write an error message to the server's log.
+
+ Prepends timestamp and request info to message, and writes the
+ result out to the WSGI server's error stream (`wsgi.error`).
+
+ Args:
+ message (str): A string describing the problem. If a byte-string
+ it is simply written out as-is. Unicode strings will be
+ converted to UTF-8.
+
+ """
+
+ if self.query_string:
+ query_string_formatted = '?' + self.query_string
+ else:
+ query_string_formatted = ''
+
+ log_line = (
+ DEFAULT_ERROR_LOG_FORMAT.
+ format(datetime.now(), self.method, self.path,
+ query_string_formatted)
+ )
+
+ if six.PY3:
+ self._wsgierrors.write(log_line + message + '\n')
+ else:
+ if isinstance(message, unicode):
+ message = message.encode('utf-8')
+
+ self._wsgierrors.write(log_line.encode('utf-8'))
+ self._wsgierrors.write(message + '\n')
+
+ # ------------------------------------------------------------------------
# Helpers
- # -------------------------------------------------------------------------
+ # ------------------------------------------------------------------------
def _get_header_by_wsgi_name(self, name):
"""Looks up a header, assuming name is already UPPERCASE_UNDERSCORE
@@ -741,8 +734,8 @@ class Request(object):
underscored
Returns:
- str: Value of the specified header, or None if the header was not
- found. Also returns None if the value of the header was blank.
+ str: Value of the specified header, or *None* if the header was not
+ found. Also returns *None* if the value of the header was blank.
"""
try: