From 91eedb31be9e64b9800f6f05b1634d269361e87a Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sat, 17 Apr 2021 13:41:43 +0200 Subject: [PATCH 01/14] v6.0.1 --- .github/workflows/ci.yml | 1 + CHANGELOG.rst | 14 +++----------- Makefile | 3 +-- src/hyperframe/__init__.py | 2 +- tox.ini | 25 +++++++++++++------------ 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08cef82..ce29d43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: - 3.6 - 3.7 - 3.8 + - 3.9 - pypy3 steps: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c03a6b3..da4061c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,22 +1,14 @@ Release History =============== -dev ---- - -**API Changes (Backward Incompatible)** - -- +6.0.1 (2021-04-17) +------------------ **API Changes (Backward-compatible)** +- Added support for Python 3.9. - Added type hints. -**Bugfixes** - -- - - 6.0.0 (2020-09-06) ------------------ diff --git a/Makefile b/Makefile index a80b7f0..4f42f4f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ .PHONY: publish publish: - rm -rf dist/ tox -e packaging - twine upload -s dist/* + twine upload dist/* diff --git a/src/hyperframe/__init__.py b/src/hyperframe/__init__.py index 8ce9ffc..bf5ba1c 100644 --- a/src/hyperframe/__init__.py +++ b/src/hyperframe/__init__.py @@ -5,4 +5,4 @@ A module for providing a pure-Python HTTP/2 framing layer. """ -__version__ = '6.1.0+dev' +__version__ = '6.0.1' diff --git a/tox.ini b/tox.ini index b5d6c60..e9fe970 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,23 @@ [tox] -envlist = py36, py37, py38, pypy3, lint, docs, packaging +envlist = py36, py37, py38, py39, pypy3, lint, docs, packaging [gh-actions] python = 3.6: py36 3.7: py37 3.8: py38, lint, docs, packaging + 3.9: py39 pypy3: pypy3 [testenv] passenv = GITHUB_* deps = - pytest==6.0.1 - pytest-cov==2.10.1 - pytest-xdist==2.1.0 + pytest>=6.0.1,<7 + pytest-cov>=2.10.1,<3 + pytest-xdist>=2.2.1,<3 commands = - pytest --cov-report=xml --cov-report=term --cov=hyperframe {posargs} + pytest --cov-report=xml --cov-report=term --cov=hyperframe {posargs} [testenv:pypy3] # temporarily disable coverage testing on PyPy due to performance problems @@ -25,7 +26,7 @@ commands = pytest {posargs} [testenv:docs] basepython = python3.8 deps = - sphinx==3.2.1 + sphinx>=3.5.4,<4 whitelist_externals = make changedir = {toxinidir}/docs commands = @@ -35,18 +36,18 @@ commands = [testenv:lint] basepython = python3.8 deps = - flake8==3.8.3 - mypy + flake8==3.9.1 + mypy==0.812 commands = flake8 --max-complexity 10 src test - mypy --strict src + mypy --strict src/ [testenv:packaging] basepython = python3.8 deps = - check-manifest==0.42 - readme-renderer==26.0 - twine==3.2.0 + check-manifest==0.46 + readme-renderer==29.0 + twine==3.4.1 whitelist_externals = rm commands = rm -rf dist/ From 9eac1012e50533537f286d1e6560dd83bf1e51a4 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sat, 17 Apr 2021 14:13:53 +0200 Subject: [PATCH 02/14] prepare for next release cycle --- CHANGELOG.rst | 15 +++++++++++++++ src/hyperframe/__init__.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index da4061c..800513e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,21 @@ Release History =============== +dev +--- + +**API Changes (Backward Incompatible)** + +- + +**API Changes (Backward-compatible)** + +- + +**Bugfixes** + +- + 6.0.1 (2021-04-17) ------------------ diff --git a/src/hyperframe/__init__.py b/src/hyperframe/__init__.py index bf5ba1c..8ce9ffc 100644 --- a/src/hyperframe/__init__.py +++ b/src/hyperframe/__init__.py @@ -5,4 +5,4 @@ A module for providing a pure-Python HTTP/2 framing layer. """ -__version__ = '6.0.1' +__version__ = '6.1.0+dev' From 5632eb814179e5df4c001a229a8b8edec4b360c5 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sun, 6 Jun 2021 15:50:04 +0200 Subject: [PATCH 03/14] unify repository layout --- MANIFEST.in | 3 +-- Makefile | 5 ----- setup.cfg | 1 + setup.py | 12 ++++-------- tox.ini | 37 ++++++++++++++++++++++--------------- 5 files changed, 28 insertions(+), 30 deletions(-) delete mode 100644 Makefile diff --git a/MANIFEST.in b/MANIFEST.in index 200507a..85d1ba5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,7 @@ -graft src +graft src/hyperframe graft docs graft test prune docs/build prune test/http2-frame-test-case include README.rst LICENSE CHANGELOG.rst CONTRIBUTORS.rst tox.ini .gitmodules -include src/hyperframe/py.typed global-exclude *.pyc *.pyo *.swo *.swp *.map *.yml *.DS_Store diff --git a/Makefile b/Makefile deleted file mode 100644 index 4f42f4f..0000000 --- a/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -.PHONY: publish - -publish: - tox -e packaging - twine upload dist/* diff --git a/setup.cfg b/setup.cfg index 71646ab..5d716ed 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ source = [flake8] max-line-length = 120 +max-complexity = 10 [check-manifest] ignore = diff --git a/setup.py b/setup.py index 165e40b..7b260b9 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import itertools +#!/usr/bin/env python3 + import os import re -import sys from setuptools import setup, find_packages @@ -12,12 +10,10 @@ with open(os.path.join(PROJECT_ROOT, 'README.rst')) as file_: long_description = file_.read() -# Get the version version_regex = r'__version__ = ["\']([^"\']*)["\']' with open(os.path.join(PROJECT_ROOT, 'src/hyperframe/__init__.py')) as file_: text = file_.read() match = re.search(version_regex, text) - if match: version = match.group(1) else: @@ -33,10 +29,9 @@ author_email='cory@lukasa.co.uk', url='https://github.com/python-hyper/hyperframe/', packages=find_packages(where="src"), - package_data={'': ['LICENSE', 'README.rst', 'CHANGELOG.rst'], "hyperframe": ["py.typed"]}, + package_data={'hyperframe': ['py.typed']}, package_dir={'': 'src'}, python_requires='>=3.6.1', - include_package_data=True, license='MIT License', classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -47,6 +42,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tox.ini b/tox.ini index e9fe970..08bbad9 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,8 @@ envlist = py36, py37, py38, py39, pypy3, lint, docs, packaging python = 3.6: py36 3.7: py37 - 3.8: py38, lint, docs, packaging - 3.9: py39 + 3.8: py38 + 3.9: py39, lint, docs, packaging pypy3: pypy3 [testenv] @@ -23,34 +23,41 @@ commands = # temporarily disable coverage testing on PyPy due to performance problems commands = pytest {posargs} +[testenv:lint] +deps = + flake8>=3.9.1,<4 + mypy==0.812 +commands = + flake8 src/ test/ + mypy --strict src/ + [testenv:docs] -basepython = python3.8 deps = - sphinx>=3.5.4,<4 + sphinx>=4.0.2,<5 whitelist_externals = make changedir = {toxinidir}/docs commands = make clean make html -[testenv:lint] -basepython = python3.8 -deps = - flake8==3.9.1 - mypy==0.812 -commands = - flake8 --max-complexity 10 src test - mypy --strict src/ - [testenv:packaging] -basepython = python3.8 +basepython = python3.9 deps = check-manifest==0.46 readme-renderer==29.0 - twine==3.4.1 + twine>=3.4.1,<4 whitelist_externals = rm commands = rm -rf dist/ check-manifest python setup.py sdist bdist_wheel twine check dist/* + +[testenv:publish] +basepython = {[testenv:packaging]basepython} +deps = + {[testenv:packaging]deps} +whitelist_externals = {[testenv:packaging]whitelist_externals} +commands = + {[testenv:packaging]commands} + twine upload dist/* From 554fcad68beffae57dc8335ed5e02c177bfd6000 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sun, 6 Jun 2021 15:58:56 +0200 Subject: [PATCH 04/14] docs: fix function parameter reference fixes #159 --- CHANGELOG.rst | 14 +++++++------- src/hyperframe/frame.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 800513e..b039feb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ dev - -**API Changes (Backward-compatible)** +**API Changes (Backward Compatible)** - @@ -19,7 +19,7 @@ dev 6.0.1 (2021-04-17) ------------------ -**API Changes (Backward-compatible)** +**API Changes (Backward Compatible)** - Added support for Python 3.9. - Added type hints. @@ -27,7 +27,7 @@ dev 6.0.0 (2020-09-06) ------------------ -**API Changes (Backward-incompatible)** +**API Changes (Backward Incompatible)** - Introduce ``HyperframeError`` base exception class for all errors raised within hyperframe. - Change exception base class of ``UnknownFrameError`` to ``HyperframeError`` @@ -37,7 +37,7 @@ dev - Invalid SETTINGS frames (non-empty but ACK) now raise ``InvalidDataError``. - Invalid ALTSVC frames with non-bytestring field or origin now raise ``InvalidDataError``. -**API Changes (Backward-compatible)** +**API Changes (Backward Compatible)** - Deprecate ``total_padding`` - use `pad_length` instead. - Improve repr() output for all frame classes. @@ -60,7 +60,7 @@ dev 5.2.0 (2019-01-18) ------------------ -**API Changes (Backward-compatible)** +**API Changes (Backward Compatible)** - Add a new ENABLE_CONNECT_PROTOCOL settings parameter. @@ -72,7 +72,7 @@ dev 5.1.0 (2017-04-24) ------------------ -**API Changes (Backward-compatible)** +**API Changes (Backward Compatible)** - Added support for ``DataFrame.data`` being a ``memoryview`` object. @@ -119,7 +119,7 @@ dev 3.2.0 (2016-02-02) ------------------ -**API Changes (Backward-compatible)** +**API Changes (Backward Compatible)** - Invalid PING frame bodies now raise ``InvalidFrameError``, not ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. diff --git a/src/hyperframe/frame.py b/src/hyperframe/frame.py index f4e75e3..b8b32fd 100644 --- a/src/hyperframe/frame.py +++ b/src/hyperframe/frame.py @@ -133,7 +133,7 @@ def parse_frame_header(header: memoryview, strict: bool = False) -> Tuple["Frame type is received. .. versionchanged:: 5.0.0 - Added :param:`strict` to accommodate :class:`ExtensionFrame` + Added ``strict`` parameter to accommodate :class:`ExtensionFrame` """ try: fields = _STRUCT_HBBBL.unpack(header) From 8174dfd085f5cff6ab6d7dd2568a097c877c1d0e Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sat, 16 Nov 2024 13:51:22 +0100 Subject: [PATCH 05/14] update supported Python versions remove EOL Python versions --- .github/workflows/ci.yml | 13 +++++++------ CHANGELOG.rst | 9 +++++++-- setup.py | 9 +++++---- tox.ini | 23 ++++++++++++----------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce29d43..a46c370 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,17 +13,18 @@ jobs: max-parallel: 5 matrix: python-version: - - 3.6 - - 3.7 - - 3.8 - - 3.9 - - pypy3 + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "pypy3.9" steps: - uses: actions/checkout@v2 with: submodules: recursive - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install tox diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b039feb..2aa8556 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,11 +6,16 @@ dev **API Changes (Backward Incompatible)** -- +- Support for Python 3.6 has been removed. +- Support for Python 3.7 has been removed. +- Support for Python 3.8 has been removed. **API Changes (Backward Compatible)** -- +- Support for Python 3.10 has been added. +- Support for Python 3.11 has been added. +- Support for Python 3.12 has been added. +- Support for Python 3.13 has been added. **Bugfixes** diff --git a/setup.py b/setup.py index 7b260b9..01ee2f0 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ packages=find_packages(where="src"), package_data={'hyperframe': ['py.typed']}, package_dir={'': 'src'}, - python_requires='>=3.6.1', + python_requires='>=3.9.0', license='MIT License', classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -39,10 +39,11 @@ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tox.ini b/tox.ini index 08bbad9..02bafb9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,13 @@ [tox] -envlist = py36, py37, py38, py39, pypy3, lint, docs, packaging +envlist = py39, py310, py311, py312, py313, pypy3, lint, docs, packaging [gh-actions] python = - 3.6: py36 - 3.7: py37 - 3.8: py38 3.9: py39, lint, docs, packaging + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 pypy3: pypy3 [testenv] @@ -33,8 +34,8 @@ commands = [testenv:docs] deps = - sphinx>=4.0.2,<5 -whitelist_externals = make + sphinx>=7.4.7,<9 +allowlist_externals = make changedir = {toxinidir}/docs commands = make clean @@ -43,10 +44,10 @@ commands = [testenv:packaging] basepython = python3.9 deps = - check-manifest==0.46 - readme-renderer==29.0 - twine>=3.4.1,<4 -whitelist_externals = rm + check-manifest==0.50 + readme-renderer==44.0 + twine>=5.1.1,<6 +allowlist_externals = rm commands = rm -rf dist/ check-manifest @@ -57,7 +58,7 @@ commands = basepython = {[testenv:packaging]basepython} deps = {[testenv:packaging]deps} -whitelist_externals = {[testenv:packaging]whitelist_externals} +allowlist_externals = {[testenv:packaging]allowlist_externals} commands = {[testenv:packaging]commands} twine upload dist/* From b72d64b0e3d514ff0fd33915774aef1fa7d63fe2 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sat, 16 Nov 2024 19:29:19 +0100 Subject: [PATCH 06/14] linting --- src/hyperframe/flags.py | 2 +- tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hyperframe/flags.py b/src/hyperframe/flags.py index f00cb99..903d12d 100644 --- a/src/hyperframe/flags.py +++ b/src/hyperframe/flags.py @@ -22,7 +22,7 @@ class Flags(MutableSet): # type: ignore Will behave like a regular set(), except that a ValueError will be thrown when .add()ing unexpected flags. """ - def __init__(self, defined_flags: Iterable[Flag]): + def __init__(self, defined_flags: Iterable[Flag]) -> None: self._valid_flags = set(flag.name for flag in defined_flags) self._flags: Set[str] = set() diff --git a/tox.ini b/tox.ini index 02bafb9..50e5b2b 100644 --- a/tox.ini +++ b/tox.ini @@ -26,8 +26,8 @@ commands = pytest {posargs} [testenv:lint] deps = - flake8>=3.9.1,<4 - mypy==0.812 + flake8>=7.1.1,<8 + mypy>=1.13.0,<2 commands = flake8 src/ test/ mypy --strict src/ From 48965e0477b52635ed9f3ff021e451e9dadb434b Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sun, 17 Nov 2024 11:11:27 +0100 Subject: [PATCH 07/14] improve typing information --- src/hyperframe/__init__.py | 1 - src/hyperframe/exceptions.py | 1 - src/hyperframe/flags.py | 1 - src/hyperframe/frame.py | 23 +++++++++++------------ 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/hyperframe/__init__.py b/src/hyperframe/__init__.py index 8ce9ffc..f9134f7 100644 --- a/src/hyperframe/__init__.py +++ b/src/hyperframe/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ hyperframe ~~~~~~~~~~ diff --git a/src/hyperframe/exceptions.py b/src/hyperframe/exceptions.py index d6adab2..6ecad21 100644 --- a/src/hyperframe/exceptions.py +++ b/src/hyperframe/exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ hyperframe/exceptions ~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/hyperframe/flags.py b/src/hyperframe/flags.py index 903d12d..08c275f 100644 --- a/src/hyperframe/flags.py +++ b/src/hyperframe/flags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ hyperframe/flags ~~~~~~~~~~~~~~~~ diff --git a/src/hyperframe/frame.py b/src/hyperframe/frame.py index b8b32fd..814bf92 100644 --- a/src/hyperframe/frame.py +++ b/src/hyperframe/frame.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ hyperframe/frame ~~~~~~~~~~~~~~~~ @@ -9,12 +8,12 @@ """ import struct import binascii +from typing import Any, Iterable, Optional, Type from .exceptions import ( UnknownFrameError, InvalidPaddingError, InvalidFrameError, InvalidDataError ) from .flags import Flag, Flags -from typing import Optional, Tuple, List, Iterable, Any, Dict, Type # The maximum initial length of a frame. Some frames have shorter maximum @@ -44,7 +43,7 @@ class Frame: The base class for all HTTP/2 frames. """ #: The flags defined on this type of frame. - defined_flags: List[Flag] = [] + defined_flags: list[Flag] = [] #: The byte used to define the type of the frame. type: Optional[int] = None @@ -99,7 +98,7 @@ def _body_repr(self) -> str: return _raw_data_repr(self.serialize_body()) @staticmethod - def explain(data: memoryview) -> Tuple["Frame", int]: + def explain(data: memoryview) -> tuple["Frame", int]: """ Takes a bytestring and tries to parse a single frame and print it. @@ -116,7 +115,7 @@ def explain(data: memoryview) -> Tuple["Frame", int]: return frame, length @staticmethod - def parse_frame_header(header: memoryview, strict: bool = False) -> Tuple["Frame", int]: + def parse_frame_header(header: memoryview, strict: bool = False) -> tuple["Frame", int]: """ Takes a 9-byte frame header and returns a tuple of the appropriate Frame object and the length that needs to be read from the socket. @@ -343,7 +342,7 @@ class PriorityFrame(Priority, Frame): reprioritisation of existing streams. """ #: The flags defined for PRIORITY frames. - defined_flags: List[Flag] = [] + defined_flags: list[Flag] = [] #: The type byte defined for PRIORITY frames. type = 0x02 @@ -381,7 +380,7 @@ class RstStreamFrame(Frame): occurred. """ #: The flags defined for RST_STREAM frames. - defined_flags: List[Flag] = [] + defined_flags: list[Flag] = [] #: The type byte defined for RST_STREAM frames. type = 0x03 @@ -454,7 +453,7 @@ class SettingsFrame(Frame): #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting. ENABLE_CONNECT_PROTOCOL = 0x08 - def __init__(self, stream_id: int = 0, settings: Optional[Dict[int, int]] = None, **kwargs: Any) -> None: + def __init__(self, stream_id: int = 0, settings: Optional[dict[int, int]] = None, **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) if settings and "ACK" in kwargs.get("flags", ()): @@ -463,7 +462,7 @@ def __init__(self, stream_id: int = 0, settings: Optional[Dict[int, int]] = None ) #: A dictionary of the setting type byte to the value of the setting. - self.settings = settings or {} + self.settings: dict[int, int] = settings or {} def _body_repr(self) -> str: return "settings={}".format( @@ -611,7 +610,7 @@ class GoAwayFrame(Frame): connection. """ #: The flags defined for GOAWAY frames. - defined_flags: List[Flag] = [] + defined_flags: list[Flag] = [] #: The type byte defined for GOAWAY frames. type = 0x07 @@ -679,7 +678,7 @@ class WindowUpdateFrame(Frame): original sender. """ #: The flags defined for WINDOW_UPDATE frames. - defined_flags: List[Flag] = [] + defined_flags: list[Flag] = [] #: The type byte defined for WINDOW_UPDATE frames. type = 0x08 @@ -951,7 +950,7 @@ def _raw_data_repr(data: Optional[bytes]) -> str: return "" -_FRAME_CLASSES: List[Type[Frame]] = [ +_FRAME_CLASSES: list[Type[Frame]] = [ DataFrame, HeadersFrame, PriorityFrame, From 0b85069655b68a4e26cd22e7207f751ae133c7af Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Fri, 22 Nov 2024 21:43:59 +0100 Subject: [PATCH 08/14] modernize everything --- .gitmodules | 2 +- CHANGELOG.rst | 6 +- MANIFEST.in | 9 +- pyproject.toml | 101 ++++++++++++++++++++ setup.cfg | 27 ------ setup.py | 50 ---------- {test => tests}/__init__.py | 0 {test => tests}/http2-frame-test-case | 0 {test => tests}/test_external_collection.py | 0 {test => tests}/test_flags.py | 0 {test => tests}/test_frames.py | 0 tox.ini | 24 ++--- 12 files changed, 119 insertions(+), 100 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py rename {test => tests}/__init__.py (100%) rename {test => tests}/http2-frame-test-case (100%) rename {test => tests}/test_external_collection.py (100%) rename {test => tests}/test_flags.py (100%) rename {test => tests}/test_frames.py (100%) diff --git a/.gitmodules b/.gitmodules index c39273c..d7710d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "test/http2-frame-test-case"] - path = test/http2-frame-test-case + path = tests/http2-frame-test-case url = https://github.com/http2jp/http2-frame-test-case.git diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2aa8556..100af6c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,10 +16,8 @@ dev - Support for Python 3.11 has been added. - Support for Python 3.12 has been added. - Support for Python 3.13 has been added. - -**Bugfixes** - -- +- Improved type hints. +- Updated packaging and testing infrastructure. 6.0.1 (2021-04-17) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index 85d1ba5..3af4619 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,10 @@ graft src/hyperframe graft docs -graft test +graft tests + prune docs/build -prune test/http2-frame-test-case -include README.rst LICENSE CHANGELOG.rst CONTRIBUTORS.rst tox.ini .gitmodules +prune tests/http2-frame-test-case + +include README.rst LICENSE CHANGELOG.rst CONTRIBUTORS.rst pyproject.toml tox.ini .gitmodules + global-exclude *.pyc *.pyo *.swo *.swp *.map *.yml *.DS_Store diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a281102 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,101 @@ +# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ +# https://packaging.python.org/en/latest/specifications/pyproject-toml/ + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "hyperframe" +description = "HTTP/2 framing layer for Python" +readme = { file = "README.rst", content-type = "text/x-rst" } +license = { file = "LICENSE" } + +authors = [ + { name = "Cory Benfield", email = "cory@lukasa.co.uk" } +] +maintainers = [ + { name = "Thomas Kriechbaumer", email = "thomas@kriechbaumer.name" }, +] + +requires-python = ">=3.9" +dependencies = [] +dynamic = ["version"] + +# For a list of valid classifiers, see https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + +[project.urls] +"Homepage" = "https://github.com/python-hyper/hyperframe/" +"Bug Reports" = "https://github.com/python-hyper/hyperframe/issues" +"Source" = "https://github.com/python-hyper/hyperframe/" +"Documentation" = "https://python-hyper.org/" + +[dependency-groups] +testing = [ + "pytest>=8.3.3,<9", + "pytest-cov>=6.0.0,<7", + "pytest-xdist>=3.6.1,<4", +] + +linting = [ + "ruff>=0.8.0,<1", + "mypy>=1.13.0,<2", +] + +packaging = [ + "check-manifest==0.50", + "readme-renderer==44.0", + "build>=1.2.2,<2", + "twine>=5.1.1,<6", + "wheel>=0.45.0,<1", +] + +docs = [ + "sphinx>=7.4.7,<9", +] + +[tool.setuptools.packages.find] +where = [ "src" ] + +[tool.setuptools.package-data] +hyperframe = [ "py.typed" ] + +[tool.setuptools.dynamic] +version = { attr = "hyperframe.__version__" } + +[tool.check-manifest] +ignore = [ + "Makefile", + "tests/http2-frame-test-case", +] + +[tool.ruff] +line-length = 140 +target-version = "py39" + +[tool.coverage.run] +branch = true +source = [ "hyperframe" ] + +[tool.coverage.report] +fail_under = 100 +show_missing = true +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError()", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5d716ed..0000000 --- a/setup.cfg +++ /dev/null @@ -1,27 +0,0 @@ -[tool:pytest] -testpaths = test - -[coverage:run] -branch = True -source = hyperframe - -[coverage:report] -fail_under = 100 -show_missing = True -exclude_lines = - pragma: no cover - raise NotImplementedError() - -[coverage:paths] -source = - src - .tox/*/site-packages - -[flake8] -max-line-length = 120 -max-complexity = 10 - -[check-manifest] -ignore = - Makefile - test/http2-frame-test-case diff --git a/setup.py b/setup.py deleted file mode 100644 index 01ee2f0..0000000 --- a/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import os -import re - -from setuptools import setup, find_packages - -PROJECT_ROOT = os.path.dirname(__file__) - -with open(os.path.join(PROJECT_ROOT, 'README.rst')) as file_: - long_description = file_.read() - -version_regex = r'__version__ = ["\']([^"\']*)["\']' -with open(os.path.join(PROJECT_ROOT, 'src/hyperframe/__init__.py')) as file_: - text = file_.read() - match = re.search(version_regex, text) - if match: - version = match.group(1) - else: - raise RuntimeError("No version number found!") - -setup( - name='hyperframe', - version=version, - description='HTTP/2 framing layer for Python', - long_description=long_description, - long_description_content_type='text/x-rst', - author='Cory Benfield', - author_email='cory@lukasa.co.uk', - url='https://github.com/python-hyper/hyperframe/', - packages=find_packages(where="src"), - package_data={'hyperframe': ['py.typed']}, - package_dir={'': 'src'}, - python_requires='>=3.9.0', - license='MIT License', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - ], -) diff --git a/test/__init__.py b/tests/__init__.py similarity index 100% rename from test/__init__.py rename to tests/__init__.py diff --git a/test/http2-frame-test-case b/tests/http2-frame-test-case similarity index 100% rename from test/http2-frame-test-case rename to tests/http2-frame-test-case diff --git a/test/test_external_collection.py b/tests/test_external_collection.py similarity index 100% rename from test/test_external_collection.py rename to tests/test_external_collection.py diff --git a/test/test_flags.py b/tests/test_flags.py similarity index 100% rename from test/test_flags.py rename to tests/test_flags.py diff --git a/test/test_frames.py b/tests/test_frames.py similarity index 100% rename from test/test_frames.py rename to tests/test_frames.py diff --git a/tox.ini b/tox.ini index 50e5b2b..dfca357 100644 --- a/tox.ini +++ b/tox.ini @@ -13,10 +13,7 @@ python = [testenv] passenv = GITHUB_* -deps = - pytest>=6.0.1,<7 - pytest-cov>=2.10.1,<3 - pytest-xdist>=2.2.1,<3 +dependency_groups = testing commands = pytest --cov-report=xml --cov-report=term --cov=hyperframe {posargs} @@ -25,16 +22,16 @@ commands = commands = pytest {posargs} [testenv:lint] -deps = - flake8>=7.1.1,<8 - mypy>=1.13.0,<2 +dependency_groups = linting +allowlist_externals = + ruff + mypy commands = - flake8 src/ test/ + ruff check src/ tests/ mypy --strict src/ [testenv:docs] -deps = - sphinx>=7.4.7,<9 +dependency_groups = docs allowlist_externals = make changedir = {toxinidir}/docs commands = @@ -43,15 +40,12 @@ commands = [testenv:packaging] basepython = python3.9 -deps = - check-manifest==0.50 - readme-renderer==44.0 - twine>=5.1.1,<6 +dependency_groups = packaging allowlist_externals = rm commands = rm -rf dist/ check-manifest - python setup.py sdist bdist_wheel + python -m build --outdir dist/ twine check dist/* [testenv:publish] From 5039d79b2782bc243aeb534a59014f949c7f00cd Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Fri, 22 Nov 2024 23:30:50 +0100 Subject: [PATCH 09/14] fix coverage path mapping --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a281102..5413614 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,3 +99,9 @@ exclude_lines = [ "pragma: no cover", "raise NotImplementedError()", ] + +[tool.coverage.paths] +source = [ + "src/", + ".tox/**/site-packages/", +] From 06993dab380dd4e576c9ebac9bf4bf748566a0f5 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Wed, 18 Dec 2024 22:45:10 +0100 Subject: [PATCH 10/14] bump codecov integration --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a46c370..0eefa1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: - name: Test with tox run: | tox --parallel 0 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v5 with: - file: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + disable_search: true From 2595cf6b734d0b6c0acd82223ea828692726caf3 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Fri, 20 Dec 2024 18:36:31 +0100 Subject: [PATCH 11/14] migrate tox.ini into pyproject.toml --- MANIFEST.in | 2 +- pyproject.toml | 74 +++++++++++++++++++++++++++++++++++++++++++++++++- tox.ini | 58 --------------------------------------- 3 files changed, 74 insertions(+), 60 deletions(-) delete mode 100644 tox.ini diff --git a/MANIFEST.in b/MANIFEST.in index 3af4619..1c9629a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,6 @@ graft tests prune docs/build prune tests/http2-frame-test-case -include README.rst LICENSE CHANGELOG.rst CONTRIBUTORS.rst pyproject.toml tox.ini .gitmodules +include README.rst LICENSE CHANGELOG.rst CONTRIBUTORS.rst pyproject.toml .gitmodules global-exclude *.pyc *.pyo *.swo *.swp *.map *.yml *.DS_Store diff --git a/pyproject.toml b/pyproject.toml index 5413614..98beef6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "hyperframe" -description = "HTTP/2 framing layer for Python" +description = "Pure-Python HTTP/2 framing" readme = { file = "README.rst", content-type = "text/x-rst" } license = { file = "LICENSE" } @@ -46,6 +46,13 @@ classifiers = [ "Documentation" = "https://python-hyper.org/" [dependency-groups] +dev = [ + { include-group = "testing" }, + { include-group = "linting" }, + { include-group = "packaging" }, + { include-group = "docs" }, +] + testing = [ "pytest>=8.3.3,<9", "pytest-cov>=6.0.0,<7", @@ -105,3 +112,68 @@ source = [ "src/", ".tox/**/site-packages/", ] + +[tool.tox] +min_version = "4.23.2" +env_list = [ "py39", "py310", "py311", "py312", "py313", "pypy3", "lint", "docs", "packaging" ] + +[tool.tox.gh-actions] +python = """ + 3.9: py39, h2spec, lint, docs, packaging + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 + pypy3: pypy3 +""" + +[tool.tox.env_run_base] +pass_env = [ + "GITHUB_*", +] +dependency_groups = ["testing"] +commands = [ + ["pytest", "--cov-report=xml", "--cov-report=term", "--cov=hyperframe", { replace = "posargs", extend = true }] +] + +[tool.tox.env.pypy3] +# temporarily disable coverage testing on PyPy due to performance problems +commands = [ + ["pytest", { replace = "posargs", extend = true }] +] + +[tool.tox.env.lint] +dependency_groups = ["linting"] +commands = [ + ["ruff", "check", "src/"], + ["mypy", "src/"], +] + +[tool.tox.env.docs] +dependency_groups = ["docs"] +allowlist_externals = ["make"] +changedir = "{toxinidir}/docs" +commands = [ + ["make", "clean"], + ["make", "html"], +] + +[tool.tox.env.packaging] +base_python = ["python39"] +dependency_groups = ["packaging"] +allowlist_externals = ["rm"] +commands = [ + ["rm", "-rf", "dist/"], + ["check-manifest"], + ["python", "-m", "build", "--outdir", "dist/"], + ["twine", "check", "dist/*"], +] + +[tool.tox.env.publish] +base_python = "{[tool.tox.env.packaging]base_python}" +deps = "{[tool.tox.env.packaging]deps}" +allowlist_externals = "{[tool.tox.env.packaging]allowlist_externals}" +commands = [ + "{[testenv:packaging]commands}", + ["twine", "upload", "dist/*"], +] diff --git a/tox.ini b/tox.ini deleted file mode 100644 index dfca357..0000000 --- a/tox.ini +++ /dev/null @@ -1,58 +0,0 @@ -[tox] -envlist = py39, py310, py311, py312, py313, pypy3, lint, docs, packaging - -[gh-actions] -python = - 3.9: py39, lint, docs, packaging - 3.10: py310 - 3.11: py311 - 3.12: py312 - 3.13: py313 - pypy3: pypy3 - -[testenv] -passenv = - GITHUB_* -dependency_groups = testing -commands = - pytest --cov-report=xml --cov-report=term --cov=hyperframe {posargs} - -[testenv:pypy3] -# temporarily disable coverage testing on PyPy due to performance problems -commands = pytest {posargs} - -[testenv:lint] -dependency_groups = linting -allowlist_externals = - ruff - mypy -commands = - ruff check src/ tests/ - mypy --strict src/ - -[testenv:docs] -dependency_groups = docs -allowlist_externals = make -changedir = {toxinidir}/docs -commands = - make clean - make html - -[testenv:packaging] -basepython = python3.9 -dependency_groups = packaging -allowlist_externals = rm -commands = - rm -rf dist/ - check-manifest - python -m build --outdir dist/ - twine check dist/* - -[testenv:publish] -basepython = {[testenv:packaging]basepython} -deps = - {[testenv:packaging]deps} -allowlist_externals = {[testenv:packaging]allowlist_externals} -commands = - {[testenv:packaging]commands} - twine upload dist/* From 0be70d15792c5ea633f13e5041ac88f038eca0b3 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Thu, 19 Dec 2024 20:01:27 +0100 Subject: [PATCH 12/14] lint++ --- docs/source/_static/.keep | 0 pyproject.toml | 39 +++- src/hyperframe/__init__.py | 9 +- src/hyperframe/exceptions.py | 13 +- src/hyperframe/flags.py | 25 ++- src/hyperframe/frame.py | 355 ++++++++++++++++------------------- tests/test_flags.py | 1 - tests/test_frames.py | 1 - 8 files changed, 217 insertions(+), 226 deletions(-) create mode 100644 docs/source/_static/.keep diff --git a/docs/source/_static/.keep b/docs/source/_static/.keep new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index 98beef6..554ccfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ # https://packaging.python.org/en/latest/specifications/pyproject-toml/ [build-system] -requires = ["setuptools"] +requires = ["setuptools>=75.6.0"] build-backend = "setuptools.build_meta" [project] @@ -92,8 +92,41 @@ ignore = [ ] [tool.ruff] -line-length = 140 +line-length = 150 target-version = "py39" +format.preview = true +format.docstring-code-line-length = 100 +format.docstring-code-format = true +lint.select = [ + "ALL", +] +lint.ignore = [ + "ANN401", # kwargs with typing.Any + "CPY", # not required + "D101", # docs readability + "D102", # docs readability + "D105", # docs readability + "D107", # docs readability + "D200", # docs readability + "D205", # docs readability + "D205", # docs readability + "D203", # docs readability + "D212", # docs readability + "D400", # docs readability + "D401", # docs readability + "D415", # docs readability + "PLR2004", # readability + "SIM108", # readability + "RUF012", # readability + "FBT001", # readability + "FBT002", # readability + "PGH003", # readability +] +lint.isort.required-imports = [ "from __future__ import annotations" ] + +[tool.mypy] +show_error_codes = true +strict = true [tool.coverage.run] branch = true @@ -104,7 +137,7 @@ fail_under = 100 show_missing = true exclude_lines = [ "pragma: no cover", - "raise NotImplementedError()", + "raise NotImplementedError", ] [tool.coverage.paths] diff --git a/src/hyperframe/__init__.py b/src/hyperframe/__init__.py index f9134f7..6b9e72f 100644 --- a/src/hyperframe/__init__.py +++ b/src/hyperframe/__init__.py @@ -1,7 +1,6 @@ """ -hyperframe -~~~~~~~~~~ - -A module for providing a pure-Python HTTP/2 framing layer. +Provides a pure-Python HTTP/2 framing layer. """ -__version__ = '6.1.0+dev' +from __future__ import annotations + +__version__ = "6.1.0+dev" diff --git a/src/hyperframe/exceptions.py b/src/hyperframe/exceptions.py index 6ecad21..7a40f4a 100644 --- a/src/hyperframe/exceptions.py +++ b/src/hyperframe/exceptions.py @@ -1,9 +1,7 @@ """ -hyperframe/exceptions -~~~~~~~~~~~~~~~~~~~~~ - -Defines the exceptions that can be thrown by hyperframe. +Exceptions that can be thrown by hyperframe. """ +from __future__ import annotations class HyperframeError(Exception): @@ -21,6 +19,7 @@ class UnknownFrameError(HyperframeError): .. versionchanged:: 6.0.0 Changed base class from `ValueError` to :class:`HyperframeError` """ + def __init__(self, frame_type: int, length: int) -> None: #: The type byte of the unknown frame that was received. self.frame_type = frame_type @@ -30,8 +29,7 @@ def __init__(self, frame_type: int, length: int) -> None: def __str__(self) -> str: return ( - "UnknownFrameError: Unknown frame type 0x%X received, " - "length %d bytes" % (self.frame_type, self.length) + f"UnknownFrameError: Unknown frame type 0x{self.frame_type:X} received, length {self.length} bytes" ) @@ -42,7 +40,6 @@ class InvalidPaddingError(HyperframeError): .. versionchanged:: 6.0.0 Changed base class from `ValueError` to :class:`HyperframeError` """ - pass class InvalidFrameError(HyperframeError): @@ -54,7 +51,6 @@ class InvalidFrameError(HyperframeError): .. versionchanged:: 6.0.0 Changed base class from `ValueError` to :class:`HyperframeError` """ - pass class InvalidDataError(HyperframeError): @@ -63,4 +59,3 @@ class InvalidDataError(HyperframeError): .. versionadded:: 6.0.0 """ - pass diff --git a/src/hyperframe/flags.py b/src/hyperframe/flags.py index 08c275f..e5f4a22 100644 --- a/src/hyperframe/flags.py +++ b/src/hyperframe/flags.py @@ -1,11 +1,10 @@ """ -hyperframe/flags -~~~~~~~~~~~~~~~~ - -Defines basic Flag and Flags data structures. +Basic Flag and Flags data structures. """ -from collections.abc import MutableSet -from typing import NamedTuple, Iterable, Set, Iterator +from __future__ import annotations + +from collections.abc import Iterable, Iterator, MutableSet +from typing import NamedTuple class Flag(NamedTuple): @@ -21,12 +20,13 @@ class Flags(MutableSet): # type: ignore Will behave like a regular set(), except that a ValueError will be thrown when .add()ing unexpected flags. """ + def __init__(self, defined_flags: Iterable[Flag]) -> None: - self._valid_flags = set(flag.name for flag in defined_flags) - self._flags: Set[str] = set() + self._valid_flags = {flag.name for flag in defined_flags} + self._flags: set[str] = set() def __repr__(self) -> str: - return repr(sorted(list(self._flags))) + return repr(sorted(self._flags)) def __contains__(self, x: object) -> bool: return self._flags.__contains__(x) @@ -42,9 +42,6 @@ def discard(self, value: str) -> None: def add(self, value: str) -> None: if value not in self._valid_flags: - raise ValueError( - "Unexpected flag: {}. Valid flags are: {}".format( - value, self._valid_flags - ) - ) + msg = f"Unexpected flag: {value}. Valid flags are: {self._valid_flags}" + raise ValueError(msg) return self._flags.add(value) diff --git a/src/hyperframe/frame.py b/src/hyperframe/frame.py index 814bf92..a67487e 100644 --- a/src/hyperframe/frame.py +++ b/src/hyperframe/frame.py @@ -1,20 +1,21 @@ """ -hyperframe/frame -~~~~~~~~~~~~~~~~ +Framing logic for HTTP/2. -Defines framing logic for HTTP/2. Provides both classes to represent framed +Provides both classes to represent framed data and logic for aiding the connection when it comes to reading from the socket. """ -import struct +from __future__ import annotations + import binascii -from typing import Any, Iterable, Optional, Type +import struct +from typing import TYPE_CHECKING, Any -from .exceptions import ( - UnknownFrameError, InvalidPaddingError, InvalidFrameError, InvalidDataError -) -from .flags import Flag, Flags +if TYPE_CHECKING: + from collections.abc import Iterable # pragma: no cover +from .exceptions import InvalidDataError, InvalidFrameError, InvalidPaddingError, UnknownFrameError +from .flags import Flag, Flags # The maximum initial length of a frame. Some frames have shorter maximum # lengths. @@ -42,15 +43,16 @@ class Frame: """ The base class for all HTTP/2 frames. """ + #: The flags defined on this type of frame. defined_flags: list[Flag] = [] #: The byte used to define the type of the frame. - type: Optional[int] = None + type: int | None = None # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream', # it must be zero. If 'either', it's not checked. - stream_association: Optional[str] = None + stream_association: str | None = None def __init__(self, stream_id: int, flags: Iterable[str] = ()) -> None: #: The stream identifier for the stream this frame was received on. @@ -66,30 +68,16 @@ def __init__(self, stream_id: int, flags: Iterable[str] = ()) -> None: for flag in flags: self.flags.add(flag) - if (not self.stream_id and - self.stream_association == _STREAM_ASSOC_HAS_STREAM): - raise InvalidDataError( - 'Stream ID must be non-zero for {}'.format( - type(self).__name__, - ) - ) - if (self.stream_id and - self.stream_association == _STREAM_ASSOC_NO_STREAM): - raise InvalidDataError( - 'Stream ID must be zero for {} with stream_id={}'.format( - type(self).__name__, - self.stream_id, - ) - ) + if not self.stream_id and self.stream_association == _STREAM_ASSOC_HAS_STREAM: + msg = f"Stream ID must be non-zero for {type(self).__name__}" + raise InvalidDataError(msg) + if self.stream_id and self.stream_association == _STREAM_ASSOC_NO_STREAM: + msg = f"Stream ID must be zero for {type(self).__name__} with stream_id={self.stream_id}" + raise InvalidDataError(msg) def __repr__(self) -> str: return ( - "{}(stream_id={}, flags={}): {}" - ).format( - type(self).__name__, - self.stream_id, - repr(self.flags), - self._body_repr(), + f"{type(self).__name__}(stream_id={self.stream_id}, flags={self.flags!r}): {self._body_repr()}" ) def _body_repr(self) -> str: @@ -98,7 +86,7 @@ def _body_repr(self) -> str: return _raw_data_repr(self.serialize_body()) @staticmethod - def explain(data: memoryview) -> tuple["Frame", int]: + def explain(data: memoryview) -> tuple[Frame, int]: """ Takes a bytestring and tries to parse a single frame and print it. @@ -111,11 +99,11 @@ def explain(data: memoryview) -> tuple["Frame", int]: """ frame, length = Frame.parse_frame_header(data[:9]) frame.parse_body(data[9:9 + length]) - print(frame) + print(frame) # noqa: T201 return frame, length @staticmethod - def parse_frame_header(header: memoryview, strict: bool = False) -> tuple["Frame", int]: + def parse_frame_header(header: memoryview, strict: bool = False) -> tuple[Frame, int]: """ Takes a 9-byte frame header and returns a tuple of the appropriate Frame object and the length that needs to be read from the socket. @@ -136,21 +124,22 @@ def parse_frame_header(header: memoryview, strict: bool = False) -> tuple["Frame """ try: fields = _STRUCT_HBBBL.unpack(header) - except struct.error: - raise InvalidFrameError("Invalid frame header") + except struct.error as err: + msg = "Invalid frame header" + raise InvalidFrameError(msg) from err # First 24 bits are frame length. length = (fields[0] << 8) + fields[1] - type = fields[2] + typ_e = fields[2] flags = fields[3] stream_id = fields[4] & 0x7FFFFFFF try: - frame = FRAMES[type](stream_id) - except KeyError: + frame = FRAMES[typ_e](stream_id) + except KeyError as err: if strict: - raise UnknownFrameError(type, length) - frame = ExtensionFrame(type=type, stream_id=stream_id) + raise UnknownFrameError(typ_e, length) from err + frame = ExtensionFrame(type=typ_e, stream_id=stream_id) frame.parse_flags(flags) return (frame, length) @@ -183,13 +172,13 @@ def serialize(self) -> bytes: self.body_len & 0xFF, self.type, flags, - self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. + self.stream_id & 0x7FFFFFFF, # Stream ID is 32 bits. ) return header + body def serialize_body(self) -> bytes: - raise NotImplementedError() + raise NotImplementedError def parse_body(self, data: memoryview) -> None: """ @@ -202,7 +191,7 @@ def parse_body(self, data: memoryview) -> None: :meth:`parse_frame_header `. """ - raise NotImplementedError() + raise NotImplementedError class Padding: @@ -210,6 +199,7 @@ class Padding: Mixin for frames that contain padding. Defines extra fields that can be used and set by frames that can be padded. """ + def __init__(self, stream_id: int, pad_length: int = 0, **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) # type: ignore @@ -217,16 +207,17 @@ def __init__(self, stream_id: int, pad_length: int = 0, **kwargs: Any) -> None: self.pad_length = pad_length def serialize_padding_data(self) -> bytes: - if 'PADDED' in self.flags: # type: ignore + if "PADDED" in self.flags: # type: ignore return _STRUCT_B.pack(self.pad_length) - return b'' + return b"" def parse_padding_data(self, data: memoryview) -> int: - if 'PADDED' in self.flags: # type: ignore + if "PADDED" in self.flags: # type: ignore try: - self.pad_length = struct.unpack('!B', data[:1])[0] - except struct.error: - raise InvalidFrameError("Invalid Padding data") + self.pad_length = struct.unpack("!B", data[:1])[0] + except struct.error as err: + msg = "Invalid Padding data" + raise InvalidFrameError(msg) from err return 1 return 0 @@ -237,7 +228,8 @@ def total_padding(self) -> int: # pragma: no cover import warnings warnings.warn( "total_padding contains the same information as pad_length.", - DeprecationWarning + DeprecationWarning, + stacklevel=2, ) return self.pad_length @@ -247,6 +239,7 @@ class Priority: Mixin for frames that contain priority data. Defines extra fields that can be used and set by frames that contain priority data. """ + def __init__(self, stream_id: int, depends_on: int = 0x0, @@ -267,16 +260,17 @@ def __init__(self, def serialize_priority_data(self) -> bytes: return _STRUCT_LB.pack( self.depends_on + (0x80000000 if self.exclusive else 0), - self.stream_weight + self.stream_weight, ) def parse_priority_data(self, data: memoryview) -> int: try: self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5]) - except struct.error: - raise InvalidFrameError("Invalid Priority data") + except struct.error as err: + msg = "Invalid Priority data" + raise InvalidFrameError(msg) from err - self.exclusive = True if self.depends_on >> 31 else False + self.exclusive = bool(self.depends_on >> 31) self.depends_on &= 0x7FFFFFFF return 5 @@ -287,10 +281,11 @@ class DataFrame(Padding, Frame): associated with a stream. One or more DATA frames are used, for instance, to carry HTTP request or response payloads. """ + #: The flags defined for DATA frames. defined_flags = [ - Flag('END_STREAM', 0x01), - Flag('PADDED', 0x08), + Flag("END_STREAM", 0x01), + Flag("PADDED", 0x08), ] #: The type byte for data frames. @@ -298,7 +293,7 @@ class DataFrame(Padding, Frame): stream_association = _STREAM_ASSOC_HAS_STREAM - def __init__(self, stream_id: int, data: bytes = b'', **kwargs: Any) -> None: + def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) #: The data contained on this frame. @@ -306,10 +301,10 @@ def __init__(self, stream_id: int, data: bytes = b'', **kwargs: Any) -> None: def serialize_body(self) -> bytes: padding_data = self.serialize_padding_data() - padding = b'\0' * self.pad_length + padding = b"\0" * self.pad_length if isinstance(self.data, memoryview): self.data = self.data.tobytes() - return b''.join([padding_data, self.data, padding]) + return b"".join([padding_data, self.data, padding]) def parse_body(self, data: memoryview) -> None: padding_data_length = self.parse_padding_data(data) @@ -319,7 +314,8 @@ def parse_body(self, data: memoryview) -> None: self.body_len = len(data) if self.pad_length and self.pad_length >= self.body_len: - raise InvalidPaddingError("Padding is too long.") + msg = "Padding is too long." + raise InvalidPaddingError(msg) @property def flow_controlled_length(self) -> int: @@ -328,7 +324,7 @@ def flow_controlled_length(self) -> int: flow control. """ padding_len = 0 - if 'PADDED' in self.flags: + if "PADDED" in self.flags: # Account for extra 1-byte padding length field, which is still # present if possibly zero-valued. padding_len = self.pad_length + 1 @@ -341,6 +337,7 @@ class PriorityFrame(Priority, Frame): can be sent at any time for an existing stream. This enables reprioritisation of existing streams. """ + #: The flags defined for PRIORITY frames. defined_flags: list[Flag] = [] @@ -350,21 +347,15 @@ class PriorityFrame(Priority, Frame): stream_association = _STREAM_ASSOC_HAS_STREAM def _body_repr(self) -> str: - return "exclusive={}, depends_on={}, stream_weight={}".format( - self.exclusive, - self.depends_on, - self.stream_weight - ) + return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}" def serialize_body(self) -> bytes: return self.serialize_priority_data() def parse_body(self, data: memoryview) -> None: if len(data) > 5: - raise InvalidFrameError( - "PRIORITY must have 5 byte body: actual length %s." % - len(data) - ) + msg = f"PRIORITY must have 5 byte body: actual length {len(data)}." + raise InvalidFrameError(msg) self.parse_priority_data(data) self.body_len = 5 @@ -379,6 +370,7 @@ class RstStreamFrame(Frame): requesting that the stream be cancelled or that an error condition has occurred. """ + #: The flags defined for RST_STREAM frames. defined_flags: list[Flag] = [] @@ -394,24 +386,21 @@ def __init__(self, stream_id: int, error_code: int = 0, **kwargs: Any) -> None: self.error_code = error_code def _body_repr(self) -> str: - return "error_code={}".format( - self.error_code, - ) + return f"error_code={self.error_code}" def serialize_body(self) -> bytes: return _STRUCT_L.pack(self.error_code) def parse_body(self, data: memoryview) -> None: if len(data) != 4: - raise InvalidFrameError( - "RST_STREAM must have 4 byte body: actual length %s." % - len(data) - ) + msg = f"RST_STREAM must have 4 byte body: actual length {len(data)}." + raise InvalidFrameError(msg) try: self.error_code = _STRUCT_L.unpack(data)[0] - except struct.error: # pragma: no cover - raise InvalidFrameError("Invalid RST_STREAM body") + except struct.error as err: # pragma: no cover + msg = "Invalid RST_STREAM body" + raise InvalidFrameError(msg) from err self.body_len = 4 @@ -428,8 +417,9 @@ class SettingsFrame(Frame): might set a high initial flow control window, whereas a server might set a lower value to conserve resources. """ + #: The flags defined for SETTINGS frames. - defined_flags = [Flag('ACK', 0x01)] + defined_flags = [Flag("ACK", 0x01)] #: The type byte defined for SETTINGS frames. type = 0x04 @@ -453,39 +443,35 @@ class SettingsFrame(Frame): #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting. ENABLE_CONNECT_PROTOCOL = 0x08 - def __init__(self, stream_id: int = 0, settings: Optional[dict[int, int]] = None, **kwargs: Any) -> None: + def __init__(self, stream_id: int = 0, settings: dict[int, int] | None = None, **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) if settings and "ACK" in kwargs.get("flags", ()): - raise InvalidDataError( - "Settings must be empty if ACK flag is set." - ) + msg = "Settings must be empty if ACK flag is set." + raise InvalidDataError(msg) #: A dictionary of the setting type byte to the value of the setting. self.settings: dict[int, int] = settings or {} def _body_repr(self) -> str: - return "settings={}".format( - self.settings, - ) + return f"settings={self.settings}" def serialize_body(self) -> bytes: - return b''.join([_STRUCT_HL.pack(setting & 0xFF, value) + return b"".join([_STRUCT_HL.pack(setting & 0xFF, value) for setting, value in self.settings.items()]) def parse_body(self, data: memoryview) -> None: - if 'ACK' in self.flags and len(data) > 0: - raise InvalidDataError( - "SETTINGS ack frame must not have payload: got %s bytes" % - len(data) - ) + if "ACK" in self.flags and len(data) > 0: + msg = f"SETTINGS ack frame must not have payload: got {len(data)} bytes" + raise InvalidDataError(msg) body_len = 0 for i in range(0, len(data), 6): try: name, value = _STRUCT_HL.unpack(data[i:i+6]) - except struct.error: - raise InvalidFrameError("Invalid SETTINGS body") + except struct.error as err: + msg = "Invalid SETTINGS body" + raise InvalidFrameError(msg) from err self.settings[name] = value body_len += 6 @@ -498,10 +484,11 @@ class PushPromiseFrame(Padding, Frame): The PUSH_PROMISE frame is used to notify the peer endpoint in advance of streams the sender intends to initiate. """ + #: The flags defined for PUSH_PROMISE frames. defined_flags = [ - Flag('END_HEADERS', 0x04), - Flag('PADDED', 0x08) + Flag("END_HEADERS", 0x04), + Flag("PADDED", 0x08), ] #: The type byte defined for PUSH_PROMISE frames. @@ -509,7 +496,7 @@ class PushPromiseFrame(Padding, Frame): stream_association = _STREAM_ASSOC_HAS_STREAM - def __init__(self, stream_id: int, promised_stream_id: int = 0, data: bytes = b'', **kwargs: Any) -> None: + def __init__(self, stream_id: int, promised_stream_id: int = 0, data: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) #: The stream ID that is promised by this frame. @@ -520,26 +507,24 @@ def __init__(self, stream_id: int, promised_stream_id: int = 0, data: bytes = b' self.data = data def _body_repr(self) -> str: - return "promised_stream_id={}, data={}".format( - self.promised_stream_id, - _raw_data_repr(self.data), - ) + return f"promised_stream_id={self.promised_stream_id}, data={_raw_data_repr(self.data)}" def serialize_body(self) -> bytes: padding_data = self.serialize_padding_data() - padding = b'\0' * self.pad_length + padding = b"\0" * self.pad_length data = _STRUCT_L.pack(self.promised_stream_id) - return b''.join([padding_data, data, self.data, padding]) + return b"".join([padding_data, data, self.data, padding]) def parse_body(self, data: memoryview) -> None: padding_data_length = self.parse_padding_data(data) try: self.promised_stream_id = _STRUCT_L.unpack( - data[padding_data_length:padding_data_length + 4] + data[padding_data_length:padding_data_length + 4], )[0] - except struct.error: - raise InvalidFrameError("Invalid PUSH_PROMISE body") + except struct.error as err: + msg = "Invalid PUSH_PROMISE body" + raise InvalidFrameError(msg) from err self.data = ( data[padding_data_length + 4:len(data)-self.pad_length].tobytes() @@ -547,13 +532,12 @@ def parse_body(self, data: memoryview) -> None: self.body_len = len(data) if self.promised_stream_id == 0 or self.promised_stream_id % 2 != 0: - raise InvalidDataError( - "Invalid PUSH_PROMISE promised stream id: %s" % - self.promised_stream_id - ) + msg = f"Invalid PUSH_PROMISE promised stream id: {self.promised_stream_id}" + raise InvalidDataError(msg) if self.pad_length and self.pad_length >= self.body_len: - raise InvalidPaddingError("Padding is too long.") + msg = "Padding is too long." + raise InvalidPaddingError(msg) class PingFrame(Frame): @@ -562,41 +546,37 @@ class PingFrame(Frame): the sender, as well as determining whether an idle connection is still functional. PING frames can be sent from any endpoint. """ + #: The flags defined for PING frames. - defined_flags = [Flag('ACK', 0x01)] + defined_flags = [Flag("ACK", 0x01)] #: The type byte defined for PING frames. type = 0x06 stream_association = _STREAM_ASSOC_NO_STREAM - def __init__(self, stream_id: int = 0, opaque_data: bytes = b'', **kwargs: Any) -> None: + def __init__(self, stream_id: int = 0, opaque_data: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) #: The opaque data sent in this PING frame, as a bytestring. self.opaque_data = opaque_data def _body_repr(self) -> str: - return "opaque_data={!r}".format( - self.opaque_data, - ) + return f"opaque_data={self.opaque_data!r}" def serialize_body(self) -> bytes: if len(self.opaque_data) > 8: - raise InvalidFrameError( - "PING frame may not have more than 8 bytes of data, got %r" % - self.opaque_data - ) + msg = f"PING frame may not have more than 8 bytes of data, got {len(self.opaque_data)}" + raise InvalidFrameError(msg) data = self.opaque_data - data += b'\x00' * (8 - len(self.opaque_data)) + data += b"\x00" * (8 - len(self.opaque_data)) return data def parse_body(self, data: memoryview) -> None: if len(data) != 8: - raise InvalidFrameError( - "PING frame must have 8 byte length: got %s" % len(data) - ) + msg = f"PING frame must have 8 byte length: got {len(data)}" + raise InvalidFrameError(msg) self.opaque_data = data.tobytes() self.body_len = 8 @@ -609,6 +589,7 @@ class GoAwayFrame(Frame): sender will ignore frames sent on new streams for the remainder of the connection. """ + #: The flags defined for GOAWAY frames. defined_flags: list[Flag] = [] @@ -621,7 +602,7 @@ def __init__(self, stream_id: int = 0, last_stream_id: int = 0, error_code: int = 0, - additional_data: bytes = b'', + additional_data: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) @@ -635,16 +616,12 @@ def __init__(self, self.additional_data = additional_data def _body_repr(self) -> str: - return "last_stream_id={}, error_code={}, additional_data={!r}".format( - self.last_stream_id, - self.error_code, - self.additional_data, - ) + return f"last_stream_id={self.last_stream_id}, error_code={self.error_code}, additional_data={self.additional_data!r}" def serialize_body(self) -> bytes: data = _STRUCT_LL.pack( self.last_stream_id & 0x7FFFFFFF, - self.error_code + self.error_code, ) data += self.additional_data @@ -653,10 +630,11 @@ def serialize_body(self) -> bytes: def parse_body(self, data: memoryview) -> None: try: self.last_stream_id, self.error_code = _STRUCT_LL.unpack( - data[:8] + data[:8], ) - except struct.error: - raise InvalidFrameError("Invalid GOAWAY body.") + except struct.error as err: + msg = "Invalid GOAWAY body." + raise InvalidFrameError(msg) from err self.body_len = len(data) @@ -677,6 +655,7 @@ class WindowUpdateFrame(Frame): can indirectly cause the propagation of flow control information toward the original sender. """ + #: The flags defined for WINDOW_UPDATE frames. defined_flags: list[Flag] = [] @@ -692,29 +671,25 @@ def __init__(self, stream_id: int, window_increment: int = 0, **kwargs: Any) -> self.window_increment = window_increment def _body_repr(self) -> str: - return "window_increment={}".format( - self.window_increment, - ) + return f"window_increment={self.window_increment}" def serialize_body(self) -> bytes: return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF) def parse_body(self, data: memoryview) -> None: if len(data) > 4: - raise InvalidFrameError( - "WINDOW_UPDATE frame must have 4 byte length: got %s" % - len(data) - ) + msg = f"WINDOW_UPDATE frame must have 4 byte length: got {len(data)}" + raise InvalidFrameError(msg) try: self.window_increment = _STRUCT_L.unpack(data)[0] - except struct.error: - raise InvalidFrameError("Invalid WINDOW_UPDATE body") + except struct.error as err: + msg = "Invalid WINDOW_UPDATE body" + raise InvalidFrameError(msg) from err if not 1 <= self.window_increment <= 2**31-1: - raise InvalidDataError( - "WINDOW_UPDATE increment must be between 1 to 2^31-1" - ) + msg = "WINDOW_UPDATE increment must be between 1 to 2^31-1" + raise InvalidDataError(msg) self.body_len = 4 @@ -731,12 +706,13 @@ class HeadersFrame(Padding, Priority, Frame): to be followed with CONTINUATION frames. From the perspective of the frame building code the header block is an opaque data segment. """ + #: The flags defined for HEADERS frames. defined_flags = [ - Flag('END_STREAM', 0x01), - Flag('END_HEADERS', 0x04), - Flag('PADDED', 0x08), - Flag('PRIORITY', 0x20), + Flag("END_STREAM", 0x01), + Flag("END_HEADERS", 0x04), + Flag("PADDED", 0x08), + Flag("PRIORITY", 0x20), ] #: The type byte defined for HEADERS frames. @@ -744,36 +720,31 @@ class HeadersFrame(Padding, Priority, Frame): stream_association = _STREAM_ASSOC_HAS_STREAM - def __init__(self, stream_id: int, data: bytes = b'', **kwargs: Any) -> None: + def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) #: The HPACK-encoded header block. self.data = data def _body_repr(self) -> str: - return "exclusive={}, depends_on={}, stream_weight={}, data={}".format( - self.exclusive, - self.depends_on, - self.stream_weight, - _raw_data_repr(self.data), - ) + return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}, data={_raw_data_repr(self.data)}" def serialize_body(self) -> bytes: padding_data = self.serialize_padding_data() - padding = b'\0' * self.pad_length + padding = b"\0" * self.pad_length - if 'PRIORITY' in self.flags: + if "PRIORITY" in self.flags: priority_data = self.serialize_priority_data() else: - priority_data = b'' + priority_data = b"" - return b''.join([padding_data, priority_data, self.data, padding]) + return b"".join([padding_data, priority_data, self.data, padding]) def parse_body(self, data: memoryview) -> None: padding_data_length = self.parse_padding_data(data) data = data[padding_data_length:] - if 'PRIORITY' in self.flags: + if "PRIORITY" in self.flags: priority_data_length = self.parse_priority_data(data) else: priority_data_length = 0 @@ -784,7 +755,8 @@ def parse_body(self, data: memoryview) -> None: ) if self.pad_length and self.pad_length >= self.body_len: - raise InvalidPaddingError("Padding is too long.") + msg = "Padding is too long." + raise InvalidPaddingError(msg) class ContinuationFrame(Frame): @@ -797,24 +769,23 @@ class ContinuationFrame(Frame): Much like the HEADERS frame, hyper treats this as an opaque data frame with different flags and a different type. """ + #: The flags defined for CONTINUATION frames. - defined_flags = [Flag('END_HEADERS', 0x04)] + defined_flags = [Flag("END_HEADERS", 0x04)] #: The type byte defined for CONTINUATION frames. type = 0x09 stream_association = _STREAM_ASSOC_HAS_STREAM - def __init__(self, stream_id: int, data: bytes = b'', **kwargs: Any) -> None: + def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) #: The HPACK-encoded header block. self.data = data def _body_repr(self) -> str: - return "data={}".format( - _raw_data_repr(self.data), - ) + return f"data={_raw_data_repr(self.data)}" def serialize_body(self) -> bytes: return self.data @@ -839,29 +810,29 @@ class AltSvcFrame(Frame): another way, a valid ALTSVC frame has ``stream_id != 0`` XOR ``len(origin) != 0``. """ - type = 0xA + + type = 0x0A stream_association = _STREAM_ASSOC_EITHER - def __init__(self, stream_id: int, origin: bytes = b'', field: bytes = b'', **kwargs: Any) -> None: + def __init__(self, stream_id: int, origin: bytes = b"", field: bytes = b"", **kwargs: Any) -> None: super().__init__(stream_id, **kwargs) if not isinstance(origin, bytes): - raise InvalidDataError("AltSvc origin must be bytestring.") + msg = "AltSvc origin must be a bytestring." + raise InvalidDataError(msg) if not isinstance(field, bytes): - raise InvalidDataError("AltSvc field must be a bytestring.") + msg = "AltSvc field must be a bytestring." + raise InvalidDataError(msg) self.origin = origin self.field = field def _body_repr(self) -> str: - return "origin={!r}, field={!r}".format( - self.origin, - self.field, - ) + return f"origin={self.origin!r}, field={self.field!r}" def serialize_body(self) -> bytes: origin_len = _STRUCT_H.pack(len(self.origin)) - return b''.join([origin_len, self.origin, self.field]) + return b"".join([origin_len, self.origin, self.field]) def parse_body(self, data: memoryview) -> None: try: @@ -869,11 +840,13 @@ def parse_body(self, data: memoryview) -> None: self.origin = data[2:2+origin_len].tobytes() if len(self.origin) != origin_len: - raise InvalidFrameError("Invalid ALTSVC frame body.") + msg = "Invalid ALTSVC frame body." + raise InvalidFrameError(msg) self.field = data[2+origin_len:].tobytes() - except (struct.error, ValueError): - raise InvalidFrameError("Invalid ALTSVC frame body.") + except (struct.error, ValueError) as err: + msg = "Invalid ALTSVC frame body." + raise InvalidFrameError(msg) from err self.body_len = len(data) @@ -896,18 +869,14 @@ class ExtensionFrame(Frame): stream_association = _STREAM_ASSOC_EITHER - def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0, body: bytes = b'', **kwargs: Any) -> None: + def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0, body: bytes = b"", **kwargs: Any) -> None: # noqa: A002 super().__init__(stream_id, **kwargs) self.type = type self.flag_byte = flag_byte self.body = body def _body_repr(self) -> str: - return "type={}, flag_byte={}, body={}".format( - self.type, - self.flag_byte, - _raw_data_repr(self.body), - ) + return f"type={self.type}, flag_byte={self.flag_byte}, body={_raw_data_repr(self.body)}" def parse_flags(self, flag_byte: int) -> None: # type: ignore """ @@ -935,22 +904,22 @@ def serialize(self) -> bytes: self.body_len & 0xFF, self.type, flags, - self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. + self.stream_id & 0x7FFFFFFF, # Stream ID is 32 bits. ) return header + self.body -def _raw_data_repr(data: Optional[bytes]) -> str: +def _raw_data_repr(data: bytes | None) -> str: if not data: return "None" - r = binascii.hexlify(data).decode('ascii') + r = binascii.hexlify(data).decode("ascii") if len(r) > 20: r = r[:20] + "..." return "" -_FRAME_CLASSES: list[Type[Frame]] = [ +_FRAME_CLASSES: list[type[Frame]] = [ DataFrame, HeadersFrame, PriorityFrame, diff --git a/tests/test_flags.py b/tests/test_flags.py index f2f701b..17b6a98 100644 --- a/tests/test_flags.py +++ b/tests/test_flags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from hyperframe.frame import ( Flags, Flag, ) diff --git a/tests/test_frames.py b/tests/test_frames.py index ff91304..10f2300 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from hyperframe.frame import ( Frame, Flags, DataFrame, PriorityFrame, RstStreamFrame, SettingsFrame, PushPromiseFrame, PingFrame, GoAwayFrame, WindowUpdateFrame, HeadersFrame, From ab477bace67e22fe865c7d8379e4c4163c191e31 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Wed, 22 Jan 2025 22:14:51 +0100 Subject: [PATCH 13/14] v6.1.0 --- CHANGELOG.rst | 7 ++++--- pyproject.toml | 7 +++---- src/hyperframe/__init__.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 100af6c..dbf2ab5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,8 @@ Release History =============== -dev ---- +6.1.0 (2025-01-22) +------------------ **API Changes (Backward Incompatible)** @@ -16,8 +16,9 @@ dev - Support for Python 3.11 has been added. - Support for Python 3.12 has been added. - Support for Python 3.13 has been added. -- Improved type hints. - Updated packaging and testing infrastructure. +- Code cleanup and linting. +- Improved type hints. 6.0.1 (2021-04-17) ------------------ diff --git a/pyproject.toml b/pyproject.toml index 554ccfb..57ec56c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,10 +203,9 @@ commands = [ ] [tool.tox.env.publish] -base_python = "{[tool.tox.env.packaging]base_python}" -deps = "{[tool.tox.env.packaging]deps}" -allowlist_externals = "{[tool.tox.env.packaging]allowlist_externals}" +base_python = ["python39"] +dependency_groups = ["packaging"] +allowlist_externals = ["twine"] commands = [ - "{[testenv:packaging]commands}", ["twine", "upload", "dist/*"], ] diff --git a/src/hyperframe/__init__.py b/src/hyperframe/__init__.py index 6b9e72f..e95b20b 100644 --- a/src/hyperframe/__init__.py +++ b/src/hyperframe/__init__.py @@ -3,4 +3,4 @@ """ from __future__ import annotations -__version__ = "6.1.0+dev" +__version__ = "6.1.0" From b57beaff1cce7d7b7c38ea3514a349cb05a80d3c Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Wed, 22 Jan 2025 22:55:19 +0100 Subject: [PATCH 14/14] prepare for next release cycle --- CHANGELOG.rst | 11 +++++++++++ src/hyperframe/__init__.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dbf2ab5..3f6600e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,17 @@ Release History =============== +dev +--- + +**API Changes (Backward Incompatible)** + +- + +**Bugfixes** + +- + 6.1.0 (2025-01-22) ------------------ diff --git a/src/hyperframe/__init__.py b/src/hyperframe/__init__.py index e95b20b..e5650c4 100644 --- a/src/hyperframe/__init__.py +++ b/src/hyperframe/__init__.py @@ -3,4 +3,4 @@ """ from __future__ import annotations -__version__ = "6.1.0" +__version__ = "6.2.0+dev"