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: