is_known_encoding,
normalize_header_key,
normalize_header_value,
+ parse_header_links,
str_query_param,
)
self._cookies.extract_cookies(self)
return self._cookies
+ @property
+ def links(self) -> typing.Dict[typing.Optional[str], typing.Dict[str, str]]:
+ """
+ Returns the parsed header links of the response, if any
+ """
+ header = self.headers.get("link")
+ ldict = {}
+ if header:
+ links = parse_header_links(header)
+ for link in links:
+ key = link.get("rel") or link.get("url")
+ ldict[key] = link
+ return ldict
+
def __repr__(self) -> str:
return f"<Response [{self.status_code} {self.reason_phrase}]>"
import codecs
import netrc
import os
+import re
import typing
from pathlib import Path
netrc_info = netrc.netrc(str(netrc_path))
return netrc_info.authenticators(host) # type: ignore
+
+
+def parse_header_links(value: str) -> typing.List[typing.Dict[str, str]]:
+ """
+ Returns a list of parsed link headers, for more info see:
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
+ The generic syntax of those is:
+ Link: < uri-reference >; param1=value1; param2="value2"
+ So for instance:
+ Link; '<http:/.../front.jpeg>; type="image/jpeg",<http://.../back.jpeg>;'
+ would return
+ [
+ {"url": "http:/.../front.jpeg", "type": "image/jpeg"},
+ {"url": "http://.../back.jpeg"},
+ ]
+ :param value: HTTP Link entity-header field
+ :return: list of parsed link headers
+ """
+ links: typing.List[typing.Dict[str, str]] = []
+ replace_chars = " '\""
+ value = value.strip(replace_chars)
+ if not value:
+ return links
+ for val in re.split(", *<", value):
+ try:
+ url, params = val.split(";", 1)
+ except ValueError:
+ url, params = val, ""
+ link = {"url": url.strip("<> '\"")}
+ for param in params.split(";"):
+ try:
+ key, value = param.split("=")
+ except ValueError:
+ break
+ link[key.strip(replace_chars)] = value.strip(replace_chars)
+ links.append(link)
+ return links
response.json()
+@pytest.mark.parametrize(
+ "headers, expected",
+ [
+ (
+ {"Link": "<https://example.com>; rel='preload'"},
+ {"preload": {"rel": "preload", "url": "https://example.com"}},
+ ),
+ (
+ {"Link": '</hub>; rel="hub", </resource>; rel="self"'},
+ {
+ "hub": {"url": "/hub", "rel": "hub"},
+ "self": {"url": "/resource", "rel": "self"},
+ },
+ ),
+ ],
+)
+def test_link_headers(headers, expected):
+ response = httpx.Response(200, content=None, headers=headers)
+ assert response.links == expected
+
+
@pytest.mark.asyncio
async def test_stream_text():
async def iterator():
import pytest
-from httpx.utils import get_netrc_login, guess_json_utf
+from httpx.utils import get_netrc_login, guess_json_utf, parse_header_links
@pytest.mark.parametrize(
None,
"example-password",
)
+
+
+@pytest.mark.parametrize(
+ "value, expected",
+ (
+ (
+ '<http:/.../front.jpeg>; rel=front; type="image/jpeg"',
+ [{"url": "http:/.../front.jpeg", "rel": "front", "type": "image/jpeg"}],
+ ),
+ ("<http:/.../front.jpeg>", [{"url": "http:/.../front.jpeg"}]),
+ ("<http:/.../front.jpeg>;", [{"url": "http:/.../front.jpeg"}]),
+ (
+ '<http:/.../front.jpeg>; type="image/jpeg",<http://.../back.jpeg>;',
+ [
+ {"url": "http:/.../front.jpeg", "type": "image/jpeg"},
+ {"url": "http://.../back.jpeg"},
+ ],
+ ),
+ ("", []),
+ ),
+)
+def test_parse_header_links(value, expected):
+ assert parse_header_links(value) == expected