From 3cfe240aae827c92e9ec61ea5876d8872c6f471b Mon Sep 17 00:00:00 2001 From: Rogdham Date: Fri, 9 Oct 2026 20:58:57 +0200 Subject: [PATCH] chore: update dev dependencies --- .github/workflows/build.yml | 21 ++++++++++++------- .github/workflows/check.yml | 22 ++++++++++++++++++++ CHANGELOG.md | 9 ++++++++ dev-requirements.txt | 10 ++++----- pyproject.toml | 2 ++ src/xz/block.py | 6 +++--- src/xz/common.py | 29 +++++++++++++------------- src/xz/io.py | 31 ++++++++++++++++------------ tests/integration/test_file_write.py | 2 +- tests/unit/test_ioabstract.py | 8 +++---- tox.ini | 16 +++++++------- 11 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a4908c..cd7a742 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,8 @@ on: - "**" pull_request: +permissions: {} + env: PY_COLORS: 1 @@ -26,9 +28,12 @@ jobs: - "pypy-3.10" - "pypy-3.11" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + submodules: true - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python }} - name: Install dependencies @@ -46,12 +51,14 @@ jobs: - lint - type steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # build env: fetch all commits for version computation fetch-depth: ${{ matrix.env == 'build' && '0' || '1' }} + persist-credentials: false + submodules: true - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - name: Install dependencies @@ -60,7 +67,7 @@ jobs: run: tox run -e ${{ matrix.env }} - if: ${{ matrix.env == 'build' }} name: Save ${{ matrix.env }} artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.env }} path: dist @@ -76,12 +83,12 @@ jobs: id-token: write # This permission is mandatory for trusted publishing steps: - name: Restore build artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: build path: dist - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 with: verbose: true print-hash: true diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..bb2aef5 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,22 @@ +name: check + +on: + push: + branches: + - "ci-*" + pull_request: + workflow_dispatch: + +permissions: {} + +jobs: + zizmor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: zizmor + uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 + with: + advanced-security: false diff --git a/CHANGELOG.md b/CHANGELOG.md index f963558..39fc32f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] + +[unreleased]: https://github.com/rogdham/python-xz/compare/v0.6.0...HEAD + +### :house: Internal + +- Update dev dependencies +- Necessary code changes following dev dependency update: mypy, ruff + ## [0.6.0] - 2025-10-18 [0.6.0]: https://github.com/rogdham/python-xz/releases/tag/v0.6.0 diff --git a/dev-requirements.txt b/dev-requirements.txt index 4cb3e16..70fda70 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,14 +2,14 @@ -e . # build -build==1.3.0 +build==1.6.1 # lint -ruff==0.13.1 +ruff==0.16.10 # tests -pytest==8.4.2 -pytest-cov==7.0.0 +pytest==9.1.1 +pytest-cov==7.1.0 # type -mypy==1.18.2 +mypy==2.4.0 diff --git a/pyproject.toml b/pyproject.toml index f56c53c..deeceb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,6 +145,7 @@ select = ["ALL"] ignore = [ "C901", "COM812", + "CPY001", "D", "E501", "EM", @@ -152,6 +153,7 @@ ignore = [ "FA100", "PLR0912", "PLR0913", + "PLR0917", "TRY003", "TRY301", ] diff --git a/src/xz/block.py b/src/xz/block.py index 9f31a8b..e8e95d7 100644 --- a/src/xz/block.py +++ b/src/xz/block.py @@ -90,7 +90,7 @@ def _write(self, data: bytes) -> None: self.fileobj.write(data) self.pos += len(data) - def compress(self, data: bytes) -> None: + def compress(self, data: bytes | memoryview) -> None: self._write(self.compressor.compress(data)) def finish(self) -> tuple[int, int]: @@ -136,7 +136,7 @@ def __init__( def uncompressed_size(self) -> int: return self._length - def _read(self, size: int) -> bytes: + def _read(self, size: int) -> bytes | memoryview: # enforce read mode if not isinstance(self.operation, BlockRead): self._write_end() @@ -164,7 +164,7 @@ def _read(self, size: int) -> bytes: def writable(self) -> bool: return isinstance(self.operation, BlockWrite) or not self._length - def _write(self, data: bytes) -> int: + def _write(self, data: memoryview) -> int: # enforce write mode if not isinstance(self.operation, BlockWrite): self.clear() diff --git a/src/xz/common.py b/src/xz/common.py index 1321418..99a4a94 100644 --- a/src/xz/common.py +++ b/src/xz/common.py @@ -1,6 +1,7 @@ # ruff: noqa: PLR2004 from binascii import crc32 as crc32int +from collections.abc import Iterable import lzma from struct import pack, unpack from typing import cast @@ -13,7 +14,7 @@ class XZError(Exception): pass -def encode_mbi(value: int) -> bytes: +def encode_mbi(value: int) -> bytearray: data = bytearray() while value >= 0x80: data.append((value & 0x7F) | 0x80) @@ -22,7 +23,7 @@ def encode_mbi(value: int) -> bytes: return data -def decode_mbi(data: bytes) -> tuple[int, int]: +def decode_mbi(data: Iterable[int]) -> tuple[int, int]: value = 0 for size, byte in enumerate(data): value |= (byte & 0x7F) << (size * 7) @@ -92,31 +93,31 @@ def parse_xz_header(header: bytes) -> int: def parse_xz_index(index: bytes) -> list[tuple[int, int]]: if len(index) < 8 or len(index) % 4: raise XZError("index length") - index = memoryview(index) - if index[0]: + index_view = memoryview(index) + if index_view[0]: raise XZError("index indicator") - if crc32(index[:-4]) != index[-4:]: + if crc32(index_view[:-4].tobytes()) != index_view[-4:]: raise XZError("index crc32") - size, nb_records = decode_mbi(index[1:]) - index = index[1 + size : -4] + size, nb_records = decode_mbi(index_view[1:]) + index_view = index_view[1 + size : -4] # records records = [] for _ in range(nb_records): - if not index: + if not index_view: raise XZError("index size") - size, unpadded_size = decode_mbi(index) + size, unpadded_size = decode_mbi(index_view) if not unpadded_size: raise XZError("index record unpadded size") - index = index[size:] - if not index: + index_view = index_view[size:] + if not index_view: raise XZError("index size") - size, uncompressed_size = decode_mbi(index) + size, uncompressed_size = decode_mbi(index_view) if not uncompressed_size: raise XZError("index record uncompressed size") - index = index[size:] + index_view = index_view[size:] records.append((unpadded_size, uncompressed_size)) # index padding - if any(index): + if any(index_view): raise XZError("index padding") return records diff --git a/src/xz/io.py b/src/xz/io.py index b03074f..0168630 100644 --- a/src/xz/io.py +++ b/src/xz/io.py @@ -135,8 +135,8 @@ def write(self, data: bytes) -> int: if padding_size > 0: null_bytes = memoryview(bytearray(DEFAULT_BUFFER_SIZE)) self._pos = self._length - data = memoryview(data) - while padding_size or data: + data_view = memoryview(data) + while padding_size or data_view: self._write_start() if padding_size > 0: # pad with null bytes, not counted in written_bytes @@ -144,8 +144,10 @@ def write(self, data: bytes) -> int: written_len = self._write(padding) # do not stop if nothing was written padding_size -= written_len else: - written_len = self._write(data) # do not stop if nothing was written - data = data[written_len:] + written_len = self._write( + data_view + ) # do not stop if nothing was written + data_view = data_view[written_len:] self._pos += written_len self._length = max(self._length, self._pos) return written_bytes @@ -186,7 +188,10 @@ def close(self) -> None: # the methods below are expected to be implemented by subclasses - def _read(self, size: int) -> bytes: # pragma: no cover # noqa: ARG002 + def _read( + self, + size: int, # noqa: ARG002 + ) -> bytes | memoryview: # pragma: no cover """Read and return up to size bytes, where size is an int. The size will not exceed the number of bytes between self._pos and @@ -203,7 +208,7 @@ def _write_before(self) -> None: def _write_after(self) -> None: """This method is called after the last write operation (usually on file close).""" - def _write(self, data: bytes) -> int: # pragma: no cover # noqa: ARG002 + def _write(self, data: memoryview) -> int: # pragma: no cover # noqa: ARG002 """Writes as many bytes from data as possible, and return the number of bytes written. @@ -229,13 +234,13 @@ def _truncate(self, size: int) -> None: # pragma: no cover # noqa: ARG002 class IOStatic(IOAbstract): def __init__(self, data: bytes) -> None: - self.data = bytearray(data) + self.data = memoryview(data) super().__init__(len(self.data)) def writable(self) -> bool: return False - def _read(self, size: int) -> bytes: + def _read(self, size: int) -> bytes | memoryview: return self.data[self._pos : self._pos + size] @@ -250,11 +255,11 @@ def __init__( self.fileobj = fileobj self.start = start - def _read(self, size: int) -> bytes: + def _read(self, size: int) -> bytes | memoryview: self.fileobj.seek(self.start + self._pos, SEEK_SET) return self.fileobj.read(size) # size already restricted by caller - def _write(self, data: bytes) -> int: + def _write(self, data: memoryview) -> int: self.fileobj.seek(self.start + self._pos, SEEK_SET) return self.fileobj.write(data) @@ -277,7 +282,7 @@ def _get_fileobj(self) -> T: fileobj.seek(self._pos - start, SEEK_SET) return fileobj - def _read(self, size: int) -> bytes: + def _read(self, size: int) -> bytes | memoryview: return self._get_fileobj().read(size) def _write_after(self) -> None: @@ -288,7 +293,7 @@ def _write_after(self) -> None: else: del self._fileobjs[self._fileobjs.last_key] - def _write(self, data: bytes) -> int: + def _write(self, data: memoryview) -> int: if self._fileobjs: fileobj: T | None = self._get_fileobj() else: @@ -300,7 +305,7 @@ def _write(self, data: bytes) -> int: # newly created fileobj should be writable # otherwise this will raise UnsupportedOperation - return fileobj.write(data) + return fileobj.write(data.tobytes()) def _truncate(self, size: int) -> None: start, fileobj = self._fileobjs.get_with_index(size) diff --git a/tests/integration/test_file_write.py b/tests/integration/test_file_write.py index 396096c..73c3654 100644 --- a/tests/integration/test_file_write.py +++ b/tests/integration/test_file_write.py @@ -27,7 +27,7 @@ def test( for block in stream["blocks"]: xzfile.filters = block.get("filters") xzfile.change_block() - xzfile.write(data[: block["length"]]) + xzfile.write(data[: block["length"]].tobytes()) data = data[block["length"] :] assert not data diff --git a/tests/unit/test_ioabstract.py b/tests/unit/test_ioabstract.py index 03d771c..4ca82ce 100644 --- a/tests/unit/test_ioabstract.py +++ b/tests/unit/test_ioabstract.py @@ -148,7 +148,7 @@ class Impl(IOAbstract): def __init__(self) -> None: super().__init__(10) - def _read(self, size: int) -> bytes: + def _read(self, size: int) -> bytes | memoryview: # for tests, does not rely on position return b"xyz"[:size] @@ -188,7 +188,7 @@ def __init__(self) -> None: super().__init__(10) self.empty_reads = 100 - def _read(self, size: int) -> bytes: # noqa: ARG002 + def _read(self, size: int) -> bytes | memoryview: # noqa: ARG002 self.empty_reads -= 1 if self.empty_reads > 0: return b"" @@ -232,7 +232,7 @@ def _write_before(self) -> None: def _write_after(self) -> None: self.mock.write_finish() - def _write(self, data: bytes) -> int: + def _write(self, data: memoryview) -> int: self.mock.write(bytes(data)) if write_partial: return min(2, len(data)) @@ -350,7 +350,7 @@ def _write_before(self) -> None: def _write_after(self) -> None: self.mock.write_finish() - def _write(self, data: bytes) -> int: # noqa: ARG002 + def _write(self, data: memoryview) -> int: # noqa: ARG002 raise RuntimeError("should not be called") def _truncate(self, size: int) -> None: diff --git a/tox.ini b/tox.ini index 80ff81f..c97abd2 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,8 @@ envlist = package = wheel wheel_build_env = .pkg # reuse same wheel across envs deps = - pytest==8.4.2 - pytest-cov==7.0.0 + pytest==9.1.1 + pytest-cov==7.1.0 passenv = PY_COLORS setenv = COVERAGE_FILE = {toxworkdir}/{envname}/.coverage @@ -22,14 +22,14 @@ commands = [testenv:build] skip_install = true deps = - build==1.3.0 + build==1.6.1 commands = python -m build [testenv:generate-integration-files] deps = - pytest==8.4.2 - pytest-cov==7.0.0 + pytest==9.1.1 + pytest-cov==7.1.0 passenv = PY_COLORS setenv = PYTEST_ADDOPTS = --no-cov @@ -38,15 +38,15 @@ commands = pytest -vv -m generate_integration_files --generate-integration-files [testenv:lint] deps = - ruff==0.13.1 + ruff==0.16.10 commands = ruff check src tests ruff format --check src tests [testenv:type] deps = - mypy==1.18.2 - pytest==8.4.2 # for typing + mypy==2.4.0 + pytest==9.1.1 # for typing commands = mypy mypy --namespace-packages --explicit-package-bases tests