From 985e45cfe3bc072d133eb53d4dbb5220c7ee39b0 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:03:17 +0200 Subject: [PATCH 01/24] ci(flake8): remove ignoring unnecessary codes --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 32febf22..e44b8108 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,2 @@ [flake8] -ignore = E501,W191,C901,E265,E402,E117 +ignore = E501 From 536a00f4301f46e20b5411d8e5c4ba069aaa9616 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:12:11 +0200 Subject: [PATCH 02/24] style: fix regex-flag-alias (FURB167) --- httoop/header/messaging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httoop/header/messaging.py b/httoop/header/messaging.py index 029bc807..511603dd 100644 --- a/httoop/header/messaging.py +++ b/httoop/header/messaging.py @@ -378,7 +378,7 @@ class SetCookie(_ListElement, _CookieElement): @classmethod def split(cls, fieldvalue: bytes) -> list[bytes]: - fieldvalue = re.sub(b'(expires)=([^"][^;]+)', b'\\1="\\2"', fieldvalue, flags=re.I) + fieldvalue = re.sub(b'(expires)=([^"][^;]+)', b'\\1="\\2"', fieldvalue, flags=re.IGNORECASE) return super().split(fieldvalue) @property From 23e413d79e2aedaab3e2ae37b3ef881468a7ba3b Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:12:26 +0200 Subject: [PATCH 03/24] style: fix single-item-membership-test (FURB171) --- httoop/semantic/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httoop/semantic/response.py b/httoop/semantic/response.py index 29ac9938..c8a37792 100644 --- a/httoop/semantic/response.py +++ b/httoop/semantic/response.py @@ -87,7 +87,7 @@ def range_conditions(self) -> Iterator[bool | Body]: yield self.request.protocol >= (1, 1) yield response.status == 200 yield 'Range' in self.request.headers - yield self.request.method in ('GET',) + yield self.request.method == 'GET' yield response.headers.element('Accept-Ranges') == 'bytes' yield not self.chunked yield response.body From 50d6527174cfd95babfa9c3b61b917a7f5b43138 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:13:38 +0200 Subject: [PATCH 04/24] style: fix implicit-namespace-package (INP001) --- tests/uri/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/uri/__init__.py diff --git a/tests/uri/__init__.py b/tests/uri/__init__.py new file mode 100644 index 00000000..e69de29b From 704320bf73b72356ade1b42689fe4e43b9b7d347 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:23:55 +0200 Subject: [PATCH 05/24] perf(messages.body): fix try-except-in-loop (PERF203) --- httoop/messages/body.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/httoop/messages/body.py b/httoop/messages/body.py index c73fc945..da74e88e 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -17,6 +17,9 @@ from httoop.util import IFile +_SENTINEL = object() + + class Body(with_metaclass(HTTPSemantic, IFile)): """ A HTTP message body. @@ -262,15 +265,13 @@ def __iter_generator(self): fd = self.fd buffer_ = [] while True: - try: - data = next(fd) - except StopIteration: + data = next(fd, _SENTINEL) + if data is _SENTINEL: self.set(buffer_) # raise return # Python 3.7 PEP 479 - else: - buffer_.append(data) - yield data + buffer_.append(data) + yield data # def __copy__(self): # body = self.__class__(self.__content_bytes()) From 7d29646b7e96369b861b5e307c60f1c889511309 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:32:30 +0200 Subject: [PATCH 06/24] perf(header.element): fix try-except-in-loop (PERF203) --- httoop/header/element.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/httoop/header/element.py b/httoop/header/element.py index d39c3e4d..c25c4594 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -160,11 +160,12 @@ def _rfc2231_and_continuation_params(cls, params: Iterator[Any]) -> Iterator[tup yield key, value for key, lines in continuations.items(): + lines: dict value = '' for i in range(len(lines)): - try: + if i in lines: value += lines.pop(i) - except KeyError: + else: break if not key: # pragma: no cover raise InvalidHeader(_('...')) From b4e67768001da635567ea335b9531406e1008488 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:35:59 +0200 Subject: [PATCH 07/24] style: fix blanket-noqa (PGH004) --- tests/api/test_date.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/test_date.py b/tests/api/test_date.py index 233ddd7e..be797bfa 100644 --- a/tests/api/test_date.py +++ b/tests/api/test_date.py @@ -58,7 +58,7 @@ def test_date_gmtime(date, expected, lt, gt): def test_date_comparing_none(): d = Date(datetime.datetime(1994, 11, 6, 8, 49, 37)) assert d == Date(d) - assert not d == None # noqa + assert not d == None # noqa: E711 assert d > None assert not d < None @@ -83,4 +83,4 @@ def test_invalid_date(invalid): @pytest.mark.xfail(reason='currently it is not implemented in this way') def test_not_existing_comparision(): - assert Date() == [] is None # noqa: E711 + assert Date() == [] is None # noqa: F632 From 7957d7902fb384c7aa00c4a31db02ad721318e07 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:37:24 +0200 Subject: [PATCH 08/24] style: fix unnecessary-range-start (PIE808) --- httoop/status/client_error.py | 2 -- tests/headers/test_host_header.py | 2 +- tests/headers/test_invalid_headers.py | 2 +- tests/messaging/test_request_header.py | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/httoop/status/client_error.py b/httoop/status/client_error.py index 90540250..755ed105 100644 --- a/httoop/status/client_error.py +++ b/httoop/status/client_error.py @@ -10,8 +10,6 @@ class ClientErrorStatus(StatusException): format of wanted representation, or error in the clients http library. """ - pass - class BAD_REQUEST(ClientErrorStatus): """ diff --git a/tests/headers/test_host_header.py b/tests/headers/test_host_header.py index d504a980..cfe1a7e6 100644 --- a/tests/headers/test_host_header.py +++ b/tests/headers/test_host_header.py @@ -56,7 +56,7 @@ def _test_iter(header, host, port, headers): @pytest.mark.parametrize('invalid', list( - list((set('\x7F()<>@,;:/\\[\\]={} \t\\\\^"\'') | set(map(chr, range(0x00, 0x1F)))) - set(';\n\x00')) + [ + list((set('\x7F()<>@,;:/\\[\\]={} \t\\\\^"\'') | set(map(chr, range(0x1F)))) - set(';\n\x00')) + [ pytest.param(';', marks=pytest.mark.xfail), # FIXME pytest.param('\n', marks=pytest.mark.xfail), # FIXME pytest.param('\x00', marks=pytest.mark.xfail), # FIXME diff --git a/tests/headers/test_invalid_headers.py b/tests/headers/test_invalid_headers.py index 76eb3844..983374f7 100644 --- a/tests/headers/test_invalid_headers.py +++ b/tests/headers/test_invalid_headers.py @@ -5,7 +5,7 @@ LATIN_CHARS = bytes(bytearray(range(0x80, 0xFF + 1))) -INVALID_HEADER_FIELD_NAMES = bytes(bytearray(range(0x00, 0x1F + 1))) + b'()<>@,;\\\\"/\\[\\]?={} \t' +INVALID_HEADER_FIELD_NAMES = bytes(bytearray(range(0x1F + 1))) + b'()<>@,;\\\\"/\\[\\]?={} \t' @pytest.mark.parametrize('invalid', six.iterbytes(INVALID_HEADER_FIELD_NAMES + LATIN_CHARS)) diff --git a/tests/messaging/test_request_header.py b/tests/messaging/test_request_header.py index 0b0e8644..b43ce0f1 100644 --- a/tests/messaging/test_request_header.py +++ b/tests/messaging/test_request_header.py @@ -31,7 +31,7 @@ def test_request_without_headers(): pass -@pytest.mark.parametrize('char', b'%s\x7f()<>@,;\\\\"/\\[\\]?={} \t%s' % (bytes(bytearray(range(0x00, 0x1F))), bytes(bytearray(range(0x80, 0xFF))))) +@pytest.mark.parametrize('char', b'%s\x7f()<>@,;\\\\"/\\[\\]?={} \t%s' % (bytes(bytearray(range(0x1F))), bytes(bytearray(range(0x80, 0xFF))))) def test_invalid_header_syntax(char, headers): with pytest.raises(InvalidHeader): headers.parse(b'Fo%co: bar' % (char,)) From 18d1841c5eb2f55f8186e3bcefedb8a814de3791 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:41:17 +0200 Subject: [PATCH 09/24] style: fix pytest (PT) --- tests/api/test_statemachine.py | 2 +- tests/api/test_status.py | 2 +- tests/api/test_uri.py | 2 +- tests/authentication/test_basic.py | 2 +- tests/headers/test_invalid_headers.py | 2 +- tests/headers/test_trailers.py | 2 +- tests/messaging/test_invalid.py | 6 +++--- tests/messaging/test_request_codecs.py | 2 +- tests/uri/test_uri_abspath.py | 4 ++-- tests/uri/test_uri_schemes.py | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/api/test_statemachine.py b/tests/api/test_statemachine.py index d19430e1..6df94177 100644 --- a/tests/api/test_statemachine.py +++ b/tests/api/test_statemachine.py @@ -143,7 +143,7 @@ def test_parse_message_with_content_length_header(statemachine): assert bytes(request.body) == b'foo=bar' -@pytest.mark.xfail() # FIXME: new line not recognized in header parsing +@pytest.mark.xfail # FIXME: new line not recognized in header parsing def test_parse_message_without_carriage_return(statemachine): statemachine.parse(b'POST / HTTP/1.1\nHost: www.example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 7\n\nfoo=') request = statemachine.parse(b'bar')[0][0] diff --git a/tests/api/test_status.py b/tests/api/test_status.py index 9ae89bcd..eb64138a 100644 --- a/tests/api/test_status.py +++ b/tests/api/test_status.py @@ -159,7 +159,7 @@ def test_status_type(response): assert response.status.server_error -@pytest.mark.parametrize('code', (99, 600, 1000)) +@pytest.mark.parametrize('code', [99, 600, 1000]) def test_invalid_status_code(code, response): with pytest.raises(TypeError): response.status = code diff --git a/tests/api/test_uri.py b/tests/api/test_uri.py index d4938d11..2eb998b4 100644 --- a/tests/api/test_uri.py +++ b/tests/api/test_uri.py @@ -51,7 +51,7 @@ def test_set_latin1_bytes_uri_path(request_): # just for code coverage... behva assert bytes(request_.uri) == b'/foo%C3%BFbar' -@pytest.mark.xfail() +@pytest.mark.xfail def test_uri_path_segments(request_): request_.uri.parse(b'/fo%2fbar/baz%2Fblub') assert request_.uri.path_segments == ['', 'fo/bar', 'baz/blub'] diff --git a/tests/authentication/test_basic.py b/tests/authentication/test_basic.py index 0b5087e5..e222166b 100644 --- a/tests/authentication/test_basic.py +++ b/tests/authentication/test_basic.py @@ -27,7 +27,7 @@ def test_basic_authorization(headers): assert elem.password == 'test' -@pytest.mark.parametrize('invalid', (b'foo', b'Zm9v', 'föo'.encode('latin1'))) +@pytest.mark.parametrize('invalid', [b'foo', b'Zm9v', 'föo'.encode('latin1')]) def test_invalid_headers(headers, invalid): headers.parse(b'Authorization: Basic %s' % (invalid,)) with pytest.raises(InvalidHeader): diff --git a/tests/headers/test_invalid_headers.py b/tests/headers/test_invalid_headers.py index 983374f7..45886339 100644 --- a/tests/headers/test_invalid_headers.py +++ b/tests/headers/test_invalid_headers.py @@ -32,7 +32,7 @@ def test_set_header_with_colon(request_): # request_.headers[name] = 'baz' -@pytest.mark.parametrize('name', (b'Content-Encoding', b'Transfer-Encoding')) +@pytest.mark.parametrize('name', [b'Content-Encoding', b'Transfer-Encoding']) def test_invalid_codec(name, headers): headers.parse(b'%s: foo' % (name,)) with pytest.raises(InvalidHeader): diff --git a/tests/headers/test_trailers.py b/tests/headers/test_trailers.py index 0618702a..58c6528e 100644 --- a/tests/headers/test_trailers.py +++ b/tests/headers/test_trailers.py @@ -3,7 +3,7 @@ from httoop.exceptions import InvalidHeader -@pytest.mark.parametrize('invalid', (b'content-length', b'Trailer', b'transfer-Encoding')) +@pytest.mark.parametrize('invalid', [b'content-length', b'Trailer', b'transfer-Encoding']) def test_invalid_trailer(invalid, headers): headers.parse(b'Trailer: %s' % (invalid,)) with pytest.raises(InvalidHeader): diff --git a/tests/messaging/test_invalid.py b/tests/messaging/test_invalid.py index e159c16b..445bd3d4 100644 --- a/tests/messaging/test_invalid.py +++ b/tests/messaging/test_invalid.py @@ -3,19 +3,19 @@ from httoop.exceptions import InvalidLine, InvalidURI -@pytest.mark.parametrize('token', (b'GET /', b'GET / foo HTTP/1.1', b'foo bar baz blah')) +@pytest.mark.parametrize('token', [b'GET /', b'GET / foo HTTP/1.1', b'foo bar baz blah']) def test_invalid_request_startline(token, request_): with pytest.raises(InvalidLine): request_.parse(token) -@pytest.mark.parametrize('token', (b'GET // HTTP/1.1', b'GET //example.com/ HTTP/1.1')) +@pytest.mark.parametrize('token', [b'GET // HTTP/1.1', b'GET //example.com/ HTTP/1.1']) def test_invalid_request_uri_startline(token, request_): with pytest.raises(InvalidURI): request_.parse(token) -@pytest.mark.parametrize('token', (b'HTTP/1.1', b'foo', b'FOOO/1.1 200 OK')) +@pytest.mark.parametrize('token', [b'HTTP/1.1', b'foo', b'FOOO/1.1 200 OK']) def test_invalid_response_startline(token, response): with pytest.raises(InvalidLine): response.parse(token) diff --git a/tests/messaging/test_request_codecs.py b/tests/messaging/test_request_codecs.py index 6be856b1..5ba0ea59 100644 --- a/tests/messaging/test_request_codecs.py +++ b/tests/messaging/test_request_codecs.py @@ -72,7 +72,7 @@ def test_compose_multipart_form_data(body): assert sorted(multipart_string.split(b'\r\n')) == sorted(bytes(body).split(b'\r\n')) -@pytest.mark.parametrize('invalid', (b'', b'foo ', b'foo\tbar', b'a' * 202)) +@pytest.mark.parametrize('invalid', [b'', b'foo ', b'foo\tbar', b'a' * 202]) def test_invalid_boundary(invalid, headers): headers.parse(b'Content-Type: multipart/mixed; boundary="%s"' % (invalid,)) with pytest.raises(InvalidHeader): diff --git a/tests/uri/test_uri_abspath.py b/tests/uri/test_uri_abspath.py index 5cb2e73f..0d8ebeec 100644 --- a/tests/uri/test_uri_abspath.py +++ b/tests/uri/test_uri_abspath.py @@ -4,7 +4,7 @@ from httoop import URI -@pytest.mark.parametrize('url,expected', ( +@pytest.mark.parametrize('url,expected', [ (b'http://..', ('http', '', '', '..', 80, '', '', '')), (b'http:///..', ('http', '', '', '', 80, '/', '', '')), (b'http://.', ('http', '', '', '.', 80, '', '', '')), @@ -13,7 +13,7 @@ (b'http:..', ('http', '', '', '', 80, '/', '', '')), (b'http:/', ('http', '', '', '', 80, '/', '', '')), pytest.param(b'http://f/..%2f..', ('http', '', '', 'f', 80, '/', '', ''), marks=pytest.mark.xfail(reason='Incorrect but we want to preserve /.')), -)) +]) def test_abspath(url, expected): uri = URI() uri.parse(url) diff --git a/tests/uri/test_uri_schemes.py b/tests/uri/test_uri_schemes.py index 14aa7490..9ae85ab9 100644 --- a/tests/uri/test_uri_schemes.py +++ b/tests/uri/test_uri_schemes.py @@ -3,7 +3,7 @@ from httoop import URI -@pytest.mark.parametrize('url,expected', ( +@pytest.mark.parametrize('url,expected', [ ('ftp://ftp.is.co.za/rfc/rfc1808.txt', ('ftp', '', '', 'ftp.is.co.za', 21, '/rfc/rfc1808.txt', '', '')), ('http://www.ietf.org/rfc/rfc2396.txt', ('http', '', '', 'www.ietf.org', 80, '/rfc/rfc2396.txt', '', '')), pytest.param('ldap://[2001:db8::7]/c=GB?objectClass?one', ('ldap', '', '', '[2001:db8::7]', 389, '/c=GB', 'objectClass?one', ''), marks=pytest.mark.skipif(True, reason='Parse query in ldap URI?')), @@ -18,7 +18,7 @@ ('nfs://server/path/to/file.txt', ('nfs', '', '', 'server', 2049, '/path/to/file.txt', '', '')), ('svn+ssh://svn.zope.org/repos/main/ZConfig/trunk/', ('svn+ssh', '', '', 'svn.zope.org', 22, '/repos/main/ZConfig/trunk/', '', '')), ('git+ssh://git@github.com/user/project.git', ('git+ssh', 'git', '', 'github.com', 22, '/user/project.git', '', '')), -)) +]) def test_parse_scheme(url, expected): uri = URI(url) assert uri.tuple == expected From 3d0e8b6b31eef075bb75e3e419ee591a7b56463d Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:43:11 +0200 Subject: [PATCH 10/24] style: fix os-path-abspath (PTH100) --- httoop/__main__.py | 3 ++- tests/main.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/httoop/__main__.py b/httoop/__main__.py index 008a7ee2..e2748603 100644 --- a/httoop/__main__.py +++ b/httoop/__main__.py @@ -7,6 +7,7 @@ python3 -m httoop compose response | python3 -m httoop parse response """ +import pathlib import sys from argparse import ArgumentParser, FileType @@ -132,7 +133,7 @@ def common(self) -> None: if body == '-': body = sys.stdin.read() elif body.startswith('@'): - body = open(body[1:], 'rb') + body = pathlib.Path(body[1:]).open('rb') self.message.body = body sys.stdout.write(self.decode(bytes(self.message))) diff --git a/tests/main.py b/tests/main.py index eea496f9..4746e82f 100755 --- a/tests/main.py +++ b/tests/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python +import pathlib import sys -from os.path import abspath, dirname from subprocess import STDOUT, Popen from types import ModuleType @@ -22,7 +22,7 @@ def main(): # cmd.append("--no-cov-on-fail") cmd.append('--cov-report=html') - cmd.append(dirname(abspath(__file__))) + cmd.append(pathlib.Path(pathlib.Path(__file__).resolve()).parent) raise SystemExit(Popen(cmd, stdout=sys.stdout, stderr=STDOUT).wait()) From 981deb23e45eb1da643aa78483b67172696c7d9d Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:44:10 +0200 Subject: [PATCH 11/24] style: fix any-eq-ne-annotation (PYI032) --- httoop/date.py | 2 +- httoop/header/conditional.py | 8 +++----- httoop/header/element.py | 4 ++-- httoop/messages/protocol.py | 3 +-- httoop/meta.py | 4 ++-- httoop/status/status.py | 2 +- httoop/uri/uri.py | 2 +- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/httoop/date.py b/httoop/date.py index b0c7a155..d28af103 100644 --- a/httoop/date.py +++ b/httoop/date.py @@ -153,7 +153,7 @@ def __int__(self) -> int: def __float__(self) -> float: return float(self.__timestamp) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: try: return int(self) == int(self.__other(other)) except NotImplementedError: # pragma: no cover diff --git a/httoop/header/conditional.py b/httoop/header/conditional.py index 74ee6f04..61ae238d 100644 --- a/httoop/header/conditional.py +++ b/httoop/header/conditional.py @@ -1,5 +1,3 @@ -from typing import Any - from httoop.date import Date from httoop.exceptions import InvalidDate from httoop.header.element import HeaderElement @@ -13,7 +11,7 @@ def sanitize(self) -> None: super().sanitize() self.value = self.Date.parse(self.value.encode('ASCII', 'replace')) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: if not isinstance(other, Date): if isinstance(other, _DateComparable): other = int(other) @@ -29,7 +27,7 @@ def __int__(self) -> int: class _MatchElement: - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: return self.value == other or self.value == '*' def matches(self, etag): @@ -57,7 +55,7 @@ class ETag(HeaderElement): is_response_header = True - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: if not isinstance(other, ETag): other = self.__class__(other) return other.value == self.value or other.value == '*' diff --git a/httoop/header/element.py b/httoop/header/element.py index c25c4594..76fd8deb 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -65,10 +65,10 @@ def __lt__(self, other: str) -> bool: def __gt__(self, other: str) -> bool: return self.value > getattr(other, 'value', other) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: return self.value == getattr(other, 'value', other) - def __ne__(self, other: Any) -> bool: + def __ne__(self, other: object) -> bool: return not self == other def __bytes__(self) -> bytes: diff --git a/httoop/messages/protocol.py b/httoop/messages/protocol.py index b7673444..9a4244b6 100644 --- a/httoop/messages/protocol.py +++ b/httoop/messages/protocol.py @@ -7,7 +7,6 @@ from __future__ import annotations import re -from typing import Any from httoop.exceptions import InvalidLine from httoop.meta import HTTPSemantic @@ -67,7 +66,7 @@ def __iter__(self): def __getitem__(self, key: int) -> int: return self.version[key] - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: try: other = Protocol(other) except (TypeError, InvalidLine): diff --git a/httoop/meta.py b/httoop/meta.py index 08e2b532..bbd35540 100644 --- a/httoop/meta.py +++ b/httoop/meta.py @@ -24,12 +24,12 @@ def parse(self, data): # pragma: no cover def compose(self): # pragma: no cover raise NotImplementedError('%s.compose()' % (type(self).__name__,)) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: if isinstance(other, str): return str(self) == other return bytes(self) == other - def __ne__(self, other: Any) -> bool: + def __ne__(self, other: object) -> bool: return not self == other def __ge__(self, other: int | tuple[int, int]) -> bool: diff --git a/httoop/status/status.py b/httoop/status/status.py index 76bc79cf..f7b39f8d 100644 --- a/httoop/status/status.py +++ b/httoop/status/status.py @@ -110,7 +110,7 @@ def __int__(self) -> int: """Returns this status as number.""" return self.__code - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: """Compares a status with another :class:`Status` or :class:`int`.""" if isinstance(other, int): return self.__code == other diff --git a/httoop/uri/uri.py b/httoop/uri/uri.py index 1dcf17e8..ff7519fa 100644 --- a/httoop/uri/uri.py +++ b/httoop/uri/uri.py @@ -336,7 +336,7 @@ def unquote(self, data: bytes) -> str: def quote(self, data: str, charset: bytes) -> bytes: return Percent.quote(str(data).encode(self.encoding), charset) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: """ Compares the URI with another string or URI. From 390e492db5f26aa381aff9466fb78522a6b91c64 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:49:06 +0200 Subject: [PATCH 12/24] style: fix implicit-return-value (RET502) --- httoop/authentication/__init__.py | 1 + httoop/authentication/basic.py | 3 +-- httoop/authentication/digest.py | 12 ++++++------ httoop/gateway/wsgi.py | 7 +++---- httoop/header/element.py | 10 ++++------ httoop/header/messaging.py | 8 ++++++++ httoop/header/security.py | 1 + httoop/messages/body.py | 3 ++- httoop/parser.py | 7 ++++--- httoop/uri/query_string.py | 3 +-- httoop/uri/uri.py | 2 ++ 11 files changed, 33 insertions(+), 24 deletions(-) diff --git a/httoop/authentication/__init__.py b/httoop/authentication/__init__.py index 71d02dc7..f531d71d 100644 --- a/httoop/authentication/__init__.py +++ b/httoop/authentication/__init__.py @@ -89,6 +89,7 @@ def username(self, username) -> None: def password(self): if self.scheme == 'basic': return self.params.get('password').decode(self.encoding) + return None @password.setter def password(self, password) -> None: diff --git a/httoop/authentication/basic.py b/httoop/authentication/basic.py index 7a97040c..76ed19db 100644 --- a/httoop/authentication/basic.py +++ b/httoop/authentication/basic.py @@ -24,13 +24,12 @@ def parse(authinfo: bytes) -> dict[str, bytes]: except ValueError: raise InvalidHeader(_('No username:password provided')) - authinfo = { + return { # 'username': username.decode('ISO8859-1'), # 'password': password.decode('ISO8859-1') 'username': username, 'password': password, } - return authinfo @staticmethod def compose(authinfo: ByteUnicodeDict) -> bytes: diff --git a/httoop/authentication/digest.py b/httoop/authentication/digest.py index ff84405e..964c4cfa 100644 --- a/httoop/authentication/digest.py +++ b/httoop/authentication/digest.py @@ -196,11 +196,11 @@ def A2(cls, params: ByteUnicodeDict) -> bytes: qop = params.get('qop', b'') if not qop or qop == b'auth': return b'%s:%s' % (params['method'], params['uri']) - elif qop == b'auth-int': + if qop == b'auth-int': H = cls.get_algorithm(params['algorithm']) return b'%s:%s:%s' % (params['method'], params['uri'], H(params['entity_body'])) - else: # pragma: no cover - raise NotImplementedError('Unknown quality of protection: %r' % (qop,)) + # pragma: no cover + raise NotImplementedError('Unknown quality of protection: %r' % (qop,)) @classmethod def A1(cls, params: ByteUnicodeDict) -> bytes: @@ -208,9 +208,9 @@ def A1(cls, params: ByteUnicodeDict) -> bytes: if not algorithm or algorithm == b'MD5': return b'%s:%s:%s' % (params['username'], params['realm'], params['password']) - elif algorithm == b'MD5-sess': + if algorithm == b'MD5-sess': H = cls.get_algorithm(algorithm) s = b'%s:%s:%s' % (params['username'], params['realm'], params['password']) return b'%s:%s:%s' % (H(s), params['nonce'], params['cnonce']) - else: # pragma: no cover - raise NotImplementedError('Unknown algorithm: %s' % (algorithm,)) + # pragma: no cover + raise NotImplementedError('Unknown algorithm: %s' % (algorithm,)) diff --git a/httoop/gateway/wsgi.py b/httoop/gateway/wsgi.py index 61a6369d..1a7e8e45 100644 --- a/httoop/gateway/wsgi.py +++ b/httoop/gateway/wsgi.py @@ -66,7 +66,7 @@ def __call__(self, application: Callable) -> Iterator[Any]: def write(data): if not self.headers_set: raise RuntimeError('write() before start_response()') - elif not self.headers_sent: + if not self.headers_sent: self.start_response() self.headers_sent = True return self.response.body.write(data) @@ -105,7 +105,7 @@ def start_response(status, response_headers, exc_info=None): break else: write(b'') # send headers now if body was empty - return + return None def buffered(data): try: @@ -147,8 +147,7 @@ def get_environ(self) -> dict[str, str | tuple[int, int] | WSGIBody | bool | Non 'wsgi.multiprocess': self.multiprocess, 'wsgi.run_once': self.run_once, }) - environ = {key: value.decode('ISO8859-1') if isinstance(value, bytes) else value for key, value in environ.items()} - return environ + return {key: value.decode('ISO8859-1') if isinstance(value, bytes) else value for key, value in environ.items()} def set_environ(self, environ: dict[str, str]) -> None: environ = environ.copy() diff --git a/httoop/header/element.py b/httoop/header/element.py index 76fd8deb..d59238a6 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -216,10 +216,8 @@ def formatparam(cls, param: bytes, value: bytes | str | None = None, quote: bool if quote or cls.RE_TSPECIALS.search(value): value = value.replace(b'\\', b'\\\\').replace(b'"', rb'\"') return b'%s="%s"' % (param, value) - else: - return b'%s=%s' % (param, value) - else: - return param + return b'%s=%s' % (param, value) + return param @classmethod def decode_rfc2047(cls, value: bytes) -> str: @@ -329,6 +327,7 @@ def quality(self) -> float: val = val.value if val: return float(val) + return None def sanitize(self) -> None: super().sanitize() @@ -370,8 +369,7 @@ def __lt__(self, other: str) -> bool: other = _AcceptElement(other) if self.quality == other.quality: return str(self) < str(other) - else: - return self.quality < other.quality + return self.quality < other.quality class _CookieElement(HeaderElement): diff --git a/httoop/header/messaging.py b/httoop/header/messaging.py index 511603dd..31ce0646 100644 --- a/httoop/header/messaging.py +++ b/httoop/header/messaging.py @@ -37,6 +37,7 @@ def codec(self) -> Any: return lookup(encoding.lower()) except KeyError: pass + return None class Accept(_AcceptElement, MimeType): @@ -120,11 +121,13 @@ def inline(self) -> bool: def creation_date(self) -> Date | None: if 'creation-date' in self.params: return self.Date(self.params['creation-date']) + return None @property def modification_date(self) -> Date | None: if 'modification-date' in self.params: return self.Date(self.params['modification-date']) + return None def sanitize(self) -> None: self.value = self.value.lower() @@ -312,6 +315,7 @@ def is_fqdn(self) -> bool: def fqdn(self) -> str | None: if self.is_fqdn: return self.host + return None @property def hostname(self) -> str | None: @@ -321,11 +325,13 @@ def hostname(self) -> str | None: def ip6address(self) -> str | None: if self.is_ip6: return self.host + return None @property def ip4address(self) -> str | None: if self.is_ip4: return self.host + return None def sanitize(self) -> None: self.value = self.value.lower() @@ -408,6 +414,7 @@ def max_age(self) -> None: return integer(self.params['max-age']) except ValueError: raise InvalidHeader(_('Cookie: max-age is not a number: %r'), self.params['max-age']) + return None @property def expires(self) -> Date: @@ -416,6 +423,7 @@ def expires(self) -> Date: return self.Date(self.params['expires']) except InvalidDate: raise InvalidHeader(_('Cookie: expires is not a valid date: %r'), self.params['expires']) + return None class TE(_HopByHopElement, _AcceptElement): diff --git a/httoop/header/security.py b/httoop/header/security.py index 9e33ecb4..e49a0b05 100644 --- a/httoop/header/security.py +++ b/httoop/header/security.py @@ -98,6 +98,7 @@ def allow_from(self) -> list[HTTPS]: from httoop.uri import URI return [URI(uri) for uri in self.params.keys()] + return None class PermittedCrossDomainPolicies(HeaderElement): diff --git a/httoop/messages/body.py b/httoop/messages/body.py index da74e88e..b5cb1052 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -146,6 +146,7 @@ def decode(self, *data) -> Any: if codec: self.data = codec.decode(self.__content_bytes(), self.encoding, self.mimetype) return self.data + return None def compress(self) -> None: """Applies the Content-Encoding codec to the content.""" @@ -251,7 +252,7 @@ def __content_iter(self): def __iterable(self): if self.fileable: return self.__iter_fileable() - elif self.generator: + if self.generator: return self.__iter_generator() return self.fd diff --git a/httoop/parser.py b/httoop/parser.py index f985ec5e..fb62f5fc 100644 --- a/httoop/parser.py +++ b/httoop/parser.py @@ -148,6 +148,7 @@ def parse_headers(self) -> bool | None: headers, self.buffer = self.buffer.split(header_end, 1) self._parse_header(headers) + return None def _parse_single_headers(self) -> None: if self.buffer.endswith(self.line_end): @@ -173,10 +174,9 @@ def parse_body(self) -> bool | None: if self.chunked: return self.parse_chunked_body() - elif self.message_length: + if self.message_length: return self.parse_body_with_message_length() - else: - return False # no message body + return False # no message body def determine_message_length(self) -> None: # RFC 2616 Section 4.4 @@ -211,6 +211,7 @@ def parse_body_with_message_length(self) -> bool | None: if unfinished: # the body is not yet received completely return NOT_RECEIVED_YET + return None def parse_chunked_body(self) -> bool: if self.state['trailer']: diff --git a/httoop/uri/query_string.py b/httoop/uri/query_string.py index aa1cf075..19ff75c6 100644 --- a/httoop/uri/query_string.py +++ b/httoop/uri/query_string.py @@ -17,5 +17,4 @@ class QueryString(FormURLEncoded): def decode(cls, data: bytes, charset: str | None = None) -> tuple[()] | tuple[tuple[str, str]] | tuple[tuple[str, str], tuple[str, str], tuple[str, str]] | tuple[tuple[str, str], tuple[str, str]]: if any(in_table(x) for x in cls.unquote(data, 'ISO8859-1') for in_table in cls.INVALID): raise DecodeError(_('Invalid query string: contains invalid token')) - data = super().decode(data, charset) - return data + return super().decode(data, charset) diff --git a/httoop/uri/uri.py b/httoop/uri/uri.py index ff7519fa..e8603535 100644 --- a/httoop/uri/uri.py +++ b/httoop/uri/uri.py @@ -254,6 +254,7 @@ def parse(self, uri: bytes) -> None: query_string.decode(self.encoding), unquote(fragment) ) + return None def _unquote_host(self, host: bytes) -> str: # IPv6 / IPvFuture @@ -376,6 +377,7 @@ def __setattr__(self, name: str, value: Any) -> None: raise TypeError('%r must be string, not %s' % (name, type(value).__name__)) super().__setattr__(name, value) + return None def __repr__(self) -> str: return '' % bytes(self) From 5eaba748d087d59e6d77df8c6e21f2659b05c6b7 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 00:51:39 +0200 Subject: [PATCH 13/24] ci(ruff): ignore unnecessary-paren-on-raise-exception (RSE102) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 845296ee..32f1e468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,7 @@ ignore = [ "TD001", # invalid-todo-tag "TD002", # missing-todo-author "TD003", # missing-todo-link + "RSE102", # unnecessary-paren-on-raise-exception # "I", "S101", "Q000", "ANN", "ERA", "UP004", "ARG001", "ARG002", ] From 03b77da856bc9c5ebbb03369071b1a621f922b06 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:14:46 +0200 Subject: [PATCH 14/24] style: fix typing-only-first-party-import (TC001) --- httoop/header/range.py | 6 ++++-- httoop/header/security.py | 5 ++++- httoop/parser.py | 8 +++++--- httoop/server/__init__.py | 5 ++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/httoop/header/range.py b/httoop/header/range.py index b17aff55..f5c5327c 100644 --- a/httoop/header/range.py +++ b/httoop/header/range.py @@ -1,14 +1,16 @@ from __future__ import annotations -from io import BytesIO from math import sqrt from os import SEEK_END, SEEK_SET -from typing import Iterator +from typing import Iterator, TYPE_CHECKING from httoop.exceptions import InvalidHeader from httoop.header.element import HeaderElement from httoop.util import _, integer +if TYPE_CHECKING: + from io import BytesIO + __all__ = ('ContentRange', 'IfRange', 'Range') diff --git a/httoop/header/security.py b/httoop/header/security.py index e49a0b05..acc3d8c1 100644 --- a/httoop/header/security.py +++ b/httoop/header/security.py @@ -5,8 +5,11 @@ import re from httoop.header.element import HeaderElement -from httoop.uri.http import HTTPS from httoop.util import integer +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from httoop.uri.http import HTTPS class ContentSecurityPolicy(HeaderElement): diff --git a/httoop/parser.py b/httoop/parser.py index fb62f5fc..deb586e9 100644 --- a/httoop/parser.py +++ b/httoop/parser.py @@ -3,16 +3,18 @@ from __future__ import annotations -from typing import Iterator +from typing import Iterator, TYPE_CHECKING from httoop.exceptions import Invalid, InvalidBody, InvalidHeader, InvalidLine, InvalidURI from httoop.header import Headers from httoop.messages import Message -from httoop.messages.request import Request -from httoop.messages.response import Response from httoop.status import BAD_REQUEST, NOT_IMPLEMENTED from httoop.util import _, integer +if TYPE_CHECKING: + from httoop.messages.response import Response + from httoop.messages.request import Request + CR = b'\r' LF = b'\n' diff --git a/httoop/server/__init__.py b/httoop/server/__init__.py index e3d0fb93..e942f93e 100644 --- a/httoop/server/__init__.py +++ b/httoop/server/__init__.py @@ -1,13 +1,16 @@ from __future__ import annotations import httoop.messages.request -import httoop.messages.response from httoop.exceptions import InvalidURI from httoop.messages import Request, Response from httoop.parser import NOT_RECEIVED_YET, StateMachine from httoop.status import BAD_REQUEST, HTTP_VERSION_NOT_SUPPORTED, LENGTH_REQUIRED, MOVED_PERMANENTLY, SWITCHING_PROTOCOLS, URI_TOO_LONG from httoop.util import _ from httoop.version import ServerHeader, ServerProtocol +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import httoop.messages.response class ServerStateMachine(StateMachine): From 2f271adc661fcaf9164a97ffe2b792ae54811420 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:22:51 +0200 Subject: [PATCH 15/24] style: fix unnecessary-dunder-call (PLC2801) --- httoop/messages/body.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httoop/messages/body.py b/httoop/messages/body.py index b5cb1052..47e8027c 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -197,7 +197,7 @@ def parse(self, data: bytes) -> None: self.write(data) def compose(self) -> bytes: - return b''.join(self.__iter__()) + return b''.join(iter(self)) def close(self) -> None: fileable = self.fileable From c9370544145a0e16b15b213759e7f92654e1f0ed Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:27:15 +0200 Subject: [PATCH 16/24] style: fix collapsible-else-if (PLR5501) --- httoop/header/element.py | 5 ++--- httoop/semantic/response.py | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/httoop/header/element.py b/httoop/header/element.py index d59238a6..24598038 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -114,9 +114,8 @@ def unescape_param(cls, value: bytes) -> tuple[bytes, bool]: quoted = value.startswith(b'"') and value.endswith(b'"') if quoted: value = re.sub(b'\\\\(?!\\\\)', b'', value[1:-1]) - else: - if cls.RE_TSPECIALS.search(value): - raise InvalidHeader(_('Unquoted parameter in %r containing TSPECIALS: %r'), cls.__name__, value) + elif cls.RE_TSPECIALS.search(value): + raise InvalidHeader(_('Unquoted parameter in %r containing TSPECIALS: %r'), cls.__name__, value) return value, quoted @classmethod diff --git a/httoop/semantic/response.py b/httoop/semantic/response.py index c8a37792..45cfe7bb 100644 --- a/httoop/semantic/response.py +++ b/httoop/semantic/response.py @@ -140,9 +140,8 @@ def close(self, close) -> None: if response.protocol >= (1, 1): response.headers['Connection'] = 'close' return - else: - if response.protocol < (1, 1): - response.headers['Connection'] = 'keep-alive' - return + elif response.protocol < (1, 1): + response.headers['Connection'] = 'keep-alive' + return if response.headers.get('Connection') == 'close': response.headers.pop('Connection', None) From b65c60fa0548b9cff241f4f6251781d7fb994a43 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:29:55 +0200 Subject: [PATCH 17/24] style: fix yoda-conditions (SIM300) --- httoop/header/security.py | 2 +- httoop/messages/body.py | 2 +- httoop/parser.py | 2 +- httoop/semantic/request.py | 5 ++--- httoop/util.py | 2 +- tests/messaging/test_body.py | 4 ++-- tests/messaging/test_request_codecs.py | 2 +- tests/test_cli.py | 16 ++++++++-------- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/httoop/header/security.py b/httoop/header/security.py index acc3d8c1..b9f17fd8 100644 --- a/httoop/header/security.py +++ b/httoop/header/security.py @@ -100,7 +100,7 @@ def allow_from(self) -> list[HTTPS]: if self.value.upper() == 'ALLOW-FROM': from httoop.uri import URI - return [URI(uri) for uri in self.params.keys()] + return [URI(uri) for uri in self.params] return None diff --git a/httoop/messages/body.py b/httoop/messages/body.py index 47e8027c..f75bd272 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -98,7 +98,7 @@ def transfer_encoding(self, transfer_encoding) -> None: def chunked(self): if not self.transfer_encoding: return False - return 'chunked' == self.transfer_encoding.value + return self.transfer_encoding.value == 'chunked' @chunked.setter def chunked(self, chunked) -> None: diff --git a/httoop/parser.py b/httoop/parser.py index deb586e9..726e23cb 100644 --- a/httoop/parser.py +++ b/httoop/parser.py @@ -189,7 +189,7 @@ def determine_message_length(self) -> None: if 'Transfer-Encoding' in message.headers and message.protocol >= (1, 1): # chunked transfer in HTTP/1.1 te = message.headers['Transfer-Encoding'].lower() - self.chunked = 'chunked' == te + self.chunked = te == 'chunked' if not self.chunked: raise NOT_IMPLEMENTED('Unknown HTTP/1.1 Transfer-Encoding: %r' % te) else: diff --git a/httoop/semantic/request.py b/httoop/semantic/request.py index 4b206b1b..f1612e45 100644 --- a/httoop/semantic/request.py +++ b/httoop/semantic/request.py @@ -33,9 +33,8 @@ def prepare(self) -> None: if 'Host' not in self.message.headers and self.message.uri.host: self.message.headers['Host'] = self.message.uri.host - if self.message.method in ('PUT', 'POST') and self.message.body: - if 'Date' not in self.message.headers: - self.message.headers['Date'] = bytes(Date()) # RFC 2616 Section 14.18 + if self.message.method in ('PUT', 'POST') and self.message.body and 'Date' not in self.message.headers: + self.message.headers['Date'] = bytes(Date()) # RFC 2616 Section 14.18 if self.message.method == 'TRACE': self.message.headers.pop('Cookie', None) diff --git a/httoop/util.py b/httoop/util.py index 8dfc0f73..7822c437 100644 --- a/httoop/util.py +++ b/httoop/util.py @@ -165,7 +165,7 @@ def get(self, key: bytes | str, default: Any | None = None) -> Any: return dict.get(self, self.formatkey(key), default) def update(self, E: dict[str, str]) -> None: - for key in E.keys(): + for key in E: self[self.formatkey(key)] = self.formatvalue(E[key]) # def setdefault(self, key: str, x: Optional[Union[UserAgent, Server, str, bytes]]=None) -> bytes: diff --git a/tests/messaging/test_body.py b/tests/messaging/test_body.py index 18298f27..40ff17d8 100644 --- a/tests/messaging/test_body.py +++ b/tests/messaging/test_body.py @@ -19,7 +19,7 @@ def content(): request_.body = content() request_.body.chunked = True - assert b'18\r\nLet us test this chunked\r\n1\r\n\n\r\n12\r\nTransfer Encoding.\r\nb\r\nIt is nice \r\n11\r\nand seems to work\r\n0\r\n\r\n' == bytes(request_.body) + assert bytes(request_.body) == b'18\r\nLet us test this chunked\r\n1\r\n\n\r\n12\r\nTransfer Encoding.\r\nb\r\nIt is nice \r\n11\r\nand seems to work\r\n0\r\n\r\n' def test_parse_chunked_body_without_trailer(statemachine): @@ -94,7 +94,7 @@ def content(): request_.headers['foo'] = 'test' request_.headers['Bar'] = 'baz' request_.body.trailer = request_.trailer - assert b'18\r\nLet us test this chunked\r\n1\r\n\n\r\n12\r\nTransfer Encoding.\r\nb\r\nIt is nice \r\n11\r\nand seems to work\r\n0\r\nBar: baz\r\nFoo: test\r\n\r\n' == bytes(request_.body) + assert bytes(request_.body) == b'18\r\nLet us test this chunked\r\n1\r\n\n\r\n12\r\nTransfer Encoding.\r\nb\r\nIt is nice \r\n11\r\nand seems to work\r\n0\r\nBar: baz\r\nFoo: test\r\n\r\n' def test_chunked_body_with_untold_trailer(): diff --git a/tests/messaging/test_request_codecs.py b/tests/messaging/test_request_codecs.py index 5ba0ea59..5d76d488 100644 --- a/tests/messaging/test_request_codecs.py +++ b/tests/messaging/test_request_codecs.py @@ -138,7 +138,7 @@ def test_hal_json(body): assert resource.get_resource('relation') == {'_embedded': {}, '_links': {}} body.encode(resource) - assert {'_embedded': {'relation': [{}], 'orders': [{'status': 'shipped', 'currency': 'USD', 'total': 30.0, '_links': {'basket': {'href': '/baskets/98712'}, 'customer': {'href': '/customers/7809'}, 'self': {'profile': None, 'deprecation': False, 'name': None, 'hreflang': None, 'href': '/orders/123', 'templated': False, 'type': None}}}, {'status': 'processing', 'currency': 'USD', 'total': 20.0, '_links': {'basket': {'href': '/baskets/97213'}, 'customer': {'href': '/customers/12369'}, 'self': {'href': '/orders/124'}}}]}, 'currentlyProcessing': 14, '_links': {'curie': [{'profile': None, 'deprecation': False, 'name': 'acme', 'hreflang': None, 'href': 'http://docs.acme.com/relations/{rel}', 'templated': True, 'type': None}], 'self': {'profile': None, 'deprecation': False, 'name': None, 'hreflang': None, 'href': '/orders', 'templated': False, 'type': None}, 'relation': [{'profile': None, 'deprecation': False, 'name': 'xx', 'hreflang': None, 'href': 'bar', 'templated': False, 'type': None}, {'profile': None, 'deprecation': False, 'name': 'name', 'hreflang': None, 'href': 'foo', 'templated': False, 'type': None}], 'find': [{'href': '/orders{?id}', 'templated': True, 'profile': None, 'deprecation': False, 'hreflang': None, 'type': None, 'name': None}, {'href': 'bar', 'name': 'xx', 'profile': None, 'deprecation': False, 'hreflang': None, 'templated': False, 'type': None}], 'next': {'profile': None, 'deprecation': False, 'name': None, 'hreflang': None, 'href': '/orders?page=2', 'templated': False, 'type': None}}, 'shippedToday': 20} == json.loads(bytes(body).decode('ASCII')) + assert json.loads(bytes(body).decode('ASCII')) == {'_embedded': {'relation': [{}], 'orders': [{'status': 'shipped', 'currency': 'USD', 'total': 30.0, '_links': {'basket': {'href': '/baskets/98712'}, 'customer': {'href': '/customers/7809'}, 'self': {'profile': None, 'deprecation': False, 'name': None, 'hreflang': None, 'href': '/orders/123', 'templated': False, 'type': None}}}, {'status': 'processing', 'currency': 'USD', 'total': 20.0, '_links': {'basket': {'href': '/baskets/97213'}, 'customer': {'href': '/customers/12369'}, 'self': {'href': '/orders/124'}}}]}, 'currentlyProcessing': 14, '_links': {'curie': [{'profile': None, 'deprecation': False, 'name': 'acme', 'hreflang': None, 'href': 'http://docs.acme.com/relations/{rel}', 'templated': True, 'type': None}], 'self': {'profile': None, 'deprecation': False, 'name': None, 'hreflang': None, 'href': '/orders', 'templated': False, 'type': None}, 'relation': [{'profile': None, 'deprecation': False, 'name': 'xx', 'hreflang': None, 'href': 'bar', 'templated': False, 'type': None}, {'profile': None, 'deprecation': False, 'name': 'name', 'hreflang': None, 'href': 'foo', 'templated': False, 'type': None}], 'find': [{'href': '/orders{?id}', 'templated': True, 'profile': None, 'deprecation': False, 'hreflang': None, 'type': None, 'name': None}, {'href': 'bar', 'name': 'xx', 'profile': None, 'deprecation': False, 'hreflang': None, 'templated': False, 'type': None}], 'next': {'profile': None, 'deprecation': False, 'name': None, 'hreflang': None, 'href': '/orders?page=2', 'templated': False, 'type': None}}, 'shippedToday': 20} def check_encoding_dict(body, data): diff --git a/tests/test_cli.py b/tests/test_cli.py index a4f8b426..436d43a5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,21 +5,21 @@ def test_cli_compose(): - assert b'GET / HTTP/1.1\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request']) - assert b'HTTP/1.1 200 OK\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'response']) - assert b'PUT /foo HTTP/1.1\r\nHost: foo\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '-m', 'PUT', '-u', '/foo', '-H', 'Host: foo']) - assert b'HTTP/1.1 400 Evil Request\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'response', '-s', '400', '--reason', 'Evil Request']) - assert b'GET / HTTP/1.0\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', '1.0']) + assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request']) == b'GET / HTTP/1.1\r\n\r\n' + assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'response']) == b'HTTP/1.1 200 OK\r\n\r\n' + assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '-m', 'PUT', '-u', '/foo', '-H', 'Host: foo']) == b'PUT /foo HTTP/1.1\r\nHost: foo\r\n\r\n' + assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'response', '-s', '400', '--reason', 'Evil Request']) == b'HTTP/1.1 400 Evil Request\r\n\r\n' + assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', '1.0']) == b'GET / HTTP/1.0\r\n\r\n' # assert b'GET / HTTP/1.0\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', 'HTTP/1.0']) p = subprocess.Popen([sys.executable, '-m', 'httoop', 'compose', 'request', '-b', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) stdout, stderr = p.communicate(b'test') - assert b'GET / HTTP/1.1\r\n\r\ntest' == stdout + assert stdout == b'GET / HTTP/1.1\r\n\r\ntest' with tempfile.NamedTemporaryFile() as fd: fd.write(b'test') fd.flush() stdout = subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '-b', '@%s' % (fd.name,)]) - assert b'GET / HTTP/1.1\r\n\r\ntest' == stdout + assert stdout == b'GET / HTTP/1.1\r\n\r\ntest' def test_cli_parse(): @@ -35,4 +35,4 @@ def test_cli_parse(): def test_invalid_input(): - assert b'GET / HTTP/1.1\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', '1:0', '-H', 'foo']) + assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', '1:0', '-H', 'foo']) == b'GET / HTTP/1.1\r\n\r\n' From 6cca7f44ff26068970b35dfb5801bc0bdf6b18d3 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:36:32 +0200 Subject: [PATCH 18/24] style: pyupgrade --- httoop/__main__.py | 2 +- httoop/authentication/digest.py | 8 +++----- httoop/codecs/application/gzip.py | 4 ++-- httoop/codecs/application/hal_json.py | 2 +- httoop/codecs/application/xml.py | 2 +- httoop/header/element.py | 16 ++++++++-------- httoop/header/headers.py | 2 +- httoop/header/messaging.py | 10 +++++----- httoop/header/range.py | 3 ++- httoop/header/security.py | 3 ++- httoop/messages/body.py | 2 +- httoop/messages/message.py | 2 +- httoop/messages/request.py | 2 +- httoop/meta.py | 4 ++-- httoop/parser.py | 9 +++++---- httoop/semantic/message.py | 4 +--- httoop/server/__init__.py | 6 ++++-- httoop/status/client_error.py | 2 +- httoop/status/types.py | 4 ++-- httoop/uri/percent_encoding.py | 8 +++----- httoop/uri/uri.py | 16 ++++++++-------- tests/api/test_repr.py | 4 +--- tests/api/test_uri.py | 2 +- tests/headers/test_host_header.py | 6 +++--- tests/headers/test_invalid_headers.py | 5 ++--- tests/headers/test_security_headers.py | 2 -- tests/test_cli.py | 2 +- tests/uri/test_uri_abspath.py | 1 - tests/uri/test_uri_parsing.py | 2 +- tests/uri/test_uri_query.py | 9 ++++----- 30 files changed, 68 insertions(+), 76 deletions(-) diff --git a/httoop/__main__.py b/httoop/__main__.py index e2748603..64089b95 100644 --- a/httoop/__main__.py +++ b/httoop/__main__.py @@ -23,7 +23,7 @@ def __init__(self) -> None: self.message = None self.parser = ArgumentParser(name, description=self.__doc__, epilog='https://github.com/spaceone/httoop/') self.parent_parser = ArgumentParser(add_help=False) - self.parser.add_argument('-v', '--version', action='version', version='%%(prog)s %s' % (version,)) + self.parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {version}') self.add_subparsers() self.parse_arguments() diff --git a/httoop/authentication/digest.py b/httoop/authentication/digest.py index 964c4cfa..3775b117 100644 --- a/httoop/authentication/digest.py +++ b/httoop/authentication/digest.py @@ -187,7 +187,7 @@ def calculate_request_digest(cls, authinfo: ByteUnicodeDict) -> bytes: elif qop is None: data = b'%s:%s' % (authinfo['nonce'], hash_a2) else: # pragma: no cover - raise NotImplementedError('Unknown quality of protection: %r' % (qop,)) + raise NotImplementedError(f'Unknown quality of protection: {qop!r}') return H(b'%s:%s' % (secret, data)) @@ -199,8 +199,7 @@ def A2(cls, params: ByteUnicodeDict) -> bytes: if qop == b'auth-int': H = cls.get_algorithm(params['algorithm']) return b'%s:%s:%s' % (params['method'], params['uri'], H(params['entity_body'])) - # pragma: no cover - raise NotImplementedError('Unknown quality of protection: %r' % (qop,)) + raise NotImplementedError(f'Unknown quality of protection: {qop!r}') # pragma: no cover @classmethod def A1(cls, params: ByteUnicodeDict) -> bytes: @@ -212,5 +211,4 @@ def A1(cls, params: ByteUnicodeDict) -> bytes: H = cls.get_algorithm(algorithm) s = b'%s:%s:%s' % (params['username'], params['realm'], params['password']) return b'%s:%s:%s' % (H(s), params['nonce'], params['cnonce']) - # pragma: no cover - raise NotImplementedError('Unknown algorithm: %s' % (algorithm,)) + raise NotImplementedError(f'Unknown algorithm: {algorithm}') # pragma: no cover diff --git a/httoop/codecs/application/gzip.py b/httoop/codecs/application/gzip.py index 006f7a24..93dc9052 100644 --- a/httoop/codecs/application/gzip.py +++ b/httoop/codecs/application/gzip.py @@ -27,7 +27,7 @@ def decode(cls, data: bytes, charset: None = None, mimetype: None = None) -> str try: with gzip.GzipFile(fileobj=io.BytesIO(data)) as fd: data = fd.read() - except (zlib.error, IOError, EOFError): + except (zlib.error, OSError, EOFError): raise DecodeError(_('Invalid gzip data.')) return Codec.decode(data, charset) @@ -61,5 +61,5 @@ def iterdecode(cls, data, charset=None, mimetype=None): fd.write(part) fd.seek(0) yield Codec.decode(gzfd.read(), charset) - except (zlib.error, IOError, EOFError): + except (zlib.error, OSError, EOFError): raise DecodeError(_('Invalid gzip data.')) diff --git a/httoop/codecs/application/hal_json.py b/httoop/codecs/application/hal_json.py index ccbf482e..bbb9e2ff 100644 --- a/httoop/codecs/application/hal_json.py +++ b/httoop/codecs/application/hal_json.py @@ -11,7 +11,7 @@ # TODO: emit a warning def expand(href: str, templates: dict[str, str]) -> str: for templ, value in templates.items(): - href = href.replace('{%s}' % (templ,), value) + href = href.replace(f'{{{templ}}}', value) return href diff --git a/httoop/codecs/application/xml.py b/httoop/codecs/application/xml.py index 9206de2a..405f047e 100644 --- a/httoop/codecs/application/xml.py +++ b/httoop/codecs/application/xml.py @@ -33,7 +33,7 @@ def decode(cls, data: bytes, charset: str | None = None, mimetype: ContentType | try: return fromstring(data) except ParseError as exc: - raise DecodeError('Could not decode as %s: %s' % (mimetype, exc,)) + raise DecodeError(f'Could not decode as {mimetype}: {exc}') @classmethod def encode(cls, root: Element, charset: str | None = None, mimetype: ContentType | None = None) -> bytes: diff --git a/httoop/header/element.py b/httoop/header/element.py index 24598038..a9b429f0 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -248,8 +248,8 @@ def encode_rfc2047(cls, value: str) -> bytes: return b'=?ISO8859-1?b?%s?=' % (b2a_base64(value.encode('ISO8859-1')).rstrip(b'\n'),) def __repr__(self) -> str: - params = ', %r' % (self.params,) if self.params else '' - return '<%s(%r%s)>' % (self.__class__.__name__, self.value, params) + params = f', {self.params!r}' if self.params else '' + return f'<{self.__class__.__name__}({self.value!r}{params})>' class MimeType: @@ -261,7 +261,7 @@ class MimeType: @property def mimetype(self) -> str: - return '%s/%s' % (self.type, self.subtype_wo_vendor) + return f'{self.type}/{self.subtype_wo_vendor}' @property def type(self): @@ -269,7 +269,7 @@ def type(self): @type.setter def type(self, type_) -> None: - self.value = '%s/%s' % (type_, self.subtype) + self.value = f'{type_}/{self.subtype}' @property def subtype(self): @@ -277,7 +277,7 @@ def subtype(self): @subtype.setter def subtype(self, subtype) -> None: - self.value = '%s/%s' % (self.type, subtype) + self.value = f'{self.type}/{subtype}' # TODO: official name @property @@ -286,7 +286,7 @@ def subtype_wo_vendor(self): @subtype_wo_vendor.setter def subtype_wo_vendor(self, subtype_wo_vendor) -> None: - self.subtype = '%s+%s' % (self.vendor, subtype_wo_vendor) + self.subtype = f'{self.vendor}+{subtype_wo_vendor}' @property def vendor(self): @@ -296,7 +296,7 @@ def vendor(self): @vendor.setter def vendor(self, vendor) -> None: - self.subtype = '%s+%s' % (vendor, self.subtype_wo_vendor) + self.subtype = f'{vendor}+{self.subtype_wo_vendor}' @property def version(self): @@ -397,7 +397,7 @@ def unescape_key(cls, key: bytes) -> bytes: @property def value(self) -> str: - return '%s=%s' % (self.cookie_name, self.cookie_value) + return f'{self.cookie_name}={self.cookie_value}' @value.setter def value(self, value) -> None: diff --git a/httoop/header/headers.py b/httoop/header/headers.py index 2fde7af4..c8ac2e4d 100644 --- a/httoop/header/headers.py +++ b/httoop/header/headers.py @@ -166,4 +166,4 @@ def __encoded_items(self): yield key, values def __repr__(self) -> str: - return '' % repr(list(self.items())) + return f'' diff --git a/httoop/header/messaging.py b/httoop/header/messaging.py index 31ce0646..dd2c6dcd 100644 --- a/httoop/header/messaging.py +++ b/httoop/header/messaging.py @@ -141,7 +141,7 @@ def sanitize(self) -> None: if b'form-data' in self.params: raise InvalidHeader(_('Mixed Content-Disposition')) else: - raise InvalidHeader(_('Unknown Content-Disposition: %r'), self.value,) + raise InvalidHeader(_('Unknown Content-Disposition: %r'), self.value) class ContentEncoding(CodecElement, HeaderElement): @@ -289,22 +289,22 @@ class Host(HeaderElement): @property def is_ip4(self) -> bool: - from socket import AF_INET, error, inet_pton + from socket import AF_INET, inet_pton try: inet_pton(AF_INET, self.host) return True - except error: + except OSError: return False @property def is_ip6(self) -> bool: - from socket import AF_INET6, error, inet_pton + from socket import AF_INET6, inet_pton try: inet_pton(AF_INET6, self.host) return True - except error: + except OSError: return False @property diff --git a/httoop/header/range.py b/httoop/header/range.py index f5c5327c..570c29ab 100644 --- a/httoop/header/range.py +++ b/httoop/header/range.py @@ -2,12 +2,13 @@ from math import sqrt from os import SEEK_END, SEEK_SET -from typing import Iterator, TYPE_CHECKING +from typing import TYPE_CHECKING, Iterator from httoop.exceptions import InvalidHeader from httoop.header.element import HeaderElement from httoop.util import _, integer + if TYPE_CHECKING: from io import BytesIO diff --git a/httoop/header/security.py b/httoop/header/security.py index b9f17fd8..abb5c695 100644 --- a/httoop/header/security.py +++ b/httoop/header/security.py @@ -3,10 +3,11 @@ from __future__ import annotations import re +from typing import TYPE_CHECKING from httoop.header.element import HeaderElement from httoop.util import integer -from typing import TYPE_CHECKING + if TYPE_CHECKING: from httoop.uri.http import HTTPS diff --git a/httoop/messages/body.py b/httoop/messages/body.py index f75bd272..578b9e1d 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -244,7 +244,7 @@ def __content_iter(self): if isinstance(data, str): data = data.encode(self.encoding) elif not isinstance(data, bytes): # pragma: no cover - raise TypeError('Iterable contained non-bytes: %r' % (type(data).__name__,)) + raise TypeError(f'Iterable contained non-bytes: {type(data).__name__!r}') yield data finally: self.seek(t) diff --git a/httoop/messages/message.py b/httoop/messages/message.py index 44a9a9e4..b01086a0 100644 --- a/httoop/messages/message.py +++ b/httoop/messages/message.py @@ -89,4 +89,4 @@ def parse(self, protocol: bytes) -> None: self.protocol.parse(protocol) def __repr__(self) -> str: - return '' % (self.protocol,) + return f'' diff --git a/httoop/messages/request.py b/httoop/messages/request.py index cdb3e472..d0150521 100644 --- a/httoop/messages/request.py +++ b/httoop/messages/request.py @@ -99,4 +99,4 @@ def compose(self) -> bytes: return b'%s %s %s\r\n' % (bytes(self.__method), bytes(self.__uri) or b'/', bytes(self.protocol)) def __repr__(self) -> str: - return '' % (self.__method, self.__uri.path, self.protocol) + return f'' diff --git a/httoop/meta.py b/httoop/meta.py index bbd35540..e7adbc63 100644 --- a/httoop/meta.py +++ b/httoop/meta.py @@ -22,7 +22,7 @@ def parse(self, data): # pragma: no cover raise NotImplementedError('%s.parse(%.5r)' % (type(self).__name__, data)) def compose(self): # pragma: no cover - raise NotImplementedError('%s.compose()' % (type(self).__name__,)) + raise NotImplementedError(f'{type(self).__name__}.compose()') def __eq__(self, other: object) -> bool: if isinstance(other, str): @@ -42,7 +42,7 @@ def __format__(self, format_spec) -> str: return format(str(self), format_spec) def __repr__(self) -> str: - return '' % (self.__class__.__name__, id(self)) + return f'' class HTTPSemantic(type): diff --git a/httoop/parser.py b/httoop/parser.py index 726e23cb..7cca4fec 100644 --- a/httoop/parser.py +++ b/httoop/parser.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Iterator, TYPE_CHECKING +from typing import TYPE_CHECKING, Iterator from httoop.exceptions import Invalid, InvalidBody, InvalidHeader, InvalidLine, InvalidURI from httoop.header import Headers @@ -11,9 +11,10 @@ from httoop.status import BAD_REQUEST, NOT_IMPLEMENTED from httoop.util import _, integer + if TYPE_CHECKING: - from httoop.messages.response import Response from httoop.messages.request import Request + from httoop.messages.response import Response CR = b'\r' @@ -191,7 +192,7 @@ def determine_message_length(self) -> None: te = message.headers['Transfer-Encoding'].lower() self.chunked = te == 'chunked' if not self.chunked: - raise NOT_IMPLEMENTED('Unknown HTTP/1.1 Transfer-Encoding: %r' % te) + raise NOT_IMPLEMENTED(f'Unknown HTTP/1.1 Transfer-Encoding: {te!r}') else: # Content-Length header defines the length of the message body try: @@ -287,7 +288,7 @@ def merge_trailer_into_header(self) -> None: message.headers.append(name, value) if self.trailers: msg_trailers = '" ,"'.join(self.trailers.keys()) - raise BAD_REQUEST('untold trailers: "%s"' % msg_trailers) + raise BAD_REQUEST(f'untold trailers: "{msg_trailers}"') del self.trailers def set_body_content_encoding(self) -> None: diff --git a/httoop/semantic/message.py b/httoop/semantic/message.py index cec7adc7..8a277530 100644 --- a/httoop/semantic/message.py +++ b/httoop/semantic/message.py @@ -1,4 +1,3 @@ - from contextlib import contextmanager from typing import Iterator @@ -54,5 +53,4 @@ def __iter__(self) -> Iterator[bytes]: start_line = bytes(self.message) headers = bytes(self.message.headers) yield start_line + headers - for data in self.message.body: - yield data + yield from self.message.body diff --git a/httoop/server/__init__.py b/httoop/server/__init__.py index e942f93e..b3a5bbe8 100644 --- a/httoop/server/__init__.py +++ b/httoop/server/__init__.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import httoop.messages.request from httoop.exceptions import InvalidURI from httoop.messages import Request, Response @@ -7,7 +9,7 @@ from httoop.status import BAD_REQUEST, HTTP_VERSION_NOT_SUPPORTED, LENGTH_REQUIRED, MOVED_PERMANENTLY, SWITCHING_PROTOCOLS, URI_TOO_LONG from httoop.util import _ from httoop.version import ServerHeader, ServerProtocol -from typing import TYPE_CHECKING + if TYPE_CHECKING: import httoop.messages.response @@ -132,7 +134,7 @@ def check_message_without_body_containing_data(self) -> None: def check_methods_without_body(self) -> None: if self.message.method in ('HEAD', 'GET', 'TRACE') and self.message.body: - raise BAD_REQUEST('A %s request is considered as safe and MUST NOT contain a request body.' % self.message.method) + raise BAD_REQUEST(f'A {self.message.method} request is considered as safe and MUST NOT contain a request body.') def check_http2_upgrade(self) -> None: diff --git a/httoop/status/client_error.py b/httoop/status/client_error.py index 755ed105..f5c5c91a 100644 --- a/httoop/status/client_error.py +++ b/httoop/status/client_error.py @@ -66,7 +66,7 @@ class NOT_FOUND(ClientErrorStatus): def __init__(self, path: str, **kwargs) -> None: self.path = path - kwargs.update({'description': 'The requested resource "%s" was not found on this server.' % (path,)}) + kwargs.update({'description': f'The requested resource "{path}" was not found on this server.'}) super().__init__(**kwargs) diff --git a/httoop/status/types.py b/httoop/status/types.py index 69f3bce5..58f23540 100644 --- a/httoop/status/types.py +++ b/httoop/status/types.py @@ -31,7 +31,7 @@ def __new__(cls: type, name: str, bases: Any, dict_: dict[str, Any]) -> Any: raise RuntimeError('A HTTP Status code can not be greater than 599 or lower than 100') if code and not any(scls == base.__name__ for base in bases): - raise RuntimeError('%s must inherit from %s' % (name, scls)) + raise RuntimeError(f'{name} must inherit from {scls}') reason = REASONS.get(code, ('', '')) dict_.setdefault('reason', reason[0]) @@ -116,7 +116,7 @@ def __init__(self, description: str | None = None, reason: None = None, headers: def __repr__(self) -> str: description = '' if self.description: - description = '(%s)' % (self.description,) + description = f'({self.description})' return '' % (int(self), self.reason, description) __str__ = __repr__ diff --git a/httoop/uri/percent_encoding.py b/httoop/uri/percent_encoding.py index f7655dc2..8482ccc6 100644 --- a/httoop/uri/percent_encoding.py +++ b/httoop/uri/percent_encoding.py @@ -1,7 +1,5 @@ from typing import Iterator -from httoop import six - class Percent: """ @@ -13,7 +11,7 @@ class Percent: b"!#$&'()*+,/:;=?@[]" """ - HEX_MAP = {(a + b).encode('ASCII'): six.int2byte(int(a + b, 16)) for a in '0123456789ABCDEFabcdef' for b in '0123456789ABCDEFabcdef'} + HEX_MAP = {(a + b).encode('ASCII'): bytes((int(a + b, 16),)) for a in '0123456789ABCDEFabcdef' for b in '0123456789ABCDEFabcdef'} # ABNF GEN_DELIMS = b':/?#[]@' @@ -49,6 +47,6 @@ def _decode_iter(cls, data: bytes) -> Iterator[bytes]: @classmethod def quote(cls, data: bytes, charset: bytes = UNRESERVED) -> bytes: - charset = {six.int2byte(c) for c in six.iterbytes(charset)} - {b'%'} - data = (six.int2byte(d) for d in six.iterbytes(data)) + charset = {bytes((c,)) for c in iter(charset)} - {b'%'} + data = (bytes((d,)) for d in iter(data)) return b''.join(b'%%%X' % (ord(d),) if d not in charset else d for d in data) diff --git a/httoop/uri/uri.py b/httoop/uri/uri.py index e8603535..057c9612 100644 --- a/httoop/uri/uri.py +++ b/httoop/uri/uri.py @@ -8,7 +8,7 @@ from __future__ import annotations import re -from socket import AF_INET, AF_INET6, error as SocketError, inet_ntop, inet_pton +from socket import AF_INET, AF_INET6, inet_ntop, inet_pton from typing import TYPE_CHECKING, Any, Iterator from httoop.exceptions import InvalidURI @@ -120,7 +120,7 @@ def normalize(self) -> None: self.abspath() if not self.path.startswith('/') and self.host and self.scheme and self.path: - self.path = '/%s' % (self.path,) + self.path = f'/{self.path}' def abspath(self) -> None: """ @@ -165,7 +165,7 @@ def set(self, uri: Any) -> None: elif isinstance(uri, dict): self.dict = uri else: - raise TypeError('URI must be bytes/str/tuple/dict not %r' % (type(uri).__name__,)) + raise TypeError(f'URI must be bytes/str/tuple/dict not {type(uri).__name__!r}') @property def dict(self): @@ -262,8 +262,8 @@ def _unquote_host(self, host: bytes) -> str: host = host[1:-1] try: host = inet_ntop(AF_INET6, inet_pton(AF_INET6, host.decode('ascii'))) - return '[%s]' % (host,) - except (SocketError, UnicodeDecodeError): + return f'[{host}]' + except (OSError, UnicodeDecodeError): # IPvFuture if host.startswith(b'v') and b'.' in host and host[1:].split(b'.', 1)[0].isdigit(): try: @@ -275,7 +275,7 @@ def _unquote_host(self, host: bytes) -> str: if all(x.isdigit() for x in host.split(b'.')): try: return inet_ntop(AF_INET, inet_pton(AF_INET, host.decode('ascii'))) - except (SocketError, UnicodeDecodeError): + except (OSError, UnicodeDecodeError): raise InvalidURI(_('Invalid IPv4 address in URI.')) if host.strip(Percent.UNRESERVED + Percent.SUB_DELIMS + b'%'): @@ -374,10 +374,10 @@ def __setattr__(self, name: str, value: Any) -> None: if value is None: pass elif not isinstance(value, str): - raise TypeError('%r must be string, not %s' % (name, type(value).__name__)) + raise TypeError(f'{name!r} must be string, not {type(value).__name__}') super().__setattr__(name, value) return None def __repr__(self) -> str: - return '' % bytes(self) + return f'' diff --git a/tests/api/test_repr.py b/tests/api/test_repr.py index 05d8ea92..aac8618f 100644 --- a/tests/api/test_repr.py +++ b/tests/api/test_repr.py @@ -1,5 +1,3 @@ -import six - from httoop import Date from httoop.messages import Message from httoop.meta import HTTPSemantic @@ -19,7 +17,7 @@ def test_repr(request_, response): def test_object_inheritance_removed(): - class Foo(six.with_metaclass(HTTPSemantic, object)): + class Foo(metaclass=HTTPSemantic): pass print(Foo.mro()) diff --git a/tests/api/test_uri.py b/tests/api/test_uri.py index 2eb998b4..c859b6a2 100644 --- a/tests/api/test_uri.py +++ b/tests/api/test_uri.py @@ -36,7 +36,7 @@ def test_set_invalid_uri_nonascii(request_): with pytest.raises(InvalidURI): request_.uri = '/fooäbar'.encode('latin-1') with pytest.raises(InvalidURI): - request_.uri = '/fooäbar'.encode('utf-8') + request_.uri = '/fooäbar'.encode() def test_set_invalid_uri(request_): diff --git a/tests/headers/test_host_header.py b/tests/headers/test_host_header.py index cfe1a7e6..acdfe924 100644 --- a/tests/headers/test_host_header.py +++ b/tests/headers/test_host_header.py @@ -63,14 +63,14 @@ def _test_iter(header, host, port, headers): ] )) def test_invalid_host_header(invalid, headers): - headers['Host'] = 'foo%sbar' % (invalid,) + headers['Host'] = f'foo{invalid}bar' with pytest.raises(InvalidHeader): headers.element('Host') @pytest.mark.parametrize('forwarded,expected', [ - (b'for=192.0.2.43', [('192.0.2.43',), ]), - (b'for=192.0.2.43; by=127.0.0.1', [('192.0.2.43', '127.0.0.1'), ]), + (b'for=192.0.2.43', [('192.0.2.43',)]), + (b'for=192.0.2.43; by=127.0.0.1', [('192.0.2.43', '127.0.0.1')]), (b'for=192.0.2.43, for="[2001:db8:cafe::17]"', [('192.0.2.43',), ('[2001:db8:cafe::17]',)]), (b'for=192.0.2.43, FOR=198.51.100.17; by=203.0.113.60; proto=http; host=example.com', [('192.0.2.43',), ('198.51.100.17', '203.0.113.60', 'http', 'example.com')]), (b'fOr=192.0.2.43, for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com', [('192.0.2.43',), ('198.51.100.17', '203.0.113.60', 'http', 'example.com')]), diff --git a/tests/headers/test_invalid_headers.py b/tests/headers/test_invalid_headers.py index 45886339..13d91c7d 100644 --- a/tests/headers/test_invalid_headers.py +++ b/tests/headers/test_invalid_headers.py @@ -1,6 +1,5 @@ import pytest -from httoop import six from httoop.exceptions import InvalidHeader @@ -8,10 +7,10 @@ INVALID_HEADER_FIELD_NAMES = bytes(bytearray(range(0x1F + 1))) + b'()<>@,;\\\\"/\\[\\]?={} \t' -@pytest.mark.parametrize('invalid', six.iterbytes(INVALID_HEADER_FIELD_NAMES + LATIN_CHARS)) +@pytest.mark.parametrize('invalid', iter(INVALID_HEADER_FIELD_NAMES + LATIN_CHARS)) def test_parse_invalid_characters(invalid, request_): with pytest.raises(InvalidHeader): - invalid = b'foo%sbaz: blub' % (six.int2byte(invalid),) + invalid = b'foo%sbaz: blub' % (bytes((invalid,)),) request_.headers.parse(invalid) diff --git a/tests/headers/test_security_headers.py b/tests/headers/test_security_headers.py index 4a493b4f..fc1059dc 100644 --- a/tests/headers/test_security_headers.py +++ b/tests/headers/test_security_headers.py @@ -1,5 +1,3 @@ - - def test_content_security_policy(headers): headers.parse(b"Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; media-src 'self'; frame-src 'self'; font-src 'none'; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; report-uri /csp-violation;") assert bytes(headers.get_element('Content-Security-Policy', 'media-src')) == b"media-src 'self'; " diff --git a/tests/test_cli.py b/tests/test_cli.py index 436d43a5..a9b04b39 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -18,7 +18,7 @@ def test_cli_compose(): with tempfile.NamedTemporaryFile() as fd: fd.write(b'test') fd.flush() - stdout = subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '-b', '@%s' % (fd.name,)]) + stdout = subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '-b', f'@{fd.name}']) assert stdout == b'GET / HTTP/1.1\r\n\r\ntest' diff --git a/tests/uri/test_uri_abspath.py b/tests/uri/test_uri_abspath.py index 0d8ebeec..b73e7c1b 100644 --- a/tests/uri/test_uri_abspath.py +++ b/tests/uri/test_uri_abspath.py @@ -1,4 +1,3 @@ - import pytest from httoop import URI diff --git a/tests/uri/test_uri_parsing.py b/tests/uri/test_uri_parsing.py index f762d85c..61ce68ce 100644 --- a/tests/uri/test_uri_parsing.py +++ b/tests/uri/test_uri_parsing.py @@ -128,7 +128,7 @@ def test_parse_invalid_netloc(url): URI(url) -@pytest.mark.parametrize('u', [b'Python', b'./Python', b'x-newscheme://foo.com/stuff', b'x://y', b'x:/y', b'x:/', b'/', ]) +@pytest.mark.parametrize('u', [b'Python', b'./Python', b'x-newscheme://foo.com/stuff', b'x://y', b'x:/y', b'x:/', b'/']) def test_unparse_parse(u): assert bytes(URI(u)) == u diff --git a/tests/uri/test_uri_query.py b/tests/uri/test_uri_query.py index 738d2df6..cdb6264f 100644 --- a/tests/uri/test_uri_query.py +++ b/tests/uri/test_uri_query.py @@ -1,4 +1,3 @@ - import pytest from httoop import URI, InvalidURI @@ -47,10 +46,10 @@ def test_query_string_compose(query_string, query): @pytest.mark.xfail(reason='API not yet implemented.') @pytest.mark.parametrize('query_string,encoding,query', [ - (b'key=\u0141%E9', 'latin-1', [('key', '\u0141\xE9')]), - (b'key=\u0141%C3%A9', 'utf-8', [('key', '\u0141\xE9')]), - (b'key=\u0141%C3%A9', 'ascii', [('key', '\u0141\ufffd\ufffd')]), - (b'key=\u0141%E9-', 'ascii', [('key', '\u0141\ufffd-')]), + (br'key=\u0141%E9', 'latin-1', [('key', '\u0141\xE9')]), + (br'key=\u0141%C3%A9', 'utf-8', [('key', '\u0141\xE9')]), + (br'key=\u0141%C3%A9', 'ascii', [('key', '\u0141\ufffd\ufffd')]), + (br'key=\u0141%E9-', 'ascii', [('key', '\u0141\ufffd-')]), ]) def test_parse_encodings(query_string, encoding, query): u = URI() From ecf11c28b91f4a1f165aeeef51ebf40ba38f4847 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:45:02 +0200 Subject: [PATCH 19/24] style: noqa SIM --- httoop/authentication/digest.py | 2 +- tests/api/test_date.py | 4 ++-- tests/api/test_protocol.py | 2 +- tests/api/test_status.py | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/httoop/authentication/digest.py b/httoop/authentication/digest.py index 3775b117..45282720 100644 --- a/httoop/authentication/digest.py +++ b/httoop/authentication/digest.py @@ -175,7 +175,7 @@ def calculate_request_digest(cls, authinfo: ByteUnicodeDict) -> bytes: algorithm = authinfo.get('algorithm', b'MD5').decode('ASCII', 'replace') H = cls.get_algorithm(algorithm) - if algorithm == 'MD5-sess' and authinfo.get('A1'): + if algorithm == 'MD5-sess' and authinfo.get('A1'): # noqa: SIM108 secret = H(authinfo['A1']) else: secret = H(cls.A1(authinfo)) diff --git a/tests/api/test_date.py b/tests/api/test_date.py index be797bfa..0350800d 100644 --- a/tests/api/test_date.py +++ b/tests/api/test_date.py @@ -58,7 +58,7 @@ def test_date_gmtime(date, expected, lt, gt): def test_date_comparing_none(): d = Date(datetime.datetime(1994, 11, 6, 8, 49, 37)) assert d == Date(d) - assert not d == None # noqa: E711 + assert not d == None # noqa: E711, SIM201 assert d > None assert not d < None @@ -73,7 +73,7 @@ def test_invalid_date(invalid): d = Date(784111777.0) assert d != invalid - assert not d == invalid + assert not d == invalid # noqa: SIM201 assert d > invalid assert not d < invalid diff --git a/tests/api/test_protocol.py b/tests/api/test_protocol.py index 3d8b9df5..1543113a 100644 --- a/tests/api/test_protocol.py +++ b/tests/api/test_protocol.py @@ -61,4 +61,4 @@ def test_protocol_comparision(request_): assert request_.protocol >= (1, 1) assert request_.protocol <= (1, 2) assert request_.protocol <= (2, 1) - assert not (request_.protocol != 1) + assert not (request_.protocol != 1) # noqa: SIM202 diff --git a/tests/api/test_status.py b/tests/api/test_status.py index eb64138a..42264b8e 100644 --- a/tests/api/test_status.py +++ b/tests/api/test_status.py @@ -27,17 +27,17 @@ def test_staus_comparision(response): assert response.status > 203 assert response.status < 205 - assert not response.status != 204 - assert not response.status == 200 + assert not response.status != 204 # noqa: SIM202 + assert not response.status == 200 # noqa: SIM201 assert not response.status <= 203 assert not response.status >= 205 assert not response.status < 203 assert not response.status > 205 assert response.status != 'foo' - assert not response.status == 'foo' + assert not response.status == 'foo' # noqa: SIM201 assert response.status != b'foo' - assert not response.status == b'foo' + assert not response.status == b'foo' # noqa: SIM201 assert response.status == b'204 No Content' @@ -53,8 +53,8 @@ def test_status_with_status_comparisions(response): assert response.status > Status(203) assert response.status < Status(205) - assert not response.status != Status(204) - assert not response.status == Status(200) + assert not response.status != Status(204) # noqa: SIM202 + assert not response.status == Status(200) # noqa: SIM201 assert not response.status <= Status(203) assert not response.status >= Status(205) assert not response.status < Status(203) From 041f0d7149bc7fe0d01f45d845f68974ff058942 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:47:20 +0200 Subject: [PATCH 20/24] style: fix unraw-re-pattern (RUF039) --- httoop/header/element.py | 6 +++--- httoop/header/messaging.py | 6 +++--- httoop/header/security.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/httoop/header/element.py b/httoop/header/element.py index a9b429f0..4a000515 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -48,8 +48,8 @@ class HeaderElement(with_metaclass(HeaderType)): # Regular expression that matches `special' characters in parameters, the # existence of which force quoting of the parameter value. RE_TSPECIALS = re.compile(b'[ \\(\\)<>@,;:\\\\"/\\[\\]\\?=]') - RE_SPLIT = re.compile(b',(?=(?:[^"]*"[^"]*")*[^"]*$)') - RE_PARAMS = re.compile(b';(?=(?:[^"]*"[^"]*")*[^"]*$)') + RE_SPLIT = re.compile(rb',(?=(?:[^"]*"[^"]*")*[^"]*$)') + RE_PARAMS = re.compile(rb';(?=(?:[^"]*"[^"]*")*[^"]*$)') def __init__(self, value: str, params: Any | None = None) -> None: self.value = value @@ -374,7 +374,7 @@ def __lt__(self, other: str) -> bool: class _CookieElement(HeaderElement): # RE_TSPECIALS = re.compile(br'[ \(\)<>@,;:\\"\[\]\?=]') - RE_TSPECIALS = re.compile(b'(?!)') + RE_TSPECIALS = re.compile(rb'(?!)') def __init__(self, cookie_name: str, cookie_value: str, params: dict[bytes, str] | None = None) -> None: self.cookie_name = cookie_name diff --git a/httoop/header/messaging.py b/httoop/header/messaging.py index dd2c6dcd..622115de 100644 --- a/httoop/header/messaging.py +++ b/httoop/header/messaging.py @@ -201,7 +201,7 @@ def charset(self): def charset(self, charset) -> None: self.params['charset'] = charset - VALID_BOUNDARY = re.compile('^[ -~]{0,200}[!-~]$') + VALID_BOUNDARY = re.compile(r'^[ -~]{0,200}[!-~]$') def sanitize(self) -> None: super().sanitize() @@ -225,7 +225,7 @@ def boundary(self, boundary) -> None: class Cookie(_CookieElement): is_request_header = True - RE_SPLIT = re.compile(b'; ') + RE_SPLIT = re.compile(rb'; ') # TODO: prohibit that multiple Cookie lines are parsed as valid @@ -384,7 +384,7 @@ class SetCookie(_ListElement, _CookieElement): @classmethod def split(cls, fieldvalue: bytes) -> list[bytes]: - fieldvalue = re.sub(b'(expires)=([^"][^;]+)', b'\\1="\\2"', fieldvalue, flags=re.IGNORECASE) + fieldvalue = re.sub(rb'(expires)=([^"][^;]+)', b'\\1="\\2"', fieldvalue, flags=re.IGNORECASE) return super().split(fieldvalue) @property diff --git a/httoop/header/security.py b/httoop/header/security.py index abb5c695..cf30c912 100644 --- a/httoop/header/security.py +++ b/httoop/header/security.py @@ -25,7 +25,7 @@ class ContentSecurityPolicy(HeaderElement): __name__ = 'Content-Security-Policy' is_response_header = True - RE_SPLIT = re.compile(b';') + RE_SPLIT = re.compile(rb';') RE_PARAMS = re.compile(b'\\s+') def compose(self) -> bytes: From 1138c4671b725c1429c7e28ddac2bd5f3b5053f1 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:51:49 +0200 Subject: [PATCH 21/24] style: fix unsorted-dunder-all (RUF022) --- httoop/__init__.py | 29 +++++++++------------------ httoop/codecs/application/__init__.py | 2 +- httoop/codecs/multipart/__init__.py | 2 +- httoop/codecs/text/__init__.py | 2 +- httoop/date.py | 2 +- httoop/messages/__init__.py | 2 +- httoop/messages/body.py | 2 +- httoop/messages/message.py | 2 +- httoop/messages/protocol.py | 2 +- httoop/messages/request.py | 2 +- httoop/messages/response.py | 2 +- httoop/six.py | 2 +- httoop/status/__init__.py | 2 +- httoop/uri/__init__.py | 2 +- httoop/uri/uri.py | 2 +- httoop/util.py | 2 +- 16 files changed, 25 insertions(+), 34 deletions(-) diff --git a/httoop/__init__.py b/httoop/__init__.py index 9e26ecbc..95e01b03 100644 --- a/httoop/__init__.py +++ b/httoop/__init__.py @@ -64,23 +64,14 @@ __all__ = [ - 'Status', 'Body', 'Headers', 'URI', 'Method', - 'Request', 'Response', 'Protocol', 'Date', 'ServerStateMachine', 'ClientStateMachine', - 'ProxyStateMachine', 'InvalidLine', 'InvalidHeader', 'InvalidURI', 'InvalidBody', 'InvalidDate', - 'CONTINUE', 'SWITCHING_PROTOCOLS', 'OK', 'CREATED', 'ACCEPTED', - 'NON_AUTHORITATIVE_INFORMATION', 'NO_CONTENT', 'RESET_CONTENT', - 'PARTIAL_CONTENT', 'MULTIPLE_CHOICES', 'MOVED_PERMANENTLY', 'FOUND', - 'SEE_OTHER', 'NOT_MODIFIED', 'USE_PROXY', 'TEMPORARY_REDIRECT', - 'BAD_REQUEST', 'UNAUTHORIZED', 'PAYMENT_REQUIRED', 'FORBIDDEN', - 'NOT_FOUND', 'METHOD_NOT_ALLOWED', 'NOT_ACCEPTABLE', 'GONE', - 'PROXY_AUTHENTICATION_REQUIRED', 'REQUEST_TIMEOUT', 'CONFLICT', - 'LENGTH_REQUIRED', 'PRECONDITION_FAILED', 'PAYLOAD_TOO_LARGE', - 'URI_TOO_LONG', 'UNSUPPORTED_MEDIA_TYPE', 'BAD_GATEWAY', - 'RANGE_NOT_SATISFIABLE', 'EXPECTATION_FAILED', - 'I_AM_A_TEAPOT', 'INTERNAL_SERVER_ERROR', 'NOT_IMPLEMENTED', - 'SERVICE_UNAVAILABLE', 'GATEWAY_TIMEOUT', 'UNPROCESSABLE_ENTITY', - 'HTTP_VERSION_NOT_SUPPORTED', 'StatusException', - 'DecodeError', 'EncodeError', - '__version__', 'ServerProtocol', 'UserAgentHeader', 'ServerHeader', - 'cache', 'ComposedRequest', 'ComposedResponse', + 'ACCEPTED', 'BAD_GATEWAY', 'BAD_REQUEST', 'CONFLICT', 'CONTINUE', 'CREATED', 'EXPECTATION_FAILED', 'FORBIDDEN', 'FOUND', 'GATEWAY_TIMEOUT', + 'GONE', 'HTTP_VERSION_NOT_SUPPORTED', 'INTERNAL_SERVER_ERROR', 'I_AM_A_TEAPOT', 'LENGTH_REQUIRED', 'METHOD_NOT_ALLOWED', 'MOVED_PERMANENTLY', + 'MULTIPLE_CHOICES', 'NON_AUTHORITATIVE_INFORMATION', 'NOT_ACCEPTABLE', 'NOT_FOUND', 'NOT_IMPLEMENTED', 'NOT_MODIFIED', 'NO_CONTENT', 'OK', + 'PARTIAL_CONTENT', 'PAYLOAD_TOO_LARGE', 'PAYMENT_REQUIRED', 'PRECONDITION_FAILED', 'PROXY_AUTHENTICATION_REQUIRED', 'RANGE_NOT_SATISFIABLE', + 'REQUEST_TIMEOUT', 'RESET_CONTENT', 'SEE_OTHER', 'SERVICE_UNAVAILABLE', 'SWITCHING_PROTOCOLS', 'TEMPORARY_REDIRECT', 'UNAUTHORIZED', + 'UNPROCESSABLE_ENTITY', 'UNSUPPORTED_MEDIA_TYPE', 'URI', 'URI_TOO_LONG', 'USE_PROXY', + 'Body', 'ClientStateMachine', 'ComposedRequest', 'ComposedResponse', 'Date', 'DecodeError', 'EncodeError', 'Headers', + 'InvalidBody', 'InvalidDate', 'InvalidHeader', 'InvalidLine', 'InvalidURI', + 'Method', 'Protocol', 'ProxyStateMachine', 'Request', 'Response', 'ServerHeader', 'ServerProtocol', 'ServerStateMachine', 'Status', + 'StatusException', 'UserAgentHeader', '__version__', 'cache', ] diff --git a/httoop/codecs/application/__init__.py b/httoop/codecs/application/__init__.py index 32262f18..6515838f 100644 --- a/httoop/codecs/application/__init__.py +++ b/httoop/codecs/application/__init__.py @@ -6,4 +6,4 @@ from httoop.codecs.application.zlib import Deflate -__all__ = ['FormURLEncoded', 'JSON', 'GZip', 'Deflate', 'XML', 'HAL'] +__all__ = ['HAL', 'JSON', 'XML', 'Deflate', 'FormURLEncoded', 'GZip'] diff --git a/httoop/codecs/multipart/__init__.py b/httoop/codecs/multipart/__init__.py index d54176b3..0229aeb1 100644 --- a/httoop/codecs/multipart/__init__.py +++ b/httoop/codecs/multipart/__init__.py @@ -1,7 +1,7 @@ from httoop.codecs.multipart.multipart import Multipart -__all__ = ['MultipartMixed', 'MultipartMixedReplace', 'MultipartFormData', 'MultipartAlternative', 'MultipartDigest', 'MultipartParallel', 'MultipartByteranges'] +__all__ = ['MultipartAlternative', 'MultipartByteranges', 'MultipartDigest', 'MultipartFormData', 'MultipartMixed', 'MultipartMixedReplace', 'MultipartParallel'] class MultipartMixed(Multipart): diff --git a/httoop/codecs/text/__init__.py b/httoop/codecs/text/__init__.py index a67acfc5..672737be 100644 --- a/httoop/codecs/text/__init__.py +++ b/httoop/codecs/text/__init__.py @@ -2,4 +2,4 @@ from httoop.codecs.text.plain import PlainText -__all__ = ['PlainText', 'HTML'] +__all__ = ['HTML', 'PlainText'] diff --git a/httoop/date.py b/httoop/date.py index d28af103..1d9b83fc 100644 --- a/httoop/date.py +++ b/httoop/date.py @@ -41,7 +41,7 @@ class Date(with_metaclass(HTTPSemantic)): Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format """ - __slots__ = ('__composed', '__timestamp', '__datetime', '__time_struct') + __slots__ = ('__composed', '__datetime', '__time_struct', '__timestamp') def __init__(self, timeval: Any | None = None) -> None: """ diff --git a/httoop/messages/__init__.py b/httoop/messages/__init__.py index beed81e8..0d11ff3e 100644 --- a/httoop/messages/__init__.py +++ b/httoop/messages/__init__.py @@ -12,4 +12,4 @@ from httoop.messages.response import Response -__all__ = ['Message', 'Request', 'Response', 'Protocol', 'Body', 'Method'] +__all__ = ['Body', 'Message', 'Method', 'Protocol', 'Request', 'Response'] diff --git a/httoop/messages/body.py b/httoop/messages/body.py index 578b9e1d..37dea17f 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -36,7 +36,7 @@ class Body(with_metaclass(HTTPSemantic, IFile)): the content using the codec specified in the MIME media type. """ - __slots__ = ('fd', 'data', '__iter', 'headers', 'trailer', 'content_codec', 'transfer_codec') + __slots__ = ('__iter', 'content_codec', 'data', 'fd', 'headers', 'trailer', 'transfer_codec') MAX_CHUNK_SIZE = 4096 diff --git a/httoop/messages/message.py b/httoop/messages/message.py index b01086a0..122f9e8a 100644 --- a/httoop/messages/message.py +++ b/httoop/messages/message.py @@ -22,7 +22,7 @@ class Message(with_metaclass(HTTPSemantic)): .. seealso:: :rfc:`2616#section-4` """ - __slots__ = ('__protocol', '__headers', '__body') + __slots__ = ('__body', '__headers', '__protocol') @property def protocol(self): diff --git a/httoop/messages/protocol.py b/httoop/messages/protocol.py index 9a4244b6..973aa055 100644 --- a/httoop/messages/protocol.py +++ b/httoop/messages/protocol.py @@ -20,7 +20,7 @@ class Protocol(with_metaclass(HTTPSemantic)): """The HTTP protocol version.""" - __slots__ = ('name', '__protocol') + __slots__ = ('__protocol', 'name') @property def version(self) -> tuple[int, int]: diff --git a/httoop/messages/request.py b/httoop/messages/request.py index d0150521..a8ebc5cd 100644 --- a/httoop/messages/request.py +++ b/httoop/messages/request.py @@ -21,7 +21,7 @@ class Request(Message): .. seealso:: :rfc:`2616#section-5` """ - __slots__ = ('__protocol', '__headers', '__body', '__uri', '__method') + __slots__ = ('__body', '__headers', '__method', '__protocol', '__uri') @property def method(self): diff --git a/httoop/messages/response.py b/httoop/messages/response.py index df193fe0..38b25cbc 100644 --- a/httoop/messages/response.py +++ b/httoop/messages/response.py @@ -20,7 +20,7 @@ class Response(Message): .. seealso:: :rfc:`2616#section-6` """ - __slots__ = ('__protocol', '__headers', '__body', '__status') + __slots__ = ('__body', '__headers', '__protocol', '__status') @property def status(self): diff --git a/httoop/six.py b/httoop/six.py index 613403fe..fee5f280 100644 --- a/httoop/six.py +++ b/httoop/six.py @@ -1,4 +1,4 @@ from six import int2byte, iterbytes, reraise, string_types, with_metaclass -__all__ = ('with_metaclass', 'reraise', 'string_types', 'iterbytes', 'int2byte') +__all__ = ('int2byte', 'iterbytes', 'reraise', 'string_types', 'with_metaclass') diff --git a/httoop/status/__init__.py b/httoop/status/__init__.py index ac007d5b..f9364a81 100644 --- a/httoop/status/__init__.py +++ b/httoop/status/__init__.py @@ -12,7 +12,7 @@ from httoop.status.types import StatusException, StatusType -__all__ = ['Status', 'REASONS', 'StatusType', 'StatusException'] +__all__ = ['REASONS', 'Status', 'StatusException', 'StatusType'] # mapping of status -> Class STATUSES = {} diff --git a/httoop/uri/__init__.py b/httoop/uri/__init__.py index 55f88d90..7bc475f6 100644 --- a/httoop/uri/__init__.py +++ b/httoop/uri/__init__.py @@ -9,4 +9,4 @@ from httoop.uri.uri import URI -__all__ = ('URI', 'HTTP', 'HTTPS', 'GitSSH') +__all__ = ('HTTP', 'HTTPS', 'URI', 'GitSSH') diff --git a/httoop/uri/uri.py b/httoop/uri/uri.py index 057c9612..b14bc263 100644 --- a/httoop/uri/uri.py +++ b/httoop/uri/uri.py @@ -26,7 +26,7 @@ class URI(with_metaclass(URIType)): """Uniform Resource Identifier.""" - __slots__ = ('scheme', 'username', 'password', 'host', '_port', 'path', 'query_string', 'fragment') + __slots__ = ('scheme', 'username', 'password', 'host', '_port', 'path', 'query_string', 'fragment') # noqa: RUF023 slots = __slots__ SCHEMES = {} diff --git a/httoop/util.py b/httoop/util.py index 7822c437..0bb83e10 100644 --- a/httoop/util.py +++ b/httoop/util.py @@ -16,7 +16,7 @@ def _(x: str) -> str: return x -__all__ = ['to_unicode', 'IFile', 'CaseInsensitiveDict', 'sanitize_encoding', '_', 'integer'] +__all__ = ['CaseInsensitiveDict', 'IFile', '_', 'integer', 'sanitize_encoding', 'to_unicode'] KNOWN_ENCODINGS = { 'cp1254', 'cp949', 'cp865', 'cp1257', 'euc_jp', 'cp1250', 'mac-cyrillic', 'mac-latin2', 'cp866', 'cp857', From 09ff5df2539ee40476d31c8cf2c191cf813a4ca6 Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:55:12 +0200 Subject: [PATCH 22/24] style: fix unused-unpacked-variable (RUF059) --- httoop/authentication/__init__.py | 2 +- httoop/codecs/application/hal_json.py | 2 +- httoop/header/element.py | 4 ++-- httoop/header/headers.py | 2 +- httoop/header/range.py | 2 +- httoop/parser.py | 8 ++++---- httoop/semantic/response.py | 2 +- httoop/uri/uri.py | 2 +- httoop/util.py | 2 +- tests/test_cli.py | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/httoop/authentication/__init__.py b/httoop/authentication/__init__.py index f531d71d..3ccc25fa 100644 --- a/httoop/authentication/__init__.py +++ b/httoop/authentication/__init__.py @@ -61,7 +61,7 @@ def compose(self) -> bytes: def split(cls, value: bytes) -> list[bytes | Any]: value = cls.RE_SPACE_SPLIT.split(value) indexes = [i for i, val in enumerate(value) if val != b',' and b'=' not in val] - return [b' '.join(value[a:b]) for a, b in zip(indexes, indexes[1:] + [None])] + return [b' '.join(value[a:b]) for a, b in zip(indexes, [*indexes[1:], None])] class AuthRequestElement(AuthElement): diff --git a/httoop/codecs/application/hal_json.py b/httoop/codecs/application/hal_json.py index bbb9e2ff..aef742cf 100644 --- a/httoop/codecs/application/hal_json.py +++ b/httoop/codecs/application/hal_json.py @@ -40,7 +40,7 @@ def __init__(self, *args, **kwargs) -> None: def self(self) -> str: return self.expand(self.get_link('self')['href']) - def get_links(self, relation: str, name: None = None) -> Iterator[dict[str, None | bool | str]]: + def get_links(self, relation: str, name: None = None) -> Iterator[dict[str, bool | str | None]]: links = self['_links'].get(relation) if links is None: return diff --git a/httoop/header/element.py b/httoop/header/element.py index 4a000515..d751585f 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -135,7 +135,7 @@ def _rfc2231_and_continuation_params(cls, params: Iterator[Any]) -> Iterator[tup count.add(key) if b'*' in key: if key.endswith(b'*') and not quoted and not value.startswith(b"'") and value.count(b"'") >= 2: - charset, language, value_ = value.split(b"'", 2) + charset, _language, value_ = value.split(b"'", 2) encoding = cls._sanitize_encoding(charset.decode('ASCII', 'replace')) try: key, value = key[:-1], Percent.unquote(value_).decode(encoding) @@ -273,7 +273,7 @@ def type(self, type_) -> None: @property def subtype(self): - return (self.value.split('/', 1) + [''])[1] + return ([*self.value.split('/', 1), ''])[1] @subtype.setter def subtype(self, subtype) -> None: diff --git a/httoop/header/headers.py b/httoop/header/headers.py index c8ac2e4d..55e20899 100644 --- a/httoop/header/headers.py +++ b/httoop/header/headers.py @@ -166,4 +166,4 @@ def __encoded_items(self): yield key, values def __repr__(self) -> str: - return f'' + return f'' diff --git a/httoop/header/range.py b/httoop/header/range.py index 570c29ab..843182bc 100644 --- a/httoop/header/range.py +++ b/httoop/header/range.py @@ -91,7 +91,7 @@ def parse(cls, elementstr: bytes) -> Range: try: start = integer(start) if start else None stop = integer(stop) if stop else None - if start and start < 0 or stop and stop < 0: + if (start and start < 0) or (stop and stop < 0): raise ValueError() except ValueError: raise InvalidHeader(_('no range number.')) diff --git a/httoop/parser.py b/httoop/parser.py index 7cca4fec..5ab8820c 100644 --- a/httoop/parser.py +++ b/httoop/parser.py @@ -97,7 +97,7 @@ def parse(self, data: bytes) -> tuple[tuple[Request, Response]] | tuple[tuple[Re except (InvalidHeader, InvalidLine, InvalidURI, InvalidBody) as exc: raise BAD_REQUEST(str(exc)) - def _parse(self) -> Iterator[None | Response | tuple[Request, Response]]: + def _parse(self) -> Iterator[Response | tuple[Request, Response] | None]: while self.buffer: if self.message is None: yield self.on_message_started() @@ -246,13 +246,13 @@ def parse_chunked_body(self) -> bool: def __parse_chunk_size(self): line, rest_chunk = self.buffer.split(self.line_end, 1) - _chunk_size = line.split(b';', 1)[0].strip() + chunk_size_ = line.split(b';', 1)[0].strip() try: - chunk_size = integer(bytes(_chunk_size), 16) + chunk_size = integer(bytes(chunk_size_), 16) if chunk_size < 0: raise ValueError() except (ValueError, OverflowError): - exc = InvalidHeader(_('Invalid chunk size: %r'), _chunk_size.decode('ISO8859-1')) + exc = InvalidHeader(_('Invalid chunk size: %r'), chunk_size_.decode('ISO8859-1')) raise BAD_REQUEST(str(exc)) else: return chunk_size, rest_chunk diff --git a/httoop/semantic/response.py b/httoop/semantic/response.py index 45cfe7bb..8ebe42d1 100644 --- a/httoop/semantic/response.py +++ b/httoop/semantic/response.py @@ -55,7 +55,7 @@ def prepare(self) -> None: if 'Content-Type' not in response.headers and response.body.mimetype and response.body: response.headers['Content-Type'] = bytes(response.body.mimetype) - if response.status == 200 and response.body.fileable and not self.chunked and 'Etag' in response.headers or 'Last-Modified' in response.headers: + if (response.status == 200 and response.body.fileable and not self.chunked and 'Etag' in response.headers) or 'Last-Modified' in response.headers: response.headers.setdefault('Accept-Ranges', b'bytes') self.prepare_ranges() diff --git a/httoop/uri/uri.py b/httoop/uri/uri.py index b14bc263..81f8fb2c 100644 --- a/httoop/uri/uri.py +++ b/httoop/uri/uri.py @@ -293,7 +293,7 @@ def compose(self) -> bytes: def _compose_absolute_iter(self) -> Iterator[bytes]: """Composes the whole URI.""" - scheme, username, password, host, port, path, _, fragment = self.tuple + scheme, _username, _password, _host, _port, _path, _, _fragment = self.tuple if scheme: yield self.quote(scheme, Percent.SCHEME) yield b':' diff --git a/httoop/util.py b/httoop/util.py index 0bb83e10..61ba3ef4 100644 --- a/httoop/util.py +++ b/httoop/util.py @@ -70,7 +70,7 @@ def integer(number: int | str | bytes, *args) -> int: ValueError: """ num = int(number, *args) - if isinstance(number, str) and '_' in number or isinstance(number, (bytes, bytearray)) and b' ' in number: + if (isinstance(number, str) and '_' in number) or (isinstance(number, (bytes, bytearray)) and b' ' in number): raise ValueError() return num diff --git a/tests/test_cli.py b/tests/test_cli.py index a9b04b39..26a613cf 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -12,7 +12,7 @@ def test_cli_compose(): assert subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', '1.0']) == b'GET / HTTP/1.0\r\n\r\n' # assert b'GET / HTTP/1.0\r\n\r\n' == subprocess.check_output([sys.executable, '-m', 'httoop', 'compose', 'request', '--protocol', 'HTTP/1.0']) p = subprocess.Popen([sys.executable, '-m', 'httoop', 'compose', 'request', '-b', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - stdout, stderr = p.communicate(b'test') + stdout, _stderr = p.communicate(b'test') assert stdout == b'GET / HTTP/1.1\r\n\r\ntest' with tempfile.NamedTemporaryFile() as fd: @@ -30,7 +30,7 @@ def test_cli_parse(): assert re.match(br"^\n\n\n$", stdout) p = subprocess.Popen([sys.executable, '-m', 'httoop', 'parse', 'response'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - stdout, stderr = p.communicate(b'HTTP/1.1 400 Evil Request\r\n\r\n') + stdout, _stderr = p.communicate(b'HTTP/1.1 400 Evil Request\r\n\r\n') assert re.match(br"^\n\n\nb''\n$", stdout), stdout From cb536734e25cb03c082cc2fee39de20bd36d78ad Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 02:05:18 +0200 Subject: [PATCH 23/24] style: drop six --- httoop/codecs/application/hal_json.py | 5 ++--- httoop/date.py | 3 +-- httoop/gateway/wsgi.py | 3 +-- httoop/header/element.py | 3 +-- httoop/header/headers.py | 3 +-- httoop/messages/body.py | 3 +-- httoop/messages/message.py | 3 +-- httoop/messages/method.py | 3 +-- httoop/messages/protocol.py | 3 +-- httoop/six.py | 4 ---- httoop/status/status.py | 3 +-- httoop/status/types.py | 3 +-- httoop/uri/uri.py | 5 ++--- pyproject.toml | 4 +--- tests/headers/test_invalid_headers.py | 8 ++++---- 15 files changed, 19 insertions(+), 37 deletions(-) delete mode 100644 httoop/six.py diff --git a/httoop/codecs/application/hal_json.py b/httoop/codecs/application/hal_json.py index aef742cf..e89cd0d2 100644 --- a/httoop/codecs/application/hal_json.py +++ b/httoop/codecs/application/hal_json.py @@ -17,7 +17,6 @@ def expand(href: str, templates: dict[str, str]) -> str: from httoop.codecs.application.json import JSON from httoop.exceptions import DecodeError, EncodeError -from httoop.six import string_types if TYPE_CHECKING: @@ -51,7 +50,7 @@ def get_links(self, relation: str, name: None = None) -> Iterator[dict[str, bool for link in links: if not isinstance(link, dict): raise DecodeError('HAL link must be object') - if not isinstance(link.get('href'), string_types): + if not isinstance(link.get('href'), str): raise DecodeError('HAL links must contain href') if name is not None and link.get('name') != name: continue @@ -62,7 +61,7 @@ def get_links(self, relation: str, name: None = None) -> Iterator[dict[str, bool link.setdefault('profile', None) link.setdefault('hreflang', None) for key in ('name', 'type', 'profile', 'hreflang'): - if not isinstance(link[key], string_types): + if not isinstance(link[key], str): link[key] = None if not isinstance(link['templated'], bool): link['templated'] = False diff --git a/httoop/date.py b/httoop/date.py index 1d9b83fc..1402b870 100644 --- a/httoop/date.py +++ b/httoop/date.py @@ -16,14 +16,13 @@ from httoop.exceptions import InvalidDate from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.util import _ __all__ = ['Date'] -class Date(with_metaclass(HTTPSemantic)): +class Date(metaclass=HTTPSemantic): """ A HTTP Date string. diff --git a/httoop/gateway/wsgi.py b/httoop/gateway/wsgi.py index 1a7e8e45..fffbb6b9 100644 --- a/httoop/gateway/wsgi.py +++ b/httoop/gateway/wsgi.py @@ -9,7 +9,6 @@ from typing import Any, Callable, Iterator from httoop.messages import Body -from httoop.six import reraise __all__ = ('WSGI',) @@ -74,7 +73,7 @@ def write(data): def start_response(status, response_headers, exc_info=None): if exc_info and self.headers_sent: try: - reraise(exc_info[0], exc_info[1], exc_info[2]) + raise exc_info[1].with_traceback(exc_info[2]) finally: exc_info = None # avoid dangling circular ref elif self.headers_set: diff --git a/httoop/header/element.py b/httoop/header/element.py index d751585f..de5155ef 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -17,7 +17,6 @@ from typing import Any, Iterator from httoop.exceptions import InvalidHeader -from httoop.six import with_metaclass from httoop.uri.percent_encoding import Percent from httoop.util import ByteUnicodeDict, CaseInsensitiveDict, _, integer, sanitize_encoding @@ -35,7 +34,7 @@ def __new__(cls: type, name: str, bases: Any, dict_: dict[str, Any]) -> Any: return super().__new__(cls, name, bases, dict_) -class HeaderElement(with_metaclass(HeaderType)): +class HeaderElement(metaclass=HeaderType): """An element (with parameters) from an HTTP header's element list.""" priority = None diff --git a/httoop/header/headers.py b/httoop/header/headers.py index 55e20899..bcb7a92b 100644 --- a/httoop/header/headers.py +++ b/httoop/header/headers.py @@ -6,11 +6,10 @@ from httoop.exceptions import InvalidHeader from httoop.header.element import HEADER, HeaderElement from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.util import CaseInsensitiveDict, _, to_unicode -class Headers(with_metaclass(HTTPSemantic, CaseInsensitiveDict)): +class Headers(CaseInsensitiveDict, metaclass=HTTPSemantic): __slots__ = () diff --git a/httoop/messages/body.py b/httoop/messages/body.py index 37dea17f..376d53e5 100644 --- a/httoop/messages/body.py +++ b/httoop/messages/body.py @@ -13,14 +13,13 @@ from httoop.header import Headers from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.util import IFile _SENTINEL = object() -class Body(with_metaclass(HTTPSemantic, IFile)): +class Body(IFile, metaclass=HTTPSemantic): """ A HTTP message body. diff --git a/httoop/messages/message.py b/httoop/messages/message.py index 122f9e8a..de42dff9 100644 --- a/httoop/messages/message.py +++ b/httoop/messages/message.py @@ -9,13 +9,12 @@ from httoop.messages.body import Body from httoop.messages.protocol import Protocol from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass __all__ = ('Message',) -class Message(with_metaclass(HTTPSemantic)): +class Message(metaclass=HTTPSemantic): """ A HTTP message. diff --git a/httoop/messages/method.py b/httoop/messages/method.py index 66288d4a..c25be41b 100644 --- a/httoop/messages/method.py +++ b/httoop/messages/method.py @@ -10,14 +10,13 @@ from httoop.exceptions import InvalidLine from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.util import _ __all__ = ('Method',) -class Method(with_metaclass(HTTPSemantic)): +class Method(metaclass=HTTPSemantic): """A HTTP request method.""" __slots__ = ('__method',) diff --git a/httoop/messages/protocol.py b/httoop/messages/protocol.py index 973aa055..dd7b6d2f 100644 --- a/httoop/messages/protocol.py +++ b/httoop/messages/protocol.py @@ -10,14 +10,13 @@ from httoop.exceptions import InvalidLine from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.util import _ __all__ = ('Protocol',) -class Protocol(with_metaclass(HTTPSemantic)): +class Protocol(metaclass=HTTPSemantic): """The HTTP protocol version.""" __slots__ = ('__protocol', 'name') diff --git a/httoop/six.py b/httoop/six.py deleted file mode 100644 index fee5f280..00000000 --- a/httoop/six.py +++ /dev/null @@ -1,4 +0,0 @@ -from six import int2byte, iterbytes, reraise, string_types, with_metaclass - - -__all__ = ('int2byte', 'iterbytes', 'reraise', 'string_types', 'with_metaclass') diff --git a/httoop/status/status.py b/httoop/status/status.py index f7b39f8d..deeb655c 100644 --- a/httoop/status/status.py +++ b/httoop/status/status.py @@ -12,11 +12,10 @@ from httoop.exceptions import InvalidLine from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.util import _, integer -class Status(with_metaclass(HTTPSemantic)): +class Status(metaclass=HTTPSemantic): """ A HTTP Status. diff --git a/httoop/status/types.py b/httoop/status/types.py index 58f23540..fd1ecefa 100644 --- a/httoop/status/types.py +++ b/httoop/status/types.py @@ -9,7 +9,6 @@ from typing import Any from httoop.meta import HTTPSemantic -from httoop.six import with_metaclass from httoop.status.status import REASONS, Status @@ -39,7 +38,7 @@ def __new__(cls: type, name: str, bases: Any, dict_: dict[str, Any]) -> Any: return super().__new__(cls, name, bases, dict_) -class StatusException(with_metaclass(StatusType, Status, Exception)): +class StatusException(Status, Exception, metaclass=StatusType): """ This class represents a small HTTP Response message for error handling purposes diff --git a/httoop/uri/uri.py b/httoop/uri/uri.py index 81f8fb2c..eed627c2 100644 --- a/httoop/uri/uri.py +++ b/httoop/uri/uri.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING, Any, Iterator from httoop.exceptions import InvalidURI -from httoop.six import int2byte, iterbytes, with_metaclass from httoop.uri.percent_encoding import Percent from httoop.uri.query_string import QueryString from httoop.uri.type import URIType @@ -23,7 +22,7 @@ from httoop.uri.http import HTTP -class URI(with_metaclass(URIType)): +class URI(metaclass=URIType): """Uniform Resource Identifier.""" __slots__ = ('scheme', 'username', 'password', 'host', '_port', 'path', 'query_string', 'fragment') # noqa: RUF023 @@ -322,7 +321,7 @@ def _compose_relative_iter(self) -> Iterator[bytes]: scheme, path, query_string, quote, fragment = self.scheme, self.path, self.query_string, self.quote, self.fragment PATH = Percent.PATH if not scheme and not path.startswith('/'): - PATH = b''.join({int2byte(c) for c in iterbytes(PATH)} - {b':', b'@'}) + PATH = b''.join({bytes((c,)) for c in iter(PATH)} - {b':', b'@'}) yield b'/'.join(quote(x, PATH) for x in path.split('/')) if query_string: yield b'?' diff --git a/pyproject.toml b/pyproject.toml index 32f1e468..314663a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61", "six"] +requires = ["setuptools>=61"] build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] @@ -38,8 +38,6 @@ classifiers = [ requires-python = ">=3.8" -dependencies = ["six"] - [project.optional-dependencies] xml = ["defusedxml"] hal = ["uritemplate"] diff --git a/tests/headers/test_invalid_headers.py b/tests/headers/test_invalid_headers.py index 13d91c7d..8069d444 100644 --- a/tests/headers/test_invalid_headers.py +++ b/tests/headers/test_invalid_headers.py @@ -21,13 +21,13 @@ def test_set_header_with_colon(request_): # @pytest.mark.skip() # def test_set_invalid_characters(request_): -# for invalid in six.iterbytes(INVALID_HEADER_FIELD_NAMES): -# name = b'foo%sbar' % (six.int2byte(invalid),) +# for invalid in iter(INVALID_HEADER_FIELD_NAMES): +# name = b'foo%sbar' % (bytes((invalid,)),) # request_.headers[name] = 'baz' # assert name not in request_.headers -# for invalid in six.iterbytes(LATIN_CHARS): +# for invalid in iter(LATIN_CHARS): # with pytest.raises(InvalidHeader): -# name = b'foo%sbar' % (six.int2byte(invalid),) +# name = b'foo%sbar' % (bytes((invalid,)),) # request_.headers[name] = 'baz' From c1c99ff5d7e84e55bf89d74d1c4893055348124d Mon Sep 17 00:00:00 2001 From: Space One Date: Tue, 28 Apr 2026 01:24:36 +0200 Subject: [PATCH 24/24] style: fix literal-membership (PLR6201) --- httoop/authentication/digest.py | 2 +- httoop/codecs/multipart/multipart.py | 2 +- httoop/gateway/wsgi.py | 2 +- httoop/header/conditional.py | 4 ++-- httoop/header/element.py | 2 +- httoop/semantic/request.py | 2 +- httoop/semantic/response.py | 2 +- httoop/server/__init__.py | 4 ++-- tests/authentication/test_basic.py | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/httoop/authentication/digest.py b/httoop/authentication/digest.py index 45282720..170bfbf5 100644 --- a/httoop/authentication/digest.py +++ b/httoop/authentication/digest.py @@ -182,7 +182,7 @@ def calculate_request_digest(cls, authinfo: ByteUnicodeDict) -> bytes: qop = authinfo.get('qop') hash_a2 = H(cls.A2(authinfo)) - if qop in (b'auth', b'auth-int'): + if qop in {b'auth', b'auth-int'}: data = b'%s:%s:%s:%s:%s' % (authinfo['nonce'], authinfo['nc'], authinfo['cnonce'], authinfo['qop'], hash_a2) elif qop is None: data = b'%s:%s' % (authinfo['nonce'], hash_a2) diff --git a/httoop/codecs/multipart/multipart.py b/httoop/codecs/multipart/multipart.py index 4c8c9dec..59bf42f0 100644 --- a/httoop/codecs/multipart/multipart.py +++ b/httoop/codecs/multipart/multipart.py @@ -34,7 +34,7 @@ def decode(cls, data: bytes, charset: str | None = None, mimetype: ContentType | if part: raise DecodeError(_('Data before boundary: %r'), part.decode('ISO8859-1')) part = parts.pop() - if part not in (b'--', b'--\r\n'): + if part not in {b'--', b'--\r\n'}: raise DecodeError(_('Invalid multipart end: %r'), part.decode('ISO8859-1')) from httoop.messages.body import Body diff --git a/httoop/gateway/wsgi.py b/httoop/gateway/wsgi.py index fffbb6b9..75ae8e7e 100644 --- a/httoop/gateway/wsgi.py +++ b/httoop/gateway/wsgi.py @@ -125,7 +125,7 @@ def get_environ(self) -> dict[str, str | tuple[int, int] | WSGIBody | bool | Non environ.update({ 'HTTP_%s' % name.upper().replace('-', '_'): value for name, value in self.request.headers.items() - if name.lower() not in ('content-type', 'content-length') + if name.lower() not in {'content-type', 'content-length'} }) environ.update({ 'REQUEST_METHOD': str(self.request.method), diff --git a/httoop/header/conditional.py b/httoop/header/conditional.py index 61ae238d..6501ad0a 100644 --- a/httoop/header/conditional.py +++ b/httoop/header/conditional.py @@ -28,7 +28,7 @@ def __int__(self) -> int: class _MatchElement: def __eq__(self, other: object) -> bool: - return self.value == other or self.value == '*' + return self.value in {other, '*'} def matches(self, etag): return self == etag @@ -58,7 +58,7 @@ class ETag(HeaderElement): def __eq__(self, other: object) -> bool: if not isinstance(other, ETag): other = self.__class__(other) - return other.value == self.value or other.value == '*' + return other.value in {self.value, '*'} class LastModified(_DateComparable, HeaderElement): diff --git a/httoop/header/element.py b/httoop/header/element.py index de5155ef..831326d7 100644 --- a/httoop/header/element.py +++ b/httoop/header/element.py @@ -390,7 +390,7 @@ def parse(cls, elementstr: bytes) -> HeaderElement: @classmethod def unescape_key(cls, key: bytes) -> bytes: key = key.strip() - if key.lower() in (b'httponly', b'secure', b'path', b'domain', b'max-age', b'expires'): + if key.lower() in {b'httponly', b'secure', b'path', b'domain', b'max-age', b'expires'}: return key.lower() return key diff --git a/httoop/semantic/request.py b/httoop/semantic/request.py index f1612e45..bc9a1281 100644 --- a/httoop/semantic/request.py +++ b/httoop/semantic/request.py @@ -33,7 +33,7 @@ def prepare(self) -> None: if 'Host' not in self.message.headers and self.message.uri.host: self.message.headers['Host'] = self.message.uri.host - if self.message.method in ('PUT', 'POST') and self.message.body and 'Date' not in self.message.headers: + if self.message.method in {'PUT', 'POST'} and self.message.body and 'Date' not in self.message.headers: self.message.headers['Date'] = bytes(Date()) # RFC 2616 Section 14.18 if self.message.method == 'TRACE': diff --git a/httoop/semantic/response.py b/httoop/semantic/response.py index 8ebe42d1..9913885e 100644 --- a/httoop/semantic/response.py +++ b/httoop/semantic/response.py @@ -28,7 +28,7 @@ def prepare(self) -> None: response = self.response status = int(response.status) - if status < 200 or status in (204, 205, 304): + if status < 200 or status in {204, 205, 304}: # 1XX, 204 NO_CONTENT, 205 RESET_CONTENT, 304 NOT_MODIFIED response.body = None diff --git a/httoop/server/__init__.py b/httoop/server/__init__.py index b3a5bbe8..58a21cd5 100644 --- a/httoop/server/__init__.py +++ b/httoop/server/__init__.py @@ -105,7 +105,7 @@ def sanitize_request_uri_path(self) -> None: def validate_request_uri_scheme(self) -> None: if self.message.uri.scheme: - if self.message.uri.scheme not in ('http', 'https'): # pragma: no cover + if self.message.uri.scheme not in {'http', 'https'}: # pragma: no cover exc = InvalidURI(_('Invalid URL: wrong scheme')) raise BAD_REQUEST(str(exc)) else: @@ -133,7 +133,7 @@ def check_message_without_body_containing_data(self) -> None: raise LENGTH_REQUIRED('Missing Content-Length header.') def check_methods_without_body(self) -> None: - if self.message.method in ('HEAD', 'GET', 'TRACE') and self.message.body: + if self.message.method in {'HEAD', 'GET', 'TRACE'} and self.message.body: raise BAD_REQUEST(f'A {self.message.method} request is considered as safe and MUST NOT contain a request body.') def check_http2_upgrade(self) -> None: diff --git a/tests/authentication/test_basic.py b/tests/authentication/test_basic.py index e222166b..b478c396 100644 --- a/tests/authentication/test_basic.py +++ b/tests/authentication/test_basic.py @@ -6,7 +6,7 @@ def test_basic_www_authenticate(headers): www_auth = WWWAuthenticate('Basic', {'realm': 'simple'}) - assert bytes(www_auth) in (b'Basic realm="simple"', b'Basic realm=simple') + assert bytes(www_auth) in {b'Basic realm="simple"', b'Basic realm=simple'} headers.parse(b'WWW-Authenticate: %s' % www_auth) assert headers.elements('WWW-Authenticate')[0].realm == 'simple'