diff --git a/httpx/_client.py b/httpx/_client.py index 2c7ae030f5..67f304bc68 100644 --- a/httpx/_client.py +++ b/httpx/_client.py @@ -1264,7 +1264,9 @@ def __enter__(self: T) -> T: if self._state != ClientState.UNOPENED: msg = { ClientState.OPENED: "Cannot open a client instance more than once.", - ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.", + ClientState.CLOSED: ( + "Cannot reopen a client instance, once it has been closed." + ), }[self._state] raise RuntimeError(msg) @@ -1980,7 +1982,9 @@ async def __aenter__(self: U) -> U: if self._state != ClientState.UNOPENED: msg = { ClientState.OPENED: "Cannot open a client instance more than once.", - ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.", + ClientState.CLOSED: ( + "Cannot reopen a client instance, once it has been closed." + ), }[self._state] raise RuntimeError(msg) diff --git a/httpx/_decoders.py b/httpx/_decoders.py index b4ac9a44af..57c649297a 100644 --- a/httpx/_decoders.py +++ b/httpx/_decoders.py @@ -259,7 +259,8 @@ class LineDecoder: """ Handles incrementally reading lines from text. - Has the same behaviour as the stdllib splitlines, but handling the input iteratively. + Has the same behaviour as the stdllib splitlines, + but handling the input iteratively. """ def __init__(self) -> None: diff --git a/httpx/_exceptions.py b/httpx/_exceptions.py index 24a4f8aba3..123692955b 100644 --- a/httpx/_exceptions.py +++ b/httpx/_exceptions.py @@ -313,7 +313,10 @@ class ResponseNotRead(StreamError): """ def __init__(self) -> None: - message = "Attempted to access streaming response content, without having called `read()`." + message = ( + "Attempted to access streaming response content," + " without having called `read()`." + ) super().__init__(message) @@ -323,7 +326,10 @@ class RequestNotRead(StreamError): """ def __init__(self) -> None: - message = "Attempted to access streaming request content, without having called `read()`." + message = ( + "Attempted to access streaming request content," + " without having called `read()`." + ) super().__init__(message) diff --git a/httpx/_main.py b/httpx/_main.py index 7c12ce841d..98e12e23c7 100644 --- a/httpx/_main.py +++ b/httpx/_main.py @@ -63,9 +63,10 @@ def print_help() -> None: ) table.add_row( "--auth [cyan]", - "Username and password to include in the request. Specify '-' for the password to use " - "a password prompt. Note that using --verbose/-v will expose the Authorization " - "header, including the password encoding in a trivially reversible format.", + "Username and password to include in the request. Specify '-' for the password" + " to use a password prompt. Note that using --verbose/-v will expose" + " the Authorization header, including the password encoding" + " in a trivially reversible format.", ) table.add_row( @@ -75,8 +76,8 @@ def print_help() -> None: table.add_row( "--timeout [cyan]FLOAT", - "Timeout value to use for network operations, such as establishing the connection, " - "reading some data, etc... [Default: 5.0]", + "Timeout value to use for network operations, such as establishing the" + " connection, reading some data, etc... [Default: 5.0]", ) table.add_row("--follow-redirects", "Automatically follow redirects.") diff --git a/httpx/_models.py b/httpx/_models.py index 8a5e6280f3..dac177c4f6 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -358,7 +358,8 @@ def __init__( # Using `content=...` implies automatically populated `Host` and content # headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`. # - # Using `stream=...` will not automatically include *any* auto-populated headers. + # Using `stream=...` will not automatically include *any* + # auto-populated headers. # # As an end-user you don't really need `stream=...`. It's only # useful when: diff --git a/httpx/_multipart.py b/httpx/_multipart.py index 6d5baa8639..5122d5114f 100644 --- a/httpx/_multipart.py +++ b/httpx/_multipart.py @@ -48,7 +48,8 @@ def __init__( ) if value is not None and not isinstance(value, (str, bytes, int, float)): raise TypeError( - f"Invalid type for value. Expected primitive type, got {type(value)}: {value!r}" + "Invalid type for value. Expected primitive type," + f" got {type(value)}: {value!r}" ) self.name = name self.value: typing.Union[str, bytes] = ( @@ -96,11 +97,13 @@ def __init__(self, name: str, value: FileTypes) -> None: content_type: typing.Optional[str] = None # This large tuple based API largely mirror's requests' API - # It would be good to think of better APIs for this that we could include in httpx 2.0 - # since variable length tuples (especially of 4 elements) are quite unwieldly + # It would be good to think of better APIs for this that we could + # include in httpx 2.0 since variable length tuples(especially of 4 elements) + # are quite unwieldly if isinstance(value, tuple): if len(value) == 2: - # neither the 3rd parameter (content_type) nor the 4th (headers) was included + # neither the 3rd parameter (content_type) nor the 4th (headers) + # was included filename, fileobj = value # type: ignore elif len(value) == 3: filename, fileobj, content_type = value # type: ignore @@ -116,9 +119,9 @@ def __init__(self, name: str, value: FileTypes) -> None: has_content_type_header = any("content-type" in key.lower() for key in headers) if content_type is not None and not has_content_type_header: - # note that unlike requests, we ignore the content_type - # provided in the 3rd tuple element if it is also included in the headers - # requests does the opposite (it overwrites the header with the 3rd tuple element) + # note that unlike requests, we ignore the content_type provided in the 3rd + # tuple element if it is also included in the headers requests does + # the opposite (it overwrites the headerwith the 3rd tuple element) headers["Content-Type"] = content_type if isinstance(fileobj, io.StringIO): diff --git a/httpx/_transports/default.py b/httpx/_transports/default.py index 343c588f9f..c0c4ffd40a 100644 --- a/httpx/_transports/default.py +++ b/httpx/_transports/default.py @@ -190,7 +190,8 @@ def __init__( ) else: # pragma: no cover raise ValueError( - f"Proxy protocol must be either 'http', 'https', or 'socks5', but got {proxy.url.scheme!r}." + "Proxy protocol must be either 'http', 'https', or 'socks5'," + f" but got {proxy.url.scheme!r}." ) def __enter__(self: T) -> T: # Use generics for subclass support. @@ -328,7 +329,8 @@ def __init__( ) else: # pragma: no cover raise ValueError( - f"Proxy protocol must be either 'http', 'https', or 'socks5', but got {proxy.url.scheme!r}." + "Proxy protocol must be either 'http', 'https', or 'socks5'," + " but got {proxy.url.scheme!r}." ) async def __aenter__(self: A) -> A: # Use generics for subclass support. diff --git a/httpx/_urlparse.py b/httpx/_urlparse.py index 8e060424e8..a9a0aecd9b 100644 --- a/httpx/_urlparse.py +++ b/httpx/_urlparse.py @@ -360,24 +360,25 @@ def normalize_port( def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None: """ - Path validation rules that depend on if the URL contains a scheme or authority component. + Path validation rules that depend on if the URL contains + a scheme or authority component. See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3 """ if has_authority: - # > If a URI contains an authority component, then the path component - # > must either be empty or begin with a slash ("/") character." + # If a URI contains an authority component, then the path component + # must either be empty or begin with a slash ("/") character." if path and not path.startswith("/"): raise InvalidURL("For absolute URLs, path must be empty or begin with '/'") else: - # > If a URI does not contain an authority component, then the path cannot begin - # > with two slash characters ("//"). + # If a URI does not contain an authority component, then the path cannot begin + # with two slash characters ("//"). if path.startswith("//"): raise InvalidURL( "URLs with no authority component cannot have a path starting with '//'" ) - # > In addition, a URI reference (Section 4.1) may be a relative-path reference, in which - # > case the first path segment cannot contain a colon (":") character. + # In addition, a URI reference (Section 4.1) may be a relative-path reference, + # in which case the first path segment cannot contain a colon (":") character. if path.startswith(":") and not has_scheme: raise InvalidURL( "URLs with no scheme component cannot have a path starting with ':'" @@ -449,16 +450,18 @@ def quote(string: str, safe: str = "/") -> str: def urlencode(items: typing.List[typing.Tuple[str, str]]) -> str: - # We can use a much simpler version of the stdlib urlencode here because - # we don't need to handle a bunch of different typing cases, such as bytes vs str. - # - # https://github.com/python/cpython/blob/b2f7b2ef0b5421e01efb8c7bee2ef95d3bab77eb/Lib/urllib/parse.py#L926 - # - # Note that we use '%20' encoding for spaces. and '%2F for '/'. - # This is slightly different than `requests`, but is the behaviour that browsers use. - # - # See - # - https://github.com/encode/httpx/issues/2536 - # - https://github.com/encode/httpx/issues/2721 - # - https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode + """ + We can use a much simpler version of the stdlib urlencode here because + we don't need to handle a bunch of different typing cases, such as bytes vs str. + + https://github.com/python/cpython/blob/b2f7b2ef0b5421e01efb8c7bee2ef95d3bab77eb/Lib/urllib/parse.py#L926 + + Note that we use '%20' encoding for spaces. and '%2F for '/'. + This is slightly different than `requests`, but is the behaviour that browsers use. + + See + - https://github.com/encode/httpx/issues/2536 + - https://github.com/encode/httpx/issues/2721 + - https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode + """ return "&".join([quote(k, safe="") + "=" + quote(v, safe="") for k, v in items]) diff --git a/httpx/_urls.py b/httpx/_urls.py index b023941b62..26202e95db 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -51,21 +51,23 @@ class URL: assert url.raw_host == b"xn--fiqs8s.icom.museum" * `url.port` is either None or an integer. URLs that include the default port for - "http", "https", "ws", "wss", and "ftp" schemes have their port normalized to `None`. + "http", "https", "ws", "wss", and "ftp" schemes have their port + normalized to `None`. assert httpx.URL("http://example.com") == httpx.URL("http://example.com:80") assert httpx.URL("http://example.com").port is None assert httpx.URL("http://example.com:80").port is None - * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work with - `url.username` and `url.password` instead, which handle the URL escaping. + * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work + with `url.username` and `url.password` instead, which handle the URL escaping. * `url.raw_path` is raw bytes of both the path and query, without URL escaping. This portion is used as the target when constructing HTTP requests. Usually you'll want to work with `url.path` instead. - * `url.query` is raw bytes, without URL escaping. A URL query string portion can only - be properly URL escaped when decoding the parameter names and values themselves. + * `url.query` is raw bytes, without URL escaping. A URL query string portion can + only be properly URL escaped when decoding the parameter names and values + themselves. """ def __init__( @@ -115,7 +117,8 @@ def __init__( self._uri_reference = url._uri_reference.copy_with(**kwargs) else: raise TypeError( - f"Invalid type for url. Expected str or httpx.URL, got {type(url)}: {url!r}" + "Invalid type for url. Expected str or httpx.URL," + f" got {type(url)}: {url!r}" ) @property @@ -305,7 +308,8 @@ def raw(self) -> RawURL: Provides the (scheme, host, port, target) for the outgoing request. In older versions of `httpx` this was used in the low-level transport API. - We no longer use `RawURL`, and this property will be deprecated in a future release. + We no longer use `RawURL`, and this property will be deprecated + in a future release. """ return RawURL( self.raw_scheme, @@ -342,7 +346,9 @@ def copy_with(self, **kwargs: typing.Any) -> "URL": For example: - url = httpx.URL("https://www.example.com").copy_with(username="jo@gmail.com", password="a secret") + url = httpx.URL("https://www.example.com").copy_with( + username="jo@gmail.com", password="a secret" + ) assert url == "https://jo%40email.com:a%20secret@www.example.com" """ return URL(self, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index 326a880cfc..4f7a848f83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,10 +97,6 @@ replacement = 'src="https://raw.githubusercontent.com/encode/httpx/master/\1"' [tool.ruff] select = ["E", "F", "I", "B", "PIE"] ignore = ["B904", "B028"] -line-length = 88 - -[tool.ruff.pycodestyle] -max-line-length = 120 [tool.ruff.isort] combine-as-imports = true diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 9b1dd88f5e..e6bac23dfc 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -596,7 +596,8 @@ async def test_digest_auth_resets_nonce_count_after_401() -> None: # with this we now force a 401 on a subsequent (but initial) request app.send_response_after_attempt = 2 - # we expect the client again to try to authenticate, i.e. the history length must be 1 + # we expect the client again to try to authenticate, + # i.e. the history length must be 1 response_2 = await client.get(url, auth=auth) assert response_2.status_code == 200 assert len(response_2.history) == 1 diff --git a/tests/models/test_cookies.py b/tests/models/test_cookies.py index dbe1bfb99e..f7abe11ad4 100644 --- a/tests/models/test_cookies.py +++ b/tests/models/test_cookies.py @@ -92,7 +92,7 @@ def test_cookies_repr(): cookies.set(name="foo", value="bar", domain="http://blah.com") cookies.set(name="fizz", value="buzz", domain="http://hello.com") - assert ( - repr(cookies) - == ", ]>" + assert repr(cookies) == ( + "," + " ]>" ) diff --git a/tests/test_config.py b/tests/test_config.py index 00913b2c17..6f6ee4f575 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -101,7 +101,10 @@ def test_create_ssl_context_with_get_request(server, cert_pem_file): def test_limits_repr(): limits = httpx.Limits(max_connections=100) - expected = "Limits(max_connections=100, max_keepalive_connections=None, keepalive_expiry=5.0)" + expected = ( + "Limits(max_connections=100, max_keepalive_connections=None," + " keepalive_expiry=5.0)" + ) assert repr(limits) == expected diff --git a/tests/test_multipart.py b/tests/test_multipart.py index 55211fb1a9..fc283c9cc4 100644 --- a/tests/test_multipart.py +++ b/tests/test_multipart.py @@ -174,7 +174,10 @@ def test_multipart_file_tuple_headers(file_content_type: typing.Optional[str]) - def test_multipart_headers_include_content_type() -> None: - """Content-Type from 4th tuple parameter (headers) should override the 3rd parameter (content_type)""" + """ + Content-Type from 4th tuple parameter (headers) should + override the 3rd parameter (content_type) + """ file_name = "test.txt" file_content = io.BytesIO(b"") file_content_type = "text/plain" diff --git a/tests/test_utils.py b/tests/test_utils.py index dedb92f7f2..bbaf6007b5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -109,7 +109,8 @@ def test_logging_redirect_chain(server, caplog): ( "httpx", logging.INFO, - 'HTTP Request: GET http://127.0.0.1:8000/redirect_301 "HTTP/1.1 301 Moved Permanently"', + "HTTP Request: GET http://127.0.0.1:8000/redirect_301" + ' "HTTP/1.1 301 Moved Permanently"', ), ( "httpx",