From b38e418438d74cdf8bb464f8f87624e51f682053 Mon Sep 17 00:00:00 2001 From: Dan Watson Date: Wed, 8 May 2024 15:57:08 -0400 Subject: [PATCH] Modernize all the things --- .coveragerc | 2 - .github/dependabot.yml | 11 ++++ .github/workflows/ci.yml | 25 +++----- .gitignore | 1 + .pre-commit-config.yaml | 10 +++ .python-version | 1 + CHANGELOG.md | 12 ++++ pyproject.toml | 58 ++++++++++++++++++ requirements-dev.lock | 108 +++++++++++++++++++++++++++++++++ requirements.lock | 16 +++++ setup.cfg | 5 -- setup.py | 39 ------------ {pzip => src/pzip}/__init__.py | 2 +- {pzip => src/pzip}/__main__.py | 0 {pzip => src/pzip}/base.py | 10 +-- {pzip => src/pzip}/cli.py | 20 +----- {pzip => src/pzip}/reader.py | 0 {pzip => src/pzip}/writer.py | 2 +- tests.py => tests/test_pzip.py | 8 +-- 19 files changed, 236 insertions(+), 94 deletions(-) delete mode 100644 .coveragerc create mode 100644 .github/dependabot.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .python-version create mode 100644 CHANGELOG.md create mode 100644 pyproject.toml create mode 100644 requirements-dev.lock create mode 100644 requirements.lock delete mode 100644 setup.cfg delete mode 100644 setup.py rename {pzip => src/pzip}/__init__.py (97%) rename {pzip => src/pzip}/__main__.py (100%) rename {pzip => src/pzip}/base.py (97%) rename {pzip => src/pzip}/cli.py (95%) rename {pzip => src/pzip}/reader.py (100%) rename {pzip => src/pzip}/writer.py (99%) rename tests.py => tests/test_pzip.py (97%) diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 599fa21..0000000 --- a/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -source = pzip diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8b13673 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fceecc6..8677dcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,32 +6,21 @@ jobs: checks: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - name: Install Checkers - run: pip install flake8 black isort - - name: flake8 - run: flake8 - - name: black - run: black --check . - - name: isort - run: isort --check . + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 test: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Requirements - run: pip install cryptography + run: pip install -r requirements-dev.lock - name: Run Tests - run: python tests.py + run: pytest diff --git a/.gitignore b/.gitignore index 3e79e4b..579a72c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ /build /pip-wheel-metadata .vscode +.nova .coverage /htmlcov /site diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..21fbe1e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.4.3 + hooks: + # Run the linter. + - id: ruff + args: ["--fix"] + # Run the formatter. + - id: ruff-format diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..8531a3b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.2 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1412ff2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +## 1.1.0 (Unreleased) + +* Increased default PBKDF2 iterations to 600,000 +* Changed `DEFAULT_BLOCK_SIZE` from 256k to 128k +* Removed the `tqdm` optional requirement +* Switched to `pyproject.toml`, [hatchling](https://hatch.pypa.io/) for builds, and + [ruff](https://github.com/astral-sh/ruff) for formatting and linting + + +## 1.0.0 (2023-03-09) + +* Initial release diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9ea55d8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[project] +name = "pzip" +dynamic = ["version"] +description = "Crytographically secure file compression." +authors = [ + { name = "Dan Watson", email = "watsond@imsweb.com" } +] +dependencies = [ + "cryptography>=42.0.7", +] +readme = "README.md" +requires-python = ">= 3.8" +license = { text = "MIT" } +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Security", + "Topic :: System :: Archiving :: Compression", +] + +[project.optional-dependencies] +deflate = ["deflate"] + +[project.urls] +Homepage = "https://github.com/imsweb/pzip" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.rye] +managed = true +dev-dependencies = [ + "pytest>=8.2.0", + "pytest-cov>=5.0.0", + "mkdocs>=1.6.0", + "mkdocs-material>=9.5.21", + "pre-commit>=3.7.0", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["src/pzip"] + +[tool.hatch.version] +path = "src/pzip/__init__.py" + +[tool.ruff.lint] +extend-select = ["I"] +isort.known-first-party = ["pzip"] + +[tool.pytest.ini_options] +addopts = "--cov=pzip" diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 0000000..fcc34af --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,108 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +-e file:. +babel==2.15.0 + # via mkdocs-material +certifi==2024.2.2 + # via requests +cffi==1.16.0 + # via cryptography +cfgv==3.4.0 + # via pre-commit +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via mkdocs +colorama==0.4.6 + # via mkdocs-material +coverage==7.5.1 + # via pytest-cov +cryptography==42.0.7 + # via pzip +distlib==0.3.8 + # via virtualenv +filelock==3.14.0 + # via virtualenv +ghp-import==2.1.0 + # via mkdocs +identify==2.5.36 + # via pre-commit +idna==3.7 + # via requests +iniconfig==2.0.0 + # via pytest +jinja2==3.1.4 + # via mkdocs + # via mkdocs-material +markdown==3.6 + # via mkdocs + # via mkdocs-material + # via pymdown-extensions +markupsafe==2.1.5 + # via jinja2 + # via mkdocs +mergedeep==1.3.4 + # via mkdocs + # via mkdocs-get-deps +mkdocs==1.6.0 + # via mkdocs-material +mkdocs-get-deps==0.2.0 + # via mkdocs +mkdocs-material==9.5.21 +mkdocs-material-extensions==1.3.1 + # via mkdocs-material +nodeenv==1.8.0 + # via pre-commit +packaging==24.0 + # via mkdocs + # via pytest +paginate==0.5.6 + # via mkdocs-material +pathspec==0.12.1 + # via mkdocs +platformdirs==4.2.1 + # via mkdocs-get-deps + # via virtualenv +pluggy==1.5.0 + # via pytest +pre-commit==3.7.0 +pycparser==2.22 + # via cffi +pygments==2.18.0 + # via mkdocs-material +pymdown-extensions==10.8.1 + # via mkdocs-material +pytest==8.2.0 + # via pytest-cov +pytest-cov==5.0.0 +python-dateutil==2.9.0.post0 + # via ghp-import +pyyaml==6.0.1 + # via mkdocs + # via mkdocs-get-deps + # via pre-commit + # via pymdown-extensions + # via pyyaml-env-tag +pyyaml-env-tag==0.1 + # via mkdocs +regex==2024.4.28 + # via mkdocs-material +requests==2.31.0 + # via mkdocs-material +setuptools==69.5.1 + # via nodeenv +six==1.16.0 + # via python-dateutil +urllib3==2.2.1 + # via requests +virtualenv==20.26.1 + # via pre-commit +watchdog==4.0.0 + # via mkdocs diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..47ca844 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,16 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +-e file:. +cffi==1.16.0 + # via cryptography +cryptography==42.0.7 + # via pzip +pycparser==2.22 + # via cffi diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 06664a7..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -max-line-length = 88 - -[isort] -profile = black diff --git a/setup.py b/setup.py deleted file mode 100644 index da9aa3e..0000000 --- a/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import re - -from setuptools import find_packages, setup - -with open("README.md", "r") as readme: - long_description = readme.read() - -with open(os.path.join("pzip", "__init__.py"), "r") as src: - version = re.match(r'.*__version__ = "(.*?)"', src.read(), re.S).group(1) - -setup( - name="pzip", - version=version, - description="Crytographically secure file compression.", - long_description=long_description, - long_description_content_type="text/markdown", - author="Dan Watson", - author_email="watsond@imsweb.com", - url="https://github.com/imsweb/pzip", - license="MIT", - packages=find_packages(), - install_requires=["cryptography"], - extras_require={ - "deflate": ["deflate"], - "tqdm": ["tqdm"], - "all": ["deflate", "tqdm"], - }, - entry_points={"console_scripts": ["pzip=pzip.cli:main"]}, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Topic :: Security", - "Topic :: System :: Archiving :: Compression", - ], -) diff --git a/pzip/__init__.py b/src/pzip/__init__.py similarity index 97% rename from pzip/__init__.py rename to src/pzip/__init__.py index 0e72872..754f3fb 100644 --- a/pzip/__init__.py +++ b/src/pzip/__init__.py @@ -16,7 +16,7 @@ from .reader import PZipReader from .writer import PZipWriter -__version__ = "1.0.0" +__version__ = "1.1.0" __version_info__ = tuple(int(num) for num in __version__.split(".")) diff --git a/pzip/__main__.py b/src/pzip/__main__.py similarity index 100% rename from pzip/__main__.py rename to src/pzip/__main__.py diff --git a/pzip/base.py b/src/pzip/base.py similarity index 97% rename from pzip/base.py rename to src/pzip/base.py index b93c73f..8fffd96 100644 --- a/pzip/base.py +++ b/src/pzip/base.py @@ -13,7 +13,7 @@ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC try: - import deflate + import deflate # pyright: ignore[reportMissingImports] gzip_compress = deflate.gzip_compress gzip_decompress = deflate.gzip_decompress @@ -42,7 +42,7 @@ class InvalidFile(Exception): # Number of PBKDF2 iterations to use by default. May increase over time. -DEFAULT_ITERATIONS = 200000 +DEFAULT_ITERATIONS = 600_000 class Flag(enum.IntFlag): @@ -235,7 +235,7 @@ class Password(KeyMaterial): class PZip(io.RawIOBase): # First two bytes of any PZIP file. - MAGIC = b"\xB6\x9E" # ¶ž + MAGIC = b"\xb6\x9e" # ¶ž # PZip header format (for struct): # magic, version, flags, algorithm, kdf, compression, #tags @@ -245,8 +245,8 @@ class PZip(io.RawIOBase): HEADER_SIZE = struct.calcsize(HEADER_FORMAT) # Default plaintext block size when encrypting. - # Benchmarking suggests that block sizes in the 256k-1MB range perform best. - DEFAULT_BLOCK_SIZE = 2**18 # 256k + # Benchmarking suggests that block sizes in the 128k-1MB range perform best. + DEFAULT_BLOCK_SIZE = 2**17 # 128k def __init__( self, diff --git a/pzip/cli.py b/src/pzip/cli.py similarity index 95% rename from pzip/cli.py rename to src/pzip/cli.py index 6df3eb2..b28a84a 100644 --- a/pzip/cli.py +++ b/src/pzip/cli.py @@ -5,13 +5,6 @@ import secrets import sys -try: - import tqdm - - has_tqdm = True -except ImportError: - has_tqdm = False - from .base import InvalidFile, PZip from .reader import PZipReader from .writer import PZipWriter @@ -265,18 +258,7 @@ def main(*args): die("passwords did not match") for filename in files: infile, outfile, total = get_files(filename, mode, key, options) - progress = ( - tqdm.tqdm( - desc=filename, - total=total, - unit="B", - unit_scale=True, - unit_divisor=1024, - ) - if has_tqdm and filename and total and not options.quiet - else None - ) - copy(infile, outfile, progress=progress) + copy(infile, outfile) if filename and not options.keep: os.remove(filename) diff --git a/pzip/reader.py b/src/pzip/reader.py similarity index 100% rename from pzip/reader.py rename to src/pzip/reader.py diff --git a/pzip/writer.py b/src/pzip/writer.py similarity index 99% rename from pzip/writer.py rename to src/pzip/writer.py index 4b6a20a..5d646ec 100644 --- a/pzip/writer.py +++ b/src/pzip/writer.py @@ -26,7 +26,7 @@ def __init__( block_size=None, append_length=True, compress=None, - **kwargs + **kwargs, ): key = KeyMaterial.resolve(key) super().__init__(fileobj, **kwargs) diff --git a/tests.py b/tests/test_pzip.py similarity index 97% rename from tests.py rename to tests/test_pzip.py index 18aa46d..e7fd167 100644 --- a/tests.py +++ b/tests/test_pzip.py @@ -82,10 +82,10 @@ def test_bad_headers(self): TestPZip(io.BytesIO(b"PZ"), "rb") bad_headers = [ b"PZ", # bad magic - b"\xB6\x9E\x02", # bad version - b"\xB6\x9E\x01\x00\x02", # bad algorithm - b"\xB6\x9E\x01\x00\x01\x03", # bad kdf - b"\xB6\x9E\x01\x00\x01\x01\x02", # bad compression + b"\xb6\x9e\x02", # bad version + b"\xb6\x9e\x01\x00\x02", # bad algorithm + b"\xb6\x9e\x01\x00\x01\x03", # bad kdf + b"\xb6\x9e\x01\x00\x01\x01\x02", # bad compression ] for bad in bad_headers: pad = b"\x00" * (pzip.PZip.HEADER_SIZE - len(bad))