added netrc support (#177)

This commit is contained in:
Can Sarıgöl 2019-08-01 11:32:00 +03:00 committed by Tom Christie
parent d1b1d35b5b
commit 919e8d3f9b
6 changed files with 123 additions and 3 deletions

View File

@ -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,
)

View File

@ -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:

View File

@ -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
View File

@ -0,0 +1,3 @@
machine netrcexample.org
login example-username
password example-password

View File

@ -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}

View File

@ -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",
)