From 249b35f2b2706cc7bfc66c0a63ee1a31a83e2dd6 Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:42:51 +0530 Subject: [PATCH 1/8] feat: 3.13 support [Drops 3.8](https://endoflife.date/python) --- .github/workflows/run-tox-tests.yml | 4 ++-- README.rst | 6 +++--- docs/contribution.rst | 2 +- pyproject.toml | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/run-tox-tests.yml b/.github/workflows/run-tox-tests.yml index 845fe6c1..cd8afbad 100644 --- a/.github/workflows/run-tox-tests.yml +++ b/.github/workflows/run-tox-tests.yml @@ -19,11 +19,11 @@ jobs: matrix: os: [ubuntu-22.04, windows-latest] python-version: [ - "3.8", "3.9", "3.10", "3.11", "3.12", + "3.13-dev", # Not yet released, so we need to use -dev to get the release candidate "pypy-3.9", ] include: @@ -137,7 +137,7 @@ jobs: run: | git config --global --add safe.directory ${GITHUB_WORKSPACE} python3 -c "import platform;print('Machine type:', platform.machine())" - python3 -m tox -e py38 + python3 -m tox -e py39 env: | PYTEST_ADDOPTS: "--cov-config=pyproject.toml --cov --cov-append --benchmark-skip" diff --git a/README.rst b/README.rst index 78ff1530..92d04df3 100644 --- a/README.rst +++ b/README.rst @@ -337,7 +337,7 @@ Requirements `py7zr` uses a python3 standard `lzma module`_ for extraction and compression. The standard lzma module uses `liblzma`_ that support core compression algorithm of 7zip. -Minimum required version is Python 3.8. +Minimum required version is Python 3.9. ``py7zr`` tested on Linux, macOS, Windows and Ubuntu aarch64. @@ -345,8 +345,8 @@ It hopefully works on M1 Mac too. Recommended versions are: -- CPython 3.8.0 and later. -- PyPy3.8-7.3.8 and later. +- CPython 3.9.0 and later. +- PyPy3.9-7.3.8 and later. Following fixes are included in these versions, and it is not fixed on python3.6. diff --git a/docs/contribution.rst b/docs/contribution.rst index 0eb8566b..2641b339 100644 --- a/docs/contribution.rst +++ b/docs/contribution.rst @@ -20,7 +20,7 @@ The py7zr is written in the Python programming language. Python installation for various platforms with various ways. You need to install Python environment which support `pip` command. Venv/Virtualenv is recommended for development. -We have a test suite with python 3.6, 3.7, 3.8 and pypy3. +We have a test suite with python 3.9, 3.10, 3.11, 3.12, 3.13, and pypy3. If you want to run all the test with these versions and variant on your local, you should install these versions. You can run test with CI environment on Github actions. diff --git a/pyproject.toml b/pyproject.toml index 445634d9..02ec96c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "py7zr" -requires-python = ">=3.8" +requires-python = ">=3.9" description = "Pure python 7-zip library" license = {text = "LGPL-2.1-or-later"} authors = [ @@ -15,11 +15,11 @@ classifiers = [ "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", - "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 :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -167,7 +167,7 @@ markers = [ legacy_tox_ini = """ [tox] isolated_build = True -envlist = mypy, check, pypy{38,39,310}, py{38,39,310,311,312}, py39d, docs, mprof +envlist = mypy, check, pypy{39,310}, py{39,310,311,312,313}, py39d, docs, mprof [testenv] passenv = @@ -179,12 +179,12 @@ extras = test commands = python -m pytest -vv depends = - pypy{38,39,310},py{38,39,310,311,312}: clean, check + pypy{39,310},py{39,310,311,312,313}: clean, check -[testenv:py38] +[testenv:py39] extras = test, test_compat -[testenv:pypy38] +[testenv:pypy39] extras = test, test_compat [testenv:mprof] @@ -238,10 +238,10 @@ commands = [gh-actions] python = - 3.8: py38 3.9: py39, check, docs, mypy 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 pypy-3.9: pypy39 """ From 90bfb2af85556bd64c62da7bceda2eef59bd1224 Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:54:16 +0530 Subject: [PATCH 2/8] update actions --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/release-note.yml | 2 +- .github/workflows/run-benchmark.yml | 4 ++-- .github/workflows/run-tox-tests.yml | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 182cc626..235bb51c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release-note.yml b/.github/workflows/release-note.yml index 3d65d5d6..b40b2fba 100644 --- a/.github/workflows/release-note.yml +++ b/.github/workflows/release-note.yml @@ -7,7 +7,7 @@ jobs: create-release-notes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Create Release Notes uses: docker://decathlon/release-notes-generator-action:2.0.0 env: diff --git a/.github/workflows/run-benchmark.yml b/.github/workflows/run-benchmark.yml index 0ef756c7..e827c16b 100644 --- a/.github/workflows/run-benchmark.yml +++ b/.github/workflows/run-benchmark.yml @@ -13,9 +13,9 @@ jobs: env: ISSUE_NUMBER: 297 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 diff --git a/.github/workflows/run-tox-tests.yml b/.github/workflows/run-tox-tests.yml index cd8afbad..7c45e7a3 100644 --- a/.github/workflows/run-tox-tests.yml +++ b/.github/workflows/run-tox-tests.yml @@ -34,11 +34,11 @@ jobs: python-version: 'pypy-3.9' name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 20 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 @@ -49,7 +49,7 @@ jobs: sudo apt-get install -q -y libarchive-dev graphviz - name: Download libarchive(Windows) if: runner.os == 'Windows' - uses: carlosperate/download-file-action@v1.0.3 + uses: carlosperate/download-file-action@v2 with: file-url: "https://libarchive.org/downloads/libarchive-v3.5.3-win64.zip" file-name: "libarchive-v3.5.3-win64.zip" @@ -91,11 +91,11 @@ jobs: name: Test slow test cases runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 20 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' architecture: x64 @@ -121,10 +121,10 @@ jobs: distro: [ubuntu20.04] steps: - name: Checkout 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 20 - - uses: uraimo/run-on-arch-action@v2.2.0 + - uses: uraimo/run-on-arch-action@v2 name: Build & run test with: arch: ${{ matrix.arch }} From dc18a64d417eef128c44588ab0c0e9106a507d04 Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:42:51 +0530 Subject: [PATCH 3/8] feat: 3.13 support [Drops 3.8](https://endoflife.date/python) --- .github/workflows/run-tox-tests.yml | 4 ++-- README.rst | 6 +++--- docs/contribution.rst | 2 +- pyproject.toml | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/run-tox-tests.yml b/.github/workflows/run-tox-tests.yml index 845fe6c1..cd8afbad 100644 --- a/.github/workflows/run-tox-tests.yml +++ b/.github/workflows/run-tox-tests.yml @@ -19,11 +19,11 @@ jobs: matrix: os: [ubuntu-22.04, windows-latest] python-version: [ - "3.8", "3.9", "3.10", "3.11", "3.12", + "3.13-dev", # Not yet released, so we need to use -dev to get the release candidate "pypy-3.9", ] include: @@ -137,7 +137,7 @@ jobs: run: | git config --global --add safe.directory ${GITHUB_WORKSPACE} python3 -c "import platform;print('Machine type:', platform.machine())" - python3 -m tox -e py38 + python3 -m tox -e py39 env: | PYTEST_ADDOPTS: "--cov-config=pyproject.toml --cov --cov-append --benchmark-skip" diff --git a/README.rst b/README.rst index 78ff1530..92d04df3 100644 --- a/README.rst +++ b/README.rst @@ -337,7 +337,7 @@ Requirements `py7zr` uses a python3 standard `lzma module`_ for extraction and compression. The standard lzma module uses `liblzma`_ that support core compression algorithm of 7zip. -Minimum required version is Python 3.8. +Minimum required version is Python 3.9. ``py7zr`` tested on Linux, macOS, Windows and Ubuntu aarch64. @@ -345,8 +345,8 @@ It hopefully works on M1 Mac too. Recommended versions are: -- CPython 3.8.0 and later. -- PyPy3.8-7.3.8 and later. +- CPython 3.9.0 and later. +- PyPy3.9-7.3.8 and later. Following fixes are included in these versions, and it is not fixed on python3.6. diff --git a/docs/contribution.rst b/docs/contribution.rst index 0eb8566b..2641b339 100644 --- a/docs/contribution.rst +++ b/docs/contribution.rst @@ -20,7 +20,7 @@ The py7zr is written in the Python programming language. Python installation for various platforms with various ways. You need to install Python environment which support `pip` command. Venv/Virtualenv is recommended for development. -We have a test suite with python 3.6, 3.7, 3.8 and pypy3. +We have a test suite with python 3.9, 3.10, 3.11, 3.12, 3.13, and pypy3. If you want to run all the test with these versions and variant on your local, you should install these versions. You can run test with CI environment on Github actions. diff --git a/pyproject.toml b/pyproject.toml index 445634d9..02ec96c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "py7zr" -requires-python = ">=3.8" +requires-python = ">=3.9" description = "Pure python 7-zip library" license = {text = "LGPL-2.1-or-later"} authors = [ @@ -15,11 +15,11 @@ classifiers = [ "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", - "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 :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -167,7 +167,7 @@ markers = [ legacy_tox_ini = """ [tox] isolated_build = True -envlist = mypy, check, pypy{38,39,310}, py{38,39,310,311,312}, py39d, docs, mprof +envlist = mypy, check, pypy{39,310}, py{39,310,311,312,313}, py39d, docs, mprof [testenv] passenv = @@ -179,12 +179,12 @@ extras = test commands = python -m pytest -vv depends = - pypy{38,39,310},py{38,39,310,311,312}: clean, check + pypy{39,310},py{39,310,311,312,313}: clean, check -[testenv:py38] +[testenv:py39] extras = test, test_compat -[testenv:pypy38] +[testenv:pypy39] extras = test, test_compat [testenv:mprof] @@ -238,10 +238,10 @@ commands = [gh-actions] python = - 3.8: py38 3.9: py39, check, docs, mypy 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 pypy-3.9: pypy39 """ From 59a19e39319eb17249e9b1f7ca58701debcc804b Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:54:16 +0530 Subject: [PATCH 4/8] update actions --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/release-note.yml | 2 +- .github/workflows/run-benchmark.yml | 4 ++-- .github/workflows/run-tox-tests.yml | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 182cc626..235bb51c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release-note.yml b/.github/workflows/release-note.yml index 3d65d5d6..b40b2fba 100644 --- a/.github/workflows/release-note.yml +++ b/.github/workflows/release-note.yml @@ -7,7 +7,7 @@ jobs: create-release-notes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Create Release Notes uses: docker://decathlon/release-notes-generator-action:2.0.0 env: diff --git a/.github/workflows/run-benchmark.yml b/.github/workflows/run-benchmark.yml index 0ef756c7..e827c16b 100644 --- a/.github/workflows/run-benchmark.yml +++ b/.github/workflows/run-benchmark.yml @@ -13,9 +13,9 @@ jobs: env: ISSUE_NUMBER: 297 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 diff --git a/.github/workflows/run-tox-tests.yml b/.github/workflows/run-tox-tests.yml index cd8afbad..7c45e7a3 100644 --- a/.github/workflows/run-tox-tests.yml +++ b/.github/workflows/run-tox-tests.yml @@ -34,11 +34,11 @@ jobs: python-version: 'pypy-3.9' name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 20 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 @@ -49,7 +49,7 @@ jobs: sudo apt-get install -q -y libarchive-dev graphviz - name: Download libarchive(Windows) if: runner.os == 'Windows' - uses: carlosperate/download-file-action@v1.0.3 + uses: carlosperate/download-file-action@v2 with: file-url: "https://libarchive.org/downloads/libarchive-v3.5.3-win64.zip" file-name: "libarchive-v3.5.3-win64.zip" @@ -91,11 +91,11 @@ jobs: name: Test slow test cases runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 20 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' architecture: x64 @@ -121,10 +121,10 @@ jobs: distro: [ubuntu20.04] steps: - name: Checkout 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 20 - - uses: uraimo/run-on-arch-action@v2.2.0 + - uses: uraimo/run-on-arch-action@v2 name: Build & run test with: arch: ${{ matrix.arch }} From a810f1a502020b02dd52f9fa3aab3e517c88fa9d Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:22:24 +0530 Subject: [PATCH 5/8] fix: aarch64 --- .github/workflows/run-tox-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tox-tests.yml b/.github/workflows/run-tox-tests.yml index 7c45e7a3..77aa7bcf 100644 --- a/.github/workflows/run-tox-tests.yml +++ b/.github/workflows/run-tox-tests.yml @@ -118,7 +118,7 @@ jobs: strategy: matrix: arch: [aarch64] - distro: [ubuntu20.04] + distro: [ubuntu22.04] steps: - name: Checkout 🛎️ uses: actions/checkout@v4 @@ -137,7 +137,7 @@ jobs: run: | git config --global --add safe.directory ${GITHUB_WORKSPACE} python3 -c "import platform;print('Machine type:', platform.machine())" - python3 -m tox -e py39 + python3 -m tox -e py310 env: | PYTEST_ADDOPTS: "--cov-config=pyproject.toml --cov --cov-append --benchmark-skip" From 6a55b3fa9bcd975182379d4345898272c5a3d333 Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:42:39 +0530 Subject: [PATCH 6/8] chore: old style strings to f-strings --- py7zr/archiveinfo.py | 20 +++++++++----------- py7zr/cli.py | 37 ++++++++++++++++--------------------- py7zr/compressor.py | 20 +++++++------------- py7zr/py7zr.py | 10 +++++----- py7zr/win32compat.py | 2 +- 5 files changed, 38 insertions(+), 51 deletions(-) diff --git a/py7zr/archiveinfo.py b/py7zr/archiveinfo.py index ba1d471c..2d6b8042 100644 --- a/py7zr/archiveinfo.py +++ b/py7zr/archiveinfo.py @@ -486,7 +486,7 @@ def __init__(self): def _read(self, file: BinaryIO): pid = file.read(1) if pid != PROPERTY.FOLDER: - raise Bad7zFile("folder id expected but %s found" % repr(pid)) # pragma: no-cover + raise Bad7zFile(f"folder id expected but {repr(pid)} found") # pragma: no-cover self.numfolders = read_uint64(file) self.folders = [] external = read_byte(file) @@ -503,7 +503,7 @@ def _read(self, file: BinaryIO): def _retrieve_coders_info(self, file: BinaryIO): pid = file.read(1) if pid != PROPERTY.CODERS_UNPACK_SIZE: - raise Bad7zFile("coders unpack size id expected but %s found" % repr(pid)) # pragma: no-cover + raise Bad7zFile(f"coders unpack size id expected but {repr(pid)} found") # pragma: no-cover for folder in self.folders: for c in folder.coders: for _ in range(c["numoutstreams"]): @@ -517,9 +517,7 @@ def _retrieve_coders_info(self, file: BinaryIO): folder.crc = crcs[idx] pid = file.read(1) if pid != PROPERTY.END: - raise Bad7zFile( - "end id expected but 0x{:02x} found at 0x{:08x}".format(ord(pid), file.tell()) - ) # pragma: no-cover # noqa + raise Bad7zFile(f"end id expected but 0x{ord(pid):02x} found at 0x{file.tell():08x}") # pragma: no-cover # noqa def write(self, file: Union[BinaryIO, WriteWithCrc]): assert self.numfolders == len(self.folders) @@ -606,7 +604,7 @@ def _read(self, file: BinaryIO, numfolders: int, folders: List[Folder]): didx += 1 pid = file.read(1) if pid != PROPERTY.END: - raise Bad7zFile("end id expected but %r found" % pid) # pragma: no-cover + raise Bad7zFile(f"end id expected but {repr(pid)} found") # pragma: no-cover if not self.digestsdefined: self.digestsdefined = [False] * num_digests_total self.digests = [0] * num_digests_total @@ -667,7 +665,7 @@ def read(self, file: BinaryIO) -> None: self.substreamsinfo = SubstreamsInfo.retrieve(file, self.unpackinfo.numfolders, self.unpackinfo.folders) pid = file.read(1) if pid != PROPERTY.END: - raise Bad7zFile("end id expected but %s found" % repr(pid)) # pragma: no-cover + raise Bad7zFile(f"end id expected but {repr(pid)} found") # pragma: no-cover def write(self, file: Union[BinaryIO, WriteWithCrc]): write_byte(file, PROPERTY.MAIN_STREAMS_INFO) @@ -765,7 +763,7 @@ def _read(self, fp: BinaryIO): elif prop == PROPERTY.START_POS: self._read_start_pos(buffer) else: - raise Bad7zFile("invalid type %r" % prop) # pragma: no-cover + raise Bad7zFile(f"invalid type {repr(prop)}") # pragma: no-cover def _read_name(self, buffer: BinaryIO) -> None: for f in self.files: @@ -934,7 +932,7 @@ def _read(self, fp: BinaryIO, buffer: BytesIO, start_pos: int, password) -> None self._extract_header_info(buffer) return if pid != PROPERTY.ENCODED_HEADER: - raise TypeError("Unknown field: %r" % pid) # pragma: no-cover + raise TypeError(f"Unknown field: {repr(pid)}") # pragma: no-cover # get from encoded header streams = HeaderStreamsInfo.retrieve(buffer) buffer2 = io.BytesIO() @@ -964,7 +962,7 @@ def _read(self, fp: BinaryIO, buffer: BytesIO, start_pos: int, password) -> None buffer2.seek(0, 0) pid = buffer2.read(1) if pid != PROPERTY.HEADER: - raise TypeError("Unknown field: %r" % pid) # pragma: no-cover + raise TypeError(f"Unknown field: {repr(pid)}") # pragma: no-cover self._extract_header_info(buffer2) def _encode_header(self, file: BinaryIO, afterheader: int, filters): @@ -1027,7 +1025,7 @@ def _extract_header_info(self, fp: BinaryIO) -> None: self.files_info = FilesInfo.retrieve(fp) pid = fp.read(1) if pid != PROPERTY.END: - raise Bad7zFile("end id expected but %s found" % (repr(pid))) # pragma: no-cover + raise Bad7zFile(f"end id expected but {repr(pid)} found") # pragma: no-cover @staticmethod def build_header(filters, password): diff --git a/py7zr/cli.py b/py7zr/cli.py index 760afeea..ba88d8b7 100644 --- a/py7zr/cli.py +++ b/py7zr/cli.py @@ -54,7 +54,7 @@ def report_start_preparation(self): pass def report_start(self, processing_file_path, processing_bytes): - self.ofd.write("- {}".format(processing_file_path)) + self.ofd.write(f"- {processing_file_path}") self.pwidth += len(processing_file_path) + 2 def report_update(self, decompressed_bytes): @@ -64,7 +64,7 @@ def report_end(self, processing_file_path, wrote_bytes): self.total_bytes += int(wrote_bytes) plest = self.columns - self.pwidth progress = self.total_bytes / self.archive_total - msg = "({:.0%})\n".format(progress) + msg = f"({progress:.0%})\n" if plest - len(msg) > 0: self.ofd.write(msg.rjust(plest)) else: @@ -158,13 +158,8 @@ def _get_version(): py_version = platform.python_version() py_impl = platform.python_implementation() py_build = platform.python_compiler() - return "{} Version {} : {} (Python {} [{} {}])".format( - module_name, - py7zr.__version__, - py7zr.__copyright__, - py_version, - py_impl, - py_build, + return ( + f"{module_name} Version {py7zr.__version__} : {py7zr.__copyright__} (Python {py_version} [{py_impl} {py_build}])" ) def show_help(self, args): @@ -180,7 +175,7 @@ def run_info(self, args): table.set_cols_dtype(["t", "t"]) table.set_cols_align(["l", "r"]) for f in SupportedMethods.formats: - m = "".join(" {:02x}".format(x) for x in f["magic"]) + m = "".join(f" {x:02x}" for x in f["magic"]) table.add_row([str(f["name"]), m]) print(table.draw()) print("\nCodecs and hashes:") @@ -190,7 +185,7 @@ def run_info(self, args): table.set_cols_align(["l", "r"]) for c in SupportedMethods.methods: method_id: bytes = c["id"] - m = "".join("{:02x}".format(x) for x in method_id) + m = "".join(f"{x:02x}" for x in method_id) method_name: str = c["name"] table.add_row([m, method_name]) table.add_row(["0", "CRC32"]) @@ -228,19 +223,19 @@ def _run_list(self, target, verbose): file_name: str = target.name # type: ignore else: file_name = str(target) - file.write("Listing archive: {}\n".format(file_name)) + file.write(f"Listing archive: {file_name}\n") file.write("--\n") - file.write("Path = {}\n".format(archive_info.filename)) + file.write(f"Path = {archive_info.filename}\n") file.write("Type = 7z\n") fstat = archive_info.stat - file.write("Physical Size = {}\n".format(fstat.st_size)) - file.write("Headers Size = {}\n".format(archive_info.header_size)) + file.write(f"Physical Size = {fstat.st_size}\n") + file.write(f"Headers Size = {archive_info.header_size}\n") file.write("Method = {}\n".format(", ".join(archive_info.method_names))) if archive_info.solid: file.write("Solid = {}\n".format("+")) else: file.write("Solid = {}\n".format("-")) - file.write("Blocks = {}\n".format(archive_info.blocks)) + file.write(f"Blocks = {archive_info.blocks}\n") file.write("\n") file.write( "total %d files and directories in %sarchive\n" @@ -287,17 +282,17 @@ def _run_list(self, target, verbose): @staticmethod def print_archiveinfo(archive, file): file.write("--\n") - file.write("Path = {}\n".format(archive.filename)) + file.write(f"Path = {archive.filename}\n") file.write("Type = 7z\n") fstat = os.stat(archive.filename) - file.write("Physical Size = {}\n".format(fstat.st_size)) - file.write("Headers Size = {}\n".format(archive.header.size)) + file.write(f"Physical Size = {fstat.st_size}\n") + file.write(f"Headers Size = {archive.header.size}\n") file.write("Method = {}\n".format(", ".join(archive._get_method_names()))) if archive._is_solid(): file.write("Solid = {}\n".format("+")) else: file.write("Solid = {}\n".format("-")) - file.write("Blocks = {}\n".format(len(archive.header.main_streams.unpackinfo.folders))) + file.write(f"Blocks = {len(archive.header.main_streams.unpackinfo.folders)}\n") def run_test(self, args): target = args.arcfile @@ -308,7 +303,7 @@ def run_test(self, args): try: a = py7zr.SevenZipFile(f) file = sys.stdout - file.write("Testing archive: {}\n".format(a.filename)) + file.write(f"Testing archive: {a.filename}\n") self.print_archiveinfo(archive=a, file=file) file.write("\n") if a.testzip() is None: diff --git a/py7zr/compressor.py b/py7zr/compressor.py index 3938705d..8828dc09 100644 --- a/py7zr/compressor.py +++ b/py7zr/compressor.py @@ -372,11 +372,11 @@ def encode_filter_properties(cls, filter: Dict[str, Union[str, int]]): elif mem.lower().endswith("b") and mem[:-1].isdecimal(): size = int(mem[:-1]) else: - raise ValueError("Ppmd:Unsupported memory size is specified: {0}".format(mem)) + raise ValueError(f"Ppmd:Unsupported memory size is specified: {mem}") elif isinstance(mem, int): size = 1 << mem else: - raise ValueError("Ppmd:Unsupported memory size is specified: {0}".format(mem)) + raise ValueError(f"Ppmd:Unsupported memory size is specified: {mem}") properties = struct.pack(" (brotli_major, brotli_minor): raise UnsupportedCompressionMethodError( properties, - "Unsupported brotli version: {}.{} our {}.{}".format( - properties[0], properties[1], brotli_major, brotli_minor - ), + f"Unsupported brotli version: {properties[0]}.{properties[1]} our {brotli_major}.{brotli_minor}", ) self._prefix_checked = False self._decompressor = brotli.Decompressor() @@ -605,9 +603,7 @@ def __init__( else: self.block_size = get_default_blocksize() if len(coders) > 4: - raise UnsupportedCompressionMethodError( - coders, "Maximum cascade of filters is 4 but got {}.".format(len(coders)) - ) + raise UnsupportedCompressionMethodError(coders, f"Maximum cascade of filters is 4 but got {len(coders)}.") self.methods_map = [SupportedMethods.is_native_coder(coder) for coder in coders] # type: List[bool] # Check if password given for encrypted archive if SupportedMethods.needs_password(coders) and password is None: @@ -775,10 +771,10 @@ def _get_alternative_decompressor( if SupportedMethods.is_native_coder(coder): raise UnsupportedCompressionMethodError(coder, "Unknown method code:{}".format(coder["method"])) if filter_id not in algorithm_class_map: - raise UnsupportedCompressionMethodError(coder, "Unknown method filter_id:{}".format(filter_id)) + raise UnsupportedCompressionMethodError(coder, f"Unknown method filter_id:{filter_id}") if algorithm_class_map[filter_id][1] is None: raise UnsupportedCompressionMethodError( - coder, "Decompression is not supported by {}.".format(SupportedMethods.get_method_name_id(filter_id)) + coder, f"Decompression is not supported by {SupportedMethods.get_method_name_id(filter_id)}." ) # if SupportedMethods.is_crypto_id(filter_id): @@ -819,9 +815,7 @@ def __init__(self, filters=None, password=None, blocksize: Optional[int] = None) else: self.filters = filters if len(self.filters) > 4: - raise UnsupportedCompressionMethodError( - filters, "Maximum cascade of filters is 4 but got {}.".format(len(self.filters)) - ) + raise UnsupportedCompressionMethodError(filters, f"Maximum cascade of filters is 4 but got {len(self.filters)}.") self.methods_map = [SupportedMethods.is_native_filter(filter) for filter in self.filters] self.coders: List[Dict[str, Any]] = [] if all(self.methods_map) and SupportedMethods.is_compressor(self.filters[-1]): # all native diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 8ef81d30..99414628 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -389,7 +389,7 @@ def __init__( self.filename = getattr(file, "name", None) self.mode = mode # noqa else: - raise TypeError("invalid file: {}".format(type(file))) + raise TypeError(f"invalid file: {type(file)}") self.encoded_header_mode = True self.header_encryption = header_encryption self._fileRefCnt = 1 @@ -619,9 +619,9 @@ def _extract( if target_dir.is_dir(): pass elif target_dir.is_file(): - raise DecompressionError("Directory {} is existed as a normal file.".format(str(target_dir))) + raise DecompressionError(f"Directory {target_dir} is existed as a normal file.") else: - raise DecompressionError("Directory {} making fails on unknown condition.".format(str(target_dir))) + raise DecompressionError(f"Directory {target_dir} making fails on unknown condition.") if callback is not None: self.worker.extract( @@ -1238,7 +1238,7 @@ def is_7zfile(file: Union[BinaryIO, str, pathlib.Path]) -> bool: with file.open(mode="rb") as fp: # noqa result = SevenZipFile._check_7zfile(fp) else: - raise TypeError("invalid type: file should be str, pathlib.Path or BinaryIO, but {}".format(type(file))) + raise TypeError(f"invalid type: file should be str, pathlib.Path or BinaryIO, but {type(file)}") except OSError: pass return result @@ -1258,7 +1258,7 @@ def pack_7zarchive(base_name, base_dir, owner=None, group=None, dry_run=None, lo """ Function for registering with shutil.register_archive_format(). """ - target_name = "{}.7z".format(base_name) + target_name = f"{base_name}.7z" with SevenZipFile(target_name, mode="w") as archive: archive.writeall(path=base_dir) return target_name diff --git a/py7zr/win32compat.py b/py7zr/win32compat.py index f700ebb1..5331cea2 100644 --- a/py7zr/win32compat.py +++ b/py7zr/win32compat.py @@ -161,7 +161,7 @@ def readlink(path: Union[str, pathlib.Path]) -> Union[str, pathlib.WindowsPath]: CloseHandle(handle) if not status: logger = getLogger(__file__) - logger.error("Failed IOCTL access to REPARSE_POINT {})".format(target)) + logger.error(f"Failed IOCTL access to REPARSE_POINT {target})") raise ValueError("not a symbolic link or access permission violation") if buf.reparse_tag == IO_REPARSE_TAG_SYMLINK: From 4d7256b1a52a8ba153c8970108f02f71e6ca133a Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:50:13 +0530 Subject: [PATCH 7/8] chore: replace deprecated type-hints --- py7zr/archiveinfo.py | 60 ++++++++++++++++++++--------------------- py7zr/cli.py | 8 +++--- py7zr/compressor.py | 42 ++++++++++++++--------------- py7zr/helpers.py | 4 +-- py7zr/py7zr.py | 63 ++++++++++++++++++++++---------------------- 5 files changed, 89 insertions(+), 88 deletions(-) diff --git a/py7zr/archiveinfo.py b/py7zr/archiveinfo.py index 2d6b8042..52362e89 100644 --- a/py7zr/archiveinfo.py +++ b/py7zr/archiveinfo.py @@ -31,7 +31,7 @@ from io import BytesIO from operator import and_, or_ from struct import pack, unpack -from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Union +from typing import Any, BinaryIO, Optional, Union from py7zr.compressor import SevenZipCompressor, SevenZipDecompressor from py7zr.exceptions import Bad7zFile @@ -58,7 +58,7 @@ def tell(self): return self._fp.tell() -def read_crcs(file: BinaryIO, count: int) -> List[int]: +def read_crcs(file: BinaryIO, count: int) -> list[int]: data = file.read(4 * count) return [unpack(" Tuple[int, bytes]: +def read_real_uint64(file: BinaryIO) -> tuple[int, bytes]: """read 8 bytes, return unpacked value as a little endian unsigned long long, and raw data.""" res = file.read(8) a = unpack(" Tuple[int, bytes]: +def read_uint32(file: BinaryIO) -> tuple[int, bytes]: """read 4 bytes, return unpacked value as a little endian unsigned long, and raw data.""" res = file.read(4) a = unpack(" List[bool]: +def read_boolean(file: BinaryIO, count: int, checkall: bool = False) -> list[bool]: if checkall: all_defined = file.read(1) if all_defined != unhexlify("00"): @@ -191,7 +191,7 @@ def read_boolean(file: BinaryIO, count: int, checkall: bool = False) -> List[boo return result -def write_boolean(file: Union[BinaryIO, WriteWithCrc], booleans: List[bool], all_defined: bool = False): +def write_boolean(file: Union[BinaryIO, WriteWithCrc], booleans: list[bool], all_defined: bool = False): if all_defined and reduce(and_, booleans, True): file.write(b"\x01") return @@ -242,9 +242,9 @@ class PackInfo: def __init__(self) -> None: self.packpos: int = 0 self.numstreams: int = 0 - self.packsizes: List[int] = [] - self.digestdefined: List[bool] = [] - self.crcs: List[int] = [] + self.packsizes: list[int] = [] + self.digestdefined: list[bool] = [] + self.crcs: list[int] = [] self.enable_digests: bool = True @classmethod @@ -267,7 +267,7 @@ def _read(self, file: BinaryIO): pid = file.read(1) if pid != PROPERTY.END: raise Bad7zFile("end id expected but %s found" % repr(pid)) # pragma: no-cover # noqa - self.packpositions = [sum(self.packsizes[:i]) for i in range(self.numstreams + 1)] # type: List[int] + self.packpositions: list[int] = [sum(self.packsizes[:i]) for i in range(self.numstreams + 1)] self.enable_digests = len(self.crcs) > 0 return self @@ -329,10 +329,10 @@ class Folder: ] def __init__(self) -> None: - self.unpacksizes: List[int] = [] - self.coders: List[Dict[str, Any]] = [] - self.bindpairs: List[Bond] = [] - self.packed_indices: List[int] = [] + self.unpacksizes: list[int] = [] + self.coders: list[dict[str, Any]] = [] + self.bindpairs: list[Bond] = [] + self.packed_indices: list[int] = [] # calculated values # internal values self.solid: bool = False @@ -361,7 +361,7 @@ def _read(self, file: BinaryIO) -> None: iscomplex = b & 0x10 == 0x10 hasattributes = b & 0x20 == 0x20 if methodsize > 0: - c: Dict[str, Any] = {"method": file.read(methodsize)} + c: dict[str, Any] = {"method": file.read(methodsize)} else: c = {"method": b"\x00"} if iscomplex: @@ -552,18 +552,18 @@ class SubstreamsInfo: ] def __init__(self): - self.digests: List[int] = [] - self.digestsdefined: List[bool] = [] - self.unpacksizes: Optional[List[int]] = None - self.num_unpackstreams_folders: List[int] = [] + self.digests: list[int] = [] + self.digestsdefined: list[bool] = [] + self.unpacksizes: Optional[list[int]] = None + self.num_unpackstreams_folders: list[int] = [] @classmethod - def retrieve(cls, file: BinaryIO, numfolders: int, folders: List[Folder]): + def retrieve(cls, file: BinaryIO, numfolders: int, folders: list[Folder]): obj = cls() obj._read(file, numfolders, folders) return obj - def _read(self, file: BinaryIO, numfolders: int, folders: List[Folder]): + def _read(self, file: BinaryIO, numfolders: int, folders: list[Folder]): pid = file.read(1) if pid == PROPERTY.NUM_UNPACK_STREAM: self.num_unpackstreams_folders = [read_uint64(file) for _ in range(numfolders)] @@ -702,8 +702,8 @@ class FilesInfo: __slots__ = ["files", "emptyfiles", "antifiles"] def __init__(self): - self.files: List[Dict[str, Any]] = [] - self.emptyfiles: List[bool] = [] + self.files: list[dict[str, Any]] = [] + self.emptyfiles: list[bool] = [] self.antifiles = None @classmethod @@ -769,7 +769,7 @@ def _read_name(self, buffer: BinaryIO) -> None: for f in self.files: f["filename"] = read_utf16(buffer).replace("\\", "/") - def _read_attributes(self, buffer: BinaryIO, defined: List[bool]) -> None: + def _read_attributes(self, buffer: BinaryIO, defined: list[bool]) -> None: for idx, f in enumerate(self.files): f["attributes"] = read_uint32(buffer)[0] if defined[idx] else None @@ -791,8 +791,8 @@ def _read_start_pos(self, fp: BinaryIO) -> None: def _write_times(self, fp: Union[BinaryIO, WriteWithCrc], propid, name: str) -> None: write_byte(fp, propid) - defined = [] # type: List[bool] - num_defined = 0 # type: int + defined: list[bool] = [] + num_defined = 0 for f in self.files: if name in f.keys(): if f[name] is not None: @@ -840,7 +840,7 @@ def _write_names(self, file: Union[BinaryIO, WriteWithCrc]): write_utf16(file, n) def _write_attributes(self, file): - defined = [] # type: List[bool] + defined: list[bool] = [] num_defined = 0 for f in self.files: if "attributes" in f.keys() and f["attributes"] is not None: @@ -908,7 +908,7 @@ def __init__(self) -> None: self._start_pos: int = 0 self.password: Optional[str] = None self._initialized: bool = False - self.filters: Optional[List[Dict[str, int]]] = None + self.filters: Optional[list[dict[str, int]]] = None @classmethod def retrieve(cls, fp: BinaryIO, buffer: BytesIO, start_pos: int, password=None): @@ -1091,10 +1091,10 @@ class SignatureHeader: ] def __init__(self) -> None: - self.version = ( + self.version: tuple[bytes, ...] = ( P7ZIP_MAJOR_VERSION, P7ZIP_MINOR_VERSION, - ) # type: Tuple[bytes, ...] + ) self.startheadercrc: int = -1 self.nextheaderofs: int = -1 self.nextheadersize: int = -1 diff --git a/py7zr/cli.py b/py7zr/cli.py index ba88d8b7..71ed9eba 100644 --- a/py7zr/cli.py +++ b/py7zr/cli.py @@ -29,7 +29,7 @@ import shutil import sys from lzma import CHECK_CRC64, CHECK_SHA256, is_check_supported -from typing import Any, List, Optional +from typing import Any, Optional import _lzma # type: ignore import multivolumefile @@ -395,8 +395,8 @@ def _volumesize_unitconv(self, size: str) -> int: return -1 def run_create(self, args): - sztarget = args.arcfile # type: str - filenames = args.filenames # type: List[str] + sztarget: str = args.arcfile + filenames: list[str] = args.filenames volume_size = args.volume[0] if getattr(args, "volume", None) is not None else None if volume_size is not None and not self._check_volumesize_valid(volume_size): sys.stderr.write("Error: Specified volume size is invalid.\n") @@ -440,7 +440,7 @@ def run_create(self, args): def run_append(self, args): sztarget: str = args.arcfile - filenames: List[str] = args.filenames + filenames: list[str] = args.filenames if not sztarget.endswith(".7z"): sys.stderr.write("Error: specified archive file is invalid.") self.show_help(args) diff --git a/py7zr/compressor.py b/py7zr/compressor.py index 8828dc09..54776544 100644 --- a/py7zr/compressor.py +++ b/py7zr/compressor.py @@ -28,7 +28,7 @@ import zlib from abc import ABC, abstractmethod from enum import Enum -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union import bcj import inflate64 @@ -359,7 +359,7 @@ def _decode_property(self, properties): return order, mem @classmethod - def encode_filter_properties(cls, filter: Dict[str, Union[str, int]]): + def encode_filter_properties(cls, filter: dict[str, Union[str, int]]): order = filter.get("order", 8) mem = filter.get("mem", 24) if isinstance(mem, str): @@ -543,7 +543,7 @@ def decompress(self, data: Union[bytes, bytearray, memoryview], max_length: int return self.decompressor.decompress(data) -algorithm_class_map: Dict[int, Tuple[Any, Any]] = { +algorithm_class_map: dict[int, tuple[Any, Any]] = { FILTER_ZSTD: (ZstdCompressor, ZstdDecompressor), FILTER_BROTLI: (BrotliCompressor, BrotliDecompressor), FILTER_PPMD: (PpmdCompressor, PpmdDecompressor), @@ -586,9 +586,9 @@ class SevenZipDecompressor: def __init__( self, - coders: List[Dict[str, Any]], + coders: list[dict[str, Any]], packsize: int, - unpacksizes: List[int], + unpacksizes: list[int], crc: Optional[int], password: Optional[str] = None, blocksize: Optional[int] = None, @@ -604,7 +604,7 @@ def __init__( self.block_size = get_default_blocksize() if len(coders) > 4: raise UnsupportedCompressionMethodError(coders, f"Maximum cascade of filters is 4 but got {len(coders)}.") - self.methods_map = [SupportedMethods.is_native_coder(coder) for coder in coders] # type: List[bool] + self.methods_map: list[bool] = [SupportedMethods.is_native_coder(coder) for coder in coders] # Check if password given for encrypted archive if SupportedMethods.needs_password(coders) and password is None: raise PasswordRequired(coders, "Password is required for extracting given archive.") @@ -630,8 +630,8 @@ def __init__( if target_compressor and has_bcj: self.methods_map[bcj_index] = False break - self.chain = [] # type: List[Union[bz2.BZ2Decompressor, lzma.LZMADecompressor, ISevenZipDecompressor]] - self._unpacksizes = [] # type: List[int] + self.chain: list[Union[bz2.BZ2Decompressor, lzma.LZMADecompressor, ISevenZipDecompressor]] = [] + self._unpacksizes: list[int] = [] self.input_size = self.input_size shift = 0 prev = False @@ -733,8 +733,8 @@ def check_crc(self): def unused_size(self): return len(self._unused) - def _get_lzma_decompressor(self, coders: List[Dict[str, Any]], unpacksize: int): - filters: List[Dict[str, Any]] = [] + def _get_lzma_decompressor(self, coders: list[dict[str, Any]], unpacksize: int): + filters: list[dict[str, Any]] = [] lzma1 = False for coder in coders: if coder["numinstreams"] != 1 or coder["numoutstreams"] != 1: @@ -755,7 +755,7 @@ def _get_lzma_decompressor(self, coders: List[Dict[str, Any]], unpacksize: int): return lzma.LZMADecompressor(format=lzma.FORMAT_RAW, filters=filters) def _get_alternative_decompressor( - self, coder: Dict[str, Any], unpacksize=None, password=None + self, coder: dict[str, Any], unpacksize=None, password=None ) -> Union[bz2.BZ2Decompressor, lzma.LZMADecompressor, ISevenZipDecompressor]: # noqa filter_id = SupportedMethods.get_filter_id(coder) # Special treatment for BCJ filters @@ -801,11 +801,11 @@ class SevenZipCompressor: ] def __init__(self, filters=None, password=None, blocksize: Optional[int] = None): - self.filters: List[Dict[str, Any]] = [] - self.chain: List[ISevenZipCompressor] = [] + self.filters: list[dict[str, Any]] = [] + self.chain: list[ISevenZipCompressor] = [] self.digest = 0 self.packsize = 0 - self._unpacksizes: List[int] = [] + self._unpacksizes: list[int] = [] if blocksize: self._block_size = blocksize else: @@ -817,7 +817,7 @@ def __init__(self, filters=None, password=None, blocksize: Optional[int] = None) if len(self.filters) > 4: raise UnsupportedCompressionMethodError(filters, f"Maximum cascade of filters is 4 but got {len(self.filters)}.") self.methods_map = [SupportedMethods.is_native_filter(filter) for filter in self.filters] - self.coders: List[Dict[str, Any]] = [] + self.coders: list[dict[str, Any]] = [] if all(self.methods_map) and SupportedMethods.is_compressor(self.filters[-1]): # all native self._set_native_compressors_coders(self.filters) return @@ -923,8 +923,8 @@ def flush(self, fp): return len(data) @property - def unpacksizes(self) -> List[int]: - result: List[int] = [] + def unpacksizes(self) -> list[int]: + result: list[int] = [] shift = 0 prev = False for i, r in enumerate(self.methods_map): @@ -943,8 +943,8 @@ class MethodsType(Enum): class SupportedMethods: """Hold list of methods.""" - formats: List[Dict[str, Any]] = [{"name": "7z", "magic": MAGIC_7Z}] - methods: List[Dict[str, Any]] = [ + formats: list[dict[str, Any]] = [{"name": "7z", "magic": MAGIC_7Z}] + methods: list[dict[str, Any]] = [ { "id": COMPRESSION_METHOD.COPY, "name": "COPY", @@ -1145,7 +1145,7 @@ def get_method_id(cls, filter_id) -> bytes: return method["id"] @classmethod - def get_coder(cls, filter) -> Dict[str, Any]: + def get_coder(cls, filter) -> dict[str, Any]: method = cls.get_method_id(filter["id"]) if filter["id"] in [lzma.FILTER_LZMA1, lzma.FILTER_LZMA2, lzma.FILTER_DELTA]: properties: Optional[bytes] = lzma._encode_filter_properties(filter) # type: ignore # noqa @@ -1193,7 +1193,7 @@ def raise_unsupported_filter_id(cls, filter_id): ) -def get_methods_names(coders_lists: List[List[dict]]) -> List[str]: +def get_methods_names(coders_lists: list[list[dict]]) -> list[str]: # list of known method names with a display priority order methods_namelist = [ diff --git a/py7zr/helpers.py b/py7zr/helpers.py index c6d00b72..d137072e 100644 --- a/py7zr/helpers.py +++ b/py7zr/helpers.py @@ -30,7 +30,7 @@ import time as _time import zlib from datetime import datetime, timedelta, timezone, tzinfo -from typing import BinaryIO, List, Optional, Union +from typing import BinaryIO, Optional, Union import py7zr.win32compat from py7zr import Bad7zFile @@ -448,7 +448,7 @@ def remove_trailing_slash(path: str) -> str: def canonical_path(target: pathlib.PurePath) -> pathlib.PurePath: """Return a canonical path of target argument.""" - stack: List[str] = [] + stack: list[str] = [] for p in target.parts: if p != ".." or len(stack) == 0: stack.append(p) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 99414628..f9156031 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -36,10 +36,11 @@ import stat import sys import time +from collections.abc import Collection from multiprocessing import Process from shutil import ReadError from threading import Thread -from typing import IO, Any, BinaryIO, Collection, Dict, List, Optional, Tuple, Type, Union +from typing import IO, Any, BinaryIO, Optional, Union import multivolumefile @@ -87,11 +88,11 @@ class ArchiveFile: The class also hold an archive parameter where file is exist in archive file folder(container).""" - def __init__(self, id: int, file_info: Dict[str, Any]) -> None: + def __init__(self, id: int, file_info: dict[str, Any]) -> None: self.id = id self._file_info = file_info - def file_properties(self) -> Dict[str, Any]: + def file_properties(self) -> dict[str, Any]: """Return file properties as a hash object. Following keys are included: ‘readonly’, ‘is_directory’, ‘posix_mode’, ‘archivable’, ‘emptystream’, ‘filename’, ‘creationtime’, ‘lastaccesstime’, ‘lastwritetime’, ‘attributes’ @@ -129,7 +130,7 @@ def emptystream(self) -> bool: return self._get_property("emptystream") @property - def uncompressed(self) -> List[int]: + def uncompressed(self) -> list[int]: return self._get_property("uncompressed") @property @@ -240,11 +241,11 @@ class ArchiveFileList(collections.abc.Iterable): """Iterable container of ArchiveFile.""" def __init__(self, offset: int = 0): - self.files_list: List[dict] = [] + self.files_list: list[dict] = [] self.index = 0 self.offset = offset - def append(self, file_info: Dict[str, Any]) -> None: + def append(self, file_info: dict[str, Any]) -> None: self.files_list.append(file_info) def __len__(self) -> int: @@ -286,10 +287,10 @@ def __init__( filename: str, stat: os.stat_result, header_size: int, - method_names: List[str], + method_names: list[str], solid: bool, blocks: int, - uncompressed: List[int], + uncompressed: list[int], ): self.stat = stat self.filename = filename @@ -331,7 +332,7 @@ def __init__( file: Union[BinaryIO, str, pathlib.Path], mode: str = "r", *, - filters: Optional[List[Dict[str, int]]] = None, + filters: Optional[list[dict[str, int]]] = None, dereference=False, password: Optional[str] = None, header_encryption: bool = False, @@ -415,7 +416,7 @@ def __init__( except Exception as e: self._fpclose() raise e - self._dict: Dict[str, IO[Any]] = {} + self._dict: dict[str, IO[Any]] = {} self.dereference = dereference self.reporterd: Optional[Thread] = None self.q: queue.Queue[Any] = queue.Queue() @@ -538,7 +539,7 @@ def _extract( return_dict: bool = False, callback: Optional[ExtractCallback] = None, recursive: Optional[bool] = False, - ) -> Optional[Dict[str, IO[Any]]]: + ) -> Optional[dict[str, IO[Any]]]: if callback is None: pass elif isinstance(callback, ExtractCallback): @@ -546,8 +547,8 @@ def _extract( self.reporterd.start() else: raise ValueError("Callback specified is not an instance of subclass of py7zr.callbacks.ExtractCallback class") - target_files: List[Tuple[pathlib.Path, Dict[str, Any]]] = [] - target_dirs: List[pathlib.Path] = [] + target_files: list[tuple[pathlib.Path, dict[str, Any]]] = [] + target_dirs: list[pathlib.Path] = [] if path is not None: if isinstance(path, str): path = pathlib.Path(path) @@ -564,7 +565,7 @@ def _extract( if targets is not None: # faster lookups targets = set(targets) - fnames: Dict[str, int] = {} # check duplicated filename in one archive? + fnames: dict[str, int] = {} # check duplicated filename in one archive? self.q.put(("pre", None, None)) for f in self.files: if targets is not None and recursive is False: @@ -790,7 +791,7 @@ def _check_7zfile(fp: Union[BinaryIO, io.BufferedReader, io.IOBase]) -> bool: # A new empty file raises OSError return False - def _get_method_names(self) -> List[str]: + def _get_method_names(self) -> list[str]: try: return get_methods_names([folder.coders for folder in self.header.main_streams.unpackinfo.folders]) except KeyError: @@ -821,8 +822,8 @@ def _var_release(self): del self.sig_header @staticmethod - def _make_file_info(target: pathlib.Path, arcname: Optional[str] = None, dereference=False) -> Dict[str, Any]: - f: Dict[str, Any] = {} + def _make_file_info(target: pathlib.Path, arcname: Optional[str] = None, dereference=False) -> dict[str, Any]: + f: dict[str, Any] = {} f["origin"] = target if arcname is not None: f["filename"] = pathlib.Path(arcname).as_posix() @@ -903,8 +904,8 @@ def _make_file_info(target: pathlib.Path, arcname: Optional[str] = None, derefer f["lastaccesstime"] = ArchiveTimestamp.from_datetime(fstat.st_atime) return f - def _make_file_info_from_name(self, bio, size: int, arcname: str) -> Dict[str, Any]: - f: Dict[str, Any] = {} + def _make_file_info_from_name(self, bio, size: int, arcname: str) -> dict[str, Any]: + f: dict[str, Any] = {} f["origin"] = None f["data"] = bio f["filename"] = pathlib.Path(arcname).as_posix() @@ -947,13 +948,13 @@ def _is_none_or_collection(self, t): # -------------------------------------------------------------------------- # The public methods which SevenZipFile provides: - def getnames(self) -> List[str]: + def getnames(self) -> list[str]: """Return the members of the archive as a list of their names. It has the same order as the list returned by getmembers(). """ return self.namelist() - def namelist(self) -> List[str]: + def namelist(self) -> list[str]: """Return a list of archive members by name.""" return list(map(lambda x: x.filename, self.files)) @@ -995,9 +996,9 @@ def archiveinfo(self) -> ArchiveInfo: def needs_password(self) -> bool: return self.password_protected - def list(self) -> List[FileInfo]: + def list(self) -> list[FileInfo]: """Returns contents information""" - alist: List[FileInfo] = [] + alist: list[FileInfo] = [] lastmodified: Optional[datetime.datetime] = None for f in self.files: if f.lastwritetime is not None: @@ -1015,7 +1016,7 @@ def list(self) -> List[FileInfo]: ) return alist - def readall(self) -> Optional[Dict[str, IO[Any]]]: + def readall(self) -> Optional[dict[str, IO[Any]]]: self._dict = {} return self._extract(path=None, return_dict=True) @@ -1027,7 +1028,7 @@ def extractall(self, path: Optional[Any] = None, callback: Optional[ExtractCallb """ self._extract(path=path, return_dict=False, callback=callback) - def read(self, targets: Optional[Collection[str]] = None) -> Optional[Dict[str, IO[Any]]]: + def read(self, targets: Optional[Collection[str]] = None) -> Optional[dict[str, IO[Any]]]: if not self._is_none_or_collection(targets): raise TypeError("Wrong argument type given.") # For interoperability with ZipFile, we strip any trailing slashes @@ -1051,7 +1052,7 @@ def extract( def reporter(self, callback: ExtractCallback): while True: try: - item: Optional[Tuple[str, str, str]] = self.q.get(timeout=1) + item: Optional[tuple[str, str, str]] = self.q.get(timeout=1) except queue.Empty: pass else: @@ -1103,7 +1104,7 @@ def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None): self.files.append(file_info) self.worker.archive(self.fp, self.files, folder, deref=self.dereference) - def writed(self, targets: Dict[str, IO[Any]]) -> None: + def writed(self, targets: dict[str, IO[Any]]) -> None: for target, input in targets.items(): self.writef(input, target) @@ -1188,7 +1189,7 @@ def reset(self) -> None: def test(self) -> Optional[bool]: self.fp.seek(self.afterheader) self.worker = Worker(self.files, self.afterheader, self.header, self.mp) - crcs: Optional[List[int]] = self.header.main_streams.packinfo.crcs + crcs: Optional[list[int]] = self.header.main_streams.packinfo.crcs if crcs is None or len(crcs) == 0: return None packpos = self.afterheader + self.header.main_streams.packinfo.packpos @@ -1270,14 +1271,14 @@ class Worker: """ def __init__(self, files, src_start: int, header, mp=False) -> None: - self.target_filepath: Dict[int, Union[MemIO, pathlib.Path, None]] = {} + self.target_filepath: dict[int, Union[MemIO, pathlib.Path, None]] = {} self.files = files self.src_start = src_start self.header = header self.current_file_index = len(self.files) self.last_file_index = len(self.files) - 1 if mp: - self.concurrent: Union[Type[Thread], Type[Process]] = Process + self.concurrent: Union[type[Thread], type[Process]] = Process else: self.concurrent = Thread @@ -1393,7 +1394,7 @@ def _extract_single( Single thread extractor that takes file lists in single 7zip folder. this may raise exception. """ - just_check: List[ArchiveFile] = [] + just_check: list[ArchiveFile] = [] for f in files: if q is not None: q.put( From f219ae82dc766389425a94674408e70cceb90beb Mon Sep 17 00:00:00 2001 From: Ravencentric <78981416+Ravencentric@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:18:58 +0530 Subject: [PATCH 8/8] fix: remove code paths that checked for bool: """ Cross-platform islink implementation. Support Windows NT symbolic links and reparse points. """ - is_symlink = os.path.islink(str(path)) - if sys.version_info >= (3, 8) or sys.platform != "win32" or sys.getwindowsversion()[0] < 6: - return is_symlink - # special check for directory junctions which py38 does. - if is_symlink: - if py7zr.win32compat.is_reparse_point(path): - is_symlink = False - return is_symlink + return os.path.islink(path) def readlink(path: Union[str, pathlib.Path], *, dir_fd=None) -> Union[str, pathlib.Path]: @@ -289,23 +281,13 @@ def readlink(path: Union[str, pathlib.Path], *, dir_fd=None) -> Union[str, pathl When called with Path object, return also Path object. When called with path argument as bytes, return result as a bytes. """ - if sys.version_info >= (3, 9): - if isinstance(path, pathlib.Path) and dir_fd is None: - return path.readlink() - else: - return os.readlink(path, dir_fd=dir_fd) - elif sys.version_info >= (3, 8) or sys.platform != "win32": - res = os.readlink(path, dir_fd=dir_fd) - # Hack to handle a wrong type of results - if isinstance(res, bytes): - res = os.fsdecode(res) - if isinstance(path, pathlib.Path): - return pathlib.Path(res) - else: - return res - elif not os.path.exists(str(path)): + if not os.path.exists(str(path)): raise OSError(22, "Invalid argument", path) - return py7zr.win32compat.readlink(path) + + if isinstance(path, pathlib.Path) and dir_fd is None: + return path.readlink() + else: + return os.readlink(path, dir_fd=dir_fd) class MemIO: