Skip to content

Commit

Permalink
Refactor Dynamic.render for method length
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Jul 20, 2016
1 parent 77e366a commit 5ab64d7
Showing 1 changed file with 54 additions and 35 deletions.
89 changes: 54 additions & 35 deletions aspen/http/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ def render(self, context):

class Dynamic(Simplate):
"""Model a dynamic HTTP resource using simplates.
Make .request_processor available as it has been historically.
"""

def __init__(self, request_processor, fs, raw, fs_media_type):
Expand All @@ -41,6 +39,7 @@ def __init__(self, request_processor, fs, raw, fs_media_type):
default_media_type = fs_media_type or request_processor.media_type_default
super(Dynamic, self).__init__(defaults, fs, raw, default_media_type)


def render(self, state):
"""Render the resource with the given state as context, return Output.
Expand All @@ -51,47 +50,67 @@ def render(self, state):
The two sources for what the client wants are the extension in the
request URL, and the Accept header. If the former fails to match we
raise NotFound (404), if the latter fails we raise NegotiationFailure
(406).
(406)--unless there's only one available type and no extension in the
URL, in which case we ignore the `Accept` header (the spec allows for
this: <https://tools.ietf.org/html/rfc7231#section-5.3.2>) and render
whatever we have available.
Note that we don't always respect the `Accept` header (the spec says
we can ignore it: <https://tools.ietf.org/html/rfc7231#section-5.3.2>).
"""
available = self.available_types
choice = available[0] # default to first available type
# When there is an extension in the URI path, the dispatcher gives us the
content_type = available[0] # default to first available type
# When there is an extension in the URL path, the dispatcher gives us the
# corresponding media type (or an empty string if unknown)
accept = dispatch_accept = state['dispatch_result'].extra.get('accept')

if dispatch_accept is not None:
# If the extension is unknown, raise NotFound
if dispatch_accept == '':
raise NotFound()
# Accept `media/type` for `media/x-type`
i = dispatch_accept.find('/x-')
if i > 0:
accept += ',' + dispatch_accept[:i+1] + dispatch_accept[i+3:]
# Accept custom JSON media type
if dispatch_accept == 'application/json':
accept += ',' + self.request_processor.media_type_json
elif len(available) == 1:
# If there's only one available type and no extension in the path,
# then we ignore the Accept header
accept = ''
else:
dispatch_accept = state['dispatch_result'].extra.get('accept')
URL_has_extension = dispatch_accept is not None

if URL_has_extension: # use the URL extension
accept = self.use_accept_from_dispatcher(dispatch_accept)
elif len(available) > 1: # use the Accept header
accept = state.get('accept_header')
else: # render whatever we have
accept = ''

if accept:
try:
best_match = mimeparse.best_match(available, accept)
except ValueError:
# Unparseable accept header
best_match = None
if best_match:
choice = best_match
elif best_match == '':
if dispatch_accept is not None:
content_type = self.negotiate_content_type(available, accept) or content_type
except NegotiationFailure:
if URL_has_extension:
# e.g. client requested `/foo.json` but `/foo.spt` has no JSON page
raise NotFound()
raise NegotiationFailure(accept, available)
raise

return super(Dynamic, self).render(content_type, state)


return super(Dynamic, self).render(choice, state)
def use_accept_from_dispatcher(self, dispatch_accept):
"""Given an Accept header from the dispatcher, extend and return it.
"""
accept = dispatch_accept

# If the extension is unknown, raise NotFound
if dispatch_accept == '':
raise NotFound()
# Accept `media/type` for `media/x-type`
i = dispatch_accept.find('/x-')
if i > 0:
accept += ',' + dispatch_accept[:i+1] + dispatch_accept[i+3:]
# Accept custom JSON media type
if dispatch_accept == 'application/json':
accept += ',' + self.request_processor.media_type_json

return accept


def negotiate_content_type(self, available, accept):
"""Given information about available and acceptable types, return a type or None.
"""
content_type = None
try:
best_match = mimeparse.best_match(available, accept)
except ValueError: # unparseable Accept header
best_match = None
if best_match:
content_type = best_match
elif best_match == '':
raise NegotiationFailure(accept, available)
return content_type

0 comments on commit 5ab64d7

Please sign in to comment.