Skip to content

Commit

Permalink
Add ExpiresAfter heuristic. Fixes #48
Browse files Browse the repository at this point in the history
This also fixed some tests that were broken.

Here is an example ExpiresAfter:

  sess = CacheControl(
      requests.Session(),
      heuristic=ExpiresAfter(days=1)
  )

The arguments to the constructor are the same as a python
datetime.timedelta.
  • Loading branch information
ionrock committed Dec 21, 2014
1 parent 03ee81a commit eb28322
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 17 deletions.
36 changes: 33 additions & 3 deletions cachecontrol/heuristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
from datetime import datetime, timedelta


def expire_after(delta, date=None):
date = date or datetime.now()
return date + delta


def datetime_to_header(dt):
return formatdate(calendar.timegm(dt.timetuple()))


class BaseHeuristic(object):

def warning(self):
Expand Down Expand Up @@ -39,7 +48,28 @@ def update_headers(self, response):

if 'expires' not in response.headers:
date = parsedate(response.headers['date'])
expires = datetime(*date[:6]) + timedelta(days=1)
headers['expires'] = formatdate(calendar.timegm(expires.timetuple()))

expires = expire_after(timedelta(days=1),
date=datetime(*date[:6]))
headers['expires'] = datetime_to_header(expires)
headers['cache-control'] = 'public'
return headers


class ExpiresAfter(BaseHeuristic):
"""
Cache **all** requests for a defined time period.
"""

def __init__(self, **kw):
self.delta = timedelta(**kw)

def update_headers(self, response):
expires = expire_after(self.delta)
return {
'expires': datetime_to_header(expires),
'cache-control': 'public',
}

def warning(self):
tmpl = '110 - Automatically cached for %s. Response might be stale'
return tmpl % self.delta
21 changes: 12 additions & 9 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,11 @@ def optional_cacheable_request(self, env, start_response):
cached. Yet, we might still choose to cache it via a
heuristic."""

headers = {
'content-encoding': 'gzip',
'transfer-encoding': 'chunked',
'server': 'nginx/1.2.6 (Ubuntu)',
'last-modified': 'Mon, 21 Jul 2014 17:45:39 GMT',
'connection': 'keep-alive',
'date': 'Wed, 23 Jul 2014 04:57:40 GMT',
'content-type': 'text/html'
}
headers = [
('server', 'nginx/1.2.6 (Ubuntu)'),
('last-modified', 'Mon, 21 Jul 2014 17:45:39 GMT'),
('content-type', 'text/html'),
]

start_response('200 OK', headers)
return [pformat(env).encode("utf8")]
Expand Down Expand Up @@ -68,6 +64,13 @@ def etag(self, env, start_response):
start_response('200 OK', headers)
return [pformat(env).encode("utf8")]

def no_cache(self, env, start_response):
headers = [
('Cache-Control', 'no-cache'),
]
start_response('200 OK', headers)
return [pformat(env).encode("utf8")]

def __call__(self, env, start_response):
func = self.dispatch(env)

Expand Down
38 changes: 33 additions & 5 deletions tests/test_expires_heuristics.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from mock import Mock

from requests import Session
from requests import Session, get
from cachecontrol import CacheControl
from cachecontrol.heuristics import OneDayCache
from cachecontrol.heuristics import OneDayCache, ExpiresAfter

from pprint import pprint


class TestUseExpiresHeuristic(object):
Expand All @@ -22,11 +24,37 @@ def setup(self):
)

def test_cache_for_one_day(self, url):
hurl = url + '/optional_cacheable_request'
r = self.sess.get(hurl)
the_url = url + 'optional_cacheable_request'
r = self.sess.get(the_url)

assert 'expires' in r.headers
assert 'warning' in r.headers

pprint(dict(r.headers))

r = self.sess.get(the_url)
pprint(dict(r.headers))
assert r.from_cache


class TestExpiresAfter(object):

def setup(self):
self.sess = Session()
self.cache_sess = CacheControl(
self.sess, heuristic=ExpiresAfter(days=1)
)

def test_expires_after_one_day(self, url):
the_url = url + 'no_cache'
resp = get(the_url)
assert resp.headers['cache-control'] == 'no-cache'

r = self.sess.get(the_url)

assert 'expires' in r.headers
assert 'warning' in r.headers
assert r.headers['cache-control'] == 'public'

r = self.sess.get(hurl)
r = self.sess.get(the_url)
assert r.from_cache

0 comments on commit eb28322

Please sign in to comment.