Last active
September 15, 2016 21:47
-
-
Save jvanasco/a83be3b3b536bc964477c1dbc830674c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
After a bit of thought, I rewrote our response caching. | |
I stripped it into this gist in case anyone needs a starting point in the future. it's still rather specialized and hacky... but a bit cleaner than the earlier approach | |
The general overiew is this: | |
* We needed to cache responses AND perform some light operations. 99% of a rendered page is cachable as-is. | |
* We wanted to consolidate where the configuration is, perferalbly with the route_name declaration - but in this case it was better off with the caching code. | |
* The cached output should be rendered, which mades the `decorators` egress perfect... however some setup code happens in the __init__ phase of view-based classes and the 'decorators' ingress happens too soon. | |
Solution achieved via: | |
* This is implemented as a `decorators` argument to view_config. | |
* A local dict is used to map routes to page keys, and whether or not the matchdict or pagination should be used. | |
* Exceptions are cached in a modified format, unless the request fails a check for `request.is_cacheable` | |
* If a cached value is found, a request attribute `is_initialize_only` is set to True. this allows the __init__ phase of class based views to execute and raise `HandlerInitializeOnly`. | |
The `initialize_only` bit is the dirtiest part of this -- but it allows me to grab the decorator's ingress and still run some code for the views via a class based view's. | |
Perhaps someone will come up with a better method. | |
request properties | |
:is_in_generator: Boolean. If True, marks that we are in a cache generator. this will disable some renderings | |
:is_initialize_only: Boolean. If True, view based classes will only do their __init__, then raise this as "okay" | |
functions: | |
`deserialize_onpage_ids` | |
`serialize_onpage_ids` | |
""" | |
from pyramid.httpexceptions import HTTPException | |
from pyramid.response import Response | |
MAX_PAGINATED_CACHE = 5 | |
_cached_mapping = { | |
# route_name: (page_key, use_matchdict, maybe_paginated ), | |
"main": ("Main", False, False, ), | |
} | |
class Uncacheable(Exception): | |
"""raise if you shouldn't cache""" | |
pass | |
class HandlerInitializeOnly(Exception): | |
"""raise if you shouldn't cache""" | |
pass | |
def cacheable_route_decorator(wrapped): | |
""" | |
This is preferable to ``@pregenerated_cache`` | |
""" | |
def wrapper(context, request): | |
def generate_response(): | |
"""consolidated response generation""" | |
response = wrapped(context, request) | |
return response | |
try: | |
# register generator status; this effects the interpolation | |
request.is_in_generator = True | |
_route_name = request.matched_route.name | |
if _route_name not in _cached_mapping: | |
# easy out! | |
raise Uncacheable() | |
# grab our cache mapping | |
(page_key, use_matchdict, maybe_paginated) = _cached_mapping[_route_name] | |
# generate a local key! | |
key = page_key | |
if use_matchdict: | |
key += '|' + ','.join([v for k, v in sorted(request.matchdict.items())]) | |
if maybe_paginated: | |
# default to page1 | |
_page = request.matchdict.get('page', 1) | |
if _page > MAX_PAGINATED_CACHE: | |
# easy out! | |
raise Uncacheable() | |
# only update the key if we're not using the matchdict already | |
if not use_matchdict: | |
key += '|p.%s' % _page | |
_cached = cache_utils.CACHE_REGIONS['pages'].get(key) | |
if _cached == cache_utils.NO_VALUE: | |
try: | |
response = generate_response() | |
response_text = response.body | |
response_status = response.status_code | |
except HTTPException, e: | |
response_text = "EXCEPTION" | |
if "Location" in e.headers: | |
response_text += "\nLocation||%s" % e.headers['Location'] | |
response_status = e.status_code | |
response = e | |
except Exception as e: | |
log.debug("Exception in cacheable_route_decorator") | |
raise | |
if request.is_cacheable: | |
onpage_ids = serialize_onpage_ids(request, page_key) | |
_cached = (response_text, response_status, onpage_ids) | |
cache_utils.CACHE_REGIONS['pages'].set(key, _cached) | |
else: | |
(response_text, response_status, onpage_ids) = _cached | |
try: | |
# we have a _cached, but we need to know more about the object... | |
request.is_initialize_only = True | |
generate_response() | |
except HandlerInitializeOnly, e: | |
""" | |
this should `raise` on the `__init__` | |
which signifies that we are fully permissioned via the Handler | |
""" | |
pass | |
except HTTPException, e: | |
raise e | |
except Exception as e: | |
raise e | |
# analyze the response | |
if response_text[:9] == 'EXCEPTION': | |
response = HTTPException() | |
response.status_code = response_status | |
response_text = response_text.split("\n")[1:] | |
for h in response_text: | |
(k, v) = h.split("||") | |
response.headers[k] = v | |
raise response | |
else: | |
response = Response(response_text) | |
deserialize_onpage_ids(request, page_key, onpage_ids) | |
except Uncacheable, e: | |
response = generate_response() | |
return response | |
finally: | |
# deregister generator status | |
request.is_in_generator = False | |
if isinstance(response, HTTPException): | |
raise response | |
return response | |
return wrapper |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment