././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1749402395.1422381 zipp-3.23.0/0000755000175100001660000000000015021341433012160 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/.coveragerc0000644000175100001660000000057315021341407014307 0ustar00runnerdocker[run] omit = # leading `*/` for pytest-dev/pytest-cov#456 */.tox/* zipp/compat/py313.py disable_warnings = couldnt-parse [report] show_missing = True exclude_also = # Exclude common false positives per # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion # Ref jaraco/skeleton#97 and jaraco/skeleton#135 class .*\bProtocol\): if TYPE_CHECKING: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/.editorconfig0000644000175100001660000000036615021341407014643 0ustar00runnerdockerroot = true [*] charset = utf-8 indent_style = tab indent_size = 4 insert_final_newline = true end_of_line = lf [*.py] indent_style = space max_line_length = 88 [*.{yml,yaml}] indent_style = space indent_size = 2 [*.rst] indent_style = space ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.133238 zipp-3.23.0/.github/0000755000175100001660000000000015021341433013520 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/.github/FUNDING.yml0000644000175100001660000000002415021341407015332 0ustar00runnerdockertidelift: pypi/zipp ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.133238 zipp-3.23.0/.github/workflows/0000755000175100001660000000000015021341433015555 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/.github/workflows/main.yml0000644000175100001660000000631315021341407017230 0ustar00runnerdockername: tests on: merge_group: push: branches-ignore: # temporary GH branches relating to merge queues (jaraco/skeleton#93) - gh-readonly-queue/** tags: # required if branches-ignore is supplied (jaraco/skeleton#103) - '**' pull_request: workflow_dispatch: permissions: contents: read env: # Environment variable to support color support (jaraco/skeleton#66) FORCE_COLOR: 1 # Suppress noisy pip warnings PIP_DISABLE_PIP_VERSION_CHECK: 'true' PIP_NO_WARN_SCRIPT_LOCATION: 'true' # Ensure tests can sense settings about the environment TOX_OVERRIDE: >- testenv.pass_env+=GITHUB_*,FORCE_COLOR jobs: test: strategy: # https://blog.jaraco.com/efficient-use-of-ci-resources/ matrix: python: - "3.9" - "3.13" platform: - ubuntu-latest - macos-latest - windows-latest include: - python: "3.10" platform: ubuntu-latest - python: "3.11" platform: ubuntu-latest - python: "3.12" platform: ubuntu-latest - python: "3.14" platform: ubuntu-latest - python: pypy3.10 platform: ubuntu-latest runs-on: ${{ matrix.platform }} continue-on-error: ${{ matrix.python == '3.14' }} steps: - uses: actions/checkout@v4 - name: Install build dependencies # Install dependencies for building packages on pre-release Pythons # jaraco/skeleton#161 if: matrix.python == '3.14' && matrix.platform == 'ubuntu-latest' run: | sudo apt update sudo apt install -y libxml2-dev libxslt-dev - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} allow-prereleases: true - name: Install tox run: python -m pip install tox - name: Run run: tox collateral: strategy: fail-fast: false matrix: job: - diffcov - docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v5 with: python-version: 3.x - name: Install tox run: python -m pip install tox - name: Eval ${{ matrix.job }} run: tox -e ${{ matrix.job }} check: # This job does nothing and is only used for the branch protection if: always() needs: - test - collateral runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} release: permissions: contents: write needs: - check if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: 3.x - name: Install tox run: python -m pip install tox - name: Run run: tox -e release env: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/.pre-commit-config.yaml0000644000175100001660000000022615021341407016442 0ustar00runnerdockerrepos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.9 hooks: - id: ruff args: [--fix, --unsafe-fixes] - id: ruff-format ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/.readthedocs.yaml0000644000175100001660000000056515021341407015416 0ustar00runnerdockerversion: 2 python: install: - path: . extra_requirements: - doc sphinx: configuration: docs/conf.py # required boilerplate readthedocs/readthedocs.org#10401 build: os: ubuntu-lts-latest tools: python: latest # post-checkout job to ensure the clone isn't shallow jaraco/skeleton#114 jobs: post_checkout: - git fetch --unshallow || true ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402394.0 zipp-3.23.0/LICENSE0000644000175100001660000000206415021341432013166 0ustar00runnerdockerMIT License Copyright (c) 2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/NEWS.rst0000644000175100001660000001607015021341407013473 0ustar00runnerdockerv3.23.0 ======= Features -------- - Add a compatibility shim for Python 3.13 and earlier. (#145) v3.22.0 ======= Features -------- - Backported simplified tests from python/cpython#123424. (#142) Bugfixes -------- - Fixed ``.name``, ``.stem``, and other basename-based properties on Windows when working with a zipfile on disk. (#133) v3.21.0 ======= Features -------- - Improve performances of :meth:`zipfile.Path.open` for non-reading modes. (1a1928d) - Rely on cached_property to cache values on the instance. - Rely on save_method_args to save method args. v3.20.2 ======= Bugfixes -------- - Make zipp.compat.overlay.zipfile hashable. (#126) v3.20.1 ======= Bugfixes -------- - Replaced SanitizedNames with a more surgical fix for infinite loops, restoring support for names with special characters in the archive. (python/cpython#123270) v3.20.0 ======= Features -------- - Made the zipfile compatibility overlay available as zipp.compat.overlay. v3.19.3 ======= Bugfixes -------- - Also match directories in Path.glob. (#121) v3.19.2 ======= No significant changes. v3.19.1 ======= Bugfixes -------- - Improved handling of malformed zip files. (#119) v3.19.0 ======= Features -------- - Implement is_symlink. (#117) v3.18.2 ======= No significant changes. v3.18.1 ======= No significant changes. v3.18.0 ======= Features -------- - Bypass ZipFile.namelist in glob for better performance. (#106) - Refactored glob functionality to support a more generalized solution with support for platform-specific path separators. (#108) Bugfixes -------- - Add special accounting for pypy when computing the stack level for text encoding warnings. (#114) v3.17.0 ======= Features -------- - Added ``CompleteDirs.inject`` classmethod to make available for use elsewhere. Bugfixes -------- - Avoid matching path separators for '?' in glob. v3.16.2 ======= Bugfixes -------- - In ``Path.match``, Windows path separators are no longer honored. The fact that they were was incidental and never supported. (#92) - Fixed name/suffix/suffixes/stem operations when no filename is present and the Path is not at the root of the zipfile. (#96) - Reworked glob utilizing the namelist directly. (#101) v3.16.1 ======= Bugfixes -------- - Replaced the ``fnmatch.translate`` with a fresh glob-to-regex translator for more correct matching behavior. (#98) v3.16.0 ======= Features -------- - Require Python 3.8 or later. v3.15.0 ======= * gh-102209: ``test_implied_dirs_performance`` now tests measures the time complexity experimentally. v3.14.0 ======= * Minor cleanup in tests, including #93. v3.13.0 ======= * In tests, add a fallback when ``func_timeout`` isn't available. v3.12.1 ======= * gh-101566: In ``CompleteDirs``, override ``ZipFile.getinfo`` to supply a ``ZipInfo`` for implied dirs. v3.12.0 ======= * gh-101144: Honor ``encoding`` as positional parameter to ``Path.open()`` and ``Path.read_text()``. v3.11.0 ======= * #85: Added support for new methods on ``Path``: - ``match`` - ``glob`` and ``rglob`` - ``relative_to`` - ``is_symlink`` v3.10.0 ======= * ``zipp`` is now a package. v3.9.1 ====== * Removed 'print' expression in test_pickle. * bpo-43651: Apply ``io.text_encoding`` on Python 3.10 and later. v3.9.0 ====== * #81: ``Path`` objects are now pickleable if they've been constructed from pickleable objects. Any restored objects will re-construct the zip file with the original arguments. v3.8.1 ====== Refreshed packaging. Enrolled with Tidelift. v3.8.0 ====== Removed compatibility code. v3.7.0 ====== Require Python 3.7 or later. v3.6.0 ====== #78: Only ``Path`` is exposed in the public API. v3.5.1 ====== #77: Remove news file intended only for CPython. v3.5.0 ====== #74 and bpo-44095: Added ``.suffix``, ``.suffixes``, and ``.stem`` properties. v3.4.2 ====== Refresh package metadata. v3.4.1 ====== Refresh packaging. v3.4.0 ====== #68 and bpo-42090: ``Path.joinpath`` now takes arbitrary positional arguments and no longer accepts ``add`` as a keyword argument. v3.3.2 ====== Updated project metadata including badges. v3.3.1 ====== bpo-42043: Add tests capturing subclassing requirements. v3.3.0 ====== #9: ``Path`` objects now expose a ``.filename`` attribute and rely on that to resolve ``.name`` and ``.parent`` when the ``Path`` object is at the root of the zipfile. v3.2.0 ====== #57 and bpo-40564: Mutate the passed ZipFile object type instead of making a copy. Prevents issues when both the local copy and the caller's copy attempt to close the same file handle. #56 and bpo-41035: ``Path._next`` now honors subclasses. #55: ``Path.is_file()`` now returns False for non-existent names. v3.1.0 ====== #47: ``.open`` now raises ``FileNotFoundError`` and ``IsADirectoryError`` when appropriate. v3.0.0 ====== #44: Merge with v1.2.0. v1.2.0 ====== #44: ``zipp.Path.open()`` now supports a compatible signature as ``pathlib.Path.open()``, accepting text (default) or binary modes and soliciting keyword parameters passed through to ``io.TextIOWrapper`` (encoding, newline, etc). The stream is opened in text-mode by default now. ``open`` no longer accepts ``pwd`` as a positional argument and does not accept the ``force_zip64`` parameter at all. This change is a backward-incompatible change for that single function. v2.2.1 ====== #43: Merge with v1.1.1. v1.1.1 ====== #43: Restored performance of implicit dir computation. v2.2.0 ====== #36: Rebuild package with minimum Python version declared both in package metadata and in the python tag. v2.1.0 ====== #32: Merge with v1.1.0. v1.1.0 ====== #32: For read-only zip files, complexity of ``.exists`` and ``joinpath`` is now constant time instead of ``O(n)``, preventing quadratic time in common use-cases and rendering large zip files unusable for Path. Big thanks to Benjy Weinberger for the bug report and contributed fix (#33). v2.0.1 ====== #30: Corrected version inference (from jaraco/skeleton#12). v2.0.0 ====== Require Python 3.6 or later. v1.0.0 ====== Re-release of 0.6 to correspond with release as found in Python 3.8. v0.6.0 ====== #12: When adding implicit dirs, ensure that ancestral directories are added and that duplicates are excluded. The library now relies on `more_itertools `_. v0.5.2 ====== #7: Parent of a directory now actually returns the parent. v0.5.1 ====== Declared package as backport. v0.5.0 ====== Add ``.joinpath()`` method and ``.parent`` property. Now a backport release of the ``zipfile.Path`` class. v0.4.0 ====== #4: Add support for zip files with implied directories. v0.3.3 ====== #3: Fix issue where ``.name`` on a directory was empty. v0.3.2 ====== #2: Fix TypeError on Python 2.7 when classic division is used. v0.3.1 ====== #1: Fix TypeError on Python 3.5 when joining to a path-like object. v0.3.0 ====== Add support for constructing a ``zipp.Path`` from any path-like object. ``zipp.Path`` is now a new-style class on Python 2.7. v0.2.1 ====== Fix issue with ``__str__``. v0.2.0 ====== Drop reliance on future-fstrings. v0.1.0 ====== Initial release with basic functionality. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.141238 zipp-3.23.0/PKG-INFO0000644000175100001660000000675315021341433013270 0ustar00runnerdockerMetadata-Version: 2.4 Name: zipp Version: 3.23.0 Summary: Backport of pathlib-compatible object wrapper for zip files Author-email: "Jason R. Coombs" License-Expression: MIT Project-URL: Source, https://github.com/jaraco/zipp Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Provides-Extra: test Requires-Dist: pytest!=8.1.*,>=6; extra == "test" Requires-Dist: jaraco.itertools; extra == "test" Requires-Dist: jaraco.functools; extra == "test" Requires-Dist: more_itertools; extra == "test" Requires-Dist: big-O; extra == "test" Requires-Dist: pytest-ignore-flaky; extra == "test" Requires-Dist: jaraco.test; extra == "test" Provides-Extra: doc Requires-Dist: sphinx>=3.5; extra == "doc" Requires-Dist: jaraco.packaging>=9.3; extra == "doc" Requires-Dist: rst.linker>=1.9; extra == "doc" Requires-Dist: furo; extra == "doc" Requires-Dist: sphinx-lint; extra == "doc" Requires-Dist: jaraco.tidelift>=1.4; extra == "doc" Provides-Extra: check Requires-Dist: pytest-checkdocs>=2.4; extra == "check" Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" Provides-Extra: cover Requires-Dist: pytest-cov; extra == "cover" Provides-Extra: enabler Requires-Dist: pytest-enabler>=2.2; extra == "enabler" Provides-Extra: type Requires-Dist: pytest-mypy; extra == "type" Dynamic: license-file .. image:: https://img.shields.io/pypi/v/zipp.svg :target: https://pypi.org/project/zipp .. image:: https://img.shields.io/pypi/pyversions/zipp.svg .. image:: https://github.com/jaraco/zipp/actions/workflows/main.yml/badge.svg :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://readthedocs.org/projects/zipp/badge/?version=latest .. :target: https://zipp.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2025-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/zipp :target: https://tidelift.com/subscription/pkg/pypi-zipp?utm_source=pypi-zipp&utm_medium=readme A pathlib-compatible Zipfile object wrapper. Official backport of the standard library `Path object `_. Compatibility ============= New features are introduced in this third-party library and later merged into CPython. The following table indicates which versions of this library were contributed to different versions in the standard library: .. list-table:: :header-rows: 1 * - zipp - stdlib * - 3.18 - 3.13 * - 3.16 - 3.12 * - 3.5 - 3.11 * - 3.2 - 3.10 * - 3.3 ?? - 3.9 * - 1.0 - 3.8 Usage ===== Use ``zipp.Path`` in place of ``zipfile.Path`` on any Python. For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/README.rst0000644000175100001660000000371415021341407013655 0ustar00runnerdocker.. image:: https://img.shields.io/pypi/v/zipp.svg :target: https://pypi.org/project/zipp .. image:: https://img.shields.io/pypi/pyversions/zipp.svg .. image:: https://github.com/jaraco/zipp/actions/workflows/main.yml/badge.svg :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://readthedocs.org/projects/zipp/badge/?version=latest .. :target: https://zipp.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2025-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/zipp :target: https://tidelift.com/subscription/pkg/pypi-zipp?utm_source=pypi-zipp&utm_medium=readme A pathlib-compatible Zipfile object wrapper. Official backport of the standard library `Path object `_. Compatibility ============= New features are introduced in this third-party library and later merged into CPython. The following table indicates which versions of this library were contributed to different versions in the standard library: .. list-table:: :header-rows: 1 * - zipp - stdlib * - 3.18 - 3.13 * - 3.16 - 3.12 * - 3.5 - 3.11 * - 3.2 - 3.10 * - 3.3 ?? - 3.9 * - 1.0 - 3.8 Usage ===== Use ``zipp.Path`` in place of ``zipfile.Path`` on any Python. For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/SECURITY.md0000644000175100001660000000026415021341407013754 0ustar00runnerdocker# Security Contact To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/conftest.py0000644000175100001660000000036515021341407014364 0ustar00runnerdockerimport builtins import sys def pytest_configure(): add_future_flags() def add_future_flags(): # pragma: no cover if sys.version_info >= (3, 10): return builtins.EncodingWarning = type('EncodingWarning', (Warning,), {}) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.134238 zipp-3.23.0/docs/0000755000175100001660000000000015021341433013110 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/docs/conf.py0000644000175100001660000000344715021341407014420 0ustar00runnerdockerfrom __future__ import annotations extensions = [ 'sphinx.ext.autodoc', 'jaraco.packaging.sphinx', ] master_doc = "index" html_theme = "furo" # Link dates and other references in the changelog extensions += ['rst.linker'] link_files = { '../NEWS.rst': dict( using=dict(GH='https://github.com'), replace=[ dict( pattern=r'(Issue #|\B#)(?P\d+)', url='{package_url}/issues/{issue}', ), dict( pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', ), dict( pattern=r'PEP[- ](?P\d+)', url='https://peps.python.org/pep-{pep_number:0>4}/', ), dict( pattern=r'(bpo-)(?P\d+)', url='http://bugs.python.org/issue{bpo}', ), dict( pattern=r'(gh-)(?P\d+)', url='http://bugs.python.org/issue{python_gh}', ), ], ) } # Be strict about any broken references nitpicky = True nitpick_ignore: list[tuple[str, str]] = [] # Include Python intersphinx mapping to prevent failures # jaraco/skeleton#51 extensions += ['sphinx.ext.intersphinx'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), } # Preserve authored syntax for defaults autodoc_preserve_defaults = True # Add support for linking usernames, PyPI projects, Wikipedia pages github_url = 'https://github.com/' extlinks = { 'user': (f'{github_url}%s', '@%s'), 'pypi': ('https://pypi.org/project/%s', '%s'), 'wiki': ('https://wikipedia.org/wiki/%s', '%s'), } extensions += ['sphinx.ext.extlinks'] # local extensions += ['jaraco.tidelift'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/docs/history.rst0000644000175100001660000000011615021341407015342 0ustar00runnerdocker:tocdepth: 2 .. _changes: History ******* .. include:: ../NEWS (links).rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/docs/index.rst0000644000175100001660000000067215021341407014757 0ustar00runnerdockerWelcome to |project| documentation! =================================== .. sidebar-links:: :home: :pypi: .. toctree:: :maxdepth: 1 history .. tidelift-referral-banner:: .. automodule:: zipp :members: :undoc-members: :show-inheritance: .. automodule:: zipp.glob :members: :undoc-members: :show-inheritance: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/mypy.ini0000644000175100001660000000102615021341407013657 0ustar00runnerdocker[mypy] # Is the project well-typed? strict = False # Early opt-in even when strict = False warn_unused_ignores = True warn_redundant_casts = True enable_error_code = ignore-without-code # Support namespace packages per https://github.com/python/mypy/issues/14057 explicit_package_bases = True disable_error_code = # Disable due to many false positives overload-overlap, # jaraco/jaraco.test#7 [mypy-jaraco.test.*] ignore_missing_imports = True # jaraco/jaraco.itertools#20 [mypy-jaraco.itertools] ignore_missing_imports = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/pyproject.toml0000644000175100001660000000252515021341407015101 0ustar00runnerdocker[build-system] requires = [ "setuptools>=77", "setuptools_scm[toml]>=3.4.1", # jaraco/skeleton#174 "coherent.licensed", ] build-backend = "setuptools.build_meta" [project] name = "zipp" authors = [ { name = "Jason R. Coombs", email = "jaraco@jaraco.com" }, ] description = "Backport of pathlib-compatible object wrapper for zip files" readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", ] requires-python = ">=3.9" license = "MIT" dependencies = [ ] dynamic = ["version"] [project.urls] Source = "https://github.com/jaraco/zipp" [project.optional-dependencies] test = [ # upstream "pytest >= 6, != 8.1.*", # local "jaraco.itertools", "jaraco.functools", "more_itertools", "big-O", "pytest-ignore-flaky", "jaraco.test", ] doc = [ # upstream "sphinx >= 3.5", "jaraco.packaging >= 9.3", "rst.linker >= 1.9", "furo", "sphinx-lint", # tidelift "jaraco.tidelift >= 1.4", # local ] check = [ "pytest-checkdocs >= 2.4", "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", ] cover = [ "pytest-cov", ] enabler = [ "pytest-enabler >= 2.2", ] type = [ # upstream "pytest-mypy", # local ] [tool.setuptools_scm] [tool.pytest-enabler.mypy] # Disabled due to jaraco/skeleton#143 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/pytest.ini0000644000175100001660000000111015021341407014203 0ustar00runnerdocker[pytest] norecursedirs=dist build .tox .eggs addopts= --doctest-modules --import-mode importlib consider_namespace_packages=true filterwarnings= ## upstream # Ensure ResourceWarnings are emitted default::ResourceWarning # realpython/pytest-mypy#152 ignore:'encoding' argument not specified::pytest_mypy # python/cpython#100750 ignore:'encoding' argument not specified::platform # pypa/build#615 ignore:'encoding' argument not specified::build.env # dateutil/dateutil#1284 ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:dateutil.tz.tz ## end upstream ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/ruff.toml0000644000175100001660000000226315021341407014023 0ustar00runnerdocker[lint] extend-select = [ # upstream "C901", # complex-structure "I", # isort "PERF401", # manual-list-comprehension # Ensure modern type annotation syntax and best practices # Not including those covered by type-checkers or exclusive to Python 3.11+ "FA", # flake8-future-annotations "F404", # late-future-import "PYI", # flake8-pyi "UP006", # non-pep585-annotation "UP007", # non-pep604-annotation "UP010", # unnecessary-future-import "UP035", # deprecated-import "UP037", # quoted-annotation "UP043", # unnecessary-default-type-args # local ] ignore = [ # upstream # Typeshed rejects complex or non-literal defaults for maintenance and testing reasons, # irrelevant to this project. "PYI011", # typed-argument-default-in-stub # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "W191", "E111", "E114", "E117", "D206", "D300", "Q000", "Q001", "Q002", "Q003", "COM812", "COM819", # local ] [format] # Enable preview to get hugged parenthesis unwrapping and other nice surprises # See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 preview = true # https://docs.astral.sh/ruff/settings/#format_quote-style quote-style = "preserve" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1749402395.1422381 zipp-3.23.0/setup.cfg0000644000175100001660000000004615021341433014001 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.136238 zipp-3.23.0/tests/0000755000175100001660000000000015021341433013322 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/__init__.py0000644000175100001660000000000015021341407015422 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/_support.py0000644000175100001660000000033315021341407015547 0ustar00runnerdockerimport importlib import unittest def import_or_skip(name): try: return importlib.import_module(name) except ImportError: # pragma: no cover raise unittest.SkipTest(f'Unable to import {name}') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/_test_params.py0000644000175100001660000000161415021341407016360 0ustar00runnerdockerimport functools import types from more_itertools import always_iterable def parameterize(names, value_groups): """ Decorate a test method to run it as a set of subtests. Modeled after pytest.parametrize. """ def decorator(func): @functools.wraps(func) def wrapped(self): for values in value_groups: resolved = map(Invoked.eval, always_iterable(values)) params = dict(zip(always_iterable(names), resolved)) with self.subTest(**params): func(self, **params) return wrapped return decorator class Invoked(types.SimpleNamespace): """ Wrap a function to be invoked for each usage. """ @classmethod def wrap(cls, func): return cls(func=func) @classmethod def eval(cls, cand): return cand.func() if isinstance(cand, cls) else cand ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.137238 zipp-3.23.0/tests/compat/0000755000175100001660000000000015021341433014605 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/compat/__init__.py0000644000175100001660000000000015021341407016705 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/compat/py310.py0000644000175100001660000000027015021341407016033 0ustar00runnerdockerimport sys if sys.version_info >= (3, 11): from importlib.resources.abc import Traversable else: # pragma: no cover from .py38 import Traversable __all__ = ['Traversable'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/compat/py313.py0000644000175100001660000000172115021341407016040 0ustar00runnerdockerimport os import sys import time def _for_archive(self, archive): """Resolve suitable defaults from the archive. Resolve the date_time, compression attributes, and external attributes to suitable defaults as used by :method:`ZipFile.writestr`. Return self. """ # gh-91279: Set the SOURCE_DATE_EPOCH to a specific timestamp epoch = os.environ.get('SOURCE_DATE_EPOCH') get_time = int(epoch) if epoch else time.time() self.date_time = time.localtime(get_time)[:6] self.compress_type = archive.compression self.compress_level = archive.compresslevel if self.filename.endswith('/'): # pragma: no cover self.external_attr = 0o40775 << 16 # drwxrwxr-x self.external_attr |= 0x10 # MS-DOS directory flag else: self.external_attr = 0o600 << 16 # ?rw------- return self ForArchive = type( 'ForArchive', (), dict(_for_archive=_for_archive) if sys.version_info < (3, 14) else dict(), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/compat/py38.py0000644000175100001660000000044415021341407015765 0ustar00runnerdockerimport sys if (3, 9) <= sys.version_info < (3, 11): # pragma: no cover from importlib.abc import Traversable # type: ignore[attr-defined, unused-ignore] elif sys.version_info < (3, 9): # pragma: no cover from importlib_resources.abc import Traversable __all__ = ['Traversable'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/compat/py39.py0000644000175100001660000000033115021341407015761 0ustar00runnerdockerimport sys from jaraco.test.cpython import from_test_support, try_import os_helper = try_import('os_helper') or from_test_support( 'FakePath', 'temp_dir', ) sys.modules[__name__ + '.os_helper'] = os_helper ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/test_complexity.py0000644000175100001660000000632115021341407017133 0ustar00runnerdockerimport io import itertools import math import re import string import unittest from jaraco.functools import compose from more_itertools import consume from zipp.compat.overlay import zipfile from ._support import import_or_skip big_o = import_or_skip('big_o') pytest = import_or_skip('pytest') class TestComplexity(unittest.TestCase): @pytest.mark.flaky def test_implied_dirs_performance(self): best, others = big_o.big_o( compose(consume, zipfile._path.CompleteDirs._implied_dirs), lambda size: [ '/'.join(string.ascii_lowercase + str(n)) for n in range(size) ], max_n=1000, min_n=1, ) assert best <= big_o.complexities.Linear def make_zip_path(self, depth=1, width=1): """ Construct a Path with width files at every level of depth. """ zf = zipfile.ZipFile(io.BytesIO(), mode='w') pairs = itertools.product(self.make_deep_paths(depth), self.make_names(width)) for path, name in pairs: zf.writestr(f"{path}{name}.txt", b'') zf.filename = "big un.zip" return zipfile.Path(zf) @classmethod def make_names(cls, width, letters=string.ascii_lowercase): """ >>> list(TestComplexity.make_names(1)) ['a'] >>> list(TestComplexity.make_names(2)) ['a', 'b'] >>> list(TestComplexity.make_names(30)) ['aa', 'ab', ..., 'bd'] >>> list(TestComplexity.make_names(17124)) ['aaa', 'aab', ..., 'zip'] """ # determine how many products are needed to produce width n_products = max(1, math.ceil(math.log(width, len(letters)))) inputs = (letters,) * n_products combinations = itertools.product(*inputs) names = map(''.join, combinations) return itertools.islice(names, width) @classmethod def make_deep_paths(cls, depth): return map(cls.make_deep_path, range(depth)) @classmethod def make_deep_path(cls, depth): return ''.join(('d/',) * depth) def test_baseline_regex_complexity(self): best, others = big_o.big_o( lambda path: re.fullmatch(r'[^/]*\\.txt', path), self.make_deep_path, max_n=100, min_n=1, ) assert best <= big_o.complexities.Constant @pytest.mark.flaky def test_glob_depth(self): best, others = big_o.big_o( lambda path: consume(path.glob('*.txt')), self.make_zip_path, max_n=100, min_n=1, ) assert best <= big_o.complexities.Linear @pytest.mark.flaky def test_glob_width(self): best, others = big_o.big_o( lambda path: consume(path.glob('*.txt')), lambda size: self.make_zip_path(width=size), max_n=100, min_n=1, ) assert best <= big_o.complexities.Linear @pytest.mark.flaky def test_glob_width_and_depth(self): best, others = big_o.big_o( lambda path: consume(path.glob('*.txt')), lambda size: self.make_zip_path(depth=size, width=size), max_n=10, min_n=1, ) assert best <= big_o.complexities.Linear ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/test_path.py0000644000175100001660000005272415021341407015702 0ustar00runnerdockerimport contextlib import io import itertools import pathlib import pickle import stat import sys import unittest import jaraco.itertools from jaraco.functools import compose from zipp.compat.overlay import zipfile from ._test_params import Invoked, parameterize from .compat.py39.os_helper import FakePath, temp_dir # type: ignore[import-not-found] from .compat.py313 import ForArchive def _make_link(info: zipfile.ZipInfo): # type: ignore[name-defined] info.external_attr |= stat.S_IFLNK << 16 def build_alpharep_fixture(): """ Create a zip file with this structure: . ├── a.txt ├── n.txt (-> a.txt) ├── b │ ├── c.txt │ ├── d │ │ └── e.txt │ └── f.txt ├── g │ └── h │ └── i.txt └── j ├── k.bin ├── l.baz └── m.bar This fixture has the following key characteristics: - a file at the root (a) - a file two levels deep (b/d/e) - multiple files in a directory (b/c, b/f) - a directory containing only a directory (g/h) - a directory with files of different extensions (j/klm) - a symlink (n) pointing to (a) "alpha" because it uses alphabet "rep" because it's a representative example """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr("a.txt", b"content of a") zf.writestr("b/c.txt", b"content of c") zf.writestr("b/d/e.txt", b"content of e") zf.writestr("b/f.txt", b"content of f") zf.writestr("g/h/i.txt", b"content of i") zf.writestr("j/k.bin", b"content of k") zf.writestr("j/l.baz", b"content of l") zf.writestr("j/m.bar", b"content of m") zf.writestr("n.txt", b"a.txt") _make_link(zf.infolist()[-1]) zf.filename = "alpharep.zip" return zf alpharep_generators = [ Invoked.wrap(build_alpharep_fixture), Invoked.wrap(compose(zipfile._path.CompleteDirs.inject, build_alpharep_fixture)), ] pass_alpharep = parameterize(['alpharep'], alpharep_generators) class TestPath(unittest.TestCase): def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) def zipfile_ondisk(self, alpharep): tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir())) buffer = alpharep.fp alpharep.close() path = tmpdir / alpharep.filename with path.open("wb") as strm: strm.write(buffer.getvalue()) return path @pass_alpharep def test_iterdir_and_types(self, alpharep): root = zipfile.Path(alpharep) assert root.is_dir() a, n, b, g, j = root.iterdir() assert a.is_file() assert b.is_dir() assert g.is_dir() c, f, d = b.iterdir() assert c.is_file() and f.is_file() (e,) = d.iterdir() assert e.is_file() (h,) = g.iterdir() (i,) = h.iterdir() assert i.is_file() @pass_alpharep def test_is_file_missing(self, alpharep): root = zipfile.Path(alpharep) assert not root.joinpath('missing.txt').is_file() @pass_alpharep def test_iterdir_on_file(self, alpharep): root = zipfile.Path(alpharep) a, n, b, g, j = root.iterdir() with self.assertRaises(ValueError): a.iterdir() @pass_alpharep def test_subdir_is_dir(self, alpharep): root = zipfile.Path(alpharep) assert (root / 'b').is_dir() assert (root / 'b/').is_dir() assert (root / 'g').is_dir() assert (root / 'g/').is_dir() @pass_alpharep def test_open(self, alpharep): root = zipfile.Path(alpharep) a, n, b, g, j = root.iterdir() with a.open(encoding="utf-8") as strm: data = strm.read() self.assertEqual(data, "content of a") with a.open('r', "utf-8") as strm: # not a kw, no gh-101144 TypeError data = strm.read() self.assertEqual(data, "content of a") def test_open_encoding_utf16(self): in_memory_file = io.BytesIO() zf = zipfile.ZipFile(in_memory_file, "w") zf.writestr("path/16.txt", "This was utf-16".encode("utf-16")) zf.filename = "test_open_utf16.zip" root = zipfile.Path(zf) (path,) = root.iterdir() u16 = path.joinpath("16.txt") with u16.open('r', "utf-16") as strm: data = strm.read() assert data == "This was utf-16" with u16.open(encoding="utf-16") as strm: data = strm.read() assert data == "This was utf-16" def test_open_encoding_errors(self): in_memory_file = io.BytesIO() zf = zipfile.ZipFile(in_memory_file, "w") zf.writestr("path/bad-utf8.bin", b"invalid utf-8: \xff\xff.") zf.filename = "test_read_text_encoding_errors.zip" root = zipfile.Path(zf) (path,) = root.iterdir() u16 = path.joinpath("bad-utf8.bin") # encoding= as a positional argument for gh-101144. data = u16.read_text("utf-8", errors="ignore") assert data == "invalid utf-8: ." with u16.open("r", "utf-8", errors="surrogateescape") as f: assert f.read() == "invalid utf-8: \udcff\udcff." # encoding= both positional and keyword is an error; gh-101144. with self.assertRaisesRegex(TypeError, "encoding"): data = u16.read_text("utf-8", encoding="utf-8") # both keyword arguments work. with u16.open("r", encoding="utf-8", errors="strict") as f: # error during decoding with wrong codec. with self.assertRaises(UnicodeDecodeError): f.read() @unittest.skipIf( not getattr(sys.flags, 'warn_default_encoding', 0), "Requires warn_default_encoding", ) @pass_alpharep def test_encoding_warnings(self, alpharep): """EncodingWarning must blame the read_text and open calls.""" assert sys.flags.warn_default_encoding root = zipfile.Path(alpharep) with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296) root.joinpath("a.txt").read_text() assert __file__ == wc.filename with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296) root.joinpath("a.txt").open("r").close() assert __file__ == wc.filename def test_open_write(self): """ If the zipfile is open for write, it should be possible to write bytes or text to it. """ zf = zipfile.Path(zipfile.ZipFile(io.BytesIO(), mode='w')) with zf.joinpath('file.bin').open('wb') as strm: strm.write(b'binary contents') with zf.joinpath('file.txt').open('w', encoding="utf-8") as strm: strm.write('text file') @pass_alpharep def test_open_extant_directory(self, alpharep): """ Attempting to open a directory raises IsADirectoryError. """ zf = zipfile.Path(alpharep) with self.assertRaises(IsADirectoryError): zf.joinpath('b').open() @pass_alpharep def test_open_binary_invalid_args(self, alpharep): root = zipfile.Path(alpharep) with self.assertRaises(ValueError): root.joinpath('a.txt').open('rb', encoding='utf-8') with self.assertRaises(ValueError): root.joinpath('a.txt').open('rb', 'utf-8') @pass_alpharep def test_open_missing_directory(self, alpharep): """ Attempting to open a missing directory raises FileNotFoundError. """ zf = zipfile.Path(alpharep) with self.assertRaises(FileNotFoundError): zf.joinpath('z').open() @pass_alpharep def test_read(self, alpharep): root = zipfile.Path(alpharep) a, n, b, g, j = root.iterdir() assert a.read_text(encoding="utf-8") == "content of a" # Also check positional encoding arg (gh-101144). assert a.read_text("utf-8") == "content of a" assert a.read_bytes() == b"content of a" @pass_alpharep def test_joinpath(self, alpharep): root = zipfile.Path(alpharep) a = root.joinpath("a.txt") assert a.is_file() e = root.joinpath("b").joinpath("d").joinpath("e.txt") assert e.read_text(encoding="utf-8") == "content of e" @pass_alpharep def test_joinpath_multiple(self, alpharep): root = zipfile.Path(alpharep) e = root.joinpath("b", "d", "e.txt") assert e.read_text(encoding="utf-8") == "content of e" @pass_alpharep def test_traverse_truediv(self, alpharep): root = zipfile.Path(alpharep) a = root / "a.txt" assert a.is_file() e = root / "b" / "d" / "e.txt" assert e.read_text(encoding="utf-8") == "content of e" @pass_alpharep def test_pathlike_construction(self, alpharep): """ zipfile.Path should be constructable from a path-like object """ zipfile_ondisk = self.zipfile_ondisk(alpharep) pathlike = FakePath(str(zipfile_ondisk)) zipfile.Path(pathlike) @pass_alpharep def test_traverse_pathlike(self, alpharep): root = zipfile.Path(alpharep) root / FakePath("a") @pass_alpharep def test_parent(self, alpharep): root = zipfile.Path(alpharep) assert (root / 'a').parent.at == '' assert (root / 'a' / 'b').parent.at == 'a/' @pass_alpharep def test_dir_parent(self, alpharep): root = zipfile.Path(alpharep) assert (root / 'b').parent.at == '' assert (root / 'b/').parent.at == '' @pass_alpharep def test_missing_dir_parent(self, alpharep): root = zipfile.Path(alpharep) assert (root / 'missing dir/').parent.at == '' @pass_alpharep def test_mutability(self, alpharep): """ If the underlying zipfile is changed, the Path object should reflect that change. """ root = zipfile.Path(alpharep) a, n, b, g, j = root.iterdir() alpharep.writestr('foo.txt', 'foo') alpharep.writestr('bar/baz.txt', 'baz') assert any(child.name == 'foo.txt' for child in root.iterdir()) assert (root / 'foo.txt').read_text(encoding="utf-8") == 'foo' (baz,) = (root / 'bar').iterdir() assert baz.read_text(encoding="utf-8") == 'baz' HUGE_ZIPFILE_NUM_ENTRIES = 2**13 def huge_zipfile(self): """Create a read-only zipfile with a huge number of entries entries.""" strm = io.BytesIO() zf = zipfile.ZipFile(strm, "w") for entry in map(str, range(self.HUGE_ZIPFILE_NUM_ENTRIES)): zf.writestr(entry, entry) zf.mode = 'r' return zf def test_joinpath_constant_time(self): """ Ensure joinpath on items in zipfile is linear time. """ root = zipfile.Path(self.huge_zipfile()) entries = jaraco.itertools.Counter(root.iterdir()) for entry in entries: entry.joinpath('suffix') # Check the file iterated all items assert entries.count == self.HUGE_ZIPFILE_NUM_ENTRIES @pass_alpharep def test_read_does_not_close(self, alpharep): alpharep = self.zipfile_ondisk(alpharep) with zipfile.ZipFile(alpharep) as file: for rep in range(2): zipfile.Path(file, 'a.txt').read_text(encoding="utf-8") @pass_alpharep def test_subclass(self, alpharep): class Subclass(zipfile.Path): pass root = Subclass(alpharep) assert isinstance(root / 'b', Subclass) @pass_alpharep def test_filename(self, alpharep): root = zipfile.Path(alpharep) assert root.filename == pathlib.Path('alpharep.zip') @pass_alpharep def test_root_name(self, alpharep): """ The name of the root should be the name of the zipfile """ root = zipfile.Path(alpharep) assert root.name == 'alpharep.zip' == root.filename.name @pass_alpharep def test_root_on_disk(self, alpharep): """ The name/stem of the root should match the zipfile on disk. This condition must hold across platforms. """ root = zipfile.Path(self.zipfile_ondisk(alpharep)) assert root.name == 'alpharep.zip' == root.filename.name assert root.stem == 'alpharep' == root.filename.stem @pass_alpharep def test_suffix(self, alpharep): """ The suffix of the root should be the suffix of the zipfile. The suffix of each nested file is the final component's last suffix, if any. Includes the leading period, just like pathlib.Path. """ root = zipfile.Path(alpharep) assert root.suffix == '.zip' == root.filename.suffix b = root / "b.txt" assert b.suffix == ".txt" c = root / "c" / "filename.tar.gz" assert c.suffix == ".gz" d = root / "d" assert d.suffix == "" @pass_alpharep def test_suffixes(self, alpharep): """ The suffix of the root should be the suffix of the zipfile. The suffix of each nested file is the final component's last suffix, if any. Includes the leading period, just like pathlib.Path. """ root = zipfile.Path(alpharep) assert root.suffixes == ['.zip'] == root.filename.suffixes b = root / 'b.txt' assert b.suffixes == ['.txt'] c = root / 'c' / 'filename.tar.gz' assert c.suffixes == ['.tar', '.gz'] d = root / 'd' assert d.suffixes == [] e = root / '.hgrc' assert e.suffixes == [] @pass_alpharep def test_suffix_no_filename(self, alpharep): alpharep.filename = None root = zipfile.Path(alpharep) assert root.joinpath('example').suffix == "" assert root.joinpath('example').suffixes == [] @pass_alpharep def test_stem(self, alpharep): """ The final path component, without its suffix """ root = zipfile.Path(alpharep) assert root.stem == 'alpharep' == root.filename.stem b = root / "b.txt" assert b.stem == "b" c = root / "c" / "filename.tar.gz" assert c.stem == "filename.tar" d = root / "d" assert d.stem == "d" assert (root / ".gitignore").stem == ".gitignore" @pass_alpharep def test_root_parent(self, alpharep): root = zipfile.Path(alpharep) assert root.parent == pathlib.Path('.') root.root.filename = 'foo/bar.zip' assert root.parent == pathlib.Path('foo') @pass_alpharep def test_root_unnamed(self, alpharep): """ It is an error to attempt to get the name or parent of an unnamed zipfile. """ alpharep.filename = None root = zipfile.Path(alpharep) with self.assertRaises(TypeError): root.name with self.assertRaises(TypeError): root.parent # .name and .parent should still work on subs sub = root / "b" assert sub.name == "b" assert sub.parent @pass_alpharep def test_match_and_glob(self, alpharep): root = zipfile.Path(alpharep) assert not root.match("*.txt") assert list(root.glob("b/c.*")) == [zipfile.Path(alpharep, "b/c.txt")] assert list(root.glob("b/*.txt")) == [ zipfile.Path(alpharep, "b/c.txt"), zipfile.Path(alpharep, "b/f.txt"), ] @pass_alpharep def test_glob_recursive(self, alpharep): root = zipfile.Path(alpharep) files = root.glob("**/*.txt") assert all(each.match("*.txt") for each in files) assert list(root.glob("**/*.txt")) == list(root.rglob("*.txt")) @pass_alpharep def test_glob_dirs(self, alpharep): root = zipfile.Path(alpharep) assert list(root.glob('b')) == [zipfile.Path(alpharep, "b/")] assert list(root.glob('b*')) == [zipfile.Path(alpharep, "b/")] @pass_alpharep def test_glob_subdir(self, alpharep): root = zipfile.Path(alpharep) assert list(root.glob('g/h')) == [zipfile.Path(alpharep, "g/h/")] assert list(root.glob('g*/h*')) == [zipfile.Path(alpharep, "g/h/")] @pass_alpharep def test_glob_subdirs(self, alpharep): root = zipfile.Path(alpharep) assert list(root.glob("*/i.txt")) == [] assert list(root.rglob("*/i.txt")) == [zipfile.Path(alpharep, "g/h/i.txt")] @pass_alpharep def test_glob_does_not_overmatch_dot(self, alpharep): root = zipfile.Path(alpharep) assert list(root.glob("*.xt")) == [] @pass_alpharep def test_glob_single_char(self, alpharep): root = zipfile.Path(alpharep) assert list(root.glob("a?txt")) == [zipfile.Path(alpharep, "a.txt")] assert list(root.glob("a[.]txt")) == [zipfile.Path(alpharep, "a.txt")] assert list(root.glob("a[?]txt")) == [] @pass_alpharep def test_glob_chars(self, alpharep): root = zipfile.Path(alpharep) assert list(root.glob("j/?.b[ai][nz]")) == [ zipfile.Path(alpharep, "j/k.bin"), zipfile.Path(alpharep, "j/l.baz"), ] def test_glob_empty(self): root = zipfile.Path(zipfile.ZipFile(io.BytesIO(), 'w')) with self.assertRaises(ValueError): root.glob('') @pass_alpharep def test_eq_hash(self, alpharep): root = zipfile.Path(alpharep) assert root == zipfile.Path(alpharep) assert root != (root / "a.txt") assert (root / "a.txt") == (root / "a.txt") root = zipfile.Path(alpharep) assert root in {root} @pass_alpharep def test_is_symlink(self, alpharep): root = zipfile.Path(alpharep) assert not root.joinpath('a.txt').is_symlink() assert root.joinpath('n.txt').is_symlink() @pass_alpharep def test_relative_to(self, alpharep): root = zipfile.Path(alpharep) relative = root.joinpath("b", "c.txt").relative_to(root / "b") assert str(relative) == "c.txt" relative = root.joinpath("b", "d", "e.txt").relative_to(root / "b") assert str(relative) == "d/e.txt" @pass_alpharep def test_inheritance(self, alpharep): cls = type('PathChild', (zipfile.Path,), {}) file = cls(alpharep).joinpath('some dir').parent assert isinstance(file, cls) @parameterize( ['alpharep', 'path_type', 'subpath'], itertools.product( alpharep_generators, [str, FakePath], ['', 'b/'], ), ) def test_pickle(self, alpharep, path_type, subpath): zipfile_ondisk = path_type(str(self.zipfile_ondisk(alpharep))) saved_1 = pickle.dumps(zipfile.Path(zipfile_ondisk, at=subpath)) restored_1 = pickle.loads(saved_1) first, *rest = restored_1.iterdir() assert first.read_text(encoding='utf-8').startswith('content of ') @pass_alpharep def test_extract_orig_with_implied_dirs(self, alpharep): """ A zip file wrapped in a Path should extract even with implied dirs. """ source_path = self.zipfile_ondisk(alpharep) zf = zipfile.ZipFile(source_path) # wrap the zipfile for its side effect zipfile.Path(zf) zf.extractall(source_path.parent) @pass_alpharep def test_getinfo_missing(self, alpharep): """ Validate behavior of getinfo on original zipfile after wrapping. """ zipfile.Path(alpharep) with self.assertRaises(KeyError): alpharep.getinfo('does-not-exist') def test_malformed_paths(self): """ Path should handle malformed paths gracefully. Paths with leading slashes are not visible. Paths with dots are treated like regular files. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr("/one-slash.txt", b"content") zf.writestr("//two-slash.txt", b"content") zf.writestr("../parent.txt", b"content") zf.filename = '' root = zipfile.Path(zf) assert list(map(str, root.iterdir())) == ['../'] assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content' def test_unsupported_names(self): """ Path segments with special characters are readable. On some platforms or file systems, characters like ``:`` and ``?`` are not allowed, but they are valid in the zip file. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr("path?", b"content") zf.writestr("V: NMS.flac", b"fLaC...") zf.filename = '' root = zipfile.Path(zf) contents = root.iterdir() assert next(contents).name == 'path?' assert next(contents).name == 'V: NMS.flac' assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..." def test_backslash_not_separator(self): """ In a zip file, backslashes are not separators. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr(DirtyZipInfo("foo\\bar")._for_archive(zf), b"content") zf.filename = '' root = zipfile.Path(zf) (first,) = root.iterdir() assert not first.is_dir() assert first.name == 'foo\\bar' @pass_alpharep def test_interface(self, alpharep): from .compat.py310 import Traversable zf = zipfile.Path(alpharep) assert isinstance(zf, Traversable) class DirtyZipInfo(zipfile.ZipInfo, ForArchive): """ Bypass name sanitization. """ def __init__(self, filename, *args, **kwargs): super().__init__(filename, *args, **kwargs) self.filename = filename ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tests/write-alpharep.py0000644000175100001660000000015615021341407016623 0ustar00runnerdockerfrom . import test_path __name__ == '__main__' and test_path.build_alpharep_fixture().extractall('alpharep') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/towncrier.toml0000644000175100001660000000005415021341407015071 0ustar00runnerdocker[tool.towncrier] title_format = "{version}" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/tox.ini0000644000175100001660000000245415021341407013501 0ustar00runnerdocker[testenv] description = perform primary checks (tests, style, types, coverage) deps = setenv = PYTHONWARNDEFAULTENCODING = 1 commands = pytest {posargs} usedevelop = True extras = test check cover enabler type [testenv:diffcov] description = run tests and check that diff from main is covered deps = {[testenv]deps} diff-cover commands = pytest {posargs} --cov-report xml diff-cover coverage.xml --compare-branch=origin/main --html-report diffcov.html diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 [testenv:docs] description = build the documentation extras = doc test changedir = docs commands = python -m sphinx -W --keep-going . {toxinidir}/build/html python -m sphinxlint [testenv:finalize] description = assemble changelog and tag a release skip_install = True deps = towncrier jaraco.develop >= 7.23 pass_env = * commands = python -m jaraco.develop.finalize [testenv:release] description = publish the package to PyPI and GitHub skip_install = True deps = build twine>=3 jaraco.develop>=7.1 pass_env = TWINE_PASSWORD GITHUB_TOKEN setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" python -m build python -m twine upload dist/* python -m jaraco.develop.create-github-release ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.137238 zipp-3.23.0/zipp/0000755000175100001660000000000015021341433013142 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/__init__.py0000644000175100001660000002731015021341407015257 0ustar00runnerdocker""" A Path-like interface for zipfiles. This codebase is shared between zipfile.Path in the stdlib and zipp in PyPI. See https://github.com/python/importlib_metadata/wiki/Development-Methodology for more detail. """ import functools import io import itertools import pathlib import posixpath import re import stat import sys import zipfile from ._functools import save_method_args from .compat.py310 import text_encoding from .glob import Translator __all__ = ['Path'] def _parents(path): """ Given a path with elements separated by posixpath.sep, generate all parents of that path. >>> list(_parents('b/d')) ['b'] >>> list(_parents('/b/d/')) ['/b'] >>> list(_parents('b/d/f/')) ['b/d', 'b'] >>> list(_parents('b')) [] >>> list(_parents('')) [] """ return itertools.islice(_ancestry(path), 1, None) def _ancestry(path): """ Given a path with elements separated by posixpath.sep, generate all elements of that path. >>> list(_ancestry('b/d')) ['b/d', 'b'] >>> list(_ancestry('/b/d/')) ['/b/d', '/b'] >>> list(_ancestry('b/d/f/')) ['b/d/f', 'b/d', 'b'] >>> list(_ancestry('b')) ['b'] >>> list(_ancestry('')) [] Multiple separators are treated like a single. >>> list(_ancestry('//b//d///f//')) ['//b//d///f', '//b//d', '//b'] """ path = path.rstrip(posixpath.sep) while path.rstrip(posixpath.sep): yield path path, tail = posixpath.split(path) _dedupe = dict.fromkeys """Deduplicate an iterable in original order""" def _difference(minuend, subtrahend): """ Return items in minuend not in subtrahend, retaining order with O(1) lookup. """ return itertools.filterfalse(set(subtrahend).__contains__, minuend) class InitializedState: """ Mix-in to save the initialization state for pickling. """ @save_method_args def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def __getstate__(self): return self._saved___init__.args, self._saved___init__.kwargs def __setstate__(self, state): args, kwargs = state super().__init__(*args, **kwargs) class CompleteDirs(InitializedState, zipfile.ZipFile): """ A ZipFile subclass that ensures that implied directories are always included in the namelist. >>> list(CompleteDirs._implied_dirs(['foo/bar.txt', 'foo/bar/baz.txt'])) ['foo/', 'foo/bar/'] >>> list(CompleteDirs._implied_dirs(['foo/bar.txt', 'foo/bar/baz.txt', 'foo/bar/'])) ['foo/'] """ @staticmethod def _implied_dirs(names): parents = itertools.chain.from_iterable(map(_parents, names)) as_dirs = (p + posixpath.sep for p in parents) return _dedupe(_difference(as_dirs, names)) def namelist(self): names = super().namelist() return names + list(self._implied_dirs(names)) def _name_set(self): return set(self.namelist()) def resolve_dir(self, name): """ If the name represents a directory, return that name as a directory (with the trailing slash). """ names = self._name_set() dirname = name + '/' dir_match = name not in names and dirname in names return dirname if dir_match else name def getinfo(self, name): """ Supplement getinfo for implied dirs. """ try: return super().getinfo(name) except KeyError: if not name.endswith('/') or name not in self._name_set(): raise return zipfile.ZipInfo(filename=name) @classmethod def make(cls, source): """ Given a source (filename or zipfile), return an appropriate CompleteDirs subclass. """ if isinstance(source, CompleteDirs): return source if not isinstance(source, zipfile.ZipFile): return cls(source) # Only allow for FastLookup when supplied zipfile is read-only if 'r' not in source.mode: cls = CompleteDirs source.__class__ = cls return source @classmethod def inject(cls, zf: zipfile.ZipFile) -> zipfile.ZipFile: """ Given a writable zip file zf, inject directory entries for any directories implied by the presence of children. """ for name in cls._implied_dirs(zf.namelist()): zf.writestr(name, b"") return zf class FastLookup(CompleteDirs): """ ZipFile subclass to ensure implicit dirs exist and are resolved rapidly. """ def namelist(self): return self._namelist @functools.cached_property def _namelist(self): return super().namelist() def _name_set(self): return self._name_set_prop @functools.cached_property def _name_set_prop(self): return super()._name_set() def _extract_text_encoding(encoding=None, *args, **kwargs): # compute stack level so that the caller of the caller sees any warning. is_pypy = sys.implementation.name == 'pypy' # PyPy no longer special cased after 7.3.19 (or maybe 7.3.18) # See jaraco/zipp#143 is_old_pypi = is_pypy and sys.pypy_version_info < (7, 3, 19) stack_level = 3 + is_old_pypi return text_encoding(encoding, stack_level), args, kwargs class Path: """ A :class:`importlib.resources.abc.Traversable` interface for zip files. Implements many of the features users enjoy from :class:`pathlib.Path`. Consider a zip file with this structure:: . ├── a.txt └── b ├── c.txt └── d └── e.txt >>> data = io.BytesIO() >>> zf = zipfile.ZipFile(data, 'w') >>> zf.writestr('a.txt', 'content of a') >>> zf.writestr('b/c.txt', 'content of c') >>> zf.writestr('b/d/e.txt', 'content of e') >>> zf.filename = 'mem/abcde.zip' Path accepts the zipfile object itself or a filename >>> path = Path(zf) From there, several path operations are available. Directory iteration (including the zip file itself): >>> a, b = path.iterdir() >>> a Path('mem/abcde.zip', 'a.txt') >>> b Path('mem/abcde.zip', 'b/') name property: >>> b.name 'b' join with divide operator: >>> c = b / 'c.txt' >>> c Path('mem/abcde.zip', 'b/c.txt') >>> c.name 'c.txt' Read text: >>> c.read_text(encoding='utf-8') 'content of c' existence: >>> c.exists() True >>> (b / 'missing.txt').exists() False Coercion to string: >>> import os >>> str(c).replace(os.sep, posixpath.sep) 'mem/abcde.zip/b/c.txt' At the root, ``name``, ``filename``, and ``parent`` resolve to the zipfile. >>> str(path) 'mem/abcde.zip/' >>> path.name 'abcde.zip' >>> path.filename == pathlib.Path('mem/abcde.zip') True >>> str(path.parent) 'mem' If the zipfile has no filename, such attributes are not valid and accessing them will raise an Exception. >>> zf.filename = None >>> path.name Traceback (most recent call last): ... TypeError: ... >>> path.filename Traceback (most recent call last): ... TypeError: ... >>> path.parent Traceback (most recent call last): ... TypeError: ... # workaround python/cpython#106763 >>> pass """ __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" def __init__(self, root, at=""): """ Construct a Path from a ZipFile or filename. Note: When the source is an existing ZipFile object, its type (__class__) will be mutated to a specialized type. If the caller wishes to retain the original type, the caller should either create a separate ZipFile object or pass a filename. """ self.root = FastLookup.make(root) self.at = at def __eq__(self, other): """ >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo' False """ if self.__class__ is not other.__class__: return NotImplemented return (self.root, self.at) == (other.root, other.at) def __hash__(self): return hash((self.root, self.at)) def open(self, mode='r', *args, pwd=None, **kwargs): """ Open this entry as text or binary following the semantics of ``pathlib.Path.open()`` by passing arguments through to io.TextIOWrapper(). """ if self.is_dir(): raise IsADirectoryError(self) zip_mode = mode[0] if zip_mode == 'r' and not self.exists(): raise FileNotFoundError(self) stream = self.root.open(self.at, zip_mode, pwd=pwd) if 'b' in mode: if args or kwargs: raise ValueError("encoding args invalid for binary operation") return stream # Text mode: encoding, args, kwargs = _extract_text_encoding(*args, **kwargs) return io.TextIOWrapper(stream, encoding, *args, **kwargs) def _base(self): return pathlib.PurePosixPath(self.at) if self.at else self.filename @property def name(self): return self._base().name @property def suffix(self): return self._base().suffix @property def suffixes(self): return self._base().suffixes @property def stem(self): return self._base().stem @property def filename(self): return pathlib.Path(self.root.filename).joinpath(self.at) def read_text(self, *args, **kwargs): encoding, args, kwargs = _extract_text_encoding(*args, **kwargs) with self.open('r', encoding, *args, **kwargs) as strm: return strm.read() def read_bytes(self): with self.open('rb') as strm: return strm.read() def _is_child(self, path): return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") def _next(self, at): return self.__class__(self.root, at) def is_dir(self): return not self.at or self.at.endswith("/") def is_file(self): return self.exists() and not self.is_dir() def exists(self): return self.at in self.root._name_set() def iterdir(self): if not self.is_dir(): raise ValueError("Can't listdir a file") subs = map(self._next, self.root.namelist()) return filter(self._is_child, subs) def match(self, path_pattern): return pathlib.PurePosixPath(self.at).match(path_pattern) def is_symlink(self): """ Return whether this path is a symlink. """ info = self.root.getinfo(self.at) mode = info.external_attr >> 16 return stat.S_ISLNK(mode) def glob(self, pattern): if not pattern: raise ValueError(f"Unacceptable pattern: {pattern!r}") prefix = re.escape(self.at) tr = Translator(seps='/') matches = re.compile(prefix + tr.translate(pattern)).fullmatch return map(self._next, filter(matches, self.root.namelist())) def rglob(self, pattern): return self.glob(f'**/{pattern}') def relative_to(self, other, *extra): return posixpath.relpath(str(self), str(other.joinpath(*extra))) def __str__(self): return posixpath.join(self.root.filename, self.at) def __repr__(self): return self.__repr.format(self=self) def joinpath(self, *other): next = posixpath.join(self.at, *other) return self._next(self.root.resolve_dir(next)) __truediv__ = joinpath @property def parent(self): if not self.at: return self.filename.parent parent_at = posixpath.dirname(self.at.rstrip('/')) if parent_at: parent_at += '/' return self._next(parent_at) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/_functools.py0000644000175100001660000000107715021341407015675 0ustar00runnerdockerimport collections import functools # from jaraco.functools 4.0.2 def save_method_args(method): """ Wrap a method such that when it is called, the args and kwargs are saved on the method. """ args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs') # noqa: PYI024 @functools.wraps(method) def wrapper(self, /, *args, **kwargs): attr_name = '_saved_' + method.__name__ attr = args_and_kwargs(args, kwargs) setattr(self, attr_name, attr) return method(self, *args, **kwargs) return wrapper ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.139238 zipp-3.23.0/zipp/compat/0000755000175100001660000000000015021341433014425 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/compat/__init__.py0000644000175100001660000000000015021341407016525 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/compat/overlay.py0000644000175100001660000000141715021341407016464 0ustar00runnerdocker""" Expose zipp.Path as .zipfile.Path. Includes everything else in ``zipfile`` to match future usage. Just use: >>> from zipp.compat.overlay import zipfile in place of ``import zipfile``. Relative imports are supported too. >>> from zipp.compat.overlay.zipfile import ZipInfo The ``zipfile`` object added to ``sys.modules`` needs to be hashable (#126). >>> _ = hash(sys.modules['zipp.compat.overlay.zipfile']) """ import importlib import sys import types import zipp class HashableNamespace(types.SimpleNamespace): def __hash__(self): return hash(tuple(vars(self))) zipfile = HashableNamespace(**vars(importlib.import_module('zipfile'))) zipfile.Path = zipp.Path zipfile._path = zipp sys.modules[__name__ + '.zipfile'] = zipfile # type: ignore[assignment] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/compat/py310.py0000644000175100001660000000040015021341407015646 0ustar00runnerdockerimport io import sys def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover return encoding text_encoding = ( io.text_encoding # type: ignore[unused-ignore, attr-defined] if sys.version_info > (3, 10) else _text_encoding ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/compat/py313.py0000644000175100001660000000121615021341407015657 0ustar00runnerdockerimport functools import sys # from jaraco.functools 4.1 def identity(x): return x # from jaraco.functools 4.1 def apply(transform): def wrap(func): return functools.wraps(func)(compose(transform, func)) return wrap # from jaraco.functools 4.1 def compose(*funcs): def compose_two(f1, f2): return lambda *args, **kwargs: f1(f2(*args, **kwargs)) return functools.reduce(compose_two, funcs) def replace(pattern): r""" >>> replace(r'foo\z') 'foo\\Z' """ return pattern[:-2] + pattern[-2:].replace(r'\z', r'\Z') legacy_end_marker = apply(replace) if sys.version_info < (3, 14) else identity ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402375.0 zipp-3.23.0/zipp/glob.py0000644000175100001660000000646615021341407014454 0ustar00runnerdockerimport os import re from .compat.py313 import legacy_end_marker _default_seps = os.sep + str(os.altsep) * bool(os.altsep) class Translator: """ >>> Translator('xyz') Traceback (most recent call last): ... AssertionError: Invalid separators >>> Translator('') Traceback (most recent call last): ... AssertionError: Invalid separators """ seps: str def __init__(self, seps: str = _default_seps): assert seps and set(seps) <= set(_default_seps), "Invalid separators" self.seps = seps def translate(self, pattern): """ Given a glob pattern, produce a regex that matches it. """ return self.extend(self.match_dirs(self.translate_core(pattern))) @legacy_end_marker def extend(self, pattern): r""" Extend regex for pattern-wide concerns. Apply '(?s:)' to create a non-matching group that matches newlines (valid on Unix). Append '\z' to imply fullmatch even when match is used. """ return rf'(?s:{pattern})\z' def match_dirs(self, pattern): """ Ensure that zipfile.Path directory names are matched. zipfile.Path directory names always end in a slash. """ return rf'{pattern}[/]?' def translate_core(self, pattern): r""" Given a glob pattern, produce a regex that matches it. >>> t = Translator() >>> t.translate_core('*.txt').replace('\\\\', '') '[^/]*\\.txt' >>> t.translate_core('a?txt') 'a[^/]txt' >>> t.translate_core('**/*').replace('\\\\', '') '.*/[^/][^/]*' """ self.restrict_rglob(pattern) return ''.join(map(self.replace, separate(self.star_not_empty(pattern)))) def replace(self, match): """ Perform the replacements for a match from :func:`separate`. """ return match.group('set') or ( re.escape(match.group(0)) .replace('\\*\\*', r'.*') .replace('\\*', rf'[^{re.escape(self.seps)}]*') .replace('\\?', r'[^/]') ) def restrict_rglob(self, pattern): """ Raise ValueError if ** appears in anything but a full path segment. >>> Translator().translate('**foo') Traceback (most recent call last): ... ValueError: ** must appear alone in a path segment """ seps_pattern = rf'[{re.escape(self.seps)}]+' segments = re.split(seps_pattern, pattern) if any('**' in segment and segment != '**' for segment in segments): raise ValueError("** must appear alone in a path segment") def star_not_empty(self, pattern): """ Ensure that * will not match an empty segment. """ def handle_segment(match): segment = match.group(0) return '?*' if segment == '*' else segment not_seps_pattern = rf'[^{re.escape(self.seps)}]+' return re.sub(not_seps_pattern, handle_segment, pattern) def separate(pattern): """ Separate out character sets to avoid translating their contents. >>> [m.group(0) for m in separate('*.txt')] ['*.txt'] >>> [m.group(0) for m in separate('a[?]txt')] ['a', '[?]', 'txt'] """ return re.finditer(r'([^\[]+)|(?P[\[].*?[\]])|([\[][^\]]*$)', pattern) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1749402395.139238 zipp-3.23.0/zipp.egg-info/0000755000175100001660000000000015021341433014634 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402395.0 zipp-3.23.0/zipp.egg-info/PKG-INFO0000644000175100001660000000675315021341433015744 0ustar00runnerdockerMetadata-Version: 2.4 Name: zipp Version: 3.23.0 Summary: Backport of pathlib-compatible object wrapper for zip files Author-email: "Jason R. Coombs" License-Expression: MIT Project-URL: Source, https://github.com/jaraco/zipp Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Provides-Extra: test Requires-Dist: pytest!=8.1.*,>=6; extra == "test" Requires-Dist: jaraco.itertools; extra == "test" Requires-Dist: jaraco.functools; extra == "test" Requires-Dist: more_itertools; extra == "test" Requires-Dist: big-O; extra == "test" Requires-Dist: pytest-ignore-flaky; extra == "test" Requires-Dist: jaraco.test; extra == "test" Provides-Extra: doc Requires-Dist: sphinx>=3.5; extra == "doc" Requires-Dist: jaraco.packaging>=9.3; extra == "doc" Requires-Dist: rst.linker>=1.9; extra == "doc" Requires-Dist: furo; extra == "doc" Requires-Dist: sphinx-lint; extra == "doc" Requires-Dist: jaraco.tidelift>=1.4; extra == "doc" Provides-Extra: check Requires-Dist: pytest-checkdocs>=2.4; extra == "check" Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" Provides-Extra: cover Requires-Dist: pytest-cov; extra == "cover" Provides-Extra: enabler Requires-Dist: pytest-enabler>=2.2; extra == "enabler" Provides-Extra: type Requires-Dist: pytest-mypy; extra == "type" Dynamic: license-file .. image:: https://img.shields.io/pypi/v/zipp.svg :target: https://pypi.org/project/zipp .. image:: https://img.shields.io/pypi/pyversions/zipp.svg .. image:: https://github.com/jaraco/zipp/actions/workflows/main.yml/badge.svg :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://readthedocs.org/projects/zipp/badge/?version=latest .. :target: https://zipp.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2025-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/zipp :target: https://tidelift.com/subscription/pkg/pypi-zipp?utm_source=pypi-zipp&utm_medium=readme A pathlib-compatible Zipfile object wrapper. Official backport of the standard library `Path object `_. Compatibility ============= New features are introduced in this third-party library and later merged into CPython. The following table indicates which versions of this library were contributed to different versions in the standard library: .. list-table:: :header-rows: 1 * - zipp - stdlib * - 3.18 - 3.13 * - 3.16 - 3.12 * - 3.5 - 3.11 * - 3.2 - 3.10 * - 3.3 ?? - 3.9 * - 1.0 - 3.8 Usage ===== Use ``zipp.Path`` in place of ``zipfile.Path`` on any Python. For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402395.0 zipp-3.23.0/zipp.egg-info/SOURCES.txt0000644000175100001660000000143115021341433016517 0ustar00runnerdocker.coveragerc .editorconfig .pre-commit-config.yaml .readthedocs.yaml LICENSE NEWS.rst README.rst SECURITY.md conftest.py mypy.ini pyproject.toml pytest.ini ruff.toml towncrier.toml tox.ini .github/FUNDING.yml .github/workflows/main.yml docs/conf.py docs/history.rst docs/index.rst tests/__init__.py tests/_support.py tests/_test_params.py tests/test_complexity.py tests/test_path.py tests/write-alpharep.py tests/compat/__init__.py tests/compat/py310.py tests/compat/py313.py tests/compat/py38.py tests/compat/py39.py zipp/__init__.py zipp/_functools.py zipp/glob.py zipp.egg-info/PKG-INFO zipp.egg-info/SOURCES.txt zipp.egg-info/dependency_links.txt zipp.egg-info/requires.txt zipp.egg-info/top_level.txt zipp/compat/__init__.py zipp/compat/overlay.py zipp/compat/py310.py zipp/compat/py313.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402395.0 zipp-3.23.0/zipp.egg-info/dependency_links.txt0000644000175100001660000000000115021341433020702 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402395.0 zipp-3.23.0/zipp.egg-info/requires.txt0000644000175100001660000000055315021341433017237 0ustar00runnerdocker [check] pytest-checkdocs>=2.4 [check:sys_platform != "cygwin"] pytest-ruff>=0.2.1 [cover] pytest-cov [doc] sphinx>=3.5 jaraco.packaging>=9.3 rst.linker>=1.9 furo sphinx-lint jaraco.tidelift>=1.4 [enabler] pytest-enabler>=2.2 [test] pytest!=8.1.*,>=6 jaraco.itertools jaraco.functools more_itertools big-O pytest-ignore-flaky jaraco.test [type] pytest-mypy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1749402395.0 zipp-3.23.0/zipp.egg-info/top_level.txt0000644000175100001660000000000515021341433017361 0ustar00runnerdockerzipp