Skip to content

Commit

Permalink
Add if_match and if_none_match properties
Browse files Browse the repository at this point in the history
  • Loading branch information
greshilov committed Nov 28, 2020
1 parent 7025fb3 commit 7776e16
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
44 changes: 44 additions & 0 deletions aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
from .abc import AbstractStreamWriter
from .helpers import (
_SENTINEL,
ETAG_ANY,
LIST_QUOTED_ETAG_RE,
ChainMapProxy,
ETag,
HeadersMixin,
is_expected_content_type,
reify,
Expand Down Expand Up @@ -494,6 +497,47 @@ def if_unmodified_since(self) -> Optional[datetime.datetime]:
"""
return self._http_date(self.headers.get(hdrs.IF_UNMODIFIED_SINCE))

@staticmethod
def _etag_values(etag_header: str) -> Iterator[ETag]:
"""Extract `ETag` objects from raw header."""
if etag_header == ETAG_ANY:
yield ETag(
is_weak=False,
value=ETAG_ANY,
)
else:
for match in LIST_QUOTED_ETAG_RE.finditer(etag_header):
is_weak, quoted_value = match.group(2), match.group(3)
yield ETag(
is_weak=bool(is_weak),
value=quoted_value[1:-1],
)

@classmethod
def _if_match_or_none_impl(
cls, header_value: Optional[str]
) -> Optional[Tuple[ETag, ...]]:
if not header_value:
return None

return tuple(cls._etag_values(header_value))

@reify
def if_match(self) -> Optional[Tuple[ETag, ...]]:
"""The value of If-Match HTTP header, or None.
This header is represented as a `tuple` of `ETag` objects.
"""
return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH))

@reify
def if_none_match(self) -> Optional[Tuple[ETag, ...]]:
"""The value of If-None-Match HTTP header, or None.
This header is represented as a `tuple` of `ETag` objects.
"""
return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH))

@reify
def if_range(self) -> Optional[datetime.datetime]:
"""The value of If-Range HTTP header, or None.
Expand Down
18 changes: 18 additions & 0 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,24 @@ and :ref:`aiohttp-web-signals` handlers.

.. versionadded:: 3.1

.. attribute:: if_match

Read-only property that returns :class:`ETag` objects specified
in the *If-Match* header.

Returns :class:`tuple` of :class:`ETag` or ``None`` if
*If-Match* header is absent.

.. attribute:: if_none_match

Read-only property that returns :class:`ETag` objects specified
*If-None-Match* header.

Returns :class:`tuple` of :class:`ETag` or ``None`` if
*If-None-Match* header is absent.

.. versionadded:: 4.0

.. attribute:: if_range

Read-only property that returns the date specified in the
Expand Down
34 changes: 34 additions & 0 deletions tests/test_web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from aiohttp.streams import StreamReader
from aiohttp.test_utils import make_mocked_request
from aiohttp.web import HTTPRequestEntityTooLarge, HTTPUnsupportedMediaType
from aiohttp.web_request import ETag


@pytest.fixture
Expand Down Expand Up @@ -820,3 +821,36 @@ async def invalid_handler_1(request):

async with client.get("/1") as resp:
assert 500 == resp.status


@pytest.mark.parametrize(
"header,header_attr",
[
pytest.param("If-Match", "if_match"),
pytest.param("If-None-Match", "if_none_match"),
],
)
@pytest.mark.parametrize(
"header_val,expected",
[
pytest.param(
'"67ab43", W/"54ed21", "7892dd"',
(
ETag(is_weak=False, value="67ab43"),
ETag(is_weak=True, value="54ed21"),
ETag(is_weak=False, value="7892dd"),
),
),
pytest.param(
'"bfc1ef-5b2c2730249c88ca92d82d"',
(ETag(is_weak=False, value="bfc1ef-5b2c2730249c88ca92d82d"),),
),
pytest.param(
"*",
(ETag(is_weak=False, value="*"),),
),
],
)
def test_etag_headers(header, header_attr, header_val, expected) -> None:
req = make_mocked_request("GET", "/", headers={header: header_val})
assert getattr(req, header_attr) == expected

0 comments on commit 7776e16

Please sign in to comment.