Files
deb-python-falcon/falcon/util/misc.py
Kurt Griffiths c2cb7091e2 doc: Unindent additional Return lines to work around napolean bug (#892)
In a google-style docstring, this is problematic:

    Returns:
        str: Some really long description that requires more
            than one line

Since the additional text (i.e., "than one line") is indented,
napolean gets confused and incorrectly formats the description. The
fix is to align subsequent lines with the first:

    Returns:
        str: Some really long description that requires more
        than one line.

Along the way I also added missing return types as needed.
2016-09-08 13:55:47 -06:00

283 lines
8.4 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.
"""Miscellaneous utilities.
This module provides misc. utility functions for apps and the Falcon
framework itself. These functions are hoisted into the front-door
`falcon` module for convenience::
import falcon
now = falcon.http_now()
"""
import datetime
import functools
import inspect
import warnings
import six
from falcon import status_codes
__all__ = (
'deprecated',
'http_now',
'dt_to_http',
'http_date_to_dt',
'to_query_str',
'get_bound_method',
'get_http_status'
)
# PERF(kgriffs): Avoid superfluous namespace lookups
strptime = datetime.datetime.strptime
utcnow = datetime.datetime.utcnow
# NOTE(kgriffs): We don't want our deprecations to be ignored by default,
# so create our own type.
#
# TODO(kgriffs): Revisit this decision if users complain.
class DeprecatedWarning(UserWarning):
pass
def deprecated(instructions):
"""Flags a method as deprecated.
This function returns a decorator which can be used to mark deprecated
functions. Applying this decorator will result in a warning being
emitted when the function is used.
Args:
instructions (str): Specific guidance for the developer, e.g.:
'Please migrate to add_proxy(...)''
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
message = 'Call to deprecated function {0}(...). {1}'.format(
func.__name__,
instructions)
frame = inspect.currentframe().f_back
warnings.warn_explicit(message,
category=DeprecatedWarning,
filename=inspect.getfile(frame.f_code),
lineno=frame.f_lineno)
return func(*args, **kwargs)
return wrapper
return decorator
def http_now():
"""Returns the current UTC time as an IMF-fixdate.
Returns:
str: The current UTC time as an IMF-fixdate,
e.g., 'Tue, 15 Nov 1994 12:45:26 GMT'.
"""
return dt_to_http(utcnow())
def dt_to_http(dt):
"""Converts a ``datetime`` instance to an HTTP date string.
Args:
dt (datetime): A ``datetime`` instance to convert, assumed to be UTC.
Returns:
str: An RFC 1123 date string, e.g.: "Tue, 15 Nov 1994 12:45:26 GMT".
"""
# Tue, 15 Nov 1994 12:45:26 GMT
return dt.strftime('%a, %d %b %Y %H:%M:%S GMT')
def http_date_to_dt(http_date, obs_date=False):
"""Converts an HTTP date string to a datetime instance.
Args:
http_date (str): An RFC 1123 date string, e.g.:
"Tue, 15 Nov 1994 12:45:26 GMT".
obs_date (bool, optional): Support obs-date formats according to
RFC 7231, e.g.:
"Sunday, 06-Nov-94 08:49:37 GMT" (default ``False``).
Returns:
datetime: A UTC datetime instance corresponding to the given
HTTP date.
Raises:
ValueError: http_date doesn't match any of the available time formats
"""
if not obs_date:
# PERF(kgriffs): This violates DRY, but we do it anyway
# to avoid the overhead of setting up a tuple, looping
# over it, and setting up exception handling blocks each
# time around the loop, in the case that we don't actually
# need to check for multiple formats.
return strptime(http_date, '%a, %d %b %Y %H:%M:%S %Z')
time_formats = (
'%a, %d %b %Y %H:%M:%S %Z',
'%a, %d-%b-%Y %H:%M:%S %Z',
'%A, %d-%b-%y %H:%M:%S %Z',
'%a %b %d %H:%M:%S %Y',
)
# Loop through the formats and return the first that matches
for time_format in time_formats:
try:
return strptime(http_date, time_format)
except ValueError:
continue
# Did not match any formats
raise ValueError('time data %r does not match known formats' % http_date)
def to_query_str(params, comma_delimited_lists=True, prefix=True):
"""Converts a dictionary of parameters to a query string.
Args:
params (dict): A dictionary of parameters, where each key is
a parameter name, and each value is either a ``str`` or
something that can be converted into a ``str``, or a
list of such values. If a ``list``, the value will be
converted to a comma-delimited string of values
(e.g., 'thing=1,2,3').
comma_delimited_lists (bool): Set to ``False`` to encode list
values by specifying multiple instances of the parameter
(e.g., 'thing=1&thing=2&thing=3'). Otherwise, parameters
will be encoded as comma-separated values (e.g.,
'thing=1,2,3'). Defaults to ``True``.
prefix (bool): Set to ``False`` to exclude the '?' prefix
in the result string (default ``True``).
Returns:
str: A URI query string, including the '?' prefix (unless
`prefix` is ``False``), or an empty string if no params are
given (the ``dict`` is empty).
"""
if not params:
return ''
# PERF: This is faster than a list comprehension and join, mainly
# because it allows us to inline the value transform.
query_str = '?' if prefix else ''
for k, v in params.items():
if v is True:
v = 'true'
elif v is False:
v = 'false'
elif isinstance(v, list):
if comma_delimited_lists:
v = ','.join(map(str, v))
else:
for list_value in v:
if list_value is True:
list_value = 'true'
elif list_value is False:
list_value = 'false'
else:
list_value = str(list_value)
query_str += k + '=' + list_value + '&'
continue
else:
v = str(v)
query_str += k + '=' + v + '&'
return query_str[:-1]
def get_bound_method(obj, method_name):
"""Get a bound method of the given object by name.
Args:
obj: Object on which to look up the method.
method_name: Name of the method to retrieve.
Returns:
Bound method, or ``None`` if the method does not exist on
the object.
Raises:
AttributeError: The method exists, but it isn't
bound (most likely a class was passed, rather than
an instance of that class).
"""
method = getattr(obj, method_name, None)
if method is not None:
# NOTE(kgriffs): Ensure it is a bound method
if six.get_method_self(method) is None:
# NOTE(kgriffs): In Python 3 this code is unreachable
# because the above will raise AttributeError on its
# own.
msg = '{0} must be a bound method'.format(method)
raise AttributeError(msg)
return method
def get_http_status(status_code, default_reason='Unknown'):
"""Gets both the http status code and description from just a code
Args:
status_code: integer or string that can be converted to an integer
default_reason: default text to be appended to the status_code
if the lookup does not find a result
Returns:
str: status code e.g. "404 Not Found"
Raises:
ValueError: the value entered could not be converted to an integer
"""
# sanitize inputs
try:
code = float(status_code) # float can validate values like "401.1"
code = int(code) # converting to int removes the decimal places
if code < 100:
raise ValueError
except ValueError:
raise ValueError('get_http_status failed: "%s" is not a '
'valid status code', status_code)
# lookup the status code
try:
return getattr(status_codes, 'HTTP_' + str(code))
except AttributeError:
# not found
return str(code) + ' ' + default_reason