Skip to content

Commit

Permalink
Content-Disposition fast access in ClientResponse
Browse files Browse the repository at this point in the history
Add ContentDisposition class and content_disposition property

Partially implements #1670
  • Loading branch information
Sergey Skripnick committed Oct 31, 2017
1 parent 20362c5 commit 6afa66c
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/2455.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Content-Disposition fast access in ClientResponse
25 changes: 25 additions & 0 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import base64
import binascii
import cgi
import collections
import datetime
import functools
import inspect
Expand Down Expand Up @@ -41,6 +42,10 @@
TOKEN = CHAR ^ CTL ^ SEPARATORS


ContentDisposition = collections.namedtuple(
'ContentDisposition', ('value', 'parameters', 'filename'))


class _BaseCoroMixin(Coroutine):

__slots__ = ('_coro')
Expand Down Expand Up @@ -703,6 +708,7 @@ class HeadersMixin:

_content_type = None
_content_dict = None
_content_disposition = False
_stored_content_type = sentinel

def _parse_content_type(self, raw):
Expand All @@ -714,6 +720,18 @@ def _parse_content_type(self, raw):
else:
self._content_type, self._content_dict = cgi.parse_header(raw)

def _parse_content_disposition(self, _CONTENT_DISPOSITION):
from . import multipart
raw = self._headers.get(_CONTENT_DISPOSITION)
if raw is None:
self._content_disposition = None
else:
value, params = multipart.parse_content_disposition(raw)
filename = multipart.content_disposition_filename(params)
self._content_disposition = ContentDisposition(value, params,
filename)
return self._content_disposition

@property
def content_type(self, *, _CONTENT_TYPE=hdrs.CONTENT_TYPE):
"""The value of content part for Content-Type HTTP header."""
Expand All @@ -737,3 +755,10 @@ def content_length(self, *, _CONTENT_LENGTH=hdrs.CONTENT_LENGTH):

if content_length:
return int(content_length)

@property
def content_disposition(self, *, header=hdrs.CONTENT_DISPOSITION):
"""The value of Content-Disposition HTTP header."""
if self._content_disposition == False:
self._parse_content_disposition(header)
return self._content_disposition
24 changes: 23 additions & 1 deletion docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1109,13 +1109,20 @@ Response object

.. attribute:: charset

Read-only property that specifies the *encoding* for the request's BODY.
Read-only property that specifies the *encoding* for the request's BODY.

The value is parsed from the *Content-Type* HTTP header.

Returns :class:`str` like ``'utf-8'`` or ``None`` if no *Content-Type*
header present in HTTP headers or it has no charset information.

.. attribute:: content_disposition

Read-only property that specified the *Content-Disposition* HTTP header.

Instance of :class:`ContentDisposition` or ``None`` if no *Content-Disposition*
header present in HTTP headers.

.. attribute:: history

A :class:`~collections.abc.Sequence` of :class:`ClientResponse`
Expand Down Expand Up @@ -1561,6 +1568,21 @@ All exceptions are available as members of *aiohttp* module.

Invalid URL, :class:`yarl.URL` instance.

.. class:: ContentDisposition

Represent Content-Disposition header

.. attribute:: value

Value of Content-Disposition header itself, e.g. ``attachment``.

.. attribute:: filename

Content filename extracted from parameters. May be ``None``.

.. attribute:: parameters

A :class:`dict` instance contains all parameters.

Response errors
^^^^^^^^^^^^^^^
Expand Down
26 changes: 26 additions & 0 deletions tests/test_client_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,32 @@ def test_charset_no_charset():
assert response.charset is None


def test_content_disposition_full():
response = ClientResponse('get', URL('http://def-cl-resp.org'))
response.headers = {'Content-Disposition':
'attachment; filename="archive.tar.gz"; foo=bar'}

assert 'attachment' == response.content_disposition.value
assert 'bar' == response.content_disposition.parameters["foo"]
assert 'archive.tar.gz' == response.content_disposition.filename


def test_content_disposition_no_parameters():
response = ClientResponse('get', URL('http://def-cl-resp.org'))
response.headers = {'Content-Disposition': 'attachment'}

assert 'attachment' == response.content_disposition.value
assert response.content_disposition.filename is None
assert {} == response.content_disposition.parameters


def test_content_disposition_no_header():
response = ClientResponse('get', URL('http://def-cl-resp.org'))
response.headers = {}

assert response.content_disposition is None


def test_response_request_info():
url = 'http://def-cl-resp.org'
headers = {'Content-Type': 'application/json;charset=cp1251'}
Expand Down

0 comments on commit 6afa66c

Please sign in to comment.