added netrc support (#177)
This commit is contained in:
parent
d1b1d35b5b
commit
919e8d3f9b
16
httpx/api.py
16
httpx/api.py
@ -32,6 +32,7 @@ def request(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
stream: bool = False,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
with Client() as client:
|
||||
return client.request(
|
||||
@ -49,6 +50,7 @@ def request(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -64,6 +66,7 @@ def get(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"GET",
|
||||
@ -77,6 +80,7 @@ def get(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -92,6 +96,7 @@ def options(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"OPTIONS",
|
||||
@ -105,6 +110,7 @@ def options(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -120,6 +126,7 @@ def head(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"HEAD",
|
||||
@ -133,6 +140,7 @@ def head(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -151,6 +159,7 @@ def post(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"POST",
|
||||
@ -167,6 +176,7 @@ def post(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -185,6 +195,7 @@ def put(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"PUT",
|
||||
@ -201,6 +212,7 @@ def put(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -219,6 +231,7 @@ def patch(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"PATCH",
|
||||
@ -235,6 +248,7 @@ def patch(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
|
||||
@ -253,6 +267,7 @@ def delete(
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = True,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return request(
|
||||
"DELETE",
|
||||
@ -269,4 +284,5 @@ def delete(
|
||||
cert=cert,
|
||||
verify=verify,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
@ -43,6 +43,7 @@ from .models import (
|
||||
URLTypes,
|
||||
)
|
||||
from .status_codes import codes
|
||||
from .utils import get_netrc_login
|
||||
|
||||
|
||||
class BaseClient:
|
||||
@ -61,6 +62,7 @@ class BaseClient:
|
||||
app: typing.Callable = None,
|
||||
raise_app_exceptions: bool = True,
|
||||
backend: ConcurrencyBackend = None,
|
||||
trust_env: bool = True,
|
||||
):
|
||||
if backend is None:
|
||||
backend = AsyncioBackend()
|
||||
@ -101,6 +103,7 @@ class BaseClient:
|
||||
self.max_redirects = max_redirects
|
||||
self.dispatch = async_dispatch
|
||||
self.concurrency_backend = backend
|
||||
self.trust_env = trust_env
|
||||
|
||||
def merge_cookies(
|
||||
self, cookies: CookieTypes = None
|
||||
@ -130,6 +133,7 @@ class BaseClient:
|
||||
verify: VerifyTypes = None,
|
||||
cert: CertTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
if auth is None:
|
||||
auth = self.auth
|
||||
@ -139,8 +143,16 @@ class BaseClient:
|
||||
if url.scheme not in ("http", "https"):
|
||||
raise InvalidURL('URL scheme must be "http" or "https".')
|
||||
|
||||
if auth is None and (url.username or url.password):
|
||||
auth = HTTPBasicAuth(username=url.username, password=url.password)
|
||||
if auth is None:
|
||||
if url.username or url.password:
|
||||
auth = HTTPBasicAuth(username=url.username, password=url.password)
|
||||
elif trust_env:
|
||||
netrc_login = get_netrc_login(url.authority)
|
||||
if netrc_login:
|
||||
netrc_username, _, netrc_password = netrc_login
|
||||
auth = HTTPBasicAuth(
|
||||
username=netrc_username, password=netrc_password
|
||||
)
|
||||
|
||||
if auth is not None:
|
||||
if isinstance(auth, tuple):
|
||||
@ -312,6 +324,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"GET",
|
||||
@ -325,6 +338,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def options(
|
||||
@ -340,6 +354,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"OPTIONS",
|
||||
@ -353,6 +368,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def head(
|
||||
@ -368,6 +384,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"HEAD",
|
||||
@ -381,6 +398,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def post(
|
||||
@ -399,6 +417,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"POST",
|
||||
@ -415,6 +434,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def put(
|
||||
@ -433,6 +453,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"PUT",
|
||||
@ -449,6 +470,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def patch(
|
||||
@ -467,6 +489,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"PATCH",
|
||||
@ -483,6 +506,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def delete(
|
||||
@ -501,6 +525,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
return await self.request(
|
||||
"DELETE",
|
||||
@ -517,6 +542,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
async def request(
|
||||
@ -536,6 +562,7 @@ class AsyncClient(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> AsyncResponse:
|
||||
url = self.base_url.join(url)
|
||||
headers = self.merge_headers(headers)
|
||||
@ -558,6 +585,7 @@ class AsyncClient(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
return response
|
||||
|
||||
@ -618,6 +646,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
url = self.base_url.join(url)
|
||||
headers = self.merge_headers(headers)
|
||||
@ -643,6 +672,7 @@ class Client(BaseClient):
|
||||
"verify": verify,
|
||||
"cert": cert,
|
||||
"timeout": timeout,
|
||||
"trust_env": trust_env,
|
||||
}
|
||||
async_response = concurrency_backend.run(coroutine, *args, **kwargs)
|
||||
|
||||
@ -685,6 +715,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"GET",
|
||||
@ -698,6 +729,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def options(
|
||||
@ -713,6 +745,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"OPTIONS",
|
||||
@ -726,6 +759,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def head(
|
||||
@ -741,6 +775,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"HEAD",
|
||||
@ -754,6 +789,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def post(
|
||||
@ -772,6 +808,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"POST",
|
||||
@ -788,6 +825,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def put(
|
||||
@ -806,6 +844,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"PUT",
|
||||
@ -822,6 +861,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def patch(
|
||||
@ -840,6 +880,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"PATCH",
|
||||
@ -856,6 +897,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def delete(
|
||||
@ -874,6 +916,7 @@ class Client(BaseClient):
|
||||
cert: CertTypes = None,
|
||||
verify: VerifyTypes = None,
|
||||
timeout: TimeoutTypes = None,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
return self.request(
|
||||
"DELETE",
|
||||
@ -890,6 +933,7 @@ class Client(BaseClient):
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
trust_env=trust_env,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import codecs
|
||||
import netrc
|
||||
import os
|
||||
import typing
|
||||
|
||||
|
||||
@ -79,3 +81,12 @@ def guess_json_utf(data: bytes) -> typing.Optional[str]:
|
||||
return "utf-32-le"
|
||||
# Did not detect a valid UTF-32 ascii-range character
|
||||
return None
|
||||
|
||||
|
||||
def get_netrc_login(host: str) -> typing.Optional[typing.Tuple[str, str, str]]:
|
||||
try:
|
||||
netrc_info = netrc.netrc(os.environ.get("NETRC")) # type: ignore
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
return netrc_info.authenticators(host) # type: ignore
|
||||
|
||||
3
tests/.netrc
Normal file
3
tests/.netrc
Normal file
@ -0,0 +1,3 @@
|
||||
machine netrcexample.org
|
||||
login example-username
|
||||
password example-password
|
||||
@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from httpx import (
|
||||
AsyncDispatcher,
|
||||
@ -67,3 +68,27 @@ def test_custom_auth():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"auth": "Token 123"}
|
||||
|
||||
|
||||
def test_netrc_auth():
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
url = "http://netrcexample.org"
|
||||
|
||||
with Client(dispatch=MockDispatch()) as client:
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"auth": "Basic ZXhhbXBsZS11c2VybmFtZTpleGFtcGxlLXBhc3N3b3Jk"
|
||||
}
|
||||
|
||||
|
||||
def test_trust_env_auth():
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
url = "http://netrcexample.org"
|
||||
|
||||
with Client(dispatch=MockDispatch()) as client:
|
||||
response = client.get(url, trust_env=False)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"auth": None}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from httpx.utils import guess_json_utf
|
||||
from httpx.utils import get_netrc_login, guess_json_utf
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -37,3 +39,22 @@ def test_bad_utf_like_encoding():
|
||||
def test_guess_by_bom(encoding, expected):
|
||||
data = u"\ufeff{}".encode(encoding)
|
||||
assert guess_json_utf(data) == expected
|
||||
|
||||
|
||||
def test_bad_get_netrc_login():
|
||||
assert get_netrc_login("url") is None
|
||||
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
assert get_netrc_login("url") is None
|
||||
|
||||
os.environ["NETRC"] = "wrongpath"
|
||||
assert get_netrc_login("url") is None
|
||||
|
||||
|
||||
def test_get_netrc_login():
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
assert get_netrc_login("netrcexample.org") == (
|
||||
"example-username",
|
||||
None,
|
||||
"example-password",
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user