Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #408: Response.age is semantically a timedelta, not a datetime #414

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions werkzeug/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,49 @@ def http_date(timestamp=None):
return _dump_date(timestamp, ' ')


def parse_age(value=None):
"""Parses a base-10 integer count of seconds into a timedelta.

If parsing fails, the return value is `None`.

:param value: a string consisting of an integer represented in base-10
:return: a :class:`datetime.timedelta` object or `None`.
"""
if not value:
return None
try:
seconds = int(value)
except ValueError:
return None
if seconds < 0:
return None
try:
return timedelta(seconds=seconds)
except OverflowError:
return None


def dump_age(age=None):
"""Formats the duration as a base-10 integer.

:param age: should be an integer number of seconds,
a :class:`datetime.timedelta` object, or,
if the age is unknown, `None` (default).
"""
if age is None:
return
if isinstance(age, timedelta):
# do the equivalent of Python 2.7's timedelta.total_seconds(),
# but disregarding fractional seconds
age = age.seconds + (age.days * 24 * 3600)

age = int(age)

This comment was marked as off-topic.

if age < 0:
raise ValueError('age cannot be negative')

return str(age)


def is_resource_modified(environ, etag=None, data=None, last_modified=None):
"""Convenience method for conditional requests.

Expand Down
12 changes: 10 additions & 2 deletions werkzeug/testsuite/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import unittest
import pickle
from io import BytesIO
from datetime import datetime
from datetime import datetime, timedelta
from werkzeug._compat import iteritems

from werkzeug.testsuite import WerkzeugTestCase
Expand Down Expand Up @@ -507,11 +507,19 @@ def test_common_response_descriptors_mixin(self):
response.content_length = '42'
self.assert_equal(response.content_length, 42)

for attr in 'date', 'age', 'expires':
for attr in 'date', 'expires':
assert getattr(response, attr) is None
setattr(response, attr, now)
self.assert_equal(getattr(response, attr), now)

assert response.age is None
age_td = timedelta(days=1, minutes=3, seconds=5)
response.age = age_td
self.assert_equal(response.age, age_td)
age_int = 42
response.age = age_int
self.assert_equal(response.age, timedelta(seconds=age_int))

assert response.retry_after is None
response.retry_after = now
self.assert_equal(response.retry_after, now)
Expand Down
5 changes: 3 additions & 2 deletions werkzeug/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
parse_www_authenticate_header, remove_entity_headers, \
parse_options_header, dump_options_header, http_date, \
parse_if_range_header, parse_cookie, dump_cookie, \
parse_range_header, parse_content_range_header, dump_header
parse_range_header, parse_content_range_header, dump_header, \
parse_age, dump_age
from werkzeug.urls import url_decode, iri_to_uri, url_join
from werkzeug.formparser import FormDataParser, default_stream_factory
from werkzeug.utils import cached_property, environ_property, \
Expand Down Expand Up @@ -1645,7 +1646,7 @@ def on_update(d):
The Location response-header field is used to redirect the recipient
to a location other than the Request-URI for completion of the request
or identification of a new resource.''')
age = header_property('Age', None, parse_date, http_date, doc='''
age = header_property('Age', None, parse_age, dump_age, doc='''
The Age response-header field conveys the sender's estimate of the
amount of time since the response (or its revalidation) was
generated at the origin server.
Expand Down