]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
added netrc support (#177)
authorCan Sarıgöl <cansarigol@derinbilgi.com.tr>
Thu, 1 Aug 2019 08:32:00 +0000 (11:32 +0300)
committerTom Christie <tom@tomchristie.com>
Thu, 1 Aug 2019 08:32:00 +0000 (09:32 +0100)
httpx/api.py
httpx/client.py
httpx/utils.py
tests/.netrc [new file with mode: 0644]
tests/client/test_auth.py
tests/test_utils.py

index 99d60128ab609bc4a0c098f0b4c48dc47bf8799c..be3390e430e13018ac0c8aaea35c1041f04a129a 100644 (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,
     )
index 60088c26574be3b4115eef8ac427d35e51414b22..46be340ccff4c6890d0c2e4936a121d432c5bf98 100644 (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:
index e96335f5471a28649e21f190d4ba54aa0fd1425a..4831578dd5d173b19ab9c6ed729eac7d9f3c0491 100644 (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
diff --git a/tests/.netrc b/tests/.netrc
new file mode 100644 (file)
index 0000000..ed65ee7
--- /dev/null
@@ -0,0 +1,3 @@
+machine netrcexample.org
+login example-username
+password example-password
\ No newline at end of file
index 44099fbada865ae5ca22f31763257cf4ad82d8b1..9127df869edd096cc738f4c208ceb1c5dd232580 100644 (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}
index b2b167116cf8d918c4b781c7459d2302f2a068d0..96d186b56153bda3e4e43c87a60646e1d0728025 100644 (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",
+    )