Skip to content

Commit

Permalink
Merge branch 'master' into 1265_set_cookie
Browse files Browse the repository at this point in the history
  • Loading branch information
kgriffs committed Feb 12, 2019
2 parents c949c76 + 74bd2ea commit 5bd84a7
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 24 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ matrix:
env: TOXENV=py36_smoke_cython
- python: 2.7
env: TOXENV=docs
- python: 3.6
env: TOXENV=hug
# TODO(kgriffs): Re-enable once we have mitigated the stream_len
# breaking change in hug.
# - python: 3.6
# env: TOXENV=hug
- python: 3.6
env: TOXENV=check_vendored

Expand Down
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Breaking Changes
``False``.
- ``RequestOptions.auto_parse_qs_csv`` now defaults to ``False`` instead of
``True``.
- The deprecated ``stream_len`` property was removed from the ``Response``
class. Please use ``Response.set_stream()`` or ``Response.content_length``
instead.
- ``Request.context_type`` was changed from dict to a subclass of dict.
- ``Response.context_type`` was changed from dict to a subclass of dict.
- ``JSONHandler`` and ``HTTPError`` no longer use
Expand Down
3 changes: 3 additions & 0 deletions docs/changes/2.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Breaking Changes
instead of ``False``.
- :attr:`~.RequestOptions.auto_parse_qs_csv` now defaults to ``False``
instead of ``True``.
- The deprecated ``stream_len`` property was removed from the
:class:`~.Response` class. Please use :meth:`~.Response.set_stream` or
:attr:`~.Response.content_length` instead.
- Request :attr:`~.Request.context_type` was changed from dict to a subclass of
dict.
- Response :attr:`~.Response.context_type` was changed from dict to a subclass
Expand Down
24 changes: 13 additions & 11 deletions falcon/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-

# Copyright 2013 by Rackspace Hosting, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -304,10 +306,11 @@ def __call__(self, env, start_response): # noqa: C901

else:
body, length = self._get_body(resp, env.get('wsgi.file_wrapper'))
if resp.content_length is None and length is not None:

# PERF(kgriffs): Böse mußt sein. Operate directly on resp._headers
# to reduce overhead since this is a hot/critical code path.
if 'content-length' not in resp._headers and length is not None:
resp._headers['content-length'] = str(length)
elif resp.content_length is not None:
resp._headers['content-length'] = str(resp.content_length)

headers = resp._wsgi_headers(media_type)

