Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-httptools for
openSUSE:Factory checked in at 2026-06-29 17:29:19
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-httptools (Old)
and /work/SRC/openSUSE:Factory/.python-httptools.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-httptools"
Mon Jun 29 17:29:19 2026 rev:9 rq:1362042 version:0.8.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-httptools/python-httptools.changes
2026-03-30 18:29:42.199090362 +0200
+++
/work/SRC/openSUSE:Factory/.python-httptools.new.11887/python-httptools.changes
2026-06-29 17:29:22.027999239 +0200
@@ -1,0 +2,11 @@
+Sat Jun 27 20:53:02 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.8.0:
+ * Add http-parser and llhttp licenses into the wheels
+ * Mark cython module as free-threading compatible
+ * Fix all typing issues
+ * Bump llhttp to 9.4.1
+ * Security: fix URL truncation issue
+ * Allow building with latest setuptools
+
+-------------------------------------------------------------------
Old:
----
httptools-0.7.1.tar.gz
New:
----
httptools-0.8.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-httptools.spec ++++++
--- /var/tmp/diff_new_pack.e99u2I/_old 2026-06-29 17:29:22.804025467 +0200
+++ /var/tmp/diff_new_pack.e99u2I/_new 2026-06-29 17:29:22.812025737 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-httptools
-Version: 0.7.1
+Version: 0.8.0
Release: 0
Summary: Python framework independent HTTP protocol utils
License: MIT
++++++ httptools-0.7.1.tar.gz -> httptools-0.8.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/.github/workflows/release.yml
new/httptools-0.8.0/.github/workflows/release.yml
--- old/httptools-0.7.1/.github/workflows/release.yml 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/.github/workflows/release.yml 2026-05-26
00:02:50.000000000 +0200
@@ -78,6 +78,7 @@
- "cp312"
- "cp313"
- "cp314"
+ - "cp314t"
exclude:
- os: ubuntu-latest
cibw_arch: universal2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/.github/workflows/tests.yml
new/httptools-0.8.0/.github/workflows/tests.yml
--- old/httptools-0.7.1/.github/workflows/tests.yml 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/.github/workflows/tests.yml 2026-05-26
00:02:50.000000000 +0200
@@ -10,11 +10,29 @@
- master
jobs:
+ typecheck:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ with:
+ fetch-depth: 50
+ submodules: true
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
+ with:
+ python-version: "3.9"
+
+ - name: Type check
+ run: make typecheck
+
+
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14",
"3.14t"]
os: [windows-latest, ubuntu-latest, macos-latest]
env:
@@ -37,7 +55,7 @@
__version__\s*=\s*(?:['"])([[:PEP440:]])(?:['"])
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #
v5.2.0
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
if: steps.release.outputs.version == 0
with:
python-version: ${{ matrix.python-version }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/Makefile new/httptools-0.8.0/Makefile
--- old/httptools-0.7.1/Makefile 2025-10-10 05:41:45.000000000 +0200
+++ new/httptools-0.8.0/Makefile 2026-05-26 00:02:50.000000000 +0200
@@ -12,11 +12,14 @@
endif
compile:
- $(PIP) install -e .
+ $(PIP) install -e . --group dev
test: compile
$(PYTHON) -m unittest -v
+typecheck: compile
+ $(PYTHON) -m pyright
+
clean:
find $(ROOT)/httptools/parser -name '*.c' | xargs rm -f
find $(ROOT)/httptools/parser -name '*.so' | xargs rm -f
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/__init__.py
new/httptools-0.8.0/httptools/__init__.py
--- old/httptools-0.7.1/httptools/__init__.py 2025-10-10 05:41:45.000000000
+0200
+++ new/httptools-0.8.0/httptools/__init__.py 2026-05-26 00:02:50.000000000
+0200
@@ -1,6 +1,35 @@
from . import parser
-from .parser import * # NOQA
+from .parser import (
+ HTTPProtocol,
+ HttpRequestParser,
+ HttpResponseParser,
+ HttpParserError,
+ HttpParserCallbackError,
+ HttpParserInvalidStatusError,
+ HttpParserInvalidMethodError,
+ HttpParserInvalidURLError,
+ HttpParserUpgrade,
+ parse_url,
+)
-from ._version import __version__ # NOQA
+from ._version import __version__
-__all__ = parser.__all__ + ('__version__',) # NOQA
+__all__ = (
+ "parser",
+ # protocol
+ "HTTPProtocol",
+ # parser
+ "HttpRequestParser",
+ "HttpResponseParser",
+ # errors
+ "HttpParserError",
+ "HttpParserCallbackError",
+ "HttpParserInvalidStatusError",
+ "HttpParserInvalidMethodError",
+ "HttpParserInvalidURLError",
+ "HttpParserUpgrade",
+ # url parser
+ "parse_url",
+ # version
+ "__version__",
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/_version.py
new/httptools-0.8.0/httptools/_version.py
--- old/httptools-0.7.1/httptools/_version.py 2025-10-10 05:41:45.000000000
+0200
+++ new/httptools-0.8.0/httptools/_version.py 2026-05-26 00:02:50.000000000
+0200
@@ -10,4 +10,4 @@
# supported platforms, publish the packages on PyPI, merge the PR
# to the target branch, create a Git tag pointing to the commit.
-__version__ = '0.7.1'
+__version__ = '0.8.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/parser/__init__.py
new/httptools-0.8.0/httptools/parser/__init__.py
--- old/httptools-0.7.1/httptools/parser/__init__.py 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/httptools/parser/__init__.py 2026-05-26
00:02:50.000000000 +0200
@@ -1,6 +1,28 @@
from .protocol import HTTPProtocol
-from .parser import * # NoQA
-from .errors import * # NoQA
-from .url_parser import * # NoQA
+from .parser import HttpRequestParser, HttpResponseParser # NoQA
+from .errors import (
+ HttpParserError,
+ HttpParserCallbackError,
+ HttpParserInvalidStatusError,
+ HttpParserInvalidMethodError,
+ HttpParserInvalidURLError,
+ HttpParserUpgrade,
+)
+from .url_parser import parse_url
-__all__ = parser.__all__ + errors.__all__ + url_parser.__all__ # NoQA
+__all__ = (
+ # protocol
+ "HTTPProtocol",
+ # parser
+ "HttpRequestParser",
+ "HttpResponseParser",
+ # errors
+ "HttpParserError",
+ "HttpParserCallbackError",
+ "HttpParserInvalidStatusError",
+ "HttpParserInvalidMethodError",
+ "HttpParserInvalidURLError",
+ "HttpParserUpgrade",
+ # url_parser
+ "parse_url",
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/parser/parser.pyi
new/httptools-0.8.0/httptools/parser/parser.pyi
--- old/httptools-0.7.1/httptools/parser/parser.pyi 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/httptools/parser/parser.pyi 2026-05-26
00:02:50.000000000 +0200
@@ -1,43 +1,44 @@
-from typing import Union, Any
from array import array
from .protocol import HTTPProtocol
class HttpParser:
- def __init__(self, protocol: Union[HTTPProtocol, Any]) -> None:
- """
- protocol -- a Python object with the following methods
- (all optional):
+ def __init__(self, protocol: HTTPProtocol | object) -> None:
+ """The HTTP parser.
- - on_message_begin()
- - on_url(url: bytes)
- - on_header(name: bytes, value: bytes)
- - on_headers_complete()
- - on_body(body: bytes)
- - on_message_complete()
- - on_chunk_header()
- - on_chunk_complete()
- - on_status(status: bytes)
+ Args:
+ protocol (HTTPProtocol): Callback interface for the parser.
"""
+ def set_dangerous_leniencies(
+ self,
+ lenient_headers: bool | None = None,
+ lenient_chunked_length: bool | None = None,
+ lenient_keep_alive: bool | None = None,
+ lenient_transfer_encoding: bool | None = None,
+ lenient_version: bool | None = None,
+ lenient_data_after_close: bool | None = None,
+ lenient_optional_lf_after_cr: bool | None = None,
+ lenient_optional_cr_before_lf: bool | None = None,
+ lenient_optional_crlf_after_chunk: bool | None = None,
+ lenient_spaces_after_chunk_size: bool | None = None,
+ ) -> None:
+ """Set dangerous leniencies for the parser."""
+
def get_http_version(self) -> str:
- """Return an HTTP protocol version."""
- ...
+ """Retrieve the HTTP protocol version e.g. "1.1"."""
def should_keep_alive(self) -> bool:
- """Return ``True`` if keep-alive mode is preferred."""
- ...
+ """Return `True` if keep-alive mode is preferred."""
def should_upgrade(self) -> bool:
- """Return ``True`` if the parsed request is a valid Upgrade request.
+ """Return `True` if the parsed request is a valid Upgrade request.
The method exposes a flag set just before on_headers_complete.
Calling this method earlier will only yield `False`."""
- ...
- def feed_data(self, data: Union[bytes, bytearray, memoryview, array]) ->
None:
+ def feed_data(self, data: bytes | bytearray | memoryview | array[int]) ->
None:
"""Feed data to the parser.
- Will eventually trigger callbacks on the ``protocol``
- object.
+ Will eventually trigger callbacks on the ``protocol`` object.
On HTTP upgrade, this method will raise an
``HttpParserUpgrade`` exception, with its sole argument
@@ -45,13 +46,13 @@
"""
class HttpRequestParser(HttpParser):
- """Used for parsing http requests from the server's side"""
+ """Used for parsing http requests from the server side."""
def get_method(self) -> bytes:
- """Return HTTP request method (GET, HEAD, etc)"""
+ """Retrieve the HTTP method of the request."""
class HttpResponseParser(HttpParser):
- """Used for parsing http requests from the client's side"""
+ """Used for parsing http responses from the client side."""
def get_status_code(self) -> int:
- """Return the status code of the HTTP response"""
+ """Retrieve the status code of the HTTP response."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/parser/protocol.py
new/httptools-0.8.0/httptools/parser/protocol.py
--- old/httptools-0.7.1/httptools/parser/protocol.py 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/httptools/parser/protocol.py 2026-05-26
00:02:50.000000000 +0200
@@ -4,12 +4,12 @@
class HTTPProtocol(Protocol):
"""Used for providing static type-checking when parsing through the http
protocol"""
- def on_message_begin() -> None: ...
- def on_url(url: bytes) -> None: ...
- def on_header(name: bytes, value: bytes) -> None: ...
- def on_headers_complete() -> None: ...
- def on_body(body: bytes) -> None: ...
- def on_message_complete() -> None: ...
- def on_chunk_header() -> None: ...
- def on_chunk_complete() -> None: ...
- def on_status(status: bytes) -> None: ...
+ def on_message_begin(self) -> None: ...
+ def on_url(self, url: bytes) -> None: ...
+ def on_header(self, name: bytes, value: bytes) -> None: ...
+ def on_headers_complete(self) -> None: ...
+ def on_body(self, body: bytes) -> None: ...
+ def on_message_complete(self) -> None: ...
+ def on_chunk_header(self) -> None: ...
+ def on_chunk_complete(self) -> None: ...
+ def on_status(self, status: bytes) -> None: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/parser/url_parser.pyi
new/httptools-0.8.0/httptools/parser/url_parser.pyi
--- old/httptools-0.7.1/httptools/parser/url_parser.pyi 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/httptools/parser/url_parser.pyi 2026-05-26
00:02:50.000000000 +0200
@@ -1,4 +1,3 @@
-from typing import Union
from array import array
class URL:
@@ -10,18 +9,5 @@
fragment: bytes
userinfo: bytes
-def parse_url(url: Union[bytes, bytearray, memoryview, array]) -> URL:
- """Parse URL strings into a structured Python object.
-
- Returns an instance of ``httptools.URL`` class with the
- following attributes:
-
- - schema: bytes
- - host: bytes
- - port: int
- - path: bytes
- - query: bytes
- - fragment: bytes
- - userinfo: bytes
- """
- ...
+def parse_url(url: bytes | bytearray | memoryview | array[int]) -> URL:
+ """Parse a URL string into a structured Python object."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/httptools/parser/url_parser.pyx
new/httptools-0.8.0/httptools/parser/url_parser.pyx
--- old/httptools-0.7.1/httptools/parser/url_parser.pyx 2025-10-10
05:41:45.000000000 +0200
+++ new/httptools-0.8.0/httptools/parser/url_parser.pyx 2026-05-26
00:02:50.000000000 +0200
@@ -12,6 +12,8 @@
__all__ = ('parse_url',)
+DEF MAX_URL_LENGTH = (1 << 16) - 1
+
@cython.freelist(250)
cdef class URL:
cdef readonly bytes schema
@@ -63,6 +65,14 @@
PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
try:
+ if py_buf.len > MAX_URL_LENGTH:
+ # http_parser stores URL field offsets/lengths as uint16_t,
+ # so URLs longer than this will cause silent truncation.
+ # See https://github.com/MagicStack/httptools/issues/142
+ raise HttpParserInvalidURLError(
+ "url is too long: url length of {} bytes exceeds the "
+ "maximum of {} bytes".format(py_buf.len, MAX_URL_LENGTH))
+
buf_data = <char*>py_buf.buf
res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/pyproject.toml
new/httptools-0.8.0/pyproject.toml
--- old/httptools-0.7.1/pyproject.toml 2025-10-10 05:41:45.000000000 +0200
+++ new/httptools-0.8.0/pyproject.toml 2026-05-26 00:02:50.000000000 +0200
@@ -1,6 +1,6 @@
[build-system]
build-backend = "setuptools.build_meta"
-requires = ["setuptools==80.9.0"]
+requires = ["setuptools>=80.9.0,<=82.0.1"]
[project]
name = "httptools"
@@ -18,12 +18,34 @@
{name = "Yury Selivanov", email="[email protected]"},
]
license = "MIT"
-license-files = ["LICENSE"]
+license-files = [
+ "LICENSE",
+ "vendor/http-parser/LICENSE-MIT",
+ "vendor/llhttp/LICENSE",
+]
description = "A collection of framework independent HTTP protocol utils."
readme = "README.md"
[project.urls]
Homepage = "https://github.com/MagicStack/httptools"
-[project.optional-dependencies]
-test = [] # for backward compatibility
+[dependency-groups]
+dev = [
+ # type checker
+ "pyright >= 1.1.406",
+ # tests
+ "pytest",
+ # build
+ "setuptools",
+ "wheel"
+]
+
+[tool.pyright]
+pythonVersion = "3.8"
+typeCheckingMode = "strict"
+reportMissingTypeStubs = false
+reportUnnecessaryIsInstance = false
+reportUnnecessaryTypeIgnoreComment = true
+reportMissingModuleSource = false
+include = ["httptools"]
+exclude = ["tests"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/setup.py new/httptools-0.8.0/setup.py
--- old/httptools-0.7.1/setup.py 2025-10-10 05:41:45.000000000 +0200
+++ new/httptools-0.8.0/setup.py 2026-05-26 00:02:50.000000000 +0200
@@ -7,7 +7,7 @@
from setuptools.command.build_ext import build_ext as build_ext
-CFLAGS = ['-O2']
+CFLAGS = ['-O2', '-DCYTHON_FREETHREADING_COMPATIBLE=1']
ROOT = pathlib.Path(__file__).parent
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/httptools-0.7.1/tests/test_parser.py
new/httptools-0.8.0/tests/test_parser.py
--- old/httptools-0.7.1/tests/test_parser.py 2025-10-10 05:41:45.000000000
+0200
+++ new/httptools-0.8.0/tests/test_parser.py 2026-05-26 00:02:50.000000000
+0200
@@ -686,3 +686,9 @@
def test_parser_url_10(self):
with self.assertRaisesRegex(TypeError, 'a bytes-like object'):
self.parse('dsf://aaa')
+
+ def test_parser_url_too_long(self):
+ url = b'http://h/' + b'a' * 65535
+ with self.assertRaisesRegex(httptools.HttpParserInvalidURLError,
+ 'url is too long'):
+ self.parse(url)