Expand Down Expand Up @@ -745,13 +748,15 @@ def _get_body(self, resp, wsgi_file_wrapper=None):
The length is returned as ``None`` when unknown. The
iterable is determined as follows:
* If resp.body is not ``None``, returns [resp.body],
* If resp.body is not ``None``, returns
([resp.body], len(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.data is not ``None``, returns ([resp.data], len(resp.data))
* If resp.stream is not ``None``, returns resp.stream
iterable using wsgi.file_wrapper, if possible.
* Otherwise, returns []
iterable using wsgi.file_wrapper, if necessary:
(closeable_iterator, None)
* Otherwise, returns ([], 0)
"""
body = resp.body
Expand Down Expand Up @@ -782,9 +787,6 @@ def _get_body(self, resp, wsgi_file_wrapper=None):
else:
iterable = stream

# NOTE(pshello): resp.stream_len is deprecated in favor of
# resp.content_length. The caller of _get_body should give
# preference to resp.content_length if it has been set.
return iterable, resp.stream_len
return iterable, None

return [], 0
31 changes: 26 additions & 5 deletions falcon/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@

GMT_TIMEZONE = TimezoneGMT()

_STREAM_LEN_REMOVED_MSG = (
'The deprecated stream_len property was removed in Falcon 2.0. '
'Please use Response.set_stream() or Response.content_length instead.'
)


class Response(object):
"""Represents an HTTP response to a client request.
Expand Down Expand Up @@ -107,7 +112,9 @@ class Response(object):
resource cleanup, it can implement a close() method to do so.
The close() method will be called upon completion of the request.
stream_len (int): Deprecated alias for :py:attr:`content_length`.
context (dict): Dictionary to hold any data about the response which is
specific to your app. Falcon itself will not interact with this
attribute after it has been initialized.
context (object): Empty object to hold any data (in its attributes)
about the response which is specific to your app (e.g. session
Expand Down Expand Up @@ -149,7 +156,6 @@ class Response(object):
'options',
'status',
'stream',
'stream_len',
'_cookies',
'_data',
'_extra_headers',
Expand Down Expand Up @@ -181,7 +187,6 @@ def __init__(self, options=None):

self.body = None
self.stream = None
self.stream_len = None
self._data = None
self._media = None

Expand Down Expand Up @@ -238,6 +243,18 @@ def media(self, obj):
# just be thrown away.
self._data = None

@property
def stream_len(self):
# NOTE(kgriffs): Provide some additional information by raising the
# error explicitly.
raise AttributeError(_STREAM_LEN_REMOVED_MSG)

@stream_len.setter
def stream_len(self, value):
# NOTE(kgriffs): We explicitly disallow setting the deprecated attribute
# so that apps relying on it do not fail silently.
raise AttributeError(_STREAM_LEN_REMOVED_MSG)

def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.status)

Expand All @@ -247,7 +264,8 @@ def set_stream(self, stream, content_length):
Although the `stream` and `content_length` properties may be set
directly, using this method ensures `content_length` is not
accidentally neglected when the length of the stream is known in
advance.
advance. Using this method is also slightly more performant
as compared to setting the properties individually.
Note:
If the stream length is unknown, you can set `stream`
Expand All @@ -262,7 +280,10 @@ def set_stream(self, stream, content_length):
"""

self.stream = stream
self.stream_len = content_length # NOTE(pshello): Deprecated in favor of `content_length`

# PERF(kgriffs): Set directly rather than incur the overhead of
# the self.content_length property.
self._headers['content-length'] = str(content_length)

def set_cookie(self, name, value, expires=None, max_age=None,
domain=None, path=None, secure=None, http_only=True):
Expand Down
12 changes: 6 additions & 6 deletions tests/test_hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def on_get(self, req, resp):
resp.set_stream(stream, stream_len)
else:
resp.stream = stream
resp.stream_len = stream_len
resp.content_length = stream_len

if 'body' in self.mode:
if 'bytes' in self.mode:
Expand Down Expand Up @@ -173,7 +173,7 @@ def test_stream_known_len(self, client):
result = client.simulate_get('/stream')
assert resource.called

expected_len = resource.resp.stream_len
expected_len = int(resource.resp.content_length)
actual_len = int(result.headers['content-length'])
assert actual_len == expected_len
assert len(result.content) == expected_len
Expand All @@ -187,7 +187,7 @@ def test_filelike(self, client):
result = client.simulate_get('/filelike', file_wrapper=file_wrapper)
assert resource.called

expected_len = resource.resp.stream_len
expected_len = int(resource.resp.content_length)
actual_len = int(result.headers['content-length'])
assert actual_len == expected_len
assert len(result.content) == expected_len
Expand All @@ -196,7 +196,7 @@ def test_filelike(self, client):
result = client.simulate_get('/filelike', file_wrapper=file_wrapper)
assert resource.called

expected_len = resource.resp.stream_len
expected_len = int(resource.resp.content_length)
actual_len = int(result.headers['content-length'])
assert actual_len == expected_len
assert len(result.content) == expected_len
Expand All @@ -212,7 +212,7 @@ def test_filelike_closing(self, client, stream_factory, assert_closed):
result = client.simulate_get('/filelike-closing', file_wrapper=None)
assert resource.called

expected_len = resource.resp.stream_len
expected_len = int(resource.resp.content_length)
actual_len = int(result.headers['content-length'])
assert actual_len == expected_len
assert len(result.content) == expected_len
Expand All @@ -227,7 +227,7 @@ def test_filelike_using_helper(self, client):
result = client.simulate_get('/filelike-helper')
assert resource.called

expected_len = resource.resp.stream_len
expected_len = int(resource.resp.content_length)
actual_len = int(result.headers['content-length'])
assert actual_len == expected_len
assert len(result.content) == expected_len
Expand Down
10 changes: 10 additions & 0 deletions tests/test_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,13 @@ def test_response_attempt_to_set_read_only_headers():
assert headers['x-things1'] == 'thing-1'
assert headers['x-things2'] == 'thing-2'
assert headers['x-things3'] == 'thing-3a, thing-3b'


def test_response_removed_stream_len():
resp = falcon.Response()

with pytest.raises(AttributeError):
resp.stream_len = 128

with pytest.raises(AttributeError):
resp.stream_len

0 comments on commit 5bd84a7

Please sign in to comment.