Upload folder using huggingface_hub (part 19)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/INSTALLER +1 -0
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/METADATA +141 -0
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/RECORD +872 -0
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/WHEEL +5 -0
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/entry_points.txt +51 -0
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/licenses/LICENSE +17 -0
- venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/top_level.txt +3 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/__init__.py +0 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/downloads/__init__.py +59 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/downloads/preload.py +18 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/setupcfg_examples.txt +22 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/test_apply_pyprojecttoml.py +772 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/test_expand.py +247 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/test_pyprojecttoml.py +396 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py +109 -0
- venv/lib/python3.11/site-packages/setuptools/tests/config/test_setupcfg.py +980 -0
- venv/lib/python3.11/site-packages/setuptools/tests/contexts.py +145 -0
- venv/lib/python3.11/site-packages/setuptools/tests/environment.py +95 -0
- venv/lib/python3.11/site-packages/setuptools/tests/fixtures.py +157 -0
- venv/lib/python3.11/site-packages/setuptools/tests/indexes/test_links_priority/external.html +3 -0
- venv/lib/python3.11/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html +4 -0
- venv/lib/python3.11/site-packages/setuptools/tests/integration/__init__.py +0 -0
- venv/lib/python3.11/site-packages/setuptools/tests/integration/helpers.py +77 -0
- venv/lib/python3.11/site-packages/setuptools/tests/integration/test_pip_install_sdist.py +223 -0
- venv/lib/python3.11/site-packages/setuptools/tests/mod_with_constant.py +1 -0
- venv/lib/python3.11/site-packages/setuptools/tests/namespaces.py +90 -0
- venv/lib/python3.11/site-packages/setuptools/tests/script-with-bom.py +1 -0
- venv/lib/python3.11/site-packages/setuptools/tests/server.py +86 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_archive_util.py +36 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_deprecations.py +28 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_egg.py +73 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_wheel.py +708 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_build.py +33 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_build_clib.py +84 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_build_ext.py +293 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_build_meta.py +983 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_build_py.py +480 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_config_discovery.py +647 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_core_metadata.py +622 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_depends.py +15 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_develop.py +175 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_dist.py +278 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_dist_info.py +210 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_distutils_adoption.py +198 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_easy_install.py +1476 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_editable_install.py +1289 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_egg_info.py +1305 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_extern.py +15 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_find_packages.py +218 -0
- venv/lib/python3.11/site-packages/setuptools/tests/test_find_py_modules.py +73 -0
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: setuptools
|
| 3 |
+
Version: 78.1.0
|
| 4 |
+
Summary: Easily download, build, install, upgrade, and uninstall Python packages
|
| 5 |
+
Author-email: Python Packaging Authority <distutils-sig@python.org>
|
| 6 |
+
Project-URL: Source, https://github.com/pypa/setuptools
|
| 7 |
+
Project-URL: Documentation, https://setuptools.pypa.io/
|
| 8 |
+
Project-URL: Changelog, https://setuptools.pypa.io/en/stable/history.html
|
| 9 |
+
Keywords: CPAN PyPI distutils eggs package management
|
| 10 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 11 |
+
Classifier: Intended Audience :: Developers
|
| 12 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 13 |
+
Classifier: Programming Language :: Python :: 3
|
| 14 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 15 |
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
| 16 |
+
Classifier: Topic :: System :: Archiving :: Packaging
|
| 17 |
+
Classifier: Topic :: System :: Systems Administration
|
| 18 |
+
Classifier: Topic :: Utilities
|
| 19 |
+
Requires-Python: >=3.9
|
| 20 |
+
Description-Content-Type: text/x-rst
|
| 21 |
+
License-File: LICENSE
|
| 22 |
+
Provides-Extra: test
|
| 23 |
+
Requires-Dist: pytest!=8.1.*,>=6; extra == "test"
|
| 24 |
+
Requires-Dist: virtualenv>=13.0.0; extra == "test"
|
| 25 |
+
Requires-Dist: wheel>=0.44.0; extra == "test"
|
| 26 |
+
Requires-Dist: pip>=19.1; extra == "test"
|
| 27 |
+
Requires-Dist: packaging>=24.2; extra == "test"
|
| 28 |
+
Requires-Dist: jaraco.envs>=2.2; extra == "test"
|
| 29 |
+
Requires-Dist: pytest-xdist>=3; extra == "test"
|
| 30 |
+
Requires-Dist: jaraco.path>=3.7.2; extra == "test"
|
| 31 |
+
Requires-Dist: build[virtualenv]>=1.0.3; extra == "test"
|
| 32 |
+
Requires-Dist: filelock>=3.4.0; extra == "test"
|
| 33 |
+
Requires-Dist: ini2toml[lite]>=0.14; extra == "test"
|
| 34 |
+
Requires-Dist: tomli-w>=1.0.0; extra == "test"
|
| 35 |
+
Requires-Dist: pytest-timeout; extra == "test"
|
| 36 |
+
Requires-Dist: pytest-perf; sys_platform != "cygwin" and extra == "test"
|
| 37 |
+
Requires-Dist: jaraco.develop>=7.21; (python_version >= "3.9" and sys_platform != "cygwin") and extra == "test"
|
| 38 |
+
Requires-Dist: pytest-home>=0.5; extra == "test"
|
| 39 |
+
Requires-Dist: pytest-subprocess; extra == "test"
|
| 40 |
+
Requires-Dist: pyproject-hooks!=1.1; extra == "test"
|
| 41 |
+
Requires-Dist: jaraco.test>=5.5; extra == "test"
|
| 42 |
+
Provides-Extra: doc
|
| 43 |
+
Requires-Dist: sphinx>=3.5; extra == "doc"
|
| 44 |
+
Requires-Dist: jaraco.packaging>=9.3; extra == "doc"
|
| 45 |
+
Requires-Dist: rst.linker>=1.9; extra == "doc"
|
| 46 |
+
Requires-Dist: furo; extra == "doc"
|
| 47 |
+
Requires-Dist: sphinx-lint; extra == "doc"
|
| 48 |
+
Requires-Dist: jaraco.tidelift>=1.4; extra == "doc"
|
| 49 |
+
Requires-Dist: pygments-github-lexers==0.0.5; extra == "doc"
|
| 50 |
+
Requires-Dist: sphinx-favicon; extra == "doc"
|
| 51 |
+
Requires-Dist: sphinx-inline-tabs; extra == "doc"
|
| 52 |
+
Requires-Dist: sphinx-reredirects; extra == "doc"
|
| 53 |
+
Requires-Dist: sphinxcontrib-towncrier; extra == "doc"
|
| 54 |
+
Requires-Dist: sphinx-notfound-page<2,>=1; extra == "doc"
|
| 55 |
+
Requires-Dist: pyproject-hooks!=1.1; extra == "doc"
|
| 56 |
+
Requires-Dist: towncrier<24.7; extra == "doc"
|
| 57 |
+
Provides-Extra: ssl
|
| 58 |
+
Provides-Extra: certs
|
| 59 |
+
Provides-Extra: core
|
| 60 |
+
Requires-Dist: packaging>=24.2; extra == "core"
|
| 61 |
+
Requires-Dist: more_itertools>=8.8; extra == "core"
|
| 62 |
+
Requires-Dist: jaraco.text>=3.7; extra == "core"
|
| 63 |
+
Requires-Dist: importlib_metadata>=6; python_version < "3.10" and extra == "core"
|
| 64 |
+
Requires-Dist: tomli>=2.0.1; python_version < "3.11" and extra == "core"
|
| 65 |
+
Requires-Dist: wheel>=0.43.0; extra == "core"
|
| 66 |
+
Requires-Dist: platformdirs>=4.2.2; extra == "core"
|
| 67 |
+
Requires-Dist: jaraco.functools>=4; extra == "core"
|
| 68 |
+
Requires-Dist: more_itertools; extra == "core"
|
| 69 |
+
Provides-Extra: check
|
| 70 |
+
Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
|
| 71 |
+
Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check"
|
| 72 |
+
Requires-Dist: ruff>=0.8.0; sys_platform != "cygwin" and extra == "check"
|
| 73 |
+
Provides-Extra: cover
|
| 74 |
+
Requires-Dist: pytest-cov; extra == "cover"
|
| 75 |
+
Provides-Extra: enabler
|
| 76 |
+
Requires-Dist: pytest-enabler>=2.2; extra == "enabler"
|
| 77 |
+
Provides-Extra: type
|
| 78 |
+
Requires-Dist: pytest-mypy; extra == "type"
|
| 79 |
+
Requires-Dist: mypy==1.14.*; extra == "type"
|
| 80 |
+
Requires-Dist: importlib_metadata>=7.0.2; python_version < "3.10" and extra == "type"
|
| 81 |
+
Requires-Dist: jaraco.develop>=7.21; sys_platform != "cygwin" and extra == "type"
|
| 82 |
+
Dynamic: license-file
|
| 83 |
+
|
| 84 |
+
.. |pypi-version| image:: https://img.shields.io/pypi/v/setuptools.svg
|
| 85 |
+
:target: https://pypi.org/project/setuptools
|
| 86 |
+
|
| 87 |
+
.. |py-version| image:: https://img.shields.io/pypi/pyversions/setuptools.svg
|
| 88 |
+
|
| 89 |
+
.. |test-badge| image:: https://github.com/pypa/setuptools/actions/workflows/main.yml/badge.svg
|
| 90 |
+
:target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22
|
| 91 |
+
:alt: tests
|
| 92 |
+
|
| 93 |
+
.. |ruff-badge| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
|
| 94 |
+
:target: https://github.com/astral-sh/ruff
|
| 95 |
+
:alt: Ruff
|
| 96 |
+
|
| 97 |
+
.. |docs-badge| image:: https://img.shields.io/readthedocs/setuptools/latest.svg
|
| 98 |
+
:target: https://setuptools.pypa.io
|
| 99 |
+
|
| 100 |
+
.. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2025-informational
|
| 101 |
+
:target: https://blog.jaraco.com/skeleton
|
| 102 |
+
|
| 103 |
+
.. |codecov-badge| image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white
|
| 104 |
+
:target: https://codecov.io/gh/pypa/setuptools
|
| 105 |
+
|
| 106 |
+
.. |tidelift-badge| image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat
|
| 107 |
+
:target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme
|
| 108 |
+
|
| 109 |
+
.. |discord-badge| image:: https://img.shields.io/discord/803025117553754132
|
| 110 |
+
:target: https://discord.com/channels/803025117553754132/815945031150993468
|
| 111 |
+
:alt: Discord
|
| 112 |
+
|
| 113 |
+
|pypi-version| |py-version| |test-badge| |ruff-badge| |docs-badge| |skeleton-badge| |codecov-badge| |discord-badge|
|
| 114 |
+
|
| 115 |
+
See the `Quickstart <https://setuptools.pypa.io/en/latest/userguide/quickstart.html>`_
|
| 116 |
+
and the `User's Guide <https://setuptools.pypa.io/en/latest/userguide/>`_ for
|
| 117 |
+
instructions on how to use Setuptools.
|
| 118 |
+
|
| 119 |
+
Questions and comments should be directed to `GitHub Discussions
|
| 120 |
+
<https://github.com/pypa/setuptools/discussions>`_.
|
| 121 |
+
Bug reports and especially tested patches may be
|
| 122 |
+
submitted directly to the `bug tracker
|
| 123 |
+
<https://github.com/pypa/setuptools/issues>`_.
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
Code of Conduct
|
| 127 |
+
===============
|
| 128 |
+
|
| 129 |
+
Everyone interacting in the setuptools project's codebases, issue trackers,
|
| 130 |
+
chat rooms, and fora is expected to follow the
|
| 131 |
+
`PSF Code of Conduct <https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md>`_.
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
For Enterprise
|
| 135 |
+
==============
|
| 136 |
+
|
| 137 |
+
Available as part of the Tidelift Subscription.
|
| 138 |
+
|
| 139 |
+
Setuptools 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.
|
| 140 |
+
|
| 141 |
+
`Learn more <https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=referral&utm_campaign=github>`_.
|
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,872 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_distutils_hack/__init__.py,sha256=34HmvLo07j45Uvd2VR-2aRQ7lJD91sTK6zJgn5fphbQ,6755
|
| 2 |
+
_distutils_hack/__pycache__/__init__.cpython-311.pyc,,
|
| 3 |
+
_distutils_hack/__pycache__/override.cpython-311.pyc,,
|
| 4 |
+
_distutils_hack/override.py,sha256=Eu_s-NF6VIZ4Cqd0tbbA5wtWky2IZPNd8et6GLt1mzo,44
|
| 5 |
+
distutils-precedence.pth,sha256=JjjOniUA5XKl4N5_rtZmHrVp0baW_LoHsN0iPaX10iQ,151
|
| 6 |
+
pkg_resources/__init__.py,sha256=-rh7XOnTxdGuC-_9FAyu5D6s8BL1UsBehxUtj7a-IVo,126203
|
| 7 |
+
pkg_resources/__pycache__/__init__.cpython-311.pyc,,
|
| 8 |
+
pkg_resources/api_tests.txt,sha256=XEdvy4igHHrq2qNHNMHnlfO6XSQKNqOyLHbl6QcpfAI,12595
|
| 9 |
+
pkg_resources/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 10 |
+
pkg_resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 11 |
+
pkg_resources/tests/__pycache__/__init__.cpython-311.pyc,,
|
| 12 |
+
pkg_resources/tests/__pycache__/test_find_distributions.cpython-311.pyc,,
|
| 13 |
+
pkg_resources/tests/__pycache__/test_integration_zope_interface.cpython-311.pyc,,
|
| 14 |
+
pkg_resources/tests/__pycache__/test_markers.cpython-311.pyc,,
|
| 15 |
+
pkg_resources/tests/__pycache__/test_pkg_resources.cpython-311.pyc,,
|
| 16 |
+
pkg_resources/tests/__pycache__/test_resources.cpython-311.pyc,,
|
| 17 |
+
pkg_resources/tests/__pycache__/test_working_set.cpython-311.pyc,,
|
| 18 |
+
pkg_resources/tests/data/my-test-package-source/__pycache__/setup.cpython-311.pyc,,
|
| 19 |
+
pkg_resources/tests/data/my-test-package-source/setup.cfg,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 20 |
+
pkg_resources/tests/data/my-test-package-source/setup.py,sha256=1VobhAZbMb7M9mfhb_NE8PwDsvukoWLs9aUAS0pYhe8,105
|
| 21 |
+
pkg_resources/tests/data/my-test-package-zip/my-test-package.zip,sha256=AYRcQ39GVePPnMT8TknP1gdDHyJnXhthESmpAjnzSCI,1809
|
| 22 |
+
pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO,sha256=JvWv9Io2PAuYwEEw2fBW4Qc5YvdbkscpKX1kmLzsoHk,187
|
| 23 |
+
pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt,sha256=4ClkH8eTovZrdVrJFsVuxdbMEF--lBVSuKonDAPE5Jc,208
|
| 24 |
+
pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
| 25 |
+
pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
| 26 |
+
pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
| 27 |
+
pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg,sha256=ZTlMGxjRGiKDNkiA2c75jbQH2TWIteP00irF9gvczbo,843
|
| 28 |
+
pkg_resources/tests/test_find_distributions.py,sha256=U91cov5L1COAIWLNq3Xy4plU7_MnOE1WtXMu6iV2waM,1972
|
| 29 |
+
pkg_resources/tests/test_integration_zope_interface.py,sha256=nzVoK557KZQN0V3DIQ1sVeaCOgt4Kpl-CODAWsO7pmc,1652
|
| 30 |
+
pkg_resources/tests/test_markers.py,sha256=0orKg7UMDf7fnuNQvRMOc-EF9EAP_JTQnk4mtGgbW50,241
|
| 31 |
+
pkg_resources/tests/test_pkg_resources.py,sha256=5Mt4bJQhLCL8j8cC46Uv32Np2Xc1TTxLGawIfET55Fk,17111
|
| 32 |
+
pkg_resources/tests/test_resources.py,sha256=K0LqMAUGpRQ9pUb9K0vyI7GesvtlQvTH074m-E2VQlo,31252
|
| 33 |
+
pkg_resources/tests/test_working_set.py,sha256=lRtGJWIixSwSMSbjHgRxeJEQiLMRXcz3xzJL2qL7eXY,8602
|
| 34 |
+
setuptools-78.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 35 |
+
setuptools-78.1.0.dist-info/METADATA,sha256=RESmtUmo67AuFzn0N1r3dWyEH5bwwxB3oYMnPHRivRA,6599
|
| 36 |
+
setuptools-78.1.0.dist-info/RECORD,,
|
| 37 |
+
setuptools-78.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
| 38 |
+
setuptools-78.1.0.dist-info/entry_points.txt,sha256=zkgthpf_Fa9NVE9p6FKT3Xk9DR1faAcRU4coggsV7jA,2449
|
| 39 |
+
setuptools-78.1.0.dist-info/licenses/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 40 |
+
setuptools-78.1.0.dist-info/top_level.txt,sha256=d9yL39v_W7qmKDDSH6sT4bE0j_Ls1M3P161OGgdsm4g,41
|
| 41 |
+
setuptools/__init__.py,sha256=AQsMeRFWz9CQ9aBtcSkbB4Dn8t2w86-GPriklpoAjew,10406
|
| 42 |
+
setuptools/__pycache__/__init__.cpython-311.pyc,,
|
| 43 |
+
setuptools/__pycache__/_core_metadata.cpython-311.pyc,,
|
| 44 |
+
setuptools/__pycache__/_entry_points.cpython-311.pyc,,
|
| 45 |
+
setuptools/__pycache__/_imp.cpython-311.pyc,,
|
| 46 |
+
setuptools/__pycache__/_importlib.cpython-311.pyc,,
|
| 47 |
+
setuptools/__pycache__/_itertools.cpython-311.pyc,,
|
| 48 |
+
setuptools/__pycache__/_normalization.cpython-311.pyc,,
|
| 49 |
+
setuptools/__pycache__/_path.cpython-311.pyc,,
|
| 50 |
+
setuptools/__pycache__/_reqs.cpython-311.pyc,,
|
| 51 |
+
setuptools/__pycache__/_shutil.cpython-311.pyc,,
|
| 52 |
+
setuptools/__pycache__/_static.cpython-311.pyc,,
|
| 53 |
+
setuptools/__pycache__/archive_util.cpython-311.pyc,,
|
| 54 |
+
setuptools/__pycache__/build_meta.cpython-311.pyc,,
|
| 55 |
+
setuptools/__pycache__/depends.cpython-311.pyc,,
|
| 56 |
+
setuptools/__pycache__/discovery.cpython-311.pyc,,
|
| 57 |
+
setuptools/__pycache__/dist.cpython-311.pyc,,
|
| 58 |
+
setuptools/__pycache__/errors.cpython-311.pyc,,
|
| 59 |
+
setuptools/__pycache__/extension.cpython-311.pyc,,
|
| 60 |
+
setuptools/__pycache__/glob.cpython-311.pyc,,
|
| 61 |
+
setuptools/__pycache__/installer.cpython-311.pyc,,
|
| 62 |
+
setuptools/__pycache__/launch.cpython-311.pyc,,
|
| 63 |
+
setuptools/__pycache__/logging.cpython-311.pyc,,
|
| 64 |
+
setuptools/__pycache__/modified.cpython-311.pyc,,
|
| 65 |
+
setuptools/__pycache__/monkey.cpython-311.pyc,,
|
| 66 |
+
setuptools/__pycache__/msvc.cpython-311.pyc,,
|
| 67 |
+
setuptools/__pycache__/namespaces.cpython-311.pyc,,
|
| 68 |
+
setuptools/__pycache__/package_index.cpython-311.pyc,,
|
| 69 |
+
setuptools/__pycache__/sandbox.cpython-311.pyc,,
|
| 70 |
+
setuptools/__pycache__/unicode_utils.cpython-311.pyc,,
|
| 71 |
+
setuptools/__pycache__/version.cpython-311.pyc,,
|
| 72 |
+
setuptools/__pycache__/warnings.cpython-311.pyc,,
|
| 73 |
+
setuptools/__pycache__/wheel.cpython-311.pyc,,
|
| 74 |
+
setuptools/__pycache__/windows_support.cpython-311.pyc,,
|
| 75 |
+
setuptools/_core_metadata.py,sha256=T7Tjp-WSoN881adev3R1wzXCPnkDHqbC2MgylN1yjS8,11978
|
| 76 |
+
setuptools/_distutils/__init__.py,sha256=xGYuhWwLG07J0Q49BVnEjPy6wyDcd6veJMDJX7ljlyM,359
|
| 77 |
+
setuptools/_distutils/__pycache__/__init__.cpython-311.pyc,,
|
| 78 |
+
setuptools/_distutils/__pycache__/_log.cpython-311.pyc,,
|
| 79 |
+
setuptools/_distutils/__pycache__/_macos_compat.cpython-311.pyc,,
|
| 80 |
+
setuptools/_distutils/__pycache__/_modified.cpython-311.pyc,,
|
| 81 |
+
setuptools/_distutils/__pycache__/_msvccompiler.cpython-311.pyc,,
|
| 82 |
+
setuptools/_distutils/__pycache__/archive_util.cpython-311.pyc,,
|
| 83 |
+
setuptools/_distutils/__pycache__/ccompiler.cpython-311.pyc,,
|
| 84 |
+
setuptools/_distutils/__pycache__/cmd.cpython-311.pyc,,
|
| 85 |
+
setuptools/_distutils/__pycache__/core.cpython-311.pyc,,
|
| 86 |
+
setuptools/_distutils/__pycache__/cygwinccompiler.cpython-311.pyc,,
|
| 87 |
+
setuptools/_distutils/__pycache__/debug.cpython-311.pyc,,
|
| 88 |
+
setuptools/_distutils/__pycache__/dep_util.cpython-311.pyc,,
|
| 89 |
+
setuptools/_distutils/__pycache__/dir_util.cpython-311.pyc,,
|
| 90 |
+
setuptools/_distutils/__pycache__/dist.cpython-311.pyc,,
|
| 91 |
+
setuptools/_distutils/__pycache__/errors.cpython-311.pyc,,
|
| 92 |
+
setuptools/_distutils/__pycache__/extension.cpython-311.pyc,,
|
| 93 |
+
setuptools/_distutils/__pycache__/fancy_getopt.cpython-311.pyc,,
|
| 94 |
+
setuptools/_distutils/__pycache__/file_util.cpython-311.pyc,,
|
| 95 |
+
setuptools/_distutils/__pycache__/filelist.cpython-311.pyc,,
|
| 96 |
+
setuptools/_distutils/__pycache__/log.cpython-311.pyc,,
|
| 97 |
+
setuptools/_distutils/__pycache__/spawn.cpython-311.pyc,,
|
| 98 |
+
setuptools/_distutils/__pycache__/sysconfig.cpython-311.pyc,,
|
| 99 |
+
setuptools/_distutils/__pycache__/text_file.cpython-311.pyc,,
|
| 100 |
+
setuptools/_distutils/__pycache__/unixccompiler.cpython-311.pyc,,
|
| 101 |
+
setuptools/_distutils/__pycache__/util.cpython-311.pyc,,
|
| 102 |
+
setuptools/_distutils/__pycache__/version.cpython-311.pyc,,
|
| 103 |
+
setuptools/_distutils/__pycache__/versionpredicate.cpython-311.pyc,,
|
| 104 |
+
setuptools/_distutils/__pycache__/zosccompiler.cpython-311.pyc,,
|
| 105 |
+
setuptools/_distutils/_log.py,sha256=i-lNTTcXS8TmWITJ6DODGvtW5z5tMattJQ76h8rZxQU,42
|
| 106 |
+
setuptools/_distutils/_macos_compat.py,sha256=JzUGhF4E5yIITHbUaPobZEWjGHdrrcNV63z86S4RjBc,239
|
| 107 |
+
setuptools/_distutils/_modified.py,sha256=RF1n1CexyDYV3lvGbeXS0s-XCJVboDOIUbA8wEQqYTY,3211
|
| 108 |
+
setuptools/_distutils/_msvccompiler.py,sha256=9PSfSHxvJnHnQL6Sqz4Xcz7iaBIT62p6BheQzGsSlwo,335
|
| 109 |
+
setuptools/_distutils/archive_util.py,sha256=Qw2z-Pt-NV8lNUQrzjs3XDGWCWHMPnqHLyt8TiD2XEA,8884
|
| 110 |
+
setuptools/_distutils/ccompiler.py,sha256=FKVjqzGJ7c-FtouNjhLiaMPm5LKMZHHAruXf8LU216c,524
|
| 111 |
+
setuptools/_distutils/cmd.py,sha256=hXtaRaH7QBnfNOIqEvCt47iwZzD9MVvBdhhdQctHsxM,22186
|
| 112 |
+
setuptools/_distutils/command/__init__.py,sha256=GfFAzbBqk1qxSH4BdaKioKS4hRRnD44BAmwEN85C4u8,386
|
| 113 |
+
setuptools/_distutils/command/__pycache__/__init__.cpython-311.pyc,,
|
| 114 |
+
setuptools/_distutils/command/__pycache__/_framework_compat.cpython-311.pyc,,
|
| 115 |
+
setuptools/_distutils/command/__pycache__/bdist.cpython-311.pyc,,
|
| 116 |
+
setuptools/_distutils/command/__pycache__/bdist_dumb.cpython-311.pyc,,
|
| 117 |
+
setuptools/_distutils/command/__pycache__/bdist_rpm.cpython-311.pyc,,
|
| 118 |
+
setuptools/_distutils/command/__pycache__/build.cpython-311.pyc,,
|
| 119 |
+
setuptools/_distutils/command/__pycache__/build_clib.cpython-311.pyc,,
|
| 120 |
+
setuptools/_distutils/command/__pycache__/build_ext.cpython-311.pyc,,
|
| 121 |
+
setuptools/_distutils/command/__pycache__/build_py.cpython-311.pyc,,
|
| 122 |
+
setuptools/_distutils/command/__pycache__/build_scripts.cpython-311.pyc,,
|
| 123 |
+
setuptools/_distutils/command/__pycache__/check.cpython-311.pyc,,
|
| 124 |
+
setuptools/_distutils/command/__pycache__/clean.cpython-311.pyc,,
|
| 125 |
+
setuptools/_distutils/command/__pycache__/config.cpython-311.pyc,,
|
| 126 |
+
setuptools/_distutils/command/__pycache__/install.cpython-311.pyc,,
|
| 127 |
+
setuptools/_distutils/command/__pycache__/install_data.cpython-311.pyc,,
|
| 128 |
+
setuptools/_distutils/command/__pycache__/install_egg_info.cpython-311.pyc,,
|
| 129 |
+
setuptools/_distutils/command/__pycache__/install_headers.cpython-311.pyc,,
|
| 130 |
+
setuptools/_distutils/command/__pycache__/install_lib.cpython-311.pyc,,
|
| 131 |
+
setuptools/_distutils/command/__pycache__/install_scripts.cpython-311.pyc,,
|
| 132 |
+
setuptools/_distutils/command/__pycache__/sdist.cpython-311.pyc,,
|
| 133 |
+
setuptools/_distutils/command/_framework_compat.py,sha256=0iZdSJYzGRWCCvzRDKE-R0-_yaAYvFMd1ylXb2eYXug,1609
|
| 134 |
+
setuptools/_distutils/command/bdist.py,sha256=jWtk61R7fWNUUNxJV0thTZzU5n80L3Ay1waSiP9kiLA,5854
|
| 135 |
+
setuptools/_distutils/command/bdist_dumb.py,sha256=Hx1jAqoZNxYIy4N5TLzUp6J5fi8Ls18py7UlLNFhO2E,4631
|
| 136 |
+
setuptools/_distutils/command/bdist_rpm.py,sha256=nxcXXv5a7B-1ntWu4DbGmCtES4EBINrJaBQcRNAYCJI,21785
|
| 137 |
+
setuptools/_distutils/command/build.py,sha256=SpHlagf0iNaKVyIhxDfhPFZ8X1-LAWOCQACy-yt2K0w,5923
|
| 138 |
+
setuptools/_distutils/command/build_clib.py,sha256=aMqZcUfCbOAu_xr-A9iW-Q9YZHzpDGLRTezOgMQJmSQ,7777
|
| 139 |
+
setuptools/_distutils/command/build_ext.py,sha256=zrrsu9HXnzV6bXYbJuZCK4SwVZMjKnl4pG1o3bNcxtc,32710
|
| 140 |
+
setuptools/_distutils/command/build_py.py,sha256=Vfq-INemoMbg6f003BTy_Ufp8bjOZhmFIhpKMcfXLgs,16696
|
| 141 |
+
setuptools/_distutils/command/build_scripts.py,sha256=tUpEzwTsnrP8qrNory3ldPB240QDCFkMp-pdYPs2wTk,5118
|
| 142 |
+
setuptools/_distutils/command/check.py,sha256=yoNe2MPY4JcTM7rwoIQdfZ75q5Ri058I2coi-Gq9CjM,4946
|
| 143 |
+
setuptools/_distutils/command/clean.py,sha256=dQAacOabwBXU9JoZ-1GFusq3eFltDaeXJFSYncqGbvE,2644
|
| 144 |
+
setuptools/_distutils/command/config.py,sha256=BuUdPu91Vo9w44xzD1RQfiyXfSfVcNpeX3aeoKcGAOw,12818
|
| 145 |
+
setuptools/_distutils/command/install.py,sha256=-JenB-mua4hc2RI_-W8F9PnP_J-OaFO7E0PJGKxLo1o,30072
|
| 146 |
+
setuptools/_distutils/command/install_data.py,sha256=GzBlUWWKubTYJlP-L0auUriq9cL-5RKOcoyHTttKj0Q,2875
|
| 147 |
+
setuptools/_distutils/command/install_egg_info.py,sha256=ffiLoU1ivQJ8q2_WL7ZygZbUcOsgdFLKL7otEIJWWkI,2868
|
| 148 |
+
setuptools/_distutils/command/install_headers.py,sha256=5ciKCj8c3XKsYNKdkdMvnypaUCKcoWCDeeZij3fD-Z4,1272
|
| 149 |
+
setuptools/_distutils/command/install_lib.py,sha256=2s9-m5-b1qKm51F28lB5L39Z6vv_GHMlv9dNBSupok0,8588
|
| 150 |
+
setuptools/_distutils/command/install_scripts.py,sha256=M0pPdiaqB7TGmqTMujpGGeiL0Iq_CTeGjMFtrmDmwzM,2002
|
| 151 |
+
setuptools/_distutils/command/sdist.py,sha256=cRIF6Ht1hJ6ayOOFVycMFBUNxjo94e_rFYPx4Hi8Ahc,19151
|
| 152 |
+
setuptools/_distutils/compat/__init__.py,sha256=J20aXGjJ86Rg41xFLIWlcWCgZ9edMdJ9vvdNEQ87vPQ,522
|
| 153 |
+
setuptools/_distutils/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 154 |
+
setuptools/_distutils/compat/__pycache__/numpy.cpython-311.pyc,,
|
| 155 |
+
setuptools/_distutils/compat/__pycache__/py39.cpython-311.pyc,,
|
| 156 |
+
setuptools/_distutils/compat/numpy.py,sha256=UFgneZw9w97g4c-yGoAIOyLxUOWQ-fPRIhhfMs7_Ouc,167
|
| 157 |
+
setuptools/_distutils/compat/py39.py,sha256=hOsD6lwZLqZoMnacNJ3P6nUA-LJQhEpVtYTzVH0o96M,1964
|
| 158 |
+
setuptools/_distutils/compilers/C/__pycache__/base.cpython-311.pyc,,
|
| 159 |
+
setuptools/_distutils/compilers/C/__pycache__/cygwin.cpython-311.pyc,,
|
| 160 |
+
setuptools/_distutils/compilers/C/__pycache__/errors.cpython-311.pyc,,
|
| 161 |
+
setuptools/_distutils/compilers/C/__pycache__/msvc.cpython-311.pyc,,
|
| 162 |
+
setuptools/_distutils/compilers/C/__pycache__/unix.cpython-311.pyc,,
|
| 163 |
+
setuptools/_distutils/compilers/C/__pycache__/zos.cpython-311.pyc,,
|
| 164 |
+
setuptools/_distutils/compilers/C/base.py,sha256=XR1rBCStCquqm7QOYXD41-LfvsFcPpGxrwxeXzJyn_w,54876
|
| 165 |
+
setuptools/_distutils/compilers/C/cygwin.py,sha256=DUlwQSb55aj7OdcmcddrmCmVEjEaxIiJ5hHUO3GBPNs,11844
|
| 166 |
+
setuptools/_distutils/compilers/C/errors.py,sha256=sKOVzJajMUmNdfywo9UM_QQGsKFcclDhtI5TlCiXMLc,573
|
| 167 |
+
setuptools/_distutils/compilers/C/msvc.py,sha256=elzG8v9jN5QytLMwLCdUdSuZ3eZ3R98VUvnm9Y2PBCA,21404
|
| 168 |
+
setuptools/_distutils/compilers/C/tests/__pycache__/test_base.cpython-311.pyc,,
|
| 169 |
+
setuptools/_distutils/compilers/C/tests/__pycache__/test_cygwin.cpython-311.pyc,,
|
| 170 |
+
setuptools/_distutils/compilers/C/tests/__pycache__/test_mingw.cpython-311.pyc,,
|
| 171 |
+
setuptools/_distutils/compilers/C/tests/__pycache__/test_msvc.cpython-311.pyc,,
|
| 172 |
+
setuptools/_distutils/compilers/C/tests/__pycache__/test_unix.cpython-311.pyc,,
|
| 173 |
+
setuptools/_distutils/compilers/C/tests/test_base.py,sha256=rdhHc56bhXtm5NnN9BSHwr6c69UqzMItZQzlw2AsdMc,2706
|
| 174 |
+
setuptools/_distutils/compilers/C/tests/test_cygwin.py,sha256=UgV2VgUXj3VulcbDc0UBWfEyJDx42tgSwS4LzHix3mY,2701
|
| 175 |
+
setuptools/_distutils/compilers/C/tests/test_mingw.py,sha256=hCmwyywISpRoyOySbFHBL4TprWRV0mUWDKmOLO8XBXE,1900
|
| 176 |
+
setuptools/_distutils/compilers/C/tests/test_msvc.py,sha256=DlGjmZ1mBSMXIgmlu80BKc7V-EJOZuYucwJwFh5dn28,4151
|
| 177 |
+
setuptools/_distutils/compilers/C/tests/test_unix.py,sha256=ErbYWio7ejY2ZqQmPk4AwOu1HFW4-_-aZdUvGa1W2Fw,11834
|
| 178 |
+
setuptools/_distutils/compilers/C/unix.py,sha256=l7CxY4rDJAECJo-vcv6io0SBmmPJ9JmN5mSmZcin2VU,16502
|
| 179 |
+
setuptools/_distutils/compilers/C/zos.py,sha256=vnNeWLRZkdIkdZ-YyBnL8idTUfcCOn0tLMW5OBJ0ScU,6586
|
| 180 |
+
setuptools/_distutils/core.py,sha256=GEHKaFC48T3o-_SmH4864GvKyx1IgbVC6ISIPVlx7a4,9364
|
| 181 |
+
setuptools/_distutils/cygwinccompiler.py,sha256=mG_cU8SVZ4amD_VtF5vH6BXP0-kghGsDPbDSXrQ963c,594
|
| 182 |
+
setuptools/_distutils/debug.py,sha256=N6MrTAqK6l9SVk6tWweR108PM8Ol7qNlfyV-nHcLhsY,139
|
| 183 |
+
setuptools/_distutils/dep_util.py,sha256=xN75p6ZpHhMiHEc-rpL2XilJQynHnDNiafHteaZ4tjU,349
|
| 184 |
+
setuptools/_distutils/dir_util.py,sha256=DXPUlfVVGsg9B-Jgg4At_j9T7vM60OgwNXkQHqTo7-I,7236
|
| 185 |
+
setuptools/_distutils/dist.py,sha256=gW598UE0WMkzXQQ31Nr-8L7MPw0oIOz5OSSRzYZlwrM,55794
|
| 186 |
+
setuptools/_distutils/errors.py,sha256=PPE2oDRh5y9Q1beKK9rhdvDaCzQhi4HCXs4KcqfqgZY,3092
|
| 187 |
+
setuptools/_distutils/extension.py,sha256=Foyu4gULcPqm1_U9zrYYHxNk4NqglXv1rbsOk_QrSds,11155
|
| 188 |
+
setuptools/_distutils/fancy_getopt.py,sha256=PjdO-bWCW0imV_UN-MGEw9R2GP2OiE8pHjITgmTAY3Q,17895
|
| 189 |
+
setuptools/_distutils/file_util.py,sha256=YFQL_pD3hLuER9II_H6-hDC_YIGEookdd4wedLuiTW0,7978
|
| 190 |
+
setuptools/_distutils/filelist.py,sha256=MBeSRJmPcKmDv8ooZgSU4BiQPZ0Khwv8l_jhD50XycI,15337
|
| 191 |
+
setuptools/_distutils/log.py,sha256=VyBs5j7z4-K6XTEEBThUc9HyMpoPLGtQpERqbz5ylww,1200
|
| 192 |
+
setuptools/_distutils/spawn.py,sha256=zseCh9sEifyp0I5Vg719JNIASlROJ2ehXqQnHlpt89Q,4086
|
| 193 |
+
setuptools/_distutils/sysconfig.py,sha256=KeI8OHbMuEzHJ8Q0cBez9KZny8iRy6Z6Y0AkMz1jlsU,19728
|
| 194 |
+
setuptools/_distutils/tests/__init__.py,sha256=j-IoPZEtQv3EOPuqNTwalr6GLyRjzCC-OOaNvZzmHsI,1485
|
| 195 |
+
setuptools/_distutils/tests/__pycache__/__init__.cpython-311.pyc,,
|
| 196 |
+
setuptools/_distutils/tests/__pycache__/support.cpython-311.pyc,,
|
| 197 |
+
setuptools/_distutils/tests/__pycache__/test_archive_util.cpython-311.pyc,,
|
| 198 |
+
setuptools/_distutils/tests/__pycache__/test_bdist.cpython-311.pyc,,
|
| 199 |
+
setuptools/_distutils/tests/__pycache__/test_bdist_dumb.cpython-311.pyc,,
|
| 200 |
+
setuptools/_distutils/tests/__pycache__/test_bdist_rpm.cpython-311.pyc,,
|
| 201 |
+
setuptools/_distutils/tests/__pycache__/test_build.cpython-311.pyc,,
|
| 202 |
+
setuptools/_distutils/tests/__pycache__/test_build_clib.cpython-311.pyc,,
|
| 203 |
+
setuptools/_distutils/tests/__pycache__/test_build_ext.cpython-311.pyc,,
|
| 204 |
+
setuptools/_distutils/tests/__pycache__/test_build_py.cpython-311.pyc,,
|
| 205 |
+
setuptools/_distutils/tests/__pycache__/test_build_scripts.cpython-311.pyc,,
|
| 206 |
+
setuptools/_distutils/tests/__pycache__/test_check.cpython-311.pyc,,
|
| 207 |
+
setuptools/_distutils/tests/__pycache__/test_clean.cpython-311.pyc,,
|
| 208 |
+
setuptools/_distutils/tests/__pycache__/test_cmd.cpython-311.pyc,,
|
| 209 |
+
setuptools/_distutils/tests/__pycache__/test_config_cmd.cpython-311.pyc,,
|
| 210 |
+
setuptools/_distutils/tests/__pycache__/test_core.cpython-311.pyc,,
|
| 211 |
+
setuptools/_distutils/tests/__pycache__/test_dir_util.cpython-311.pyc,,
|
| 212 |
+
setuptools/_distutils/tests/__pycache__/test_dist.cpython-311.pyc,,
|
| 213 |
+
setuptools/_distutils/tests/__pycache__/test_extension.cpython-311.pyc,,
|
| 214 |
+
setuptools/_distutils/tests/__pycache__/test_file_util.cpython-311.pyc,,
|
| 215 |
+
setuptools/_distutils/tests/__pycache__/test_filelist.cpython-311.pyc,,
|
| 216 |
+
setuptools/_distutils/tests/__pycache__/test_install.cpython-311.pyc,,
|
| 217 |
+
setuptools/_distutils/tests/__pycache__/test_install_data.cpython-311.pyc,,
|
| 218 |
+
setuptools/_distutils/tests/__pycache__/test_install_headers.cpython-311.pyc,,
|
| 219 |
+
setuptools/_distutils/tests/__pycache__/test_install_lib.cpython-311.pyc,,
|
| 220 |
+
setuptools/_distutils/tests/__pycache__/test_install_scripts.cpython-311.pyc,,
|
| 221 |
+
setuptools/_distutils/tests/__pycache__/test_log.cpython-311.pyc,,
|
| 222 |
+
setuptools/_distutils/tests/__pycache__/test_modified.cpython-311.pyc,,
|
| 223 |
+
setuptools/_distutils/tests/__pycache__/test_sdist.cpython-311.pyc,,
|
| 224 |
+
setuptools/_distutils/tests/__pycache__/test_spawn.cpython-311.pyc,,
|
| 225 |
+
setuptools/_distutils/tests/__pycache__/test_sysconfig.cpython-311.pyc,,
|
| 226 |
+
setuptools/_distutils/tests/__pycache__/test_text_file.cpython-311.pyc,,
|
| 227 |
+
setuptools/_distutils/tests/__pycache__/test_util.cpython-311.pyc,,
|
| 228 |
+
setuptools/_distutils/tests/__pycache__/test_version.cpython-311.pyc,,
|
| 229 |
+
setuptools/_distutils/tests/__pycache__/test_versionpredicate.cpython-311.pyc,,
|
| 230 |
+
setuptools/_distutils/tests/__pycache__/unix_compat.cpython-311.pyc,,
|
| 231 |
+
setuptools/_distutils/tests/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 232 |
+
setuptools/_distutils/tests/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 233 |
+
setuptools/_distutils/tests/compat/__pycache__/py39.cpython-311.pyc,,
|
| 234 |
+
setuptools/_distutils/tests/compat/py39.py,sha256=t0GBTM-30jX-9zCfkwlNBFtzzabemx6065mJ0d9_VRw,1026
|
| 235 |
+
setuptools/_distutils/tests/support.py,sha256=tjsYsyxvpTK4NrkCseh2ujvDIGV0Mf_b5SI5fP2T0yM,4099
|
| 236 |
+
setuptools/_distutils/tests/test_archive_util.py,sha256=jozimSwPBF-JoJfN_vDaiVGZp66BNcWZGh34FlW57DQ,11787
|
| 237 |
+
setuptools/_distutils/tests/test_bdist.py,sha256=xNHxUsLlHsZQRwkzLb_iSD24s-9Mk-NX2ffBWwOyPyc,1396
|
| 238 |
+
setuptools/_distutils/tests/test_bdist_dumb.py,sha256=QF05MHNhPOdZyh88Xpw8KsO64s7pRFkl8KL-RoV4XK0,2247
|
| 239 |
+
setuptools/_distutils/tests/test_bdist_rpm.py,sha256=Hdm-pwWgyaoGdGbEcGZa8cRhGU45y8gHK8umOanTjik,3932
|
| 240 |
+
setuptools/_distutils/tests/test_build.py,sha256=JJY5XpOZco25ZY0pstxl-iI8mHsWP0ujf5o8aOtuZYY,1742
|
| 241 |
+
setuptools/_distutils/tests/test_build_clib.py,sha256=Mo1ZFb4C1VXBYOGvnallwN7YCnTtr24akLDO8Zi4CsY,4331
|
| 242 |
+
setuptools/_distutils/tests/test_build_ext.py,sha256=QFO9qYVhWWdJu17HXc4x9RMnLZlhk0lAHi9HVppbuX4,22545
|
| 243 |
+
setuptools/_distutils/tests/test_build_py.py,sha256=NsfmRrojOHBXNMqWR_mp5g4PLTgjhD7iZFUffGZFIdw,6882
|
| 244 |
+
setuptools/_distutils/tests/test_build_scripts.py,sha256=cD-FRy-oX55sXRX5Ez5xQCaeHrWajyKc4Xuwv2fe48w,2880
|
| 245 |
+
setuptools/_distutils/tests/test_check.py,sha256=hHSV07qf7YoSxGsTbbsUQ9tssZz5RRNdbrY1s2SwaFI,6226
|
| 246 |
+
setuptools/_distutils/tests/test_clean.py,sha256=hPH6jfIpGFUrvWbF1txkiNVSNaAxt2wq5XjV499zO4E,1240
|
| 247 |
+
setuptools/_distutils/tests/test_cmd.py,sha256=bgRB79mitoOKR1OiyZHnCogvGxt3pWkxeTqIC04lQWQ,3254
|
| 248 |
+
setuptools/_distutils/tests/test_config_cmd.py,sha256=Zs6WX0IfxDvmuC19XzuVNnYCnTr9Y-hl73TAmDSBN4Y,2664
|
| 249 |
+
setuptools/_distutils/tests/test_core.py,sha256=L7XKVAxa-MGoAZeANopnuK9fRKneYhkSQpgw8XQvcF8,3829
|
| 250 |
+
setuptools/_distutils/tests/test_dir_util.py,sha256=E84lC-k4riVUwURyWaQ0Jqx2ui2-io-0RuJa3M7qkJs,4500
|
| 251 |
+
setuptools/_distutils/tests/test_dist.py,sha256=a6wlc5fQJd5qQ6HOndzcupNhjTxvj6-_JLtpuYvaP1M,18793
|
| 252 |
+
setuptools/_distutils/tests/test_extension.py,sha256=-YejLgZCuycFrOBd64pVH0JvwMc9NwhzHvQxvvjXHqk,3670
|
| 253 |
+
setuptools/_distutils/tests/test_file_util.py,sha256=livjnl3FkilQlrB2rFdFQq9nvjEVZHynNya0bfzv_b4,3522
|
| 254 |
+
setuptools/_distutils/tests/test_filelist.py,sha256=rJwkqCUfkGDgWlD22TozsT8ycbupMHB8DXqThzwT1T4,10766
|
| 255 |
+
setuptools/_distutils/tests/test_install.py,sha256=TfCB0ykhIxydIC2Q4SuTAZzSHvteMHgrBL9whoSgK9Q,8618
|
| 256 |
+
setuptools/_distutils/tests/test_install_data.py,sha256=vKq3K97k0hBAnOg38nmwEdf7cEDVr9rTVyCeJolgb4A,2464
|
| 257 |
+
setuptools/_distutils/tests/test_install_headers.py,sha256=PVAYpo_tYl980Qf64DPOmmSvyefIHdU06f7VsJeZykE,936
|
| 258 |
+
setuptools/_distutils/tests/test_install_lib.py,sha256=qri6Rl-maNTQrNDV8DbeXNl0hjsfRIKiI4rfZLrmWBI,3612
|
| 259 |
+
setuptools/_distutils/tests/test_install_scripts.py,sha256=KE3v0cDkFW-90IOID-OmZZGM2mhy-ZkEuuW7UXS2SHw,1600
|
| 260 |
+
setuptools/_distutils/tests/test_log.py,sha256=isFtOufloCyEdZaQOV7cVUr46GwtdVMj43mGBB5XH7k,323
|
| 261 |
+
setuptools/_distutils/tests/test_modified.py,sha256=h1--bOWmtJo1bpVV6uRhdnS9br71CBiNDM1MDwSGpug,4221
|
| 262 |
+
setuptools/_distutils/tests/test_sdist.py,sha256=cfzUhlCA418-1vH9ta3IBs26c_jUBbkJoFOK5GnAyNk,15062
|
| 263 |
+
setuptools/_distutils/tests/test_spawn.py,sha256=eS8w9D7bTxyFLSyRahJWeuh8Kc1F8RWWiY_dSG5B5Bc,4803
|
| 264 |
+
setuptools/_distutils/tests/test_sysconfig.py,sha256=lxM8LsUi1TomjDV4HoYK8u5nUoBkeNL60Uq8PY1DcwU,11986
|
| 265 |
+
setuptools/_distutils/tests/test_text_file.py,sha256=WQWSB5AfdBDZaMA8BFgipJPnsJb_2SKMfL90fSkRVtw,3460
|
| 266 |
+
setuptools/_distutils/tests/test_util.py,sha256=H9zlZ4z4Vh4TfjNYDBsxP7wguQLpxCfJYyOcm1yZU3c,7988
|
| 267 |
+
setuptools/_distutils/tests/test_version.py,sha256=ahfg_mP8wRy1sgwY-_Px5hrjgf6_upTIpnCgpR4yWRk,2750
|
| 268 |
+
setuptools/_distutils/tests/test_versionpredicate.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 269 |
+
setuptools/_distutils/tests/unix_compat.py,sha256=z-op6C2iVdX1aq5BIBR7cqOxijKE97alNwJqHNdLpoI,386
|
| 270 |
+
setuptools/_distutils/text_file.py,sha256=z4dkOJBr9Bo2LG0TNqm8sD63LEEaKSSP0J0bWBrFG3c,12101
|
| 271 |
+
setuptools/_distutils/unixccompiler.py,sha256=1bXJWH4fiu_A2WfriHzf88xjllQTXnnjUkZdRKs9cWU,212
|
| 272 |
+
setuptools/_distutils/util.py,sha256=Njfnqk60zMdkiAjRnGcTWX3t49-obHapOlbNvyIl02I,18094
|
| 273 |
+
setuptools/_distutils/version.py,sha256=vImT5-ECXkQ21oKL0XYFiTqK6NyM09cpzBNoA_34CQU,12619
|
| 274 |
+
setuptools/_distutils/versionpredicate.py,sha256=qBWQ6wTj12ODytoTmIydefIY2jb4uY1sdbgbuLn-IJM,5205
|
| 275 |
+
setuptools/_distutils/zosccompiler.py,sha256=svdiXZ2kdcwKrJKfhUhib03y8gz7aGZKukXH3I7YkBc,58
|
| 276 |
+
setuptools/_entry_points.py,sha256=Y3QUE9JKFW_YyquDnpffNWSs6f3jKEt1e-dnx--9-Kw,2310
|
| 277 |
+
setuptools/_imp.py,sha256=YY1EjZEN-0zYci1cxO10B_adAEOr7i8eK8JoCc9Ierc,2435
|
| 278 |
+
setuptools/_importlib.py,sha256=aKIjcK0HKXNz2D-XTrxaixGn_juTkONwmu3dcheMOF0,223
|
| 279 |
+
setuptools/_itertools.py,sha256=jWRfsIrpC7myooz3hDURj9GtvpswZeKXg2HakmEhNjo,657
|
| 280 |
+
setuptools/_normalization.py,sha256=kAmGfrwjF5djydEfLLyKgjkXCbL_0_ZxUPO-DlLlmIY,5824
|
| 281 |
+
setuptools/_path.py,sha256=cPv41v03HD7uEYqCIo-E_cGRfpPVr4lywBCiK-HSrCg,2685
|
| 282 |
+
setuptools/_reqs.py,sha256=QI3C9uOBSNRccu208qPnixHx51nxCry7_nPTIJaSYxM,1438
|
| 283 |
+
setuptools/_shutil.py,sha256=cAOllcoyMTXs5JLoybQi29yI5gABk82hepJyOBv2bMw,1496
|
| 284 |
+
setuptools/_static.py,sha256=GTR79gESF1_JaK4trLkpDrEuCeEtPlwQW0MRv7VNQX4,4855
|
| 285 |
+
setuptools/_vendor/__pycache__/typing_extensions.cpython-311.pyc,,
|
| 286 |
+
setuptools/_vendor/autocommand-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 287 |
+
setuptools/_vendor/autocommand-2.2.2.dist-info/LICENSE,sha256=reeNBJgtaZctREqOFKlPh6IzTdOFXMgDSOqOJAqg3y0,7634
|
| 288 |
+
setuptools/_vendor/autocommand-2.2.2.dist-info/METADATA,sha256=OADZuR3O6iBlpu1ieTgzYul6w4uOVrk0P0BO5TGGAJk,15006
|
| 289 |
+
setuptools/_vendor/autocommand-2.2.2.dist-info/RECORD,sha256=giu6ZrQVJvpUcYa4AiH4XaUNZSvuVJPb_l0UCFES8MM,1308
|
| 290 |
+
setuptools/_vendor/autocommand-2.2.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
| 291 |
+
setuptools/_vendor/autocommand-2.2.2.dist-info/top_level.txt,sha256=AzfhgKKS8EdAwWUTSF8mgeVQbXOY9kokHB6kSqwwqu0,12
|
| 292 |
+
setuptools/_vendor/autocommand/__init__.py,sha256=zko5Rnvolvb-UXjCx_2ArPTGBWwUK5QY4LIQIKYR7As,1037
|
| 293 |
+
setuptools/_vendor/autocommand/__pycache__/__init__.cpython-311.pyc,,
|
| 294 |
+
setuptools/_vendor/autocommand/__pycache__/autoasync.cpython-311.pyc,,
|
| 295 |
+
setuptools/_vendor/autocommand/__pycache__/autocommand.cpython-311.pyc,,
|
| 296 |
+
setuptools/_vendor/autocommand/__pycache__/automain.cpython-311.pyc,,
|
| 297 |
+
setuptools/_vendor/autocommand/__pycache__/autoparse.cpython-311.pyc,,
|
| 298 |
+
setuptools/_vendor/autocommand/__pycache__/errors.cpython-311.pyc,,
|
| 299 |
+
setuptools/_vendor/autocommand/autoasync.py,sha256=AMdyrxNS4pqWJfP_xuoOcImOHWD-qT7x06wmKN1Vp-U,5680
|
| 300 |
+
setuptools/_vendor/autocommand/autocommand.py,sha256=hmkEmQ72HtL55gnURVjDOnsfYlGd5lLXbvT4KG496Qw,2505
|
| 301 |
+
setuptools/_vendor/autocommand/automain.py,sha256=A2b8i754Mxc_DjU9WFr6vqYDWlhz0cn8miu8d8EsxV8,2076
|
| 302 |
+
setuptools/_vendor/autocommand/autoparse.py,sha256=WVWmZJPcbzUKXP40raQw_0HD8qPJ2V9VG1eFFmmnFxw,11642
|
| 303 |
+
setuptools/_vendor/autocommand/errors.py,sha256=7aa3roh9Herd6nIKpQHNWEslWE8oq7GiHYVUuRqORnA,886
|
| 304 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 305 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 306 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/METADATA,sha256=ghXFTq132dxaEIolxr3HK1mZqm9iyUmaRANZQSr6WlE,2020
|
| 307 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/RECORD,sha256=JYofHISeEXUGmlWl1s41ev3QTjTNXeJwk-Ss7HqdLOE,1360
|
| 308 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 309 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
| 310 |
+
setuptools/_vendor/backports.tarfile-1.2.0.dist-info/top_level.txt,sha256=cGjaLMOoBR1FK0ApojtzWVmViTtJ7JGIK_HwXiEsvtU,10
|
| 311 |
+
setuptools/_vendor/backports/__init__.py,sha256=iOEMwnlORWezdO8-2vxBIPSR37D7JGjluZ8f55vzxls,81
|
| 312 |
+
setuptools/_vendor/backports/__pycache__/__init__.cpython-311.pyc,,
|
| 313 |
+
setuptools/_vendor/backports/tarfile/__init__.py,sha256=Pwf2qUIfB0SolJPCKcx3vz3UEu_aids4g4sAfxy94qg,108491
|
| 314 |
+
setuptools/_vendor/backports/tarfile/__main__.py,sha256=Yw2oGT1afrz2eBskzdPYL8ReB_3liApmhFkN2EbDmc4,59
|
| 315 |
+
setuptools/_vendor/backports/tarfile/__pycache__/__init__.cpython-311.pyc,,
|
| 316 |
+
setuptools/_vendor/backports/tarfile/__pycache__/__main__.cpython-311.pyc,,
|
| 317 |
+
setuptools/_vendor/backports/tarfile/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 318 |
+
setuptools/_vendor/backports/tarfile/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 319 |
+
setuptools/_vendor/backports/tarfile/compat/__pycache__/py38.cpython-311.pyc,,
|
| 320 |
+
setuptools/_vendor/backports/tarfile/compat/py38.py,sha256=iYkyt_gvWjLzGUTJD9TuTfMMjOk-ersXZmRlvQYN2qE,568
|
| 321 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 322 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
| 323 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/METADATA,sha256=anuQ7_7h4J1bSEzfcjIBakPi2cyVQ7y7jklLHsBeH1k,4648
|
| 324 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/RECORD,sha256=DY08buueu-hsrH1ghhVSQzwynanqUSSLYdAr4uXmQDA,2518
|
| 325 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 326 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
|
| 327 |
+
setuptools/_vendor/importlib_metadata-8.0.0.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
|
| 328 |
+
setuptools/_vendor/importlib_metadata/__init__.py,sha256=tZNB-23h8Bixi9uCrQqj9Yf0aeC--Josdy3IZRIQeB0,33798
|
| 329 |
+
setuptools/_vendor/importlib_metadata/__pycache__/__init__.cpython-311.pyc,,
|
| 330 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_adapters.cpython-311.pyc,,
|
| 331 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_collections.cpython-311.pyc,,
|
| 332 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_compat.cpython-311.pyc,,
|
| 333 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_functools.cpython-311.pyc,,
|
| 334 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_itertools.cpython-311.pyc,,
|
| 335 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_meta.cpython-311.pyc,,
|
| 336 |
+
setuptools/_vendor/importlib_metadata/__pycache__/_text.cpython-311.pyc,,
|
| 337 |
+
setuptools/_vendor/importlib_metadata/__pycache__/diagnose.cpython-311.pyc,,
|
| 338 |
+
setuptools/_vendor/importlib_metadata/_adapters.py,sha256=rIhWTwBvYA1bV7i-5FfVX38qEXDTXFeS5cb5xJtP3ks,2317
|
| 339 |
+
setuptools/_vendor/importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
|
| 340 |
+
setuptools/_vendor/importlib_metadata/_compat.py,sha256=73QKrN9KNoaZzhbX5yPCCZa-FaALwXe8TPlDR72JgBU,1314
|
| 341 |
+
setuptools/_vendor/importlib_metadata/_functools.py,sha256=PsY2-4rrKX4RVeRC1oGp1lB1pmC9eKN88_f-bD9uOoA,2895
|
| 342 |
+
setuptools/_vendor/importlib_metadata/_itertools.py,sha256=cvr_2v8BRbxcIl5x5ldfqdHjhI8Yi8s8yk50G_nm6jQ,2068
|
| 343 |
+
setuptools/_vendor/importlib_metadata/_meta.py,sha256=nxZ7C8GVlcBFAKWyVOn_dn7ot_twBcbm1NmvjIetBHI,1801
|
| 344 |
+
setuptools/_vendor/importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
|
| 345 |
+
setuptools/_vendor/importlib_metadata/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 346 |
+
setuptools/_vendor/importlib_metadata/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 347 |
+
setuptools/_vendor/importlib_metadata/compat/__pycache__/py311.cpython-311.pyc,,
|
| 348 |
+
setuptools/_vendor/importlib_metadata/compat/__pycache__/py39.cpython-311.pyc,,
|
| 349 |
+
setuptools/_vendor/importlib_metadata/compat/py311.py,sha256=uqm-K-uohyj1042TH4a9Er_I5o7667DvulcD-gC_fSA,608
|
| 350 |
+
setuptools/_vendor/importlib_metadata/compat/py39.py,sha256=cPkMv6-0ilK-0Jw_Tkn0xYbOKJZc4WJKQHow0c2T44w,1102
|
| 351 |
+
setuptools/_vendor/importlib_metadata/diagnose.py,sha256=nkSRMiowlmkhLYhKhvCg9glmt_11Cox-EmLzEbqYTa8,379
|
| 352 |
+
setuptools/_vendor/importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 353 |
+
setuptools/_vendor/inflect-7.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 354 |
+
setuptools/_vendor/inflect-7.3.1.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 355 |
+
setuptools/_vendor/inflect-7.3.1.dist-info/METADATA,sha256=ZgMNY0WAZRs-U8wZiV2SMfjSKqBrMngXyDMs_CAwMwg,21079
|
| 356 |
+
setuptools/_vendor/inflect-7.3.1.dist-info/RECORD,sha256=XXg0rBuiYSxoAQUP3lenuYsPNqz4jDwtTzdv2JEbMJE,943
|
| 357 |
+
setuptools/_vendor/inflect-7.3.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
| 358 |
+
setuptools/_vendor/inflect-7.3.1.dist-info/top_level.txt,sha256=m52ujdp10CqT6jh1XQxZT6kEntcnv-7Tl7UiGNTzWZA,8
|
| 359 |
+
setuptools/_vendor/inflect/__init__.py,sha256=Jxy1HJXZiZ85kHeLAhkmvz6EMTdFqBe-duvt34R6IOc,103796
|
| 360 |
+
setuptools/_vendor/inflect/__pycache__/__init__.cpython-311.pyc,,
|
| 361 |
+
setuptools/_vendor/inflect/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 362 |
+
setuptools/_vendor/inflect/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 363 |
+
setuptools/_vendor/inflect/compat/__pycache__/py38.cpython-311.pyc,,
|
| 364 |
+
setuptools/_vendor/inflect/compat/py38.py,sha256=oObVfVnWX9_OpnOuEJn1mFbJxVhwyR5epbiTNXDDaso,160
|
| 365 |
+
setuptools/_vendor/inflect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 366 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 367 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 368 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/METADATA,sha256=IMUaliNsA5X1Ox9MXUWOagch5R4Wwb_3M7erp29dBtg,3933
|
| 369 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/RECORD,sha256=HptivXDkpfom6VlMu4CGD_7KPev-6Hc9rvp3TNJZygY,873
|
| 370 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 371 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
| 372 |
+
setuptools/_vendor/jaraco.collections-5.1.0.dist-info/top_level.txt,sha256=0JnN3LfXH4LIRfXL-QFOGCJzQWZO3ELx4R1d_louoQM,7
|
| 373 |
+
setuptools/_vendor/jaraco.context-5.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 374 |
+
setuptools/_vendor/jaraco.context-5.3.0.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 375 |
+
setuptools/_vendor/jaraco.context-5.3.0.dist-info/METADATA,sha256=xDtguJej0tN9iEXCUvxEJh2a7xceIRVBEakBLSr__tY,4020
|
| 376 |
+
setuptools/_vendor/jaraco.context-5.3.0.dist-info/RECORD,sha256=VRl7iKeEQyl7stgnp1uq50CzOJYlHYcoNdS0x17C9X4,641
|
| 377 |
+
setuptools/_vendor/jaraco.context-5.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
| 378 |
+
setuptools/_vendor/jaraco.context-5.3.0.dist-info/top_level.txt,sha256=0JnN3LfXH4LIRfXL-QFOGCJzQWZO3ELx4R1d_louoQM,7
|
| 379 |
+
setuptools/_vendor/jaraco.functools-4.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 380 |
+
setuptools/_vendor/jaraco.functools-4.0.1.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 381 |
+
setuptools/_vendor/jaraco.functools-4.0.1.dist-info/METADATA,sha256=i4aUaQDX-jjdEQK5wevhegyx8JyLfin2HyvaSk3FHso,2891
|
| 382 |
+
setuptools/_vendor/jaraco.functools-4.0.1.dist-info/RECORD,sha256=YyqnwE98S8wBwCevW5vHb-iVj0oYEDW5V6O9MBS6JIs,843
|
| 383 |
+
setuptools/_vendor/jaraco.functools-4.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
| 384 |
+
setuptools/_vendor/jaraco.functools-4.0.1.dist-info/top_level.txt,sha256=0JnN3LfXH4LIRfXL-QFOGCJzQWZO3ELx4R1d_louoQM,7
|
| 385 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 386 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 387 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/METADATA,sha256=AzWdm6ViMfDOPoQMfLWn2zgBQSGJScyqeN29TcuWXVI,3658
|
| 388 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/RECORD,sha256=gW2UV0HcokYJk4jKPu10_AZnrLqjb3C1WbJJTDl5sfY,1500
|
| 389 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 390 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
| 391 |
+
setuptools/_vendor/jaraco.text-3.12.1.dist-info/top_level.txt,sha256=0JnN3LfXH4LIRfXL-QFOGCJzQWZO3ELx4R1d_louoQM,7
|
| 392 |
+
setuptools/_vendor/jaraco/__pycache__/context.cpython-311.pyc,,
|
| 393 |
+
setuptools/_vendor/jaraco/collections/__init__.py,sha256=Pc1-SqjWm81ad1P0-GttpkwO_LWlnaY6gUq8gcKh2v0,26640
|
| 394 |
+
setuptools/_vendor/jaraco/collections/__pycache__/__init__.cpython-311.pyc,,
|
| 395 |
+
setuptools/_vendor/jaraco/collections/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 396 |
+
setuptools/_vendor/jaraco/context.py,sha256=REoLIxDkO5MfEYowt_WoupNCRoxBS5v7YX2PbW8lIcs,9552
|
| 397 |
+
setuptools/_vendor/jaraco/functools/__init__.py,sha256=hEAJaS2uSZRuF_JY4CxCHIYh79ZpxaPp9OiHyr9EJ1w,16642
|
| 398 |
+
setuptools/_vendor/jaraco/functools/__init__.pyi,sha256=gk3dsgHzo5F_U74HzAvpNivFAPCkPJ1b2-yCd62dfnw,3878
|
| 399 |
+
setuptools/_vendor/jaraco/functools/__pycache__/__init__.cpython-311.pyc,,
|
| 400 |
+
setuptools/_vendor/jaraco/functools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 401 |
+
setuptools/_vendor/jaraco/text/Lorem ipsum.txt,sha256=N_7c_79zxOufBY9HZ3yzMgOkNv-TkOTTio4BydrSjgs,1335
|
| 402 |
+
setuptools/_vendor/jaraco/text/__init__.py,sha256=Y2YUqXR_orUoDaY4SkPRe6ZZhb5HUHB_Ah9RCNsVyho,16250
|
| 403 |
+
setuptools/_vendor/jaraco/text/__pycache__/__init__.cpython-311.pyc,,
|
| 404 |
+
setuptools/_vendor/jaraco/text/__pycache__/layouts.cpython-311.pyc,,
|
| 405 |
+
setuptools/_vendor/jaraco/text/__pycache__/show-newlines.cpython-311.pyc,,
|
| 406 |
+
setuptools/_vendor/jaraco/text/__pycache__/strip-prefix.cpython-311.pyc,,
|
| 407 |
+
setuptools/_vendor/jaraco/text/__pycache__/to-dvorak.cpython-311.pyc,,
|
| 408 |
+
setuptools/_vendor/jaraco/text/__pycache__/to-qwerty.cpython-311.pyc,,
|
| 409 |
+
setuptools/_vendor/jaraco/text/layouts.py,sha256=HTC8aSTLZ7uXipyOXapRMC158juecjK6RVwitfmZ9_w,643
|
| 410 |
+
setuptools/_vendor/jaraco/text/show-newlines.py,sha256=WGQa65e8lyhb92LUOLqVn6KaCtoeVgVws6WtSRmLk6w,904
|
| 411 |
+
setuptools/_vendor/jaraco/text/strip-prefix.py,sha256=NfVXV8JVNo6nqcuYASfMV7_y4Eo8zMQqlCOGvAnRIVw,412
|
| 412 |
+
setuptools/_vendor/jaraco/text/to-dvorak.py,sha256=1SNcbSsvISpXXg-LnybIHHY-RUFOQr36zcHkY1pWFqw,119
|
| 413 |
+
setuptools/_vendor/jaraco/text/to-qwerty.py,sha256=s4UMQUnPwFn_dB5uZC27BurHOQcYondBfzIpVL5pEzw,119
|
| 414 |
+
setuptools/_vendor/more_itertools-10.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 415 |
+
setuptools/_vendor/more_itertools-10.3.0.dist-info/LICENSE,sha256=CfHIyelBrz5YTVlkHqm4fYPAyw_QB-te85Gn4mQ8GkY,1053
|
| 416 |
+
setuptools/_vendor/more_itertools-10.3.0.dist-info/METADATA,sha256=BFO90O-fLNiVQMpj7oIS5ztzgJUUQZ3TA32P5HH3N-A,36293
|
| 417 |
+
setuptools/_vendor/more_itertools-10.3.0.dist-info/RECORD,sha256=d8jnPgGNwP1-ntbICwWkQEVF9kH7CFIgzkKzaLWao9M,1259
|
| 418 |
+
setuptools/_vendor/more_itertools-10.3.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 419 |
+
setuptools/_vendor/more_itertools-10.3.0.dist-info/WHEEL,sha256=rSgq_JpHF9fHR1lx53qwg_1-2LypZE_qmcuXbVUq948,81
|
| 420 |
+
setuptools/_vendor/more_itertools/__init__.py,sha256=dtAbGjTDmn_ghiU5YXfhyDy0phAlXVdt5klZA5fUa-Q,149
|
| 421 |
+
setuptools/_vendor/more_itertools/__init__.pyi,sha256=5B3eTzON1BBuOLob1vCflyEb2lSd6usXQQ-Cv-hXkeA,43
|
| 422 |
+
setuptools/_vendor/more_itertools/__pycache__/__init__.cpython-311.pyc,,
|
| 423 |
+
setuptools/_vendor/more_itertools/__pycache__/more.cpython-311.pyc,,
|
| 424 |
+
setuptools/_vendor/more_itertools/__pycache__/recipes.cpython-311.pyc,,
|
| 425 |
+
setuptools/_vendor/more_itertools/more.py,sha256=1E5kzFncRKTDw0cYv1yRXMgDdunstLQd1QStcnL6U90,148370
|
| 426 |
+
setuptools/_vendor/more_itertools/more.pyi,sha256=iXXeqt48Nxe8VGmIWpkVXuKpR2FYNuu2DU8nQLWCCu0,21484
|
| 427 |
+
setuptools/_vendor/more_itertools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 428 |
+
setuptools/_vendor/more_itertools/recipes.py,sha256=WedhhfhGVgr6zii8fIbGJVmRTw0ZKRiLKnYBDGJv4nY,28591
|
| 429 |
+
setuptools/_vendor/more_itertools/recipes.pyi,sha256=T_mdGpcFdfrP3JSWbwzYP9JyNV-Go-7RPfpxfftAWlA,4617
|
| 430 |
+
setuptools/_vendor/packaging-24.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 431 |
+
setuptools/_vendor/packaging-24.2.dist-info/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
| 432 |
+
setuptools/_vendor/packaging-24.2.dist-info/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
| 433 |
+
setuptools/_vendor/packaging-24.2.dist-info/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
| 434 |
+
setuptools/_vendor/packaging-24.2.dist-info/METADATA,sha256=ohH86s6k5mIfQxY2TS0LcSfADeOFa4BiCC-bxZV-pNs,3204
|
| 435 |
+
setuptools/_vendor/packaging-24.2.dist-info/RECORD,sha256=Y4DrXM0KY0ArfzhbAEa1LYFPwW3WEgEeL4iCqXe-A-M,2009
|
| 436 |
+
setuptools/_vendor/packaging-24.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 437 |
+
setuptools/_vendor/packaging-24.2.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
| 438 |
+
setuptools/_vendor/packaging/__init__.py,sha256=dk4Ta_vmdVJxYHDcfyhvQNw8V3PgSBomKNXqg-D2JDY,494
|
| 439 |
+
setuptools/_vendor/packaging/__pycache__/__init__.cpython-311.pyc,,
|
| 440 |
+
setuptools/_vendor/packaging/__pycache__/_elffile.cpython-311.pyc,,
|
| 441 |
+
setuptools/_vendor/packaging/__pycache__/_manylinux.cpython-311.pyc,,
|
| 442 |
+
setuptools/_vendor/packaging/__pycache__/_musllinux.cpython-311.pyc,,
|
| 443 |
+
setuptools/_vendor/packaging/__pycache__/_parser.cpython-311.pyc,,
|
| 444 |
+
setuptools/_vendor/packaging/__pycache__/_structures.cpython-311.pyc,,
|
| 445 |
+
setuptools/_vendor/packaging/__pycache__/_tokenizer.cpython-311.pyc,,
|
| 446 |
+
setuptools/_vendor/packaging/__pycache__/markers.cpython-311.pyc,,
|
| 447 |
+
setuptools/_vendor/packaging/__pycache__/metadata.cpython-311.pyc,,
|
| 448 |
+
setuptools/_vendor/packaging/__pycache__/requirements.cpython-311.pyc,,
|
| 449 |
+
setuptools/_vendor/packaging/__pycache__/specifiers.cpython-311.pyc,,
|
| 450 |
+
setuptools/_vendor/packaging/__pycache__/tags.cpython-311.pyc,,
|
| 451 |
+
setuptools/_vendor/packaging/__pycache__/utils.cpython-311.pyc,,
|
| 452 |
+
setuptools/_vendor/packaging/__pycache__/version.cpython-311.pyc,,
|
| 453 |
+
setuptools/_vendor/packaging/_elffile.py,sha256=cflAQAkE25tzhYmq_aCi72QfbT_tn891tPzfpbeHOwE,3306
|
| 454 |
+
setuptools/_vendor/packaging/_manylinux.py,sha256=vl5OCoz4kx80H5rwXKeXWjl9WNISGmr4ZgTpTP9lU9c,9612
|
| 455 |
+
setuptools/_vendor/packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
|
| 456 |
+
setuptools/_vendor/packaging/_parser.py,sha256=s_TvTvDNK0NrM2QB3VKThdWFM4Nc0P6JnkObkl3MjpM,10236
|
| 457 |
+
setuptools/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
|
| 458 |
+
setuptools/_vendor/packaging/_tokenizer.py,sha256=J6v5H7Jzvb-g81xp_2QACKwO7LxHQA6ikryMU7zXwN8,5273
|
| 459 |
+
setuptools/_vendor/packaging/licenses/__init__.py,sha256=1x5M1nEYjcgwEbLt0dXwz2ukjr18DiCzC0sraQqJ-Ww,5715
|
| 460 |
+
setuptools/_vendor/packaging/licenses/__pycache__/__init__.cpython-311.pyc,,
|
| 461 |
+
setuptools/_vendor/packaging/licenses/__pycache__/_spdx.cpython-311.pyc,,
|
| 462 |
+
setuptools/_vendor/packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398
|
| 463 |
+
setuptools/_vendor/packaging/markers.py,sha256=c89TNzB7ZdGYhkovm6PYmqGyHxXlYVaLW591PHUNKD8,10561
|
| 464 |
+
setuptools/_vendor/packaging/metadata.py,sha256=YJibM7GYe4re8-0a3OlXmGS-XDgTEoO4tlBt2q25Bng,34762
|
| 465 |
+
setuptools/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 466 |
+
setuptools/_vendor/packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
|
| 467 |
+
setuptools/_vendor/packaging/specifiers.py,sha256=GG1wPNMcL0fMJO68vF53wKMdwnfehDcaI-r9NpTfilA,40074
|
| 468 |
+
setuptools/_vendor/packaging/tags.py,sha256=CFqrJzAzc2XNGexerH__T-Y5Iwq7WbsYXsiLERLWxY0,21014
|
| 469 |
+
setuptools/_vendor/packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050
|
| 470 |
+
setuptools/_vendor/packaging/version.py,sha256=olfyuk_DPbflNkJ4wBWetXQ17c74x3DB501degUv7DY,16676
|
| 471 |
+
setuptools/_vendor/platformdirs-4.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 472 |
+
setuptools/_vendor/platformdirs-4.2.2.dist-info/METADATA,sha256=zmsie01G1MtXR0wgIv5XpVeTO7idr0WWvfmxKsKWuGk,11429
|
| 473 |
+
setuptools/_vendor/platformdirs-4.2.2.dist-info/RECORD,sha256=TCEddtQu1A78Os_Mhm2JEqcYr7yit-UYSUQjZtbpn-g,1642
|
| 474 |
+
setuptools/_vendor/platformdirs-4.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 475 |
+
setuptools/_vendor/platformdirs-4.2.2.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
| 476 |
+
setuptools/_vendor/platformdirs-4.2.2.dist-info/licenses/LICENSE,sha256=KeD9YukphQ6G6yjD_czwzv30-pSHkBHP-z0NS-1tTbY,1089
|
| 477 |
+
setuptools/_vendor/platformdirs/__init__.py,sha256=EMGE8qeHRR9CzDFr8kL3tA8hdZZniYjXBVZd0UGTWK0,22225
|
| 478 |
+
setuptools/_vendor/platformdirs/__main__.py,sha256=HnsUQHpiBaiTxwcmwVw-nFaPdVNZtQIdi1eWDtI-MzI,1493
|
| 479 |
+
setuptools/_vendor/platformdirs/__pycache__/__init__.cpython-311.pyc,,
|
| 480 |
+
setuptools/_vendor/platformdirs/__pycache__/__main__.cpython-311.pyc,,
|
| 481 |
+
setuptools/_vendor/platformdirs/__pycache__/android.cpython-311.pyc,,
|
| 482 |
+
setuptools/_vendor/platformdirs/__pycache__/api.cpython-311.pyc,,
|
| 483 |
+
setuptools/_vendor/platformdirs/__pycache__/macos.cpython-311.pyc,,
|
| 484 |
+
setuptools/_vendor/platformdirs/__pycache__/unix.cpython-311.pyc,,
|
| 485 |
+
setuptools/_vendor/platformdirs/__pycache__/version.cpython-311.pyc,,
|
| 486 |
+
setuptools/_vendor/platformdirs/__pycache__/windows.cpython-311.pyc,,
|
| 487 |
+
setuptools/_vendor/platformdirs/android.py,sha256=xZXY9Jd46WOsxT2U6-5HsNtDZ-IQqxcEUrBLl3hYk4o,9016
|
| 488 |
+
setuptools/_vendor/platformdirs/api.py,sha256=QBYdUac2eC521ek_y53uD1Dcq-lJX8IgSRVd4InC6uc,8996
|
| 489 |
+
setuptools/_vendor/platformdirs/macos.py,sha256=wftsbsvq6nZ0WORXSiCrZNkRHz_WKuktl0a6mC7MFkI,5580
|
| 490 |
+
setuptools/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 491 |
+
setuptools/_vendor/platformdirs/unix.py,sha256=Cci9Wqt35dAMsg6HT9nRGHSBW5obb0pR3AE1JJnsCXg,10643
|
| 492 |
+
setuptools/_vendor/platformdirs/version.py,sha256=r7F76tZRjgQKzrpx_I0_ZMQOMU-PS7eGnHD7zEK3KB0,411
|
| 493 |
+
setuptools/_vendor/platformdirs/windows.py,sha256=IFpiohUBwxPtCzlyKwNtxyW4Jk8haa6W8o59mfrDXVo,10125
|
| 494 |
+
setuptools/_vendor/tomli-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 495 |
+
setuptools/_vendor/tomli-2.0.1.dist-info/LICENSE,sha256=uAgWsNUwuKzLTCIReDeQmEpuO2GSLCte6S8zcqsnQv4,1072
|
| 496 |
+
setuptools/_vendor/tomli-2.0.1.dist-info/METADATA,sha256=zPDceKmPwJGLWtZykrHixL7WVXWmJGzZ1jyRT5lCoPI,8875
|
| 497 |
+
setuptools/_vendor/tomli-2.0.1.dist-info/RECORD,sha256=DLn5pFGh42WsVLTIhmLh2gy1SnLRalJY-wq_-dPhwCI,999
|
| 498 |
+
setuptools/_vendor/tomli-2.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 499 |
+
setuptools/_vendor/tomli-2.0.1.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
|
| 500 |
+
setuptools/_vendor/tomli/__init__.py,sha256=JhUwV66DB1g4Hvt1UQCVMdfCu-IgAV8FXmvDU9onxd4,396
|
| 501 |
+
setuptools/_vendor/tomli/__pycache__/__init__.cpython-311.pyc,,
|
| 502 |
+
setuptools/_vendor/tomli/__pycache__/_parser.cpython-311.pyc,,
|
| 503 |
+
setuptools/_vendor/tomli/__pycache__/_re.cpython-311.pyc,,
|
| 504 |
+
setuptools/_vendor/tomli/__pycache__/_types.cpython-311.pyc,,
|
| 505 |
+
setuptools/_vendor/tomli/_parser.py,sha256=g9-ENaALS-B8dokYpCuzUFalWlog7T-SIYMjLZSWrtM,22633
|
| 506 |
+
setuptools/_vendor/tomli/_re.py,sha256=dbjg5ChZT23Ka9z9DHOXfdtSpPwUfdgMXnj8NOoly-w,2943
|
| 507 |
+
setuptools/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254
|
| 508 |
+
setuptools/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
|
| 509 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 510 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/LICENSE,sha256=YWP3mH37ONa8MgzitwsvArhivEESZRbVUu8c1DJH51g,1130
|
| 511 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/METADATA,sha256=z2dcHAp0TwhYCFU5Deh8x31nazElgujUz9tbuP0pjSE,3717
|
| 512 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/RECORD,sha256=SKUZWVgkeDUidUKM7s1473fXmsna55bjmi6vJUAoJVI,2402
|
| 513 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
| 514 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/entry_points.txt,sha256=qp7NQ1aLtiSgMQqo6gWlfGpy0IIXzoMJmeQTLpzqFZQ,48
|
| 515 |
+
setuptools/_vendor/typeguard-4.3.0.dist-info/top_level.txt,sha256=4z28AhuDodwRS_c1J_l8H51t5QuwfTseskYzlxp6grs,10
|
| 516 |
+
setuptools/_vendor/typeguard/__init__.py,sha256=Onh4w38elPCjtlcU3JY9k3h70NjsxXIkAflmQn-Z0FY,2071
|
| 517 |
+
setuptools/_vendor/typeguard/__pycache__/__init__.cpython-311.pyc,,
|
| 518 |
+
setuptools/_vendor/typeguard/__pycache__/_checkers.cpython-311.pyc,,
|
| 519 |
+
setuptools/_vendor/typeguard/__pycache__/_config.cpython-311.pyc,,
|
| 520 |
+
setuptools/_vendor/typeguard/__pycache__/_decorators.cpython-311.pyc,,
|
| 521 |
+
setuptools/_vendor/typeguard/__pycache__/_exceptions.cpython-311.pyc,,
|
| 522 |
+
setuptools/_vendor/typeguard/__pycache__/_functions.cpython-311.pyc,,
|
| 523 |
+
setuptools/_vendor/typeguard/__pycache__/_importhook.cpython-311.pyc,,
|
| 524 |
+
setuptools/_vendor/typeguard/__pycache__/_memo.cpython-311.pyc,,
|
| 525 |
+
setuptools/_vendor/typeguard/__pycache__/_pytest_plugin.cpython-311.pyc,,
|
| 526 |
+
setuptools/_vendor/typeguard/__pycache__/_suppression.cpython-311.pyc,,
|
| 527 |
+
setuptools/_vendor/typeguard/__pycache__/_transformer.cpython-311.pyc,,
|
| 528 |
+
setuptools/_vendor/typeguard/__pycache__/_union_transformer.cpython-311.pyc,,
|
| 529 |
+
setuptools/_vendor/typeguard/__pycache__/_utils.cpython-311.pyc,,
|
| 530 |
+
setuptools/_vendor/typeguard/_checkers.py,sha256=JRrgKicdOEfIBoNEtegYCEIlhpad-a1u1Em7GCj0WCI,31360
|
| 531 |
+
setuptools/_vendor/typeguard/_config.py,sha256=nIz8QwDa-oFO3L9O8_6srzlmd99pSby2wOM4Wb7F_B0,2846
|
| 532 |
+
setuptools/_vendor/typeguard/_decorators.py,sha256=v6dsIeWvPhExGLP_wXF-RmDUyjZf_Ak28g7gBJ_v0-0,9033
|
| 533 |
+
setuptools/_vendor/typeguard/_exceptions.py,sha256=ZIPeiV-FBd5Emw2EaWd2Fvlsrwi4ocwT2fVGBIAtHcQ,1121
|
| 534 |
+
setuptools/_vendor/typeguard/_functions.py,sha256=ibgSAKa5ptIm1eR9ARG0BSozAFJPFNASZqhPVyQeqig,10393
|
| 535 |
+
setuptools/_vendor/typeguard/_importhook.py,sha256=ugjCDvFcdWMU7UugqlJG91IpVNpEIxtRr-99s0h1k7M,6389
|
| 536 |
+
setuptools/_vendor/typeguard/_memo.py,sha256=1juQV_vxnD2JYKbSrebiQuj4oKHz6n67v9pYA-CCISg,1303
|
| 537 |
+
setuptools/_vendor/typeguard/_pytest_plugin.py,sha256=-fcSqkv54rIfIF8pDavY5YQPkj4OX8GMt_lL7CQSD4I,4416
|
| 538 |
+
setuptools/_vendor/typeguard/_suppression.py,sha256=VQfzxcwIbu3if0f7VBkKM7hkYOA7tNFw9a7jMBsmMg4,2266
|
| 539 |
+
setuptools/_vendor/typeguard/_transformer.py,sha256=9Ha7_QhdwoUni_6hvdY-hZbuEergowHrNL2vzHIakFY,44937
|
| 540 |
+
setuptools/_vendor/typeguard/_union_transformer.py,sha256=v_42r7-6HuRX2SoFwnyJ-E5PlxXpVeUJPJR1-HU9qSo,1354
|
| 541 |
+
setuptools/_vendor/typeguard/_utils.py,sha256=5HhO1rPn5f1M6ymkVAEv7Xmlz1cX-j0OnTMlyHqqrR8,5270
|
| 542 |
+
setuptools/_vendor/typeguard/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 543 |
+
setuptools/_vendor/typing_extensions-4.12.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 544 |
+
setuptools/_vendor/typing_extensions-4.12.2.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
|
| 545 |
+
setuptools/_vendor/typing_extensions-4.12.2.dist-info/METADATA,sha256=BeUQIa8cnYbrjWx-N8TOznM9UGW5Gm2DicVpDtRA8W0,3018
|
| 546 |
+
setuptools/_vendor/typing_extensions-4.12.2.dist-info/RECORD,sha256=dxAALYGXHmMqpqL8M9xddKr118quIgQKZdPjFQOwXuk,571
|
| 547 |
+
setuptools/_vendor/typing_extensions-4.12.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
| 548 |
+
setuptools/_vendor/typing_extensions.py,sha256=gwekpyG9DVG3lxWKX4ni8u7nk3We5slG98mA9F3DJQw,134451
|
| 549 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 550 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/LICENSE.txt,sha256=MMI2GGeRCPPo6h0qZYx8pBe9_IkcmO8aifpP8MmChlQ,1107
|
| 551 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/METADATA,sha256=mKz84H7m7jsxJyzeIcTVORiTb0NPMV39KvOIYhGgmjA,2313
|
| 552 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/RECORD,sha256=1jnxrHyZPDcVvULyfGFhiba4Z5L9_RsXr9dxcNbhaYQ,4900
|
| 553 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 554 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
| 555 |
+
setuptools/_vendor/wheel-0.45.1.dist-info/entry_points.txt,sha256=rTY1BbkPHhkGMm4Q3F0pIzJBzW2kMxoG1oriffvGdA0,104
|
| 556 |
+
setuptools/_vendor/wheel/__init__.py,sha256=mrxMnvdXACur_LWegbUfh5g5ysWZrd63UJn890wvGNk,59
|
| 557 |
+
setuptools/_vendor/wheel/__main__.py,sha256=NkMUnuTCGcOkgY0IBLgBCVC_BGGcWORx2K8jYGS12UE,455
|
| 558 |
+
setuptools/_vendor/wheel/__pycache__/__init__.cpython-311.pyc,,
|
| 559 |
+
setuptools/_vendor/wheel/__pycache__/__main__.cpython-311.pyc,,
|
| 560 |
+
setuptools/_vendor/wheel/__pycache__/_bdist_wheel.cpython-311.pyc,,
|
| 561 |
+
setuptools/_vendor/wheel/__pycache__/_setuptools_logging.cpython-311.pyc,,
|
| 562 |
+
setuptools/_vendor/wheel/__pycache__/bdist_wheel.cpython-311.pyc,,
|
| 563 |
+
setuptools/_vendor/wheel/__pycache__/macosx_libfile.cpython-311.pyc,,
|
| 564 |
+
setuptools/_vendor/wheel/__pycache__/metadata.cpython-311.pyc,,
|
| 565 |
+
setuptools/_vendor/wheel/__pycache__/util.cpython-311.pyc,,
|
| 566 |
+
setuptools/_vendor/wheel/__pycache__/wheelfile.cpython-311.pyc,,
|
| 567 |
+
setuptools/_vendor/wheel/_bdist_wheel.py,sha256=UghCQjSH_pVfcZh6oRjzSw_TQhcf3anSx1OkiLSL82M,21694
|
| 568 |
+
setuptools/_vendor/wheel/_setuptools_logging.py,sha256=-5KC-lne0ilOUWIDfOkqapUWGMFZhuKYDIavIZiB5kM,781
|
| 569 |
+
setuptools/_vendor/wheel/bdist_wheel.py,sha256=tpf9WufiSO1RuEMg5oPhIfSG8DMziCZ_4muCKF69Cqo,1107
|
| 570 |
+
setuptools/_vendor/wheel/cli/__init__.py,sha256=Npq6_jKi03dhIcRnmbuFhwviVJxwO0tYEnEhWMv9cJo,4402
|
| 571 |
+
setuptools/_vendor/wheel/cli/__pycache__/__init__.cpython-311.pyc,,
|
| 572 |
+
setuptools/_vendor/wheel/cli/__pycache__/convert.cpython-311.pyc,,
|
| 573 |
+
setuptools/_vendor/wheel/cli/__pycache__/pack.cpython-311.pyc,,
|
| 574 |
+
setuptools/_vendor/wheel/cli/__pycache__/tags.cpython-311.pyc,,
|
| 575 |
+
setuptools/_vendor/wheel/cli/__pycache__/unpack.cpython-311.pyc,,
|
| 576 |
+
setuptools/_vendor/wheel/cli/convert.py,sha256=Bi0ntEXb9nTllCxWeTRQ4j-nPs3szWSEKipG_GgnMkQ,12634
|
| 577 |
+
setuptools/_vendor/wheel/cli/pack.py,sha256=CAFcHdBVulvsHYJlndKVO7KMI9JqBTZz5ii0PKxxCOs,3103
|
| 578 |
+
setuptools/_vendor/wheel/cli/tags.py,sha256=lHw-LaWrkS5Jy_qWcw-6pSjeNM6yAjDnqKI3E5JTTCU,4760
|
| 579 |
+
setuptools/_vendor/wheel/cli/unpack.py,sha256=Y_J7ynxPSoFFTT7H0fMgbBlVErwyDGcObgme5MBuz58,1021
|
| 580 |
+
setuptools/_vendor/wheel/macosx_libfile.py,sha256=k1x7CE3LPtOVGqj6NXQ1nTGYVPaeRrhVzUG_KPq3zDs,16572
|
| 581 |
+
setuptools/_vendor/wheel/metadata.py,sha256=JC4p7jlQZu2bUTAQ2fevkqLjg_X6gnNyRhLn6OUO1tc,6171
|
| 582 |
+
setuptools/_vendor/wheel/util.py,sha256=aL7aibHwYUgfc8WlolL5tXdkV4DatbJxZHb1kwHFJAU,423
|
| 583 |
+
setuptools/_vendor/wheel/vendored/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 584 |
+
setuptools/_vendor/wheel/vendored/__pycache__/__init__.cpython-311.pyc,,
|
| 585 |
+
setuptools/_vendor/wheel/vendored/packaging/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
| 586 |
+
setuptools/_vendor/wheel/vendored/packaging/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
| 587 |
+
setuptools/_vendor/wheel/vendored/packaging/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
| 588 |
+
setuptools/_vendor/wheel/vendored/packaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 589 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/__init__.cpython-311.pyc,,
|
| 590 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/_elffile.cpython-311.pyc,,
|
| 591 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/_manylinux.cpython-311.pyc,,
|
| 592 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/_musllinux.cpython-311.pyc,,
|
| 593 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/_parser.cpython-311.pyc,,
|
| 594 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/_structures.cpython-311.pyc,,
|
| 595 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/_tokenizer.cpython-311.pyc,,
|
| 596 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/markers.cpython-311.pyc,,
|
| 597 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/requirements.cpython-311.pyc,,
|
| 598 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/specifiers.cpython-311.pyc,,
|
| 599 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/tags.cpython-311.pyc,,
|
| 600 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/utils.cpython-311.pyc,,
|
| 601 |
+
setuptools/_vendor/wheel/vendored/packaging/__pycache__/version.cpython-311.pyc,,
|
| 602 |
+
setuptools/_vendor/wheel/vendored/packaging/_elffile.py,sha256=hbmK8OD6Z7fY6hwinHEUcD1by7czkGiNYu7ShnFEk2k,3266
|
| 603 |
+
setuptools/_vendor/wheel/vendored/packaging/_manylinux.py,sha256=P7sdR5_7XBY09LVYYPhHmydMJIIwPXWsh4olk74Uuj4,9588
|
| 604 |
+
setuptools/_vendor/wheel/vendored/packaging/_musllinux.py,sha256=z1s8To2hQ0vpn_d-O2i5qxGwEK8WmGlLt3d_26V7NeY,2674
|
| 605 |
+
setuptools/_vendor/wheel/vendored/packaging/_parser.py,sha256=4tT4emSl2qTaU7VTQE1Xa9o1jMPCsBezsYBxyNMUN-s,10347
|
| 606 |
+
setuptools/_vendor/wheel/vendored/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
|
| 607 |
+
setuptools/_vendor/wheel/vendored/packaging/_tokenizer.py,sha256=alCtbwXhOFAmFGZ6BQ-wCTSFoRAJ2z-ysIf7__MTJ_k,5292
|
| 608 |
+
setuptools/_vendor/wheel/vendored/packaging/markers.py,sha256=_TSPI1BhJYO7Bp9AzTmHQxIqHEVXaTjmDh9G-w8qzPA,8232
|
| 609 |
+
setuptools/_vendor/wheel/vendored/packaging/requirements.py,sha256=dgoBeVprPu2YE6Q8nGfwOPTjATHbRa_ZGLyXhFEln6Q,2933
|
| 610 |
+
setuptools/_vendor/wheel/vendored/packaging/specifiers.py,sha256=IWSt0SrLSP72heWhAC8UL0eGvas7XIQHjqiViVfmPKE,39778
|
| 611 |
+
setuptools/_vendor/wheel/vendored/packaging/tags.py,sha256=fedHXiOHkBxNZTXotXv8uXPmMFU9ae-TKBujgYHigcA,18950
|
| 612 |
+
setuptools/_vendor/wheel/vendored/packaging/utils.py,sha256=XgdmP3yx9-wQEFjO7OvMj9RjEf5JlR5HFFR69v7SQ9E,5268
|
| 613 |
+
setuptools/_vendor/wheel/vendored/packaging/version.py,sha256=PFJaYZDxBgyxkfYhH3SQw4qfE9ICCWrTmitvq14y3bs,16234
|
| 614 |
+
setuptools/_vendor/wheel/vendored/vendor.txt,sha256=Z2ENjB1i5prfez8CdM1Sdr3c6Zxv2rRRolMpLmBncAE,16
|
| 615 |
+
setuptools/_vendor/wheel/wheelfile.py,sha256=USCttNlJwafxt51YYFFKG7jnxz8dfhbyqAZL6jMTA9s,8411
|
| 616 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 617 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
| 618 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/METADATA,sha256=UIrk_kMIHGSwsKKChYizqMw0MMZpPRZ2ZiVpQAsN_bE,3575
|
| 619 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/RECORD,sha256=8xby4D_ZrefrvAsVRwaEjiu4_VaLkJNRCfDY484rm_4,1039
|
| 620 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 621 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
| 622 |
+
setuptools/_vendor/zipp-3.19.2.dist-info/top_level.txt,sha256=iAbdoSHfaGqBfVb2XuR9JqSQHCoOsOtG6y9C_LSpqFw,5
|
| 623 |
+
setuptools/_vendor/zipp/__init__.py,sha256=QuI1g00G4fRAcGt-HqbV0oWIkmSgedCGGYsHHYzNa8A,13412
|
| 624 |
+
setuptools/_vendor/zipp/__pycache__/__init__.cpython-311.pyc,,
|
| 625 |
+
setuptools/_vendor/zipp/__pycache__/glob.cpython-311.pyc,,
|
| 626 |
+
setuptools/_vendor/zipp/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 627 |
+
setuptools/_vendor/zipp/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 628 |
+
setuptools/_vendor/zipp/compat/__pycache__/py310.cpython-311.pyc,,
|
| 629 |
+
setuptools/_vendor/zipp/compat/py310.py,sha256=eZpkW0zRtunkhEh8jjX3gCGe22emoKCBJw72Zt4RkhA,219
|
| 630 |
+
setuptools/_vendor/zipp/glob.py,sha256=etWpnfEoRyfUvrUsi6sTiGmErvPwe6HzY6pT8jg_lUI,3082
|
| 631 |
+
setuptools/archive_util.py,sha256=Tl_64hSTtc4y8x7xa98rFVUbG24oArpjzLAYGYP2_sI,7356
|
| 632 |
+
setuptools/build_meta.py,sha256=rry-LowqvWFsxG6Qm5QWetHJGeETzRdiQ5-bs4bOkjo,20446
|
| 633 |
+
setuptools/cli-32.exe,sha256=MqzBvFQxFsviz_EMuGd3LfLyVP8mNMhwrvC0bEtpb9s,11776
|
| 634 |
+
setuptools/cli-64.exe,sha256=u7PeVwdinmpgoMI4zUd7KPB_AGaYL9qVP6b87DkHOko,14336
|
| 635 |
+
setuptools/cli-arm64.exe,sha256=uafQjaiA36yLz1SOuksG-1m28JsX0zFIoPZhgyiSbGE,13824
|
| 636 |
+
setuptools/cli.exe,sha256=MqzBvFQxFsviz_EMuGd3LfLyVP8mNMhwrvC0bEtpb9s,11776
|
| 637 |
+
setuptools/command/__init__.py,sha256=wdSrlNR0P6nCz9_oFtCAiAkeFJMsZa1jPcpXT53f0SM,803
|
| 638 |
+
setuptools/command/__pycache__/__init__.cpython-311.pyc,,
|
| 639 |
+
setuptools/command/__pycache__/_requirestxt.cpython-311.pyc,,
|
| 640 |
+
setuptools/command/__pycache__/alias.cpython-311.pyc,,
|
| 641 |
+
setuptools/command/__pycache__/bdist_egg.cpython-311.pyc,,
|
| 642 |
+
setuptools/command/__pycache__/bdist_rpm.cpython-311.pyc,,
|
| 643 |
+
setuptools/command/__pycache__/bdist_wheel.cpython-311.pyc,,
|
| 644 |
+
setuptools/command/__pycache__/build.cpython-311.pyc,,
|
| 645 |
+
setuptools/command/__pycache__/build_clib.cpython-311.pyc,,
|
| 646 |
+
setuptools/command/__pycache__/build_ext.cpython-311.pyc,,
|
| 647 |
+
setuptools/command/__pycache__/build_py.cpython-311.pyc,,
|
| 648 |
+
setuptools/command/__pycache__/develop.cpython-311.pyc,,
|
| 649 |
+
setuptools/command/__pycache__/dist_info.cpython-311.pyc,,
|
| 650 |
+
setuptools/command/__pycache__/easy_install.cpython-311.pyc,,
|
| 651 |
+
setuptools/command/__pycache__/editable_wheel.cpython-311.pyc,,
|
| 652 |
+
setuptools/command/__pycache__/egg_info.cpython-311.pyc,,
|
| 653 |
+
setuptools/command/__pycache__/install.cpython-311.pyc,,
|
| 654 |
+
setuptools/command/__pycache__/install_egg_info.cpython-311.pyc,,
|
| 655 |
+
setuptools/command/__pycache__/install_lib.cpython-311.pyc,,
|
| 656 |
+
setuptools/command/__pycache__/install_scripts.cpython-311.pyc,,
|
| 657 |
+
setuptools/command/__pycache__/rotate.cpython-311.pyc,,
|
| 658 |
+
setuptools/command/__pycache__/saveopts.cpython-311.pyc,,
|
| 659 |
+
setuptools/command/__pycache__/sdist.cpython-311.pyc,,
|
| 660 |
+
setuptools/command/__pycache__/setopt.cpython-311.pyc,,
|
| 661 |
+
setuptools/command/__pycache__/test.cpython-311.pyc,,
|
| 662 |
+
setuptools/command/_requirestxt.py,sha256=ItYMTJGh_i5TlQstX_nFopqEhkC4PJFadBL2Zd3V670,4228
|
| 663 |
+
setuptools/command/alias.py,sha256=rDdrMt32DS6qf3K7tjZZyHD_dMKrm77AXcAtx-nBQ0I,2380
|
| 664 |
+
setuptools/command/bdist_egg.py,sha256=3eDucQ4fdeYMsLO9PhBfY1JkcMLhZXgnAI_9FdFNsEE,16972
|
| 665 |
+
setuptools/command/bdist_rpm.py,sha256=LyqI49w48SKk0FmuHsE9MLzX1SuXjL7YMNbZMFZqFII,1435
|
| 666 |
+
setuptools/command/bdist_wheel.py,sha256=_LfGHB7CV_uyncqlOTSETEi2gjVCoPKuAXcyRFoq7Cs,22246
|
| 667 |
+
setuptools/command/build.py,sha256=eI7STMERGGZEpzk1tvJN8p9IOjAAXMcGLzljv2mwI3M,6052
|
| 668 |
+
setuptools/command/build_clib.py,sha256=AbgpPIF_3qL8fZr3JIebI-WHTMTBiMfrFkVQz8K40G4,4528
|
| 669 |
+
setuptools/command/build_ext.py,sha256=bUH4M0NizaJJrv10wK-ZD3uY0TxCSZlYQDhiwwzHslM,18377
|
| 670 |
+
setuptools/command/build_py.py,sha256=DCbjvB18kkL-xUK5rvlzm0C6twTeOxNhyvJDxxa7fII,15539
|
| 671 |
+
setuptools/command/develop.py,sha256=zX22119sI1G1gfJ1gNCE4hkg2zbLKx0uUwvNmC5bIu8,6886
|
| 672 |
+
setuptools/command/dist_info.py,sha256=HU752iLLmmYMHbsDBgz2ubRjkgJobugOp8H71LzzDys,3450
|
| 673 |
+
setuptools/command/easy_install.py,sha256=0Z4kFlE78Ae2AfHXYTxZFUa2t3qlNqXCtQu4J1Nx8iA,87870
|
| 674 |
+
setuptools/command/editable_wheel.py,sha256=3bBipRZA3E4poQ8LEWhMYEjHjCzqU_pIdO86C3t7oNY,35624
|
| 675 |
+
setuptools/command/egg_info.py,sha256=WWUozR3DZCrWsTQhHXOygMiEUcrjLWphET0-Zsocsm4,25982
|
| 676 |
+
setuptools/command/install.py,sha256=MmTGb8m1R8fJ0cc5FTWCF6uq-s1ZJmvpYm-N_CtqEaI,7046
|
| 677 |
+
setuptools/command/install_egg_info.py,sha256=3I9IPCH7D59Sh-6aVYz-h6wwyxq-wkxrKwKg3nDdJqs,2075
|
| 678 |
+
setuptools/command/install_lib.py,sha256=9n1_U83eHcERL_a_rv_LhHCkhXlLdqyZ4SdBow-9qcE,4319
|
| 679 |
+
setuptools/command/install_scripts.py,sha256=tVOCj3e8OTIrkoL_bGbT5pOksdxZfQblH_bdI4DtVV4,2637
|
| 680 |
+
setuptools/command/launcher manifest.xml,sha256=xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE,628
|
| 681 |
+
setuptools/command/rotate.py,sha256=XNd_BEEOWAJHW1FcLTMUWWl4QB6zAuk7b8VWQg3FHos,2187
|
| 682 |
+
setuptools/command/saveopts.py,sha256=Np0PVb7SD7oTbu9Z9sosS7D-CkkIkU7x4glu5Es1tjA,692
|
| 683 |
+
setuptools/command/sdist.py,sha256=JaQm2-ebXI2kvyrBjJKP8yNLPa5eMbMeis88CXBMYlk,7374
|
| 684 |
+
setuptools/command/setopt.py,sha256=xZF2RCc4ABvE9eHHAzF50-fkQg3au8fcRUVVGd58k3U,5100
|
| 685 |
+
setuptools/command/test.py,sha256=k7xcq7D7bEehgxarbw-dW3AtmGZORqz8HjKR6FGJ3jk,1343
|
| 686 |
+
setuptools/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 687 |
+
setuptools/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 688 |
+
setuptools/compat/__pycache__/py310.cpython-311.pyc,,
|
| 689 |
+
setuptools/compat/__pycache__/py311.cpython-311.pyc,,
|
| 690 |
+
setuptools/compat/__pycache__/py312.cpython-311.pyc,,
|
| 691 |
+
setuptools/compat/__pycache__/py39.cpython-311.pyc,,
|
| 692 |
+
setuptools/compat/py310.py,sha256=8sqwWczIcrkzeAbhaim4pKVd4tXZdcqmebgdvzji0rc,141
|
| 693 |
+
setuptools/compat/py311.py,sha256=e6tJAFwZEP82hmMBl10HYeSypelo_Ti2wTjKZVKLwOE,790
|
| 694 |
+
setuptools/compat/py312.py,sha256=vYKVtdrdOTsO_R90dJkEXsFwfMJFuIFJflhIgHrjJ-Y,366
|
| 695 |
+
setuptools/compat/py39.py,sha256=BJMtnkfcqyTfccqjYQxfoRtU2nTnWaEESBVkshTiXqY,493
|
| 696 |
+
setuptools/config/NOTICE,sha256=Ld3wiBgpejuJ1D2V_2WdjahXQRCMkTbfo6TYVsBiO9g,493
|
| 697 |
+
setuptools/config/__init__.py,sha256=aiPnL9BJn1O6MfmuNXyn8W2Lp8u9qizRVqwPiOdPIjY,1499
|
| 698 |
+
setuptools/config/__pycache__/__init__.cpython-311.pyc,,
|
| 699 |
+
setuptools/config/__pycache__/_apply_pyprojecttoml.cpython-311.pyc,,
|
| 700 |
+
setuptools/config/__pycache__/expand.cpython-311.pyc,,
|
| 701 |
+
setuptools/config/__pycache__/pyprojecttoml.cpython-311.pyc,,
|
| 702 |
+
setuptools/config/__pycache__/setupcfg.cpython-311.pyc,,
|
| 703 |
+
setuptools/config/_apply_pyprojecttoml.py,sha256=SUyTw7A2btZ1lBuWKN5o42-Diyv95eGTiYJ3rZOnGSc,19120
|
| 704 |
+
setuptools/config/_validate_pyproject/NOTICE,sha256=XTANv6ZDE4sBO3WsnK7uWR-VG4sO4kKIw0zNkmxHgMg,18737
|
| 705 |
+
setuptools/config/_validate_pyproject/__init__.py,sha256=dnp6T7ePP1R5z4OuC7Fd2dkFlIrtIfizUfvpGJP6nz0,1042
|
| 706 |
+
setuptools/config/_validate_pyproject/__pycache__/__init__.cpython-311.pyc,,
|
| 707 |
+
setuptools/config/_validate_pyproject/__pycache__/error_reporting.cpython-311.pyc,,
|
| 708 |
+
setuptools/config/_validate_pyproject/__pycache__/extra_validations.cpython-311.pyc,,
|
| 709 |
+
setuptools/config/_validate_pyproject/__pycache__/fastjsonschema_exceptions.cpython-311.pyc,,
|
| 710 |
+
setuptools/config/_validate_pyproject/__pycache__/fastjsonschema_validations.cpython-311.pyc,,
|
| 711 |
+
setuptools/config/_validate_pyproject/__pycache__/formats.cpython-311.pyc,,
|
| 712 |
+
setuptools/config/_validate_pyproject/error_reporting.py,sha256=meldD7nBQdolQhvG-43r1Ue-gU1n7ORAJR86vh3Rrvk,11813
|
| 713 |
+
setuptools/config/_validate_pyproject/extra_validations.py,sha256=-GUG5S--ijY8WfXbdXPoHl6ywGsyEF9dtDpenSoJPHg,2858
|
| 714 |
+
setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py,sha256=w749JgqKi8clBFcObdcbZVqsmF4oJ_QByhZ1SGbUFNw,1612
|
| 715 |
+
setuptools/config/_validate_pyproject/fastjsonschema_validations.py,sha256=FihD5ZcM6p77BPZ04CGqh3BEwVNoPMKJZJAyuJpkAU0,354682
|
| 716 |
+
setuptools/config/_validate_pyproject/formats.py,sha256=TETokJBK9hjl-cVg1olsojkJwLxfP7_chgJQNmzAB98,13564
|
| 717 |
+
setuptools/config/distutils.schema.json,sha256=Tcp32kRnhwORGw_9p6GEi08lj2h15tQRzOYBbzGmcBU,972
|
| 718 |
+
setuptools/config/expand.py,sha256=JNAktRCsyyRB-rQodbPnCucmLWqcYvzCDC8Ebn2Z7xM,16041
|
| 719 |
+
setuptools/config/pyprojecttoml.py,sha256=YMu5PdbJJI5azp6kR_boM1mflf5nqOA-InF4s6LnLgw,18320
|
| 720 |
+
setuptools/config/setupcfg.py,sha256=VZDkwE7DYv45SbadJD8CwKrDtiXvjgllL8PYSvoRCyg,26575
|
| 721 |
+
setuptools/config/setuptools.schema.json,sha256=dZBRuSEnZkatoVlt1kVwG8ocTeRdO7BD0xvOWKH54PY,16071
|
| 722 |
+
setuptools/depends.py,sha256=jKYfjmt_2ZQYVghb8L9bU7LJ6erHJ5ze-K_fKV1BMXk,5965
|
| 723 |
+
setuptools/discovery.py,sha256=-42c3XhwzkfodDKKP50C2YBzr11fncAgmUzBdBRb0-Q,21258
|
| 724 |
+
setuptools/dist.py,sha256=RZz7aj9RxqSYriqgFoZOu-7KIV82cmlaMFx9owZrApQ,44897
|
| 725 |
+
setuptools/errors.py,sha256=gY2x2PIaIgy01yRANRC-zcCwxDCqCScgJoCOZFe0yio,3024
|
| 726 |
+
setuptools/extension.py,sha256=KCnv9p3tgm0ZVqtgE451fyILsm4hCyvOiUtOu787D-4,6683
|
| 727 |
+
setuptools/glob.py,sha256=AC_B33DY8g-CHELxDsJrtwFrpiucSAZsakPFdSOQzhc,6062
|
| 728 |
+
setuptools/gui-32.exe,sha256=hdrh6V13hF8stZvKw9Sv50u-TJGpvMW_SnHNQxBNvnw,11776
|
| 729 |
+
setuptools/gui-64.exe,sha256=NHG2FA6txkEid9u-_j_vjDRaDxpZd2CGuAo2GMOoPjs,14336
|
| 730 |
+
setuptools/gui-arm64.exe,sha256=5pT0dDQFyLWSb_RX22_n8aEt7HwWqcOGR4TT9OB64Jc,13824
|
| 731 |
+
setuptools/gui.exe,sha256=hdrh6V13hF8stZvKw9Sv50u-TJGpvMW_SnHNQxBNvnw,11776
|
| 732 |
+
setuptools/installer.py,sha256=_4Wegx4r3L05sMo3-IlqFp-OuxnWyBqjyMZ7LWQXmh8,5110
|
| 733 |
+
setuptools/launch.py,sha256=IBb5lEv69CyuZ9ewIrmKlXh154kdLmP29LKfTMkximE,820
|
| 734 |
+
setuptools/logging.py,sha256=W16iHJ1HcCXYQ0RxyrEfJ83FT4175tCtoYg-E6uSpVI,1261
|
| 735 |
+
setuptools/modified.py,sha256=ZwbfBfCFP88ltvbv_dJDz-t1LsQjnM-JUpgZnnQZjjM,568
|
| 736 |
+
setuptools/monkey.py,sha256=FwMWl2n1v2bHbeqBy-o9g8yUNaAkYFbszCbXe9d5Za8,3717
|
| 737 |
+
setuptools/msvc.py,sha256=vmM0qL4rIzrtD9pia9ZEwtqZ4LbbrgL0dU0EANVYRm8,41631
|
| 738 |
+
setuptools/namespaces.py,sha256=2GGqYY1BNDEhMtBc1rHTv7klgmNVRdksJeW-L1f--ys,3171
|
| 739 |
+
setuptools/package_index.py,sha256=yHXj4Z_QaUeh8W6rwq08I5LxnyHrSkNf01uXHwdE8K0,39095
|
| 740 |
+
setuptools/sandbox.py,sha256=fMqtcOuipHO6RKPh1YB5o7d985dLKo76Whp3vrIei2E,14906
|
| 741 |
+
setuptools/script (dev).tmpl,sha256=RUzQzCQUaXtwdLtYHWYbIQmOaES5Brqq1FvUA_tu-5I,218
|
| 742 |
+
setuptools/script.tmpl,sha256=WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY,138
|
| 743 |
+
setuptools/tests/__init__.py,sha256=AnBfls2iJbTDQzmMKeLRt-9lxhaOHUVOZEgXv89Uwvs,335
|
| 744 |
+
setuptools/tests/__pycache__/__init__.cpython-311.pyc,,
|
| 745 |
+
setuptools/tests/__pycache__/contexts.cpython-311.pyc,,
|
| 746 |
+
setuptools/tests/__pycache__/environment.cpython-311.pyc,,
|
| 747 |
+
setuptools/tests/__pycache__/fixtures.cpython-311.pyc,,
|
| 748 |
+
setuptools/tests/__pycache__/mod_with_constant.cpython-311.pyc,,
|
| 749 |
+
setuptools/tests/__pycache__/namespaces.cpython-311.pyc,,
|
| 750 |
+
setuptools/tests/__pycache__/script-with-bom.cpython-311.pyc,,
|
| 751 |
+
setuptools/tests/__pycache__/server.cpython-311.pyc,,
|
| 752 |
+
setuptools/tests/__pycache__/test_archive_util.cpython-311.pyc,,
|
| 753 |
+
setuptools/tests/__pycache__/test_bdist_deprecations.cpython-311.pyc,,
|
| 754 |
+
setuptools/tests/__pycache__/test_bdist_egg.cpython-311.pyc,,
|
| 755 |
+
setuptools/tests/__pycache__/test_bdist_wheel.cpython-311.pyc,,
|
| 756 |
+
setuptools/tests/__pycache__/test_build.cpython-311.pyc,,
|
| 757 |
+
setuptools/tests/__pycache__/test_build_clib.cpython-311.pyc,,
|
| 758 |
+
setuptools/tests/__pycache__/test_build_ext.cpython-311.pyc,,
|
| 759 |
+
setuptools/tests/__pycache__/test_build_meta.cpython-311.pyc,,
|
| 760 |
+
setuptools/tests/__pycache__/test_build_py.cpython-311.pyc,,
|
| 761 |
+
setuptools/tests/__pycache__/test_config_discovery.cpython-311.pyc,,
|
| 762 |
+
setuptools/tests/__pycache__/test_core_metadata.cpython-311.pyc,,
|
| 763 |
+
setuptools/tests/__pycache__/test_depends.cpython-311.pyc,,
|
| 764 |
+
setuptools/tests/__pycache__/test_develop.cpython-311.pyc,,
|
| 765 |
+
setuptools/tests/__pycache__/test_dist.cpython-311.pyc,,
|
| 766 |
+
setuptools/tests/__pycache__/test_dist_info.cpython-311.pyc,,
|
| 767 |
+
setuptools/tests/__pycache__/test_distutils_adoption.cpython-311.pyc,,
|
| 768 |
+
setuptools/tests/__pycache__/test_easy_install.cpython-311.pyc,,
|
| 769 |
+
setuptools/tests/__pycache__/test_editable_install.cpython-311.pyc,,
|
| 770 |
+
setuptools/tests/__pycache__/test_egg_info.cpython-311.pyc,,
|
| 771 |
+
setuptools/tests/__pycache__/test_extern.cpython-311.pyc,,
|
| 772 |
+
setuptools/tests/__pycache__/test_find_packages.cpython-311.pyc,,
|
| 773 |
+
setuptools/tests/__pycache__/test_find_py_modules.cpython-311.pyc,,
|
| 774 |
+
setuptools/tests/__pycache__/test_glob.cpython-311.pyc,,
|
| 775 |
+
setuptools/tests/__pycache__/test_install_scripts.cpython-311.pyc,,
|
| 776 |
+
setuptools/tests/__pycache__/test_logging.cpython-311.pyc,,
|
| 777 |
+
setuptools/tests/__pycache__/test_manifest.cpython-311.pyc,,
|
| 778 |
+
setuptools/tests/__pycache__/test_namespaces.cpython-311.pyc,,
|
| 779 |
+
setuptools/tests/__pycache__/test_packageindex.cpython-311.pyc,,
|
| 780 |
+
setuptools/tests/__pycache__/test_sandbox.cpython-311.pyc,,
|
| 781 |
+
setuptools/tests/__pycache__/test_sdist.cpython-311.pyc,,
|
| 782 |
+
setuptools/tests/__pycache__/test_setopt.cpython-311.pyc,,
|
| 783 |
+
setuptools/tests/__pycache__/test_setuptools.cpython-311.pyc,,
|
| 784 |
+
setuptools/tests/__pycache__/test_shutil_wrapper.cpython-311.pyc,,
|
| 785 |
+
setuptools/tests/__pycache__/test_unicode_utils.cpython-311.pyc,,
|
| 786 |
+
setuptools/tests/__pycache__/test_virtualenv.cpython-311.pyc,,
|
| 787 |
+
setuptools/tests/__pycache__/test_warnings.cpython-311.pyc,,
|
| 788 |
+
setuptools/tests/__pycache__/test_wheel.cpython-311.pyc,,
|
| 789 |
+
setuptools/tests/__pycache__/test_windows_wrappers.cpython-311.pyc,,
|
| 790 |
+
setuptools/tests/__pycache__/text.cpython-311.pyc,,
|
| 791 |
+
setuptools/tests/__pycache__/textwrap.cpython-311.pyc,,
|
| 792 |
+
setuptools/tests/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 793 |
+
setuptools/tests/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 794 |
+
setuptools/tests/compat/__pycache__/py39.cpython-311.pyc,,
|
| 795 |
+
setuptools/tests/compat/py39.py,sha256=eUy7_F-6KRTOIKl-veshUu6I0EdTSdBZMh0EV0lZ1-g,135
|
| 796 |
+
setuptools/tests/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 797 |
+
setuptools/tests/config/__pycache__/__init__.cpython-311.pyc,,
|
| 798 |
+
setuptools/tests/config/__pycache__/test_apply_pyprojecttoml.cpython-311.pyc,,
|
| 799 |
+
setuptools/tests/config/__pycache__/test_expand.cpython-311.pyc,,
|
| 800 |
+
setuptools/tests/config/__pycache__/test_pyprojecttoml.cpython-311.pyc,,
|
| 801 |
+
setuptools/tests/config/__pycache__/test_pyprojecttoml_dynamic_deps.cpython-311.pyc,,
|
| 802 |
+
setuptools/tests/config/__pycache__/test_setupcfg.cpython-311.pyc,,
|
| 803 |
+
setuptools/tests/config/downloads/__init__.py,sha256=9ixnDEdyL_arKbUzfuiJftAj9bGxKz8M9alOFZMjx9Y,1827
|
| 804 |
+
setuptools/tests/config/downloads/__pycache__/__init__.cpython-311.pyc,,
|
| 805 |
+
setuptools/tests/config/downloads/__pycache__/preload.cpython-311.pyc,,
|
| 806 |
+
setuptools/tests/config/downloads/preload.py,sha256=sIGGZpY3cmMpMwiJYYYYHG2ifZJkvJgEotRFtiulV1I,450
|
| 807 |
+
setuptools/tests/config/setupcfg_examples.txt,sha256=cAbVvCbkFZuTUL6xRRzRgqyB0rLvJTfvw3D30glo2OE,1912
|
| 808 |
+
setuptools/tests/config/test_apply_pyprojecttoml.py,sha256=l6nE4d8WLU_eSWRic7VSoqeKv9Bi7CZGHcEuB2ehk2w,28807
|
| 809 |
+
setuptools/tests/config/test_expand.py,sha256=S0oT6JvgA_oujR4YS4RUuf5gmOt1CTQV66RQDzV8xd4,8933
|
| 810 |
+
setuptools/tests/config/test_pyprojecttoml.py,sha256=0LefSljUhA6MqtJ5AVzLhomqZcYiFKdu_1ckDeMT1LY,12406
|
| 811 |
+
setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py,sha256=9W73-yLhZJmvCiO4rTiQoBpZT5wNA90Xbd5n2HCshd4,3271
|
| 812 |
+
setuptools/tests/config/test_setupcfg.py,sha256=ZvN-O-2Dgon1adp6oM6il8JWdgT9y196fRvqESU5ELI,33427
|
| 813 |
+
setuptools/tests/contexts.py,sha256=TAdZKxmmodx1ExMVo01o4QpRjpIpo4X3IWKq_BnjxpU,3480
|
| 814 |
+
setuptools/tests/environment.py,sha256=95_UtTaRiuvwYC9eXKEHbn02kDtZysvZq3UZJmPUj1I,3102
|
| 815 |
+
setuptools/tests/fixtures.py,sha256=-V7iD6BeE2E0Rw6dVvTOCm36JG8ZTTnrXhN0GISlgrg,5197
|
| 816 |
+
setuptools/tests/indexes/test_links_priority/external.html,sha256=eL9euOuE93JKZdqlXxBOlHbKwIuNuIdq7GBRpsaPMcU,92
|
| 817 |
+
setuptools/tests/indexes/test_links_priority/simple/foobar/index.html,sha256=DD-TKr7UU4zAjHHz4VexYDNSAzR27levSh1c-k3ZdLE,174
|
| 818 |
+
setuptools/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 819 |
+
setuptools/tests/integration/__pycache__/__init__.cpython-311.pyc,,
|
| 820 |
+
setuptools/tests/integration/__pycache__/helpers.cpython-311.pyc,,
|
| 821 |
+
setuptools/tests/integration/__pycache__/test_pip_install_sdist.cpython-311.pyc,,
|
| 822 |
+
setuptools/tests/integration/helpers.py,sha256=3PHcS9SCA-fwVJmUP2ad5NQOttJAETI5Nnoc_xroO5k,2522
|
| 823 |
+
setuptools/tests/integration/test_pip_install_sdist.py,sha256=SFbvuYF_hDzt6OtsQ5GjFNnxmoJ_eElfvpYsiyyGJ-g,8256
|
| 824 |
+
setuptools/tests/mod_with_constant.py,sha256=X_Kj80M55w1tmQ4f7uZY91ZTALo4hKVT6EHxgYocUMQ,22
|
| 825 |
+
setuptools/tests/namespaces.py,sha256=HPcI3nR5MCFWXpaADIJ1fwKxymcQgBkuw87Ic5PUSAQ,2774
|
| 826 |
+
setuptools/tests/script-with-bom.py,sha256=hRRgIizEULGiG_ZTNoMY46HhKhxpWfy5FGcD6Qbh5fc,18
|
| 827 |
+
setuptools/tests/server.py,sha256=0FDZf0cSInCP5n1haK7AxHo3jD261RK7D3-RjP-F53k,2397
|
| 828 |
+
setuptools/tests/test_archive_util.py,sha256=buuKdY8XkW26Pe3IKAoBRGHG0MDumnuNoPg2WsAQzIg,845
|
| 829 |
+
setuptools/tests/test_bdist_deprecations.py,sha256=75Xq3gYn79LIIyusEltbHan0bEgAt2e_CaL7KLS8-KQ,775
|
| 830 |
+
setuptools/tests/test_bdist_egg.py,sha256=6PaYN1F3JDbIh1uK0urv7yJFcx98z5dn9SOJ8Mv91l8,1957
|
| 831 |
+
setuptools/tests/test_bdist_wheel.py,sha256=dZ9a7OT_UyRvLnoCi2KGEIbtzhEQjM3YutYMA6ZCezs,23083
|
| 832 |
+
setuptools/tests/test_build.py,sha256=wJgMz2hwHADcLFg-nXrwRVhus7hjmAeEGgrpIQwCGnA,798
|
| 833 |
+
setuptools/tests/test_build_clib.py,sha256=bX51XRAf4uO7IuHFpjePnoK8mE74N2gsoeEqF-ofgws,3123
|
| 834 |
+
setuptools/tests/test_build_ext.py,sha256=e4ZSxsYPB5zq1KSqGEuATZ0t0PJQzMhjjkKJ-hIjcgc,10099
|
| 835 |
+
setuptools/tests/test_build_meta.py,sha256=Iakpp9Micvhxi9_F2RPyY2NnCB1G90a38s4O5A3CuiE,34118
|
| 836 |
+
setuptools/tests/test_build_py.py,sha256=gobME_Cvzf6Ugxq70iWfXekb_xyyT61khwjFq8zkwfw,14186
|
| 837 |
+
setuptools/tests/test_config_discovery.py,sha256=FqV-lOtkqaI-ayzU2zocSdD5TaRAgCZnixNDilKA6FQ,22580
|
| 838 |
+
setuptools/tests/test_core_metadata.py,sha256=vbVJ5_Lsx_hsO_GdB6nQEXJRjA2ydx6_qSbr5LpheAA,20881
|
| 839 |
+
setuptools/tests/test_depends.py,sha256=yQBXoQbNQlJit6mbRVoz6Bb553f3sNrq02lZimNz5XY,424
|
| 840 |
+
setuptools/tests/test_develop.py,sha256=CLzXZ8-b5-VFTuau4P4yXEdLx1UdyTFcOfrV0qyUIdE,5142
|
| 841 |
+
setuptools/tests/test_dist.py,sha256=GFjyL2etAxvVM3q7NhFEGcXS5gyKj8VzbqcbKzpqbOk,8901
|
| 842 |
+
setuptools/tests/test_dist_info.py,sha256=5kBRj9tuBsVreBsY22H2feMO_JQZsSoOZMU_MJfUevY,7077
|
| 843 |
+
setuptools/tests/test_distutils_adoption.py,sha256=_eynrOfyEqXFEmjUJhzpe8GXPyTUPvNSObs4qAAmBy8,5987
|
| 844 |
+
setuptools/tests/test_easy_install.py,sha256=jx4lpFyee0G432cdnwBow3AkL4ibw-0QILwldwv5SCI,53534
|
| 845 |
+
setuptools/tests/test_editable_install.py,sha256=7eTEtpT0k7QeVyZg64eh3kZn-SjckuB9LcokOuV37DI,43383
|
| 846 |
+
setuptools/tests/test_egg_info.py,sha256=QCzoUOkFocmbkwS6XU7F8WNzKE8CGEMRxYBqKLgfUrc,44866
|
| 847 |
+
setuptools/tests/test_extern.py,sha256=rpKU6oCcksumLwf5TeKlDluFQ0TUfbPwTLQbpxcFrCU,296
|
| 848 |
+
setuptools/tests/test_find_packages.py,sha256=CTLAcTzWGWBLCcd2aAsUVkvO3ibrlqexFBdDKOWPoq8,7819
|
| 849 |
+
setuptools/tests/test_find_py_modules.py,sha256=zQjuhIG5TQN2SJPix9ARo4DL_w84Ln8QsHDUjjbrtAQ,2404
|
| 850 |
+
setuptools/tests/test_glob.py,sha256=P3JvpH-kXQ4BZ3zvRF-zKxOgwyWzwIaQIz0WHdxS0kk,887
|
| 851 |
+
setuptools/tests/test_install_scripts.py,sha256=scIrJ6a_ssKqg4vIBNaUjmAKHEYLUUZ9WKnPeKnE6gc,3433
|
| 852 |
+
setuptools/tests/test_logging.py,sha256=zlE5DlldukC7Jc54FNvDV_7ux3ErAkrfrN5CSsnNOUQ,2099
|
| 853 |
+
setuptools/tests/test_manifest.py,sha256=eMg65pIA52DizB6mpktSU-b8CjwaNCS5MSgL_V1LrFI,18562
|
| 854 |
+
setuptools/tests/test_namespaces.py,sha256=Y6utoe5PHHqL_DlgawqB9F8XpsUDPvvw1sQMenK04e0,4515
|
| 855 |
+
setuptools/tests/test_packageindex.py,sha256=qEjLHpSu2gAkegwEstzHQT-Om1uQIYjA8zeNzEX79uo,8775
|
| 856 |
+
setuptools/tests/test_sandbox.py,sha256=shUWE7fLTWe7Jzdfi6NheBWauG3oUukbUV46cArD0u0,4330
|
| 857 |
+
setuptools/tests/test_sdist.py,sha256=RYLvPa_nfyC1ZmoinzqMzJynTDG4RtPYC19_0LU6pvs,32872
|
| 858 |
+
setuptools/tests/test_setopt.py,sha256=3VxxM4ATfP-P4AGnDjoWCnHr5-i9CSEQTFYU1-FTnvI,1365
|
| 859 |
+
setuptools/tests/test_setuptools.py,sha256=_eIhqKf45-OtHqxRf20KndOZJlJdS0PuFLXBO3M-LN8,9008
|
| 860 |
+
setuptools/tests/test_shutil_wrapper.py,sha256=g15E11PtZxG-InB2BWNFyH-svObXx2XcMhgMLJPuFnc,641
|
| 861 |
+
setuptools/tests/test_unicode_utils.py,sha256=xWfEEl8jkQCt9othUTXJfFmdyATAFggJs2tTxjbumbw,316
|
| 862 |
+
setuptools/tests/test_virtualenv.py,sha256=g-njC_9JTAs1YVx_1dGJ_Q6RlInO4qKVu9-XAgNb6TY,3730
|
| 863 |
+
setuptools/tests/test_warnings.py,sha256=zwR2zcnCeCeDqILZlJOPAcuyPHoDvGu1OtOVYiLMk74,3347
|
| 864 |
+
setuptools/tests/test_wheel.py,sha256=J-83W1KdXTgAjFZE3H-ytohhvDE1iqdbE5YF5jLQlGQ,19370
|
| 865 |
+
setuptools/tests/test_windows_wrappers.py,sha256=aF6UTowN3yzCgdBh9nDQVvYIfSYogrTK776TEyXEBqg,7881
|
| 866 |
+
setuptools/tests/text.py,sha256=a12197pMVTvB6FAWQ0ujT8fIQiLIWJlFAl1UCaDUDfg,123
|
| 867 |
+
setuptools/tests/textwrap.py,sha256=FNNNq_MiaEJx88PnsbJQIRxmj1qmgcAOCXXRsODPJN4,98
|
| 868 |
+
setuptools/unicode_utils.py,sha256=ukMGh8pEAw6F_Ezb-K5D3c-078RgA_GcF0oW6lg4lSs,3189
|
| 869 |
+
setuptools/version.py,sha256=WJCeUuyq74Aok2TeK9-OexZOu8XrlQy7-y0BEuWNovQ,161
|
| 870 |
+
setuptools/warnings.py,sha256=oY0Se5eOqje_FEyjTgonUc0XGwgsrI5cgm1kkwulz_w,3796
|
| 871 |
+
setuptools/wheel.py,sha256=xkAtvgm7uPTyYV2zqVmQ0wA8kLwOyRT2Jes1zAy07Ks,8624
|
| 872 |
+
setuptools/windows_support.py,sha256=wW4IYLM1Bv7Z1MaauP2xmPjyy-wkmQnXdyvXscAf9fw,726
|
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (78.1.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[distutils.commands]
|
| 2 |
+
alias = setuptools.command.alias:alias
|
| 3 |
+
bdist_egg = setuptools.command.bdist_egg:bdist_egg
|
| 4 |
+
bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm
|
| 5 |
+
bdist_wheel = setuptools.command.bdist_wheel:bdist_wheel
|
| 6 |
+
build = setuptools.command.build:build
|
| 7 |
+
build_clib = setuptools.command.build_clib:build_clib
|
| 8 |
+
build_ext = setuptools.command.build_ext:build_ext
|
| 9 |
+
build_py = setuptools.command.build_py:build_py
|
| 10 |
+
develop = setuptools.command.develop:develop
|
| 11 |
+
dist_info = setuptools.command.dist_info:dist_info
|
| 12 |
+
easy_install = setuptools.command.easy_install:easy_install
|
| 13 |
+
editable_wheel = setuptools.command.editable_wheel:editable_wheel
|
| 14 |
+
egg_info = setuptools.command.egg_info:egg_info
|
| 15 |
+
install = setuptools.command.install:install
|
| 16 |
+
install_egg_info = setuptools.command.install_egg_info:install_egg_info
|
| 17 |
+
install_lib = setuptools.command.install_lib:install_lib
|
| 18 |
+
install_scripts = setuptools.command.install_scripts:install_scripts
|
| 19 |
+
rotate = setuptools.command.rotate:rotate
|
| 20 |
+
saveopts = setuptools.command.saveopts:saveopts
|
| 21 |
+
sdist = setuptools.command.sdist:sdist
|
| 22 |
+
setopt = setuptools.command.setopt:setopt
|
| 23 |
+
|
| 24 |
+
[distutils.setup_keywords]
|
| 25 |
+
dependency_links = setuptools.dist:assert_string_list
|
| 26 |
+
eager_resources = setuptools.dist:assert_string_list
|
| 27 |
+
entry_points = setuptools.dist:check_entry_points
|
| 28 |
+
exclude_package_data = setuptools.dist:check_package_data
|
| 29 |
+
extras_require = setuptools.dist:check_extras
|
| 30 |
+
include_package_data = setuptools.dist:assert_bool
|
| 31 |
+
install_requires = setuptools.dist:check_requirements
|
| 32 |
+
namespace_packages = setuptools.dist:check_nsp
|
| 33 |
+
package_data = setuptools.dist:check_package_data
|
| 34 |
+
packages = setuptools.dist:check_packages
|
| 35 |
+
python_requires = setuptools.dist:check_specifier
|
| 36 |
+
setup_requires = setuptools.dist:check_requirements
|
| 37 |
+
use_2to3 = setuptools.dist:invalid_unless_false
|
| 38 |
+
zip_safe = setuptools.dist:assert_bool
|
| 39 |
+
|
| 40 |
+
[egg_info.writers]
|
| 41 |
+
PKG-INFO = setuptools.command.egg_info:write_pkg_info
|
| 42 |
+
dependency_links.txt = setuptools.command.egg_info:overwrite_arg
|
| 43 |
+
eager_resources.txt = setuptools.command.egg_info:overwrite_arg
|
| 44 |
+
entry_points.txt = setuptools.command.egg_info:write_entries
|
| 45 |
+
namespace_packages.txt = setuptools.command.egg_info:overwrite_arg
|
| 46 |
+
requires.txt = setuptools.command.egg_info:write_requirements
|
| 47 |
+
top_level.txt = setuptools.command.egg_info:write_toplevel_names
|
| 48 |
+
|
| 49 |
+
[setuptools.finalize_distribution_options]
|
| 50 |
+
keywords = setuptools.dist:Distribution._finalize_setup_keywords
|
| 51 |
+
parent_finalize = setuptools.dist:_Distribution.finalize_options
|
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 2 |
+
of this software and associated documentation files (the "Software"), to
|
| 3 |
+
deal in the Software without restriction, including without limitation the
|
| 4 |
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
| 5 |
+
sell copies of the Software, and to permit persons to whom the Software is
|
| 6 |
+
furnished to do so, subject to the following conditions:
|
| 7 |
+
|
| 8 |
+
The above copyright notice and this permission notice shall be included in
|
| 9 |
+
all copies or substantial portions of the Software.
|
| 10 |
+
|
| 11 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 12 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 13 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 14 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 15 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
| 16 |
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
| 17 |
+
IN THE SOFTWARE.
|
venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_distutils_hack
|
| 2 |
+
pkg_resources
|
| 3 |
+
setuptools
|
venv/lib/python3.11/site-packages/setuptools/tests/config/__init__.py
ADDED
|
File without changes
|
venv/lib/python3.11/site-packages/setuptools/tests/config/downloads/__init__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
import time
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from urllib.error import HTTPError
|
| 7 |
+
from urllib.request import urlopen
|
| 8 |
+
|
| 9 |
+
__all__ = ["DOWNLOAD_DIR", "retrieve_file", "output_file", "urls_from_file"]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
NAME_REMOVE = ("http://", "https://", "github.com/", "/raw/")
|
| 13 |
+
DOWNLOAD_DIR = Path(__file__).parent
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# ----------------------------------------------------------------------
|
| 17 |
+
# Please update ./preload.py accordingly when modifying this file
|
| 18 |
+
# ----------------------------------------------------------------------
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def output_file(url: str, download_dir: Path = DOWNLOAD_DIR) -> Path:
|
| 22 |
+
file_name = url.strip()
|
| 23 |
+
for part in NAME_REMOVE:
|
| 24 |
+
file_name = file_name.replace(part, '').strip().strip('/:').strip()
|
| 25 |
+
return Path(download_dir, re.sub(r"[^\-_\.\w\d]+", "_", file_name))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def retrieve_file(url: str, download_dir: Path = DOWNLOAD_DIR, wait: float = 5) -> Path:
|
| 29 |
+
path = output_file(url, download_dir)
|
| 30 |
+
if path.exists():
|
| 31 |
+
print(f"Skipping {url} (already exists: {path})")
|
| 32 |
+
else:
|
| 33 |
+
download_dir.mkdir(exist_ok=True, parents=True)
|
| 34 |
+
print(f"Downloading {url} to {path}")
|
| 35 |
+
try:
|
| 36 |
+
download(url, path)
|
| 37 |
+
except HTTPError:
|
| 38 |
+
time.sleep(wait) # wait a few seconds and try again.
|
| 39 |
+
download(url, path)
|
| 40 |
+
return path
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def urls_from_file(list_file: Path) -> list[str]:
|
| 44 |
+
"""``list_file`` should be a text file where each line corresponds to a URL to
|
| 45 |
+
download.
|
| 46 |
+
"""
|
| 47 |
+
print(f"file: {list_file}")
|
| 48 |
+
content = list_file.read_text(encoding="utf-8")
|
| 49 |
+
return [url for url in content.splitlines() if not url.startswith("#")]
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def download(url: str, dest: Path):
|
| 53 |
+
with urlopen(url) as f:
|
| 54 |
+
data = f.read()
|
| 55 |
+
|
| 56 |
+
with open(dest, "wb") as f:
|
| 57 |
+
f.write(data)
|
| 58 |
+
|
| 59 |
+
assert Path(dest).exists()
|
venv/lib/python3.11/site-packages/setuptools/tests/config/downloads/preload.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""This file can be used to preload files needed for testing.
|
| 2 |
+
|
| 3 |
+
For example you can use::
|
| 4 |
+
|
| 5 |
+
cd setuptools/tests/config
|
| 6 |
+
python -m downloads.preload setupcfg_examples.txt
|
| 7 |
+
|
| 8 |
+
to make sure the `setup.cfg` examples are downloaded before starting the tests.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
from . import retrieve_file, urls_from_file
|
| 15 |
+
|
| 16 |
+
if __name__ == "__main__":
|
| 17 |
+
urls = urls_from_file(Path(sys.argv[1]))
|
| 18 |
+
list(map(retrieve_file, urls))
|
venv/lib/python3.11/site-packages/setuptools/tests/config/setupcfg_examples.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ====================================================================
|
| 2 |
+
# Some popular packages that use setup.cfg (and others not so popular)
|
| 3 |
+
# Reference: https://hugovk.github.io/top-pypi-packages/
|
| 4 |
+
# ====================================================================
|
| 5 |
+
https://github.com/pypa/setuptools/raw/52c990172fec37766b3566679724aa8bf70ae06d/setup.cfg
|
| 6 |
+
https://github.com/pypa/wheel/raw/0acd203cd896afec7f715aa2ff5980a403459a3b/setup.cfg
|
| 7 |
+
https://github.com/python/importlib_metadata/raw/2f05392ca980952a6960d82b2f2d2ea10aa53239/setup.cfg
|
| 8 |
+
https://github.com/jaraco/skeleton/raw/d9008b5c510cd6969127a6a2ab6f832edddef296/setup.cfg
|
| 9 |
+
https://github.com/jaraco/zipp/raw/700d3a96390e970b6b962823bfea78b4f7e1c537/setup.cfg
|
| 10 |
+
https://github.com/pallets/jinja/raw/7d72eb7fefb7dce065193967f31f805180508448/setup.cfg
|
| 11 |
+
https://github.com/tkem/cachetools/raw/2fd87a94b8d3861d80e9e4236cd480bfdd21c90d/setup.cfg
|
| 12 |
+
https://github.com/aio-libs/aiohttp/raw/5e0e6b7080f2408d5f1dd544c0e1cf88378b7b10/setup.cfg
|
| 13 |
+
https://github.com/pallets/flask/raw/9486b6cf57bd6a8a261f67091aca8ca78eeec1e3/setup.cfg
|
| 14 |
+
https://github.com/pallets/click/raw/6411f425fae545f42795665af4162006b36c5e4a/setup.cfg
|
| 15 |
+
https://github.com/sqlalchemy/sqlalchemy/raw/533f5718904b620be8d63f2474229945d6f8ba5d/setup.cfg
|
| 16 |
+
https://github.com/pytest-dev/pluggy/raw/461ef63291d13589c4e21aa182cd1529257e9a0a/setup.cfg
|
| 17 |
+
https://github.com/pytest-dev/pytest/raw/c7be96dae487edbd2f55b561b31b68afac1dabe6/setup.cfg
|
| 18 |
+
https://github.com/platformdirs/platformdirs/raw/7b7852128dd6f07511b618d6edea35046bd0c6ff/setup.cfg
|
| 19 |
+
https://github.com/pandas-dev/pandas/raw/bc17343f934a33dc231c8c74be95d8365537c376/setup.cfg
|
| 20 |
+
https://github.com/django/django/raw/4e249d11a6e56ca8feb4b055b681cec457ef3a3d/setup.cfg
|
| 21 |
+
https://github.com/pyscaffold/pyscaffold/raw/de7aa5dc059fbd04307419c667cc4961bc9df4b8/setup.cfg
|
| 22 |
+
https://github.com/pypa/virtualenv/raw/f92eda6e3da26a4d28c2663ffb85c4960bdb990c/setup.cfg
|
venv/lib/python3.11/site-packages/setuptools/tests/config/test_apply_pyprojecttoml.py
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Make sure that applying the configuration from pyproject.toml is equivalent to
|
| 2 |
+
applying a similar configuration from setup.cfg
|
| 3 |
+
|
| 4 |
+
To run these tests offline, please have a look on ``./downloads/preload.py``
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import io
|
| 10 |
+
import re
|
| 11 |
+
import tarfile
|
| 12 |
+
from inspect import cleandoc
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from unittest.mock import Mock
|
| 15 |
+
|
| 16 |
+
import pytest
|
| 17 |
+
from ini2toml.api import LiteTranslator
|
| 18 |
+
from packaging.metadata import Metadata
|
| 19 |
+
|
| 20 |
+
import setuptools # noqa: F401 # ensure monkey patch to metadata
|
| 21 |
+
from setuptools._static import is_static
|
| 22 |
+
from setuptools.command.egg_info import write_requirements
|
| 23 |
+
from setuptools.config import expand, pyprojecttoml, setupcfg
|
| 24 |
+
from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter
|
| 25 |
+
from setuptools.dist import Distribution
|
| 26 |
+
from setuptools.errors import InvalidConfigError, RemovedConfigError
|
| 27 |
+
from setuptools.warnings import InformationOnly, SetuptoolsDeprecationWarning
|
| 28 |
+
|
| 29 |
+
from .downloads import retrieve_file, urls_from_file
|
| 30 |
+
|
| 31 |
+
HERE = Path(__file__).parent
|
| 32 |
+
EXAMPLES_FILE = "setupcfg_examples.txt"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def makedist(path, **attrs):
|
| 36 |
+
return Distribution({"src_root": path, **attrs})
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _mock_expand_patterns(patterns, *_, **__):
|
| 40 |
+
"""
|
| 41 |
+
Allow comparing the given patterns for 2 dist objects.
|
| 42 |
+
We need to strip special chars to avoid errors when validating.
|
| 43 |
+
"""
|
| 44 |
+
return [re.sub("[^a-z0-9]+", "", p, flags=re.I) or "empty" for p in patterns]
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@pytest.mark.parametrize("url", urls_from_file(HERE / EXAMPLES_FILE))
|
| 48 |
+
@pytest.mark.filterwarnings("ignore")
|
| 49 |
+
@pytest.mark.uses_network
|
| 50 |
+
def test_apply_pyproject_equivalent_to_setupcfg(url, monkeypatch, tmp_path):
|
| 51 |
+
monkeypatch.setattr(expand, "read_attr", Mock(return_value="0.0.1"))
|
| 52 |
+
monkeypatch.setattr(
|
| 53 |
+
Distribution, "_expand_patterns", Mock(side_effect=_mock_expand_patterns)
|
| 54 |
+
)
|
| 55 |
+
setupcfg_example = retrieve_file(url)
|
| 56 |
+
pyproject_example = Path(tmp_path, "pyproject.toml")
|
| 57 |
+
setupcfg_text = setupcfg_example.read_text(encoding="utf-8")
|
| 58 |
+
toml_config = LiteTranslator().translate(setupcfg_text, "setup.cfg")
|
| 59 |
+
pyproject_example.write_text(toml_config, encoding="utf-8")
|
| 60 |
+
|
| 61 |
+
dist_toml = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject_example)
|
| 62 |
+
dist_cfg = setupcfg.apply_configuration(makedist(tmp_path), setupcfg_example)
|
| 63 |
+
|
| 64 |
+
pkg_info_toml = core_metadata(dist_toml)
|
| 65 |
+
pkg_info_cfg = core_metadata(dist_cfg)
|
| 66 |
+
assert pkg_info_toml == pkg_info_cfg
|
| 67 |
+
|
| 68 |
+
if any(getattr(d, "license_files", None) for d in (dist_toml, dist_cfg)):
|
| 69 |
+
assert set(dist_toml.license_files) == set(dist_cfg.license_files)
|
| 70 |
+
|
| 71 |
+
if any(getattr(d, "entry_points", None) for d in (dist_toml, dist_cfg)):
|
| 72 |
+
print(dist_cfg.entry_points)
|
| 73 |
+
ep_toml = {
|
| 74 |
+
(k, *sorted(i.replace(" ", "") for i in v))
|
| 75 |
+
for k, v in dist_toml.entry_points.items()
|
| 76 |
+
}
|
| 77 |
+
ep_cfg = {
|
| 78 |
+
(k, *sorted(i.replace(" ", "") for i in v))
|
| 79 |
+
for k, v in dist_cfg.entry_points.items()
|
| 80 |
+
}
|
| 81 |
+
assert ep_toml == ep_cfg
|
| 82 |
+
|
| 83 |
+
if any(getattr(d, "package_data", None) for d in (dist_toml, dist_cfg)):
|
| 84 |
+
pkg_data_toml = {(k, *sorted(v)) for k, v in dist_toml.package_data.items()}
|
| 85 |
+
pkg_data_cfg = {(k, *sorted(v)) for k, v in dist_cfg.package_data.items()}
|
| 86 |
+
assert pkg_data_toml == pkg_data_cfg
|
| 87 |
+
|
| 88 |
+
if any(getattr(d, "data_files", None) for d in (dist_toml, dist_cfg)):
|
| 89 |
+
data_files_toml = {(k, *sorted(v)) for k, v in dist_toml.data_files}
|
| 90 |
+
data_files_cfg = {(k, *sorted(v)) for k, v in dist_cfg.data_files}
|
| 91 |
+
assert data_files_toml == data_files_cfg
|
| 92 |
+
|
| 93 |
+
assert set(dist_toml.install_requires) == set(dist_cfg.install_requires)
|
| 94 |
+
if any(getattr(d, "extras_require", None) for d in (dist_toml, dist_cfg)):
|
| 95 |
+
extra_req_toml = {(k, *sorted(v)) for k, v in dist_toml.extras_require.items()}
|
| 96 |
+
extra_req_cfg = {(k, *sorted(v)) for k, v in dist_cfg.extras_require.items()}
|
| 97 |
+
assert extra_req_toml == extra_req_cfg
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
PEP621_EXAMPLE = """\
|
| 101 |
+
[project]
|
| 102 |
+
name = "spam"
|
| 103 |
+
version = "2020.0.0"
|
| 104 |
+
description = "Lovely Spam! Wonderful Spam!"
|
| 105 |
+
readme = "README.rst"
|
| 106 |
+
requires-python = ">=3.8"
|
| 107 |
+
license-files = ["LICENSE.txt"] # Updated to be PEP 639 compliant
|
| 108 |
+
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
|
| 109 |
+
authors = [
|
| 110 |
+
{email = "hi@pradyunsg.me"},
|
| 111 |
+
{name = "Tzu-Ping Chung"}
|
| 112 |
+
]
|
| 113 |
+
maintainers = [
|
| 114 |
+
{name = "Brett Cannon", email = "brett@python.org"},
|
| 115 |
+
{name = "John X. Ãørçeč", email = "john@utf8.org"},
|
| 116 |
+
{name = "Γαμα קּ 東", email = "gama@utf8.org"},
|
| 117 |
+
]
|
| 118 |
+
classifiers = [
|
| 119 |
+
"Development Status :: 4 - Beta",
|
| 120 |
+
"Programming Language :: Python"
|
| 121 |
+
]
|
| 122 |
+
|
| 123 |
+
dependencies = [
|
| 124 |
+
"httpx",
|
| 125 |
+
"gidgethub[httpx]>4.0.0",
|
| 126 |
+
"django>2.1; os_name != 'nt'",
|
| 127 |
+
"django>2.0; os_name == 'nt'"
|
| 128 |
+
]
|
| 129 |
+
|
| 130 |
+
[project.optional-dependencies]
|
| 131 |
+
test = [
|
| 132 |
+
"pytest < 5.0.0",
|
| 133 |
+
"pytest-cov[all]"
|
| 134 |
+
]
|
| 135 |
+
|
| 136 |
+
[project.urls]
|
| 137 |
+
homepage = "http://example.com"
|
| 138 |
+
documentation = "http://readthedocs.org"
|
| 139 |
+
repository = "http://github.com"
|
| 140 |
+
changelog = "http://github.com/me/spam/blob/master/CHANGELOG.md"
|
| 141 |
+
|
| 142 |
+
[project.scripts]
|
| 143 |
+
spam-cli = "spam:main_cli"
|
| 144 |
+
|
| 145 |
+
[project.gui-scripts]
|
| 146 |
+
spam-gui = "spam:main_gui"
|
| 147 |
+
|
| 148 |
+
[project.entry-points."spam.magical"]
|
| 149 |
+
tomatoes = "spam:main_tomatoes"
|
| 150 |
+
"""
|
| 151 |
+
|
| 152 |
+
PEP621_INTERNATIONAL_EMAIL_EXAMPLE = """\
|
| 153 |
+
[project]
|
| 154 |
+
name = "spam"
|
| 155 |
+
version = "2020.0.0"
|
| 156 |
+
authors = [
|
| 157 |
+
{email = "hi@pradyunsg.me"},
|
| 158 |
+
{name = "Tzu-Ping Chung"}
|
| 159 |
+
]
|
| 160 |
+
maintainers = [
|
| 161 |
+
{name = "Степан Бандера", email = "криївка@оун-упа.укр"},
|
| 162 |
+
]
|
| 163 |
+
"""
|
| 164 |
+
|
| 165 |
+
PEP621_EXAMPLE_SCRIPT = """
|
| 166 |
+
def main_cli(): pass
|
| 167 |
+
def main_gui(): pass
|
| 168 |
+
def main_tomatoes(): pass
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
PEP639_LICENSE_TEXT = """\
|
| 172 |
+
[project]
|
| 173 |
+
name = "spam"
|
| 174 |
+
version = "2020.0.0"
|
| 175 |
+
authors = [
|
| 176 |
+
{email = "hi@pradyunsg.me"},
|
| 177 |
+
{name = "Tzu-Ping Chung"}
|
| 178 |
+
]
|
| 179 |
+
license = {text = "MIT"}
|
| 180 |
+
"""
|
| 181 |
+
|
| 182 |
+
PEP639_LICENSE_EXPRESSION = """\
|
| 183 |
+
[project]
|
| 184 |
+
name = "spam"
|
| 185 |
+
version = "2020.0.0"
|
| 186 |
+
authors = [
|
| 187 |
+
{email = "hi@pradyunsg.me"},
|
| 188 |
+
{name = "Tzu-Ping Chung"}
|
| 189 |
+
]
|
| 190 |
+
license = "mit or apache-2.0" # should be normalized in metadata
|
| 191 |
+
classifiers = [
|
| 192 |
+
"Development Status :: 5 - Production/Stable",
|
| 193 |
+
"Programming Language :: Python",
|
| 194 |
+
]
|
| 195 |
+
"""
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def _pep621_example_project(
|
| 199 |
+
tmp_path,
|
| 200 |
+
readme="README.rst",
|
| 201 |
+
pyproject_text=PEP621_EXAMPLE,
|
| 202 |
+
):
|
| 203 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 204 |
+
text = pyproject_text
|
| 205 |
+
replacements = {'readme = "README.rst"': f'readme = "{readme}"'}
|
| 206 |
+
for orig, subst in replacements.items():
|
| 207 |
+
text = text.replace(orig, subst)
|
| 208 |
+
pyproject.write_text(text, encoding="utf-8")
|
| 209 |
+
|
| 210 |
+
(tmp_path / readme).write_text("hello world", encoding="utf-8")
|
| 211 |
+
(tmp_path / "LICENSE.txt").write_text("--- LICENSE stub ---", encoding="utf-8")
|
| 212 |
+
(tmp_path / "spam.py").write_text(PEP621_EXAMPLE_SCRIPT, encoding="utf-8")
|
| 213 |
+
return pyproject
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def test_pep621_example(tmp_path):
|
| 217 |
+
"""Make sure the example in PEP 621 works"""
|
| 218 |
+
pyproject = _pep621_example_project(tmp_path)
|
| 219 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 220 |
+
assert set(dist.metadata.license_files) == {"LICENSE.txt"}
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
@pytest.mark.parametrize(
|
| 224 |
+
("readme", "ctype"),
|
| 225 |
+
[
|
| 226 |
+
("Readme.txt", "text/plain"),
|
| 227 |
+
("readme.md", "text/markdown"),
|
| 228 |
+
("text.rst", "text/x-rst"),
|
| 229 |
+
],
|
| 230 |
+
)
|
| 231 |
+
def test_readme_content_type(tmp_path, readme, ctype):
|
| 232 |
+
pyproject = _pep621_example_project(tmp_path, readme)
|
| 233 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 234 |
+
assert dist.metadata.long_description_content_type == ctype
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def test_undefined_content_type(tmp_path):
|
| 238 |
+
pyproject = _pep621_example_project(tmp_path, "README.tex")
|
| 239 |
+
with pytest.raises(ValueError, match="Undefined content type for README.tex"):
|
| 240 |
+
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def test_no_explicit_content_type_for_missing_extension(tmp_path):
|
| 244 |
+
pyproject = _pep621_example_project(tmp_path, "README")
|
| 245 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 246 |
+
assert dist.metadata.long_description_content_type is None
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
@pytest.mark.parametrize(
|
| 250 |
+
("pyproject_text", "expected_maintainers_meta_value"),
|
| 251 |
+
(
|
| 252 |
+
pytest.param(
|
| 253 |
+
PEP621_EXAMPLE,
|
| 254 |
+
(
|
| 255 |
+
'Brett Cannon <brett@python.org>, "John X. Ãørçeč" <john@utf8.org>, '
|
| 256 |
+
'Γαμα קּ 東 <gama@utf8.org>'
|
| 257 |
+
),
|
| 258 |
+
id='non-international-emails',
|
| 259 |
+
),
|
| 260 |
+
pytest.param(
|
| 261 |
+
PEP621_INTERNATIONAL_EMAIL_EXAMPLE,
|
| 262 |
+
'Степан Бандера <криївка@оун-упа.укр>',
|
| 263 |
+
marks=pytest.mark.xfail(
|
| 264 |
+
reason="CPython's `email.headerregistry.Address` only supports "
|
| 265 |
+
'RFC 5322, as of Nov 10, 2022 and latest Python 3.11.0',
|
| 266 |
+
strict=True,
|
| 267 |
+
),
|
| 268 |
+
id='international-email',
|
| 269 |
+
),
|
| 270 |
+
),
|
| 271 |
+
)
|
| 272 |
+
def test_utf8_maintainer_in_metadata( # issue-3663
|
| 273 |
+
expected_maintainers_meta_value,
|
| 274 |
+
pyproject_text,
|
| 275 |
+
tmp_path,
|
| 276 |
+
):
|
| 277 |
+
pyproject = _pep621_example_project(
|
| 278 |
+
tmp_path,
|
| 279 |
+
"README",
|
| 280 |
+
pyproject_text=pyproject_text,
|
| 281 |
+
)
|
| 282 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 283 |
+
assert dist.metadata.maintainer_email == expected_maintainers_meta_value
|
| 284 |
+
pkg_file = tmp_path / "PKG-FILE"
|
| 285 |
+
with open(pkg_file, "w", encoding="utf-8") as fh:
|
| 286 |
+
dist.metadata.write_pkg_file(fh)
|
| 287 |
+
content = pkg_file.read_text(encoding="utf-8")
|
| 288 |
+
assert f"Maintainer-email: {expected_maintainers_meta_value}" in content
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
@pytest.mark.parametrize(
|
| 292 |
+
(
|
| 293 |
+
'pyproject_text',
|
| 294 |
+
'license',
|
| 295 |
+
'license_expression',
|
| 296 |
+
'content_str',
|
| 297 |
+
'not_content_str',
|
| 298 |
+
),
|
| 299 |
+
(
|
| 300 |
+
pytest.param(
|
| 301 |
+
PEP639_LICENSE_TEXT,
|
| 302 |
+
'MIT',
|
| 303 |
+
None,
|
| 304 |
+
'License: MIT',
|
| 305 |
+
'License-Expression: ',
|
| 306 |
+
id='license-text',
|
| 307 |
+
marks=[
|
| 308 |
+
pytest.mark.filterwarnings(
|
| 309 |
+
"ignore:.project.license. as a TOML table is deprecated",
|
| 310 |
+
)
|
| 311 |
+
],
|
| 312 |
+
),
|
| 313 |
+
pytest.param(
|
| 314 |
+
PEP639_LICENSE_EXPRESSION,
|
| 315 |
+
None,
|
| 316 |
+
'MIT OR Apache-2.0',
|
| 317 |
+
'License-Expression: MIT OR Apache-2.0',
|
| 318 |
+
'License: ',
|
| 319 |
+
id='license-expression',
|
| 320 |
+
),
|
| 321 |
+
),
|
| 322 |
+
)
|
| 323 |
+
def test_license_in_metadata(
|
| 324 |
+
license,
|
| 325 |
+
license_expression,
|
| 326 |
+
content_str,
|
| 327 |
+
not_content_str,
|
| 328 |
+
pyproject_text,
|
| 329 |
+
tmp_path,
|
| 330 |
+
):
|
| 331 |
+
pyproject = _pep621_example_project(
|
| 332 |
+
tmp_path,
|
| 333 |
+
"README",
|
| 334 |
+
pyproject_text=pyproject_text,
|
| 335 |
+
)
|
| 336 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 337 |
+
assert dist.metadata.license == license
|
| 338 |
+
assert dist.metadata.license_expression == license_expression
|
| 339 |
+
pkg_file = tmp_path / "PKG-FILE"
|
| 340 |
+
with open(pkg_file, "w", encoding="utf-8") as fh:
|
| 341 |
+
dist.metadata.write_pkg_file(fh)
|
| 342 |
+
content = pkg_file.read_text(encoding="utf-8")
|
| 343 |
+
assert "Metadata-Version: 2.4" in content
|
| 344 |
+
assert content_str in content
|
| 345 |
+
assert not_content_str not in content
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def test_license_classifier_with_license_expression(tmp_path):
|
| 349 |
+
text = PEP639_LICENSE_EXPRESSION.rsplit("\n", 2)[0]
|
| 350 |
+
pyproject = _pep621_example_project(
|
| 351 |
+
tmp_path,
|
| 352 |
+
"README",
|
| 353 |
+
f"{text}\n \"License :: OSI Approved :: MIT License\"\n]",
|
| 354 |
+
)
|
| 355 |
+
msg = "License classifiers have been superseded by license expressions"
|
| 356 |
+
with pytest.raises(InvalidConfigError, match=msg) as exc:
|
| 357 |
+
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 358 |
+
|
| 359 |
+
assert "License :: OSI Approved :: MIT License" in str(exc.value)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def test_license_classifier_without_license_expression(tmp_path):
|
| 363 |
+
text = """\
|
| 364 |
+
[project]
|
| 365 |
+
name = "spam"
|
| 366 |
+
version = "2020.0.0"
|
| 367 |
+
license = {text = "mit or apache-2.0"}
|
| 368 |
+
classifiers = ["License :: OSI Approved :: MIT License"]
|
| 369 |
+
"""
|
| 370 |
+
pyproject = _pep621_example_project(tmp_path, "README", text)
|
| 371 |
+
|
| 372 |
+
msg1 = "License classifiers are deprecated(?:.|\n)*MIT License"
|
| 373 |
+
msg2 = ".project.license. as a TOML table is deprecated"
|
| 374 |
+
with (
|
| 375 |
+
pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
|
| 376 |
+
pytest.warns(SetuptoolsDeprecationWarning, match=msg2),
|
| 377 |
+
):
|
| 378 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 379 |
+
|
| 380 |
+
# Check license classifier is still included
|
| 381 |
+
assert dist.metadata.get_classifiers() == ["License :: OSI Approved :: MIT License"]
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class TestLicenseFiles:
|
| 385 |
+
def base_pyproject(
|
| 386 |
+
self,
|
| 387 |
+
tmp_path,
|
| 388 |
+
additional_text="",
|
| 389 |
+
license_toml='license = {file = "LICENSE.txt"}\n',
|
| 390 |
+
):
|
| 391 |
+
text = PEP639_LICENSE_EXPRESSION
|
| 392 |
+
|
| 393 |
+
# Sanity-check
|
| 394 |
+
assert 'license = "mit or apache-2.0"' in text
|
| 395 |
+
assert 'license-files' not in text
|
| 396 |
+
assert "[tool.setuptools]" not in text
|
| 397 |
+
|
| 398 |
+
text = re.sub(
|
| 399 |
+
r"(license = .*)\n",
|
| 400 |
+
license_toml,
|
| 401 |
+
text,
|
| 402 |
+
count=1,
|
| 403 |
+
)
|
| 404 |
+
assert license_toml in text # sanity check
|
| 405 |
+
text = f"{text}\n{additional_text}\n"
|
| 406 |
+
pyproject = _pep621_example_project(tmp_path, "README", pyproject_text=text)
|
| 407 |
+
return pyproject
|
| 408 |
+
|
| 409 |
+
def base_pyproject_license_pep639(self, tmp_path, additional_text=""):
|
| 410 |
+
return self.base_pyproject(
|
| 411 |
+
tmp_path,
|
| 412 |
+
additional_text=additional_text,
|
| 413 |
+
license_toml='license = "licenseref-Proprietary"'
|
| 414 |
+
'\nlicense-files = ["_FILE*"]\n',
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
def test_both_license_and_license_files_defined(self, tmp_path):
|
| 418 |
+
setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
|
| 419 |
+
pyproject = self.base_pyproject(tmp_path, setuptools_config)
|
| 420 |
+
|
| 421 |
+
(tmp_path / "_FILE.txt").touch()
|
| 422 |
+
(tmp_path / "_FILE.rst").touch()
|
| 423 |
+
|
| 424 |
+
# Would normally match the `license_files` patterns, but we want to exclude it
|
| 425 |
+
# by being explicit. On the other hand, contents should be added to `license`
|
| 426 |
+
license = tmp_path / "LICENSE.txt"
|
| 427 |
+
license.write_text("LicenseRef-Proprietary\n", encoding="utf-8")
|
| 428 |
+
|
| 429 |
+
msg1 = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
|
| 430 |
+
msg2 = ".project.license. as a TOML table is deprecated"
|
| 431 |
+
with (
|
| 432 |
+
pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
|
| 433 |
+
pytest.warns(SetuptoolsDeprecationWarning, match=msg2),
|
| 434 |
+
):
|
| 435 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 436 |
+
assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
|
| 437 |
+
assert dist.metadata.license == "LicenseRef-Proprietary\n"
|
| 438 |
+
|
| 439 |
+
def test_both_license_and_license_files_defined_pep639(self, tmp_path):
|
| 440 |
+
# Set license and license-files
|
| 441 |
+
pyproject = self.base_pyproject_license_pep639(tmp_path)
|
| 442 |
+
|
| 443 |
+
(tmp_path / "_FILE.txt").touch()
|
| 444 |
+
(tmp_path / "_FILE.rst").touch()
|
| 445 |
+
|
| 446 |
+
msg = "Normalizing.*LicenseRef"
|
| 447 |
+
with pytest.warns(InformationOnly, match=msg):
|
| 448 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 449 |
+
|
| 450 |
+
assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
|
| 451 |
+
assert dist.metadata.license is None
|
| 452 |
+
assert dist.metadata.license_expression == "LicenseRef-Proprietary"
|
| 453 |
+
|
| 454 |
+
def test_license_files_defined_twice(self, tmp_path):
|
| 455 |
+
# Set project.license-files and tools.setuptools.license-files
|
| 456 |
+
setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
|
| 457 |
+
pyproject = self.base_pyproject_license_pep639(tmp_path, setuptools_config)
|
| 458 |
+
|
| 459 |
+
msg = "'project.license-files' is defined already. Remove 'tool.setuptools.license-files'"
|
| 460 |
+
with pytest.raises(InvalidConfigError, match=msg):
|
| 461 |
+
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 462 |
+
|
| 463 |
+
def test_default_patterns(self, tmp_path):
|
| 464 |
+
setuptools_config = '[tool.setuptools]\nzip-safe = false'
|
| 465 |
+
# ^ used just to trigger section validation
|
| 466 |
+
pyproject = self.base_pyproject(tmp_path, setuptools_config, license_toml="")
|
| 467 |
+
|
| 468 |
+
license_files = "LICENCE-a.html COPYING-abc.txt AUTHORS-xyz NOTICE,def".split()
|
| 469 |
+
|
| 470 |
+
for fname in license_files:
|
| 471 |
+
(tmp_path / fname).write_text(f"{fname}\n", encoding="utf-8")
|
| 472 |
+
|
| 473 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 474 |
+
|
| 475 |
+
assert (tmp_path / "LICENSE.txt").exists() # from base example
|
| 476 |
+
assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"}
|
| 477 |
+
|
| 478 |
+
def test_missing_patterns(self, tmp_path):
|
| 479 |
+
pyproject = self.base_pyproject_license_pep639(tmp_path)
|
| 480 |
+
assert list(tmp_path.glob("_FILE*")) == [] # sanity check
|
| 481 |
+
|
| 482 |
+
msg1 = "Cannot find any files for the given pattern.*"
|
| 483 |
+
msg2 = "Normalizing 'licenseref-Proprietary' to 'LicenseRef-Proprietary'"
|
| 484 |
+
with (
|
| 485 |
+
pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
|
| 486 |
+
pytest.warns(InformationOnly, match=msg2),
|
| 487 |
+
):
|
| 488 |
+
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 489 |
+
|
| 490 |
+
def test_deprecated_file_expands_to_text(self, tmp_path):
|
| 491 |
+
"""Make sure the old example with ``license = {text = ...}`` works"""
|
| 492 |
+
|
| 493 |
+
assert 'license-files = ["LICENSE.txt"]' in PEP621_EXAMPLE # sanity check
|
| 494 |
+
text = PEP621_EXAMPLE.replace(
|
| 495 |
+
'license-files = ["LICENSE.txt"]',
|
| 496 |
+
'license = {file = "LICENSE.txt"}',
|
| 497 |
+
)
|
| 498 |
+
pyproject = _pep621_example_project(tmp_path, pyproject_text=text)
|
| 499 |
+
|
| 500 |
+
msg = ".project.license. as a TOML table is deprecated"
|
| 501 |
+
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
|
| 502 |
+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 503 |
+
|
| 504 |
+
assert dist.metadata.license == "--- LICENSE stub ---"
|
| 505 |
+
assert set(dist.metadata.license_files) == {"LICENSE.txt"} # auto-filled
|
| 506 |
+
|
| 507 |
+
|
| 508 |
+
class TestPyModules:
|
| 509 |
+
# https://github.com/pypa/setuptools/issues/4316
|
| 510 |
+
|
| 511 |
+
def dist(self, name):
|
| 512 |
+
toml_config = f"""
|
| 513 |
+
[project]
|
| 514 |
+
name = "test"
|
| 515 |
+
version = "42.0"
|
| 516 |
+
[tool.setuptools]
|
| 517 |
+
py-modules = [{name!r}]
|
| 518 |
+
"""
|
| 519 |
+
pyproject = Path("pyproject.toml")
|
| 520 |
+
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
| 521 |
+
return pyprojecttoml.apply_configuration(Distribution({}), pyproject)
|
| 522 |
+
|
| 523 |
+
@pytest.mark.parametrize("module", ["pip-run", "abc-d.λ-xyz-e"])
|
| 524 |
+
def test_valid_module_name(self, tmp_path, monkeypatch, module):
|
| 525 |
+
monkeypatch.chdir(tmp_path)
|
| 526 |
+
assert module in self.dist(module).py_modules
|
| 527 |
+
|
| 528 |
+
@pytest.mark.parametrize("module", ["pip run", "-pip-run", "pip-run-stubs"])
|
| 529 |
+
def test_invalid_module_name(self, tmp_path, monkeypatch, module):
|
| 530 |
+
monkeypatch.chdir(tmp_path)
|
| 531 |
+
with pytest.raises(ValueError, match="py-modules"):
|
| 532 |
+
self.dist(module).py_modules
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
class TestExtModules:
|
| 536 |
+
def test_pyproject_sets_attribute(self, tmp_path, monkeypatch):
|
| 537 |
+
monkeypatch.chdir(tmp_path)
|
| 538 |
+
pyproject = Path("pyproject.toml")
|
| 539 |
+
toml_config = """
|
| 540 |
+
[project]
|
| 541 |
+
name = "test"
|
| 542 |
+
version = "42.0"
|
| 543 |
+
[tool.setuptools]
|
| 544 |
+
ext-modules = [
|
| 545 |
+
{name = "my.ext", sources = ["hello.c", "world.c"]}
|
| 546 |
+
]
|
| 547 |
+
"""
|
| 548 |
+
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
| 549 |
+
with pytest.warns(pyprojecttoml._ExperimentalConfiguration):
|
| 550 |
+
dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject)
|
| 551 |
+
assert len(dist.ext_modules) == 1
|
| 552 |
+
assert dist.ext_modules[0].name == "my.ext"
|
| 553 |
+
assert set(dist.ext_modules[0].sources) == {"hello.c", "world.c"}
|
| 554 |
+
|
| 555 |
+
|
| 556 |
+
class TestDeprecatedFields:
|
| 557 |
+
def test_namespace_packages(self, tmp_path):
|
| 558 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 559 |
+
config = """
|
| 560 |
+
[project]
|
| 561 |
+
name = "myproj"
|
| 562 |
+
version = "42"
|
| 563 |
+
[tool.setuptools]
|
| 564 |
+
namespace-packages = ["myproj.pkg"]
|
| 565 |
+
"""
|
| 566 |
+
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
| 567 |
+
with pytest.raises(RemovedConfigError, match="namespace-packages"):
|
| 568 |
+
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
class TestPresetField:
|
| 572 |
+
def pyproject(self, tmp_path, dynamic, extra_content=""):
|
| 573 |
+
content = f"[project]\nname = 'proj'\ndynamic = {dynamic!r}\n"
|
| 574 |
+
if "version" not in dynamic:
|
| 575 |
+
content += "version = '42'\n"
|
| 576 |
+
file = tmp_path / "pyproject.toml"
|
| 577 |
+
file.write_text(content + extra_content, encoding="utf-8")
|
| 578 |
+
return file
|
| 579 |
+
|
| 580 |
+
@pytest.mark.parametrize(
|
| 581 |
+
("attr", "field", "value"),
|
| 582 |
+
[
|
| 583 |
+
("license_expression", "license", "MIT"),
|
| 584 |
+
pytest.param(
|
| 585 |
+
*("license", "license", "Not SPDX"),
|
| 586 |
+
marks=[pytest.mark.filterwarnings("ignore:.*license. overwritten")],
|
| 587 |
+
),
|
| 588 |
+
("classifiers", "classifiers", ["Private :: Classifier"]),
|
| 589 |
+
("entry_points", "scripts", {"console_scripts": ["foobar=foobar:main"]}),
|
| 590 |
+
("entry_points", "gui-scripts", {"gui_scripts": ["bazquux=bazquux:main"]}),
|
| 591 |
+
pytest.param(
|
| 592 |
+
*("install_requires", "dependencies", ["six"]),
|
| 593 |
+
marks=[
|
| 594 |
+
pytest.mark.filterwarnings("ignore:.*install_requires. overwritten")
|
| 595 |
+
],
|
| 596 |
+
),
|
| 597 |
+
],
|
| 598 |
+
)
|
| 599 |
+
def test_not_listed_in_dynamic(self, tmp_path, attr, field, value):
|
| 600 |
+
"""Setuptools cannot set a field if not listed in ``dynamic``"""
|
| 601 |
+
pyproject = self.pyproject(tmp_path, [])
|
| 602 |
+
dist = makedist(tmp_path, **{attr: value})
|
| 603 |
+
msg = re.compile(f"defined outside of `pyproject.toml`:.*{field}", re.S)
|
| 604 |
+
with pytest.warns(_MissingDynamic, match=msg):
|
| 605 |
+
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
| 606 |
+
|
| 607 |
+
dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist)
|
| 608 |
+
assert not dist_value
|
| 609 |
+
|
| 610 |
+
@pytest.mark.parametrize(
|
| 611 |
+
("attr", "field", "value"),
|
| 612 |
+
[
|
| 613 |
+
("license_expression", "license", "MIT"),
|
| 614 |
+
("install_requires", "dependencies", []),
|
| 615 |
+
("extras_require", "optional-dependencies", {}),
|
| 616 |
+
("install_requires", "dependencies", ["six"]),
|
| 617 |
+
("classifiers", "classifiers", ["Private :: Classifier"]),
|
| 618 |
+
],
|
| 619 |
+
)
|
| 620 |
+
def test_listed_in_dynamic(self, tmp_path, attr, field, value):
|
| 621 |
+
pyproject = self.pyproject(tmp_path, [field])
|
| 622 |
+
dist = makedist(tmp_path, **{attr: value})
|
| 623 |
+
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
| 624 |
+
dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist)
|
| 625 |
+
assert dist_value == value
|
| 626 |
+
|
| 627 |
+
def test_license_files_exempt_from_dynamic(self, monkeypatch, tmp_path):
|
| 628 |
+
"""
|
| 629 |
+
license-file is currently not considered in the context of dynamic.
|
| 630 |
+
As per 2025-02-19, https://packaging.python.org/en/latest/specifications/pyproject-toml/#license-files
|
| 631 |
+
allows setuptools to fill-in `license-files` the way it sees fit:
|
| 632 |
+
|
| 633 |
+
> If the license-files key is not defined, tools can decide how to handle license files.
|
| 634 |
+
> For example they can choose not to include any files or use their own
|
| 635 |
+
> logic to discover the appropriate files in the distribution.
|
| 636 |
+
|
| 637 |
+
Using license_files from setup.py to fill-in the value is in accordance
|
| 638 |
+
with this rule.
|
| 639 |
+
"""
|
| 640 |
+
monkeypatch.chdir(tmp_path)
|
| 641 |
+
pyproject = self.pyproject(tmp_path, [])
|
| 642 |
+
dist = makedist(tmp_path, license_files=["LIC*"])
|
| 643 |
+
(tmp_path / "LIC1").write_text("42", encoding="utf-8")
|
| 644 |
+
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
| 645 |
+
assert dist.metadata.license_files == ["LIC1"]
|
| 646 |
+
|
| 647 |
+
def test_warning_overwritten_dependencies(self, tmp_path):
|
| 648 |
+
src = "[project]\nname='pkg'\nversion='0.1'\ndependencies=['click']\n"
|
| 649 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 650 |
+
pyproject.write_text(src, encoding="utf-8")
|
| 651 |
+
dist = makedist(tmp_path, install_requires=["wheel"])
|
| 652 |
+
with pytest.warns(match="`install_requires` overwritten"):
|
| 653 |
+
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
| 654 |
+
assert "wheel" not in dist.install_requires
|
| 655 |
+
|
| 656 |
+
def test_optional_dependencies_dont_remove_env_markers(self, tmp_path):
|
| 657 |
+
"""
|
| 658 |
+
Internally setuptools converts dependencies with markers to "extras".
|
| 659 |
+
If ``install_requires`` is given by ``setup.py``, we have to ensure that
|
| 660 |
+
applying ``optional-dependencies`` does not overwrite the mandatory
|
| 661 |
+
dependencies with markers (see #3204).
|
| 662 |
+
"""
|
| 663 |
+
# If setuptools replace its internal mechanism that uses `requires.txt`
|
| 664 |
+
# this test has to be rewritten to adapt accordingly
|
| 665 |
+
extra = "\n[project.optional-dependencies]\nfoo = ['bar>1']\n"
|
| 666 |
+
pyproject = self.pyproject(tmp_path, ["dependencies"], extra)
|
| 667 |
+
install_req = ['importlib-resources (>=3.0.0) ; python_version < "3.7"']
|
| 668 |
+
dist = makedist(tmp_path, install_requires=install_req)
|
| 669 |
+
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
| 670 |
+
assert "foo" in dist.extras_require
|
| 671 |
+
egg_info = dist.get_command_obj("egg_info")
|
| 672 |
+
write_requirements(egg_info, tmp_path, tmp_path / "requires.txt")
|
| 673 |
+
reqs = (tmp_path / "requires.txt").read_text(encoding="utf-8")
|
| 674 |
+
assert "importlib-resources" in reqs
|
| 675 |
+
assert "bar" in reqs
|
| 676 |
+
assert ':python_version < "3.7"' in reqs
|
| 677 |
+
|
| 678 |
+
@pytest.mark.parametrize(
|
| 679 |
+
("field", "group"),
|
| 680 |
+
[("scripts", "console_scripts"), ("gui-scripts", "gui_scripts")],
|
| 681 |
+
)
|
| 682 |
+
@pytest.mark.filterwarnings("error")
|
| 683 |
+
def test_scripts_dont_require_dynamic_entry_points(self, tmp_path, field, group):
|
| 684 |
+
# Issue 3862
|
| 685 |
+
pyproject = self.pyproject(tmp_path, [field])
|
| 686 |
+
dist = makedist(tmp_path, entry_points={group: ["foobar=foobar:main"]})
|
| 687 |
+
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
| 688 |
+
assert group in dist.entry_points
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
class TestMeta:
|
| 692 |
+
def test_example_file_in_sdist(self, setuptools_sdist):
|
| 693 |
+
"""Meta test to ensure tests can run from sdist"""
|
| 694 |
+
with tarfile.open(setuptools_sdist) as tar:
|
| 695 |
+
assert any(name.endswith(EXAMPLES_FILE) for name in tar.getnames())
|
| 696 |
+
|
| 697 |
+
|
| 698 |
+
class TestInteropCommandLineParsing:
|
| 699 |
+
def test_version(self, tmp_path, monkeypatch, capsys):
|
| 700 |
+
# See pypa/setuptools#4047
|
| 701 |
+
# This test can be removed once the CLI interface of setup.py is removed
|
| 702 |
+
monkeypatch.chdir(tmp_path)
|
| 703 |
+
toml_config = """
|
| 704 |
+
[project]
|
| 705 |
+
name = "test"
|
| 706 |
+
version = "42.0"
|
| 707 |
+
"""
|
| 708 |
+
pyproject = Path(tmp_path, "pyproject.toml")
|
| 709 |
+
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
| 710 |
+
opts = {"script_args": ["--version"]}
|
| 711 |
+
dist = pyprojecttoml.apply_configuration(Distribution(opts), pyproject)
|
| 712 |
+
dist.parse_command_line() # <-- there should be no exception here.
|
| 713 |
+
captured = capsys.readouterr()
|
| 714 |
+
assert "42.0" in captured.out
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
class TestStaticConfig:
|
| 718 |
+
def test_mark_static_fields(self, tmp_path, monkeypatch):
|
| 719 |
+
monkeypatch.chdir(tmp_path)
|
| 720 |
+
toml_config = """
|
| 721 |
+
[project]
|
| 722 |
+
name = "test"
|
| 723 |
+
version = "42.0"
|
| 724 |
+
dependencies = ["hello"]
|
| 725 |
+
keywords = ["world"]
|
| 726 |
+
classifiers = ["private :: hello world"]
|
| 727 |
+
[tool.setuptools]
|
| 728 |
+
obsoletes = ["abcd"]
|
| 729 |
+
provides = ["abcd"]
|
| 730 |
+
platforms = ["abcd"]
|
| 731 |
+
"""
|
| 732 |
+
pyproject = Path(tmp_path, "pyproject.toml")
|
| 733 |
+
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
| 734 |
+
dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject)
|
| 735 |
+
assert is_static(dist.install_requires)
|
| 736 |
+
assert is_static(dist.metadata.keywords)
|
| 737 |
+
assert is_static(dist.metadata.classifiers)
|
| 738 |
+
assert is_static(dist.metadata.obsoletes)
|
| 739 |
+
assert is_static(dist.metadata.provides)
|
| 740 |
+
assert is_static(dist.metadata.platforms)
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
# --- Auxiliary Functions ---
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
def core_metadata(dist) -> str:
|
| 747 |
+
with io.StringIO() as buffer:
|
| 748 |
+
dist.metadata.write_pkg_file(buffer)
|
| 749 |
+
pkg_file_txt = buffer.getvalue()
|
| 750 |
+
|
| 751 |
+
# Make sure core metadata is valid
|
| 752 |
+
Metadata.from_email(pkg_file_txt, validate=True) # can raise exceptions
|
| 753 |
+
|
| 754 |
+
skip_prefixes: tuple[str, ...] = ()
|
| 755 |
+
skip_lines = set()
|
| 756 |
+
# ---- DIFF NORMALISATION ----
|
| 757 |
+
# PEP 621 is very particular about author/maintainer metadata conversion, so skip
|
| 758 |
+
skip_prefixes += ("Author:", "Author-email:", "Maintainer:", "Maintainer-email:")
|
| 759 |
+
# May be redundant with Home-page
|
| 760 |
+
skip_prefixes += ("Project-URL: Homepage,", "Home-page:")
|
| 761 |
+
# May be missing in original (relying on default) but backfilled in the TOML
|
| 762 |
+
skip_prefixes += ("Description-Content-Type:",)
|
| 763 |
+
# Remove empty lines
|
| 764 |
+
skip_lines.add("")
|
| 765 |
+
|
| 766 |
+
result = []
|
| 767 |
+
for line in pkg_file_txt.splitlines():
|
| 768 |
+
if line.startswith(skip_prefixes) or line in skip_lines:
|
| 769 |
+
continue
|
| 770 |
+
result.append(line + "\n")
|
| 771 |
+
|
| 772 |
+
return "".join(result)
|
venv/lib/python3.11/site-packages/setuptools/tests/config/test_expand.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
from setuptools._static import is_static
|
| 8 |
+
from setuptools.config import expand
|
| 9 |
+
from setuptools.discovery import find_package_path
|
| 10 |
+
|
| 11 |
+
from distutils.errors import DistutilsOptionError
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def write_files(files, root_dir):
|
| 15 |
+
for file, content in files.items():
|
| 16 |
+
path = root_dir / file
|
| 17 |
+
path.parent.mkdir(exist_ok=True, parents=True)
|
| 18 |
+
path.write_text(content, encoding="utf-8")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def test_glob_relative(tmp_path, monkeypatch):
|
| 22 |
+
files = {
|
| 23 |
+
"dir1/dir2/dir3/file1.txt",
|
| 24 |
+
"dir1/dir2/file2.txt",
|
| 25 |
+
"dir1/file3.txt",
|
| 26 |
+
"a.ini",
|
| 27 |
+
"b.ini",
|
| 28 |
+
"dir1/c.ini",
|
| 29 |
+
"dir1/dir2/a.ini",
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
write_files({k: "" for k in files}, tmp_path)
|
| 33 |
+
patterns = ["**/*.txt", "[ab].*", "**/[ac].ini"]
|
| 34 |
+
monkeypatch.chdir(tmp_path)
|
| 35 |
+
assert set(expand.glob_relative(patterns)) == files
|
| 36 |
+
# Make sure the same APIs work outside cwd
|
| 37 |
+
assert set(expand.glob_relative(patterns, tmp_path)) == files
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_read_files(tmp_path, monkeypatch):
|
| 41 |
+
dir_ = tmp_path / "dir_"
|
| 42 |
+
(tmp_path / "_dir").mkdir(exist_ok=True)
|
| 43 |
+
(tmp_path / "a.txt").touch()
|
| 44 |
+
files = {"a.txt": "a", "dir1/b.txt": "b", "dir1/dir2/c.txt": "c"}
|
| 45 |
+
write_files(files, dir_)
|
| 46 |
+
|
| 47 |
+
secrets = Path(str(dir_) + "secrets")
|
| 48 |
+
secrets.mkdir(exist_ok=True)
|
| 49 |
+
write_files({"secrets.txt": "secret keys"}, secrets)
|
| 50 |
+
|
| 51 |
+
with monkeypatch.context() as m:
|
| 52 |
+
m.chdir(dir_)
|
| 53 |
+
assert expand.read_files(list(files)) == "a\nb\nc"
|
| 54 |
+
|
| 55 |
+
cannot_access_msg = r"Cannot access '.*\.\..a\.txt'"
|
| 56 |
+
with pytest.raises(DistutilsOptionError, match=cannot_access_msg):
|
| 57 |
+
expand.read_files(["../a.txt"])
|
| 58 |
+
|
| 59 |
+
cannot_access_secrets_msg = r"Cannot access '.*secrets\.txt'"
|
| 60 |
+
with pytest.raises(DistutilsOptionError, match=cannot_access_secrets_msg):
|
| 61 |
+
expand.read_files(["../dir_secrets/secrets.txt"])
|
| 62 |
+
|
| 63 |
+
# Make sure the same APIs work outside cwd
|
| 64 |
+
assert expand.read_files(list(files), dir_) == "a\nb\nc"
|
| 65 |
+
with pytest.raises(DistutilsOptionError, match=cannot_access_msg):
|
| 66 |
+
expand.read_files(["../a.txt"], dir_)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class TestReadAttr:
|
| 70 |
+
@pytest.mark.parametrize(
|
| 71 |
+
"example",
|
| 72 |
+
[
|
| 73 |
+
# No cookie means UTF-8:
|
| 74 |
+
b"__version__ = '\xc3\xa9'\nraise SystemExit(1)\n",
|
| 75 |
+
# If a cookie is present, honor it:
|
| 76 |
+
b"# -*- coding: utf-8 -*-\n__version__ = '\xc3\xa9'\nraise SystemExit(1)\n",
|
| 77 |
+
b"# -*- coding: latin1 -*-\n__version__ = '\xe9'\nraise SystemExit(1)\n",
|
| 78 |
+
],
|
| 79 |
+
)
|
| 80 |
+
def test_read_attr_encoding_cookie(self, example, tmp_path):
|
| 81 |
+
(tmp_path / "mod.py").write_bytes(example)
|
| 82 |
+
assert expand.read_attr('mod.__version__', root_dir=tmp_path) == 'é'
|
| 83 |
+
|
| 84 |
+
def test_read_attr(self, tmp_path, monkeypatch):
|
| 85 |
+
files = {
|
| 86 |
+
"pkg/__init__.py": "",
|
| 87 |
+
"pkg/sub/__init__.py": "VERSION = '0.1.1'",
|
| 88 |
+
"pkg/sub/mod.py": (
|
| 89 |
+
"VALUES = {'a': 0, 'b': {42}, 'c': (0, 1, 1)}\nraise SystemExit(1)"
|
| 90 |
+
),
|
| 91 |
+
}
|
| 92 |
+
write_files(files, tmp_path)
|
| 93 |
+
|
| 94 |
+
with monkeypatch.context() as m:
|
| 95 |
+
m.chdir(tmp_path)
|
| 96 |
+
# Make sure it can read the attr statically without evaluating the module
|
| 97 |
+
version = expand.read_attr('pkg.sub.VERSION')
|
| 98 |
+
values = expand.read_attr('lib.mod.VALUES', {'lib': 'pkg/sub'})
|
| 99 |
+
|
| 100 |
+
assert version == '0.1.1'
|
| 101 |
+
assert is_static(values)
|
| 102 |
+
|
| 103 |
+
assert values['a'] == 0
|
| 104 |
+
assert values['b'] == {42}
|
| 105 |
+
assert is_static(values)
|
| 106 |
+
|
| 107 |
+
# Make sure the same APIs work outside cwd
|
| 108 |
+
assert expand.read_attr('pkg.sub.VERSION', root_dir=tmp_path) == '0.1.1'
|
| 109 |
+
values = expand.read_attr('lib.mod.VALUES', {'lib': 'pkg/sub'}, tmp_path)
|
| 110 |
+
assert values['c'] == (0, 1, 1)
|
| 111 |
+
|
| 112 |
+
@pytest.mark.parametrize(
|
| 113 |
+
"example",
|
| 114 |
+
[
|
| 115 |
+
"VERSION: str\nVERSION = '0.1.1'\nraise SystemExit(1)\n",
|
| 116 |
+
"VERSION: str = '0.1.1'\nraise SystemExit(1)\n",
|
| 117 |
+
],
|
| 118 |
+
)
|
| 119 |
+
def test_read_annotated_attr(self, tmp_path, example):
|
| 120 |
+
files = {
|
| 121 |
+
"pkg/__init__.py": "",
|
| 122 |
+
"pkg/sub/__init__.py": example,
|
| 123 |
+
}
|
| 124 |
+
write_files(files, tmp_path)
|
| 125 |
+
# Make sure this attribute can be read statically
|
| 126 |
+
version = expand.read_attr('pkg.sub.VERSION', root_dir=tmp_path)
|
| 127 |
+
assert version == '0.1.1'
|
| 128 |
+
assert is_static(version)
|
| 129 |
+
|
| 130 |
+
@pytest.mark.parametrize(
|
| 131 |
+
"example",
|
| 132 |
+
[
|
| 133 |
+
"VERSION = (lambda: '0.1.1')()\n",
|
| 134 |
+
"def fn(): return '0.1.1'\nVERSION = fn()\n",
|
| 135 |
+
"VERSION: str = (lambda: '0.1.1')()\n",
|
| 136 |
+
],
|
| 137 |
+
)
|
| 138 |
+
def test_read_dynamic_attr(self, tmp_path, monkeypatch, example):
|
| 139 |
+
files = {
|
| 140 |
+
"pkg/__init__.py": "",
|
| 141 |
+
"pkg/sub/__init__.py": example,
|
| 142 |
+
}
|
| 143 |
+
write_files(files, tmp_path)
|
| 144 |
+
monkeypatch.chdir(tmp_path)
|
| 145 |
+
version = expand.read_attr('pkg.sub.VERSION')
|
| 146 |
+
assert version == '0.1.1'
|
| 147 |
+
assert not is_static(version)
|
| 148 |
+
|
| 149 |
+
def test_import_order(self, tmp_path):
|
| 150 |
+
"""
|
| 151 |
+
Sometimes the import machinery will import the parent package of a nested
|
| 152 |
+
module, which triggers side-effects and might create problems (see issue #3176)
|
| 153 |
+
|
| 154 |
+
``read_attr`` should bypass these limitations by resolving modules statically
|
| 155 |
+
(via ast.literal_eval).
|
| 156 |
+
"""
|
| 157 |
+
files = {
|
| 158 |
+
"src/pkg/__init__.py": "from .main import func\nfrom .about import version",
|
| 159 |
+
"src/pkg/main.py": "import super_complicated_dep\ndef func(): return 42",
|
| 160 |
+
"src/pkg/about.py": "version = '42'",
|
| 161 |
+
}
|
| 162 |
+
write_files(files, tmp_path)
|
| 163 |
+
attr_desc = "pkg.about.version"
|
| 164 |
+
package_dir = {"": "src"}
|
| 165 |
+
# `import super_complicated_dep` should not run, otherwise the build fails
|
| 166 |
+
assert expand.read_attr(attr_desc, package_dir, tmp_path) == "42"
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
@pytest.mark.parametrize(
|
| 170 |
+
("package_dir", "file", "module", "return_value"),
|
| 171 |
+
[
|
| 172 |
+
({"": "src"}, "src/pkg/main.py", "pkg.main", 42),
|
| 173 |
+
({"pkg": "lib"}, "lib/main.py", "pkg.main", 13),
|
| 174 |
+
({}, "single_module.py", "single_module", 70),
|
| 175 |
+
({}, "flat_layout/pkg.py", "flat_layout.pkg", 836),
|
| 176 |
+
],
|
| 177 |
+
)
|
| 178 |
+
def test_resolve_class(monkeypatch, tmp_path, package_dir, file, module, return_value):
|
| 179 |
+
monkeypatch.setattr(sys, "modules", {}) # reproducibility
|
| 180 |
+
files = {file: f"class Custom:\n def testing(self): return {return_value}"}
|
| 181 |
+
write_files(files, tmp_path)
|
| 182 |
+
cls = expand.resolve_class(f"{module}.Custom", package_dir, tmp_path)
|
| 183 |
+
assert cls().testing() == return_value
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
@pytest.mark.parametrize(
|
| 187 |
+
("args", "pkgs"),
|
| 188 |
+
[
|
| 189 |
+
({"where": ["."], "namespaces": False}, {"pkg", "other"}),
|
| 190 |
+
({"where": [".", "dir1"], "namespaces": False}, {"pkg", "other", "dir2"}),
|
| 191 |
+
({"namespaces": True}, {"pkg", "other", "dir1", "dir1.dir2"}),
|
| 192 |
+
({}, {"pkg", "other", "dir1", "dir1.dir2"}), # default value for `namespaces`
|
| 193 |
+
],
|
| 194 |
+
)
|
| 195 |
+
def test_find_packages(tmp_path, args, pkgs):
|
| 196 |
+
files = {
|
| 197 |
+
"pkg/__init__.py",
|
| 198 |
+
"other/__init__.py",
|
| 199 |
+
"dir1/dir2/__init__.py",
|
| 200 |
+
}
|
| 201 |
+
write_files({k: "" for k in files}, tmp_path)
|
| 202 |
+
|
| 203 |
+
package_dir = {}
|
| 204 |
+
kwargs = {"root_dir": tmp_path, "fill_package_dir": package_dir, **args}
|
| 205 |
+
where = kwargs.get("where", ["."])
|
| 206 |
+
assert set(expand.find_packages(**kwargs)) == pkgs
|
| 207 |
+
for pkg in pkgs:
|
| 208 |
+
pkg_path = find_package_path(pkg, package_dir, tmp_path)
|
| 209 |
+
assert os.path.exists(pkg_path)
|
| 210 |
+
|
| 211 |
+
# Make sure the same APIs work outside cwd
|
| 212 |
+
where = [
|
| 213 |
+
str((tmp_path / p).resolve()).replace(os.sep, "/") # ensure posix-style paths
|
| 214 |
+
for p in args.pop("where", ["."])
|
| 215 |
+
]
|
| 216 |
+
|
| 217 |
+
assert set(expand.find_packages(where=where, **args)) == pkgs
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
@pytest.mark.parametrize(
|
| 221 |
+
("files", "where", "expected_package_dir"),
|
| 222 |
+
[
|
| 223 |
+
(["pkg1/__init__.py", "pkg1/other.py"], ["."], {}),
|
| 224 |
+
(["pkg1/__init__.py", "pkg2/__init__.py"], ["."], {}),
|
| 225 |
+
(["src/pkg1/__init__.py", "src/pkg1/other.py"], ["src"], {"": "src"}),
|
| 226 |
+
(["src/pkg1/__init__.py", "src/pkg2/__init__.py"], ["src"], {"": "src"}),
|
| 227 |
+
(
|
| 228 |
+
["src1/pkg1/__init__.py", "src2/pkg2/__init__.py"],
|
| 229 |
+
["src1", "src2"],
|
| 230 |
+
{"pkg1": "src1/pkg1", "pkg2": "src2/pkg2"},
|
| 231 |
+
),
|
| 232 |
+
(
|
| 233 |
+
["src/pkg1/__init__.py", "pkg2/__init__.py"],
|
| 234 |
+
["src", "."],
|
| 235 |
+
{"pkg1": "src/pkg1"},
|
| 236 |
+
),
|
| 237 |
+
],
|
| 238 |
+
)
|
| 239 |
+
def test_fill_package_dir(tmp_path, files, where, expected_package_dir):
|
| 240 |
+
write_files({k: "" for k in files}, tmp_path)
|
| 241 |
+
pkg_dir = {}
|
| 242 |
+
kwargs = {"root_dir": tmp_path, "fill_package_dir": pkg_dir, "namespaces": False}
|
| 243 |
+
pkgs = expand.find_packages(where=where, **kwargs)
|
| 244 |
+
assert set(pkg_dir.items()) == set(expected_package_dir.items())
|
| 245 |
+
for pkg in pkgs:
|
| 246 |
+
pkg_path = find_package_path(pkg, pkg_dir, tmp_path)
|
| 247 |
+
assert os.path.exists(pkg_path)
|
venv/lib/python3.11/site-packages/setuptools/tests/config/test_pyprojecttoml.py
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
from configparser import ConfigParser
|
| 3 |
+
from inspect import cleandoc
|
| 4 |
+
|
| 5 |
+
import jaraco.path
|
| 6 |
+
import pytest
|
| 7 |
+
import tomli_w
|
| 8 |
+
from path import Path
|
| 9 |
+
|
| 10 |
+
import setuptools # noqa: F401 # force distutils.core to be patched
|
| 11 |
+
from setuptools.config.pyprojecttoml import (
|
| 12 |
+
_ToolsTypoInMetadata,
|
| 13 |
+
apply_configuration,
|
| 14 |
+
expand_configuration,
|
| 15 |
+
read_configuration,
|
| 16 |
+
validate,
|
| 17 |
+
)
|
| 18 |
+
from setuptools.dist import Distribution
|
| 19 |
+
from setuptools.errors import OptionError
|
| 20 |
+
|
| 21 |
+
import distutils.core
|
| 22 |
+
|
| 23 |
+
EXAMPLE = """
|
| 24 |
+
[project]
|
| 25 |
+
name = "myproj"
|
| 26 |
+
keywords = ["some", "key", "words"]
|
| 27 |
+
dynamic = ["version", "readme"]
|
| 28 |
+
requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
| 29 |
+
dependencies = [
|
| 30 |
+
'importlib-metadata>=0.12;python_version<"3.8"',
|
| 31 |
+
'importlib-resources>=1.0;python_version<"3.7"',
|
| 32 |
+
'pathlib2>=2.3.3,<3;python_version < "3.4" and sys.platform != "win32"',
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
[project.optional-dependencies]
|
| 36 |
+
docs = [
|
| 37 |
+
"sphinx>=3",
|
| 38 |
+
"sphinx-argparse>=0.2.5",
|
| 39 |
+
"sphinx-rtd-theme>=0.4.3",
|
| 40 |
+
]
|
| 41 |
+
testing = [
|
| 42 |
+
"pytest>=1",
|
| 43 |
+
"coverage>=3,<5",
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
[project.scripts]
|
| 47 |
+
exec = "pkg.__main__:exec"
|
| 48 |
+
|
| 49 |
+
[build-system]
|
| 50 |
+
requires = ["setuptools", "wheel"]
|
| 51 |
+
build-backend = "setuptools.build_meta"
|
| 52 |
+
|
| 53 |
+
[tool.setuptools]
|
| 54 |
+
package-dir = {"" = "src"}
|
| 55 |
+
zip-safe = true
|
| 56 |
+
platforms = ["any"]
|
| 57 |
+
|
| 58 |
+
[tool.setuptools.packages.find]
|
| 59 |
+
where = ["src"]
|
| 60 |
+
|
| 61 |
+
[tool.setuptools.cmdclass]
|
| 62 |
+
sdist = "pkg.mod.CustomSdist"
|
| 63 |
+
|
| 64 |
+
[tool.setuptools.dynamic.version]
|
| 65 |
+
attr = "pkg.__version__.VERSION"
|
| 66 |
+
|
| 67 |
+
[tool.setuptools.dynamic.readme]
|
| 68 |
+
file = ["README.md"]
|
| 69 |
+
content-type = "text/markdown"
|
| 70 |
+
|
| 71 |
+
[tool.setuptools.package-data]
|
| 72 |
+
"*" = ["*.txt"]
|
| 73 |
+
|
| 74 |
+
[tool.setuptools.data-files]
|
| 75 |
+
"data" = ["_files/*.txt"]
|
| 76 |
+
|
| 77 |
+
[tool.distutils.sdist]
|
| 78 |
+
formats = "gztar"
|
| 79 |
+
|
| 80 |
+
[tool.distutils.bdist_wheel]
|
| 81 |
+
universal = true
|
| 82 |
+
"""
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def create_example(path, pkg_root):
|
| 86 |
+
files = {
|
| 87 |
+
"pyproject.toml": EXAMPLE,
|
| 88 |
+
"README.md": "hello world",
|
| 89 |
+
"_files": {
|
| 90 |
+
"file.txt": "",
|
| 91 |
+
},
|
| 92 |
+
}
|
| 93 |
+
packages = {
|
| 94 |
+
"pkg": {
|
| 95 |
+
"__init__.py": "",
|
| 96 |
+
"mod.py": "class CustomSdist: pass",
|
| 97 |
+
"__version__.py": "VERSION = (3, 10)",
|
| 98 |
+
"__main__.py": "def exec(): print('hello')",
|
| 99 |
+
},
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
assert pkg_root # Meta-test: cannot be empty string.
|
| 103 |
+
|
| 104 |
+
if pkg_root == ".":
|
| 105 |
+
files = {**files, **packages}
|
| 106 |
+
# skip other files: flat-layout will raise error for multi-package dist
|
| 107 |
+
else:
|
| 108 |
+
# Use this opportunity to ensure namespaces are discovered
|
| 109 |
+
files[pkg_root] = {**packages, "other": {"nested": {"__init__.py": ""}}}
|
| 110 |
+
|
| 111 |
+
jaraco.path.build(files, prefix=path)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def verify_example(config, path, pkg_root):
|
| 115 |
+
pyproject = path / "pyproject.toml"
|
| 116 |
+
pyproject.write_text(tomli_w.dumps(config), encoding="utf-8")
|
| 117 |
+
expanded = expand_configuration(config, path)
|
| 118 |
+
expanded_project = expanded["project"]
|
| 119 |
+
assert read_configuration(pyproject, expand=True) == expanded
|
| 120 |
+
assert expanded_project["version"] == "3.10"
|
| 121 |
+
assert expanded_project["readme"]["text"] == "hello world"
|
| 122 |
+
assert "packages" in expanded["tool"]["setuptools"]
|
| 123 |
+
if pkg_root == ".":
|
| 124 |
+
# Auto-discovery will raise error for multi-package dist
|
| 125 |
+
assert set(expanded["tool"]["setuptools"]["packages"]) == {"pkg"}
|
| 126 |
+
else:
|
| 127 |
+
assert set(expanded["tool"]["setuptools"]["packages"]) == {
|
| 128 |
+
"pkg",
|
| 129 |
+
"other",
|
| 130 |
+
"other.nested",
|
| 131 |
+
}
|
| 132 |
+
assert expanded["tool"]["setuptools"]["include-package-data"] is True
|
| 133 |
+
assert "" in expanded["tool"]["setuptools"]["package-data"]
|
| 134 |
+
assert "*" not in expanded["tool"]["setuptools"]["package-data"]
|
| 135 |
+
assert expanded["tool"]["setuptools"]["data-files"] == [
|
| 136 |
+
("data", ["_files/file.txt"])
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def test_read_configuration(tmp_path):
|
| 141 |
+
create_example(tmp_path, "src")
|
| 142 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 143 |
+
|
| 144 |
+
config = read_configuration(pyproject, expand=False)
|
| 145 |
+
assert config["project"].get("version") is None
|
| 146 |
+
assert config["project"].get("readme") is None
|
| 147 |
+
|
| 148 |
+
verify_example(config, tmp_path, "src")
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@pytest.mark.parametrize(
|
| 152 |
+
("pkg_root", "opts"),
|
| 153 |
+
[
|
| 154 |
+
(".", {}),
|
| 155 |
+
("src", {}),
|
| 156 |
+
("lib", {"packages": {"find": {"where": ["lib"]}}}),
|
| 157 |
+
],
|
| 158 |
+
)
|
| 159 |
+
def test_discovered_package_dir_with_attr_directive_in_config(tmp_path, pkg_root, opts):
|
| 160 |
+
create_example(tmp_path, pkg_root)
|
| 161 |
+
|
| 162 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 163 |
+
|
| 164 |
+
config = read_configuration(pyproject, expand=False)
|
| 165 |
+
assert config["project"].get("version") is None
|
| 166 |
+
assert config["project"].get("readme") is None
|
| 167 |
+
config["tool"]["setuptools"].pop("packages", None)
|
| 168 |
+
config["tool"]["setuptools"].pop("package-dir", None)
|
| 169 |
+
|
| 170 |
+
config["tool"]["setuptools"].update(opts)
|
| 171 |
+
verify_example(config, tmp_path, pkg_root)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
ENTRY_POINTS = {
|
| 175 |
+
"console_scripts": {"a": "mod.a:func"},
|
| 176 |
+
"gui_scripts": {"b": "mod.b:func"},
|
| 177 |
+
"other": {"c": "mod.c:func [extra]"},
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class TestEntryPoints:
|
| 182 |
+
def write_entry_points(self, tmp_path):
|
| 183 |
+
entry_points = ConfigParser()
|
| 184 |
+
entry_points.read_dict(ENTRY_POINTS)
|
| 185 |
+
with open(tmp_path / "entry-points.txt", "w", encoding="utf-8") as f:
|
| 186 |
+
entry_points.write(f)
|
| 187 |
+
|
| 188 |
+
def pyproject(self, dynamic=None):
|
| 189 |
+
project = {"dynamic": dynamic or ["scripts", "gui-scripts", "entry-points"]}
|
| 190 |
+
tool = {"dynamic": {"entry-points": {"file": "entry-points.txt"}}}
|
| 191 |
+
return {"project": project, "tool": {"setuptools": tool}}
|
| 192 |
+
|
| 193 |
+
def test_all_listed_in_dynamic(self, tmp_path):
|
| 194 |
+
self.write_entry_points(tmp_path)
|
| 195 |
+
expanded = expand_configuration(self.pyproject(), tmp_path)
|
| 196 |
+
expanded_project = expanded["project"]
|
| 197 |
+
assert len(expanded_project["scripts"]) == 1
|
| 198 |
+
assert expanded_project["scripts"]["a"] == "mod.a:func"
|
| 199 |
+
assert len(expanded_project["gui-scripts"]) == 1
|
| 200 |
+
assert expanded_project["gui-scripts"]["b"] == "mod.b:func"
|
| 201 |
+
assert len(expanded_project["entry-points"]) == 1
|
| 202 |
+
assert expanded_project["entry-points"]["other"]["c"] == "mod.c:func [extra]"
|
| 203 |
+
|
| 204 |
+
@pytest.mark.parametrize("missing_dynamic", ("scripts", "gui-scripts"))
|
| 205 |
+
def test_scripts_not_listed_in_dynamic(self, tmp_path, missing_dynamic):
|
| 206 |
+
self.write_entry_points(tmp_path)
|
| 207 |
+
dynamic = {"scripts", "gui-scripts", "entry-points"} - {missing_dynamic}
|
| 208 |
+
|
| 209 |
+
msg = f"defined outside of `pyproject.toml`:.*{missing_dynamic}"
|
| 210 |
+
with pytest.raises(OptionError, match=re.compile(msg, re.S)):
|
| 211 |
+
expand_configuration(self.pyproject(dynamic), tmp_path)
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
class TestClassifiers:
|
| 215 |
+
def test_dynamic(self, tmp_path):
|
| 216 |
+
# Let's create a project example that has dynamic classifiers
|
| 217 |
+
# coming from a txt file.
|
| 218 |
+
create_example(tmp_path, "src")
|
| 219 |
+
classifiers = cleandoc(
|
| 220 |
+
"""
|
| 221 |
+
Framework :: Flask
|
| 222 |
+
Programming Language :: Haskell
|
| 223 |
+
"""
|
| 224 |
+
)
|
| 225 |
+
(tmp_path / "classifiers.txt").write_text(classifiers, encoding="utf-8")
|
| 226 |
+
|
| 227 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 228 |
+
config = read_configuration(pyproject, expand=False)
|
| 229 |
+
dynamic = config["project"]["dynamic"]
|
| 230 |
+
config["project"]["dynamic"] = list({*dynamic, "classifiers"})
|
| 231 |
+
dynamic_config = config["tool"]["setuptools"]["dynamic"]
|
| 232 |
+
dynamic_config["classifiers"] = {"file": "classifiers.txt"}
|
| 233 |
+
|
| 234 |
+
# When the configuration is expanded,
|
| 235 |
+
# each line of the file should be an different classifier.
|
| 236 |
+
validate(config, pyproject)
|
| 237 |
+
expanded = expand_configuration(config, tmp_path)
|
| 238 |
+
|
| 239 |
+
assert set(expanded["project"]["classifiers"]) == {
|
| 240 |
+
"Framework :: Flask",
|
| 241 |
+
"Programming Language :: Haskell",
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
def test_dynamic_without_config(self, tmp_path):
|
| 245 |
+
config = """
|
| 246 |
+
[project]
|
| 247 |
+
name = "myproj"
|
| 248 |
+
version = '42'
|
| 249 |
+
dynamic = ["classifiers"]
|
| 250 |
+
"""
|
| 251 |
+
|
| 252 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 253 |
+
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
| 254 |
+
with pytest.raises(OptionError, match="No configuration .* .classifiers."):
|
| 255 |
+
read_configuration(pyproject)
|
| 256 |
+
|
| 257 |
+
def test_dynamic_readme_from_setup_script_args(self, tmp_path):
|
| 258 |
+
config = """
|
| 259 |
+
[project]
|
| 260 |
+
name = "myproj"
|
| 261 |
+
version = '42'
|
| 262 |
+
dynamic = ["readme"]
|
| 263 |
+
"""
|
| 264 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 265 |
+
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
| 266 |
+
dist = Distribution(attrs={"long_description": "42"})
|
| 267 |
+
# No error should occur because of missing `readme`
|
| 268 |
+
dist = apply_configuration(dist, pyproject)
|
| 269 |
+
assert dist.metadata.long_description == "42"
|
| 270 |
+
|
| 271 |
+
def test_dynamic_without_file(self, tmp_path):
|
| 272 |
+
config = """
|
| 273 |
+
[project]
|
| 274 |
+
name = "myproj"
|
| 275 |
+
version = '42'
|
| 276 |
+
dynamic = ["classifiers"]
|
| 277 |
+
|
| 278 |
+
[tool.setuptools.dynamic]
|
| 279 |
+
classifiers = {file = ["classifiers.txt"]}
|
| 280 |
+
"""
|
| 281 |
+
|
| 282 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 283 |
+
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
| 284 |
+
with pytest.warns(UserWarning, match="File .*classifiers.txt. cannot be found"):
|
| 285 |
+
expanded = read_configuration(pyproject)
|
| 286 |
+
assert "classifiers" not in expanded["project"]
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
@pytest.mark.parametrize(
|
| 290 |
+
"example",
|
| 291 |
+
(
|
| 292 |
+
"""
|
| 293 |
+
[project]
|
| 294 |
+
name = "myproj"
|
| 295 |
+
version = "1.2"
|
| 296 |
+
|
| 297 |
+
[my-tool.that-disrespect.pep518]
|
| 298 |
+
value = 42
|
| 299 |
+
""",
|
| 300 |
+
),
|
| 301 |
+
)
|
| 302 |
+
def test_ignore_unrelated_config(tmp_path, example):
|
| 303 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 304 |
+
pyproject.write_text(cleandoc(example), encoding="utf-8")
|
| 305 |
+
|
| 306 |
+
# Make sure no error is raised due to 3rd party configs in pyproject.toml
|
| 307 |
+
assert read_configuration(pyproject) is not None
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
@pytest.mark.parametrize(
|
| 311 |
+
("example", "error_msg"),
|
| 312 |
+
[
|
| 313 |
+
(
|
| 314 |
+
"""
|
| 315 |
+
[project]
|
| 316 |
+
name = "myproj"
|
| 317 |
+
version = "1.2"
|
| 318 |
+
requires = ['pywin32; platform_system=="Windows"' ]
|
| 319 |
+
""",
|
| 320 |
+
"configuration error: .project. must not contain ..requires.. properties",
|
| 321 |
+
),
|
| 322 |
+
],
|
| 323 |
+
)
|
| 324 |
+
def test_invalid_example(tmp_path, example, error_msg):
|
| 325 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 326 |
+
pyproject.write_text(cleandoc(example), encoding="utf-8")
|
| 327 |
+
|
| 328 |
+
pattern = re.compile(f"invalid pyproject.toml.*{error_msg}.*", re.M | re.S)
|
| 329 |
+
with pytest.raises(ValueError, match=pattern):
|
| 330 |
+
read_configuration(pyproject)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
@pytest.mark.parametrize("config", ("", "[tool.something]\nvalue = 42"))
|
| 334 |
+
def test_empty(tmp_path, config):
|
| 335 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 336 |
+
pyproject.write_text(config, encoding="utf-8")
|
| 337 |
+
|
| 338 |
+
# Make sure no error is raised
|
| 339 |
+
assert read_configuration(pyproject) == {}
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
@pytest.mark.parametrize("config", ("[project]\nname = 'myproj'\nversion='42'\n",))
|
| 343 |
+
def test_include_package_data_by_default(tmp_path, config):
|
| 344 |
+
"""Builds with ``pyproject.toml`` should consider ``include-package-data=True`` as
|
| 345 |
+
default.
|
| 346 |
+
"""
|
| 347 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 348 |
+
pyproject.write_text(config, encoding="utf-8")
|
| 349 |
+
|
| 350 |
+
config = read_configuration(pyproject)
|
| 351 |
+
assert config["tool"]["setuptools"]["include-package-data"] is True
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def test_include_package_data_in_setuppy(tmp_path):
|
| 355 |
+
"""Builds with ``pyproject.toml`` should consider ``include_package_data`` set in
|
| 356 |
+
``setup.py``.
|
| 357 |
+
|
| 358 |
+
See https://github.com/pypa/setuptools/issues/3197#issuecomment-1079023889
|
| 359 |
+
"""
|
| 360 |
+
files = {
|
| 361 |
+
"pyproject.toml": "[project]\nname = 'myproj'\nversion='42'\n",
|
| 362 |
+
"setup.py": "__import__('setuptools').setup(include_package_data=False)",
|
| 363 |
+
}
|
| 364 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 365 |
+
|
| 366 |
+
with Path(tmp_path):
|
| 367 |
+
dist = distutils.core.run_setup("setup.py", {}, stop_after="config")
|
| 368 |
+
|
| 369 |
+
assert dist.get_name() == "myproj"
|
| 370 |
+
assert dist.get_version() == "42"
|
| 371 |
+
assert dist.include_package_data is False
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def test_warn_tools_typo(tmp_path):
|
| 375 |
+
"""Test that the common ``tools.setuptools`` typo in ``pyproject.toml`` issues a warning
|
| 376 |
+
|
| 377 |
+
See https://github.com/pypa/setuptools/issues/4150
|
| 378 |
+
"""
|
| 379 |
+
config = """
|
| 380 |
+
[build-system]
|
| 381 |
+
requires = ["setuptools"]
|
| 382 |
+
build-backend = "setuptools.build_meta"
|
| 383 |
+
|
| 384 |
+
[project]
|
| 385 |
+
name = "myproj"
|
| 386 |
+
version = '42'
|
| 387 |
+
|
| 388 |
+
[tools.setuptools]
|
| 389 |
+
packages = ["package"]
|
| 390 |
+
"""
|
| 391 |
+
|
| 392 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 393 |
+
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
| 394 |
+
|
| 395 |
+
with pytest.warns(_ToolsTypoInMetadata):
|
| 396 |
+
read_configuration(pyproject)
|
venv/lib/python3.11/site-packages/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from inspect import cleandoc
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
from jaraco import path
|
| 5 |
+
|
| 6 |
+
from setuptools.config.pyprojecttoml import apply_configuration
|
| 7 |
+
from setuptools.dist import Distribution
|
| 8 |
+
from setuptools.warnings import SetuptoolsWarning
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_dynamic_dependencies(tmp_path):
|
| 12 |
+
files = {
|
| 13 |
+
"requirements.txt": "six\n # comment\n",
|
| 14 |
+
"pyproject.toml": cleandoc(
|
| 15 |
+
"""
|
| 16 |
+
[project]
|
| 17 |
+
name = "myproj"
|
| 18 |
+
version = "1.0"
|
| 19 |
+
dynamic = ["dependencies"]
|
| 20 |
+
|
| 21 |
+
[build-system]
|
| 22 |
+
requires = ["setuptools", "wheel"]
|
| 23 |
+
build-backend = "setuptools.build_meta"
|
| 24 |
+
|
| 25 |
+
[tool.setuptools.dynamic.dependencies]
|
| 26 |
+
file = ["requirements.txt"]
|
| 27 |
+
"""
|
| 28 |
+
),
|
| 29 |
+
}
|
| 30 |
+
path.build(files, prefix=tmp_path)
|
| 31 |
+
dist = Distribution()
|
| 32 |
+
dist = apply_configuration(dist, tmp_path / "pyproject.toml")
|
| 33 |
+
assert dist.install_requires == ["six"]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_dynamic_optional_dependencies(tmp_path):
|
| 37 |
+
files = {
|
| 38 |
+
"requirements-docs.txt": "sphinx\n # comment\n",
|
| 39 |
+
"pyproject.toml": cleandoc(
|
| 40 |
+
"""
|
| 41 |
+
[project]
|
| 42 |
+
name = "myproj"
|
| 43 |
+
version = "1.0"
|
| 44 |
+
dynamic = ["optional-dependencies"]
|
| 45 |
+
|
| 46 |
+
[tool.setuptools.dynamic.optional-dependencies.docs]
|
| 47 |
+
file = ["requirements-docs.txt"]
|
| 48 |
+
|
| 49 |
+
[build-system]
|
| 50 |
+
requires = ["setuptools", "wheel"]
|
| 51 |
+
build-backend = "setuptools.build_meta"
|
| 52 |
+
"""
|
| 53 |
+
),
|
| 54 |
+
}
|
| 55 |
+
path.build(files, prefix=tmp_path)
|
| 56 |
+
dist = Distribution()
|
| 57 |
+
dist = apply_configuration(dist, tmp_path / "pyproject.toml")
|
| 58 |
+
assert dist.extras_require == {"docs": ["sphinx"]}
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def test_mixed_dynamic_optional_dependencies(tmp_path):
|
| 62 |
+
"""
|
| 63 |
+
Test that if PEP 621 was loosened to allow mixing of dynamic and static
|
| 64 |
+
configurations in the case of fields containing sub-fields (groups),
|
| 65 |
+
things would work out.
|
| 66 |
+
"""
|
| 67 |
+
files = {
|
| 68 |
+
"requirements-images.txt": "pillow~=42.0\n # comment\n",
|
| 69 |
+
"pyproject.toml": cleandoc(
|
| 70 |
+
"""
|
| 71 |
+
[project]
|
| 72 |
+
name = "myproj"
|
| 73 |
+
version = "1.0"
|
| 74 |
+
dynamic = ["optional-dependencies"]
|
| 75 |
+
|
| 76 |
+
[project.optional-dependencies]
|
| 77 |
+
docs = ["sphinx"]
|
| 78 |
+
|
| 79 |
+
[tool.setuptools.dynamic.optional-dependencies.images]
|
| 80 |
+
file = ["requirements-images.txt"]
|
| 81 |
+
"""
|
| 82 |
+
),
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
path.build(files, prefix=tmp_path)
|
| 86 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 87 |
+
with pytest.raises(ValueError, match="project.optional-dependencies"):
|
| 88 |
+
apply_configuration(Distribution(), pyproject)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def test_mixed_extras_require_optional_dependencies(tmp_path):
|
| 92 |
+
files = {
|
| 93 |
+
"pyproject.toml": cleandoc(
|
| 94 |
+
"""
|
| 95 |
+
[project]
|
| 96 |
+
name = "myproj"
|
| 97 |
+
version = "1.0"
|
| 98 |
+
optional-dependencies.docs = ["sphinx"]
|
| 99 |
+
"""
|
| 100 |
+
),
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
path.build(files, prefix=tmp_path)
|
| 104 |
+
pyproject = tmp_path / "pyproject.toml"
|
| 105 |
+
|
| 106 |
+
with pytest.warns(SetuptoolsWarning, match=".extras_require. overwritten"):
|
| 107 |
+
dist = Distribution({"extras_require": {"hello": ["world"]}})
|
| 108 |
+
dist = apply_configuration(dist, pyproject)
|
| 109 |
+
assert dist.extras_require == {"docs": ["sphinx"]}
|
venv/lib/python3.11/site-packages/setuptools/tests/config/test_setupcfg.py
ADDED
|
@@ -0,0 +1,980 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import configparser
|
| 2 |
+
import contextlib
|
| 3 |
+
import inspect
|
| 4 |
+
import re
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from unittest.mock import Mock, patch
|
| 7 |
+
|
| 8 |
+
import pytest
|
| 9 |
+
from packaging.requirements import InvalidRequirement
|
| 10 |
+
|
| 11 |
+
from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration
|
| 12 |
+
from setuptools.dist import Distribution, _Distribution
|
| 13 |
+
from setuptools.warnings import SetuptoolsDeprecationWarning
|
| 14 |
+
|
| 15 |
+
from ..textwrap import DALS
|
| 16 |
+
|
| 17 |
+
from distutils.errors import DistutilsFileError, DistutilsOptionError
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class ErrConfigHandler(ConfigHandler[Target]):
|
| 21 |
+
"""Erroneous handler. Fails to implement required methods."""
|
| 22 |
+
|
| 23 |
+
section_prefix = "**err**"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def make_package_dir(name, base_dir, ns=False):
|
| 27 |
+
dir_package = base_dir
|
| 28 |
+
for dir_name in name.split('/'):
|
| 29 |
+
dir_package = dir_package.mkdir(dir_name)
|
| 30 |
+
init_file = None
|
| 31 |
+
if not ns:
|
| 32 |
+
init_file = dir_package.join('__init__.py')
|
| 33 |
+
init_file.write('')
|
| 34 |
+
return dir_package, init_file
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def fake_env(
|
| 38 |
+
tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'
|
| 39 |
+
):
|
| 40 |
+
if setup_py is None:
|
| 41 |
+
setup_py = 'from setuptools import setup\nsetup()\n'
|
| 42 |
+
|
| 43 |
+
tmpdir.join('setup.py').write(setup_py)
|
| 44 |
+
config = tmpdir.join('setup.cfg')
|
| 45 |
+
config.write(setup_cfg.encode(encoding), mode='wb')
|
| 46 |
+
|
| 47 |
+
package_dir, init_file = make_package_dir(package_path, tmpdir)
|
| 48 |
+
|
| 49 |
+
init_file.write(
|
| 50 |
+
'VERSION = (1, 2, 3)\n'
|
| 51 |
+
'\n'
|
| 52 |
+
'VERSION_MAJOR = 1'
|
| 53 |
+
'\n'
|
| 54 |
+
'def get_version():\n'
|
| 55 |
+
' return [3, 4, 5, "dev"]\n'
|
| 56 |
+
'\n'
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
return package_dir, config
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@contextlib.contextmanager
|
| 63 |
+
def get_dist(tmpdir, kwargs_initial=None, parse=True):
|
| 64 |
+
kwargs_initial = kwargs_initial or {}
|
| 65 |
+
|
| 66 |
+
with tmpdir.as_cwd():
|
| 67 |
+
dist = Distribution(kwargs_initial)
|
| 68 |
+
dist.script_name = 'setup.py'
|
| 69 |
+
parse and dist.parse_config_files()
|
| 70 |
+
|
| 71 |
+
yield dist
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_parsers_implemented():
|
| 75 |
+
with pytest.raises(NotImplementedError):
|
| 76 |
+
handler = ErrConfigHandler(None, {}, False, Mock())
|
| 77 |
+
handler.parsers
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class TestConfigurationReader:
|
| 81 |
+
def test_basic(self, tmpdir):
|
| 82 |
+
_, config = fake_env(
|
| 83 |
+
tmpdir,
|
| 84 |
+
'[metadata]\n'
|
| 85 |
+
'version = 10.1.1\n'
|
| 86 |
+
'keywords = one, two\n'
|
| 87 |
+
'\n'
|
| 88 |
+
'[options]\n'
|
| 89 |
+
'scripts = bin/a.py, bin/b.py\n',
|
| 90 |
+
)
|
| 91 |
+
config_dict = read_configuration(str(config))
|
| 92 |
+
assert config_dict['metadata']['version'] == '10.1.1'
|
| 93 |
+
assert config_dict['metadata']['keywords'] == ['one', 'two']
|
| 94 |
+
assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py']
|
| 95 |
+
|
| 96 |
+
def test_no_config(self, tmpdir):
|
| 97 |
+
with pytest.raises(DistutilsFileError):
|
| 98 |
+
read_configuration(str(tmpdir.join('setup.cfg')))
|
| 99 |
+
|
| 100 |
+
def test_ignore_errors(self, tmpdir):
|
| 101 |
+
_, config = fake_env(
|
| 102 |
+
tmpdir,
|
| 103 |
+
'[metadata]\nversion = attr: none.VERSION\nkeywords = one, two\n',
|
| 104 |
+
)
|
| 105 |
+
with pytest.raises(ImportError):
|
| 106 |
+
read_configuration(str(config))
|
| 107 |
+
|
| 108 |
+
config_dict = read_configuration(str(config), ignore_option_errors=True)
|
| 109 |
+
|
| 110 |
+
assert config_dict['metadata']['keywords'] == ['one', 'two']
|
| 111 |
+
assert 'version' not in config_dict['metadata']
|
| 112 |
+
|
| 113 |
+
config.remove()
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class TestMetadata:
|
| 117 |
+
def test_basic(self, tmpdir):
|
| 118 |
+
fake_env(
|
| 119 |
+
tmpdir,
|
| 120 |
+
'[metadata]\n'
|
| 121 |
+
'version = 10.1.1\n'
|
| 122 |
+
'description = Some description\n'
|
| 123 |
+
'long_description_content_type = text/something\n'
|
| 124 |
+
'long_description = file: README\n'
|
| 125 |
+
'name = fake_name\n'
|
| 126 |
+
'keywords = one, two\n'
|
| 127 |
+
'provides = package, package.sub\n'
|
| 128 |
+
'license = otherlic\n'
|
| 129 |
+
'download_url = http://test.test.com/test/\n'
|
| 130 |
+
'maintainer_email = test@test.com\n',
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
tmpdir.join('README').write('readme contents\nline2')
|
| 134 |
+
|
| 135 |
+
meta_initial = {
|
| 136 |
+
# This will be used so `otherlic` won't replace it.
|
| 137 |
+
'license': 'BSD 3-Clause License',
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
with get_dist(tmpdir, meta_initial) as dist:
|
| 141 |
+
metadata = dist.metadata
|
| 142 |
+
|
| 143 |
+
assert metadata.version == '10.1.1'
|
| 144 |
+
assert metadata.description == 'Some description'
|
| 145 |
+
assert metadata.long_description_content_type == 'text/something'
|
| 146 |
+
assert metadata.long_description == 'readme contents\nline2'
|
| 147 |
+
assert metadata.provides == ['package', 'package.sub']
|
| 148 |
+
assert metadata.license == 'BSD 3-Clause License'
|
| 149 |
+
assert metadata.name == 'fake_name'
|
| 150 |
+
assert metadata.keywords == ['one', 'two']
|
| 151 |
+
assert metadata.download_url == 'http://test.test.com/test/'
|
| 152 |
+
assert metadata.maintainer_email == 'test@test.com'
|
| 153 |
+
|
| 154 |
+
def test_license_cfg(self, tmpdir):
|
| 155 |
+
fake_env(
|
| 156 |
+
tmpdir,
|
| 157 |
+
DALS(
|
| 158 |
+
"""
|
| 159 |
+
[metadata]
|
| 160 |
+
name=foo
|
| 161 |
+
version=0.0.1
|
| 162 |
+
license=Apache 2.0
|
| 163 |
+
"""
|
| 164 |
+
),
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
with get_dist(tmpdir) as dist:
|
| 168 |
+
metadata = dist.metadata
|
| 169 |
+
|
| 170 |
+
assert metadata.name == "foo"
|
| 171 |
+
assert metadata.version == "0.0.1"
|
| 172 |
+
assert metadata.license == "Apache 2.0"
|
| 173 |
+
|
| 174 |
+
def test_file_mixed(self, tmpdir):
|
| 175 |
+
fake_env(
|
| 176 |
+
tmpdir,
|
| 177 |
+
'[metadata]\nlong_description = file: README.rst, CHANGES.rst\n\n',
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
tmpdir.join('README.rst').write('readme contents\nline2')
|
| 181 |
+
tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff')
|
| 182 |
+
|
| 183 |
+
with get_dist(tmpdir) as dist:
|
| 184 |
+
assert dist.metadata.long_description == (
|
| 185 |
+
'readme contents\nline2\nchangelog contents\nand stuff'
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
def test_file_sandboxed(self, tmpdir):
|
| 189 |
+
tmpdir.ensure("README")
|
| 190 |
+
project = tmpdir.join('depth1', 'depth2')
|
| 191 |
+
project.ensure(dir=True)
|
| 192 |
+
fake_env(project, '[metadata]\nlong_description = file: ../../README\n')
|
| 193 |
+
|
| 194 |
+
with get_dist(project, parse=False) as dist:
|
| 195 |
+
with pytest.raises(DistutilsOptionError):
|
| 196 |
+
dist.parse_config_files() # file: out of sandbox
|
| 197 |
+
|
| 198 |
+
def test_aliases(self, tmpdir):
|
| 199 |
+
fake_env(
|
| 200 |
+
tmpdir,
|
| 201 |
+
'[metadata]\n'
|
| 202 |
+
'author_email = test@test.com\n'
|
| 203 |
+
'home_page = http://test.test.com/test/\n'
|
| 204 |
+
'summary = Short summary\n'
|
| 205 |
+
'platform = a, b\n'
|
| 206 |
+
'classifier =\n'
|
| 207 |
+
' Framework :: Django\n'
|
| 208 |
+
' Programming Language :: Python :: 3.5\n',
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
with get_dist(tmpdir) as dist:
|
| 212 |
+
metadata = dist.metadata
|
| 213 |
+
assert metadata.author_email == 'test@test.com'
|
| 214 |
+
assert metadata.url == 'http://test.test.com/test/'
|
| 215 |
+
assert metadata.description == 'Short summary'
|
| 216 |
+
assert metadata.platforms == ['a', 'b']
|
| 217 |
+
assert metadata.classifiers == [
|
| 218 |
+
'Framework :: Django',
|
| 219 |
+
'Programming Language :: Python :: 3.5',
|
| 220 |
+
]
|
| 221 |
+
|
| 222 |
+
def test_multiline(self, tmpdir):
|
| 223 |
+
fake_env(
|
| 224 |
+
tmpdir,
|
| 225 |
+
'[metadata]\n'
|
| 226 |
+
'name = fake_name\n'
|
| 227 |
+
'keywords =\n'
|
| 228 |
+
' one\n'
|
| 229 |
+
' two\n'
|
| 230 |
+
'classifiers =\n'
|
| 231 |
+
' Framework :: Django\n'
|
| 232 |
+
' Programming Language :: Python :: 3.5\n',
|
| 233 |
+
)
|
| 234 |
+
with get_dist(tmpdir) as dist:
|
| 235 |
+
metadata = dist.metadata
|
| 236 |
+
assert metadata.keywords == ['one', 'two']
|
| 237 |
+
assert metadata.classifiers == [
|
| 238 |
+
'Framework :: Django',
|
| 239 |
+
'Programming Language :: Python :: 3.5',
|
| 240 |
+
]
|
| 241 |
+
|
| 242 |
+
def test_dict(self, tmpdir):
|
| 243 |
+
fake_env(
|
| 244 |
+
tmpdir,
|
| 245 |
+
'[metadata]\n'
|
| 246 |
+
'project_urls =\n'
|
| 247 |
+
' Link One = https://example.com/one/\n'
|
| 248 |
+
' Link Two = https://example.com/two/\n',
|
| 249 |
+
)
|
| 250 |
+
with get_dist(tmpdir) as dist:
|
| 251 |
+
metadata = dist.metadata
|
| 252 |
+
assert metadata.project_urls == {
|
| 253 |
+
'Link One': 'https://example.com/one/',
|
| 254 |
+
'Link Two': 'https://example.com/two/',
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
def test_version(self, tmpdir):
|
| 258 |
+
package_dir, config = fake_env(
|
| 259 |
+
tmpdir, '[metadata]\nversion = attr: fake_package.VERSION\n'
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
sub_a = package_dir.mkdir('subpkg_a')
|
| 263 |
+
sub_a.join('__init__.py').write('')
|
| 264 |
+
sub_a.join('mod.py').write('VERSION = (2016, 11, 26)')
|
| 265 |
+
|
| 266 |
+
sub_b = package_dir.mkdir('subpkg_b')
|
| 267 |
+
sub_b.join('__init__.py').write('')
|
| 268 |
+
sub_b.join('mod.py').write(
|
| 269 |
+
'import third_party_module\nVERSION = (2016, 11, 26)'
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
with get_dist(tmpdir) as dist:
|
| 273 |
+
assert dist.metadata.version == '1.2.3'
|
| 274 |
+
|
| 275 |
+
config.write('[metadata]\nversion = attr: fake_package.get_version\n')
|
| 276 |
+
with get_dist(tmpdir) as dist:
|
| 277 |
+
assert dist.metadata.version == '3.4.5.dev'
|
| 278 |
+
|
| 279 |
+
config.write('[metadata]\nversion = attr: fake_package.VERSION_MAJOR\n')
|
| 280 |
+
with get_dist(tmpdir) as dist:
|
| 281 |
+
assert dist.metadata.version == '1'
|
| 282 |
+
|
| 283 |
+
config.write('[metadata]\nversion = attr: fake_package.subpkg_a.mod.VERSION\n')
|
| 284 |
+
with get_dist(tmpdir) as dist:
|
| 285 |
+
assert dist.metadata.version == '2016.11.26'
|
| 286 |
+
|
| 287 |
+
config.write('[metadata]\nversion = attr: fake_package.subpkg_b.mod.VERSION\n')
|
| 288 |
+
with get_dist(tmpdir) as dist:
|
| 289 |
+
assert dist.metadata.version == '2016.11.26'
|
| 290 |
+
|
| 291 |
+
def test_version_file(self, tmpdir):
|
| 292 |
+
fake_env(tmpdir, '[metadata]\nversion = file: fake_package/version.txt\n')
|
| 293 |
+
tmpdir.join('fake_package', 'version.txt').write('1.2.3\n')
|
| 294 |
+
|
| 295 |
+
with get_dist(tmpdir) as dist:
|
| 296 |
+
assert dist.metadata.version == '1.2.3'
|
| 297 |
+
|
| 298 |
+
tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
|
| 299 |
+
with pytest.raises(DistutilsOptionError):
|
| 300 |
+
with get_dist(tmpdir) as dist:
|
| 301 |
+
dist.metadata.version
|
| 302 |
+
|
| 303 |
+
def test_version_with_package_dir_simple(self, tmpdir):
|
| 304 |
+
fake_env(
|
| 305 |
+
tmpdir,
|
| 306 |
+
'[metadata]\n'
|
| 307 |
+
'version = attr: fake_package_simple.VERSION\n'
|
| 308 |
+
'[options]\n'
|
| 309 |
+
'package_dir =\n'
|
| 310 |
+
' = src\n',
|
| 311 |
+
package_path='src/fake_package_simple',
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
with get_dist(tmpdir) as dist:
|
| 315 |
+
assert dist.metadata.version == '1.2.3'
|
| 316 |
+
|
| 317 |
+
def test_version_with_package_dir_rename(self, tmpdir):
|
| 318 |
+
fake_env(
|
| 319 |
+
tmpdir,
|
| 320 |
+
'[metadata]\n'
|
| 321 |
+
'version = attr: fake_package_rename.VERSION\n'
|
| 322 |
+
'[options]\n'
|
| 323 |
+
'package_dir =\n'
|
| 324 |
+
' fake_package_rename = fake_dir\n',
|
| 325 |
+
package_path='fake_dir',
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
with get_dist(tmpdir) as dist:
|
| 329 |
+
assert dist.metadata.version == '1.2.3'
|
| 330 |
+
|
| 331 |
+
def test_version_with_package_dir_complex(self, tmpdir):
|
| 332 |
+
fake_env(
|
| 333 |
+
tmpdir,
|
| 334 |
+
'[metadata]\n'
|
| 335 |
+
'version = attr: fake_package_complex.VERSION\n'
|
| 336 |
+
'[options]\n'
|
| 337 |
+
'package_dir =\n'
|
| 338 |
+
' fake_package_complex = src/fake_dir\n',
|
| 339 |
+
package_path='src/fake_dir',
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
with get_dist(tmpdir) as dist:
|
| 343 |
+
assert dist.metadata.version == '1.2.3'
|
| 344 |
+
|
| 345 |
+
def test_unknown_meta_item(self, tmpdir):
|
| 346 |
+
fake_env(tmpdir, '[metadata]\nname = fake_name\nunknown = some\n')
|
| 347 |
+
with get_dist(tmpdir, parse=False) as dist:
|
| 348 |
+
dist.parse_config_files() # Skip unknown.
|
| 349 |
+
|
| 350 |
+
def test_usupported_section(self, tmpdir):
|
| 351 |
+
fake_env(tmpdir, '[metadata.some]\nkey = val\n')
|
| 352 |
+
with get_dist(tmpdir, parse=False) as dist:
|
| 353 |
+
with pytest.raises(DistutilsOptionError):
|
| 354 |
+
dist.parse_config_files()
|
| 355 |
+
|
| 356 |
+
def test_classifiers(self, tmpdir):
|
| 357 |
+
expected = set([
|
| 358 |
+
'Framework :: Django',
|
| 359 |
+
'Programming Language :: Python :: 3',
|
| 360 |
+
'Programming Language :: Python :: 3.5',
|
| 361 |
+
])
|
| 362 |
+
|
| 363 |
+
# From file.
|
| 364 |
+
_, config = fake_env(tmpdir, '[metadata]\nclassifiers = file: classifiers\n')
|
| 365 |
+
|
| 366 |
+
tmpdir.join('classifiers').write(
|
| 367 |
+
'Framework :: Django\n'
|
| 368 |
+
'Programming Language :: Python :: 3\n'
|
| 369 |
+
'Programming Language :: Python :: 3.5\n'
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
with get_dist(tmpdir) as dist:
|
| 373 |
+
assert set(dist.metadata.classifiers) == expected
|
| 374 |
+
|
| 375 |
+
# From list notation
|
| 376 |
+
config.write(
|
| 377 |
+
'[metadata]\n'
|
| 378 |
+
'classifiers =\n'
|
| 379 |
+
' Framework :: Django\n'
|
| 380 |
+
' Programming Language :: Python :: 3\n'
|
| 381 |
+
' Programming Language :: Python :: 3.5\n'
|
| 382 |
+
)
|
| 383 |
+
with get_dist(tmpdir) as dist:
|
| 384 |
+
assert set(dist.metadata.classifiers) == expected
|
| 385 |
+
|
| 386 |
+
def test_interpolation(self, tmpdir):
|
| 387 |
+
fake_env(tmpdir, '[metadata]\ndescription = %(message)s\n')
|
| 388 |
+
with pytest.raises(configparser.InterpolationMissingOptionError):
|
| 389 |
+
with get_dist(tmpdir):
|
| 390 |
+
pass
|
| 391 |
+
|
| 392 |
+
def test_non_ascii_1(self, tmpdir):
|
| 393 |
+
fake_env(tmpdir, '[metadata]\ndescription = éàïôñ\n', encoding='utf-8')
|
| 394 |
+
with get_dist(tmpdir):
|
| 395 |
+
pass
|
| 396 |
+
|
| 397 |
+
def test_non_ascii_3(self, tmpdir):
|
| 398 |
+
fake_env(tmpdir, '\n# -*- coding: invalid\n')
|
| 399 |
+
with get_dist(tmpdir):
|
| 400 |
+
pass
|
| 401 |
+
|
| 402 |
+
def test_non_ascii_4(self, tmpdir):
|
| 403 |
+
fake_env(
|
| 404 |
+
tmpdir,
|
| 405 |
+
'# -*- coding: utf-8\n[metadata]\ndescription = éàïôñ\n',
|
| 406 |
+
encoding='utf-8',
|
| 407 |
+
)
|
| 408 |
+
with get_dist(tmpdir) as dist:
|
| 409 |
+
assert dist.metadata.description == 'éàïôñ'
|
| 410 |
+
|
| 411 |
+
def test_not_utf8(self, tmpdir):
|
| 412 |
+
"""
|
| 413 |
+
Config files encoded not in UTF-8 will fail
|
| 414 |
+
"""
|
| 415 |
+
fake_env(
|
| 416 |
+
tmpdir,
|
| 417 |
+
'# vim: set fileencoding=iso-8859-15 :\n[metadata]\ndescription = éàïôñ\n',
|
| 418 |
+
encoding='iso-8859-15',
|
| 419 |
+
)
|
| 420 |
+
with pytest.raises(UnicodeDecodeError):
|
| 421 |
+
with get_dist(tmpdir):
|
| 422 |
+
pass
|
| 423 |
+
|
| 424 |
+
@pytest.mark.parametrize(
|
| 425 |
+
("error_msg", "config", "invalid"),
|
| 426 |
+
[
|
| 427 |
+
(
|
| 428 |
+
"Invalid dash-separated key 'author-email' in 'metadata' (setup.cfg)",
|
| 429 |
+
DALS(
|
| 430 |
+
"""
|
| 431 |
+
[metadata]
|
| 432 |
+
author-email = test@test.com
|
| 433 |
+
maintainer_email = foo@foo.com
|
| 434 |
+
"""
|
| 435 |
+
),
|
| 436 |
+
{"author-email": "test@test.com"},
|
| 437 |
+
),
|
| 438 |
+
(
|
| 439 |
+
"Invalid uppercase key 'Name' in 'metadata' (setup.cfg)",
|
| 440 |
+
DALS(
|
| 441 |
+
"""
|
| 442 |
+
[metadata]
|
| 443 |
+
Name = foo
|
| 444 |
+
description = Some description
|
| 445 |
+
"""
|
| 446 |
+
),
|
| 447 |
+
{"Name": "foo"},
|
| 448 |
+
),
|
| 449 |
+
],
|
| 450 |
+
)
|
| 451 |
+
def test_invalid_options_previously_deprecated(
|
| 452 |
+
self, tmpdir, error_msg, config, invalid
|
| 453 |
+
):
|
| 454 |
+
# This test and related methods can be removed when no longer needed.
|
| 455 |
+
# Deprecation postponed due to push-back from the community in
|
| 456 |
+
# https://github.com/pypa/setuptools/issues/4910
|
| 457 |
+
fake_env(tmpdir, config)
|
| 458 |
+
with pytest.warns(SetuptoolsDeprecationWarning, match=re.escape(error_msg)):
|
| 459 |
+
dist = get_dist(tmpdir).__enter__()
|
| 460 |
+
|
| 461 |
+
tmpdir.join('setup.cfg').remove()
|
| 462 |
+
|
| 463 |
+
for field, value in invalid.items():
|
| 464 |
+
attr = field.replace("-", "_").lower()
|
| 465 |
+
assert getattr(dist.metadata, attr) == value
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
class TestOptions:
|
| 469 |
+
def test_basic(self, tmpdir):
|
| 470 |
+
fake_env(
|
| 471 |
+
tmpdir,
|
| 472 |
+
'[options]\n'
|
| 473 |
+
'zip_safe = True\n'
|
| 474 |
+
'include_package_data = yes\n'
|
| 475 |
+
'package_dir = b=c, =src\n'
|
| 476 |
+
'packages = pack_a, pack_b.subpack\n'
|
| 477 |
+
'namespace_packages = pack1, pack2\n'
|
| 478 |
+
'scripts = bin/one.py, bin/two.py\n'
|
| 479 |
+
'eager_resources = bin/one.py, bin/two.py\n'
|
| 480 |
+
'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n'
|
| 481 |
+
'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
|
| 482 |
+
'dependency_links = http://some.com/here/1, '
|
| 483 |
+
'http://some.com/there/2\n'
|
| 484 |
+
'python_requires = >=1.0, !=2.8\n'
|
| 485 |
+
'py_modules = module1, module2\n',
|
| 486 |
+
)
|
| 487 |
+
deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")
|
| 488 |
+
with deprec, get_dist(tmpdir) as dist:
|
| 489 |
+
assert dist.zip_safe
|
| 490 |
+
assert dist.include_package_data
|
| 491 |
+
assert dist.package_dir == {'': 'src', 'b': 'c'}
|
| 492 |
+
assert dist.packages == ['pack_a', 'pack_b.subpack']
|
| 493 |
+
assert dist.namespace_packages == ['pack1', 'pack2']
|
| 494 |
+
assert dist.scripts == ['bin/one.py', 'bin/two.py']
|
| 495 |
+
assert dist.dependency_links == ([
|
| 496 |
+
'http://some.com/here/1',
|
| 497 |
+
'http://some.com/there/2',
|
| 498 |
+
])
|
| 499 |
+
assert dist.install_requires == ([
|
| 500 |
+
'docutils>=0.3',
|
| 501 |
+
'pack==1.1,==1.3',
|
| 502 |
+
'hey',
|
| 503 |
+
])
|
| 504 |
+
assert dist.setup_requires == ([
|
| 505 |
+
'docutils>=0.3',
|
| 506 |
+
'spack ==1.1, ==1.3',
|
| 507 |
+
'there',
|
| 508 |
+
])
|
| 509 |
+
assert dist.python_requires == '>=1.0, !=2.8'
|
| 510 |
+
assert dist.py_modules == ['module1', 'module2']
|
| 511 |
+
|
| 512 |
+
def test_multiline(self, tmpdir):
|
| 513 |
+
fake_env(
|
| 514 |
+
tmpdir,
|
| 515 |
+
'[options]\n'
|
| 516 |
+
'package_dir = \n'
|
| 517 |
+
' b=c\n'
|
| 518 |
+
' =src\n'
|
| 519 |
+
'packages = \n'
|
| 520 |
+
' pack_a\n'
|
| 521 |
+
' pack_b.subpack\n'
|
| 522 |
+
'namespace_packages = \n'
|
| 523 |
+
' pack1\n'
|
| 524 |
+
' pack2\n'
|
| 525 |
+
'scripts = \n'
|
| 526 |
+
' bin/one.py\n'
|
| 527 |
+
' bin/two.py\n'
|
| 528 |
+
'eager_resources = \n'
|
| 529 |
+
' bin/one.py\n'
|
| 530 |
+
' bin/two.py\n'
|
| 531 |
+
'install_requires = \n'
|
| 532 |
+
' docutils>=0.3\n'
|
| 533 |
+
' pack ==1.1, ==1.3\n'
|
| 534 |
+
' hey\n'
|
| 535 |
+
'setup_requires = \n'
|
| 536 |
+
' docutils>=0.3\n'
|
| 537 |
+
' spack ==1.1, ==1.3\n'
|
| 538 |
+
' there\n'
|
| 539 |
+
'dependency_links = \n'
|
| 540 |
+
' http://some.com/here/1\n'
|
| 541 |
+
' http://some.com/there/2\n',
|
| 542 |
+
)
|
| 543 |
+
deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")
|
| 544 |
+
with deprec, get_dist(tmpdir) as dist:
|
| 545 |
+
assert dist.package_dir == {'': 'src', 'b': 'c'}
|
| 546 |
+
assert dist.packages == ['pack_a', 'pack_b.subpack']
|
| 547 |
+
assert dist.namespace_packages == ['pack1', 'pack2']
|
| 548 |
+
assert dist.scripts == ['bin/one.py', 'bin/two.py']
|
| 549 |
+
assert dist.dependency_links == ([
|
| 550 |
+
'http://some.com/here/1',
|
| 551 |
+
'http://some.com/there/2',
|
| 552 |
+
])
|
| 553 |
+
assert dist.install_requires == ([
|
| 554 |
+
'docutils>=0.3',
|
| 555 |
+
'pack==1.1,==1.3',
|
| 556 |
+
'hey',
|
| 557 |
+
])
|
| 558 |
+
assert dist.setup_requires == ([
|
| 559 |
+
'docutils>=0.3',
|
| 560 |
+
'spack ==1.1, ==1.3',
|
| 561 |
+
'there',
|
| 562 |
+
])
|
| 563 |
+
|
| 564 |
+
def test_package_dir_fail(self, tmpdir):
|
| 565 |
+
fake_env(tmpdir, '[options]\npackage_dir = a b\n')
|
| 566 |
+
with get_dist(tmpdir, parse=False) as dist:
|
| 567 |
+
with pytest.raises(DistutilsOptionError):
|
| 568 |
+
dist.parse_config_files()
|
| 569 |
+
|
| 570 |
+
def test_package_data(self, tmpdir):
|
| 571 |
+
fake_env(
|
| 572 |
+
tmpdir,
|
| 573 |
+
'[options.package_data]\n'
|
| 574 |
+
'* = *.txt, *.rst\n'
|
| 575 |
+
'hello = *.msg\n'
|
| 576 |
+
'\n'
|
| 577 |
+
'[options.exclude_package_data]\n'
|
| 578 |
+
'* = fake1.txt, fake2.txt\n'
|
| 579 |
+
'hello = *.dat\n',
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
with get_dist(tmpdir) as dist:
|
| 583 |
+
assert dist.package_data == {
|
| 584 |
+
'': ['*.txt', '*.rst'],
|
| 585 |
+
'hello': ['*.msg'],
|
| 586 |
+
}
|
| 587 |
+
assert dist.exclude_package_data == {
|
| 588 |
+
'': ['fake1.txt', 'fake2.txt'],
|
| 589 |
+
'hello': ['*.dat'],
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
def test_packages(self, tmpdir):
|
| 593 |
+
fake_env(tmpdir, '[options]\npackages = find:\n')
|
| 594 |
+
|
| 595 |
+
with get_dist(tmpdir) as dist:
|
| 596 |
+
assert dist.packages == ['fake_package']
|
| 597 |
+
|
| 598 |
+
def test_find_directive(self, tmpdir):
|
| 599 |
+
dir_package, config = fake_env(tmpdir, '[options]\npackages = find:\n')
|
| 600 |
+
|
| 601 |
+
make_package_dir('sub_one', dir_package)
|
| 602 |
+
make_package_dir('sub_two', dir_package)
|
| 603 |
+
|
| 604 |
+
with get_dist(tmpdir) as dist:
|
| 605 |
+
assert set(dist.packages) == set([
|
| 606 |
+
'fake_package',
|
| 607 |
+
'fake_package.sub_two',
|
| 608 |
+
'fake_package.sub_one',
|
| 609 |
+
])
|
| 610 |
+
|
| 611 |
+
config.write(
|
| 612 |
+
'[options]\n'
|
| 613 |
+
'packages = find:\n'
|
| 614 |
+
'\n'
|
| 615 |
+
'[options.packages.find]\n'
|
| 616 |
+
'where = .\n'
|
| 617 |
+
'include =\n'
|
| 618 |
+
' fake_package.sub_one\n'
|
| 619 |
+
' two\n'
|
| 620 |
+
)
|
| 621 |
+
with get_dist(tmpdir) as dist:
|
| 622 |
+
assert dist.packages == ['fake_package.sub_one']
|
| 623 |
+
|
| 624 |
+
config.write(
|
| 625 |
+
'[options]\n'
|
| 626 |
+
'packages = find:\n'
|
| 627 |
+
'\n'
|
| 628 |
+
'[options.packages.find]\n'
|
| 629 |
+
'exclude =\n'
|
| 630 |
+
' fake_package.sub_one\n'
|
| 631 |
+
)
|
| 632 |
+
with get_dist(tmpdir) as dist:
|
| 633 |
+
assert set(dist.packages) == set(['fake_package', 'fake_package.sub_two'])
|
| 634 |
+
|
| 635 |
+
def test_find_namespace_directive(self, tmpdir):
|
| 636 |
+
dir_package, config = fake_env(
|
| 637 |
+
tmpdir, '[options]\npackages = find_namespace:\n'
|
| 638 |
+
)
|
| 639 |
+
|
| 640 |
+
make_package_dir('sub_one', dir_package)
|
| 641 |
+
make_package_dir('sub_two', dir_package, ns=True)
|
| 642 |
+
|
| 643 |
+
with get_dist(tmpdir) as dist:
|
| 644 |
+
assert set(dist.packages) == {
|
| 645 |
+
'fake_package',
|
| 646 |
+
'fake_package.sub_two',
|
| 647 |
+
'fake_package.sub_one',
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
config.write(
|
| 651 |
+
'[options]\n'
|
| 652 |
+
'packages = find_namespace:\n'
|
| 653 |
+
'\n'
|
| 654 |
+
'[options.packages.find]\n'
|
| 655 |
+
'where = .\n'
|
| 656 |
+
'include =\n'
|
| 657 |
+
' fake_package.sub_one\n'
|
| 658 |
+
' two\n'
|
| 659 |
+
)
|
| 660 |
+
with get_dist(tmpdir) as dist:
|
| 661 |
+
assert dist.packages == ['fake_package.sub_one']
|
| 662 |
+
|
| 663 |
+
config.write(
|
| 664 |
+
'[options]\n'
|
| 665 |
+
'packages = find_namespace:\n'
|
| 666 |
+
'\n'
|
| 667 |
+
'[options.packages.find]\n'
|
| 668 |
+
'exclude =\n'
|
| 669 |
+
' fake_package.sub_one\n'
|
| 670 |
+
)
|
| 671 |
+
with get_dist(tmpdir) as dist:
|
| 672 |
+
assert set(dist.packages) == {'fake_package', 'fake_package.sub_two'}
|
| 673 |
+
|
| 674 |
+
def test_extras_require(self, tmpdir):
|
| 675 |
+
fake_env(
|
| 676 |
+
tmpdir,
|
| 677 |
+
'[options.extras_require]\n'
|
| 678 |
+
'pdf = ReportLab>=1.2; RXP\n'
|
| 679 |
+
'rest = \n'
|
| 680 |
+
' docutils>=0.3\n'
|
| 681 |
+
' pack ==1.1, ==1.3\n',
|
| 682 |
+
)
|
| 683 |
+
|
| 684 |
+
with get_dist(tmpdir) as dist:
|
| 685 |
+
assert dist.extras_require == {
|
| 686 |
+
'pdf': ['ReportLab>=1.2', 'RXP'],
|
| 687 |
+
'rest': ['docutils>=0.3', 'pack==1.1,==1.3'],
|
| 688 |
+
}
|
| 689 |
+
assert set(dist.metadata.provides_extras) == {'pdf', 'rest'}
|
| 690 |
+
|
| 691 |
+
@pytest.mark.parametrize(
|
| 692 |
+
"config",
|
| 693 |
+
[
|
| 694 |
+
"[options.extras_require]\nfoo = bar;python_version<'3'",
|
| 695 |
+
"[options.extras_require]\nfoo = bar;os_name=='linux'",
|
| 696 |
+
"[options.extras_require]\nfoo = bar;python_version<'3'\n",
|
| 697 |
+
"[options.extras_require]\nfoo = bar;os_name=='linux'\n",
|
| 698 |
+
"[options]\ninstall_requires = bar;python_version<'3'",
|
| 699 |
+
"[options]\ninstall_requires = bar;os_name=='linux'",
|
| 700 |
+
"[options]\ninstall_requires = bar;python_version<'3'\n",
|
| 701 |
+
"[options]\ninstall_requires = bar;os_name=='linux'\n",
|
| 702 |
+
],
|
| 703 |
+
)
|
| 704 |
+
def test_raises_accidental_env_marker_misconfig(self, config, tmpdir):
|
| 705 |
+
fake_env(tmpdir, config)
|
| 706 |
+
match = (
|
| 707 |
+
r"One of the parsed requirements in `(install_requires|extras_require.+)` "
|
| 708 |
+
"looks like a valid environment marker.*"
|
| 709 |
+
)
|
| 710 |
+
with pytest.raises(InvalidRequirement, match=match):
|
| 711 |
+
with get_dist(tmpdir) as _:
|
| 712 |
+
pass
|
| 713 |
+
|
| 714 |
+
@pytest.mark.parametrize(
|
| 715 |
+
"config",
|
| 716 |
+
[
|
| 717 |
+
"[options.extras_require]\nfoo = bar;python_version<3",
|
| 718 |
+
"[options.extras_require]\nfoo = bar;python_version<3\n",
|
| 719 |
+
"[options]\ninstall_requires = bar;python_version<3",
|
| 720 |
+
"[options]\ninstall_requires = bar;python_version<3\n",
|
| 721 |
+
],
|
| 722 |
+
)
|
| 723 |
+
def test_warn_accidental_env_marker_misconfig(self, config, tmpdir):
|
| 724 |
+
fake_env(tmpdir, config)
|
| 725 |
+
match = (
|
| 726 |
+
r"One of the parsed requirements in `(install_requires|extras_require.+)` "
|
| 727 |
+
"looks like a valid environment marker.*"
|
| 728 |
+
)
|
| 729 |
+
with pytest.warns(SetuptoolsDeprecationWarning, match=match):
|
| 730 |
+
with get_dist(tmpdir) as _:
|
| 731 |
+
pass
|
| 732 |
+
|
| 733 |
+
@pytest.mark.parametrize(
|
| 734 |
+
"config",
|
| 735 |
+
[
|
| 736 |
+
"[options.extras_require]\nfoo =\n bar;python_version<'3'",
|
| 737 |
+
"[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy",
|
| 738 |
+
"[options.extras_require]\nfoo =\n bar;python_version<'3'\n",
|
| 739 |
+
"[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy\n",
|
| 740 |
+
"[options.extras_require]\nfoo =\n bar\n python_version<3\n",
|
| 741 |
+
"[options]\ninstall_requires =\n bar;python_version<'3'",
|
| 742 |
+
"[options]\ninstall_requires = bar;baz\nboo = xxx;yyy",
|
| 743 |
+
"[options]\ninstall_requires =\n bar;python_version<'3'\n",
|
| 744 |
+
"[options]\ninstall_requires = bar;baz\nboo = xxx;yyy\n",
|
| 745 |
+
"[options]\ninstall_requires =\n bar\n python_version<3\n",
|
| 746 |
+
],
|
| 747 |
+
)
|
| 748 |
+
@pytest.mark.filterwarnings("error::setuptools.SetuptoolsDeprecationWarning")
|
| 749 |
+
def test_nowarn_accidental_env_marker_misconfig(self, config, tmpdir, recwarn):
|
| 750 |
+
fake_env(tmpdir, config)
|
| 751 |
+
num_warnings = len(recwarn)
|
| 752 |
+
with get_dist(tmpdir) as _:
|
| 753 |
+
pass
|
| 754 |
+
# The examples are valid, no warnings shown
|
| 755 |
+
assert len(recwarn) == num_warnings
|
| 756 |
+
|
| 757 |
+
def test_dash_preserved_extras_require(self, tmpdir):
|
| 758 |
+
fake_env(tmpdir, '[options.extras_require]\nfoo-a = foo\nfoo_b = test\n')
|
| 759 |
+
|
| 760 |
+
with get_dist(tmpdir) as dist:
|
| 761 |
+
assert dist.extras_require == {'foo-a': ['foo'], 'foo_b': ['test']}
|
| 762 |
+
|
| 763 |
+
def test_entry_points(self, tmpdir):
|
| 764 |
+
_, config = fake_env(
|
| 765 |
+
tmpdir,
|
| 766 |
+
'[options.entry_points]\n'
|
| 767 |
+
'group1 = point1 = pack.module:func, '
|
| 768 |
+
'.point2 = pack.module2:func_rest [rest]\n'
|
| 769 |
+
'group2 = point3 = pack.module:func2\n',
|
| 770 |
+
)
|
| 771 |
+
|
| 772 |
+
with get_dist(tmpdir) as dist:
|
| 773 |
+
assert dist.entry_points == {
|
| 774 |
+
'group1': [
|
| 775 |
+
'point1 = pack.module:func',
|
| 776 |
+
'.point2 = pack.module2:func_rest [rest]',
|
| 777 |
+
],
|
| 778 |
+
'group2': ['point3 = pack.module:func2'],
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
expected = (
|
| 782 |
+
'[blogtool.parsers]\n'
|
| 783 |
+
'.rst = some.nested.module:SomeClass.some_classmethod[reST]\n'
|
| 784 |
+
)
|
| 785 |
+
|
| 786 |
+
tmpdir.join('entry_points').write(expected)
|
| 787 |
+
|
| 788 |
+
# From file.
|
| 789 |
+
config.write('[options]\nentry_points = file: entry_points\n')
|
| 790 |
+
|
| 791 |
+
with get_dist(tmpdir) as dist:
|
| 792 |
+
assert dist.entry_points == expected
|
| 793 |
+
|
| 794 |
+
def test_case_sensitive_entry_points(self, tmpdir):
|
| 795 |
+
fake_env(
|
| 796 |
+
tmpdir,
|
| 797 |
+
'[options.entry_points]\n'
|
| 798 |
+
'GROUP1 = point1 = pack.module:func, '
|
| 799 |
+
'.point2 = pack.module2:func_rest [rest]\n'
|
| 800 |
+
'group2 = point3 = pack.module:func2\n',
|
| 801 |
+
)
|
| 802 |
+
|
| 803 |
+
with get_dist(tmpdir) as dist:
|
| 804 |
+
assert dist.entry_points == {
|
| 805 |
+
'GROUP1': [
|
| 806 |
+
'point1 = pack.module:func',
|
| 807 |
+
'.point2 = pack.module2:func_rest [rest]',
|
| 808 |
+
],
|
| 809 |
+
'group2': ['point3 = pack.module:func2'],
|
| 810 |
+
}
|
| 811 |
+
|
| 812 |
+
def test_data_files(self, tmpdir):
|
| 813 |
+
fake_env(
|
| 814 |
+
tmpdir,
|
| 815 |
+
'[options.data_files]\n'
|
| 816 |
+
'cfg =\n'
|
| 817 |
+
' a/b.conf\n'
|
| 818 |
+
' c/d.conf\n'
|
| 819 |
+
'data = e/f.dat, g/h.dat\n',
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
with get_dist(tmpdir) as dist:
|
| 823 |
+
expected = [
|
| 824 |
+
('cfg', ['a/b.conf', 'c/d.conf']),
|
| 825 |
+
('data', ['e/f.dat', 'g/h.dat']),
|
| 826 |
+
]
|
| 827 |
+
assert sorted(dist.data_files) == sorted(expected)
|
| 828 |
+
|
| 829 |
+
def test_data_files_globby(self, tmpdir):
|
| 830 |
+
fake_env(
|
| 831 |
+
tmpdir,
|
| 832 |
+
'[options.data_files]\n'
|
| 833 |
+
'cfg =\n'
|
| 834 |
+
' a/b.conf\n'
|
| 835 |
+
' c/d.conf\n'
|
| 836 |
+
'data = *.dat\n'
|
| 837 |
+
'icons = \n'
|
| 838 |
+
' *.ico\n'
|
| 839 |
+
'audio = \n'
|
| 840 |
+
' *.wav\n'
|
| 841 |
+
' sounds.db\n',
|
| 842 |
+
)
|
| 843 |
+
|
| 844 |
+
# Create dummy files for glob()'s sake:
|
| 845 |
+
tmpdir.join('a.dat').write('')
|
| 846 |
+
tmpdir.join('b.dat').write('')
|
| 847 |
+
tmpdir.join('c.dat').write('')
|
| 848 |
+
tmpdir.join('a.ico').write('')
|
| 849 |
+
tmpdir.join('b.ico').write('')
|
| 850 |
+
tmpdir.join('c.ico').write('')
|
| 851 |
+
tmpdir.join('beep.wav').write('')
|
| 852 |
+
tmpdir.join('boop.wav').write('')
|
| 853 |
+
tmpdir.join('sounds.db').write('')
|
| 854 |
+
|
| 855 |
+
with get_dist(tmpdir) as dist:
|
| 856 |
+
expected = [
|
| 857 |
+
('cfg', ['a/b.conf', 'c/d.conf']),
|
| 858 |
+
('data', ['a.dat', 'b.dat', 'c.dat']),
|
| 859 |
+
('icons', ['a.ico', 'b.ico', 'c.ico']),
|
| 860 |
+
('audio', ['beep.wav', 'boop.wav', 'sounds.db']),
|
| 861 |
+
]
|
| 862 |
+
assert sorted(dist.data_files) == sorted(expected)
|
| 863 |
+
|
| 864 |
+
def test_python_requires_simple(self, tmpdir):
|
| 865 |
+
fake_env(
|
| 866 |
+
tmpdir,
|
| 867 |
+
DALS(
|
| 868 |
+
"""
|
| 869 |
+
[options]
|
| 870 |
+
python_requires=>=2.7
|
| 871 |
+
"""
|
| 872 |
+
),
|
| 873 |
+
)
|
| 874 |
+
with get_dist(tmpdir) as dist:
|
| 875 |
+
dist.parse_config_files()
|
| 876 |
+
|
| 877 |
+
def test_python_requires_compound(self, tmpdir):
|
| 878 |
+
fake_env(
|
| 879 |
+
tmpdir,
|
| 880 |
+
DALS(
|
| 881 |
+
"""
|
| 882 |
+
[options]
|
| 883 |
+
python_requires=>=2.7,!=3.0.*
|
| 884 |
+
"""
|
| 885 |
+
),
|
| 886 |
+
)
|
| 887 |
+
with get_dist(tmpdir) as dist:
|
| 888 |
+
dist.parse_config_files()
|
| 889 |
+
|
| 890 |
+
def test_python_requires_invalid(self, tmpdir):
|
| 891 |
+
fake_env(
|
| 892 |
+
tmpdir,
|
| 893 |
+
DALS(
|
| 894 |
+
"""
|
| 895 |
+
[options]
|
| 896 |
+
python_requires=invalid
|
| 897 |
+
"""
|
| 898 |
+
),
|
| 899 |
+
)
|
| 900 |
+
with pytest.raises(Exception):
|
| 901 |
+
with get_dist(tmpdir) as dist:
|
| 902 |
+
dist.parse_config_files()
|
| 903 |
+
|
| 904 |
+
def test_cmdclass(self, tmpdir):
|
| 905 |
+
module_path = Path(tmpdir, "src/custom_build.py") # auto discovery for src
|
| 906 |
+
module_path.parent.mkdir(parents=True, exist_ok=True)
|
| 907 |
+
module_path.write_text(
|
| 908 |
+
"from distutils.core import Command\nclass CustomCmd(Command): pass\n",
|
| 909 |
+
encoding="utf-8",
|
| 910 |
+
)
|
| 911 |
+
|
| 912 |
+
setup_cfg = """
|
| 913 |
+
[options]
|
| 914 |
+
cmdclass =
|
| 915 |
+
customcmd = custom_build.CustomCmd
|
| 916 |
+
"""
|
| 917 |
+
fake_env(tmpdir, inspect.cleandoc(setup_cfg))
|
| 918 |
+
|
| 919 |
+
with get_dist(tmpdir) as dist:
|
| 920 |
+
cmdclass = dist.cmdclass['customcmd']
|
| 921 |
+
assert cmdclass.__name__ == "CustomCmd"
|
| 922 |
+
assert cmdclass.__module__ == "custom_build"
|
| 923 |
+
assert module_path.samefile(inspect.getfile(cmdclass))
|
| 924 |
+
|
| 925 |
+
def test_requirements_file(self, tmpdir):
|
| 926 |
+
fake_env(
|
| 927 |
+
tmpdir,
|
| 928 |
+
DALS(
|
| 929 |
+
"""
|
| 930 |
+
[options]
|
| 931 |
+
install_requires = file:requirements.txt
|
| 932 |
+
[options.extras_require]
|
| 933 |
+
colors = file:requirements-extra.txt
|
| 934 |
+
"""
|
| 935 |
+
),
|
| 936 |
+
)
|
| 937 |
+
|
| 938 |
+
tmpdir.join('requirements.txt').write('\ndocutils>=0.3\n\n')
|
| 939 |
+
tmpdir.join('requirements-extra.txt').write('colorama')
|
| 940 |
+
|
| 941 |
+
with get_dist(tmpdir) as dist:
|
| 942 |
+
assert dist.install_requires == ['docutils>=0.3']
|
| 943 |
+
assert dist.extras_require == {'colors': ['colorama']}
|
| 944 |
+
|
| 945 |
+
|
| 946 |
+
saved_dist_init = _Distribution.__init__
|
| 947 |
+
|
| 948 |
+
|
| 949 |
+
class TestExternalSetters:
|
| 950 |
+
# During creation of the setuptools Distribution() object, we call
|
| 951 |
+
# the init of the parent distutils Distribution object via
|
| 952 |
+
# _Distribution.__init__ ().
|
| 953 |
+
#
|
| 954 |
+
# It's possible distutils calls out to various keyword
|
| 955 |
+
# implementations (i.e. distutils.setup_keywords entry points)
|
| 956 |
+
# that may set a range of variables.
|
| 957 |
+
#
|
| 958 |
+
# This wraps distutil's Distribution.__init__ and simulates
|
| 959 |
+
# pbr or something else setting these values.
|
| 960 |
+
def _fake_distribution_init(self, dist, attrs):
|
| 961 |
+
saved_dist_init(dist, attrs)
|
| 962 |
+
# see self._DISTUTILS_UNSUPPORTED_METADATA
|
| 963 |
+
dist.metadata.long_description_content_type = 'text/something'
|
| 964 |
+
# Test overwrite setup() args
|
| 965 |
+
dist.metadata.project_urls = {
|
| 966 |
+
'Link One': 'https://example.com/one/',
|
| 967 |
+
'Link Two': 'https://example.com/two/',
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
@patch.object(_Distribution, '__init__', autospec=True)
|
| 971 |
+
def test_external_setters(self, mock_parent_init, tmpdir):
|
| 972 |
+
mock_parent_init.side_effect = self._fake_distribution_init
|
| 973 |
+
|
| 974 |
+
dist = Distribution(attrs={'project_urls': {'will_be': 'ignored'}})
|
| 975 |
+
|
| 976 |
+
assert dist.metadata.long_description_content_type == 'text/something'
|
| 977 |
+
assert dist.metadata.project_urls == {
|
| 978 |
+
'Link One': 'https://example.com/one/',
|
| 979 |
+
'Link Two': 'https://example.com/two/',
|
| 980 |
+
}
|
venv/lib/python3.11/site-packages/setuptools/tests/contexts.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import io
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import site
|
| 6 |
+
import sys
|
| 7 |
+
import tempfile
|
| 8 |
+
|
| 9 |
+
from filelock import FileLock
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@contextlib.contextmanager
|
| 13 |
+
def tempdir(cd=lambda dir: None, **kwargs):
|
| 14 |
+
temp_dir = tempfile.mkdtemp(**kwargs)
|
| 15 |
+
orig_dir = os.getcwd()
|
| 16 |
+
try:
|
| 17 |
+
cd(temp_dir)
|
| 18 |
+
yield temp_dir
|
| 19 |
+
finally:
|
| 20 |
+
cd(orig_dir)
|
| 21 |
+
shutil.rmtree(temp_dir)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@contextlib.contextmanager
|
| 25 |
+
def environment(**replacements):
|
| 26 |
+
"""
|
| 27 |
+
In a context, patch the environment with replacements. Pass None values
|
| 28 |
+
to clear the values.
|
| 29 |
+
"""
|
| 30 |
+
saved = dict((key, os.environ[key]) for key in replacements if key in os.environ)
|
| 31 |
+
|
| 32 |
+
# remove values that are null
|
| 33 |
+
remove = (key for (key, value) in replacements.items() if value is None)
|
| 34 |
+
for key in list(remove):
|
| 35 |
+
os.environ.pop(key, None)
|
| 36 |
+
replacements.pop(key)
|
| 37 |
+
|
| 38 |
+
os.environ.update(replacements)
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
yield saved
|
| 42 |
+
finally:
|
| 43 |
+
for key in replacements:
|
| 44 |
+
os.environ.pop(key, None)
|
| 45 |
+
os.environ.update(saved)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@contextlib.contextmanager
|
| 49 |
+
def quiet():
|
| 50 |
+
"""
|
| 51 |
+
Redirect stdout/stderr to StringIO objects to prevent console output from
|
| 52 |
+
distutils commands.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
old_stdout = sys.stdout
|
| 56 |
+
old_stderr = sys.stderr
|
| 57 |
+
new_stdout = sys.stdout = io.StringIO()
|
| 58 |
+
new_stderr = sys.stderr = io.StringIO()
|
| 59 |
+
try:
|
| 60 |
+
yield new_stdout, new_stderr
|
| 61 |
+
finally:
|
| 62 |
+
new_stdout.seek(0)
|
| 63 |
+
new_stderr.seek(0)
|
| 64 |
+
sys.stdout = old_stdout
|
| 65 |
+
sys.stderr = old_stderr
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@contextlib.contextmanager
|
| 69 |
+
def save_user_site_setting():
|
| 70 |
+
saved = site.ENABLE_USER_SITE
|
| 71 |
+
try:
|
| 72 |
+
yield saved
|
| 73 |
+
finally:
|
| 74 |
+
site.ENABLE_USER_SITE = saved
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@contextlib.contextmanager
|
| 78 |
+
def save_pkg_resources_state():
|
| 79 |
+
import pkg_resources
|
| 80 |
+
|
| 81 |
+
pr_state = pkg_resources.__getstate__()
|
| 82 |
+
# also save sys.path
|
| 83 |
+
sys_path = sys.path[:]
|
| 84 |
+
try:
|
| 85 |
+
yield pr_state, sys_path
|
| 86 |
+
finally:
|
| 87 |
+
sys.path[:] = sys_path
|
| 88 |
+
pkg_resources.__setstate__(pr_state)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
@contextlib.contextmanager
|
| 92 |
+
def suppress_exceptions(*excs):
|
| 93 |
+
try:
|
| 94 |
+
yield
|
| 95 |
+
except excs:
|
| 96 |
+
pass
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def multiproc(request):
|
| 100 |
+
"""
|
| 101 |
+
Return True if running under xdist and multiple
|
| 102 |
+
workers are used.
|
| 103 |
+
"""
|
| 104 |
+
try:
|
| 105 |
+
worker_id = request.getfixturevalue('worker_id')
|
| 106 |
+
except Exception:
|
| 107 |
+
return False
|
| 108 |
+
return worker_id != 'master'
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@contextlib.contextmanager
|
| 112 |
+
def session_locked_tmp_dir(request, tmp_path_factory, name):
|
| 113 |
+
"""Uses a file lock to guarantee only one worker can access a temp dir"""
|
| 114 |
+
# get the temp directory shared by all workers
|
| 115 |
+
base = tmp_path_factory.getbasetemp()
|
| 116 |
+
shared_dir = base.parent if multiproc(request) else base
|
| 117 |
+
|
| 118 |
+
locked_dir = shared_dir / name
|
| 119 |
+
with FileLock(locked_dir.with_suffix(".lock")):
|
| 120 |
+
# ^-- prevent multiple workers to access the directory at once
|
| 121 |
+
locked_dir.mkdir(exist_ok=True, parents=True)
|
| 122 |
+
yield locked_dir
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@contextlib.contextmanager
|
| 126 |
+
def save_paths():
|
| 127 |
+
"""Make sure ``sys.path``, ``sys.meta_path`` and ``sys.path_hooks`` are preserved"""
|
| 128 |
+
prev = sys.path[:], sys.meta_path[:], sys.path_hooks[:]
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
yield
|
| 132 |
+
finally:
|
| 133 |
+
sys.path, sys.meta_path, sys.path_hooks = prev
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
@contextlib.contextmanager
|
| 137 |
+
def save_sys_modules():
|
| 138 |
+
"""Make sure initial ``sys.modules`` is preserved"""
|
| 139 |
+
prev_modules = sys.modules
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
sys.modules = sys.modules.copy()
|
| 143 |
+
yield
|
| 144 |
+
finally:
|
| 145 |
+
sys.modules = prev_modules
|
venv/lib/python3.11/site-packages/setuptools/tests/environment.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
import unicodedata
|
| 5 |
+
from subprocess import PIPE as _PIPE, Popen as _Popen
|
| 6 |
+
|
| 7 |
+
import jaraco.envs
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class VirtualEnv(jaraco.envs.VirtualEnv):
|
| 11 |
+
name = '.env'
|
| 12 |
+
# Some version of PyPy will import distutils on startup, implicitly
|
| 13 |
+
# importing setuptools, and thus leading to BackendInvalid errors
|
| 14 |
+
# when upgrading Setuptools. Bypass this behavior by avoiding the
|
| 15 |
+
# early availability and need to upgrade.
|
| 16 |
+
create_opts = ['--no-setuptools']
|
| 17 |
+
|
| 18 |
+
def run(self, cmd, *args, **kwargs):
|
| 19 |
+
cmd = [self.exe(cmd[0])] + cmd[1:]
|
| 20 |
+
kwargs = {"cwd": self.root, "encoding": "utf-8", **kwargs} # Allow overriding
|
| 21 |
+
# In some environments (eg. downstream distro packaging), where:
|
| 22 |
+
# - tox isn't used to run tests and
|
| 23 |
+
# - PYTHONPATH is set to point to a specific setuptools codebase and
|
| 24 |
+
# - no custom env is explicitly set by a test
|
| 25 |
+
# PYTHONPATH will leak into the spawned processes.
|
| 26 |
+
# In that case tests look for module in the wrong place (on PYTHONPATH).
|
| 27 |
+
# Unless the test sets its own special env, pass a copy of the existing
|
| 28 |
+
# environment with removed PYTHONPATH to the subprocesses.
|
| 29 |
+
if "env" not in kwargs:
|
| 30 |
+
env = dict(os.environ)
|
| 31 |
+
if "PYTHONPATH" in env:
|
| 32 |
+
del env["PYTHONPATH"]
|
| 33 |
+
kwargs["env"] = env
|
| 34 |
+
return subprocess.check_output(cmd, *args, **kwargs)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _which_dirs(cmd):
|
| 38 |
+
result = set()
|
| 39 |
+
for path in os.environ.get('PATH', '').split(os.pathsep):
|
| 40 |
+
filename = os.path.join(path, cmd)
|
| 41 |
+
if os.access(filename, os.X_OK):
|
| 42 |
+
result.add(path)
|
| 43 |
+
return result
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def run_setup_py(cmd, pypath=None, path=None, data_stream=0, env=None):
|
| 47 |
+
"""
|
| 48 |
+
Execution command for tests, separate from those used by the
|
| 49 |
+
code directly to prevent accidental behavior issues
|
| 50 |
+
"""
|
| 51 |
+
if env is None:
|
| 52 |
+
env = dict()
|
| 53 |
+
for envname in os.environ:
|
| 54 |
+
env[envname] = os.environ[envname]
|
| 55 |
+
|
| 56 |
+
# override the python path if needed
|
| 57 |
+
if pypath is not None:
|
| 58 |
+
env["PYTHONPATH"] = pypath
|
| 59 |
+
|
| 60 |
+
# override the execution path if needed
|
| 61 |
+
if path is not None:
|
| 62 |
+
env["PATH"] = path
|
| 63 |
+
if not env.get("PATH", ""):
|
| 64 |
+
env["PATH"] = _which_dirs("tar").union(_which_dirs("gzip"))
|
| 65 |
+
env["PATH"] = os.pathsep.join(env["PATH"])
|
| 66 |
+
|
| 67 |
+
cmd = [sys.executable, "setup.py"] + list(cmd)
|
| 68 |
+
|
| 69 |
+
# https://bugs.python.org/issue8557
|
| 70 |
+
shell = sys.platform == 'win32'
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
proc = _Popen(
|
| 74 |
+
cmd,
|
| 75 |
+
stdout=_PIPE,
|
| 76 |
+
stderr=_PIPE,
|
| 77 |
+
shell=shell,
|
| 78 |
+
env=env,
|
| 79 |
+
encoding="utf-8",
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
if isinstance(data_stream, tuple):
|
| 83 |
+
data_stream = slice(*data_stream)
|
| 84 |
+
data = proc.communicate()[data_stream]
|
| 85 |
+
except OSError:
|
| 86 |
+
return 1, ''
|
| 87 |
+
|
| 88 |
+
# decode the console string if needed
|
| 89 |
+
if hasattr(data, "decode"):
|
| 90 |
+
# use the default encoding
|
| 91 |
+
data = data.decode()
|
| 92 |
+
data = unicodedata.normalize('NFC', data)
|
| 93 |
+
|
| 94 |
+
# communicate calls wait()
|
| 95 |
+
return proc.returncode, data
|
venv/lib/python3.11/site-packages/setuptools/tests/fixtures.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import os
|
| 3 |
+
import subprocess
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import path
|
| 8 |
+
import pytest
|
| 9 |
+
|
| 10 |
+
from . import contexts, environment
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@pytest.fixture
|
| 14 |
+
def user_override(monkeypatch):
|
| 15 |
+
"""
|
| 16 |
+
Override site.USER_BASE and site.USER_SITE with temporary directories in
|
| 17 |
+
a context.
|
| 18 |
+
"""
|
| 19 |
+
with contexts.tempdir() as user_base:
|
| 20 |
+
monkeypatch.setattr('site.USER_BASE', user_base)
|
| 21 |
+
with contexts.tempdir() as user_site:
|
| 22 |
+
monkeypatch.setattr('site.USER_SITE', user_site)
|
| 23 |
+
with contexts.save_user_site_setting():
|
| 24 |
+
yield
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@pytest.fixture
|
| 28 |
+
def tmpdir_cwd(tmpdir):
|
| 29 |
+
with tmpdir.as_cwd() as orig:
|
| 30 |
+
yield orig
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@pytest.fixture(autouse=True, scope="session")
|
| 34 |
+
def workaround_xdist_376(request):
|
| 35 |
+
"""
|
| 36 |
+
Workaround pytest-dev/pytest-xdist#376
|
| 37 |
+
|
| 38 |
+
``pytest-xdist`` tends to inject '' into ``sys.path``,
|
| 39 |
+
which may break certain isolation expectations.
|
| 40 |
+
Remove the entry so the import
|
| 41 |
+
machinery behaves the same irrespective of xdist.
|
| 42 |
+
"""
|
| 43 |
+
if not request.config.pluginmanager.has_plugin('xdist'):
|
| 44 |
+
return
|
| 45 |
+
|
| 46 |
+
with contextlib.suppress(ValueError):
|
| 47 |
+
sys.path.remove('')
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@pytest.fixture
|
| 51 |
+
def sample_project(tmp_path):
|
| 52 |
+
"""
|
| 53 |
+
Clone the 'sampleproject' and return a path to it.
|
| 54 |
+
"""
|
| 55 |
+
cmd = ['git', 'clone', 'https://github.com/pypa/sampleproject']
|
| 56 |
+
try:
|
| 57 |
+
subprocess.check_call(cmd, cwd=str(tmp_path))
|
| 58 |
+
except Exception:
|
| 59 |
+
pytest.skip("Unable to clone sampleproject")
|
| 60 |
+
return tmp_path / 'sampleproject'
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# sdist and wheel artifacts should be stable across a round of tests
|
| 64 |
+
# so we can build them once per session and use the files as "readonly"
|
| 65 |
+
|
| 66 |
+
# In the case of setuptools, building the wheel without sdist may cause
|
| 67 |
+
# it to contain the `build` directory, and therefore create situations with
|
| 68 |
+
# `setuptools/build/lib/build/lib/...`. To avoid that, build both artifacts at once.
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _build_distributions(tmp_path_factory, request):
|
| 72 |
+
with contexts.session_locked_tmp_dir(
|
| 73 |
+
request, tmp_path_factory, "dist_build"
|
| 74 |
+
) as tmp: # pragma: no cover
|
| 75 |
+
sdist = next(tmp.glob("*.tar.gz"), None)
|
| 76 |
+
wheel = next(tmp.glob("*.whl"), None)
|
| 77 |
+
if sdist and wheel:
|
| 78 |
+
return (sdist, wheel)
|
| 79 |
+
|
| 80 |
+
# Sanity check: should not create recursive setuptools/build/lib/build/lib/...
|
| 81 |
+
assert not Path(request.config.rootdir, "build/lib/build").exists()
|
| 82 |
+
|
| 83 |
+
subprocess.check_output([
|
| 84 |
+
sys.executable,
|
| 85 |
+
"-m",
|
| 86 |
+
"build",
|
| 87 |
+
"--outdir",
|
| 88 |
+
str(tmp),
|
| 89 |
+
str(request.config.rootdir),
|
| 90 |
+
])
|
| 91 |
+
|
| 92 |
+
# Sanity check: should not create recursive setuptools/build/lib/build/lib/...
|
| 93 |
+
assert not Path(request.config.rootdir, "build/lib/build").exists()
|
| 94 |
+
|
| 95 |
+
return next(tmp.glob("*.tar.gz")), next(tmp.glob("*.whl"))
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
@pytest.fixture(scope="session")
|
| 99 |
+
def setuptools_sdist(tmp_path_factory, request):
|
| 100 |
+
prebuilt = os.getenv("PRE_BUILT_SETUPTOOLS_SDIST")
|
| 101 |
+
if prebuilt and os.path.exists(prebuilt): # pragma: no cover
|
| 102 |
+
return Path(prebuilt).resolve()
|
| 103 |
+
|
| 104 |
+
sdist, _ = _build_distributions(tmp_path_factory, request)
|
| 105 |
+
return sdist
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
@pytest.fixture(scope="session")
|
| 109 |
+
def setuptools_wheel(tmp_path_factory, request):
|
| 110 |
+
prebuilt = os.getenv("PRE_BUILT_SETUPTOOLS_WHEEL")
|
| 111 |
+
if prebuilt and os.path.exists(prebuilt): # pragma: no cover
|
| 112 |
+
return Path(prebuilt).resolve()
|
| 113 |
+
|
| 114 |
+
_, wheel = _build_distributions(tmp_path_factory, request)
|
| 115 |
+
return wheel
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@pytest.fixture
|
| 119 |
+
def venv(tmp_path, setuptools_wheel):
|
| 120 |
+
"""Virtual env with the version of setuptools under test installed"""
|
| 121 |
+
env = environment.VirtualEnv()
|
| 122 |
+
env.root = path.Path(tmp_path / 'venv')
|
| 123 |
+
env.create_opts = ['--no-setuptools', '--wheel=bundle']
|
| 124 |
+
# TODO: Use `--no-wheel` when setuptools implements its own bdist_wheel
|
| 125 |
+
env.req = str(setuptools_wheel)
|
| 126 |
+
# In some environments (eg. downstream distro packaging),
|
| 127 |
+
# where tox isn't used to run tests and PYTHONPATH is set to point to
|
| 128 |
+
# a specific setuptools codebase, PYTHONPATH will leak into the spawned
|
| 129 |
+
# processes.
|
| 130 |
+
# env.create() should install the just created setuptools
|
| 131 |
+
# wheel, but it doesn't if it finds another existing matching setuptools
|
| 132 |
+
# installation present on PYTHONPATH:
|
| 133 |
+
# `setuptools is already installed with the same version as the provided
|
| 134 |
+
# wheel. Use --force-reinstall to force an installation of the wheel.`
|
| 135 |
+
# This prevents leaking PYTHONPATH to the created environment.
|
| 136 |
+
with contexts.environment(PYTHONPATH=None):
|
| 137 |
+
return env.create()
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@pytest.fixture
|
| 141 |
+
def venv_without_setuptools(tmp_path):
|
| 142 |
+
"""Virtual env without any version of setuptools installed"""
|
| 143 |
+
env = environment.VirtualEnv()
|
| 144 |
+
env.root = path.Path(tmp_path / 'venv_without_setuptools')
|
| 145 |
+
env.create_opts = ['--no-setuptools', '--no-wheel']
|
| 146 |
+
env.ensure_env()
|
| 147 |
+
return env
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@pytest.fixture
|
| 151 |
+
def bare_venv(tmp_path):
|
| 152 |
+
"""Virtual env without any common packages installed"""
|
| 153 |
+
env = environment.VirtualEnv()
|
| 154 |
+
env.root = path.Path(tmp_path / 'bare_venv')
|
| 155 |
+
env.create_opts = ['--no-setuptools', '--no-pip', '--no-wheel', '--no-seed']
|
| 156 |
+
env.ensure_env()
|
| 157 |
+
return env
|
venv/lib/python3.11/site-packages/setuptools/tests/indexes/test_links_priority/external.html
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<html><body>
|
| 2 |
+
<a href="/foobar-0.1.tar.gz#md5=1__bad_md5___">bad old link</a>
|
| 3 |
+
</body></html>
|
venv/lib/python3.11/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<html><body>
|
| 2 |
+
<a href="/foobar-0.1.tar.gz#md5=0_correct_md5">foobar-0.1.tar.gz</a><br/>
|
| 3 |
+
<a href="../../external.html" rel="homepage">external homepage</a><br/>
|
| 4 |
+
</body></html>
|
venv/lib/python3.11/site-packages/setuptools/tests/integration/__init__.py
ADDED
|
File without changes
|
venv/lib/python3.11/site-packages/setuptools/tests/integration/helpers.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reusable functions and classes for different types of integration tests.
|
| 2 |
+
|
| 3 |
+
For example ``Archive`` can be used to check the contents of distribution built
|
| 4 |
+
with setuptools, and ``run`` will always try to be as verbose as possible to
|
| 5 |
+
facilitate debugging.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import subprocess
|
| 10 |
+
import tarfile
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from zipfile import ZipFile
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def run(cmd, env=None):
|
| 16 |
+
r = subprocess.run(
|
| 17 |
+
cmd,
|
| 18 |
+
capture_output=True,
|
| 19 |
+
text=True,
|
| 20 |
+
encoding="utf-8",
|
| 21 |
+
env={**os.environ, **(env or {})},
|
| 22 |
+
# ^-- allow overwriting instead of discarding the current env
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
out = r.stdout + "\n" + r.stderr
|
| 26 |
+
# pytest omits stdout/err by default, if the test fails they help debugging
|
| 27 |
+
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
| 28 |
+
print(f"Command: {cmd}\nreturn code: {r.returncode}\n\n{out}")
|
| 29 |
+
|
| 30 |
+
if r.returncode == 0:
|
| 31 |
+
return out
|
| 32 |
+
raise subprocess.CalledProcessError(r.returncode, cmd, r.stdout, r.stderr)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Archive:
|
| 36 |
+
"""Compatibility layer for ZipFile/Info and TarFile/Info"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, filename):
|
| 39 |
+
self._filename = filename
|
| 40 |
+
if filename.endswith("tar.gz"):
|
| 41 |
+
self._obj = tarfile.open(filename, "r:gz")
|
| 42 |
+
elif filename.endswith("zip"):
|
| 43 |
+
self._obj = ZipFile(filename)
|
| 44 |
+
else:
|
| 45 |
+
raise ValueError(f"{filename} doesn't seem to be a zip or tar.gz")
|
| 46 |
+
|
| 47 |
+
def __iter__(self):
|
| 48 |
+
if hasattr(self._obj, "infolist"):
|
| 49 |
+
return iter(self._obj.infolist())
|
| 50 |
+
return iter(self._obj)
|
| 51 |
+
|
| 52 |
+
def get_name(self, zip_or_tar_info):
|
| 53 |
+
if hasattr(zip_or_tar_info, "filename"):
|
| 54 |
+
return zip_or_tar_info.filename
|
| 55 |
+
return zip_or_tar_info.name
|
| 56 |
+
|
| 57 |
+
def get_content(self, zip_or_tar_info):
|
| 58 |
+
if hasattr(self._obj, "extractfile"):
|
| 59 |
+
content = self._obj.extractfile(zip_or_tar_info)
|
| 60 |
+
if content is None:
|
| 61 |
+
msg = f"Invalid {zip_or_tar_info.name} in {self._filename}"
|
| 62 |
+
raise ValueError(msg)
|
| 63 |
+
return str(content.read(), "utf-8")
|
| 64 |
+
return str(self._obj.read(zip_or_tar_info), "utf-8")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_sdist_members(sdist_path):
|
| 68 |
+
with tarfile.open(sdist_path, "r:gz") as tar:
|
| 69 |
+
files = [Path(f) for f in tar.getnames()]
|
| 70 |
+
# remove root folder
|
| 71 |
+
relative_files = ("/".join(f.parts[1:]) for f in files)
|
| 72 |
+
return {f for f in relative_files if f}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def get_wheel_members(wheel_path):
|
| 76 |
+
with ZipFile(wheel_path) as zipfile:
|
| 77 |
+
return set(zipfile.namelist())
|
venv/lib/python3.11/site-packages/setuptools/tests/integration/test_pip_install_sdist.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# https://github.com/python/mypy/issues/16936
|
| 2 |
+
# mypy: disable-error-code="has-type"
|
| 3 |
+
"""Integration tests for setuptools that focus on building packages via pip.
|
| 4 |
+
|
| 5 |
+
The idea behind these tests is not to exhaustively check all the possible
|
| 6 |
+
combinations of packages, operating systems, supporting libraries, etc, but
|
| 7 |
+
rather check a limited number of popular packages and how they interact with
|
| 8 |
+
the exposed public API. This way if any change in API is introduced, we hope to
|
| 9 |
+
identify backward compatibility problems before publishing a release.
|
| 10 |
+
|
| 11 |
+
The number of tested packages is purposefully kept small, to minimise duration
|
| 12 |
+
and the associated maintenance cost (changes in the way these packages define
|
| 13 |
+
their build process may require changes in the tests).
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import os
|
| 18 |
+
import shutil
|
| 19 |
+
import sys
|
| 20 |
+
from enum import Enum
|
| 21 |
+
from glob import glob
|
| 22 |
+
from hashlib import md5
|
| 23 |
+
from urllib.request import urlopen
|
| 24 |
+
|
| 25 |
+
import pytest
|
| 26 |
+
from packaging.requirements import Requirement
|
| 27 |
+
|
| 28 |
+
from .helpers import Archive, run
|
| 29 |
+
|
| 30 |
+
pytestmark = pytest.mark.integration
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
(LATEST,) = Enum("v", "LATEST") # type: ignore[misc] # https://github.com/python/mypy/issues/16936
|
| 34 |
+
"""Default version to be checked"""
|
| 35 |
+
# There are positive and negative aspects of checking the latest version of the
|
| 36 |
+
# packages.
|
| 37 |
+
# The main positive aspect is that the latest version might have already
|
| 38 |
+
# removed the use of APIs deprecated in previous releases of setuptools.
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# Packages to be tested:
|
| 42 |
+
# (Please notice the test environment cannot support EVERY library required for
|
| 43 |
+
# compiling binary extensions. In Ubuntu/Debian nomenclature, we only assume
|
| 44 |
+
# that `build-essential`, `gfortran` and `libopenblas-dev` are installed,
|
| 45 |
+
# due to their relevance to the numerical/scientific programming ecosystem)
|
| 46 |
+
EXAMPLES = [
|
| 47 |
+
("pip", LATEST), # just in case...
|
| 48 |
+
("pytest", LATEST), # uses setuptools_scm
|
| 49 |
+
("mypy", LATEST), # custom build_py + ext_modules
|
| 50 |
+
# --- Popular packages: https://hugovk.github.io/top-pypi-packages/ ---
|
| 51 |
+
("botocore", LATEST),
|
| 52 |
+
("kiwisolver", LATEST), # build_ext
|
| 53 |
+
("brotli", LATEST), # not in the list but used by urllib3
|
| 54 |
+
("pyyaml", LATEST), # cython + custom build_ext + custom distclass
|
| 55 |
+
("charset-normalizer", LATEST), # uses mypyc, used by aiohttp
|
| 56 |
+
("protobuf", LATEST),
|
| 57 |
+
# ("requests", LATEST), # XXX: https://github.com/psf/requests/pull/6920
|
| 58 |
+
("celery", LATEST),
|
| 59 |
+
# When adding packages to this list, make sure they expose a `__version__`
|
| 60 |
+
# attribute, or modify the tests below
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# Some packages have "optional" dependencies that modify their build behaviour
|
| 65 |
+
# and are not listed in pyproject.toml, others still use `setup_requires`
|
| 66 |
+
EXTRA_BUILD_DEPS = {
|
| 67 |
+
"pyyaml": ("Cython<3.0",), # constraint to avoid errors
|
| 68 |
+
"charset-normalizer": ("mypy>=1.4.1",), # no pyproject.toml available
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
EXTRA_ENV_VARS = {
|
| 72 |
+
"pyyaml": {"PYYAML_FORCE_CYTHON": "1"},
|
| 73 |
+
"charset-normalizer": {"CHARSET_NORMALIZER_USE_MYPYC": "1"},
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
IMPORT_NAME = {
|
| 77 |
+
"pyyaml": "yaml",
|
| 78 |
+
"protobuf": "google.protobuf",
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
VIRTUALENV = (sys.executable, "-m", "virtualenv")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# By default, pip will try to build packages in isolation (PEP 517), which
|
| 86 |
+
# means it will download the previous stable version of setuptools.
|
| 87 |
+
# `pip` flags can avoid that (the version of setuptools under test
|
| 88 |
+
# should be the one to be used)
|
| 89 |
+
INSTALL_OPTIONS = (
|
| 90 |
+
"--ignore-installed",
|
| 91 |
+
"--no-build-isolation",
|
| 92 |
+
# Omit "--no-binary :all:" the sdist is supplied directly.
|
| 93 |
+
# Allows dependencies as wheels.
|
| 94 |
+
)
|
| 95 |
+
# The downside of `--no-build-isolation` is that pip will not download build
|
| 96 |
+
# dependencies. The test script will have to also handle that.
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@pytest.fixture
|
| 100 |
+
def venv_python(tmp_path):
|
| 101 |
+
run([*VIRTUALENV, str(tmp_path / ".venv")])
|
| 102 |
+
possible_path = (str(p.parent) for p in tmp_path.glob(".venv/*/python*"))
|
| 103 |
+
return shutil.which("python", path=os.pathsep.join(possible_path))
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@pytest.fixture(autouse=True)
|
| 107 |
+
def _prepare(tmp_path, venv_python, monkeypatch):
|
| 108 |
+
download_path = os.getenv("DOWNLOAD_PATH", str(tmp_path))
|
| 109 |
+
os.makedirs(download_path, exist_ok=True)
|
| 110 |
+
|
| 111 |
+
# Environment vars used for building some of the packages
|
| 112 |
+
monkeypatch.setenv("USE_MYPYC", "1")
|
| 113 |
+
|
| 114 |
+
yield
|
| 115 |
+
|
| 116 |
+
# Let's provide the maximum amount of information possible in the case
|
| 117 |
+
# it is necessary to debug the tests directly from the CI logs.
|
| 118 |
+
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
| 119 |
+
print("Temporary directory:")
|
| 120 |
+
map(print, tmp_path.glob("*"))
|
| 121 |
+
print("Virtual environment:")
|
| 122 |
+
run([venv_python, "-m", "pip", "freeze"])
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@pytest.mark.parametrize(("package", "version"), EXAMPLES)
|
| 126 |
+
@pytest.mark.uses_network
|
| 127 |
+
def test_install_sdist(package, version, tmp_path, venv_python, setuptools_wheel):
|
| 128 |
+
venv_pip = (venv_python, "-m", "pip")
|
| 129 |
+
sdist = retrieve_sdist(package, version, tmp_path)
|
| 130 |
+
deps = build_deps(package, sdist)
|
| 131 |
+
if deps:
|
| 132 |
+
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
| 133 |
+
print("Dependencies:", deps)
|
| 134 |
+
run([*venv_pip, "install", *deps])
|
| 135 |
+
|
| 136 |
+
# Use a virtualenv to simulate PEP 517 isolation
|
| 137 |
+
# but install fresh setuptools wheel to ensure the version under development
|
| 138 |
+
env = EXTRA_ENV_VARS.get(package, {})
|
| 139 |
+
run([*venv_pip, "install", "--force-reinstall", setuptools_wheel])
|
| 140 |
+
run([*venv_pip, "install", *INSTALL_OPTIONS, sdist], env)
|
| 141 |
+
|
| 142 |
+
# Execute a simple script to make sure the package was installed correctly
|
| 143 |
+
pkg = IMPORT_NAME.get(package, package).replace("-", "_")
|
| 144 |
+
script = f"import {pkg}; print(getattr({pkg}, '__version__', 0))"
|
| 145 |
+
run([venv_python, "-c", script])
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# ---- Helper Functions ----
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def retrieve_sdist(package, version, tmp_path):
|
| 152 |
+
"""Either use cached sdist file or download it from PyPI"""
|
| 153 |
+
# `pip download` cannot be used due to
|
| 154 |
+
# https://github.com/pypa/pip/issues/1884
|
| 155 |
+
# https://discuss.python.org/t/pep-625-file-name-of-a-source-distribution/4686
|
| 156 |
+
# We have to find the correct distribution file and download it
|
| 157 |
+
download_path = os.getenv("DOWNLOAD_PATH", str(tmp_path))
|
| 158 |
+
dist = retrieve_pypi_sdist_metadata(package, version)
|
| 159 |
+
|
| 160 |
+
# Remove old files to prevent cache to grow indefinitely
|
| 161 |
+
for file in glob(os.path.join(download_path, f"{package}*")):
|
| 162 |
+
if dist["filename"] != file:
|
| 163 |
+
os.unlink(file)
|
| 164 |
+
|
| 165 |
+
dist_file = os.path.join(download_path, dist["filename"])
|
| 166 |
+
if not os.path.exists(dist_file):
|
| 167 |
+
download(dist["url"], dist_file, dist["md5_digest"])
|
| 168 |
+
return dist_file
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def retrieve_pypi_sdist_metadata(package, version):
|
| 172 |
+
# https://warehouse.pypa.io/api-reference/json.html
|
| 173 |
+
id_ = package if version is LATEST else f"{package}/{version}"
|
| 174 |
+
with urlopen(f"https://pypi.org/pypi/{id_}/json") as f:
|
| 175 |
+
metadata = json.load(f)
|
| 176 |
+
|
| 177 |
+
if metadata["info"]["yanked"]:
|
| 178 |
+
raise ValueError(f"Release for {package} {version} was yanked")
|
| 179 |
+
|
| 180 |
+
version = metadata["info"]["version"]
|
| 181 |
+
release = metadata["releases"][version] if version is LATEST else metadata["urls"]
|
| 182 |
+
(sdist,) = filter(lambda d: d["packagetype"] == "sdist", release)
|
| 183 |
+
return sdist
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def download(url, dest, md5_digest):
|
| 187 |
+
with urlopen(url) as f:
|
| 188 |
+
data = f.read()
|
| 189 |
+
|
| 190 |
+
assert md5(data).hexdigest() == md5_digest
|
| 191 |
+
|
| 192 |
+
with open(dest, "wb") as f:
|
| 193 |
+
f.write(data)
|
| 194 |
+
|
| 195 |
+
assert os.path.exists(dest)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def build_deps(package, sdist_file):
|
| 199 |
+
"""Find out what are the build dependencies for a package.
|
| 200 |
+
|
| 201 |
+
"Manually" install them, since pip will not install build
|
| 202 |
+
deps with `--no-build-isolation`.
|
| 203 |
+
"""
|
| 204 |
+
# delay importing, since pytest discovery phase may hit this file from a
|
| 205 |
+
# testenv without tomli
|
| 206 |
+
from setuptools.compat.py310 import tomllib
|
| 207 |
+
|
| 208 |
+
archive = Archive(sdist_file)
|
| 209 |
+
info = tomllib.loads(_read_pyproject(archive))
|
| 210 |
+
deps = info.get("build-system", {}).get("requires", [])
|
| 211 |
+
deps += EXTRA_BUILD_DEPS.get(package, [])
|
| 212 |
+
# Remove setuptools from requirements (and deduplicate)
|
| 213 |
+
requirements = {Requirement(d).name: d for d in deps}
|
| 214 |
+
return [v for k, v in requirements.items() if k != "setuptools"]
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def _read_pyproject(archive):
|
| 218 |
+
contents = (
|
| 219 |
+
archive.get_content(member)
|
| 220 |
+
for member in archive
|
| 221 |
+
if os.path.basename(archive.get_name(member)) == "pyproject.toml"
|
| 222 |
+
)
|
| 223 |
+
return next(contents, "")
|
venv/lib/python3.11/site-packages/setuptools/tests/mod_with_constant.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
value = 'three, sir!'
|
venv/lib/python3.11/site-packages/setuptools/tests/namespaces.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
+
import json
|
| 3 |
+
import textwrap
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def iter_namespace_pkgs(namespace):
|
| 8 |
+
parts = namespace.split(".")
|
| 9 |
+
for i in range(len(parts)):
|
| 10 |
+
yield ".".join(parts[: i + 1])
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def build_namespace_package(tmpdir, name, version="1.0", impl="pkg_resources"):
|
| 14 |
+
src_dir = tmpdir / name
|
| 15 |
+
src_dir.mkdir()
|
| 16 |
+
setup_py = src_dir / 'setup.py'
|
| 17 |
+
namespace, _, rest = name.rpartition('.')
|
| 18 |
+
namespaces = list(iter_namespace_pkgs(namespace))
|
| 19 |
+
setup_args = {
|
| 20 |
+
"name": name,
|
| 21 |
+
"version": version,
|
| 22 |
+
"packages": namespaces,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
if impl == "pkg_resources":
|
| 26 |
+
tmpl = '__import__("pkg_resources").declare_namespace(__name__)'
|
| 27 |
+
setup_args["namespace_packages"] = namespaces
|
| 28 |
+
elif impl == "pkgutil":
|
| 29 |
+
tmpl = '__path__ = __import__("pkgutil").extend_path(__path__, __name__)'
|
| 30 |
+
else:
|
| 31 |
+
raise ValueError(f"Cannot recognise {impl=} when creating namespaces")
|
| 32 |
+
|
| 33 |
+
args = json.dumps(setup_args, indent=4)
|
| 34 |
+
assert ast.literal_eval(args) # ensure it is valid Python
|
| 35 |
+
|
| 36 |
+
script = textwrap.dedent(
|
| 37 |
+
"""\
|
| 38 |
+
import setuptools
|
| 39 |
+
args = {args}
|
| 40 |
+
setuptools.setup(**args)
|
| 41 |
+
"""
|
| 42 |
+
).format(args=args)
|
| 43 |
+
setup_py.write_text(script, encoding='utf-8')
|
| 44 |
+
|
| 45 |
+
ns_pkg_dir = Path(src_dir, namespace.replace(".", "/"))
|
| 46 |
+
ns_pkg_dir.mkdir(parents=True)
|
| 47 |
+
|
| 48 |
+
for ns in namespaces:
|
| 49 |
+
pkg_init = src_dir / ns.replace(".", "/") / '__init__.py'
|
| 50 |
+
pkg_init.write_text(tmpl, encoding='utf-8')
|
| 51 |
+
|
| 52 |
+
pkg_mod = ns_pkg_dir / (rest + '.py')
|
| 53 |
+
some_functionality = 'name = {rest!r}'.format(**locals())
|
| 54 |
+
pkg_mod.write_text(some_functionality, encoding='utf-8')
|
| 55 |
+
return src_dir
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def build_pep420_namespace_package(tmpdir, name):
|
| 59 |
+
src_dir = tmpdir / name
|
| 60 |
+
src_dir.mkdir()
|
| 61 |
+
pyproject = src_dir / "pyproject.toml"
|
| 62 |
+
namespace, _, rest = name.rpartition(".")
|
| 63 |
+
script = f"""\
|
| 64 |
+
[build-system]
|
| 65 |
+
requires = ["setuptools"]
|
| 66 |
+
build-backend = "setuptools.build_meta"
|
| 67 |
+
|
| 68 |
+
[project]
|
| 69 |
+
name = "{name}"
|
| 70 |
+
version = "3.14159"
|
| 71 |
+
"""
|
| 72 |
+
pyproject.write_text(textwrap.dedent(script), encoding='utf-8')
|
| 73 |
+
ns_pkg_dir = Path(src_dir, namespace.replace(".", "/"))
|
| 74 |
+
ns_pkg_dir.mkdir(parents=True)
|
| 75 |
+
pkg_mod = ns_pkg_dir / (rest + ".py")
|
| 76 |
+
some_functionality = f"name = {rest!r}"
|
| 77 |
+
pkg_mod.write_text(some_functionality, encoding='utf-8')
|
| 78 |
+
return src_dir
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def make_site_dir(target):
|
| 82 |
+
"""
|
| 83 |
+
Add a sitecustomize.py module in target to cause
|
| 84 |
+
target to be added to site dirs such that .pth files
|
| 85 |
+
are processed there.
|
| 86 |
+
"""
|
| 87 |
+
sc = target / 'sitecustomize.py'
|
| 88 |
+
target_str = str(target)
|
| 89 |
+
tmpl = '__import__("site").addsitedir({target_str!r})'
|
| 90 |
+
sc.write_text(tmpl.format(**locals()), encoding='utf-8')
|
venv/lib/python3.11/site-packages/setuptools/tests/script-with-bom.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
result = 'passed'
|
venv/lib/python3.11/site-packages/setuptools/tests/server.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Basic http server for tests to simulate PyPI or custom indexes"""
|
| 2 |
+
|
| 3 |
+
import http.server
|
| 4 |
+
import os
|
| 5 |
+
import threading
|
| 6 |
+
import time
|
| 7 |
+
import urllib.parse
|
| 8 |
+
import urllib.request
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class IndexServer(http.server.HTTPServer):
|
| 12 |
+
"""Basic single-threaded http server simulating a package index
|
| 13 |
+
|
| 14 |
+
You can use this server in unittest like this::
|
| 15 |
+
s = IndexServer()
|
| 16 |
+
s.start()
|
| 17 |
+
index_url = s.base_url() + 'mytestindex'
|
| 18 |
+
# do some test requests to the index
|
| 19 |
+
# The index files should be located in setuptools/tests/indexes
|
| 20 |
+
s.stop()
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
server_address=('', 0),
|
| 26 |
+
RequestHandlerClass=http.server.SimpleHTTPRequestHandler,
|
| 27 |
+
):
|
| 28 |
+
http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
|
| 29 |
+
self._run = True
|
| 30 |
+
|
| 31 |
+
def start(self):
|
| 32 |
+
self.thread = threading.Thread(target=self.serve_forever)
|
| 33 |
+
self.thread.start()
|
| 34 |
+
|
| 35 |
+
def stop(self):
|
| 36 |
+
"Stop the server"
|
| 37 |
+
|
| 38 |
+
# Let the server finish the last request and wait for a new one.
|
| 39 |
+
time.sleep(0.1)
|
| 40 |
+
|
| 41 |
+
self.shutdown()
|
| 42 |
+
self.thread.join()
|
| 43 |
+
self.socket.close()
|
| 44 |
+
|
| 45 |
+
def base_url(self):
|
| 46 |
+
port = self.server_port
|
| 47 |
+
return f'http://127.0.0.1:{port}/setuptools/tests/indexes/'
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class RequestRecorder(http.server.BaseHTTPRequestHandler):
|
| 51 |
+
def do_GET(self):
|
| 52 |
+
requests = vars(self.server).setdefault('requests', [])
|
| 53 |
+
requests.append(self)
|
| 54 |
+
self.send_response(200, 'OK')
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class MockServer(http.server.HTTPServer, threading.Thread):
|
| 58 |
+
"""
|
| 59 |
+
A simple HTTP Server that records the requests made to it.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def __init__(self, server_address=('', 0), RequestHandlerClass=RequestRecorder):
|
| 63 |
+
http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
|
| 64 |
+
threading.Thread.__init__(self)
|
| 65 |
+
self.daemon = True
|
| 66 |
+
self.requests = []
|
| 67 |
+
|
| 68 |
+
def run(self):
|
| 69 |
+
self.serve_forever()
|
| 70 |
+
|
| 71 |
+
@property
|
| 72 |
+
def netloc(self):
|
| 73 |
+
return f'localhost:{self.server_port}'
|
| 74 |
+
|
| 75 |
+
@property
|
| 76 |
+
def url(self):
|
| 77 |
+
return f'http://{self.netloc}/'
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def path_to_url(path, authority=None):
|
| 81 |
+
"""Convert a path to a file: URL."""
|
| 82 |
+
path = os.path.normpath(os.path.abspath(path))
|
| 83 |
+
base = 'file:'
|
| 84 |
+
if authority is not None:
|
| 85 |
+
base += '//' + authority
|
| 86 |
+
return urllib.parse.urljoin(base, urllib.request.pathname2url(path))
|
venv/lib/python3.11/site-packages/setuptools/tests/test_archive_util.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
import tarfile
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from setuptools import archive_util
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@pytest.fixture
|
| 10 |
+
def tarfile_with_unicode(tmpdir):
|
| 11 |
+
"""
|
| 12 |
+
Create a tarfile containing only a file whose name is
|
| 13 |
+
a zero byte file called testimäge.png.
|
| 14 |
+
"""
|
| 15 |
+
tarobj = io.BytesIO()
|
| 16 |
+
|
| 17 |
+
with tarfile.open(fileobj=tarobj, mode="w:gz") as tgz:
|
| 18 |
+
data = b""
|
| 19 |
+
|
| 20 |
+
filename = "testimäge.png"
|
| 21 |
+
|
| 22 |
+
t = tarfile.TarInfo(filename)
|
| 23 |
+
t.size = len(data)
|
| 24 |
+
|
| 25 |
+
tgz.addfile(t, io.BytesIO(data))
|
| 26 |
+
|
| 27 |
+
target = tmpdir / 'unicode-pkg-1.0.tar.gz'
|
| 28 |
+
with open(str(target), mode='wb') as tf:
|
| 29 |
+
tf.write(tarobj.getvalue())
|
| 30 |
+
return str(target)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@pytest.mark.xfail(reason="#710 and #712")
|
| 34 |
+
def test_unicode_files(tarfile_with_unicode, tmpdir):
|
| 35 |
+
target = tmpdir / 'out'
|
| 36 |
+
archive_util.unpack_archive(tarfile_with_unicode, str(target))
|
venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_deprecations.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""develop tests"""
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
from unittest import mock
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from setuptools import SetuptoolsDeprecationWarning
|
| 9 |
+
from setuptools.dist import Distribution
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
|
| 13 |
+
@pytest.mark.xfail(reason="bdist_rpm is long deprecated, should we remove it? #1988")
|
| 14 |
+
@mock.patch('distutils.command.bdist_rpm.bdist_rpm')
|
| 15 |
+
def test_bdist_rpm_warning(distutils_cmd, tmpdir_cwd):
|
| 16 |
+
dist = Distribution(
|
| 17 |
+
dict(
|
| 18 |
+
script_name='setup.py',
|
| 19 |
+
script_args=['bdist_rpm'],
|
| 20 |
+
name='foo',
|
| 21 |
+
py_modules=['hi'],
|
| 22 |
+
)
|
| 23 |
+
)
|
| 24 |
+
dist.parse_command_line()
|
| 25 |
+
with pytest.warns(SetuptoolsDeprecationWarning):
|
| 26 |
+
dist.run_commands()
|
| 27 |
+
|
| 28 |
+
distutils_cmd.run.assert_called_once()
|
venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_egg.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""develop tests"""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import zipfile
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
|
| 9 |
+
from setuptools.dist import Distribution
|
| 10 |
+
|
| 11 |
+
from . import contexts
|
| 12 |
+
|
| 13 |
+
SETUP_PY = """\
|
| 14 |
+
from setuptools import setup
|
| 15 |
+
|
| 16 |
+
setup(py_modules=['hi'])
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@pytest.fixture
|
| 21 |
+
def setup_context(tmpdir):
|
| 22 |
+
with (tmpdir / 'setup.py').open('w') as f:
|
| 23 |
+
f.write(SETUP_PY)
|
| 24 |
+
with (tmpdir / 'hi.py').open('w') as f:
|
| 25 |
+
f.write('1\n')
|
| 26 |
+
with tmpdir.as_cwd():
|
| 27 |
+
yield tmpdir
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class Test:
|
| 31 |
+
@pytest.mark.usefixtures("user_override")
|
| 32 |
+
@pytest.mark.usefixtures("setup_context")
|
| 33 |
+
def test_bdist_egg(self):
|
| 34 |
+
dist = Distribution(
|
| 35 |
+
dict(
|
| 36 |
+
script_name='setup.py',
|
| 37 |
+
script_args=['bdist_egg'],
|
| 38 |
+
name='foo',
|
| 39 |
+
py_modules=['hi'],
|
| 40 |
+
)
|
| 41 |
+
)
|
| 42 |
+
os.makedirs(os.path.join('build', 'src'))
|
| 43 |
+
with contexts.quiet():
|
| 44 |
+
dist.parse_command_line()
|
| 45 |
+
dist.run_commands()
|
| 46 |
+
|
| 47 |
+
# let's see if we got our egg link at the right place
|
| 48 |
+
[content] = os.listdir('dist')
|
| 49 |
+
assert re.match(r'foo-0.0.0-py[23].\d+.egg$', content)
|
| 50 |
+
|
| 51 |
+
@pytest.mark.xfail(
|
| 52 |
+
os.environ.get('PYTHONDONTWRITEBYTECODE', False),
|
| 53 |
+
reason="Byte code disabled",
|
| 54 |
+
)
|
| 55 |
+
@pytest.mark.usefixtures("user_override")
|
| 56 |
+
@pytest.mark.usefixtures("setup_context")
|
| 57 |
+
def test_exclude_source_files(self):
|
| 58 |
+
dist = Distribution(
|
| 59 |
+
dict(
|
| 60 |
+
script_name='setup.py',
|
| 61 |
+
script_args=['bdist_egg', '--exclude-source-files'],
|
| 62 |
+
py_modules=['hi'],
|
| 63 |
+
)
|
| 64 |
+
)
|
| 65 |
+
with contexts.quiet():
|
| 66 |
+
dist.parse_command_line()
|
| 67 |
+
dist.run_commands()
|
| 68 |
+
[dist_name] = os.listdir('dist')
|
| 69 |
+
dist_filename = os.path.join('dist', dist_name)
|
| 70 |
+
zip = zipfile.ZipFile(dist_filename)
|
| 71 |
+
names = list(zi.filename for zi in zip.filelist)
|
| 72 |
+
assert 'hi.pyc' in names
|
| 73 |
+
assert 'hi.py' not in names
|
venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_wheel.py
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import builtins
|
| 4 |
+
import importlib
|
| 5 |
+
import os.path
|
| 6 |
+
import platform
|
| 7 |
+
import shutil
|
| 8 |
+
import stat
|
| 9 |
+
import struct
|
| 10 |
+
import sys
|
| 11 |
+
import sysconfig
|
| 12 |
+
from contextlib import suppress
|
| 13 |
+
from inspect import cleandoc
|
| 14 |
+
from zipfile import ZipFile
|
| 15 |
+
|
| 16 |
+
import jaraco.path
|
| 17 |
+
import pytest
|
| 18 |
+
from packaging import tags
|
| 19 |
+
|
| 20 |
+
import setuptools
|
| 21 |
+
from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag
|
| 22 |
+
from setuptools.dist import Distribution
|
| 23 |
+
from setuptools.warnings import SetuptoolsDeprecationWarning
|
| 24 |
+
|
| 25 |
+
from distutils.core import run_setup
|
| 26 |
+
|
| 27 |
+
DEFAULT_FILES = {
|
| 28 |
+
"dummy_dist-1.0.dist-info/top_level.txt",
|
| 29 |
+
"dummy_dist-1.0.dist-info/METADATA",
|
| 30 |
+
"dummy_dist-1.0.dist-info/WHEEL",
|
| 31 |
+
"dummy_dist-1.0.dist-info/RECORD",
|
| 32 |
+
}
|
| 33 |
+
DEFAULT_LICENSE_FILES = {
|
| 34 |
+
"LICENSE",
|
| 35 |
+
"LICENSE.txt",
|
| 36 |
+
"LICENCE",
|
| 37 |
+
"LICENCE.txt",
|
| 38 |
+
"COPYING",
|
| 39 |
+
"COPYING.md",
|
| 40 |
+
"NOTICE",
|
| 41 |
+
"NOTICE.rst",
|
| 42 |
+
"AUTHORS",
|
| 43 |
+
"AUTHORS.txt",
|
| 44 |
+
}
|
| 45 |
+
OTHER_IGNORED_FILES = {
|
| 46 |
+
"LICENSE~",
|
| 47 |
+
"AUTHORS~",
|
| 48 |
+
}
|
| 49 |
+
SETUPPY_EXAMPLE = """\
|
| 50 |
+
from setuptools import setup
|
| 51 |
+
|
| 52 |
+
setup(
|
| 53 |
+
name='dummy_dist',
|
| 54 |
+
version='1.0',
|
| 55 |
+
)
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
EXAMPLES = {
|
| 60 |
+
"dummy-dist": {
|
| 61 |
+
"setup.py": SETUPPY_EXAMPLE,
|
| 62 |
+
"licenses_dir": {"DUMMYFILE": ""},
|
| 63 |
+
**dict.fromkeys(DEFAULT_LICENSE_FILES | OTHER_IGNORED_FILES, ""),
|
| 64 |
+
},
|
| 65 |
+
"simple-dist": {
|
| 66 |
+
"setup.py": cleandoc(
|
| 67 |
+
"""
|
| 68 |
+
from setuptools import setup
|
| 69 |
+
|
| 70 |
+
setup(
|
| 71 |
+
name="simple.dist",
|
| 72 |
+
version="0.1",
|
| 73 |
+
description="A testing distribution \N{SNOWMAN}",
|
| 74 |
+
extras_require={"voting": ["beaglevote"]},
|
| 75 |
+
)
|
| 76 |
+
"""
|
| 77 |
+
),
|
| 78 |
+
"simpledist": "",
|
| 79 |
+
},
|
| 80 |
+
"complex-dist": {
|
| 81 |
+
"setup.py": cleandoc(
|
| 82 |
+
"""
|
| 83 |
+
from setuptools import setup
|
| 84 |
+
|
| 85 |
+
setup(
|
| 86 |
+
name="complex-dist",
|
| 87 |
+
version="0.1",
|
| 88 |
+
description="Another testing distribution \N{SNOWMAN}",
|
| 89 |
+
long_description="Another testing distribution \N{SNOWMAN}",
|
| 90 |
+
author="Illustrious Author",
|
| 91 |
+
author_email="illustrious@example.org",
|
| 92 |
+
url="http://example.org/exemplary",
|
| 93 |
+
packages=["complexdist"],
|
| 94 |
+
setup_requires=["setuptools"],
|
| 95 |
+
install_requires=["quux", "splort"],
|
| 96 |
+
extras_require={"simple": ["simple.dist"]},
|
| 97 |
+
entry_points={
|
| 98 |
+
"console_scripts": [
|
| 99 |
+
"complex-dist=complexdist:main",
|
| 100 |
+
"complex-dist2=complexdist:main",
|
| 101 |
+
],
|
| 102 |
+
},
|
| 103 |
+
)
|
| 104 |
+
"""
|
| 105 |
+
),
|
| 106 |
+
"complexdist": {"__init__.py": "def main(): return"},
|
| 107 |
+
},
|
| 108 |
+
"headers-dist": {
|
| 109 |
+
"setup.py": cleandoc(
|
| 110 |
+
"""
|
| 111 |
+
from setuptools import setup
|
| 112 |
+
|
| 113 |
+
setup(
|
| 114 |
+
name="headers.dist",
|
| 115 |
+
version="0.1",
|
| 116 |
+
description="A distribution with headers",
|
| 117 |
+
headers=["header.h"],
|
| 118 |
+
)
|
| 119 |
+
"""
|
| 120 |
+
),
|
| 121 |
+
"headersdist.py": "",
|
| 122 |
+
"header.h": "",
|
| 123 |
+
},
|
| 124 |
+
"commasinfilenames-dist": {
|
| 125 |
+
"setup.py": cleandoc(
|
| 126 |
+
"""
|
| 127 |
+
from setuptools import setup
|
| 128 |
+
|
| 129 |
+
setup(
|
| 130 |
+
name="testrepo",
|
| 131 |
+
version="0.1",
|
| 132 |
+
packages=["mypackage"],
|
| 133 |
+
description="A test package with commas in file names",
|
| 134 |
+
include_package_data=True,
|
| 135 |
+
package_data={"mypackage.data": ["*"]},
|
| 136 |
+
)
|
| 137 |
+
"""
|
| 138 |
+
),
|
| 139 |
+
"mypackage": {
|
| 140 |
+
"__init__.py": "",
|
| 141 |
+
"data": {"__init__.py": "", "1,2,3.txt": ""},
|
| 142 |
+
},
|
| 143 |
+
"testrepo-0.1.0": {
|
| 144 |
+
"mypackage": {"__init__.py": ""},
|
| 145 |
+
},
|
| 146 |
+
},
|
| 147 |
+
"unicode-dist": {
|
| 148 |
+
"setup.py": cleandoc(
|
| 149 |
+
"""
|
| 150 |
+
from setuptools import setup
|
| 151 |
+
|
| 152 |
+
setup(
|
| 153 |
+
name="unicode.dist",
|
| 154 |
+
version="0.1",
|
| 155 |
+
description="A testing distribution \N{SNOWMAN}",
|
| 156 |
+
packages=["unicodedist"],
|
| 157 |
+
zip_safe=True,
|
| 158 |
+
)
|
| 159 |
+
"""
|
| 160 |
+
),
|
| 161 |
+
"unicodedist": {"__init__.py": "", "åäö_日本語.py": ""},
|
| 162 |
+
},
|
| 163 |
+
"utf8-metadata-dist": {
|
| 164 |
+
"setup.cfg": cleandoc(
|
| 165 |
+
"""
|
| 166 |
+
[metadata]
|
| 167 |
+
name = utf8-metadata-dist
|
| 168 |
+
version = 42
|
| 169 |
+
author_email = "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
|
| 170 |
+
long_description = file: README.rst
|
| 171 |
+
"""
|
| 172 |
+
),
|
| 173 |
+
"README.rst": "UTF-8 描述 説明",
|
| 174 |
+
},
|
| 175 |
+
"licenses-dist": {
|
| 176 |
+
"setup.cfg": cleandoc(
|
| 177 |
+
"""
|
| 178 |
+
[metadata]
|
| 179 |
+
name = licenses-dist
|
| 180 |
+
version = 1.0
|
| 181 |
+
license_files = **/LICENSE
|
| 182 |
+
"""
|
| 183 |
+
),
|
| 184 |
+
"LICENSE": "",
|
| 185 |
+
"src": {
|
| 186 |
+
"vendor": {"LICENSE": ""},
|
| 187 |
+
},
|
| 188 |
+
},
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
if sys.platform != "win32":
|
| 193 |
+
# ABI3 extensions don't really work on Windows
|
| 194 |
+
EXAMPLES["abi3extension-dist"] = {
|
| 195 |
+
"setup.py": cleandoc(
|
| 196 |
+
"""
|
| 197 |
+
from setuptools import Extension, setup
|
| 198 |
+
|
| 199 |
+
setup(
|
| 200 |
+
name="extension.dist",
|
| 201 |
+
version="0.1",
|
| 202 |
+
description="A testing distribution \N{SNOWMAN}",
|
| 203 |
+
ext_modules=[
|
| 204 |
+
Extension(
|
| 205 |
+
name="extension", sources=["extension.c"], py_limited_api=True
|
| 206 |
+
)
|
| 207 |
+
],
|
| 208 |
+
)
|
| 209 |
+
"""
|
| 210 |
+
),
|
| 211 |
+
"setup.cfg": "[bdist_wheel]\npy_limited_api=cp32",
|
| 212 |
+
"extension.c": "#define Py_LIMITED_API 0x03020000\n#include <Python.h>",
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def bdist_wheel_cmd(**kwargs):
|
| 217 |
+
"""Run command in the same process so that it is easier to collect coverage"""
|
| 218 |
+
dist_obj = (
|
| 219 |
+
run_setup("setup.py", stop_after="init")
|
| 220 |
+
if os.path.exists("setup.py")
|
| 221 |
+
else Distribution({"script_name": "%%build_meta%%"})
|
| 222 |
+
)
|
| 223 |
+
dist_obj.parse_config_files()
|
| 224 |
+
cmd = bdist_wheel(dist_obj)
|
| 225 |
+
for attr, value in kwargs.items():
|
| 226 |
+
setattr(cmd, attr, value)
|
| 227 |
+
cmd.finalize_options()
|
| 228 |
+
return cmd
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def mkexample(tmp_path_factory, name):
|
| 232 |
+
basedir = tmp_path_factory.mktemp(name)
|
| 233 |
+
jaraco.path.build(EXAMPLES[name], prefix=str(basedir))
|
| 234 |
+
return basedir
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@pytest.fixture(scope="session")
|
| 238 |
+
def wheel_paths(tmp_path_factory):
|
| 239 |
+
build_base = tmp_path_factory.mktemp("build")
|
| 240 |
+
dist_dir = tmp_path_factory.mktemp("dist")
|
| 241 |
+
for name in EXAMPLES:
|
| 242 |
+
example_dir = mkexample(tmp_path_factory, name)
|
| 243 |
+
build_dir = build_base / name
|
| 244 |
+
with jaraco.path.DirectoryStack().context(example_dir):
|
| 245 |
+
bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()
|
| 246 |
+
|
| 247 |
+
return sorted(str(fname) for fname in dist_dir.glob("*.whl"))
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
@pytest.fixture
|
| 251 |
+
def dummy_dist(tmp_path_factory):
|
| 252 |
+
return mkexample(tmp_path_factory, "dummy-dist")
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
@pytest.fixture
|
| 256 |
+
def licenses_dist(tmp_path_factory):
|
| 257 |
+
return mkexample(tmp_path_factory, "licenses-dist")
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def test_no_scripts(wheel_paths):
|
| 261 |
+
"""Make sure entry point scripts are not generated."""
|
| 262 |
+
path = next(path for path in wheel_paths if "complex_dist" in path)
|
| 263 |
+
for entry in ZipFile(path).infolist():
|
| 264 |
+
assert ".data/scripts/" not in entry.filename
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def test_unicode_record(wheel_paths):
|
| 268 |
+
path = next(path for path in wheel_paths if "unicode_dist" in path)
|
| 269 |
+
with ZipFile(path) as zf:
|
| 270 |
+
record = zf.read("unicode_dist-0.1.dist-info/RECORD")
|
| 271 |
+
|
| 272 |
+
assert "åäö_日本語.py".encode() in record
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
UTF8_PKG_INFO = """\
|
| 276 |
+
Metadata-Version: 2.1
|
| 277 |
+
Name: helloworld
|
| 278 |
+
Version: 42
|
| 279 |
+
Author-email: "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
UTF-8 描述 説明
|
| 283 |
+
"""
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def test_preserve_unicode_metadata(monkeypatch, tmp_path):
|
| 287 |
+
monkeypatch.chdir(tmp_path)
|
| 288 |
+
egginfo = tmp_path / "dummy_dist.egg-info"
|
| 289 |
+
distinfo = tmp_path / "dummy_dist.dist-info"
|
| 290 |
+
|
| 291 |
+
egginfo.mkdir()
|
| 292 |
+
(egginfo / "PKG-INFO").write_text(UTF8_PKG_INFO, encoding="utf-8")
|
| 293 |
+
(egginfo / "dependency_links.txt").touch()
|
| 294 |
+
|
| 295 |
+
class simpler_bdist_wheel(bdist_wheel):
|
| 296 |
+
"""Avoid messing with setuptools/distutils internals"""
|
| 297 |
+
|
| 298 |
+
def __init__(self):
|
| 299 |
+
pass
|
| 300 |
+
|
| 301 |
+
@property
|
| 302 |
+
def license_paths(self):
|
| 303 |
+
return []
|
| 304 |
+
|
| 305 |
+
cmd_obj = simpler_bdist_wheel()
|
| 306 |
+
cmd_obj.egg2dist(egginfo, distinfo)
|
| 307 |
+
|
| 308 |
+
metadata = (distinfo / "METADATA").read_text(encoding="utf-8")
|
| 309 |
+
assert 'Author-email: "John X. Ãørçeč"' in metadata
|
| 310 |
+
assert "Γαμα קּ 東 " in metadata
|
| 311 |
+
assert "UTF-8 描述 説明" in metadata
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def test_licenses_default(dummy_dist, monkeypatch, tmp_path):
|
| 315 |
+
monkeypatch.chdir(dummy_dist)
|
| 316 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
|
| 317 |
+
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
|
| 318 |
+
license_files = {
|
| 319 |
+
"dummy_dist-1.0.dist-info/licenses/" + fname
|
| 320 |
+
for fname in DEFAULT_LICENSE_FILES
|
| 321 |
+
}
|
| 322 |
+
assert set(wf.namelist()) == DEFAULT_FILES | license_files
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def test_licenses_deprecated(dummy_dist, monkeypatch, tmp_path):
|
| 326 |
+
dummy_dist.joinpath("setup.cfg").write_text(
|
| 327 |
+
"[metadata]\nlicense_file=licenses_dir/DUMMYFILE", encoding="utf-8"
|
| 328 |
+
)
|
| 329 |
+
monkeypatch.chdir(dummy_dist)
|
| 330 |
+
|
| 331 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
|
| 332 |
+
|
| 333 |
+
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
|
| 334 |
+
license_files = {"dummy_dist-1.0.dist-info/licenses/licenses_dir/DUMMYFILE"}
|
| 335 |
+
assert set(wf.namelist()) == DEFAULT_FILES | license_files
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
@pytest.mark.parametrize(
|
| 339 |
+
("config_file", "config"),
|
| 340 |
+
[
|
| 341 |
+
("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*\n LICENSE"),
|
| 342 |
+
("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*, LICENSE"),
|
| 343 |
+
(
|
| 344 |
+
"setup.py",
|
| 345 |
+
SETUPPY_EXAMPLE.replace(
|
| 346 |
+
")", " license_files=['licenses_dir/DUMMYFILE', 'LICENSE'])"
|
| 347 |
+
),
|
| 348 |
+
),
|
| 349 |
+
],
|
| 350 |
+
)
|
| 351 |
+
def test_licenses_override(dummy_dist, monkeypatch, tmp_path, config_file, config):
|
| 352 |
+
dummy_dist.joinpath(config_file).write_text(config, encoding="utf-8")
|
| 353 |
+
monkeypatch.chdir(dummy_dist)
|
| 354 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
|
| 355 |
+
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
|
| 356 |
+
license_files = {
|
| 357 |
+
"dummy_dist-1.0.dist-info/licenses/" + fname
|
| 358 |
+
for fname in {"licenses_dir/DUMMYFILE", "LICENSE"}
|
| 359 |
+
}
|
| 360 |
+
assert set(wf.namelist()) == DEFAULT_FILES | license_files
|
| 361 |
+
metadata = wf.read("dummy_dist-1.0.dist-info/METADATA").decode("utf8")
|
| 362 |
+
assert "License-File: licenses_dir/DUMMYFILE" in metadata
|
| 363 |
+
assert "License-File: LICENSE" in metadata
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def test_licenses_preserve_folder_structure(licenses_dist, monkeypatch, tmp_path):
|
| 367 |
+
monkeypatch.chdir(licenses_dist)
|
| 368 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
|
| 369 |
+
print(os.listdir("dist"))
|
| 370 |
+
with ZipFile("dist/licenses_dist-1.0-py3-none-any.whl") as wf:
|
| 371 |
+
default_files = {name.replace("dummy_", "licenses_") for name in DEFAULT_FILES}
|
| 372 |
+
license_files = {
|
| 373 |
+
"licenses_dist-1.0.dist-info/licenses/LICENSE",
|
| 374 |
+
"licenses_dist-1.0.dist-info/licenses/src/vendor/LICENSE",
|
| 375 |
+
}
|
| 376 |
+
assert set(wf.namelist()) == default_files | license_files
|
| 377 |
+
metadata = wf.read("licenses_dist-1.0.dist-info/METADATA").decode("utf8")
|
| 378 |
+
assert "License-File: src/vendor/LICENSE" in metadata
|
| 379 |
+
assert "License-File: LICENSE" in metadata
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def test_licenses_disabled(dummy_dist, monkeypatch, tmp_path):
|
| 383 |
+
dummy_dist.joinpath("setup.cfg").write_text(
|
| 384 |
+
"[metadata]\nlicense_files=\n", encoding="utf-8"
|
| 385 |
+
)
|
| 386 |
+
monkeypatch.chdir(dummy_dist)
|
| 387 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
|
| 388 |
+
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
|
| 389 |
+
assert set(wf.namelist()) == DEFAULT_FILES
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
def test_build_number(dummy_dist, monkeypatch, tmp_path):
|
| 393 |
+
monkeypatch.chdir(dummy_dist)
|
| 394 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path), build_number="2").run()
|
| 395 |
+
with ZipFile("dist/dummy_dist-1.0-2-py3-none-any.whl") as wf:
|
| 396 |
+
filenames = set(wf.namelist())
|
| 397 |
+
assert "dummy_dist-1.0.dist-info/RECORD" in filenames
|
| 398 |
+
assert "dummy_dist-1.0.dist-info/METADATA" in filenames
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path):
|
| 402 |
+
monkeypatch.chdir(dummy_dist)
|
| 403 |
+
with pytest.warns(SetuptoolsDeprecationWarning, match=".*universal is deprecated"):
|
| 404 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path), universal=True).run()
|
| 405 |
+
|
| 406 |
+
# For now we still respect the option
|
| 407 |
+
assert os.path.exists("dist/dummy_dist-1.0-py2.py3-none-any.whl")
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
EXTENSION_EXAMPLE = """\
|
| 411 |
+
#include <Python.h>
|
| 412 |
+
|
| 413 |
+
static PyMethodDef methods[] = {
|
| 414 |
+
{ NULL, NULL, 0, NULL }
|
| 415 |
+
};
|
| 416 |
+
|
| 417 |
+
static struct PyModuleDef module_def = {
|
| 418 |
+
PyModuleDef_HEAD_INIT,
|
| 419 |
+
"extension",
|
| 420 |
+
"Dummy extension module",
|
| 421 |
+
-1,
|
| 422 |
+
methods
|
| 423 |
+
};
|
| 424 |
+
|
| 425 |
+
PyMODINIT_FUNC PyInit_extension(void) {
|
| 426 |
+
return PyModule_Create(&module_def);
|
| 427 |
+
}
|
| 428 |
+
"""
|
| 429 |
+
EXTENSION_SETUPPY = """\
|
| 430 |
+
from __future__ import annotations
|
| 431 |
+
|
| 432 |
+
from setuptools import Extension, setup
|
| 433 |
+
|
| 434 |
+
setup(
|
| 435 |
+
name="extension.dist",
|
| 436 |
+
version="0.1",
|
| 437 |
+
description="A testing distribution \N{SNOWMAN}",
|
| 438 |
+
ext_modules=[Extension(name="extension", sources=["extension.c"])],
|
| 439 |
+
)
|
| 440 |
+
"""
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
@pytest.mark.filterwarnings(
|
| 444 |
+
"once:Config variable '.*' is unset.*, Python ABI tag may be incorrect"
|
| 445 |
+
)
|
| 446 |
+
def test_limited_abi(monkeypatch, tmp_path, tmp_path_factory):
|
| 447 |
+
"""Test that building a binary wheel with the limited ABI works."""
|
| 448 |
+
source_dir = tmp_path_factory.mktemp("extension_dist")
|
| 449 |
+
(source_dir / "setup.py").write_text(EXTENSION_SETUPPY, encoding="utf-8")
|
| 450 |
+
(source_dir / "extension.c").write_text(EXTENSION_EXAMPLE, encoding="utf-8")
|
| 451 |
+
build_dir = tmp_path.joinpath("build")
|
| 452 |
+
dist_dir = tmp_path.joinpath("dist")
|
| 453 |
+
monkeypatch.chdir(source_dir)
|
| 454 |
+
bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
def test_build_from_readonly_tree(dummy_dist, monkeypatch, tmp_path):
|
| 458 |
+
basedir = str(tmp_path.joinpath("dummy"))
|
| 459 |
+
shutil.copytree(str(dummy_dist), basedir)
|
| 460 |
+
monkeypatch.chdir(basedir)
|
| 461 |
+
|
| 462 |
+
# Make the tree read-only
|
| 463 |
+
for root, _dirs, files in os.walk(basedir):
|
| 464 |
+
for fname in files:
|
| 465 |
+
os.chmod(os.path.join(root, fname), stat.S_IREAD)
|
| 466 |
+
|
| 467 |
+
bdist_wheel_cmd().run()
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
@pytest.mark.parametrize(
|
| 471 |
+
("option", "compress_type"),
|
| 472 |
+
list(bdist_wheel.supported_compressions.items()),
|
| 473 |
+
ids=list(bdist_wheel.supported_compressions),
|
| 474 |
+
)
|
| 475 |
+
def test_compression(dummy_dist, monkeypatch, tmp_path, option, compress_type):
|
| 476 |
+
monkeypatch.chdir(dummy_dist)
|
| 477 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path), compression=option).run()
|
| 478 |
+
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
|
| 479 |
+
filenames = set(wf.namelist())
|
| 480 |
+
assert "dummy_dist-1.0.dist-info/RECORD" in filenames
|
| 481 |
+
assert "dummy_dist-1.0.dist-info/METADATA" in filenames
|
| 482 |
+
for zinfo in wf.filelist:
|
| 483 |
+
assert zinfo.compress_type == compress_type
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def test_wheelfile_line_endings(wheel_paths):
|
| 487 |
+
for path in wheel_paths:
|
| 488 |
+
with ZipFile(path) as wf:
|
| 489 |
+
wheelfile = next(fn for fn in wf.filelist if fn.filename.endswith("WHEEL"))
|
| 490 |
+
wheelfile_contents = wf.read(wheelfile)
|
| 491 |
+
assert b"\r" not in wheelfile_contents
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
def test_unix_epoch_timestamps(dummy_dist, monkeypatch, tmp_path):
|
| 495 |
+
monkeypatch.setenv("SOURCE_DATE_EPOCH", "0")
|
| 496 |
+
monkeypatch.chdir(dummy_dist)
|
| 497 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path), build_number="2a").run()
|
| 498 |
+
with ZipFile("dist/dummy_dist-1.0-2a-py3-none-any.whl") as wf:
|
| 499 |
+
for zinfo in wf.filelist:
|
| 500 |
+
assert zinfo.date_time >= (1980, 1, 1, 0, 0, 0) # min epoch is used
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
def test_get_abi_tag_windows(monkeypatch):
|
| 504 |
+
monkeypatch.setattr(tags, "interpreter_name", lambda: "cp")
|
| 505 |
+
monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313-win_amd64")
|
| 506 |
+
assert get_abi_tag() == "cp313"
|
| 507 |
+
monkeypatch.setattr(sys, "gettotalrefcount", lambda: 1, False)
|
| 508 |
+
assert get_abi_tag() == "cp313d"
|
| 509 |
+
monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313t-win_amd64")
|
| 510 |
+
assert get_abi_tag() == "cp313td"
|
| 511 |
+
monkeypatch.delattr(sys, "gettotalrefcount")
|
| 512 |
+
assert get_abi_tag() == "cp313t"
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def test_get_abi_tag_pypy_old(monkeypatch):
|
| 516 |
+
monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
|
| 517 |
+
monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy36-pp73")
|
| 518 |
+
assert get_abi_tag() == "pypy36_pp73"
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def test_get_abi_tag_pypy_new(monkeypatch):
|
| 522 |
+
monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy37-pp73-darwin")
|
| 523 |
+
monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
|
| 524 |
+
assert get_abi_tag() == "pypy37_pp73"
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
def test_get_abi_tag_graalpy(monkeypatch):
|
| 528 |
+
monkeypatch.setattr(
|
| 529 |
+
sysconfig, "get_config_var", lambda x: "graalpy231-310-native-x86_64-linux"
|
| 530 |
+
)
|
| 531 |
+
monkeypatch.setattr(tags, "interpreter_name", lambda: "graalpy")
|
| 532 |
+
assert get_abi_tag() == "graalpy231_310_native"
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
def test_get_abi_tag_fallback(monkeypatch):
|
| 536 |
+
monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "unknown-python-310")
|
| 537 |
+
monkeypatch.setattr(tags, "interpreter_name", lambda: "unknown-python")
|
| 538 |
+
assert get_abi_tag() == "unknown_python_310"
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
def test_platform_with_space(dummy_dist, monkeypatch):
|
| 542 |
+
"""Ensure building on platforms with a space in the name succeed."""
|
| 543 |
+
monkeypatch.chdir(dummy_dist)
|
| 544 |
+
bdist_wheel_cmd(plat_name="isilon onefs").run()
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def test_data_dir_with_tag_build(monkeypatch, tmp_path):
|
| 548 |
+
"""
|
| 549 |
+
Setuptools allow authors to set PEP 440's local version segments
|
| 550 |
+
using ``egg_info.tag_build``. This should be reflected not only in the
|
| 551 |
+
``.whl`` file name, but also in the ``.dist-info`` and ``.data`` dirs.
|
| 552 |
+
See pypa/setuptools#3997.
|
| 553 |
+
"""
|
| 554 |
+
monkeypatch.chdir(tmp_path)
|
| 555 |
+
files = {
|
| 556 |
+
"setup.py": """
|
| 557 |
+
from setuptools import setup
|
| 558 |
+
setup(headers=["hello.h"])
|
| 559 |
+
""",
|
| 560 |
+
"setup.cfg": """
|
| 561 |
+
[metadata]
|
| 562 |
+
name = test
|
| 563 |
+
version = 1.0
|
| 564 |
+
|
| 565 |
+
[options.data_files]
|
| 566 |
+
hello/world = file.txt
|
| 567 |
+
|
| 568 |
+
[egg_info]
|
| 569 |
+
tag_build = +what
|
| 570 |
+
tag_date = 0
|
| 571 |
+
""",
|
| 572 |
+
"file.txt": "",
|
| 573 |
+
"hello.h": "",
|
| 574 |
+
}
|
| 575 |
+
for file, content in files.items():
|
| 576 |
+
with open(file, "w", encoding="utf-8") as fh:
|
| 577 |
+
fh.write(cleandoc(content))
|
| 578 |
+
|
| 579 |
+
bdist_wheel_cmd().run()
|
| 580 |
+
|
| 581 |
+
# Ensure .whl, .dist-info and .data contain the local segment
|
| 582 |
+
wheel_path = "dist/test-1.0+what-py3-none-any.whl"
|
| 583 |
+
assert os.path.exists(wheel_path)
|
| 584 |
+
entries = set(ZipFile(wheel_path).namelist())
|
| 585 |
+
for expected in (
|
| 586 |
+
"test-1.0+what.data/headers/hello.h",
|
| 587 |
+
"test-1.0+what.data/data/hello/world/file.txt",
|
| 588 |
+
"test-1.0+what.dist-info/METADATA",
|
| 589 |
+
"test-1.0+what.dist-info/WHEEL",
|
| 590 |
+
):
|
| 591 |
+
assert expected in entries
|
| 592 |
+
|
| 593 |
+
for not_expected in (
|
| 594 |
+
"test.data/headers/hello.h",
|
| 595 |
+
"test-1.0.data/data/hello/world/file.txt",
|
| 596 |
+
"test.dist-info/METADATA",
|
| 597 |
+
"test-1.0.dist-info/WHEEL",
|
| 598 |
+
):
|
| 599 |
+
assert not_expected not in entries
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
@pytest.mark.parametrize(
|
| 603 |
+
("reported", "expected"),
|
| 604 |
+
[("linux-x86_64", "linux_i686"), ("linux-aarch64", "linux_armv7l")],
|
| 605 |
+
)
|
| 606 |
+
@pytest.mark.skipif(
|
| 607 |
+
platform.system() != "Linux", reason="Only makes sense to test on Linux"
|
| 608 |
+
)
|
| 609 |
+
def test_platform_linux32(reported, expected, monkeypatch):
|
| 610 |
+
monkeypatch.setattr(struct, "calcsize", lambda x: 4)
|
| 611 |
+
dist = setuptools.Distribution()
|
| 612 |
+
cmd = bdist_wheel(dist)
|
| 613 |
+
cmd.plat_name = reported
|
| 614 |
+
cmd.root_is_pure = False
|
| 615 |
+
_, _, actual = cmd.get_tag()
|
| 616 |
+
assert actual == expected
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
def test_no_ctypes(monkeypatch) -> None:
|
| 620 |
+
def _fake_import(name: str, *args, **kwargs):
|
| 621 |
+
if name == "ctypes":
|
| 622 |
+
raise ModuleNotFoundError(f"No module named {name}")
|
| 623 |
+
|
| 624 |
+
return importlib.__import__(name, *args, **kwargs)
|
| 625 |
+
|
| 626 |
+
with suppress(KeyError):
|
| 627 |
+
monkeypatch.delitem(sys.modules, "wheel.macosx_libfile")
|
| 628 |
+
|
| 629 |
+
# Install an importer shim that refuses to load ctypes
|
| 630 |
+
monkeypatch.setattr(builtins, "__import__", _fake_import)
|
| 631 |
+
with pytest.raises(ModuleNotFoundError, match="No module named ctypes"):
|
| 632 |
+
import wheel.macosx_libfile # noqa: F401
|
| 633 |
+
|
| 634 |
+
# Unload and reimport the bdist_wheel command module to make sure it won't try to
|
| 635 |
+
# import ctypes
|
| 636 |
+
monkeypatch.delitem(sys.modules, "setuptools.command.bdist_wheel")
|
| 637 |
+
|
| 638 |
+
import setuptools.command.bdist_wheel # noqa: F401
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path):
|
| 642 |
+
monkeypatch.chdir(dummy_dist)
|
| 643 |
+
distinfo = tmp_path / "dummy_dist.dist-info"
|
| 644 |
+
|
| 645 |
+
distinfo.mkdir()
|
| 646 |
+
(distinfo / "METADATA").write_text("name: helloworld", encoding="utf-8")
|
| 647 |
+
|
| 648 |
+
# We don't control the metadata. According to PEP-517, "The hook MAY also
|
| 649 |
+
# create other files inside this directory, and a build frontend MUST
|
| 650 |
+
# preserve".
|
| 651 |
+
(distinfo / "FOO").write_text("bar", encoding="utf-8")
|
| 652 |
+
|
| 653 |
+
bdist_wheel_cmd(bdist_dir=str(tmp_path), dist_info_dir=str(distinfo)).run()
|
| 654 |
+
expected = {
|
| 655 |
+
"dummy_dist-1.0.dist-info/FOO",
|
| 656 |
+
"dummy_dist-1.0.dist-info/RECORD",
|
| 657 |
+
}
|
| 658 |
+
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
|
| 659 |
+
files_found = set(wf.namelist())
|
| 660 |
+
# Check that all expected files are there.
|
| 661 |
+
assert expected - files_found == set()
|
| 662 |
+
# Make sure there is no accidental egg-info bleeding into the wheel.
|
| 663 |
+
assert not [path for path in files_found if 'egg-info' in str(path)]
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
def test_allow_grace_period_parent_directory_license(monkeypatch, tmp_path):
|
| 667 |
+
# Motivation: https://github.com/pypa/setuptools/issues/4892
|
| 668 |
+
# TODO: Remove this test after deprecation period is over
|
| 669 |
+
files = {
|
| 670 |
+
"LICENSE.txt": "parent license", # <---- the license files are outside
|
| 671 |
+
"NOTICE.txt": "parent notice",
|
| 672 |
+
"python": {
|
| 673 |
+
"pyproject.toml": cleandoc(
|
| 674 |
+
"""
|
| 675 |
+
[project]
|
| 676 |
+
name = "test-proj"
|
| 677 |
+
dynamic = ["version"] # <---- testing dynamic will not break
|
| 678 |
+
[tool.setuptools.dynamic]
|
| 679 |
+
version.file = "VERSION"
|
| 680 |
+
"""
|
| 681 |
+
),
|
| 682 |
+
"setup.cfg": cleandoc(
|
| 683 |
+
"""
|
| 684 |
+
[metadata]
|
| 685 |
+
license_files =
|
| 686 |
+
../LICENSE.txt
|
| 687 |
+
../NOTICE.txt
|
| 688 |
+
"""
|
| 689 |
+
),
|
| 690 |
+
"VERSION": "42",
|
| 691 |
+
},
|
| 692 |
+
}
|
| 693 |
+
jaraco.path.build(files, prefix=str(tmp_path))
|
| 694 |
+
monkeypatch.chdir(tmp_path / "python")
|
| 695 |
+
msg = "Pattern '../.*.txt' cannot contain '..'"
|
| 696 |
+
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
|
| 697 |
+
bdist_wheel_cmd().run()
|
| 698 |
+
with ZipFile("dist/test_proj-42-py3-none-any.whl") as wf:
|
| 699 |
+
files_found = set(wf.namelist())
|
| 700 |
+
expected_files = {
|
| 701 |
+
"test_proj-42.dist-info/licenses/LICENSE.txt",
|
| 702 |
+
"test_proj-42.dist-info/licenses/NOTICE.txt",
|
| 703 |
+
}
|
| 704 |
+
assert expected_files <= files_found
|
| 705 |
+
|
| 706 |
+
metadata = wf.read("test_proj-42.dist-info/METADATA").decode("utf8")
|
| 707 |
+
assert "License-File: LICENSE.txt" in metadata
|
| 708 |
+
assert "License-File: NOTICE.txt" in metadata
|
venv/lib/python3.11/site-packages/setuptools/tests/test_build.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import Command
|
| 2 |
+
from setuptools.command.build import build
|
| 3 |
+
from setuptools.dist import Distribution
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_distribution_gives_setuptools_build_obj(tmpdir_cwd):
|
| 7 |
+
"""
|
| 8 |
+
Check that the setuptools Distribution uses the
|
| 9 |
+
setuptools specific build object.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
dist = Distribution(
|
| 13 |
+
dict(
|
| 14 |
+
script_name='setup.py',
|
| 15 |
+
script_args=['build'],
|
| 16 |
+
packages=[],
|
| 17 |
+
package_data={'': ['path/*']},
|
| 18 |
+
)
|
| 19 |
+
)
|
| 20 |
+
assert isinstance(dist.get_command_obj("build"), build)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class Subcommand(Command):
|
| 24 |
+
"""Dummy command to be used in tests"""
|
| 25 |
+
|
| 26 |
+
def initialize_options(self):
|
| 27 |
+
pass
|
| 28 |
+
|
| 29 |
+
def finalize_options(self):
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
def run(self):
|
| 33 |
+
raise NotImplementedError("just to check if the command runs")
|
venv/lib/python3.11/site-packages/setuptools/tests/test_build_clib.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from unittest import mock
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from setuptools.command.build_clib import build_clib
|
| 7 |
+
from setuptools.dist import Distribution
|
| 8 |
+
|
| 9 |
+
from distutils.errors import DistutilsSetupError
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TestBuildCLib:
|
| 13 |
+
@mock.patch('setuptools.command.build_clib.newer_pairwise_group')
|
| 14 |
+
def test_build_libraries(self, mock_newer):
|
| 15 |
+
dist = Distribution()
|
| 16 |
+
cmd = build_clib(dist)
|
| 17 |
+
|
| 18 |
+
# this will be a long section, just making sure all
|
| 19 |
+
# exceptions are properly raised
|
| 20 |
+
libs = [('example', {'sources': 'broken.c'})]
|
| 21 |
+
with pytest.raises(DistutilsSetupError):
|
| 22 |
+
cmd.build_libraries(libs)
|
| 23 |
+
|
| 24 |
+
obj_deps = 'some_string'
|
| 25 |
+
libs = [('example', {'sources': ['source.c'], 'obj_deps': obj_deps})]
|
| 26 |
+
with pytest.raises(DistutilsSetupError):
|
| 27 |
+
cmd.build_libraries(libs)
|
| 28 |
+
|
| 29 |
+
obj_deps = {'': ''}
|
| 30 |
+
libs = [('example', {'sources': ['source.c'], 'obj_deps': obj_deps})]
|
| 31 |
+
with pytest.raises(DistutilsSetupError):
|
| 32 |
+
cmd.build_libraries(libs)
|
| 33 |
+
|
| 34 |
+
obj_deps = {'source.c': ''}
|
| 35 |
+
libs = [('example', {'sources': ['source.c'], 'obj_deps': obj_deps})]
|
| 36 |
+
with pytest.raises(DistutilsSetupError):
|
| 37 |
+
cmd.build_libraries(libs)
|
| 38 |
+
|
| 39 |
+
# with that out of the way, let's see if the crude dependency
|
| 40 |
+
# system works
|
| 41 |
+
cmd.compiler = mock.MagicMock(spec=cmd.compiler)
|
| 42 |
+
mock_newer.return_value = ([], [])
|
| 43 |
+
|
| 44 |
+
obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
|
| 45 |
+
libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})]
|
| 46 |
+
|
| 47 |
+
cmd.build_libraries(libs)
|
| 48 |
+
assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0]
|
| 49 |
+
assert not cmd.compiler.compile.called
|
| 50 |
+
assert cmd.compiler.create_static_lib.call_count == 1
|
| 51 |
+
|
| 52 |
+
# reset the call numbers so we can test again
|
| 53 |
+
cmd.compiler.reset_mock()
|
| 54 |
+
|
| 55 |
+
mock_newer.return_value = '' # anything as long as it's not ([],[])
|
| 56 |
+
cmd.build_libraries(libs)
|
| 57 |
+
assert cmd.compiler.compile.call_count == 1
|
| 58 |
+
assert cmd.compiler.create_static_lib.call_count == 1
|
| 59 |
+
|
| 60 |
+
@mock.patch('setuptools.command.build_clib.newer_pairwise_group')
|
| 61 |
+
def test_build_libraries_reproducible(self, mock_newer):
|
| 62 |
+
dist = Distribution()
|
| 63 |
+
cmd = build_clib(dist)
|
| 64 |
+
|
| 65 |
+
# with that out of the way, let's see if the crude dependency
|
| 66 |
+
# system works
|
| 67 |
+
cmd.compiler = mock.MagicMock(spec=cmd.compiler)
|
| 68 |
+
mock_newer.return_value = ([], [])
|
| 69 |
+
|
| 70 |
+
original_sources = ['a-example.c', 'example.c']
|
| 71 |
+
sources = original_sources
|
| 72 |
+
|
| 73 |
+
obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
|
| 74 |
+
libs = [('example', {'sources': sources, 'obj_deps': obj_deps})]
|
| 75 |
+
|
| 76 |
+
cmd.build_libraries(libs)
|
| 77 |
+
computed_call_args = mock_newer.call_args[0]
|
| 78 |
+
|
| 79 |
+
while sources == original_sources:
|
| 80 |
+
sources = random.sample(original_sources, len(original_sources))
|
| 81 |
+
libs = [('example', {'sources': sources, 'obj_deps': obj_deps})]
|
| 82 |
+
|
| 83 |
+
cmd.build_libraries(libs)
|
| 84 |
+
assert computed_call_args == mock_newer.call_args[0]
|
venv/lib/python3.11/site-packages/setuptools/tests/test_build_ext.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
from importlib.util import cache_from_source as _compiled_file_name
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
from jaraco import path
|
| 9 |
+
|
| 10 |
+
from setuptools.command.build_ext import build_ext, get_abi3_suffix
|
| 11 |
+
from setuptools.dist import Distribution
|
| 12 |
+
from setuptools.errors import CompileError
|
| 13 |
+
from setuptools.extension import Extension
|
| 14 |
+
|
| 15 |
+
from . import environment
|
| 16 |
+
from .textwrap import DALS
|
| 17 |
+
|
| 18 |
+
import distutils.command.build_ext as orig
|
| 19 |
+
from distutils.sysconfig import get_config_var
|
| 20 |
+
|
| 21 |
+
IS_PYPY = '__pypy__' in sys.builtin_module_names
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class TestBuildExt:
|
| 25 |
+
def test_get_ext_filename(self):
|
| 26 |
+
"""
|
| 27 |
+
Setuptools needs to give back the same
|
| 28 |
+
result as distutils, even if the fullname
|
| 29 |
+
is not in ext_map.
|
| 30 |
+
"""
|
| 31 |
+
dist = Distribution()
|
| 32 |
+
cmd = build_ext(dist)
|
| 33 |
+
cmd.ext_map['foo/bar'] = ''
|
| 34 |
+
res = cmd.get_ext_filename('foo')
|
| 35 |
+
wanted = orig.build_ext.get_ext_filename(cmd, 'foo')
|
| 36 |
+
assert res == wanted
|
| 37 |
+
|
| 38 |
+
def test_abi3_filename(self):
|
| 39 |
+
"""
|
| 40 |
+
Filename needs to be loadable by several versions
|
| 41 |
+
of Python 3 if 'is_abi3' is truthy on Extension()
|
| 42 |
+
"""
|
| 43 |
+
print(get_abi3_suffix())
|
| 44 |
+
|
| 45 |
+
extension = Extension('spam.eggs', ['eggs.c'], py_limited_api=True)
|
| 46 |
+
dist = Distribution(dict(ext_modules=[extension]))
|
| 47 |
+
cmd = build_ext(dist)
|
| 48 |
+
cmd.finalize_options()
|
| 49 |
+
assert 'spam.eggs' in cmd.ext_map
|
| 50 |
+
res = cmd.get_ext_filename('spam.eggs')
|
| 51 |
+
|
| 52 |
+
if not get_abi3_suffix():
|
| 53 |
+
assert res.endswith(get_config_var('EXT_SUFFIX'))
|
| 54 |
+
elif sys.platform == 'win32':
|
| 55 |
+
assert res.endswith('eggs.pyd')
|
| 56 |
+
else:
|
| 57 |
+
assert 'abi3' in res
|
| 58 |
+
|
| 59 |
+
def test_ext_suffix_override(self):
|
| 60 |
+
"""
|
| 61 |
+
SETUPTOOLS_EXT_SUFFIX variable always overrides
|
| 62 |
+
default extension options.
|
| 63 |
+
"""
|
| 64 |
+
dist = Distribution()
|
| 65 |
+
cmd = build_ext(dist)
|
| 66 |
+
cmd.ext_map['for_abi3'] = ext = Extension(
|
| 67 |
+
'for_abi3',
|
| 68 |
+
['s.c'],
|
| 69 |
+
# Override shouldn't affect abi3 modules
|
| 70 |
+
py_limited_api=True,
|
| 71 |
+
)
|
| 72 |
+
# Mock value needed to pass tests
|
| 73 |
+
ext._links_to_dynamic = False
|
| 74 |
+
|
| 75 |
+
if not IS_PYPY:
|
| 76 |
+
expect = cmd.get_ext_filename('for_abi3')
|
| 77 |
+
else:
|
| 78 |
+
# PyPy builds do not use ABI3 tag, so they will
|
| 79 |
+
# also get the overridden suffix.
|
| 80 |
+
expect = 'for_abi3.test-suffix'
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
os.environ['SETUPTOOLS_EXT_SUFFIX'] = '.test-suffix'
|
| 84 |
+
res = cmd.get_ext_filename('normal')
|
| 85 |
+
assert 'normal.test-suffix' == res
|
| 86 |
+
res = cmd.get_ext_filename('for_abi3')
|
| 87 |
+
assert expect == res
|
| 88 |
+
finally:
|
| 89 |
+
del os.environ['SETUPTOOLS_EXT_SUFFIX']
|
| 90 |
+
|
| 91 |
+
def dist_with_example(self):
|
| 92 |
+
files = {
|
| 93 |
+
"src": {"mypkg": {"subpkg": {"ext2.c": ""}}},
|
| 94 |
+
"c-extensions": {"ext1": {"main.c": ""}},
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
ext1 = Extension("mypkg.ext1", ["c-extensions/ext1/main.c"])
|
| 98 |
+
ext2 = Extension("mypkg.subpkg.ext2", ["src/mypkg/subpkg/ext2.c"])
|
| 99 |
+
ext3 = Extension("ext3", ["c-extension/ext3.c"])
|
| 100 |
+
|
| 101 |
+
path.build(files)
|
| 102 |
+
return Distribution({
|
| 103 |
+
"script_name": "%test%",
|
| 104 |
+
"ext_modules": [ext1, ext2, ext3],
|
| 105 |
+
"package_dir": {"": "src"},
|
| 106 |
+
})
|
| 107 |
+
|
| 108 |
+
def test_get_outputs(self, tmpdir_cwd, monkeypatch):
|
| 109 |
+
monkeypatch.setenv('SETUPTOOLS_EXT_SUFFIX', '.mp3') # make test OS-independent
|
| 110 |
+
monkeypatch.setattr('setuptools.command.build_ext.use_stubs', False)
|
| 111 |
+
dist = self.dist_with_example()
|
| 112 |
+
|
| 113 |
+
# Regular build: get_outputs not empty, but get_output_mappings is empty
|
| 114 |
+
build_ext = dist.get_command_obj("build_ext")
|
| 115 |
+
build_ext.editable_mode = False
|
| 116 |
+
build_ext.ensure_finalized()
|
| 117 |
+
build_lib = build_ext.build_lib.replace(os.sep, "/")
|
| 118 |
+
outputs = [x.replace(os.sep, "/") for x in build_ext.get_outputs()]
|
| 119 |
+
assert outputs == [
|
| 120 |
+
f"{build_lib}/ext3.mp3",
|
| 121 |
+
f"{build_lib}/mypkg/ext1.mp3",
|
| 122 |
+
f"{build_lib}/mypkg/subpkg/ext2.mp3",
|
| 123 |
+
]
|
| 124 |
+
assert build_ext.get_output_mapping() == {}
|
| 125 |
+
|
| 126 |
+
# Editable build: get_output_mappings should contain everything in get_outputs
|
| 127 |
+
dist.reinitialize_command("build_ext")
|
| 128 |
+
build_ext.editable_mode = True
|
| 129 |
+
build_ext.ensure_finalized()
|
| 130 |
+
mapping = {
|
| 131 |
+
k.replace(os.sep, "/"): v.replace(os.sep, "/")
|
| 132 |
+
for k, v in build_ext.get_output_mapping().items()
|
| 133 |
+
}
|
| 134 |
+
assert mapping == {
|
| 135 |
+
f"{build_lib}/ext3.mp3": "src/ext3.mp3",
|
| 136 |
+
f"{build_lib}/mypkg/ext1.mp3": "src/mypkg/ext1.mp3",
|
| 137 |
+
f"{build_lib}/mypkg/subpkg/ext2.mp3": "src/mypkg/subpkg/ext2.mp3",
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
def test_get_output_mapping_with_stub(self, tmpdir_cwd, monkeypatch):
|
| 141 |
+
monkeypatch.setenv('SETUPTOOLS_EXT_SUFFIX', '.mp3') # make test OS-independent
|
| 142 |
+
monkeypatch.setattr('setuptools.command.build_ext.use_stubs', True)
|
| 143 |
+
dist = self.dist_with_example()
|
| 144 |
+
|
| 145 |
+
# Editable build should create compiled stubs (.pyc files only, no .py)
|
| 146 |
+
build_ext = dist.get_command_obj("build_ext")
|
| 147 |
+
build_ext.editable_mode = True
|
| 148 |
+
build_ext.ensure_finalized()
|
| 149 |
+
for ext in build_ext.extensions:
|
| 150 |
+
monkeypatch.setattr(ext, "_needs_stub", True)
|
| 151 |
+
|
| 152 |
+
build_lib = build_ext.build_lib.replace(os.sep, "/")
|
| 153 |
+
mapping = {
|
| 154 |
+
k.replace(os.sep, "/"): v.replace(os.sep, "/")
|
| 155 |
+
for k, v in build_ext.get_output_mapping().items()
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
def C(file):
|
| 159 |
+
"""Make it possible to do comparisons and tests in a OS-independent way"""
|
| 160 |
+
return _compiled_file_name(file).replace(os.sep, "/")
|
| 161 |
+
|
| 162 |
+
assert mapping == {
|
| 163 |
+
C(f"{build_lib}/ext3.py"): C("src/ext3.py"),
|
| 164 |
+
f"{build_lib}/ext3.mp3": "src/ext3.mp3",
|
| 165 |
+
C(f"{build_lib}/mypkg/ext1.py"): C("src/mypkg/ext1.py"),
|
| 166 |
+
f"{build_lib}/mypkg/ext1.mp3": "src/mypkg/ext1.mp3",
|
| 167 |
+
C(f"{build_lib}/mypkg/subpkg/ext2.py"): C("src/mypkg/subpkg/ext2.py"),
|
| 168 |
+
f"{build_lib}/mypkg/subpkg/ext2.mp3": "src/mypkg/subpkg/ext2.mp3",
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
# Ensure only the compiled stubs are present not the raw .py stub
|
| 172 |
+
assert f"{build_lib}/mypkg/ext1.py" not in mapping
|
| 173 |
+
assert f"{build_lib}/mypkg/subpkg/ext2.py" not in mapping
|
| 174 |
+
|
| 175 |
+
# Visualize what the cached stub files look like
|
| 176 |
+
example_stub = C(f"{build_lib}/mypkg/ext1.py")
|
| 177 |
+
assert example_stub in mapping
|
| 178 |
+
assert example_stub.startswith(f"{build_lib}/mypkg/__pycache__/ext1")
|
| 179 |
+
assert example_stub.endswith(".pyc")
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
class TestBuildExtInplace:
|
| 183 |
+
def get_build_ext_cmd(self, optional: bool, **opts) -> build_ext:
|
| 184 |
+
files: dict[str, str | dict[str, dict[str, str]]] = {
|
| 185 |
+
"eggs.c": "#include missingheader.h\n",
|
| 186 |
+
".build": {"lib": {}, "tmp": {}},
|
| 187 |
+
}
|
| 188 |
+
path.build(files)
|
| 189 |
+
extension = Extension('spam.eggs', ['eggs.c'], optional=optional)
|
| 190 |
+
dist = Distribution(dict(ext_modules=[extension]))
|
| 191 |
+
dist.script_name = 'setup.py'
|
| 192 |
+
cmd = build_ext(dist)
|
| 193 |
+
vars(cmd).update(build_lib=".build/lib", build_temp=".build/tmp", **opts)
|
| 194 |
+
cmd.ensure_finalized()
|
| 195 |
+
return cmd
|
| 196 |
+
|
| 197 |
+
def get_log_messages(self, caplog, capsys):
|
| 198 |
+
"""
|
| 199 |
+
Historically, distutils "logged" by printing to sys.std*.
|
| 200 |
+
Later versions adopted the logging framework. Grab
|
| 201 |
+
messages regardless of how they were captured.
|
| 202 |
+
"""
|
| 203 |
+
std = capsys.readouterr()
|
| 204 |
+
return std.out.splitlines() + std.err.splitlines() + caplog.messages
|
| 205 |
+
|
| 206 |
+
def test_optional(self, tmpdir_cwd, caplog, capsys):
|
| 207 |
+
"""
|
| 208 |
+
If optional extensions fail to build, setuptools should show the error
|
| 209 |
+
in the logs but not fail to build
|
| 210 |
+
"""
|
| 211 |
+
cmd = self.get_build_ext_cmd(optional=True, inplace=True)
|
| 212 |
+
cmd.run()
|
| 213 |
+
assert any(
|
| 214 |
+
'build_ext: building extension "spam.eggs" failed'
|
| 215 |
+
for msg in self.get_log_messages(caplog, capsys)
|
| 216 |
+
)
|
| 217 |
+
# No compile error exception should be raised
|
| 218 |
+
|
| 219 |
+
def test_non_optional(self, tmpdir_cwd):
|
| 220 |
+
# Non-optional extensions should raise an exception
|
| 221 |
+
cmd = self.get_build_ext_cmd(optional=False, inplace=True)
|
| 222 |
+
with pytest.raises(CompileError):
|
| 223 |
+
cmd.run()
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def test_build_ext_config_handling(tmpdir_cwd):
|
| 227 |
+
files = {
|
| 228 |
+
'setup.py': DALS(
|
| 229 |
+
"""
|
| 230 |
+
from setuptools import Extension, setup
|
| 231 |
+
setup(
|
| 232 |
+
name='foo',
|
| 233 |
+
version='0.0.0',
|
| 234 |
+
ext_modules=[Extension('foo', ['foo.c'])],
|
| 235 |
+
)
|
| 236 |
+
"""
|
| 237 |
+
),
|
| 238 |
+
'foo.c': DALS(
|
| 239 |
+
"""
|
| 240 |
+
#include "Python.h"
|
| 241 |
+
|
| 242 |
+
#if PY_MAJOR_VERSION >= 3
|
| 243 |
+
|
| 244 |
+
static struct PyModuleDef moduledef = {
|
| 245 |
+
PyModuleDef_HEAD_INIT,
|
| 246 |
+
"foo",
|
| 247 |
+
NULL,
|
| 248 |
+
0,
|
| 249 |
+
NULL,
|
| 250 |
+
NULL,
|
| 251 |
+
NULL,
|
| 252 |
+
NULL,
|
| 253 |
+
NULL
|
| 254 |
+
};
|
| 255 |
+
|
| 256 |
+
#define INITERROR return NULL
|
| 257 |
+
|
| 258 |
+
PyMODINIT_FUNC PyInit_foo(void)
|
| 259 |
+
|
| 260 |
+
#else
|
| 261 |
+
|
| 262 |
+
#define INITERROR return
|
| 263 |
+
|
| 264 |
+
void initfoo(void)
|
| 265 |
+
|
| 266 |
+
#endif
|
| 267 |
+
{
|
| 268 |
+
#if PY_MAJOR_VERSION >= 3
|
| 269 |
+
PyObject *module = PyModule_Create(&moduledef);
|
| 270 |
+
#else
|
| 271 |
+
PyObject *module = Py_InitModule("extension", NULL);
|
| 272 |
+
#endif
|
| 273 |
+
if (module == NULL)
|
| 274 |
+
INITERROR;
|
| 275 |
+
#if PY_MAJOR_VERSION >= 3
|
| 276 |
+
return module;
|
| 277 |
+
#endif
|
| 278 |
+
}
|
| 279 |
+
"""
|
| 280 |
+
),
|
| 281 |
+
'setup.cfg': DALS(
|
| 282 |
+
"""
|
| 283 |
+
[build]
|
| 284 |
+
build_base = foo_build
|
| 285 |
+
"""
|
| 286 |
+
),
|
| 287 |
+
}
|
| 288 |
+
path.build(files)
|
| 289 |
+
code, (stdout, stderr) = environment.run_setup_py(
|
| 290 |
+
cmd=['build'],
|
| 291 |
+
data_stream=(0, 2),
|
| 292 |
+
)
|
| 293 |
+
assert code == 0, f'\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}'
|
venv/lib/python3.11/site-packages/setuptools/tests/test_build_meta.py
ADDED
|
@@ -0,0 +1,983 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import importlib
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import shutil
|
| 6 |
+
import signal
|
| 7 |
+
import sys
|
| 8 |
+
import tarfile
|
| 9 |
+
import warnings
|
| 10 |
+
from concurrent import futures
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any, Callable
|
| 13 |
+
from zipfile import ZipFile
|
| 14 |
+
|
| 15 |
+
import pytest
|
| 16 |
+
from jaraco import path
|
| 17 |
+
from packaging.requirements import Requirement
|
| 18 |
+
|
| 19 |
+
from setuptools.warnings import SetuptoolsDeprecationWarning
|
| 20 |
+
|
| 21 |
+
from .textwrap import DALS
|
| 22 |
+
|
| 23 |
+
SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
TIMEOUT = int(os.getenv("TIMEOUT_BACKEND_TEST", "180")) # in seconds
|
| 27 |
+
IS_PYPY = '__pypy__' in sys.builtin_module_names
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
pytestmark = pytest.mark.skipif(
|
| 31 |
+
sys.platform == "win32" and IS_PYPY,
|
| 32 |
+
reason="The combination of PyPy + Windows + pytest-xdist + ProcessPoolExecutor "
|
| 33 |
+
"is flaky and problematic",
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class BuildBackendBase:
|
| 38 |
+
def __init__(self, cwd='.', env=None, backend_name='setuptools.build_meta'):
|
| 39 |
+
self.cwd = cwd
|
| 40 |
+
self.env = env or {}
|
| 41 |
+
self.backend_name = backend_name
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class BuildBackend(BuildBackendBase):
|
| 45 |
+
"""PEP 517 Build Backend"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, *args, **kwargs):
|
| 48 |
+
super().__init__(*args, **kwargs)
|
| 49 |
+
self.pool = futures.ProcessPoolExecutor(max_workers=1)
|
| 50 |
+
|
| 51 |
+
def __getattr__(self, name: str) -> Callable[..., Any]:
|
| 52 |
+
"""Handles arbitrary function invocations on the build backend."""
|
| 53 |
+
|
| 54 |
+
def method(*args, **kw):
|
| 55 |
+
root = os.path.abspath(self.cwd)
|
| 56 |
+
caller = BuildBackendCaller(root, self.env, self.backend_name)
|
| 57 |
+
pid = None
|
| 58 |
+
try:
|
| 59 |
+
pid = self.pool.submit(os.getpid).result(TIMEOUT)
|
| 60 |
+
return self.pool.submit(caller, name, *args, **kw).result(TIMEOUT)
|
| 61 |
+
except futures.TimeoutError:
|
| 62 |
+
self.pool.shutdown(wait=False) # doesn't stop already running processes
|
| 63 |
+
self._kill(pid)
|
| 64 |
+
pytest.xfail(f"Backend did not respond before timeout ({TIMEOUT} s)")
|
| 65 |
+
except (futures.process.BrokenProcessPool, MemoryError, OSError):
|
| 66 |
+
if IS_PYPY:
|
| 67 |
+
pytest.xfail("PyPy frequently fails tests with ProcessPoolExector")
|
| 68 |
+
raise
|
| 69 |
+
|
| 70 |
+
return method
|
| 71 |
+
|
| 72 |
+
def _kill(self, pid):
|
| 73 |
+
if pid is None:
|
| 74 |
+
return
|
| 75 |
+
with contextlib.suppress(ProcessLookupError, OSError):
|
| 76 |
+
os.kill(pid, signal.SIGTERM if os.name == "nt" else signal.SIGKILL)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class BuildBackendCaller(BuildBackendBase):
|
| 80 |
+
def __init__(self, *args, **kwargs):
|
| 81 |
+
super().__init__(*args, **kwargs)
|
| 82 |
+
|
| 83 |
+
(self.backend_name, _, self.backend_obj) = self.backend_name.partition(':')
|
| 84 |
+
|
| 85 |
+
def __call__(self, name, *args, **kw):
|
| 86 |
+
"""Handles arbitrary function invocations on the build backend."""
|
| 87 |
+
os.chdir(self.cwd)
|
| 88 |
+
os.environ.update(self.env)
|
| 89 |
+
mod = importlib.import_module(self.backend_name)
|
| 90 |
+
|
| 91 |
+
if self.backend_obj:
|
| 92 |
+
backend = getattr(mod, self.backend_obj)
|
| 93 |
+
else:
|
| 94 |
+
backend = mod
|
| 95 |
+
|
| 96 |
+
return getattr(backend, name)(*args, **kw)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
defns = [
|
| 100 |
+
{ # simple setup.py script
|
| 101 |
+
'setup.py': DALS(
|
| 102 |
+
"""
|
| 103 |
+
__import__('setuptools').setup(
|
| 104 |
+
name='foo',
|
| 105 |
+
version='0.0.0',
|
| 106 |
+
py_modules=['hello'],
|
| 107 |
+
setup_requires=['six'],
|
| 108 |
+
)
|
| 109 |
+
"""
|
| 110 |
+
),
|
| 111 |
+
'hello.py': DALS(
|
| 112 |
+
"""
|
| 113 |
+
def run():
|
| 114 |
+
print('hello')
|
| 115 |
+
"""
|
| 116 |
+
),
|
| 117 |
+
},
|
| 118 |
+
{ # setup.py that relies on __name__
|
| 119 |
+
'setup.py': DALS(
|
| 120 |
+
"""
|
| 121 |
+
assert __name__ == '__main__'
|
| 122 |
+
__import__('setuptools').setup(
|
| 123 |
+
name='foo',
|
| 124 |
+
version='0.0.0',
|
| 125 |
+
py_modules=['hello'],
|
| 126 |
+
setup_requires=['six'],
|
| 127 |
+
)
|
| 128 |
+
"""
|
| 129 |
+
),
|
| 130 |
+
'hello.py': DALS(
|
| 131 |
+
"""
|
| 132 |
+
def run():
|
| 133 |
+
print('hello')
|
| 134 |
+
"""
|
| 135 |
+
),
|
| 136 |
+
},
|
| 137 |
+
{ # setup.py script that runs arbitrary code
|
| 138 |
+
'setup.py': DALS(
|
| 139 |
+
"""
|
| 140 |
+
variable = True
|
| 141 |
+
def function():
|
| 142 |
+
return variable
|
| 143 |
+
assert variable
|
| 144 |
+
__import__('setuptools').setup(
|
| 145 |
+
name='foo',
|
| 146 |
+
version='0.0.0',
|
| 147 |
+
py_modules=['hello'],
|
| 148 |
+
setup_requires=['six'],
|
| 149 |
+
)
|
| 150 |
+
"""
|
| 151 |
+
),
|
| 152 |
+
'hello.py': DALS(
|
| 153 |
+
"""
|
| 154 |
+
def run():
|
| 155 |
+
print('hello')
|
| 156 |
+
"""
|
| 157 |
+
),
|
| 158 |
+
},
|
| 159 |
+
{ # setup.py script that constructs temp files to be included in the distribution
|
| 160 |
+
'setup.py': DALS(
|
| 161 |
+
"""
|
| 162 |
+
# Some packages construct files on the fly, include them in the package,
|
| 163 |
+
# and immediately remove them after `setup()` (e.g. pybind11==2.9.1).
|
| 164 |
+
# Therefore, we cannot use `distutils.core.run_setup(..., stop_after=...)`
|
| 165 |
+
# to obtain a distribution object first, and then run the distutils
|
| 166 |
+
# commands later, because these files will be removed in the meantime.
|
| 167 |
+
|
| 168 |
+
with open('world.py', 'w', encoding="utf-8") as f:
|
| 169 |
+
f.write('x = 42')
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
__import__('setuptools').setup(
|
| 173 |
+
name='foo',
|
| 174 |
+
version='0.0.0',
|
| 175 |
+
py_modules=['world'],
|
| 176 |
+
setup_requires=['six'],
|
| 177 |
+
)
|
| 178 |
+
finally:
|
| 179 |
+
# Some packages will clean temporary files
|
| 180 |
+
__import__('os').unlink('world.py')
|
| 181 |
+
"""
|
| 182 |
+
),
|
| 183 |
+
},
|
| 184 |
+
{ # setup.cfg only
|
| 185 |
+
'setup.cfg': DALS(
|
| 186 |
+
"""
|
| 187 |
+
[metadata]
|
| 188 |
+
name = foo
|
| 189 |
+
version = 0.0.0
|
| 190 |
+
|
| 191 |
+
[options]
|
| 192 |
+
py_modules=hello
|
| 193 |
+
setup_requires=six
|
| 194 |
+
"""
|
| 195 |
+
),
|
| 196 |
+
'hello.py': DALS(
|
| 197 |
+
"""
|
| 198 |
+
def run():
|
| 199 |
+
print('hello')
|
| 200 |
+
"""
|
| 201 |
+
),
|
| 202 |
+
},
|
| 203 |
+
{ # setup.cfg and setup.py
|
| 204 |
+
'setup.cfg': DALS(
|
| 205 |
+
"""
|
| 206 |
+
[metadata]
|
| 207 |
+
name = foo
|
| 208 |
+
version = 0.0.0
|
| 209 |
+
|
| 210 |
+
[options]
|
| 211 |
+
py_modules=hello
|
| 212 |
+
setup_requires=six
|
| 213 |
+
"""
|
| 214 |
+
),
|
| 215 |
+
'setup.py': "__import__('setuptools').setup()",
|
| 216 |
+
'hello.py': DALS(
|
| 217 |
+
"""
|
| 218 |
+
def run():
|
| 219 |
+
print('hello')
|
| 220 |
+
"""
|
| 221 |
+
),
|
| 222 |
+
},
|
| 223 |
+
]
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
class TestBuildMetaBackend:
|
| 227 |
+
backend_name = 'setuptools.build_meta'
|
| 228 |
+
|
| 229 |
+
def get_build_backend(self):
|
| 230 |
+
return BuildBackend(backend_name=self.backend_name)
|
| 231 |
+
|
| 232 |
+
@pytest.fixture(params=defns)
|
| 233 |
+
def build_backend(self, tmpdir, request):
|
| 234 |
+
path.build(request.param, prefix=str(tmpdir))
|
| 235 |
+
with tmpdir.as_cwd():
|
| 236 |
+
yield self.get_build_backend()
|
| 237 |
+
|
| 238 |
+
def test_get_requires_for_build_wheel(self, build_backend):
|
| 239 |
+
actual = build_backend.get_requires_for_build_wheel()
|
| 240 |
+
expected = ['six']
|
| 241 |
+
assert sorted(actual) == sorted(expected)
|
| 242 |
+
|
| 243 |
+
def test_get_requires_for_build_sdist(self, build_backend):
|
| 244 |
+
actual = build_backend.get_requires_for_build_sdist()
|
| 245 |
+
expected = ['six']
|
| 246 |
+
assert sorted(actual) == sorted(expected)
|
| 247 |
+
|
| 248 |
+
def test_build_wheel(self, build_backend):
|
| 249 |
+
dist_dir = os.path.abspath('pip-wheel')
|
| 250 |
+
os.makedirs(dist_dir)
|
| 251 |
+
wheel_name = build_backend.build_wheel(dist_dir)
|
| 252 |
+
|
| 253 |
+
wheel_file = os.path.join(dist_dir, wheel_name)
|
| 254 |
+
assert os.path.isfile(wheel_file)
|
| 255 |
+
|
| 256 |
+
# Temporary files should be removed
|
| 257 |
+
assert not os.path.isfile('world.py')
|
| 258 |
+
|
| 259 |
+
with ZipFile(wheel_file) as zipfile:
|
| 260 |
+
wheel_contents = set(zipfile.namelist())
|
| 261 |
+
|
| 262 |
+
# Each one of the examples have a single module
|
| 263 |
+
# that should be included in the distribution
|
| 264 |
+
python_scripts = (f for f in wheel_contents if f.endswith('.py'))
|
| 265 |
+
modules = [f for f in python_scripts if not f.endswith('setup.py')]
|
| 266 |
+
assert len(modules) == 1
|
| 267 |
+
|
| 268 |
+
@pytest.mark.parametrize('build_type', ('wheel', 'sdist'))
|
| 269 |
+
def test_build_with_existing_file_present(self, build_type, tmpdir_cwd):
|
| 270 |
+
# Building a sdist/wheel should still succeed if there's
|
| 271 |
+
# already a sdist/wheel in the destination directory.
|
| 272 |
+
files = {
|
| 273 |
+
'setup.py': "from setuptools import setup\nsetup()",
|
| 274 |
+
'VERSION': "0.0.1",
|
| 275 |
+
'setup.cfg': DALS(
|
| 276 |
+
"""
|
| 277 |
+
[metadata]
|
| 278 |
+
name = foo
|
| 279 |
+
version = file: VERSION
|
| 280 |
+
"""
|
| 281 |
+
),
|
| 282 |
+
'pyproject.toml': DALS(
|
| 283 |
+
"""
|
| 284 |
+
[build-system]
|
| 285 |
+
requires = ["setuptools", "wheel"]
|
| 286 |
+
build-backend = "setuptools.build_meta"
|
| 287 |
+
"""
|
| 288 |
+
),
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
path.build(files)
|
| 292 |
+
|
| 293 |
+
dist_dir = os.path.abspath('preexisting-' + build_type)
|
| 294 |
+
|
| 295 |
+
build_backend = self.get_build_backend()
|
| 296 |
+
build_method = getattr(build_backend, 'build_' + build_type)
|
| 297 |
+
|
| 298 |
+
# Build a first sdist/wheel.
|
| 299 |
+
# Note: this also check the destination directory is
|
| 300 |
+
# successfully created if it does not exist already.
|
| 301 |
+
first_result = build_method(dist_dir)
|
| 302 |
+
|
| 303 |
+
# Change version.
|
| 304 |
+
with open("VERSION", "wt", encoding="utf-8") as version_file:
|
| 305 |
+
version_file.write("0.0.2")
|
| 306 |
+
|
| 307 |
+
# Build a *second* sdist/wheel.
|
| 308 |
+
second_result = build_method(dist_dir)
|
| 309 |
+
|
| 310 |
+
assert os.path.isfile(os.path.join(dist_dir, first_result))
|
| 311 |
+
assert first_result != second_result
|
| 312 |
+
|
| 313 |
+
# And if rebuilding the exact same sdist/wheel?
|
| 314 |
+
open(os.path.join(dist_dir, second_result), 'wb').close()
|
| 315 |
+
third_result = build_method(dist_dir)
|
| 316 |
+
assert third_result == second_result
|
| 317 |
+
assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0
|
| 318 |
+
|
| 319 |
+
@pytest.mark.parametrize("setup_script", [None, SETUP_SCRIPT_STUB])
|
| 320 |
+
def test_build_with_pyproject_config(self, tmpdir, setup_script):
|
| 321 |
+
files = {
|
| 322 |
+
'pyproject.toml': DALS(
|
| 323 |
+
"""
|
| 324 |
+
[build-system]
|
| 325 |
+
requires = ["setuptools", "wheel"]
|
| 326 |
+
build-backend = "setuptools.build_meta"
|
| 327 |
+
|
| 328 |
+
[project]
|
| 329 |
+
name = "foo"
|
| 330 |
+
license = {text = "MIT"}
|
| 331 |
+
description = "This is a Python package"
|
| 332 |
+
dynamic = ["version", "readme"]
|
| 333 |
+
classifiers = [
|
| 334 |
+
"Development Status :: 5 - Production/Stable",
|
| 335 |
+
"Intended Audience :: Developers"
|
| 336 |
+
]
|
| 337 |
+
urls = {Homepage = "http://github.com"}
|
| 338 |
+
dependencies = [
|
| 339 |
+
"appdirs",
|
| 340 |
+
]
|
| 341 |
+
|
| 342 |
+
[project.optional-dependencies]
|
| 343 |
+
all = [
|
| 344 |
+
"tomli>=1",
|
| 345 |
+
"pyscaffold>=4,<5",
|
| 346 |
+
'importlib; python_version == "2.6"',
|
| 347 |
+
]
|
| 348 |
+
|
| 349 |
+
[project.scripts]
|
| 350 |
+
foo = "foo.cli:main"
|
| 351 |
+
|
| 352 |
+
[tool.setuptools]
|
| 353 |
+
zip-safe = false
|
| 354 |
+
package-dir = {"" = "src"}
|
| 355 |
+
packages = {find = {where = ["src"]}}
|
| 356 |
+
license-files = ["LICENSE*"]
|
| 357 |
+
|
| 358 |
+
[tool.setuptools.dynamic]
|
| 359 |
+
version = {attr = "foo.__version__"}
|
| 360 |
+
readme = {file = "README.rst"}
|
| 361 |
+
|
| 362 |
+
[tool.distutils.sdist]
|
| 363 |
+
formats = "gztar"
|
| 364 |
+
"""
|
| 365 |
+
),
|
| 366 |
+
"MANIFEST.in": DALS(
|
| 367 |
+
"""
|
| 368 |
+
global-include *.py *.txt
|
| 369 |
+
global-exclude *.py[cod]
|
| 370 |
+
"""
|
| 371 |
+
),
|
| 372 |
+
"README.rst": "This is a ``README``",
|
| 373 |
+
"LICENSE.txt": "---- placeholder MIT license ----",
|
| 374 |
+
"src": {
|
| 375 |
+
"foo": {
|
| 376 |
+
"__init__.py": "__version__ = '0.1'",
|
| 377 |
+
"__init__.pyi": "__version__: str",
|
| 378 |
+
"cli.py": "def main(): print('hello world')",
|
| 379 |
+
"data.txt": "def main(): print('hello world')",
|
| 380 |
+
"py.typed": "",
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
}
|
| 384 |
+
if setup_script:
|
| 385 |
+
files["setup.py"] = setup_script
|
| 386 |
+
|
| 387 |
+
build_backend = self.get_build_backend()
|
| 388 |
+
with tmpdir.as_cwd():
|
| 389 |
+
path.build(files)
|
| 390 |
+
msgs = [
|
| 391 |
+
"'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'",
|
| 392 |
+
"`project.license` as a TOML table is deprecated",
|
| 393 |
+
]
|
| 394 |
+
with warnings.catch_warnings():
|
| 395 |
+
for msg in msgs:
|
| 396 |
+
warnings.filterwarnings("ignore", msg, SetuptoolsDeprecationWarning)
|
| 397 |
+
sdist_path = build_backend.build_sdist("temp")
|
| 398 |
+
wheel_file = build_backend.build_wheel("temp")
|
| 399 |
+
|
| 400 |
+
with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
|
| 401 |
+
sdist_contents = set(tar.getnames())
|
| 402 |
+
|
| 403 |
+
with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
|
| 404 |
+
wheel_contents = set(zipfile.namelist())
|
| 405 |
+
metadata = str(zipfile.read("foo-0.1.dist-info/METADATA"), "utf-8")
|
| 406 |
+
license = str(
|
| 407 |
+
zipfile.read("foo-0.1.dist-info/licenses/LICENSE.txt"), "utf-8"
|
| 408 |
+
)
|
| 409 |
+
epoints = str(zipfile.read("foo-0.1.dist-info/entry_points.txt"), "utf-8")
|
| 410 |
+
|
| 411 |
+
assert sdist_contents - {"foo-0.1/setup.py"} == {
|
| 412 |
+
'foo-0.1',
|
| 413 |
+
'foo-0.1/LICENSE.txt',
|
| 414 |
+
'foo-0.1/MANIFEST.in',
|
| 415 |
+
'foo-0.1/PKG-INFO',
|
| 416 |
+
'foo-0.1/README.rst',
|
| 417 |
+
'foo-0.1/pyproject.toml',
|
| 418 |
+
'foo-0.1/setup.cfg',
|
| 419 |
+
'foo-0.1/src',
|
| 420 |
+
'foo-0.1/src/foo',
|
| 421 |
+
'foo-0.1/src/foo/__init__.py',
|
| 422 |
+
'foo-0.1/src/foo/__init__.pyi',
|
| 423 |
+
'foo-0.1/src/foo/cli.py',
|
| 424 |
+
'foo-0.1/src/foo/data.txt',
|
| 425 |
+
'foo-0.1/src/foo/py.typed',
|
| 426 |
+
'foo-0.1/src/foo.egg-info',
|
| 427 |
+
'foo-0.1/src/foo.egg-info/PKG-INFO',
|
| 428 |
+
'foo-0.1/src/foo.egg-info/SOURCES.txt',
|
| 429 |
+
'foo-0.1/src/foo.egg-info/dependency_links.txt',
|
| 430 |
+
'foo-0.1/src/foo.egg-info/entry_points.txt',
|
| 431 |
+
'foo-0.1/src/foo.egg-info/requires.txt',
|
| 432 |
+
'foo-0.1/src/foo.egg-info/top_level.txt',
|
| 433 |
+
'foo-0.1/src/foo.egg-info/not-zip-safe',
|
| 434 |
+
}
|
| 435 |
+
assert wheel_contents == {
|
| 436 |
+
"foo/__init__.py",
|
| 437 |
+
"foo/__init__.pyi", # include type information by default
|
| 438 |
+
"foo/cli.py",
|
| 439 |
+
"foo/data.txt", # include_package_data defaults to True
|
| 440 |
+
"foo/py.typed", # include type information by default
|
| 441 |
+
"foo-0.1.dist-info/licenses/LICENSE.txt",
|
| 442 |
+
"foo-0.1.dist-info/METADATA",
|
| 443 |
+
"foo-0.1.dist-info/WHEEL",
|
| 444 |
+
"foo-0.1.dist-info/entry_points.txt",
|
| 445 |
+
"foo-0.1.dist-info/top_level.txt",
|
| 446 |
+
"foo-0.1.dist-info/RECORD",
|
| 447 |
+
}
|
| 448 |
+
assert license == "---- placeholder MIT license ----"
|
| 449 |
+
|
| 450 |
+
for line in (
|
| 451 |
+
"Summary: This is a Python package",
|
| 452 |
+
"License: MIT",
|
| 453 |
+
"License-File: LICENSE.txt",
|
| 454 |
+
"Classifier: Intended Audience :: Developers",
|
| 455 |
+
"Requires-Dist: appdirs",
|
| 456 |
+
"Requires-Dist: " + str(Requirement('tomli>=1 ; extra == "all"')),
|
| 457 |
+
"Requires-Dist: "
|
| 458 |
+
+ str(Requirement('importlib; python_version=="2.6" and extra =="all"')),
|
| 459 |
+
):
|
| 460 |
+
assert line in metadata, (line, metadata)
|
| 461 |
+
|
| 462 |
+
assert metadata.strip().endswith("This is a ``README``")
|
| 463 |
+
assert epoints.strip() == "[console_scripts]\nfoo = foo.cli:main"
|
| 464 |
+
|
| 465 |
+
def test_static_metadata_in_pyproject_config(self, tmpdir):
|
| 466 |
+
# Make sure static metadata in pyproject.toml is not overwritten by setup.py
|
| 467 |
+
# as required by PEP 621
|
| 468 |
+
files = {
|
| 469 |
+
'pyproject.toml': DALS(
|
| 470 |
+
"""
|
| 471 |
+
[build-system]
|
| 472 |
+
requires = ["setuptools", "wheel"]
|
| 473 |
+
build-backend = "setuptools.build_meta"
|
| 474 |
+
|
| 475 |
+
[project]
|
| 476 |
+
name = "foo"
|
| 477 |
+
description = "This is a Python package"
|
| 478 |
+
version = "42"
|
| 479 |
+
dependencies = ["six"]
|
| 480 |
+
"""
|
| 481 |
+
),
|
| 482 |
+
'hello.py': DALS(
|
| 483 |
+
"""
|
| 484 |
+
def run():
|
| 485 |
+
print('hello')
|
| 486 |
+
"""
|
| 487 |
+
),
|
| 488 |
+
'setup.py': DALS(
|
| 489 |
+
"""
|
| 490 |
+
__import__('setuptools').setup(
|
| 491 |
+
name='bar',
|
| 492 |
+
version='13',
|
| 493 |
+
)
|
| 494 |
+
"""
|
| 495 |
+
),
|
| 496 |
+
}
|
| 497 |
+
build_backend = self.get_build_backend()
|
| 498 |
+
with tmpdir.as_cwd():
|
| 499 |
+
path.build(files)
|
| 500 |
+
sdist_path = build_backend.build_sdist("temp")
|
| 501 |
+
wheel_file = build_backend.build_wheel("temp")
|
| 502 |
+
|
| 503 |
+
assert (tmpdir / "temp/foo-42.tar.gz").exists()
|
| 504 |
+
assert (tmpdir / "temp/foo-42-py3-none-any.whl").exists()
|
| 505 |
+
assert not (tmpdir / "temp/bar-13.tar.gz").exists()
|
| 506 |
+
assert not (tmpdir / "temp/bar-42.tar.gz").exists()
|
| 507 |
+
assert not (tmpdir / "temp/foo-13.tar.gz").exists()
|
| 508 |
+
assert not (tmpdir / "temp/bar-13-py3-none-any.whl").exists()
|
| 509 |
+
assert not (tmpdir / "temp/bar-42-py3-none-any.whl").exists()
|
| 510 |
+
assert not (tmpdir / "temp/foo-13-py3-none-any.whl").exists()
|
| 511 |
+
|
| 512 |
+
with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
|
| 513 |
+
pkg_info = str(tar.extractfile('foo-42/PKG-INFO').read(), "utf-8")
|
| 514 |
+
members = tar.getnames()
|
| 515 |
+
assert "bar-13/PKG-INFO" not in members
|
| 516 |
+
|
| 517 |
+
with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
|
| 518 |
+
metadata = str(zipfile.read("foo-42.dist-info/METADATA"), "utf-8")
|
| 519 |
+
members = zipfile.namelist()
|
| 520 |
+
assert "bar-13.dist-info/METADATA" not in members
|
| 521 |
+
|
| 522 |
+
for file in pkg_info, metadata:
|
| 523 |
+
for line in ("Name: foo", "Version: 42"):
|
| 524 |
+
assert line in file
|
| 525 |
+
for line in ("Name: bar", "Version: 13"):
|
| 526 |
+
assert line not in file
|
| 527 |
+
|
| 528 |
+
def test_build_sdist(self, build_backend):
|
| 529 |
+
dist_dir = os.path.abspath('pip-sdist')
|
| 530 |
+
os.makedirs(dist_dir)
|
| 531 |
+
sdist_name = build_backend.build_sdist(dist_dir)
|
| 532 |
+
|
| 533 |
+
assert os.path.isfile(os.path.join(dist_dir, sdist_name))
|
| 534 |
+
|
| 535 |
+
def test_prepare_metadata_for_build_wheel(self, build_backend):
|
| 536 |
+
dist_dir = os.path.abspath('pip-dist-info')
|
| 537 |
+
os.makedirs(dist_dir)
|
| 538 |
+
|
| 539 |
+
dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
|
| 540 |
+
|
| 541 |
+
assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
|
| 542 |
+
|
| 543 |
+
def test_prepare_metadata_inplace(self, build_backend):
|
| 544 |
+
"""
|
| 545 |
+
Some users might pass metadata_directory pre-populated with `.tox` or `.venv`.
|
| 546 |
+
See issue #3523.
|
| 547 |
+
"""
|
| 548 |
+
for pre_existing in [
|
| 549 |
+
".tox/python/lib/python3.10/site-packages/attrs-22.1.0.dist-info",
|
| 550 |
+
".tox/python/lib/python3.10/site-packages/autocommand-2.2.1.dist-info",
|
| 551 |
+
".nox/python/lib/python3.10/site-packages/build-0.8.0.dist-info",
|
| 552 |
+
".venv/python3.10/site-packages/click-8.1.3.dist-info",
|
| 553 |
+
"venv/python3.10/site-packages/distlib-0.3.5.dist-info",
|
| 554 |
+
"env/python3.10/site-packages/docutils-0.19.dist-info",
|
| 555 |
+
]:
|
| 556 |
+
os.makedirs(pre_existing, exist_ok=True)
|
| 557 |
+
dist_info = build_backend.prepare_metadata_for_build_wheel(".")
|
| 558 |
+
assert os.path.isfile(os.path.join(dist_info, 'METADATA'))
|
| 559 |
+
|
| 560 |
+
def test_build_sdist_explicit_dist(self, build_backend):
|
| 561 |
+
# explicitly specifying the dist folder should work
|
| 562 |
+
# the folder sdist_directory and the ``--dist-dir`` can be the same
|
| 563 |
+
dist_dir = os.path.abspath('dist')
|
| 564 |
+
sdist_name = build_backend.build_sdist(dist_dir)
|
| 565 |
+
assert os.path.isfile(os.path.join(dist_dir, sdist_name))
|
| 566 |
+
|
| 567 |
+
def test_build_sdist_version_change(self, build_backend):
|
| 568 |
+
sdist_into_directory = os.path.abspath("out_sdist")
|
| 569 |
+
os.makedirs(sdist_into_directory)
|
| 570 |
+
|
| 571 |
+
sdist_name = build_backend.build_sdist(sdist_into_directory)
|
| 572 |
+
assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
|
| 573 |
+
|
| 574 |
+
# if the setup.py changes subsequent call of the build meta
|
| 575 |
+
# should still succeed, given the
|
| 576 |
+
# sdist_directory the frontend specifies is empty
|
| 577 |
+
setup_loc = os.path.abspath("setup.py")
|
| 578 |
+
if not os.path.exists(setup_loc):
|
| 579 |
+
setup_loc = os.path.abspath("setup.cfg")
|
| 580 |
+
|
| 581 |
+
with open(setup_loc, 'rt', encoding="utf-8") as file_handler:
|
| 582 |
+
content = file_handler.read()
|
| 583 |
+
with open(setup_loc, 'wt', encoding="utf-8") as file_handler:
|
| 584 |
+
file_handler.write(content.replace("version='0.0.0'", "version='0.0.1'"))
|
| 585 |
+
|
| 586 |
+
shutil.rmtree(sdist_into_directory)
|
| 587 |
+
os.makedirs(sdist_into_directory)
|
| 588 |
+
|
| 589 |
+
sdist_name = build_backend.build_sdist("out_sdist")
|
| 590 |
+
assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name))
|
| 591 |
+
|
| 592 |
+
def test_build_sdist_pyproject_toml_exists(self, tmpdir_cwd):
|
| 593 |
+
files = {
|
| 594 |
+
'setup.py': DALS(
|
| 595 |
+
"""
|
| 596 |
+
__import__('setuptools').setup(
|
| 597 |
+
name='foo',
|
| 598 |
+
version='0.0.0',
|
| 599 |
+
py_modules=['hello']
|
| 600 |
+
)"""
|
| 601 |
+
),
|
| 602 |
+
'hello.py': '',
|
| 603 |
+
'pyproject.toml': DALS(
|
| 604 |
+
"""
|
| 605 |
+
[build-system]
|
| 606 |
+
requires = ["setuptools", "wheel"]
|
| 607 |
+
build-backend = "setuptools.build_meta"
|
| 608 |
+
"""
|
| 609 |
+
),
|
| 610 |
+
}
|
| 611 |
+
path.build(files)
|
| 612 |
+
build_backend = self.get_build_backend()
|
| 613 |
+
targz_path = build_backend.build_sdist("temp")
|
| 614 |
+
with tarfile.open(os.path.join("temp", targz_path)) as tar:
|
| 615 |
+
assert any('pyproject.toml' in name for name in tar.getnames())
|
| 616 |
+
|
| 617 |
+
def test_build_sdist_setup_py_exists(self, tmpdir_cwd):
|
| 618 |
+
# If build_sdist is called from a script other than setup.py,
|
| 619 |
+
# ensure setup.py is included
|
| 620 |
+
path.build(defns[0])
|
| 621 |
+
|
| 622 |
+
build_backend = self.get_build_backend()
|
| 623 |
+
targz_path = build_backend.build_sdist("temp")
|
| 624 |
+
with tarfile.open(os.path.join("temp", targz_path)) as tar:
|
| 625 |
+
assert any('setup.py' in name for name in tar.getnames())
|
| 626 |
+
|
| 627 |
+
def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd):
|
| 628 |
+
# Ensure that MANIFEST.in can exclude setup.py
|
| 629 |
+
files = {
|
| 630 |
+
'setup.py': DALS(
|
| 631 |
+
"""
|
| 632 |
+
__import__('setuptools').setup(
|
| 633 |
+
name='foo',
|
| 634 |
+
version='0.0.0',
|
| 635 |
+
py_modules=['hello']
|
| 636 |
+
)"""
|
| 637 |
+
),
|
| 638 |
+
'hello.py': '',
|
| 639 |
+
'MANIFEST.in': DALS(
|
| 640 |
+
"""
|
| 641 |
+
exclude setup.py
|
| 642 |
+
"""
|
| 643 |
+
),
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
path.build(files)
|
| 647 |
+
|
| 648 |
+
build_backend = self.get_build_backend()
|
| 649 |
+
targz_path = build_backend.build_sdist("temp")
|
| 650 |
+
with tarfile.open(os.path.join("temp", targz_path)) as tar:
|
| 651 |
+
assert not any('setup.py' in name for name in tar.getnames())
|
| 652 |
+
|
| 653 |
+
def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd):
|
| 654 |
+
files = {
|
| 655 |
+
'setup.py': DALS(
|
| 656 |
+
"""
|
| 657 |
+
__import__('setuptools').setup(
|
| 658 |
+
name='foo',
|
| 659 |
+
version='0.0.0',
|
| 660 |
+
py_modules=['hello']
|
| 661 |
+
)"""
|
| 662 |
+
),
|
| 663 |
+
'hello.py': '',
|
| 664 |
+
'setup.cfg': DALS(
|
| 665 |
+
"""
|
| 666 |
+
[sdist]
|
| 667 |
+
formats=zip
|
| 668 |
+
"""
|
| 669 |
+
),
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
path.build(files)
|
| 673 |
+
|
| 674 |
+
build_backend = self.get_build_backend()
|
| 675 |
+
build_backend.build_sdist("temp")
|
| 676 |
+
|
| 677 |
+
_relative_path_import_files = {
|
| 678 |
+
'setup.py': DALS(
|
| 679 |
+
"""
|
| 680 |
+
__import__('setuptools').setup(
|
| 681 |
+
name='foo',
|
| 682 |
+
version=__import__('hello').__version__,
|
| 683 |
+
py_modules=['hello']
|
| 684 |
+
)"""
|
| 685 |
+
),
|
| 686 |
+
'hello.py': '__version__ = "0.0.0"',
|
| 687 |
+
'setup.cfg': DALS(
|
| 688 |
+
"""
|
| 689 |
+
[sdist]
|
| 690 |
+
formats=zip
|
| 691 |
+
"""
|
| 692 |
+
),
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
def test_build_sdist_relative_path_import(self, tmpdir_cwd):
|
| 696 |
+
path.build(self._relative_path_import_files)
|
| 697 |
+
build_backend = self.get_build_backend()
|
| 698 |
+
with pytest.raises(ImportError, match="^No module named 'hello'$"):
|
| 699 |
+
build_backend.build_sdist("temp")
|
| 700 |
+
|
| 701 |
+
_simple_pyproject_example = {
|
| 702 |
+
"pyproject.toml": DALS(
|
| 703 |
+
"""
|
| 704 |
+
[project]
|
| 705 |
+
name = "proj"
|
| 706 |
+
version = "42"
|
| 707 |
+
"""
|
| 708 |
+
),
|
| 709 |
+
"src": {"proj": {"__init__.py": ""}},
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
def _assert_link_tree(self, parent_dir):
|
| 713 |
+
"""All files in the directory should be either links or hard links"""
|
| 714 |
+
files = list(Path(parent_dir).glob("**/*"))
|
| 715 |
+
assert files # Should not be empty
|
| 716 |
+
for file in files:
|
| 717 |
+
assert file.is_symlink() or os.stat(file).st_nlink > 0
|
| 718 |
+
|
| 719 |
+
def test_editable_without_config_settings(self, tmpdir_cwd):
|
| 720 |
+
"""
|
| 721 |
+
Sanity check to ensure tests with --mode=strict are different from the ones
|
| 722 |
+
without --mode.
|
| 723 |
+
|
| 724 |
+
--mode=strict should create a local directory with a package tree.
|
| 725 |
+
The directory should not get created otherwise.
|
| 726 |
+
"""
|
| 727 |
+
path.build(self._simple_pyproject_example)
|
| 728 |
+
build_backend = self.get_build_backend()
|
| 729 |
+
assert not Path("build").exists()
|
| 730 |
+
build_backend.build_editable("temp")
|
| 731 |
+
assert not Path("build").exists()
|
| 732 |
+
|
| 733 |
+
def test_build_wheel_inplace(self, tmpdir_cwd):
|
| 734 |
+
config_settings = {"--build-option": ["build_ext", "--inplace"]}
|
| 735 |
+
path.build(self._simple_pyproject_example)
|
| 736 |
+
build_backend = self.get_build_backend()
|
| 737 |
+
assert not Path("build").exists()
|
| 738 |
+
Path("build").mkdir()
|
| 739 |
+
build_backend.prepare_metadata_for_build_wheel("build", config_settings)
|
| 740 |
+
build_backend.build_wheel("build", config_settings)
|
| 741 |
+
assert Path("build/proj-42-py3-none-any.whl").exists()
|
| 742 |
+
|
| 743 |
+
@pytest.mark.parametrize("config_settings", [{"editable-mode": "strict"}])
|
| 744 |
+
def test_editable_with_config_settings(self, tmpdir_cwd, config_settings):
|
| 745 |
+
path.build({**self._simple_pyproject_example, '_meta': {}})
|
| 746 |
+
assert not Path("build").exists()
|
| 747 |
+
build_backend = self.get_build_backend()
|
| 748 |
+
build_backend.prepare_metadata_for_build_editable("_meta", config_settings)
|
| 749 |
+
build_backend.build_editable("temp", config_settings, "_meta")
|
| 750 |
+
self._assert_link_tree(next(Path("build").glob("__editable__.*")))
|
| 751 |
+
|
| 752 |
+
@pytest.mark.parametrize(
|
| 753 |
+
("setup_literal", "requirements"),
|
| 754 |
+
[
|
| 755 |
+
("'foo'", ['foo']),
|
| 756 |
+
("['foo']", ['foo']),
|
| 757 |
+
(r"'foo\n'", ['foo']),
|
| 758 |
+
(r"'foo\n\n'", ['foo']),
|
| 759 |
+
("['foo', 'bar']", ['foo', 'bar']),
|
| 760 |
+
(r"'# Has a comment line\nfoo'", ['foo']),
|
| 761 |
+
(r"'foo # Has an inline comment'", ['foo']),
|
| 762 |
+
(r"'foo \\\n >=3.0'", ['foo>=3.0']),
|
| 763 |
+
(r"'foo\nbar'", ['foo', 'bar']),
|
| 764 |
+
(r"'foo\nbar\n'", ['foo', 'bar']),
|
| 765 |
+
(r"['foo\n', 'bar\n']", ['foo', 'bar']),
|
| 766 |
+
],
|
| 767 |
+
)
|
| 768 |
+
@pytest.mark.parametrize('use_wheel', [True, False])
|
| 769 |
+
def test_setup_requires(self, setup_literal, requirements, use_wheel, tmpdir_cwd):
|
| 770 |
+
files = {
|
| 771 |
+
'setup.py': DALS(
|
| 772 |
+
"""
|
| 773 |
+
from setuptools import setup
|
| 774 |
+
|
| 775 |
+
setup(
|
| 776 |
+
name="qux",
|
| 777 |
+
version="0.0.0",
|
| 778 |
+
py_modules=["hello"],
|
| 779 |
+
setup_requires={setup_literal},
|
| 780 |
+
)
|
| 781 |
+
"""
|
| 782 |
+
).format(setup_literal=setup_literal),
|
| 783 |
+
'hello.py': DALS(
|
| 784 |
+
"""
|
| 785 |
+
def run():
|
| 786 |
+
print('hello')
|
| 787 |
+
"""
|
| 788 |
+
),
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
+
path.build(files)
|
| 792 |
+
|
| 793 |
+
build_backend = self.get_build_backend()
|
| 794 |
+
|
| 795 |
+
if use_wheel:
|
| 796 |
+
get_requires = build_backend.get_requires_for_build_wheel
|
| 797 |
+
else:
|
| 798 |
+
get_requires = build_backend.get_requires_for_build_sdist
|
| 799 |
+
|
| 800 |
+
# Ensure that the build requirements are properly parsed
|
| 801 |
+
expected = sorted(requirements)
|
| 802 |
+
actual = get_requires()
|
| 803 |
+
|
| 804 |
+
assert expected == sorted(actual)
|
| 805 |
+
|
| 806 |
+
def test_setup_requires_with_auto_discovery(self, tmpdir_cwd):
|
| 807 |
+
# Make sure patches introduced to retrieve setup_requires don't accidentally
|
| 808 |
+
# activate auto-discovery and cause problems due to the incomplete set of
|
| 809 |
+
# attributes passed to MinimalDistribution
|
| 810 |
+
files = {
|
| 811 |
+
'pyproject.toml': DALS(
|
| 812 |
+
"""
|
| 813 |
+
[project]
|
| 814 |
+
name = "proj"
|
| 815 |
+
version = "42"
|
| 816 |
+
"""
|
| 817 |
+
),
|
| 818 |
+
"setup.py": DALS(
|
| 819 |
+
"""
|
| 820 |
+
__import__('setuptools').setup(
|
| 821 |
+
setup_requires=["foo"],
|
| 822 |
+
py_modules = ["hello", "world"]
|
| 823 |
+
)
|
| 824 |
+
"""
|
| 825 |
+
),
|
| 826 |
+
'hello.py': "'hello'",
|
| 827 |
+
'world.py': "'world'",
|
| 828 |
+
}
|
| 829 |
+
path.build(files)
|
| 830 |
+
build_backend = self.get_build_backend()
|
| 831 |
+
setup_requires = build_backend.get_requires_for_build_wheel()
|
| 832 |
+
assert setup_requires == ["foo"]
|
| 833 |
+
|
| 834 |
+
def test_dont_install_setup_requires(self, tmpdir_cwd):
|
| 835 |
+
files = {
|
| 836 |
+
'setup.py': DALS(
|
| 837 |
+
"""
|
| 838 |
+
from setuptools import setup
|
| 839 |
+
|
| 840 |
+
setup(
|
| 841 |
+
name="qux",
|
| 842 |
+
version="0.0.0",
|
| 843 |
+
py_modules=["hello"],
|
| 844 |
+
setup_requires=["does-not-exist >99"],
|
| 845 |
+
)
|
| 846 |
+
"""
|
| 847 |
+
),
|
| 848 |
+
'hello.py': DALS(
|
| 849 |
+
"""
|
| 850 |
+
def run():
|
| 851 |
+
print('hello')
|
| 852 |
+
"""
|
| 853 |
+
),
|
| 854 |
+
}
|
| 855 |
+
|
| 856 |
+
path.build(files)
|
| 857 |
+
|
| 858 |
+
build_backend = self.get_build_backend()
|
| 859 |
+
|
| 860 |
+
dist_dir = os.path.abspath('pip-dist-info')
|
| 861 |
+
os.makedirs(dist_dir)
|
| 862 |
+
|
| 863 |
+
# does-not-exist can't be satisfied, so if it attempts to install
|
| 864 |
+
# setup_requires, it will fail.
|
| 865 |
+
build_backend.prepare_metadata_for_build_wheel(dist_dir)
|
| 866 |
+
|
| 867 |
+
_sys_argv_0_passthrough = {
|
| 868 |
+
'setup.py': DALS(
|
| 869 |
+
"""
|
| 870 |
+
import os
|
| 871 |
+
import sys
|
| 872 |
+
|
| 873 |
+
__import__('setuptools').setup(
|
| 874 |
+
name='foo',
|
| 875 |
+
version='0.0.0',
|
| 876 |
+
)
|
| 877 |
+
|
| 878 |
+
sys_argv = os.path.abspath(sys.argv[0])
|
| 879 |
+
file_path = os.path.abspath('setup.py')
|
| 880 |
+
assert sys_argv == file_path
|
| 881 |
+
"""
|
| 882 |
+
)
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
def test_sys_argv_passthrough(self, tmpdir_cwd):
|
| 886 |
+
path.build(self._sys_argv_0_passthrough)
|
| 887 |
+
build_backend = self.get_build_backend()
|
| 888 |
+
with pytest.raises(AssertionError):
|
| 889 |
+
build_backend.build_sdist("temp")
|
| 890 |
+
|
| 891 |
+
_setup_py_file_abspath = {
|
| 892 |
+
'setup.py': DALS(
|
| 893 |
+
"""
|
| 894 |
+
import os
|
| 895 |
+
assert os.path.isabs(__file__)
|
| 896 |
+
__import__('setuptools').setup(
|
| 897 |
+
name='foo',
|
| 898 |
+
version='0.0.0',
|
| 899 |
+
py_modules=['hello'],
|
| 900 |
+
setup_requires=['six'],
|
| 901 |
+
)
|
| 902 |
+
"""
|
| 903 |
+
)
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
+
def test_setup_py_file_abspath(self, tmpdir_cwd):
|
| 907 |
+
path.build(self._setup_py_file_abspath)
|
| 908 |
+
build_backend = self.get_build_backend()
|
| 909 |
+
build_backend.build_sdist("temp")
|
| 910 |
+
|
| 911 |
+
@pytest.mark.parametrize('build_hook', ('build_sdist', 'build_wheel'))
|
| 912 |
+
def test_build_with_empty_setuppy(self, build_backend, build_hook):
|
| 913 |
+
files = {'setup.py': ''}
|
| 914 |
+
path.build(files)
|
| 915 |
+
|
| 916 |
+
msg = re.escape('No distribution was found.')
|
| 917 |
+
with pytest.raises(ValueError, match=msg):
|
| 918 |
+
getattr(build_backend, build_hook)("temp")
|
| 919 |
+
|
| 920 |
+
|
| 921 |
+
class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
|
| 922 |
+
backend_name = 'setuptools.build_meta:__legacy__'
|
| 923 |
+
|
| 924 |
+
# build_meta_legacy-specific tests
|
| 925 |
+
def test_build_sdist_relative_path_import(self, tmpdir_cwd):
|
| 926 |
+
# This must fail in build_meta, but must pass in build_meta_legacy
|
| 927 |
+
path.build(self._relative_path_import_files)
|
| 928 |
+
|
| 929 |
+
build_backend = self.get_build_backend()
|
| 930 |
+
build_backend.build_sdist("temp")
|
| 931 |
+
|
| 932 |
+
def test_sys_argv_passthrough(self, tmpdir_cwd):
|
| 933 |
+
path.build(self._sys_argv_0_passthrough)
|
| 934 |
+
|
| 935 |
+
build_backend = self.get_build_backend()
|
| 936 |
+
build_backend.build_sdist("temp")
|
| 937 |
+
|
| 938 |
+
|
| 939 |
+
def test_legacy_editable_install(venv, tmpdir, tmpdir_cwd):
|
| 940 |
+
pyproject = """
|
| 941 |
+
[build-system]
|
| 942 |
+
requires = ["setuptools"]
|
| 943 |
+
build-backend = "setuptools.build_meta"
|
| 944 |
+
[project]
|
| 945 |
+
name = "myproj"
|
| 946 |
+
version = "42"
|
| 947 |
+
"""
|
| 948 |
+
path.build({"pyproject.toml": DALS(pyproject), "mymod.py": ""})
|
| 949 |
+
|
| 950 |
+
# First: sanity check
|
| 951 |
+
cmd = ["pip", "install", "--no-build-isolation", "-e", "."]
|
| 952 |
+
output = venv.run(cmd, cwd=tmpdir).lower()
|
| 953 |
+
assert "running setup.py develop for myproj" not in output
|
| 954 |
+
assert "created wheel for myproj" in output
|
| 955 |
+
|
| 956 |
+
# Then: real test
|
| 957 |
+
env = {**os.environ, "SETUPTOOLS_ENABLE_FEATURES": "legacy-editable"}
|
| 958 |
+
cmd = ["pip", "install", "--no-build-isolation", "-e", "."]
|
| 959 |
+
output = venv.run(cmd, cwd=tmpdir, env=env).lower()
|
| 960 |
+
assert "running setup.py develop for myproj" in output
|
| 961 |
+
|
| 962 |
+
|
| 963 |
+
@pytest.mark.filterwarnings("ignore::setuptools.SetuptoolsDeprecationWarning")
|
| 964 |
+
def test_sys_exit_0_in_setuppy(monkeypatch, tmp_path):
|
| 965 |
+
"""Setuptools should be resilient to setup.py with ``sys.exit(0)`` (#3973)."""
|
| 966 |
+
monkeypatch.chdir(tmp_path)
|
| 967 |
+
setuppy = """
|
| 968 |
+
import sys, setuptools
|
| 969 |
+
setuptools.setup(name='foo', version='0.0.0')
|
| 970 |
+
sys.exit(0)
|
| 971 |
+
"""
|
| 972 |
+
(tmp_path / "setup.py").write_text(DALS(setuppy), encoding="utf-8")
|
| 973 |
+
backend = BuildBackend(backend_name="setuptools.build_meta")
|
| 974 |
+
assert backend.get_requires_for_build_wheel() == []
|
| 975 |
+
|
| 976 |
+
|
| 977 |
+
def test_system_exit_in_setuppy(monkeypatch, tmp_path):
|
| 978 |
+
monkeypatch.chdir(tmp_path)
|
| 979 |
+
setuppy = "import sys; sys.exit('some error')"
|
| 980 |
+
(tmp_path / "setup.py").write_text(setuppy, encoding="utf-8")
|
| 981 |
+
with pytest.raises(SystemExit, match="some error"):
|
| 982 |
+
backend = BuildBackend(backend_name="setuptools.build_meta")
|
| 983 |
+
backend.get_requires_for_build_wheel()
|
venv/lib/python3.11/site-packages/setuptools/tests/test_build_py.py
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import stat
|
| 4 |
+
import warnings
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from unittest.mock import Mock
|
| 7 |
+
|
| 8 |
+
import jaraco.path
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from setuptools import SetuptoolsDeprecationWarning
|
| 12 |
+
from setuptools.dist import Distribution
|
| 13 |
+
|
| 14 |
+
from .textwrap import DALS
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_directories_in_package_data_glob(tmpdir_cwd):
|
| 18 |
+
"""
|
| 19 |
+
Directories matching the glob in package_data should
|
| 20 |
+
not be included in the package data.
|
| 21 |
+
|
| 22 |
+
Regression test for #261.
|
| 23 |
+
"""
|
| 24 |
+
dist = Distribution(
|
| 25 |
+
dict(
|
| 26 |
+
script_name='setup.py',
|
| 27 |
+
script_args=['build_py'],
|
| 28 |
+
packages=[''],
|
| 29 |
+
package_data={'': ['path/*']},
|
| 30 |
+
)
|
| 31 |
+
)
|
| 32 |
+
os.makedirs('path/subpath')
|
| 33 |
+
dist.parse_command_line()
|
| 34 |
+
dist.run_commands()
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def test_recursive_in_package_data_glob(tmpdir_cwd):
|
| 38 |
+
"""
|
| 39 |
+
Files matching recursive globs (**) in package_data should
|
| 40 |
+
be included in the package data.
|
| 41 |
+
|
| 42 |
+
#1806
|
| 43 |
+
"""
|
| 44 |
+
dist = Distribution(
|
| 45 |
+
dict(
|
| 46 |
+
script_name='setup.py',
|
| 47 |
+
script_args=['build_py'],
|
| 48 |
+
packages=[''],
|
| 49 |
+
package_data={'': ['path/**/data']},
|
| 50 |
+
)
|
| 51 |
+
)
|
| 52 |
+
os.makedirs('path/subpath/subsubpath')
|
| 53 |
+
open('path/subpath/subsubpath/data', 'wb').close()
|
| 54 |
+
|
| 55 |
+
dist.parse_command_line()
|
| 56 |
+
dist.run_commands()
|
| 57 |
+
|
| 58 |
+
assert stat.S_ISREG(os.stat('build/lib/path/subpath/subsubpath/data').st_mode), (
|
| 59 |
+
"File is not included"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def test_read_only(tmpdir_cwd):
|
| 64 |
+
"""
|
| 65 |
+
Ensure read-only flag is not preserved in copy
|
| 66 |
+
for package modules and package data, as that
|
| 67 |
+
causes problems with deleting read-only files on
|
| 68 |
+
Windows.
|
| 69 |
+
|
| 70 |
+
#1451
|
| 71 |
+
"""
|
| 72 |
+
dist = Distribution(
|
| 73 |
+
dict(
|
| 74 |
+
script_name='setup.py',
|
| 75 |
+
script_args=['build_py'],
|
| 76 |
+
packages=['pkg'],
|
| 77 |
+
package_data={'pkg': ['data.dat']},
|
| 78 |
+
)
|
| 79 |
+
)
|
| 80 |
+
os.makedirs('pkg')
|
| 81 |
+
open('pkg/__init__.py', 'wb').close()
|
| 82 |
+
open('pkg/data.dat', 'wb').close()
|
| 83 |
+
os.chmod('pkg/__init__.py', stat.S_IREAD)
|
| 84 |
+
os.chmod('pkg/data.dat', stat.S_IREAD)
|
| 85 |
+
dist.parse_command_line()
|
| 86 |
+
dist.run_commands()
|
| 87 |
+
shutil.rmtree('build')
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@pytest.mark.xfail(
|
| 91 |
+
'platform.system() == "Windows"',
|
| 92 |
+
reason="On Windows, files do not have executable bits",
|
| 93 |
+
raises=AssertionError,
|
| 94 |
+
strict=True,
|
| 95 |
+
)
|
| 96 |
+
def test_executable_data(tmpdir_cwd):
|
| 97 |
+
"""
|
| 98 |
+
Ensure executable bit is preserved in copy for
|
| 99 |
+
package data, as users rely on it for scripts.
|
| 100 |
+
|
| 101 |
+
#2041
|
| 102 |
+
"""
|
| 103 |
+
dist = Distribution(
|
| 104 |
+
dict(
|
| 105 |
+
script_name='setup.py',
|
| 106 |
+
script_args=['build_py'],
|
| 107 |
+
packages=['pkg'],
|
| 108 |
+
package_data={'pkg': ['run-me']},
|
| 109 |
+
)
|
| 110 |
+
)
|
| 111 |
+
os.makedirs('pkg')
|
| 112 |
+
open('pkg/__init__.py', 'wb').close()
|
| 113 |
+
open('pkg/run-me', 'wb').close()
|
| 114 |
+
os.chmod('pkg/run-me', 0o700)
|
| 115 |
+
|
| 116 |
+
dist.parse_command_line()
|
| 117 |
+
dist.run_commands()
|
| 118 |
+
|
| 119 |
+
assert os.stat('build/lib/pkg/run-me').st_mode & stat.S_IEXEC, (
|
| 120 |
+
"Script is not executable"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
EXAMPLE_WITH_MANIFEST = {
|
| 125 |
+
"setup.cfg": DALS(
|
| 126 |
+
"""
|
| 127 |
+
[metadata]
|
| 128 |
+
name = mypkg
|
| 129 |
+
version = 42
|
| 130 |
+
|
| 131 |
+
[options]
|
| 132 |
+
include_package_data = True
|
| 133 |
+
packages = find:
|
| 134 |
+
|
| 135 |
+
[options.packages.find]
|
| 136 |
+
exclude = *.tests*
|
| 137 |
+
"""
|
| 138 |
+
),
|
| 139 |
+
"mypkg": {
|
| 140 |
+
"__init__.py": "",
|
| 141 |
+
"resource_file.txt": "",
|
| 142 |
+
"tests": {
|
| 143 |
+
"__init__.py": "",
|
| 144 |
+
"test_mypkg.py": "",
|
| 145 |
+
"test_file.txt": "",
|
| 146 |
+
},
|
| 147 |
+
},
|
| 148 |
+
"MANIFEST.in": DALS(
|
| 149 |
+
"""
|
| 150 |
+
global-include *.py *.txt
|
| 151 |
+
global-exclude *.py[cod]
|
| 152 |
+
prune dist
|
| 153 |
+
prune build
|
| 154 |
+
prune *.egg-info
|
| 155 |
+
"""
|
| 156 |
+
),
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def test_excluded_subpackages(tmpdir_cwd):
|
| 161 |
+
jaraco.path.build(EXAMPLE_WITH_MANIFEST)
|
| 162 |
+
dist = Distribution({"script_name": "%PEP 517%"})
|
| 163 |
+
dist.parse_config_files()
|
| 164 |
+
|
| 165 |
+
build_py = dist.get_command_obj("build_py")
|
| 166 |
+
|
| 167 |
+
msg = r"Python recognizes 'mypkg\.tests' as an importable package"
|
| 168 |
+
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
|
| 169 |
+
# TODO: To fix #3260 we need some transition period to deprecate the
|
| 170 |
+
# existing behavior of `include_package_data`. After the transition, we
|
| 171 |
+
# should remove the warning and fix the behavior.
|
| 172 |
+
|
| 173 |
+
if os.getenv("SETUPTOOLS_USE_DISTUTILS") == "stdlib":
|
| 174 |
+
# pytest.warns reset the warning filter temporarily
|
| 175 |
+
# https://github.com/pytest-dev/pytest/issues/4011#issuecomment-423494810
|
| 176 |
+
warnings.filterwarnings(
|
| 177 |
+
"ignore",
|
| 178 |
+
"'encoding' argument not specified",
|
| 179 |
+
module="distutils.text_file",
|
| 180 |
+
# This warning is already fixed in pypa/distutils but not in stdlib
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
build_py.finalize_options()
|
| 184 |
+
build_py.run()
|
| 185 |
+
|
| 186 |
+
build_dir = Path(dist.get_command_obj("build_py").build_lib)
|
| 187 |
+
assert (build_dir / "mypkg/__init__.py").exists()
|
| 188 |
+
assert (build_dir / "mypkg/resource_file.txt").exists()
|
| 189 |
+
|
| 190 |
+
# Setuptools is configured to ignore `mypkg.tests`, therefore the following
|
| 191 |
+
# files/dirs should not be included in the distribution.
|
| 192 |
+
for f in [
|
| 193 |
+
"mypkg/tests/__init__.py",
|
| 194 |
+
"mypkg/tests/test_mypkg.py",
|
| 195 |
+
"mypkg/tests/test_file.txt",
|
| 196 |
+
"mypkg/tests",
|
| 197 |
+
]:
|
| 198 |
+
with pytest.raises(AssertionError):
|
| 199 |
+
# TODO: Enforce the following assertion once #3260 is fixed
|
| 200 |
+
# (remove context manager and the following xfail).
|
| 201 |
+
assert not (build_dir / f).exists()
|
| 202 |
+
|
| 203 |
+
pytest.xfail("#3260")
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
@pytest.mark.filterwarnings("ignore::setuptools.SetuptoolsDeprecationWarning")
|
| 207 |
+
def test_existing_egg_info(tmpdir_cwd, monkeypatch):
|
| 208 |
+
"""When provided with the ``existing_egg_info_dir`` attribute, build_py should not
|
| 209 |
+
attempt to run egg_info again.
|
| 210 |
+
"""
|
| 211 |
+
# == Pre-condition ==
|
| 212 |
+
# Generate an egg-info dir
|
| 213 |
+
jaraco.path.build(EXAMPLE_WITH_MANIFEST)
|
| 214 |
+
dist = Distribution({"script_name": "%PEP 517%"})
|
| 215 |
+
dist.parse_config_files()
|
| 216 |
+
assert dist.include_package_data
|
| 217 |
+
|
| 218 |
+
egg_info = dist.get_command_obj("egg_info")
|
| 219 |
+
dist.run_command("egg_info")
|
| 220 |
+
egg_info_dir = next(Path(egg_info.egg_base).glob("*.egg-info"))
|
| 221 |
+
assert egg_info_dir.is_dir()
|
| 222 |
+
|
| 223 |
+
# == Setup ==
|
| 224 |
+
build_py = dist.get_command_obj("build_py")
|
| 225 |
+
build_py.finalize_options()
|
| 226 |
+
egg_info = dist.get_command_obj("egg_info")
|
| 227 |
+
egg_info_run = Mock(side_effect=egg_info.run)
|
| 228 |
+
monkeypatch.setattr(egg_info, "run", egg_info_run)
|
| 229 |
+
|
| 230 |
+
# == Remove caches ==
|
| 231 |
+
# egg_info is called when build_py looks for data_files, which gets cached.
|
| 232 |
+
# We need to ensure it is not cached yet, otherwise it may impact on the tests
|
| 233 |
+
build_py.__dict__.pop('data_files', None)
|
| 234 |
+
dist.reinitialize_command(egg_info)
|
| 235 |
+
|
| 236 |
+
# == Sanity check ==
|
| 237 |
+
# Ensure that if existing_egg_info is not given, build_py attempts to run egg_info
|
| 238 |
+
build_py.existing_egg_info_dir = None
|
| 239 |
+
build_py.run()
|
| 240 |
+
egg_info_run.assert_called()
|
| 241 |
+
|
| 242 |
+
# == Remove caches ==
|
| 243 |
+
egg_info_run.reset_mock()
|
| 244 |
+
build_py.__dict__.pop('data_files', None)
|
| 245 |
+
dist.reinitialize_command(egg_info)
|
| 246 |
+
|
| 247 |
+
# == Actual test ==
|
| 248 |
+
# Ensure that if existing_egg_info_dir is given, egg_info doesn't run
|
| 249 |
+
build_py.existing_egg_info_dir = egg_info_dir
|
| 250 |
+
build_py.run()
|
| 251 |
+
egg_info_run.assert_not_called()
|
| 252 |
+
assert build_py.data_files
|
| 253 |
+
|
| 254 |
+
# Make sure the list of outputs is actually OK
|
| 255 |
+
outputs = map(lambda x: x.replace(os.sep, "/"), build_py.get_outputs())
|
| 256 |
+
assert outputs
|
| 257 |
+
example = str(Path(build_py.build_lib, "mypkg/__init__.py")).replace(os.sep, "/")
|
| 258 |
+
assert example in outputs
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
EXAMPLE_ARBITRARY_MAPPING = {
|
| 262 |
+
"pyproject.toml": DALS(
|
| 263 |
+
"""
|
| 264 |
+
[project]
|
| 265 |
+
name = "mypkg"
|
| 266 |
+
version = "42"
|
| 267 |
+
|
| 268 |
+
[tool.setuptools]
|
| 269 |
+
packages = ["mypkg", "mypkg.sub1", "mypkg.sub2", "mypkg.sub2.nested"]
|
| 270 |
+
|
| 271 |
+
[tool.setuptools.package-dir]
|
| 272 |
+
"" = "src"
|
| 273 |
+
"mypkg.sub2" = "src/mypkg/_sub2"
|
| 274 |
+
"mypkg.sub2.nested" = "other"
|
| 275 |
+
"""
|
| 276 |
+
),
|
| 277 |
+
"src": {
|
| 278 |
+
"mypkg": {
|
| 279 |
+
"__init__.py": "",
|
| 280 |
+
"resource_file.txt": "",
|
| 281 |
+
"sub1": {
|
| 282 |
+
"__init__.py": "",
|
| 283 |
+
"mod1.py": "",
|
| 284 |
+
},
|
| 285 |
+
"_sub2": {
|
| 286 |
+
"mod2.py": "",
|
| 287 |
+
},
|
| 288 |
+
},
|
| 289 |
+
},
|
| 290 |
+
"other": {
|
| 291 |
+
"__init__.py": "",
|
| 292 |
+
"mod3.py": "",
|
| 293 |
+
},
|
| 294 |
+
"MANIFEST.in": DALS(
|
| 295 |
+
"""
|
| 296 |
+
global-include *.py *.txt
|
| 297 |
+
global-exclude *.py[cod]
|
| 298 |
+
"""
|
| 299 |
+
),
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def test_get_outputs(tmpdir_cwd):
|
| 304 |
+
jaraco.path.build(EXAMPLE_ARBITRARY_MAPPING)
|
| 305 |
+
dist = Distribution({"script_name": "%test%"})
|
| 306 |
+
dist.parse_config_files()
|
| 307 |
+
|
| 308 |
+
build_py = dist.get_command_obj("build_py")
|
| 309 |
+
build_py.editable_mode = True
|
| 310 |
+
build_py.ensure_finalized()
|
| 311 |
+
build_lib = build_py.build_lib.replace(os.sep, "/")
|
| 312 |
+
outputs = {x.replace(os.sep, "/") for x in build_py.get_outputs()}
|
| 313 |
+
assert outputs == {
|
| 314 |
+
f"{build_lib}/mypkg/__init__.py",
|
| 315 |
+
f"{build_lib}/mypkg/resource_file.txt",
|
| 316 |
+
f"{build_lib}/mypkg/sub1/__init__.py",
|
| 317 |
+
f"{build_lib}/mypkg/sub1/mod1.py",
|
| 318 |
+
f"{build_lib}/mypkg/sub2/mod2.py",
|
| 319 |
+
f"{build_lib}/mypkg/sub2/nested/__init__.py",
|
| 320 |
+
f"{build_lib}/mypkg/sub2/nested/mod3.py",
|
| 321 |
+
}
|
| 322 |
+
mapping = {
|
| 323 |
+
k.replace(os.sep, "/"): v.replace(os.sep, "/")
|
| 324 |
+
for k, v in build_py.get_output_mapping().items()
|
| 325 |
+
}
|
| 326 |
+
assert mapping == {
|
| 327 |
+
f"{build_lib}/mypkg/__init__.py": "src/mypkg/__init__.py",
|
| 328 |
+
f"{build_lib}/mypkg/resource_file.txt": "src/mypkg/resource_file.txt",
|
| 329 |
+
f"{build_lib}/mypkg/sub1/__init__.py": "src/mypkg/sub1/__init__.py",
|
| 330 |
+
f"{build_lib}/mypkg/sub1/mod1.py": "src/mypkg/sub1/mod1.py",
|
| 331 |
+
f"{build_lib}/mypkg/sub2/mod2.py": "src/mypkg/_sub2/mod2.py",
|
| 332 |
+
f"{build_lib}/mypkg/sub2/nested/__init__.py": "other/__init__.py",
|
| 333 |
+
f"{build_lib}/mypkg/sub2/nested/mod3.py": "other/mod3.py",
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
class TestTypeInfoFiles:
|
| 338 |
+
PYPROJECTS = {
|
| 339 |
+
"default_pyproject": DALS(
|
| 340 |
+
"""
|
| 341 |
+
[project]
|
| 342 |
+
name = "foo"
|
| 343 |
+
version = "1"
|
| 344 |
+
"""
|
| 345 |
+
),
|
| 346 |
+
"dont_include_package_data": DALS(
|
| 347 |
+
"""
|
| 348 |
+
[project]
|
| 349 |
+
name = "foo"
|
| 350 |
+
version = "1"
|
| 351 |
+
|
| 352 |
+
[tool.setuptools]
|
| 353 |
+
include-package-data = false
|
| 354 |
+
"""
|
| 355 |
+
),
|
| 356 |
+
"exclude_type_info": DALS(
|
| 357 |
+
"""
|
| 358 |
+
[project]
|
| 359 |
+
name = "foo"
|
| 360 |
+
version = "1"
|
| 361 |
+
|
| 362 |
+
[tool.setuptools]
|
| 363 |
+
include-package-data = false
|
| 364 |
+
|
| 365 |
+
[tool.setuptools.exclude-package-data]
|
| 366 |
+
"*" = ["py.typed", "*.pyi"]
|
| 367 |
+
"""
|
| 368 |
+
),
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
EXAMPLES = {
|
| 372 |
+
"simple_namespace": {
|
| 373 |
+
"directory_structure": {
|
| 374 |
+
"foo": {
|
| 375 |
+
"bar.pyi": "",
|
| 376 |
+
"py.typed": "",
|
| 377 |
+
"__init__.py": "",
|
| 378 |
+
}
|
| 379 |
+
},
|
| 380 |
+
"expected_type_files": {"foo/bar.pyi", "foo/py.typed"},
|
| 381 |
+
},
|
| 382 |
+
"nested_inside_namespace": {
|
| 383 |
+
"directory_structure": {
|
| 384 |
+
"foo": {
|
| 385 |
+
"bar": {
|
| 386 |
+
"py.typed": "",
|
| 387 |
+
"mod.pyi": "",
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
},
|
| 391 |
+
"expected_type_files": {"foo/bar/mod.pyi", "foo/bar/py.typed"},
|
| 392 |
+
},
|
| 393 |
+
"namespace_nested_inside_regular": {
|
| 394 |
+
"directory_structure": {
|
| 395 |
+
"foo": {
|
| 396 |
+
"namespace": {
|
| 397 |
+
"foo.pyi": "",
|
| 398 |
+
},
|
| 399 |
+
"__init__.pyi": "",
|
| 400 |
+
"py.typed": "",
|
| 401 |
+
}
|
| 402 |
+
},
|
| 403 |
+
"expected_type_files": {
|
| 404 |
+
"foo/namespace/foo.pyi",
|
| 405 |
+
"foo/__init__.pyi",
|
| 406 |
+
"foo/py.typed",
|
| 407 |
+
},
|
| 408 |
+
},
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
@pytest.mark.parametrize(
|
| 412 |
+
"pyproject",
|
| 413 |
+
[
|
| 414 |
+
"default_pyproject",
|
| 415 |
+
pytest.param(
|
| 416 |
+
"dont_include_package_data",
|
| 417 |
+
marks=pytest.mark.xfail(reason="pypa/setuptools#4350"),
|
| 418 |
+
),
|
| 419 |
+
],
|
| 420 |
+
)
|
| 421 |
+
@pytest.mark.parametrize("example", EXAMPLES.keys())
|
| 422 |
+
def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example):
|
| 423 |
+
structure = {
|
| 424 |
+
**self.EXAMPLES[example]["directory_structure"],
|
| 425 |
+
"pyproject.toml": self.PYPROJECTS[pyproject],
|
| 426 |
+
}
|
| 427 |
+
expected_type_files = self.EXAMPLES[example]["expected_type_files"]
|
| 428 |
+
jaraco.path.build(structure)
|
| 429 |
+
|
| 430 |
+
build_py = get_finalized_build_py()
|
| 431 |
+
outputs = get_outputs(build_py)
|
| 432 |
+
assert expected_type_files <= outputs
|
| 433 |
+
|
| 434 |
+
@pytest.mark.parametrize("pyproject", ["exclude_type_info"])
|
| 435 |
+
@pytest.mark.parametrize("example", EXAMPLES.keys())
|
| 436 |
+
def test_type_files_can_be_excluded(self, tmpdir_cwd, pyproject, example):
|
| 437 |
+
structure = {
|
| 438 |
+
**self.EXAMPLES[example]["directory_structure"],
|
| 439 |
+
"pyproject.toml": self.PYPROJECTS[pyproject],
|
| 440 |
+
}
|
| 441 |
+
expected_type_files = self.EXAMPLES[example]["expected_type_files"]
|
| 442 |
+
jaraco.path.build(structure)
|
| 443 |
+
|
| 444 |
+
build_py = get_finalized_build_py()
|
| 445 |
+
outputs = get_outputs(build_py)
|
| 446 |
+
assert expected_type_files.isdisjoint(outputs)
|
| 447 |
+
|
| 448 |
+
def test_stub_only_package(self, tmpdir_cwd):
|
| 449 |
+
structure = {
|
| 450 |
+
"pyproject.toml": DALS(
|
| 451 |
+
"""
|
| 452 |
+
[project]
|
| 453 |
+
name = "foo-stubs"
|
| 454 |
+
version = "1"
|
| 455 |
+
"""
|
| 456 |
+
),
|
| 457 |
+
"foo-stubs": {"__init__.pyi": "", "bar.pyi": ""},
|
| 458 |
+
}
|
| 459 |
+
expected_type_files = {"foo-stubs/__init__.pyi", "foo-stubs/bar.pyi"}
|
| 460 |
+
jaraco.path.build(structure)
|
| 461 |
+
|
| 462 |
+
build_py = get_finalized_build_py()
|
| 463 |
+
outputs = get_outputs(build_py)
|
| 464 |
+
assert expected_type_files <= outputs
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def get_finalized_build_py(script_name="%build_py-test%"):
|
| 468 |
+
dist = Distribution({"script_name": script_name})
|
| 469 |
+
dist.parse_config_files()
|
| 470 |
+
build_py = dist.get_command_obj("build_py")
|
| 471 |
+
build_py.finalize_options()
|
| 472 |
+
return build_py
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def get_outputs(build_py):
|
| 476 |
+
build_dir = Path(build_py.build_lib)
|
| 477 |
+
return {
|
| 478 |
+
os.path.relpath(x, build_dir).replace(os.sep, "/")
|
| 479 |
+
for x in build_py.get_outputs()
|
| 480 |
+
}
|
venv/lib/python3.11/site-packages/setuptools/tests/test_config_discovery.py
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from configparser import ConfigParser
|
| 4 |
+
from itertools import product
|
| 5 |
+
from typing import cast
|
| 6 |
+
|
| 7 |
+
import jaraco.path
|
| 8 |
+
import pytest
|
| 9 |
+
from path import Path
|
| 10 |
+
|
| 11 |
+
import setuptools # noqa: F401 # force distutils.core to be patched
|
| 12 |
+
from setuptools.command.sdist import sdist
|
| 13 |
+
from setuptools.discovery import find_package_path, find_parent_package
|
| 14 |
+
from setuptools.dist import Distribution
|
| 15 |
+
from setuptools.errors import PackageDiscoveryError
|
| 16 |
+
|
| 17 |
+
from .contexts import quiet
|
| 18 |
+
from .integration.helpers import get_sdist_members, get_wheel_members, run
|
| 19 |
+
from .textwrap import DALS
|
| 20 |
+
|
| 21 |
+
import distutils.core
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class TestFindParentPackage:
|
| 25 |
+
def test_single_package(self, tmp_path):
|
| 26 |
+
# find_parent_package should find a non-namespace parent package
|
| 27 |
+
(tmp_path / "src/namespace/pkg/nested").mkdir(exist_ok=True, parents=True)
|
| 28 |
+
(tmp_path / "src/namespace/pkg/nested/__init__.py").touch()
|
| 29 |
+
(tmp_path / "src/namespace/pkg/__init__.py").touch()
|
| 30 |
+
packages = ["namespace", "namespace.pkg", "namespace.pkg.nested"]
|
| 31 |
+
assert find_parent_package(packages, {"": "src"}, tmp_path) == "namespace.pkg"
|
| 32 |
+
|
| 33 |
+
def test_multiple_toplevel(self, tmp_path):
|
| 34 |
+
# find_parent_package should return null if the given list of packages does not
|
| 35 |
+
# have a single parent package
|
| 36 |
+
multiple = ["pkg", "pkg1", "pkg2"]
|
| 37 |
+
for name in multiple:
|
| 38 |
+
(tmp_path / f"src/{name}").mkdir(exist_ok=True, parents=True)
|
| 39 |
+
(tmp_path / f"src/{name}/__init__.py").touch()
|
| 40 |
+
assert find_parent_package(multiple, {"": "src"}, tmp_path) is None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class TestDiscoverPackagesAndPyModules:
|
| 44 |
+
"""Make sure discovered values for ``packages`` and ``py_modules`` work
|
| 45 |
+
similarly to explicit configuration for the simple scenarios.
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
OPTIONS = {
|
| 49 |
+
# Different options according to the circumstance being tested
|
| 50 |
+
"explicit-src": {"package_dir": {"": "src"}, "packages": ["pkg"]},
|
| 51 |
+
"variation-lib": {
|
| 52 |
+
"package_dir": {"": "lib"}, # variation of the source-layout
|
| 53 |
+
},
|
| 54 |
+
"explicit-flat": {"packages": ["pkg"]},
|
| 55 |
+
"explicit-single_module": {"py_modules": ["pkg"]},
|
| 56 |
+
"explicit-namespace": {"packages": ["ns", "ns.pkg"]},
|
| 57 |
+
"automatic-src": {},
|
| 58 |
+
"automatic-flat": {},
|
| 59 |
+
"automatic-single_module": {},
|
| 60 |
+
"automatic-namespace": {},
|
| 61 |
+
}
|
| 62 |
+
FILES = {
|
| 63 |
+
"src": ["src/pkg/__init__.py", "src/pkg/main.py"],
|
| 64 |
+
"lib": ["lib/pkg/__init__.py", "lib/pkg/main.py"],
|
| 65 |
+
"flat": ["pkg/__init__.py", "pkg/main.py"],
|
| 66 |
+
"single_module": ["pkg.py"],
|
| 67 |
+
"namespace": ["ns/pkg/__init__.py"],
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
def _get_info(self, circumstance):
|
| 71 |
+
_, _, layout = circumstance.partition("-")
|
| 72 |
+
files = self.FILES[layout]
|
| 73 |
+
options = self.OPTIONS[circumstance]
|
| 74 |
+
return files, options
|
| 75 |
+
|
| 76 |
+
@pytest.mark.parametrize("circumstance", OPTIONS.keys())
|
| 77 |
+
def test_sdist_filelist(self, tmp_path, circumstance):
|
| 78 |
+
files, options = self._get_info(circumstance)
|
| 79 |
+
_populate_project_dir(tmp_path, files, options)
|
| 80 |
+
|
| 81 |
+
_, cmd = _run_sdist_programatically(tmp_path, options)
|
| 82 |
+
|
| 83 |
+
manifest = [f.replace(os.sep, "/") for f in cmd.filelist.files]
|
| 84 |
+
for file in files:
|
| 85 |
+
assert any(f.endswith(file) for f in manifest)
|
| 86 |
+
|
| 87 |
+
@pytest.mark.parametrize("circumstance", OPTIONS.keys())
|
| 88 |
+
def test_project(self, tmp_path, circumstance):
|
| 89 |
+
files, options = self._get_info(circumstance)
|
| 90 |
+
_populate_project_dir(tmp_path, files, options)
|
| 91 |
+
|
| 92 |
+
# Simulate a pre-existing `build` directory
|
| 93 |
+
(tmp_path / "build").mkdir()
|
| 94 |
+
(tmp_path / "build/lib").mkdir()
|
| 95 |
+
(tmp_path / "build/bdist.linux-x86_64").mkdir()
|
| 96 |
+
(tmp_path / "build/bdist.linux-x86_64/file.py").touch()
|
| 97 |
+
(tmp_path / "build/lib/__init__.py").touch()
|
| 98 |
+
(tmp_path / "build/lib/file.py").touch()
|
| 99 |
+
(tmp_path / "dist").mkdir()
|
| 100 |
+
(tmp_path / "dist/file.py").touch()
|
| 101 |
+
|
| 102 |
+
_run_build(tmp_path)
|
| 103 |
+
|
| 104 |
+
sdist_files = get_sdist_members(next(tmp_path.glob("dist/*.tar.gz")))
|
| 105 |
+
print("~~~~~ sdist_members ~~~~~")
|
| 106 |
+
print('\n'.join(sdist_files))
|
| 107 |
+
assert sdist_files >= set(files)
|
| 108 |
+
|
| 109 |
+
wheel_files = get_wheel_members(next(tmp_path.glob("dist/*.whl")))
|
| 110 |
+
print("~~~~~ wheel_members ~~~~~")
|
| 111 |
+
print('\n'.join(wheel_files))
|
| 112 |
+
orig_files = {f.replace("src/", "").replace("lib/", "") for f in files}
|
| 113 |
+
assert wheel_files >= orig_files
|
| 114 |
+
|
| 115 |
+
# Make sure build files are not included by mistake
|
| 116 |
+
for file in wheel_files:
|
| 117 |
+
assert "build" not in files
|
| 118 |
+
assert "dist" not in files
|
| 119 |
+
|
| 120 |
+
PURPOSEFULLY_EMPY = {
|
| 121 |
+
"setup.cfg": DALS(
|
| 122 |
+
"""
|
| 123 |
+
[metadata]
|
| 124 |
+
name = myproj
|
| 125 |
+
version = 0.0.0
|
| 126 |
+
|
| 127 |
+
[options]
|
| 128 |
+
{param} =
|
| 129 |
+
"""
|
| 130 |
+
),
|
| 131 |
+
"setup.py": DALS(
|
| 132 |
+
"""
|
| 133 |
+
__import__('setuptools').setup(
|
| 134 |
+
name="myproj",
|
| 135 |
+
version="0.0.0",
|
| 136 |
+
{param}=[]
|
| 137 |
+
)
|
| 138 |
+
"""
|
| 139 |
+
),
|
| 140 |
+
"pyproject.toml": DALS(
|
| 141 |
+
"""
|
| 142 |
+
[build-system]
|
| 143 |
+
requires = []
|
| 144 |
+
build-backend = 'setuptools.build_meta'
|
| 145 |
+
|
| 146 |
+
[project]
|
| 147 |
+
name = "myproj"
|
| 148 |
+
version = "0.0.0"
|
| 149 |
+
|
| 150 |
+
[tool.setuptools]
|
| 151 |
+
{param} = []
|
| 152 |
+
"""
|
| 153 |
+
),
|
| 154 |
+
"template-pyproject.toml": DALS(
|
| 155 |
+
"""
|
| 156 |
+
[build-system]
|
| 157 |
+
requires = []
|
| 158 |
+
build-backend = 'setuptools.build_meta'
|
| 159 |
+
"""
|
| 160 |
+
),
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
@pytest.mark.parametrize(
|
| 164 |
+
("config_file", "param", "circumstance"),
|
| 165 |
+
product(
|
| 166 |
+
["setup.cfg", "setup.py", "pyproject.toml"],
|
| 167 |
+
["packages", "py_modules"],
|
| 168 |
+
FILES.keys(),
|
| 169 |
+
),
|
| 170 |
+
)
|
| 171 |
+
def test_purposefully_empty(self, tmp_path, config_file, param, circumstance):
|
| 172 |
+
files = self.FILES[circumstance] + ["mod.py", "other.py", "src/pkg/__init__.py"]
|
| 173 |
+
_populate_project_dir(tmp_path, files, {})
|
| 174 |
+
|
| 175 |
+
if config_file == "pyproject.toml":
|
| 176 |
+
template_param = param.replace("_", "-")
|
| 177 |
+
else:
|
| 178 |
+
# Make sure build works with or without setup.cfg
|
| 179 |
+
pyproject = self.PURPOSEFULLY_EMPY["template-pyproject.toml"]
|
| 180 |
+
(tmp_path / "pyproject.toml").write_text(pyproject, encoding="utf-8")
|
| 181 |
+
template_param = param
|
| 182 |
+
|
| 183 |
+
config = self.PURPOSEFULLY_EMPY[config_file].format(param=template_param)
|
| 184 |
+
(tmp_path / config_file).write_text(config, encoding="utf-8")
|
| 185 |
+
|
| 186 |
+
dist = _get_dist(tmp_path, {})
|
| 187 |
+
# When either parameter package or py_modules is an empty list,
|
| 188 |
+
# then there should be no discovery
|
| 189 |
+
assert getattr(dist, param) == []
|
| 190 |
+
other = {"py_modules": "packages", "packages": "py_modules"}[param]
|
| 191 |
+
assert getattr(dist, other) is None
|
| 192 |
+
|
| 193 |
+
@pytest.mark.parametrize(
|
| 194 |
+
("extra_files", "pkgs"),
|
| 195 |
+
[
|
| 196 |
+
(["venv/bin/simulate_venv"], {"pkg"}),
|
| 197 |
+
(["pkg-stubs/__init__.pyi"], {"pkg", "pkg-stubs"}),
|
| 198 |
+
(["other-stubs/__init__.pyi"], {"pkg", "other-stubs"}),
|
| 199 |
+
(
|
| 200 |
+
# Type stubs can also be namespaced
|
| 201 |
+
["namespace-stubs/pkg/__init__.pyi"],
|
| 202 |
+
{"pkg", "namespace-stubs", "namespace-stubs.pkg"},
|
| 203 |
+
),
|
| 204 |
+
(
|
| 205 |
+
# Just the top-level package can have `-stubs`, ignore nested ones
|
| 206 |
+
["namespace-stubs/pkg-stubs/__init__.pyi"],
|
| 207 |
+
{"pkg", "namespace-stubs"},
|
| 208 |
+
),
|
| 209 |
+
(["_hidden/file.py"], {"pkg"}),
|
| 210 |
+
(["news/finalize.py"], {"pkg"}),
|
| 211 |
+
],
|
| 212 |
+
)
|
| 213 |
+
def test_flat_layout_with_extra_files(self, tmp_path, extra_files, pkgs):
|
| 214 |
+
files = self.FILES["flat"] + extra_files
|
| 215 |
+
_populate_project_dir(tmp_path, files, {})
|
| 216 |
+
dist = _get_dist(tmp_path, {})
|
| 217 |
+
assert set(dist.packages) == pkgs
|
| 218 |
+
|
| 219 |
+
@pytest.mark.parametrize(
|
| 220 |
+
"extra_files",
|
| 221 |
+
[
|
| 222 |
+
["other/__init__.py"],
|
| 223 |
+
["other/finalize.py"],
|
| 224 |
+
],
|
| 225 |
+
)
|
| 226 |
+
def test_flat_layout_with_dangerous_extra_files(self, tmp_path, extra_files):
|
| 227 |
+
files = self.FILES["flat"] + extra_files
|
| 228 |
+
_populate_project_dir(tmp_path, files, {})
|
| 229 |
+
with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
|
| 230 |
+
_get_dist(tmp_path, {})
|
| 231 |
+
|
| 232 |
+
def test_flat_layout_with_single_module(self, tmp_path):
|
| 233 |
+
files = self.FILES["single_module"] + ["invalid-module-name.py"]
|
| 234 |
+
_populate_project_dir(tmp_path, files, {})
|
| 235 |
+
dist = _get_dist(tmp_path, {})
|
| 236 |
+
assert set(dist.py_modules) == {"pkg"}
|
| 237 |
+
|
| 238 |
+
def test_flat_layout_with_multiple_modules(self, tmp_path):
|
| 239 |
+
files = self.FILES["single_module"] + ["valid_module_name.py"]
|
| 240 |
+
_populate_project_dir(tmp_path, files, {})
|
| 241 |
+
with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
|
| 242 |
+
_get_dist(tmp_path, {})
|
| 243 |
+
|
| 244 |
+
def test_py_modules_when_wheel_dir_is_cwd(self, tmp_path):
|
| 245 |
+
"""Regression for issue 3692"""
|
| 246 |
+
from setuptools import build_meta
|
| 247 |
+
|
| 248 |
+
pyproject = '[project]\nname = "test"\nversion = "1"'
|
| 249 |
+
(tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
|
| 250 |
+
(tmp_path / "foo.py").touch()
|
| 251 |
+
with jaraco.path.DirectoryStack().context(tmp_path):
|
| 252 |
+
build_meta.build_wheel(".")
|
| 253 |
+
# Ensure py_modules are found
|
| 254 |
+
wheel_files = get_wheel_members(next(tmp_path.glob("*.whl")))
|
| 255 |
+
assert "foo.py" in wheel_files
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
class TestNoConfig:
|
| 259 |
+
DEFAULT_VERSION = "0.0.0" # Default version given by setuptools
|
| 260 |
+
|
| 261 |
+
EXAMPLES = {
|
| 262 |
+
"pkg1": ["src/pkg1.py"],
|
| 263 |
+
"pkg2": ["src/pkg2/__init__.py"],
|
| 264 |
+
"pkg3": ["src/pkg3/__init__.py", "src/pkg3-stubs/__init__.py"],
|
| 265 |
+
"pkg4": ["pkg4/__init__.py", "pkg4-stubs/__init__.py"],
|
| 266 |
+
"ns.nested.pkg1": ["src/ns/nested/pkg1/__init__.py"],
|
| 267 |
+
"ns.nested.pkg2": ["ns/nested/pkg2/__init__.py"],
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
@pytest.mark.parametrize("example", EXAMPLES.keys())
|
| 271 |
+
def test_discover_name(self, tmp_path, example):
|
| 272 |
+
_populate_project_dir(tmp_path, self.EXAMPLES[example], {})
|
| 273 |
+
dist = _get_dist(tmp_path, {})
|
| 274 |
+
assert dist.get_name() == example
|
| 275 |
+
|
| 276 |
+
def test_build_with_discovered_name(self, tmp_path):
|
| 277 |
+
files = ["src/ns/nested/pkg/__init__.py"]
|
| 278 |
+
_populate_project_dir(tmp_path, files, {})
|
| 279 |
+
_run_build(tmp_path, "--sdist")
|
| 280 |
+
# Expected distribution file
|
| 281 |
+
dist_file = tmp_path / f"dist/ns_nested_pkg-{self.DEFAULT_VERSION}.tar.gz"
|
| 282 |
+
assert dist_file.is_file()
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
class TestWithAttrDirective:
|
| 286 |
+
@pytest.mark.parametrize(
|
| 287 |
+
("folder", "opts"),
|
| 288 |
+
[
|
| 289 |
+
("src", {}),
|
| 290 |
+
("lib", {"packages": "find:", "packages.find": {"where": "lib"}}),
|
| 291 |
+
],
|
| 292 |
+
)
|
| 293 |
+
def test_setupcfg_metadata(self, tmp_path, folder, opts):
|
| 294 |
+
files = [f"{folder}/pkg/__init__.py", "setup.cfg"]
|
| 295 |
+
_populate_project_dir(tmp_path, files, opts)
|
| 296 |
+
|
| 297 |
+
config = (tmp_path / "setup.cfg").read_text(encoding="utf-8")
|
| 298 |
+
overwrite = {
|
| 299 |
+
folder: {"pkg": {"__init__.py": "version = 42"}},
|
| 300 |
+
"setup.cfg": "[metadata]\nversion = attr: pkg.version\n" + config,
|
| 301 |
+
}
|
| 302 |
+
jaraco.path.build(overwrite, prefix=tmp_path)
|
| 303 |
+
|
| 304 |
+
dist = _get_dist(tmp_path, {})
|
| 305 |
+
assert dist.get_name() == "pkg"
|
| 306 |
+
assert dist.get_version() == "42"
|
| 307 |
+
assert dist.package_dir
|
| 308 |
+
package_path = find_package_path("pkg", dist.package_dir, tmp_path)
|
| 309 |
+
assert os.path.exists(package_path)
|
| 310 |
+
assert folder in Path(package_path).parts()
|
| 311 |
+
|
| 312 |
+
_run_build(tmp_path, "--sdist")
|
| 313 |
+
dist_file = tmp_path / "dist/pkg-42.tar.gz"
|
| 314 |
+
assert dist_file.is_file()
|
| 315 |
+
|
| 316 |
+
def test_pyproject_metadata(self, tmp_path):
|
| 317 |
+
_populate_project_dir(tmp_path, ["src/pkg/__init__.py"], {})
|
| 318 |
+
|
| 319 |
+
overwrite = {
|
| 320 |
+
"src": {"pkg": {"__init__.py": "version = 42"}},
|
| 321 |
+
"pyproject.toml": (
|
| 322 |
+
"[project]\nname = 'pkg'\ndynamic = ['version']\n"
|
| 323 |
+
"[tool.setuptools.dynamic]\nversion = {attr = 'pkg.version'}\n"
|
| 324 |
+
),
|
| 325 |
+
}
|
| 326 |
+
jaraco.path.build(overwrite, prefix=tmp_path)
|
| 327 |
+
|
| 328 |
+
dist = _get_dist(tmp_path, {})
|
| 329 |
+
assert dist.get_version() == "42"
|
| 330 |
+
assert dist.package_dir == {"": "src"}
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
class TestWithCExtension:
|
| 334 |
+
def _simulate_package_with_extension(self, tmp_path):
|
| 335 |
+
# This example is based on: https://github.com/nucleic/kiwi/tree/1.4.0
|
| 336 |
+
files = [
|
| 337 |
+
"benchmarks/file.py",
|
| 338 |
+
"docs/Makefile",
|
| 339 |
+
"docs/requirements.txt",
|
| 340 |
+
"docs/source/conf.py",
|
| 341 |
+
"proj/header.h",
|
| 342 |
+
"proj/file.py",
|
| 343 |
+
"py/proj.cpp",
|
| 344 |
+
"py/other.cpp",
|
| 345 |
+
"py/file.py",
|
| 346 |
+
"py/py.typed",
|
| 347 |
+
"py/tests/test_proj.py",
|
| 348 |
+
"README.rst",
|
| 349 |
+
]
|
| 350 |
+
_populate_project_dir(tmp_path, files, {})
|
| 351 |
+
|
| 352 |
+
setup_script = """
|
| 353 |
+
from setuptools import Extension, setup
|
| 354 |
+
|
| 355 |
+
ext_modules = [
|
| 356 |
+
Extension(
|
| 357 |
+
"proj",
|
| 358 |
+
["py/proj.cpp", "py/other.cpp"],
|
| 359 |
+
include_dirs=["."],
|
| 360 |
+
language="c++",
|
| 361 |
+
),
|
| 362 |
+
]
|
| 363 |
+
setup(ext_modules=ext_modules)
|
| 364 |
+
"""
|
| 365 |
+
(tmp_path / "setup.py").write_text(DALS(setup_script), encoding="utf-8")
|
| 366 |
+
|
| 367 |
+
def test_skip_discovery_with_setupcfg_metadata(self, tmp_path):
|
| 368 |
+
"""Ensure that auto-discovery is not triggered when the project is based on
|
| 369 |
+
C-extensions only, for backward compatibility.
|
| 370 |
+
"""
|
| 371 |
+
self._simulate_package_with_extension(tmp_path)
|
| 372 |
+
|
| 373 |
+
pyproject = """
|
| 374 |
+
[build-system]
|
| 375 |
+
requires = []
|
| 376 |
+
build-backend = 'setuptools.build_meta'
|
| 377 |
+
"""
|
| 378 |
+
(tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
|
| 379 |
+
|
| 380 |
+
setupcfg = """
|
| 381 |
+
[metadata]
|
| 382 |
+
name = proj
|
| 383 |
+
version = 42
|
| 384 |
+
"""
|
| 385 |
+
(tmp_path / "setup.cfg").write_text(DALS(setupcfg), encoding="utf-8")
|
| 386 |
+
|
| 387 |
+
dist = _get_dist(tmp_path, {})
|
| 388 |
+
assert dist.get_name() == "proj"
|
| 389 |
+
assert dist.get_version() == "42"
|
| 390 |
+
assert dist.py_modules is None
|
| 391 |
+
assert dist.packages is None
|
| 392 |
+
assert len(dist.ext_modules) == 1
|
| 393 |
+
assert dist.ext_modules[0].name == "proj"
|
| 394 |
+
|
| 395 |
+
def test_dont_skip_discovery_with_pyproject_metadata(self, tmp_path):
|
| 396 |
+
"""When opting-in to pyproject.toml metadata, auto-discovery will be active if
|
| 397 |
+
the package lists C-extensions, but does not configure py-modules or packages.
|
| 398 |
+
|
| 399 |
+
This way we ensure users with complex package layouts that would lead to the
|
| 400 |
+
discovery of multiple top-level modules/packages see errors and are forced to
|
| 401 |
+
explicitly set ``packages`` or ``py-modules``.
|
| 402 |
+
"""
|
| 403 |
+
self._simulate_package_with_extension(tmp_path)
|
| 404 |
+
|
| 405 |
+
pyproject = """
|
| 406 |
+
[project]
|
| 407 |
+
name = 'proj'
|
| 408 |
+
version = '42'
|
| 409 |
+
"""
|
| 410 |
+
(tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
|
| 411 |
+
with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
|
| 412 |
+
_get_dist(tmp_path, {})
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
class TestWithPackageData:
|
| 416 |
+
def _simulate_package_with_data_files(self, tmp_path, src_root):
|
| 417 |
+
files = [
|
| 418 |
+
f"{src_root}/proj/__init__.py",
|
| 419 |
+
f"{src_root}/proj/file1.txt",
|
| 420 |
+
f"{src_root}/proj/nested/file2.txt",
|
| 421 |
+
]
|
| 422 |
+
_populate_project_dir(tmp_path, files, {})
|
| 423 |
+
|
| 424 |
+
manifest = """
|
| 425 |
+
global-include *.py *.txt
|
| 426 |
+
"""
|
| 427 |
+
(tmp_path / "MANIFEST.in").write_text(DALS(manifest), encoding="utf-8")
|
| 428 |
+
|
| 429 |
+
EXAMPLE_SETUPCFG = """
|
| 430 |
+
[metadata]
|
| 431 |
+
name = proj
|
| 432 |
+
version = 42
|
| 433 |
+
|
| 434 |
+
[options]
|
| 435 |
+
include_package_data = True
|
| 436 |
+
"""
|
| 437 |
+
EXAMPLE_PYPROJECT = """
|
| 438 |
+
[project]
|
| 439 |
+
name = "proj"
|
| 440 |
+
version = "42"
|
| 441 |
+
"""
|
| 442 |
+
|
| 443 |
+
PYPROJECT_PACKAGE_DIR = """
|
| 444 |
+
[tool.setuptools]
|
| 445 |
+
package-dir = {"" = "src"}
|
| 446 |
+
"""
|
| 447 |
+
|
| 448 |
+
@pytest.mark.parametrize(
|
| 449 |
+
("src_root", "files"),
|
| 450 |
+
[
|
| 451 |
+
(".", {"setup.cfg": DALS(EXAMPLE_SETUPCFG)}),
|
| 452 |
+
(".", {"pyproject.toml": DALS(EXAMPLE_PYPROJECT)}),
|
| 453 |
+
("src", {"setup.cfg": DALS(EXAMPLE_SETUPCFG)}),
|
| 454 |
+
("src", {"pyproject.toml": DALS(EXAMPLE_PYPROJECT)}),
|
| 455 |
+
(
|
| 456 |
+
"src",
|
| 457 |
+
{
|
| 458 |
+
"setup.cfg": DALS(EXAMPLE_SETUPCFG)
|
| 459 |
+
+ DALS(
|
| 460 |
+
"""
|
| 461 |
+
packages = find:
|
| 462 |
+
package_dir =
|
| 463 |
+
=src
|
| 464 |
+
|
| 465 |
+
[options.packages.find]
|
| 466 |
+
where = src
|
| 467 |
+
"""
|
| 468 |
+
)
|
| 469 |
+
},
|
| 470 |
+
),
|
| 471 |
+
(
|
| 472 |
+
"src",
|
| 473 |
+
{
|
| 474 |
+
"pyproject.toml": DALS(EXAMPLE_PYPROJECT)
|
| 475 |
+
+ DALS(
|
| 476 |
+
"""
|
| 477 |
+
[tool.setuptools]
|
| 478 |
+
package-dir = {"" = "src"}
|
| 479 |
+
"""
|
| 480 |
+
)
|
| 481 |
+
},
|
| 482 |
+
),
|
| 483 |
+
],
|
| 484 |
+
)
|
| 485 |
+
def test_include_package_data(self, tmp_path, src_root, files):
|
| 486 |
+
"""
|
| 487 |
+
Make sure auto-discovery does not affect package include_package_data.
|
| 488 |
+
See issue #3196.
|
| 489 |
+
"""
|
| 490 |
+
jaraco.path.build(files, prefix=str(tmp_path))
|
| 491 |
+
self._simulate_package_with_data_files(tmp_path, src_root)
|
| 492 |
+
|
| 493 |
+
expected = {
|
| 494 |
+
os.path.normpath(f"{src_root}/proj/file1.txt").replace(os.sep, "/"),
|
| 495 |
+
os.path.normpath(f"{src_root}/proj/nested/file2.txt").replace(os.sep, "/"),
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
_run_build(tmp_path)
|
| 499 |
+
|
| 500 |
+
sdist_files = get_sdist_members(next(tmp_path.glob("dist/*.tar.gz")))
|
| 501 |
+
print("~~~~~ sdist_members ~~~~~")
|
| 502 |
+
print('\n'.join(sdist_files))
|
| 503 |
+
assert sdist_files >= expected
|
| 504 |
+
|
| 505 |
+
wheel_files = get_wheel_members(next(tmp_path.glob("dist/*.whl")))
|
| 506 |
+
print("~~~~~ wheel_members ~~~~~")
|
| 507 |
+
print('\n'.join(wheel_files))
|
| 508 |
+
orig_files = {f.replace("src/", "").replace("lib/", "") for f in expected}
|
| 509 |
+
assert wheel_files >= orig_files
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
def test_compatible_with_numpy_configuration(tmp_path):
|
| 513 |
+
files = [
|
| 514 |
+
"dir1/__init__.py",
|
| 515 |
+
"dir2/__init__.py",
|
| 516 |
+
"file.py",
|
| 517 |
+
]
|
| 518 |
+
_populate_project_dir(tmp_path, files, {})
|
| 519 |
+
dist = Distribution({})
|
| 520 |
+
dist.configuration = object()
|
| 521 |
+
dist.set_defaults()
|
| 522 |
+
assert dist.py_modules is None
|
| 523 |
+
assert dist.packages is None
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
def test_name_discovery_doesnt_break_cli(tmpdir_cwd):
|
| 527 |
+
jaraco.path.build({"pkg.py": ""})
|
| 528 |
+
dist = Distribution({})
|
| 529 |
+
dist.script_args = ["--name"]
|
| 530 |
+
dist.set_defaults()
|
| 531 |
+
dist.parse_command_line() # <-- no exception should be raised here.
|
| 532 |
+
assert dist.get_name() == "pkg"
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
def test_preserve_explicit_name_with_dynamic_version(tmpdir_cwd, monkeypatch):
|
| 536 |
+
"""According to #3545 it seems that ``name`` discovery is running,
|
| 537 |
+
even when the project already explicitly sets it.
|
| 538 |
+
This seems to be related to parsing of dynamic versions (via ``attr`` directive),
|
| 539 |
+
which requires the auto-discovery of ``package_dir``.
|
| 540 |
+
"""
|
| 541 |
+
files = {
|
| 542 |
+
"src": {
|
| 543 |
+
"pkg": {"__init__.py": "__version__ = 42\n"},
|
| 544 |
+
},
|
| 545 |
+
"pyproject.toml": DALS(
|
| 546 |
+
"""
|
| 547 |
+
[project]
|
| 548 |
+
name = "myproj" # purposefully different from package name
|
| 549 |
+
dynamic = ["version"]
|
| 550 |
+
[tool.setuptools.dynamic]
|
| 551 |
+
version = {"attr" = "pkg.__version__"}
|
| 552 |
+
"""
|
| 553 |
+
),
|
| 554 |
+
}
|
| 555 |
+
jaraco.path.build(files)
|
| 556 |
+
dist = Distribution({})
|
| 557 |
+
orig_analyse_name = dist.set_defaults.analyse_name
|
| 558 |
+
|
| 559 |
+
def spy_analyse_name():
|
| 560 |
+
# We can check if name discovery was triggered by ensuring the original
|
| 561 |
+
# name remains instead of the package name.
|
| 562 |
+
orig_analyse_name()
|
| 563 |
+
assert dist.get_name() == "myproj"
|
| 564 |
+
|
| 565 |
+
monkeypatch.setattr(dist.set_defaults, "analyse_name", spy_analyse_name)
|
| 566 |
+
dist.parse_config_files()
|
| 567 |
+
assert dist.get_version() == "42"
|
| 568 |
+
assert set(dist.packages) == {"pkg"}
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
def _populate_project_dir(root, files, options):
|
| 572 |
+
# NOTE: Currently pypa/build will refuse to build the project if no
|
| 573 |
+
# `pyproject.toml` or `setup.py` is found. So it is impossible to do
|
| 574 |
+
# completely "config-less" projects.
|
| 575 |
+
basic = {
|
| 576 |
+
"setup.py": "import setuptools\nsetuptools.setup()",
|
| 577 |
+
"README.md": "# Example Package",
|
| 578 |
+
"LICENSE": "Copyright (c) 2018",
|
| 579 |
+
}
|
| 580 |
+
jaraco.path.build(basic, prefix=root)
|
| 581 |
+
_write_setupcfg(root, options)
|
| 582 |
+
paths = (root / f for f in files)
|
| 583 |
+
for path in paths:
|
| 584 |
+
path.parent.mkdir(exist_ok=True, parents=True)
|
| 585 |
+
path.touch()
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
def _write_setupcfg(root, options):
|
| 589 |
+
if not options:
|
| 590 |
+
print("~~~~~ **NO** setup.cfg ~~~~~")
|
| 591 |
+
return
|
| 592 |
+
setupcfg = ConfigParser()
|
| 593 |
+
setupcfg.add_section("options")
|
| 594 |
+
for key, value in options.items():
|
| 595 |
+
if key == "packages.find":
|
| 596 |
+
setupcfg.add_section(f"options.{key}")
|
| 597 |
+
setupcfg[f"options.{key}"].update(value)
|
| 598 |
+
elif isinstance(value, list):
|
| 599 |
+
setupcfg["options"][key] = ", ".join(value)
|
| 600 |
+
elif isinstance(value, dict):
|
| 601 |
+
str_value = "\n".join(f"\t{k} = {v}" for k, v in value.items())
|
| 602 |
+
setupcfg["options"][key] = "\n" + str_value
|
| 603 |
+
else:
|
| 604 |
+
setupcfg["options"][key] = str(value)
|
| 605 |
+
with open(root / "setup.cfg", "w", encoding="utf-8") as f:
|
| 606 |
+
setupcfg.write(f)
|
| 607 |
+
print("~~~~~ setup.cfg ~~~~~")
|
| 608 |
+
print((root / "setup.cfg").read_text(encoding="utf-8"))
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def _run_build(path, *flags):
|
| 612 |
+
cmd = [sys.executable, "-m", "build", "--no-isolation", *flags, str(path)]
|
| 613 |
+
return run(cmd, env={'DISTUTILS_DEBUG': ''})
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
def _get_dist(dist_path, attrs):
|
| 617 |
+
root = "/".join(os.path.split(dist_path)) # POSIX-style
|
| 618 |
+
|
| 619 |
+
script = dist_path / 'setup.py'
|
| 620 |
+
if script.exists():
|
| 621 |
+
with Path(dist_path):
|
| 622 |
+
dist = cast(
|
| 623 |
+
Distribution,
|
| 624 |
+
distutils.core.run_setup("setup.py", {}, stop_after="init"),
|
| 625 |
+
)
|
| 626 |
+
else:
|
| 627 |
+
dist = Distribution(attrs)
|
| 628 |
+
|
| 629 |
+
dist.src_root = root
|
| 630 |
+
dist.script_name = "setup.py"
|
| 631 |
+
with Path(dist_path):
|
| 632 |
+
dist.parse_config_files()
|
| 633 |
+
|
| 634 |
+
dist.set_defaults()
|
| 635 |
+
return dist
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
def _run_sdist_programatically(dist_path, attrs):
|
| 639 |
+
dist = _get_dist(dist_path, attrs)
|
| 640 |
+
cmd = sdist(dist)
|
| 641 |
+
cmd.ensure_finalized()
|
| 642 |
+
assert cmd.distribution.packages or cmd.distribution.py_modules
|
| 643 |
+
|
| 644 |
+
with quiet(), Path(dist_path):
|
| 645 |
+
cmd.run()
|
| 646 |
+
|
| 647 |
+
return dist, cmd
|
venv/lib/python3.11/site-packages/setuptools/tests/test_core_metadata.py
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import functools
|
| 4 |
+
import importlib
|
| 5 |
+
import io
|
| 6 |
+
from email import message_from_string
|
| 7 |
+
from email.generator import Generator
|
| 8 |
+
from email.message import EmailMessage, Message
|
| 9 |
+
from email.parser import Parser
|
| 10 |
+
from email.policy import EmailPolicy
|
| 11 |
+
from inspect import cleandoc
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from unittest.mock import Mock
|
| 14 |
+
|
| 15 |
+
import jaraco.path
|
| 16 |
+
import pytest
|
| 17 |
+
from packaging.metadata import Metadata
|
| 18 |
+
from packaging.requirements import Requirement
|
| 19 |
+
|
| 20 |
+
from setuptools import _reqs, sic
|
| 21 |
+
from setuptools._core_metadata import rfc822_escape, rfc822_unescape
|
| 22 |
+
from setuptools.command.egg_info import egg_info, write_requirements
|
| 23 |
+
from setuptools.config import expand, setupcfg
|
| 24 |
+
from setuptools.dist import Distribution
|
| 25 |
+
|
| 26 |
+
from .config.downloads import retrieve_file, urls_from_file
|
| 27 |
+
|
| 28 |
+
EXAMPLE_BASE_INFO = dict(
|
| 29 |
+
name="package",
|
| 30 |
+
version="0.0.1",
|
| 31 |
+
author="Foo Bar",
|
| 32 |
+
author_email="foo@bar.net",
|
| 33 |
+
long_description="Long\ndescription",
|
| 34 |
+
description="Short description",
|
| 35 |
+
keywords=["one", "two"],
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@pytest.mark.parametrize(
|
| 40 |
+
("content", "result"),
|
| 41 |
+
(
|
| 42 |
+
pytest.param(
|
| 43 |
+
"Just a single line",
|
| 44 |
+
None,
|
| 45 |
+
id="single_line",
|
| 46 |
+
),
|
| 47 |
+
pytest.param(
|
| 48 |
+
"Multiline\nText\nwithout\nextra indents\n",
|
| 49 |
+
None,
|
| 50 |
+
id="multiline",
|
| 51 |
+
),
|
| 52 |
+
pytest.param(
|
| 53 |
+
"Multiline\n With\n\nadditional\n indentation",
|
| 54 |
+
None,
|
| 55 |
+
id="multiline_with_indentation",
|
| 56 |
+
),
|
| 57 |
+
pytest.param(
|
| 58 |
+
" Leading whitespace",
|
| 59 |
+
"Leading whitespace",
|
| 60 |
+
id="remove_leading_whitespace",
|
| 61 |
+
),
|
| 62 |
+
pytest.param(
|
| 63 |
+
" Leading whitespace\nIn\n Multiline comment",
|
| 64 |
+
"Leading whitespace\nIn\n Multiline comment",
|
| 65 |
+
id="remove_leading_whitespace_multiline",
|
| 66 |
+
),
|
| 67 |
+
),
|
| 68 |
+
)
|
| 69 |
+
def test_rfc822_unescape(content, result):
|
| 70 |
+
assert (result or content) == rfc822_unescape(rfc822_escape(content))
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def __read_test_cases():
|
| 74 |
+
base = EXAMPLE_BASE_INFO
|
| 75 |
+
|
| 76 |
+
params = functools.partial(dict, base)
|
| 77 |
+
|
| 78 |
+
return [
|
| 79 |
+
('Metadata version 1.0', params()),
|
| 80 |
+
(
|
| 81 |
+
'Metadata Version 1.0: Short long description',
|
| 82 |
+
params(
|
| 83 |
+
long_description='Short long description',
|
| 84 |
+
),
|
| 85 |
+
),
|
| 86 |
+
(
|
| 87 |
+
'Metadata version 1.1: Classifiers',
|
| 88 |
+
params(
|
| 89 |
+
classifiers=[
|
| 90 |
+
'Programming Language :: Python :: 3',
|
| 91 |
+
'Programming Language :: Python :: 3.7',
|
| 92 |
+
'License :: OSI Approved :: MIT License',
|
| 93 |
+
],
|
| 94 |
+
),
|
| 95 |
+
),
|
| 96 |
+
(
|
| 97 |
+
'Metadata version 1.1: Download URL',
|
| 98 |
+
params(
|
| 99 |
+
download_url='https://example.com',
|
| 100 |
+
),
|
| 101 |
+
),
|
| 102 |
+
(
|
| 103 |
+
'Metadata Version 1.2: Requires-Python',
|
| 104 |
+
params(
|
| 105 |
+
python_requires='>=3.7',
|
| 106 |
+
),
|
| 107 |
+
),
|
| 108 |
+
pytest.param(
|
| 109 |
+
'Metadata Version 1.2: Project-Url',
|
| 110 |
+
params(project_urls=dict(Foo='https://example.bar')),
|
| 111 |
+
marks=pytest.mark.xfail(
|
| 112 |
+
reason="Issue #1578: project_urls not read",
|
| 113 |
+
),
|
| 114 |
+
),
|
| 115 |
+
(
|
| 116 |
+
'Metadata Version 2.1: Long Description Content Type',
|
| 117 |
+
params(
|
| 118 |
+
long_description_content_type='text/x-rst; charset=UTF-8',
|
| 119 |
+
),
|
| 120 |
+
),
|
| 121 |
+
(
|
| 122 |
+
'License',
|
| 123 |
+
params(
|
| 124 |
+
license='MIT',
|
| 125 |
+
),
|
| 126 |
+
),
|
| 127 |
+
(
|
| 128 |
+
'License multiline',
|
| 129 |
+
params(
|
| 130 |
+
license='This is a long license \nover multiple lines',
|
| 131 |
+
),
|
| 132 |
+
),
|
| 133 |
+
pytest.param(
|
| 134 |
+
'Metadata Version 2.1: Provides Extra',
|
| 135 |
+
params(provides_extras=['foo', 'bar']),
|
| 136 |
+
marks=pytest.mark.xfail(reason="provides_extras not read"),
|
| 137 |
+
),
|
| 138 |
+
(
|
| 139 |
+
'Missing author',
|
| 140 |
+
dict(
|
| 141 |
+
name='foo',
|
| 142 |
+
version='1.0.0',
|
| 143 |
+
author_email='snorri@sturluson.name',
|
| 144 |
+
),
|
| 145 |
+
),
|
| 146 |
+
(
|
| 147 |
+
'Missing author e-mail',
|
| 148 |
+
dict(
|
| 149 |
+
name='foo',
|
| 150 |
+
version='1.0.0',
|
| 151 |
+
author='Snorri Sturluson',
|
| 152 |
+
),
|
| 153 |
+
),
|
| 154 |
+
(
|
| 155 |
+
'Missing author and e-mail',
|
| 156 |
+
dict(
|
| 157 |
+
name='foo',
|
| 158 |
+
version='1.0.0',
|
| 159 |
+
),
|
| 160 |
+
),
|
| 161 |
+
(
|
| 162 |
+
'Bypass normalized version',
|
| 163 |
+
dict(
|
| 164 |
+
name='foo',
|
| 165 |
+
version=sic('1.0.0a'),
|
| 166 |
+
),
|
| 167 |
+
),
|
| 168 |
+
]
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
@pytest.mark.parametrize(("name", "attrs"), __read_test_cases())
|
| 172 |
+
def test_read_metadata(name, attrs):
|
| 173 |
+
dist = Distribution(attrs)
|
| 174 |
+
metadata_out = dist.metadata
|
| 175 |
+
dist_class = metadata_out.__class__
|
| 176 |
+
|
| 177 |
+
# Write to PKG_INFO and then load into a new metadata object
|
| 178 |
+
PKG_INFO = io.StringIO()
|
| 179 |
+
|
| 180 |
+
metadata_out.write_pkg_file(PKG_INFO)
|
| 181 |
+
PKG_INFO.seek(0)
|
| 182 |
+
pkg_info = PKG_INFO.read()
|
| 183 |
+
assert _valid_metadata(pkg_info)
|
| 184 |
+
|
| 185 |
+
PKG_INFO.seek(0)
|
| 186 |
+
metadata_in = dist_class()
|
| 187 |
+
metadata_in.read_pkg_file(PKG_INFO)
|
| 188 |
+
|
| 189 |
+
tested_attrs = [
|
| 190 |
+
('name', dist_class.get_name),
|
| 191 |
+
('version', dist_class.get_version),
|
| 192 |
+
('author', dist_class.get_contact),
|
| 193 |
+
('author_email', dist_class.get_contact_email),
|
| 194 |
+
('metadata_version', dist_class.get_metadata_version),
|
| 195 |
+
('provides', dist_class.get_provides),
|
| 196 |
+
('description', dist_class.get_description),
|
| 197 |
+
('long_description', dist_class.get_long_description),
|
| 198 |
+
('download_url', dist_class.get_download_url),
|
| 199 |
+
('keywords', dist_class.get_keywords),
|
| 200 |
+
('platforms', dist_class.get_platforms),
|
| 201 |
+
('obsoletes', dist_class.get_obsoletes),
|
| 202 |
+
('requires', dist_class.get_requires),
|
| 203 |
+
('classifiers', dist_class.get_classifiers),
|
| 204 |
+
('project_urls', lambda s: getattr(s, 'project_urls', {})),
|
| 205 |
+
('provides_extras', lambda s: getattr(s, 'provides_extras', {})),
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
for attr, getter in tested_attrs:
|
| 209 |
+
assert getter(metadata_in) == getter(metadata_out)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def __maintainer_test_cases():
|
| 213 |
+
attrs = {"name": "package", "version": "1.0", "description": "xxx"}
|
| 214 |
+
|
| 215 |
+
def merge_dicts(d1, d2):
|
| 216 |
+
d1 = d1.copy()
|
| 217 |
+
d1.update(d2)
|
| 218 |
+
|
| 219 |
+
return d1
|
| 220 |
+
|
| 221 |
+
return [
|
| 222 |
+
('No author, no maintainer', attrs.copy()),
|
| 223 |
+
(
|
| 224 |
+
'Author (no e-mail), no maintainer',
|
| 225 |
+
merge_dicts(attrs, {'author': 'Author Name'}),
|
| 226 |
+
),
|
| 227 |
+
(
|
| 228 |
+
'Author (e-mail), no maintainer',
|
| 229 |
+
merge_dicts(
|
| 230 |
+
attrs, {'author': 'Author Name', 'author_email': 'author@name.com'}
|
| 231 |
+
),
|
| 232 |
+
),
|
| 233 |
+
(
|
| 234 |
+
'No author, maintainer (no e-mail)',
|
| 235 |
+
merge_dicts(attrs, {'maintainer': 'Maintainer Name'}),
|
| 236 |
+
),
|
| 237 |
+
(
|
| 238 |
+
'No author, maintainer (e-mail)',
|
| 239 |
+
merge_dicts(
|
| 240 |
+
attrs,
|
| 241 |
+
{
|
| 242 |
+
'maintainer': 'Maintainer Name',
|
| 243 |
+
'maintainer_email': 'maintainer@name.com',
|
| 244 |
+
},
|
| 245 |
+
),
|
| 246 |
+
),
|
| 247 |
+
(
|
| 248 |
+
'Author (no e-mail), Maintainer (no-email)',
|
| 249 |
+
merge_dicts(
|
| 250 |
+
attrs, {'author': 'Author Name', 'maintainer': 'Maintainer Name'}
|
| 251 |
+
),
|
| 252 |
+
),
|
| 253 |
+
(
|
| 254 |
+
'Author (e-mail), Maintainer (e-mail)',
|
| 255 |
+
merge_dicts(
|
| 256 |
+
attrs,
|
| 257 |
+
{
|
| 258 |
+
'author': 'Author Name',
|
| 259 |
+
'author_email': 'author@name.com',
|
| 260 |
+
'maintainer': 'Maintainer Name',
|
| 261 |
+
'maintainer_email': 'maintainer@name.com',
|
| 262 |
+
},
|
| 263 |
+
),
|
| 264 |
+
),
|
| 265 |
+
(
|
| 266 |
+
'No author (e-mail), no maintainer (e-mail)',
|
| 267 |
+
merge_dicts(
|
| 268 |
+
attrs,
|
| 269 |
+
{
|
| 270 |
+
'author_email': 'author@name.com',
|
| 271 |
+
'maintainer_email': 'maintainer@name.com',
|
| 272 |
+
},
|
| 273 |
+
),
|
| 274 |
+
),
|
| 275 |
+
('Author unicode', merge_dicts(attrs, {'author': '鉄沢寛'})),
|
| 276 |
+
('Maintainer unicode', merge_dicts(attrs, {'maintainer': 'Jan Łukasiewicz'})),
|
| 277 |
+
]
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
@pytest.mark.parametrize(("name", "attrs"), __maintainer_test_cases())
|
| 281 |
+
def test_maintainer_author(name, attrs, tmpdir):
|
| 282 |
+
tested_keys = {
|
| 283 |
+
'author': 'Author',
|
| 284 |
+
'author_email': 'Author-email',
|
| 285 |
+
'maintainer': 'Maintainer',
|
| 286 |
+
'maintainer_email': 'Maintainer-email',
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
# Generate a PKG-INFO file
|
| 290 |
+
dist = Distribution(attrs)
|
| 291 |
+
fn = tmpdir.mkdir('pkg_info')
|
| 292 |
+
fn_s = str(fn)
|
| 293 |
+
|
| 294 |
+
dist.metadata.write_pkg_info(fn_s)
|
| 295 |
+
|
| 296 |
+
with open(str(fn.join('PKG-INFO')), 'r', encoding='utf-8') as f:
|
| 297 |
+
pkg_info = f.read()
|
| 298 |
+
|
| 299 |
+
assert _valid_metadata(pkg_info)
|
| 300 |
+
|
| 301 |
+
# Drop blank lines and strip lines from default description
|
| 302 |
+
raw_pkg_lines = pkg_info.splitlines()
|
| 303 |
+
pkg_lines = list(filter(None, raw_pkg_lines[:-2]))
|
| 304 |
+
|
| 305 |
+
pkg_lines_set = set(pkg_lines)
|
| 306 |
+
|
| 307 |
+
# Duplicate lines should not be generated
|
| 308 |
+
assert len(pkg_lines) == len(pkg_lines_set)
|
| 309 |
+
|
| 310 |
+
for fkey, dkey in tested_keys.items():
|
| 311 |
+
val = attrs.get(dkey, None)
|
| 312 |
+
if val is None:
|
| 313 |
+
for line in pkg_lines:
|
| 314 |
+
assert not line.startswith(fkey + ':')
|
| 315 |
+
else:
|
| 316 |
+
line = f'{fkey}: {val}'
|
| 317 |
+
assert line in pkg_lines_set
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
class TestParityWithMetadataFromPyPaWheel:
|
| 321 |
+
def base_example(self):
|
| 322 |
+
attrs = dict(
|
| 323 |
+
**EXAMPLE_BASE_INFO,
|
| 324 |
+
# Example with complex requirement definition
|
| 325 |
+
python_requires=">=3.8",
|
| 326 |
+
install_requires="""
|
| 327 |
+
packaging==23.2
|
| 328 |
+
more-itertools==8.8.0; extra == "other"
|
| 329 |
+
jaraco.text==3.7.0
|
| 330 |
+
importlib-resources==5.10.2; python_version<"3.8"
|
| 331 |
+
importlib-metadata==6.0.0 ; python_version<"3.8"
|
| 332 |
+
colorama>=0.4.4; sys_platform == "win32"
|
| 333 |
+
""",
|
| 334 |
+
extras_require={
|
| 335 |
+
"testing": """
|
| 336 |
+
pytest >= 6
|
| 337 |
+
pytest-checkdocs >= 2.4
|
| 338 |
+
tomli ; \\
|
| 339 |
+
# Using stdlib when possible
|
| 340 |
+
python_version < "3.11"
|
| 341 |
+
ini2toml[lite]>=0.9
|
| 342 |
+
""",
|
| 343 |
+
"other": [],
|
| 344 |
+
},
|
| 345 |
+
)
|
| 346 |
+
# Generate a PKG-INFO file using setuptools
|
| 347 |
+
return Distribution(attrs)
|
| 348 |
+
|
| 349 |
+
def test_requires_dist(self, tmp_path):
|
| 350 |
+
dist = self.base_example()
|
| 351 |
+
pkg_info = _get_pkginfo(dist)
|
| 352 |
+
assert _valid_metadata(pkg_info)
|
| 353 |
+
|
| 354 |
+
# Ensure Requires-Dist is present
|
| 355 |
+
expected = [
|
| 356 |
+
'Metadata-Version:',
|
| 357 |
+
'Requires-Python: >=3.8',
|
| 358 |
+
'Provides-Extra: other',
|
| 359 |
+
'Provides-Extra: testing',
|
| 360 |
+
'Requires-Dist: tomli; python_version < "3.11" and extra == "testing"',
|
| 361 |
+
'Requires-Dist: more-itertools==8.8.0; extra == "other"',
|
| 362 |
+
'Requires-Dist: ini2toml[lite]>=0.9; extra == "testing"',
|
| 363 |
+
]
|
| 364 |
+
for line in expected:
|
| 365 |
+
assert line in pkg_info
|
| 366 |
+
|
| 367 |
+
HERE = Path(__file__).parent
|
| 368 |
+
EXAMPLES_FILE = HERE / "config/setupcfg_examples.txt"
|
| 369 |
+
|
| 370 |
+
@pytest.fixture(params=[None, *urls_from_file(EXAMPLES_FILE)])
|
| 371 |
+
def dist(self, request, monkeypatch, tmp_path):
|
| 372 |
+
"""Example of distribution with arbitrary configuration"""
|
| 373 |
+
monkeypatch.chdir(tmp_path)
|
| 374 |
+
monkeypatch.setattr(expand, "read_attr", Mock(return_value="0.42"))
|
| 375 |
+
monkeypatch.setattr(expand, "read_files", Mock(return_value="hello world"))
|
| 376 |
+
monkeypatch.setattr(
|
| 377 |
+
Distribution, "_finalize_license_files", Mock(return_value=None)
|
| 378 |
+
)
|
| 379 |
+
if request.param is None:
|
| 380 |
+
yield self.base_example()
|
| 381 |
+
else:
|
| 382 |
+
# Real-world usage
|
| 383 |
+
config = retrieve_file(request.param)
|
| 384 |
+
yield setupcfg.apply_configuration(Distribution({}), config)
|
| 385 |
+
|
| 386 |
+
@pytest.mark.uses_network
|
| 387 |
+
def test_equivalent_output(self, tmp_path, dist):
|
| 388 |
+
"""Ensure output from setuptools is equivalent to the one from `pypa/wheel`"""
|
| 389 |
+
# Generate a METADATA file using pypa/wheel for comparison
|
| 390 |
+
wheel_metadata = importlib.import_module("wheel.metadata")
|
| 391 |
+
pkginfo_to_metadata = getattr(wheel_metadata, "pkginfo_to_metadata", None)
|
| 392 |
+
|
| 393 |
+
if pkginfo_to_metadata is None: # pragma: nocover
|
| 394 |
+
pytest.xfail(
|
| 395 |
+
"wheel.metadata.pkginfo_to_metadata is undefined, "
|
| 396 |
+
"(this is likely to be caused by API changes in pypa/wheel"
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
# Generate an simplified "egg-info" dir for pypa/wheel to convert
|
| 400 |
+
pkg_info = _get_pkginfo(dist)
|
| 401 |
+
egg_info_dir = tmp_path / "pkg.egg-info"
|
| 402 |
+
egg_info_dir.mkdir(parents=True)
|
| 403 |
+
(egg_info_dir / "PKG-INFO").write_text(pkg_info, encoding="utf-8")
|
| 404 |
+
write_requirements(egg_info(dist), egg_info_dir, egg_info_dir / "requires.txt")
|
| 405 |
+
|
| 406 |
+
# Get pypa/wheel generated METADATA but normalize requirements formatting
|
| 407 |
+
metadata_msg = pkginfo_to_metadata(egg_info_dir, egg_info_dir / "PKG-INFO")
|
| 408 |
+
metadata_str = _normalize_metadata(metadata_msg)
|
| 409 |
+
pkg_info_msg = message_from_string(pkg_info)
|
| 410 |
+
pkg_info_str = _normalize_metadata(pkg_info_msg)
|
| 411 |
+
|
| 412 |
+
# Compare setuptools PKG-INFO x pypa/wheel METADATA
|
| 413 |
+
assert metadata_str == pkg_info_str
|
| 414 |
+
|
| 415 |
+
# Make sure it parses/serializes well in pypa/wheel
|
| 416 |
+
_assert_roundtrip_message(pkg_info)
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class TestPEP643:
|
| 420 |
+
STATIC_CONFIG = {
|
| 421 |
+
"setup.cfg": cleandoc(
|
| 422 |
+
"""
|
| 423 |
+
[metadata]
|
| 424 |
+
name = package
|
| 425 |
+
version = 0.0.1
|
| 426 |
+
author = Foo Bar
|
| 427 |
+
author_email = foo@bar.net
|
| 428 |
+
long_description = Long
|
| 429 |
+
description
|
| 430 |
+
description = Short description
|
| 431 |
+
keywords = one, two
|
| 432 |
+
platforms = abcd
|
| 433 |
+
[options]
|
| 434 |
+
install_requires = requests
|
| 435 |
+
"""
|
| 436 |
+
),
|
| 437 |
+
"pyproject.toml": cleandoc(
|
| 438 |
+
"""
|
| 439 |
+
[project]
|
| 440 |
+
name = "package"
|
| 441 |
+
version = "0.0.1"
|
| 442 |
+
authors = [
|
| 443 |
+
{name = "Foo Bar", email = "foo@bar.net"}
|
| 444 |
+
]
|
| 445 |
+
description = "Short description"
|
| 446 |
+
readme = {text = "Long\\ndescription", content-type = "text/plain"}
|
| 447 |
+
keywords = ["one", "two"]
|
| 448 |
+
dependencies = ["requests"]
|
| 449 |
+
license = "AGPL-3.0-or-later"
|
| 450 |
+
[tool.setuptools]
|
| 451 |
+
provides = ["abcd"]
|
| 452 |
+
obsoletes = ["abcd"]
|
| 453 |
+
"""
|
| 454 |
+
),
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
@pytest.mark.parametrize("file", STATIC_CONFIG.keys())
|
| 458 |
+
def test_static_config_has_no_dynamic(self, file, tmpdir_cwd):
|
| 459 |
+
Path(file).write_text(self.STATIC_CONFIG[file], encoding="utf-8")
|
| 460 |
+
metadata = _get_metadata()
|
| 461 |
+
assert metadata.get_all("Dynamic") is None
|
| 462 |
+
assert metadata.get_all("dynamic") is None
|
| 463 |
+
|
| 464 |
+
@pytest.mark.parametrize("file", STATIC_CONFIG.keys())
|
| 465 |
+
@pytest.mark.parametrize(
|
| 466 |
+
"fields",
|
| 467 |
+
[
|
| 468 |
+
# Single dynamic field
|
| 469 |
+
{"requires-python": ("python_requires", ">=3.12")},
|
| 470 |
+
{"author-email": ("author_email", "snoopy@peanuts.com")},
|
| 471 |
+
{"keywords": ("keywords", ["hello", "world"])},
|
| 472 |
+
{"platform": ("platforms", ["abcd"])},
|
| 473 |
+
# Multiple dynamic fields
|
| 474 |
+
{
|
| 475 |
+
"summary": ("description", "hello world"),
|
| 476 |
+
"description": ("long_description", "bla bla bla bla"),
|
| 477 |
+
"requires-dist": ("install_requires", ["hello-world"]),
|
| 478 |
+
},
|
| 479 |
+
],
|
| 480 |
+
)
|
| 481 |
+
def test_modified_fields_marked_as_dynamic(self, file, fields, tmpdir_cwd):
|
| 482 |
+
# We start with a static config
|
| 483 |
+
Path(file).write_text(self.STATIC_CONFIG[file], encoding="utf-8")
|
| 484 |
+
dist = _makedist()
|
| 485 |
+
|
| 486 |
+
# ... but then we simulate the effects of a plugin modifying the distribution
|
| 487 |
+
for attr, value in fields.values():
|
| 488 |
+
# `dist` and `dist.metadata` are complicated...
|
| 489 |
+
# Some attributes work when set on `dist`, others on `dist.metadata`...
|
| 490 |
+
# Here we set in both just in case (this also avoids calling `_finalize_*`)
|
| 491 |
+
setattr(dist, attr, value)
|
| 492 |
+
setattr(dist.metadata, attr, value)
|
| 493 |
+
|
| 494 |
+
# Then we should be able to list the modified fields as Dynamic
|
| 495 |
+
metadata = _get_metadata(dist)
|
| 496 |
+
assert set(metadata.get_all("Dynamic")) == set(fields)
|
| 497 |
+
|
| 498 |
+
@pytest.mark.parametrize(
|
| 499 |
+
"extra_toml",
|
| 500 |
+
[
|
| 501 |
+
"# Let setuptools autofill license-files",
|
| 502 |
+
"license-files = ['LICENSE*', 'AUTHORS*', 'NOTICE']",
|
| 503 |
+
],
|
| 504 |
+
)
|
| 505 |
+
def test_license_files_dynamic(self, extra_toml, tmpdir_cwd):
|
| 506 |
+
# For simplicity (and for the time being) setuptools is not making
|
| 507 |
+
# any special handling to guarantee `License-File` is considered static.
|
| 508 |
+
# Instead we rely in the fact that, although suboptimal, it is OK to have
|
| 509 |
+
# it as dynamics, as per:
|
| 510 |
+
# https://github.com/pypa/setuptools/issues/4629#issuecomment-2331233677
|
| 511 |
+
files = {
|
| 512 |
+
"pyproject.toml": self.STATIC_CONFIG["pyproject.toml"].replace(
|
| 513 |
+
'license = "AGPL-3.0-or-later"',
|
| 514 |
+
f"dynamic = ['license']\n{extra_toml}",
|
| 515 |
+
),
|
| 516 |
+
"LICENSE.md": "--- mock license ---",
|
| 517 |
+
"NOTICE": "--- mock notice ---",
|
| 518 |
+
"AUTHORS.txt": "--- me ---",
|
| 519 |
+
}
|
| 520 |
+
# Sanity checks:
|
| 521 |
+
assert extra_toml in files["pyproject.toml"]
|
| 522 |
+
assert 'license = "AGPL-3.0-or-later"' not in extra_toml
|
| 523 |
+
|
| 524 |
+
jaraco.path.build(files)
|
| 525 |
+
dist = _makedist(license_expression="AGPL-3.0-or-later")
|
| 526 |
+
metadata = _get_metadata(dist)
|
| 527 |
+
assert set(metadata.get_all("Dynamic")) == {
|
| 528 |
+
'license-file',
|
| 529 |
+
'license-expression',
|
| 530 |
+
}
|
| 531 |
+
assert metadata.get("License-Expression") == "AGPL-3.0-or-later"
|
| 532 |
+
assert set(metadata.get_all("License-File")) == {
|
| 533 |
+
"NOTICE",
|
| 534 |
+
"AUTHORS.txt",
|
| 535 |
+
"LICENSE.md",
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
def _makedist(**attrs):
|
| 540 |
+
dist = Distribution(attrs)
|
| 541 |
+
dist.parse_config_files()
|
| 542 |
+
return dist
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
def _assert_roundtrip_message(metadata: str) -> None:
|
| 546 |
+
"""Emulate the way wheel.bdist_wheel parses and regenerates the message,
|
| 547 |
+
then ensures the metadata generated by setuptools is compatible.
|
| 548 |
+
"""
|
| 549 |
+
with io.StringIO(metadata) as buffer:
|
| 550 |
+
msg = Parser(EmailMessage).parse(buffer)
|
| 551 |
+
|
| 552 |
+
serialization_policy = EmailPolicy(
|
| 553 |
+
utf8=True,
|
| 554 |
+
mangle_from_=False,
|
| 555 |
+
max_line_length=0,
|
| 556 |
+
)
|
| 557 |
+
with io.BytesIO() as buffer:
|
| 558 |
+
out = io.TextIOWrapper(buffer, encoding="utf-8")
|
| 559 |
+
Generator(out, policy=serialization_policy).flatten(msg)
|
| 560 |
+
out.flush()
|
| 561 |
+
regenerated = buffer.getvalue()
|
| 562 |
+
|
| 563 |
+
raw_metadata = bytes(metadata, "utf-8")
|
| 564 |
+
# Normalise newlines to avoid test errors on Windows:
|
| 565 |
+
raw_metadata = b"\n".join(raw_metadata.splitlines())
|
| 566 |
+
regenerated = b"\n".join(regenerated.splitlines())
|
| 567 |
+
assert regenerated == raw_metadata
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
def _normalize_metadata(msg: Message) -> str:
|
| 571 |
+
"""Allow equivalent metadata to be compared directly"""
|
| 572 |
+
# The main challenge regards the requirements and extras.
|
| 573 |
+
# Both setuptools and wheel already apply some level of normalization
|
| 574 |
+
# but they differ regarding which character is chosen, according to the
|
| 575 |
+
# following spec it should be "-":
|
| 576 |
+
# https://packaging.python.org/en/latest/specifications/name-normalization/
|
| 577 |
+
|
| 578 |
+
# Related issues:
|
| 579 |
+
# https://github.com/pypa/packaging/issues/845
|
| 580 |
+
# https://github.com/pypa/packaging/issues/644#issuecomment-2429813968
|
| 581 |
+
|
| 582 |
+
extras = {x.replace("_", "-"): x for x in msg.get_all("Provides-Extra", [])}
|
| 583 |
+
reqs = [
|
| 584 |
+
_normalize_req(req, extras)
|
| 585 |
+
for req in _reqs.parse(msg.get_all("Requires-Dist", []))
|
| 586 |
+
]
|
| 587 |
+
del msg["Requires-Dist"]
|
| 588 |
+
del msg["Provides-Extra"]
|
| 589 |
+
|
| 590 |
+
# Ensure consistent ord
|
| 591 |
+
for req in sorted(reqs):
|
| 592 |
+
msg["Requires-Dist"] = req
|
| 593 |
+
for extra in sorted(extras):
|
| 594 |
+
msg["Provides-Extra"] = extra
|
| 595 |
+
|
| 596 |
+
# TODO: Handle lack of PEP 643 implementation in pypa/wheel?
|
| 597 |
+
del msg["Metadata-Version"]
|
| 598 |
+
|
| 599 |
+
return msg.as_string()
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
def _normalize_req(req: Requirement, extras: dict[str, str]) -> str:
|
| 603 |
+
"""Allow equivalent requirement objects to be compared directly"""
|
| 604 |
+
as_str = str(req).replace(req.name, req.name.replace("_", "-"))
|
| 605 |
+
for norm, orig in extras.items():
|
| 606 |
+
as_str = as_str.replace(orig, norm)
|
| 607 |
+
return as_str
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
def _get_pkginfo(dist: Distribution):
|
| 611 |
+
with io.StringIO() as fp:
|
| 612 |
+
dist.metadata.write_pkg_file(fp)
|
| 613 |
+
return fp.getvalue()
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
def _get_metadata(dist: Distribution | None = None):
|
| 617 |
+
return message_from_string(_get_pkginfo(dist or _makedist()))
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def _valid_metadata(text: str) -> bool:
|
| 621 |
+
metadata = Metadata.from_email(text, validate=True) # can raise exceptions
|
| 622 |
+
return metadata is not None
|
venv/lib/python3.11/site-packages/setuptools/tests/test_depends.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
|
| 3 |
+
from setuptools import depends
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class TestGetModuleConstant:
|
| 7 |
+
def test_basic(self):
|
| 8 |
+
"""
|
| 9 |
+
Invoke get_module_constant on a module in
|
| 10 |
+
the test package.
|
| 11 |
+
"""
|
| 12 |
+
mod_name = 'setuptools.tests.mod_with_constant'
|
| 13 |
+
val = depends.get_module_constant(mod_name, 'value')
|
| 14 |
+
assert val == 'three, sir!'
|
| 15 |
+
assert 'setuptools.tests.mod_with_constant' not in sys.modules
|
venv/lib/python3.11/site-packages/setuptools/tests/test_develop.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""develop tests"""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import pathlib
|
| 5 |
+
import platform
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from setuptools._path import paths_on_pythonpath
|
| 12 |
+
from setuptools.command.develop import develop
|
| 13 |
+
from setuptools.dist import Distribution
|
| 14 |
+
|
| 15 |
+
from . import contexts, namespaces
|
| 16 |
+
|
| 17 |
+
SETUP_PY = """\
|
| 18 |
+
from setuptools import setup
|
| 19 |
+
|
| 20 |
+
setup(name='foo',
|
| 21 |
+
packages=['foo'],
|
| 22 |
+
)
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
INIT_PY = """print "foo"
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@pytest.fixture
|
| 30 |
+
def temp_user(monkeypatch):
|
| 31 |
+
with contexts.tempdir() as user_base:
|
| 32 |
+
with contexts.tempdir() as user_site:
|
| 33 |
+
monkeypatch.setattr('site.USER_BASE', user_base)
|
| 34 |
+
monkeypatch.setattr('site.USER_SITE', user_site)
|
| 35 |
+
yield
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@pytest.fixture
|
| 39 |
+
def test_env(tmpdir, temp_user):
|
| 40 |
+
target = tmpdir
|
| 41 |
+
foo = target.mkdir('foo')
|
| 42 |
+
setup = target / 'setup.py'
|
| 43 |
+
if setup.isfile():
|
| 44 |
+
raise ValueError(dir(target))
|
| 45 |
+
with setup.open('w') as f:
|
| 46 |
+
f.write(SETUP_PY)
|
| 47 |
+
init = foo / '__init__.py'
|
| 48 |
+
with init.open('w') as f:
|
| 49 |
+
f.write(INIT_PY)
|
| 50 |
+
with target.as_cwd():
|
| 51 |
+
yield target
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class TestDevelop:
|
| 55 |
+
in_virtualenv = hasattr(sys, 'real_prefix')
|
| 56 |
+
in_venv = hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix
|
| 57 |
+
|
| 58 |
+
def test_console_scripts(self, tmpdir):
|
| 59 |
+
"""
|
| 60 |
+
Test that console scripts are installed and that they reference
|
| 61 |
+
only the project by name and not the current version.
|
| 62 |
+
"""
|
| 63 |
+
pytest.skip(
|
| 64 |
+
"TODO: needs a fixture to cause 'develop' "
|
| 65 |
+
"to be invoked without mutating environment."
|
| 66 |
+
)
|
| 67 |
+
settings = dict(
|
| 68 |
+
name='foo',
|
| 69 |
+
packages=['foo'],
|
| 70 |
+
version='0.0',
|
| 71 |
+
entry_points={
|
| 72 |
+
'console_scripts': [
|
| 73 |
+
'foocmd = foo:foo',
|
| 74 |
+
],
|
| 75 |
+
},
|
| 76 |
+
)
|
| 77 |
+
dist = Distribution(settings)
|
| 78 |
+
dist.script_name = 'setup.py'
|
| 79 |
+
cmd = develop(dist)
|
| 80 |
+
cmd.ensure_finalized()
|
| 81 |
+
cmd.install_dir = tmpdir
|
| 82 |
+
cmd.run()
|
| 83 |
+
# assert '0.0' not in foocmd_text
|
| 84 |
+
|
| 85 |
+
@pytest.mark.xfail(reason="legacy behavior retained for compatibility #4167")
|
| 86 |
+
def test_egg_link_filename(self):
|
| 87 |
+
settings = dict(
|
| 88 |
+
name='Foo $$$ Bar_baz-bing',
|
| 89 |
+
)
|
| 90 |
+
dist = Distribution(settings)
|
| 91 |
+
cmd = develop(dist)
|
| 92 |
+
cmd.ensure_finalized()
|
| 93 |
+
link = pathlib.Path(cmd.egg_link)
|
| 94 |
+
assert link.suffix == '.egg-link'
|
| 95 |
+
assert link.stem == 'Foo_Bar_baz_bing'
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class TestResolver:
|
| 99 |
+
"""
|
| 100 |
+
TODO: These tests were written with a minimal understanding
|
| 101 |
+
of what _resolve_setup_path is intending to do. Come up with
|
| 102 |
+
more meaningful cases that look like real-world scenarios.
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
def test_resolve_setup_path_cwd(self):
|
| 106 |
+
assert develop._resolve_setup_path('.', '.', '.') == '.'
|
| 107 |
+
|
| 108 |
+
def test_resolve_setup_path_one_dir(self):
|
| 109 |
+
assert develop._resolve_setup_path('pkgs', '.', 'pkgs') == '../'
|
| 110 |
+
|
| 111 |
+
def test_resolve_setup_path_one_dir_trailing_slash(self):
|
| 112 |
+
assert develop._resolve_setup_path('pkgs/', '.', 'pkgs') == '../'
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class TestNamespaces:
|
| 116 |
+
@staticmethod
|
| 117 |
+
def install_develop(src_dir, target):
|
| 118 |
+
develop_cmd = [
|
| 119 |
+
sys.executable,
|
| 120 |
+
'setup.py',
|
| 121 |
+
'develop',
|
| 122 |
+
'--install-dir',
|
| 123 |
+
str(target),
|
| 124 |
+
]
|
| 125 |
+
with src_dir.as_cwd():
|
| 126 |
+
with paths_on_pythonpath([str(target)]):
|
| 127 |
+
subprocess.check_call(develop_cmd)
|
| 128 |
+
|
| 129 |
+
@pytest.mark.skipif(
|
| 130 |
+
bool(os.environ.get("APPVEYOR")),
|
| 131 |
+
reason="https://github.com/pypa/setuptools/issues/851",
|
| 132 |
+
)
|
| 133 |
+
@pytest.mark.skipif(
|
| 134 |
+
platform.python_implementation() == 'PyPy',
|
| 135 |
+
reason="https://github.com/pypa/setuptools/issues/1202",
|
| 136 |
+
)
|
| 137 |
+
def test_namespace_package_importable(self, tmpdir):
|
| 138 |
+
"""
|
| 139 |
+
Installing two packages sharing the same namespace, one installed
|
| 140 |
+
naturally using pip or `--single-version-externally-managed`
|
| 141 |
+
and the other installed using `develop` should leave the namespace
|
| 142 |
+
in tact and both packages reachable by import.
|
| 143 |
+
"""
|
| 144 |
+
pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
|
| 145 |
+
pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB')
|
| 146 |
+
target = tmpdir / 'packages'
|
| 147 |
+
# use pip to install to the target directory
|
| 148 |
+
install_cmd = [
|
| 149 |
+
sys.executable,
|
| 150 |
+
'-m',
|
| 151 |
+
'pip',
|
| 152 |
+
'install',
|
| 153 |
+
str(pkg_A),
|
| 154 |
+
'-t',
|
| 155 |
+
str(target),
|
| 156 |
+
]
|
| 157 |
+
subprocess.check_call(install_cmd)
|
| 158 |
+
self.install_develop(pkg_B, target)
|
| 159 |
+
namespaces.make_site_dir(target)
|
| 160 |
+
try_import = [
|
| 161 |
+
sys.executable,
|
| 162 |
+
'-c',
|
| 163 |
+
'import myns.pkgA; import myns.pkgB',
|
| 164 |
+
]
|
| 165 |
+
with paths_on_pythonpath([str(target)]):
|
| 166 |
+
subprocess.check_call(try_import)
|
| 167 |
+
|
| 168 |
+
# additionally ensure that pkg_resources import works
|
| 169 |
+
pkg_resources_imp = [
|
| 170 |
+
sys.executable,
|
| 171 |
+
'-c',
|
| 172 |
+
'import pkg_resources',
|
| 173 |
+
]
|
| 174 |
+
with paths_on_pythonpath([str(target)]):
|
| 175 |
+
subprocess.check_call(pkg_resources_imp)
|
venv/lib/python3.11/site-packages/setuptools/tests/test_dist.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import urllib.parse
|
| 4 |
+
import urllib.request
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from setuptools import Distribution
|
| 9 |
+
from setuptools.dist import check_package_data, check_specifier
|
| 10 |
+
|
| 11 |
+
from .test_easy_install import make_trivial_sdist
|
| 12 |
+
from .test_find_packages import ensure_files
|
| 13 |
+
from .textwrap import DALS
|
| 14 |
+
|
| 15 |
+
from distutils.errors import DistutilsSetupError
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_dist_fetch_build_egg(tmpdir):
|
| 19 |
+
"""
|
| 20 |
+
Check multiple calls to `Distribution.fetch_build_egg` work as expected.
|
| 21 |
+
"""
|
| 22 |
+
index = tmpdir.mkdir('index')
|
| 23 |
+
index_url = urllib.parse.urljoin('file://', urllib.request.pathname2url(str(index)))
|
| 24 |
+
|
| 25 |
+
def sdist_with_index(distname, version):
|
| 26 |
+
dist_dir = index.mkdir(distname)
|
| 27 |
+
dist_sdist = f'{distname}-{version}.tar.gz'
|
| 28 |
+
make_trivial_sdist(str(dist_dir.join(dist_sdist)), distname, version)
|
| 29 |
+
with dist_dir.join('index.html').open('w') as fp:
|
| 30 |
+
fp.write(
|
| 31 |
+
DALS(
|
| 32 |
+
"""
|
| 33 |
+
<!DOCTYPE html><html><body>
|
| 34 |
+
<a href="{dist_sdist}" rel="internal">{dist_sdist}</a><br/>
|
| 35 |
+
</body></html>
|
| 36 |
+
"""
|
| 37 |
+
).format(dist_sdist=dist_sdist)
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
sdist_with_index('barbazquux', '3.2.0')
|
| 41 |
+
sdist_with_index('barbazquux-runner', '2.11.1')
|
| 42 |
+
with tmpdir.join('setup.cfg').open('w') as fp:
|
| 43 |
+
fp.write(
|
| 44 |
+
DALS(
|
| 45 |
+
"""
|
| 46 |
+
[easy_install]
|
| 47 |
+
index_url = {index_url}
|
| 48 |
+
"""
|
| 49 |
+
).format(index_url=index_url)
|
| 50 |
+
)
|
| 51 |
+
reqs = """
|
| 52 |
+
barbazquux-runner
|
| 53 |
+
barbazquux
|
| 54 |
+
""".split()
|
| 55 |
+
with tmpdir.as_cwd():
|
| 56 |
+
dist = Distribution()
|
| 57 |
+
dist.parse_config_files()
|
| 58 |
+
resolved_dists = [dist.fetch_build_egg(r) for r in reqs]
|
| 59 |
+
assert [dist.key for dist in resolved_dists if dist] == reqs
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
EXAMPLE_BASE_INFO = dict(
|
| 63 |
+
name="package",
|
| 64 |
+
version="0.0.1",
|
| 65 |
+
author="Foo Bar",
|
| 66 |
+
author_email="foo@bar.net",
|
| 67 |
+
long_description="Long\ndescription",
|
| 68 |
+
description="Short description",
|
| 69 |
+
keywords=["one", "two"],
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def test_provides_extras_deterministic_order():
|
| 74 |
+
attrs = dict(extras_require=dict(a=['foo'], b=['bar']))
|
| 75 |
+
dist = Distribution(attrs)
|
| 76 |
+
assert list(dist.metadata.provides_extras) == ['a', 'b']
|
| 77 |
+
attrs['extras_require'] = dict(reversed(attrs['extras_require'].items()))
|
| 78 |
+
dist = Distribution(attrs)
|
| 79 |
+
assert list(dist.metadata.provides_extras) == ['b', 'a']
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
CHECK_PACKAGE_DATA_TESTS = (
|
| 83 |
+
# Valid.
|
| 84 |
+
(
|
| 85 |
+
{
|
| 86 |
+
'': ['*.txt', '*.rst'],
|
| 87 |
+
'hello': ['*.msg'],
|
| 88 |
+
},
|
| 89 |
+
None,
|
| 90 |
+
),
|
| 91 |
+
# Not a dictionary.
|
| 92 |
+
(
|
| 93 |
+
(
|
| 94 |
+
('', ['*.txt', '*.rst']),
|
| 95 |
+
('hello', ['*.msg']),
|
| 96 |
+
),
|
| 97 |
+
(
|
| 98 |
+
"'package_data' must be a dictionary mapping package"
|
| 99 |
+
" names to lists of string wildcard patterns"
|
| 100 |
+
),
|
| 101 |
+
),
|
| 102 |
+
# Invalid key type.
|
| 103 |
+
(
|
| 104 |
+
{
|
| 105 |
+
400: ['*.txt', '*.rst'],
|
| 106 |
+
},
|
| 107 |
+
("keys of 'package_data' dict must be strings (got 400)"),
|
| 108 |
+
),
|
| 109 |
+
# Invalid value type.
|
| 110 |
+
(
|
| 111 |
+
{
|
| 112 |
+
'hello': '*.msg',
|
| 113 |
+
},
|
| 114 |
+
(
|
| 115 |
+
"\"values of 'package_data' dict\" must be of type <tuple[str, ...] | list[str]>"
|
| 116 |
+
" (got '*.msg')"
|
| 117 |
+
),
|
| 118 |
+
),
|
| 119 |
+
# Invalid value type (generators are single use)
|
| 120 |
+
(
|
| 121 |
+
{
|
| 122 |
+
'hello': (x for x in "generator"),
|
| 123 |
+
},
|
| 124 |
+
(
|
| 125 |
+
"\"values of 'package_data' dict\" must be of type <tuple[str, ...] | list[str]>"
|
| 126 |
+
" (got <generator object"
|
| 127 |
+
),
|
| 128 |
+
),
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@pytest.mark.parametrize(('package_data', 'expected_message'), CHECK_PACKAGE_DATA_TESTS)
|
| 133 |
+
def test_check_package_data(package_data, expected_message):
|
| 134 |
+
if expected_message is None:
|
| 135 |
+
assert check_package_data(None, 'package_data', package_data) is None
|
| 136 |
+
else:
|
| 137 |
+
with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)):
|
| 138 |
+
check_package_data(None, 'package_data', package_data)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def test_check_specifier():
|
| 142 |
+
# valid specifier value
|
| 143 |
+
attrs = {'name': 'foo', 'python_requires': '>=3.0, !=3.1'}
|
| 144 |
+
dist = Distribution(attrs)
|
| 145 |
+
check_specifier(dist, attrs, attrs['python_requires'])
|
| 146 |
+
|
| 147 |
+
attrs = {'name': 'foo', 'python_requires': ['>=3.0', '!=3.1']}
|
| 148 |
+
dist = Distribution(attrs)
|
| 149 |
+
check_specifier(dist, attrs, attrs['python_requires'])
|
| 150 |
+
|
| 151 |
+
# invalid specifier value
|
| 152 |
+
attrs = {'name': 'foo', 'python_requires': '>=invalid-version'}
|
| 153 |
+
with pytest.raises(DistutilsSetupError):
|
| 154 |
+
dist = Distribution(attrs)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def test_metadata_name():
|
| 158 |
+
with pytest.raises(DistutilsSetupError, match='missing.*name'):
|
| 159 |
+
Distribution()._validate_metadata()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
@pytest.mark.parametrize(
|
| 163 |
+
('dist_name', 'py_module'),
|
| 164 |
+
[
|
| 165 |
+
("my.pkg", "my_pkg"),
|
| 166 |
+
("my-pkg", "my_pkg"),
|
| 167 |
+
("my_pkg", "my_pkg"),
|
| 168 |
+
("pkg", "pkg"),
|
| 169 |
+
],
|
| 170 |
+
)
|
| 171 |
+
def test_dist_default_py_modules(tmp_path, dist_name, py_module):
|
| 172 |
+
(tmp_path / f"{py_module}.py").touch()
|
| 173 |
+
|
| 174 |
+
(tmp_path / "setup.py").touch()
|
| 175 |
+
(tmp_path / "noxfile.py").touch()
|
| 176 |
+
# ^-- make sure common tool files are ignored
|
| 177 |
+
|
| 178 |
+
attrs = {**EXAMPLE_BASE_INFO, "name": dist_name, "src_root": str(tmp_path)}
|
| 179 |
+
# Find `py_modules` corresponding to dist_name if not given
|
| 180 |
+
dist = Distribution(attrs)
|
| 181 |
+
dist.set_defaults()
|
| 182 |
+
assert dist.py_modules == [py_module]
|
| 183 |
+
# When `py_modules` is given, don't do anything
|
| 184 |
+
dist = Distribution({**attrs, "py_modules": ["explicity_py_module"]})
|
| 185 |
+
dist.set_defaults()
|
| 186 |
+
assert dist.py_modules == ["explicity_py_module"]
|
| 187 |
+
# When `packages` is given, don't do anything
|
| 188 |
+
dist = Distribution({**attrs, "packages": ["explicity_package"]})
|
| 189 |
+
dist.set_defaults()
|
| 190 |
+
assert not dist.py_modules
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
@pytest.mark.parametrize(
|
| 194 |
+
('dist_name', 'package_dir', 'package_files', 'packages'),
|
| 195 |
+
[
|
| 196 |
+
("my.pkg", None, ["my_pkg/__init__.py", "my_pkg/mod.py"], ["my_pkg"]),
|
| 197 |
+
("my-pkg", None, ["my_pkg/__init__.py", "my_pkg/mod.py"], ["my_pkg"]),
|
| 198 |
+
("my_pkg", None, ["my_pkg/__init__.py", "my_pkg/mod.py"], ["my_pkg"]),
|
| 199 |
+
("my.pkg", None, ["my/pkg/__init__.py"], ["my", "my.pkg"]),
|
| 200 |
+
(
|
| 201 |
+
"my_pkg",
|
| 202 |
+
None,
|
| 203 |
+
["src/my_pkg/__init__.py", "src/my_pkg2/__init__.py"],
|
| 204 |
+
["my_pkg", "my_pkg2"],
|
| 205 |
+
),
|
| 206 |
+
(
|
| 207 |
+
"my_pkg",
|
| 208 |
+
{"pkg": "lib", "pkg2": "lib2"},
|
| 209 |
+
["lib/__init__.py", "lib/nested/__init__.pyt", "lib2/__init__.py"],
|
| 210 |
+
["pkg", "pkg.nested", "pkg2"],
|
| 211 |
+
),
|
| 212 |
+
],
|
| 213 |
+
)
|
| 214 |
+
def test_dist_default_packages(
|
| 215 |
+
tmp_path, dist_name, package_dir, package_files, packages
|
| 216 |
+
):
|
| 217 |
+
ensure_files(tmp_path, package_files)
|
| 218 |
+
|
| 219 |
+
(tmp_path / "setup.py").touch()
|
| 220 |
+
(tmp_path / "noxfile.py").touch()
|
| 221 |
+
# ^-- should not be included by default
|
| 222 |
+
|
| 223 |
+
attrs = {
|
| 224 |
+
**EXAMPLE_BASE_INFO,
|
| 225 |
+
"name": dist_name,
|
| 226 |
+
"src_root": str(tmp_path),
|
| 227 |
+
"package_dir": package_dir,
|
| 228 |
+
}
|
| 229 |
+
# Find `packages` either corresponding to dist_name or inside src
|
| 230 |
+
dist = Distribution(attrs)
|
| 231 |
+
dist.set_defaults()
|
| 232 |
+
assert not dist.py_modules
|
| 233 |
+
assert not dist.py_modules
|
| 234 |
+
assert set(dist.packages) == set(packages)
|
| 235 |
+
# When `py_modules` is given, don't do anything
|
| 236 |
+
dist = Distribution({**attrs, "py_modules": ["explicit_py_module"]})
|
| 237 |
+
dist.set_defaults()
|
| 238 |
+
assert not dist.packages
|
| 239 |
+
assert set(dist.py_modules) == {"explicit_py_module"}
|
| 240 |
+
# When `packages` is given, don't do anything
|
| 241 |
+
dist = Distribution({**attrs, "packages": ["explicit_package"]})
|
| 242 |
+
dist.set_defaults()
|
| 243 |
+
assert not dist.py_modules
|
| 244 |
+
assert set(dist.packages) == {"explicit_package"}
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
@pytest.mark.parametrize(
|
| 248 |
+
('dist_name', 'package_dir', 'package_files'),
|
| 249 |
+
[
|
| 250 |
+
("my.pkg.nested", None, ["my/pkg/nested/__init__.py"]),
|
| 251 |
+
("my.pkg", None, ["my/pkg/__init__.py", "my/pkg/file.py"]),
|
| 252 |
+
("my_pkg", None, ["my_pkg.py"]),
|
| 253 |
+
("my_pkg", None, ["my_pkg/__init__.py", "my_pkg/nested/__init__.py"]),
|
| 254 |
+
("my_pkg", None, ["src/my_pkg/__init__.py", "src/my_pkg/nested/__init__.py"]),
|
| 255 |
+
(
|
| 256 |
+
"my_pkg",
|
| 257 |
+
{"my_pkg": "lib", "my_pkg.lib2": "lib2"},
|
| 258 |
+
["lib/__init__.py", "lib/nested/__init__.pyt", "lib2/__init__.py"],
|
| 259 |
+
),
|
| 260 |
+
# Should not try to guess a name from multiple py_modules/packages
|
| 261 |
+
("UNKNOWN", None, ["src/mod1.py", "src/mod2.py"]),
|
| 262 |
+
("UNKNOWN", None, ["src/pkg1/__ini__.py", "src/pkg2/__init__.py"]),
|
| 263 |
+
],
|
| 264 |
+
)
|
| 265 |
+
def test_dist_default_name(tmp_path, dist_name, package_dir, package_files):
|
| 266 |
+
"""Make sure dist.name is discovered from packages/py_modules"""
|
| 267 |
+
ensure_files(tmp_path, package_files)
|
| 268 |
+
attrs = {
|
| 269 |
+
**EXAMPLE_BASE_INFO,
|
| 270 |
+
"src_root": "/".join(os.path.split(tmp_path)), # POSIX-style
|
| 271 |
+
"package_dir": package_dir,
|
| 272 |
+
}
|
| 273 |
+
del attrs["name"]
|
| 274 |
+
|
| 275 |
+
dist = Distribution(attrs)
|
| 276 |
+
dist.set_defaults()
|
| 277 |
+
assert dist.py_modules or dist.packages
|
| 278 |
+
assert dist.get_name() == dist_name
|
venv/lib/python3.11/site-packages/setuptools/tests/test_dist_info.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test .dist-info style distributions."""
|
| 2 |
+
|
| 3 |
+
import pathlib
|
| 4 |
+
import re
|
| 5 |
+
import shutil
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
from functools import partial
|
| 9 |
+
|
| 10 |
+
import pytest
|
| 11 |
+
|
| 12 |
+
import pkg_resources
|
| 13 |
+
from setuptools.archive_util import unpack_archive
|
| 14 |
+
|
| 15 |
+
from .textwrap import DALS
|
| 16 |
+
|
| 17 |
+
read = partial(pathlib.Path.read_text, encoding="utf-8")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TestDistInfo:
|
| 21 |
+
metadata_base = DALS(
|
| 22 |
+
"""
|
| 23 |
+
Metadata-Version: 1.2
|
| 24 |
+
Requires-Dist: splort (==4)
|
| 25 |
+
Provides-Extra: baz
|
| 26 |
+
Requires-Dist: quux (>=1.1); extra == 'baz'
|
| 27 |
+
"""
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
@classmethod
|
| 31 |
+
def build_metadata(cls, **kwargs):
|
| 32 |
+
lines = ('{key}: {value}\n'.format(**locals()) for key, value in kwargs.items())
|
| 33 |
+
return cls.metadata_base + ''.join(lines)
|
| 34 |
+
|
| 35 |
+
@pytest.fixture
|
| 36 |
+
def metadata(self, tmpdir):
|
| 37 |
+
dist_info_name = 'VersionedDistribution-2.718.dist-info'
|
| 38 |
+
versioned = tmpdir / dist_info_name
|
| 39 |
+
versioned.mkdir()
|
| 40 |
+
filename = versioned / 'METADATA'
|
| 41 |
+
content = self.build_metadata(
|
| 42 |
+
Name='VersionedDistribution',
|
| 43 |
+
)
|
| 44 |
+
filename.write_text(content, encoding='utf-8')
|
| 45 |
+
|
| 46 |
+
dist_info_name = 'UnversionedDistribution.dist-info'
|
| 47 |
+
unversioned = tmpdir / dist_info_name
|
| 48 |
+
unversioned.mkdir()
|
| 49 |
+
filename = unversioned / 'METADATA'
|
| 50 |
+
content = self.build_metadata(
|
| 51 |
+
Name='UnversionedDistribution',
|
| 52 |
+
Version='0.3',
|
| 53 |
+
)
|
| 54 |
+
filename.write_text(content, encoding='utf-8')
|
| 55 |
+
|
| 56 |
+
return str(tmpdir)
|
| 57 |
+
|
| 58 |
+
def test_distinfo(self, metadata):
|
| 59 |
+
dists = dict(
|
| 60 |
+
(d.project_name, d) for d in pkg_resources.find_distributions(metadata)
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
assert len(dists) == 2, dists
|
| 64 |
+
|
| 65 |
+
unversioned = dists['UnversionedDistribution']
|
| 66 |
+
versioned = dists['VersionedDistribution']
|
| 67 |
+
|
| 68 |
+
assert versioned.version == '2.718' # from filename
|
| 69 |
+
assert unversioned.version == '0.3' # from METADATA
|
| 70 |
+
|
| 71 |
+
def test_conditional_dependencies(self, metadata):
|
| 72 |
+
specs = 'splort==4', 'quux>=1.1'
|
| 73 |
+
requires = list(map(pkg_resources.Requirement.parse, specs))
|
| 74 |
+
|
| 75 |
+
for d in pkg_resources.find_distributions(metadata):
|
| 76 |
+
assert d.requires() == requires[:1]
|
| 77 |
+
assert d.requires(extras=('baz',)) == [
|
| 78 |
+
requires[0],
|
| 79 |
+
pkg_resources.Requirement.parse('quux>=1.1;extra=="baz"'),
|
| 80 |
+
]
|
| 81 |
+
assert d.extras == ['baz']
|
| 82 |
+
|
| 83 |
+
def test_invalid_version(self, tmp_path):
|
| 84 |
+
"""
|
| 85 |
+
Supplying an invalid version crashes dist_info.
|
| 86 |
+
"""
|
| 87 |
+
config = "[metadata]\nname=proj\nversion=42\n[egg_info]\ntag_build=invalid!!!\n"
|
| 88 |
+
(tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
|
| 89 |
+
msg = re.compile("invalid version", re.M | re.I)
|
| 90 |
+
proc = run_command_inner("dist_info", cwd=tmp_path, check=False)
|
| 91 |
+
assert proc.returncode
|
| 92 |
+
assert msg.search(proc.stdout)
|
| 93 |
+
assert not list(tmp_path.glob("*.dist-info"))
|
| 94 |
+
|
| 95 |
+
def test_tag_arguments(self, tmp_path):
|
| 96 |
+
config = """
|
| 97 |
+
[metadata]
|
| 98 |
+
name=proj
|
| 99 |
+
version=42
|
| 100 |
+
[egg_info]
|
| 101 |
+
tag_date=1
|
| 102 |
+
tag_build=.post
|
| 103 |
+
"""
|
| 104 |
+
(tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
|
| 105 |
+
|
| 106 |
+
print(run_command("dist_info", "--no-date", cwd=tmp_path))
|
| 107 |
+
dist_info = next(tmp_path.glob("*.dist-info"))
|
| 108 |
+
assert dist_info.name.startswith("proj-42")
|
| 109 |
+
shutil.rmtree(dist_info)
|
| 110 |
+
|
| 111 |
+
print(run_command("dist_info", "--tag-build", ".a", cwd=tmp_path))
|
| 112 |
+
dist_info = next(tmp_path.glob("*.dist-info"))
|
| 113 |
+
assert dist_info.name.startswith("proj-42a")
|
| 114 |
+
|
| 115 |
+
@pytest.mark.parametrize("keep_egg_info", (False, True))
|
| 116 |
+
def test_output_dir(self, tmp_path, keep_egg_info):
|
| 117 |
+
config = "[metadata]\nname=proj\nversion=42\n"
|
| 118 |
+
(tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
|
| 119 |
+
out = tmp_path / "__out"
|
| 120 |
+
out.mkdir()
|
| 121 |
+
opts = ["--keep-egg-info"] if keep_egg_info else []
|
| 122 |
+
run_command("dist_info", "--output-dir", out, *opts, cwd=tmp_path)
|
| 123 |
+
assert len(list(out.glob("*.dist-info"))) == 1
|
| 124 |
+
assert len(list(tmp_path.glob("*.dist-info"))) == 0
|
| 125 |
+
expected_egg_info = int(keep_egg_info)
|
| 126 |
+
assert len(list(out.glob("*.egg-info"))) == expected_egg_info
|
| 127 |
+
assert len(list(tmp_path.glob("*.egg-info"))) == 0
|
| 128 |
+
assert len(list(out.glob("*.__bkp__"))) == 0
|
| 129 |
+
assert len(list(tmp_path.glob("*.__bkp__"))) == 0
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class TestWheelCompatibility:
|
| 133 |
+
"""Make sure the .dist-info directory produced with the ``dist_info`` command
|
| 134 |
+
is the same as the one produced by ``bdist_wheel``.
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
SETUPCFG = DALS(
|
| 138 |
+
"""
|
| 139 |
+
[metadata]
|
| 140 |
+
name = {name}
|
| 141 |
+
version = {version}
|
| 142 |
+
|
| 143 |
+
[options]
|
| 144 |
+
install_requires =
|
| 145 |
+
foo>=12; sys_platform != "linux"
|
| 146 |
+
|
| 147 |
+
[options.extras_require]
|
| 148 |
+
test = pytest
|
| 149 |
+
|
| 150 |
+
[options.entry_points]
|
| 151 |
+
console_scripts =
|
| 152 |
+
executable-name = my_package.module:function
|
| 153 |
+
discover =
|
| 154 |
+
myproj = my_package.other_module:function
|
| 155 |
+
"""
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
EGG_INFO_OPTS = [
|
| 159 |
+
# Related: #3088 #2872
|
| 160 |
+
("", ""),
|
| 161 |
+
(".post", "[egg_info]\ntag_build = post\n"),
|
| 162 |
+
(".post", "[egg_info]\ntag_build = .post\n"),
|
| 163 |
+
(".post", "[egg_info]\ntag_build = post\ntag_date = 1\n"),
|
| 164 |
+
(".dev", "[egg_info]\ntag_build = .dev\n"),
|
| 165 |
+
(".dev", "[egg_info]\ntag_build = .dev\ntag_date = 1\n"),
|
| 166 |
+
("a1", "[egg_info]\ntag_build = .a1\n"),
|
| 167 |
+
("+local", "[egg_info]\ntag_build = +local\n"),
|
| 168 |
+
]
|
| 169 |
+
|
| 170 |
+
@pytest.mark.parametrize("name", "my-proj my_proj my.proj My.Proj".split())
|
| 171 |
+
@pytest.mark.parametrize("version", ["0.42.13"])
|
| 172 |
+
@pytest.mark.parametrize(("suffix", "cfg"), EGG_INFO_OPTS)
|
| 173 |
+
def test_dist_info_is_the_same_as_in_wheel(
|
| 174 |
+
self, name, version, tmp_path, suffix, cfg
|
| 175 |
+
):
|
| 176 |
+
config = self.SETUPCFG.format(name=name, version=version) + cfg
|
| 177 |
+
|
| 178 |
+
for i in "dir_wheel", "dir_dist":
|
| 179 |
+
(tmp_path / i).mkdir()
|
| 180 |
+
(tmp_path / i / "setup.cfg").write_text(config, encoding="utf-8")
|
| 181 |
+
|
| 182 |
+
run_command("bdist_wheel", cwd=tmp_path / "dir_wheel")
|
| 183 |
+
wheel = next(tmp_path.glob("dir_wheel/dist/*.whl"))
|
| 184 |
+
unpack_archive(wheel, tmp_path / "unpack")
|
| 185 |
+
wheel_dist_info = next(tmp_path.glob("unpack/*.dist-info"))
|
| 186 |
+
|
| 187 |
+
run_command("dist_info", cwd=tmp_path / "dir_dist")
|
| 188 |
+
dist_info = next(tmp_path.glob("dir_dist/*.dist-info"))
|
| 189 |
+
|
| 190 |
+
assert dist_info.name == wheel_dist_info.name
|
| 191 |
+
assert dist_info.name.startswith(f"my_proj-{version}{suffix}")
|
| 192 |
+
for file in "METADATA", "entry_points.txt":
|
| 193 |
+
assert read(dist_info / file) == read(wheel_dist_info / file)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def run_command_inner(*cmd, **kwargs):
|
| 197 |
+
opts = {
|
| 198 |
+
"stderr": subprocess.STDOUT,
|
| 199 |
+
"stdout": subprocess.PIPE,
|
| 200 |
+
"text": True,
|
| 201 |
+
"encoding": "utf-8",
|
| 202 |
+
"check": True,
|
| 203 |
+
**kwargs,
|
| 204 |
+
}
|
| 205 |
+
cmd = [sys.executable, "-c", "__import__('setuptools').setup()", *map(str, cmd)]
|
| 206 |
+
return subprocess.run(cmd, **opts)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def run_command(*args, **kwargs):
|
| 210 |
+
return run_command_inner(*args, **kwargs).stdout
|
venv/lib/python3.11/site-packages/setuptools/tests/test_distutils_adoption.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import platform
|
| 3 |
+
import sys
|
| 4 |
+
import textwrap
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
IS_PYPY = '__pypy__' in sys.builtin_module_names
|
| 9 |
+
|
| 10 |
+
_TEXT_KWARGS = {"text": True, "encoding": "utf-8"} # For subprocess.run
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def win_sr(env):
|
| 14 |
+
"""
|
| 15 |
+
On Windows, SYSTEMROOT must be present to avoid
|
| 16 |
+
|
| 17 |
+
> Fatal Python error: _Py_HashRandomization_Init: failed to
|
| 18 |
+
> get random numbers to initialize Python
|
| 19 |
+
"""
|
| 20 |
+
if env and platform.system() == 'Windows':
|
| 21 |
+
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
|
| 22 |
+
return env
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def find_distutils(venv, imports='distutils', env=None, **kwargs):
|
| 26 |
+
py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals())
|
| 27 |
+
cmd = ['python', '-c', py_cmd]
|
| 28 |
+
return venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS, **kwargs)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def count_meta_path(venv, env=None):
|
| 32 |
+
py_cmd = textwrap.dedent(
|
| 33 |
+
"""
|
| 34 |
+
import sys
|
| 35 |
+
is_distutils = lambda finder: finder.__class__.__name__ == "DistutilsMetaFinder"
|
| 36 |
+
print(len(list(filter(is_distutils, sys.meta_path))))
|
| 37 |
+
"""
|
| 38 |
+
)
|
| 39 |
+
cmd = ['python', '-c', py_cmd]
|
| 40 |
+
return int(venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS))
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
skip_without_stdlib_distutils = pytest.mark.skipif(
|
| 44 |
+
sys.version_info >= (3, 12),
|
| 45 |
+
reason='stdlib distutils is removed from Python 3.12+',
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@skip_without_stdlib_distutils
|
| 50 |
+
def test_distutils_stdlib(venv):
|
| 51 |
+
"""
|
| 52 |
+
Ensure stdlib distutils is used when appropriate.
|
| 53 |
+
"""
|
| 54 |
+
env = dict(SETUPTOOLS_USE_DISTUTILS='stdlib')
|
| 55 |
+
assert venv.name not in find_distutils(venv, env=env).split(os.sep)
|
| 56 |
+
assert count_meta_path(venv, env=env) == 0
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def test_distutils_local_with_setuptools(venv):
|
| 60 |
+
"""
|
| 61 |
+
Ensure local distutils is used when appropriate.
|
| 62 |
+
"""
|
| 63 |
+
env = dict(SETUPTOOLS_USE_DISTUTILS='local')
|
| 64 |
+
loc = find_distutils(venv, imports='setuptools, distutils', env=env)
|
| 65 |
+
assert venv.name in loc.split(os.sep)
|
| 66 |
+
assert count_meta_path(venv, env=env) <= 1
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
@pytest.mark.xfail('IS_PYPY', reason='pypy imports distutils on startup')
|
| 70 |
+
def test_distutils_local(venv):
|
| 71 |
+
"""
|
| 72 |
+
Even without importing, the setuptools-local copy of distutils is
|
| 73 |
+
preferred.
|
| 74 |
+
"""
|
| 75 |
+
env = dict(SETUPTOOLS_USE_DISTUTILS='local')
|
| 76 |
+
assert venv.name in find_distutils(venv, env=env).split(os.sep)
|
| 77 |
+
assert count_meta_path(venv, env=env) <= 1
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def test_pip_import(venv):
|
| 81 |
+
"""
|
| 82 |
+
Ensure pip can be imported.
|
| 83 |
+
Regression test for #3002.
|
| 84 |
+
"""
|
| 85 |
+
cmd = ['python', '-c', 'import pip']
|
| 86 |
+
venv.run(cmd, **_TEXT_KWARGS)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def test_distutils_has_origin():
|
| 90 |
+
"""
|
| 91 |
+
Distutils module spec should have an origin. #2990.
|
| 92 |
+
"""
|
| 93 |
+
assert __import__('distutils').__spec__.origin
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
ENSURE_IMPORTS_ARE_NOT_DUPLICATED = r"""
|
| 97 |
+
# Depending on the importlib machinery and _distutils_hack, some imports are
|
| 98 |
+
# duplicated resulting in different module objects being loaded, which prevents
|
| 99 |
+
# patches as shown in #3042.
|
| 100 |
+
# This script provides a way of verifying if this duplication is happening.
|
| 101 |
+
|
| 102 |
+
from distutils import cmd
|
| 103 |
+
import distutils.command.sdist as sdist
|
| 104 |
+
|
| 105 |
+
# import last to prevent caching
|
| 106 |
+
from distutils import {imported_module}
|
| 107 |
+
|
| 108 |
+
for mod in (cmd, sdist):
|
| 109 |
+
assert mod.{imported_module} == {imported_module}, (
|
| 110 |
+
f"\n{{mod.dir_util}}\n!=\n{{{imported_module}}}"
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
print("success")
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
@pytest.mark.usefixtures("tmpdir_cwd")
|
| 118 |
+
@pytest.mark.parametrize(
|
| 119 |
+
('distutils_version', 'imported_module'),
|
| 120 |
+
[
|
| 121 |
+
pytest.param("stdlib", "dir_util", marks=skip_without_stdlib_distutils),
|
| 122 |
+
pytest.param("stdlib", "file_util", marks=skip_without_stdlib_distutils),
|
| 123 |
+
pytest.param("stdlib", "archive_util", marks=skip_without_stdlib_distutils),
|
| 124 |
+
("local", "dir_util"),
|
| 125 |
+
("local", "file_util"),
|
| 126 |
+
("local", "archive_util"),
|
| 127 |
+
],
|
| 128 |
+
)
|
| 129 |
+
def test_modules_are_not_duplicated_on_import(distutils_version, imported_module, venv):
|
| 130 |
+
env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
|
| 131 |
+
script = ENSURE_IMPORTS_ARE_NOT_DUPLICATED.format(imported_module=imported_module)
|
| 132 |
+
cmd = ['python', '-c', script]
|
| 133 |
+
output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip()
|
| 134 |
+
assert output == "success"
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED = r"""
|
| 138 |
+
import types
|
| 139 |
+
import distutils.dist as dist
|
| 140 |
+
from distutils import log
|
| 141 |
+
if isinstance(dist.log, types.ModuleType):
|
| 142 |
+
assert dist.log == log, f"\n{dist.log}\n!=\n{log}"
|
| 143 |
+
print("success")
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@pytest.mark.usefixtures("tmpdir_cwd")
|
| 148 |
+
@pytest.mark.parametrize(
|
| 149 |
+
"distutils_version",
|
| 150 |
+
[
|
| 151 |
+
"local",
|
| 152 |
+
pytest.param("stdlib", marks=skip_without_stdlib_distutils),
|
| 153 |
+
],
|
| 154 |
+
)
|
| 155 |
+
def test_log_module_is_not_duplicated_on_import(distutils_version, venv):
|
| 156 |
+
env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
|
| 157 |
+
cmd = ['python', '-c', ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED]
|
| 158 |
+
output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip()
|
| 159 |
+
assert output == "success"
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
ENSURE_CONSISTENT_ERROR_FROM_MODIFIED_PY = r"""
|
| 163 |
+
from setuptools.modified import newer
|
| 164 |
+
from {imported_module}.errors import DistutilsError
|
| 165 |
+
|
| 166 |
+
# Can't use pytest.raises in this context
|
| 167 |
+
try:
|
| 168 |
+
newer("", "")
|
| 169 |
+
except DistutilsError:
|
| 170 |
+
print("success")
|
| 171 |
+
else:
|
| 172 |
+
raise AssertionError("Expected to raise")
|
| 173 |
+
"""
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@pytest.mark.usefixtures("tmpdir_cwd")
|
| 177 |
+
@pytest.mark.parametrize(
|
| 178 |
+
('distutils_version', 'imported_module'),
|
| 179 |
+
[
|
| 180 |
+
("local", "distutils"),
|
| 181 |
+
# Unfortunately we still get ._distutils.errors.DistutilsError with SETUPTOOLS_USE_DISTUTILS=stdlib
|
| 182 |
+
# But that's a deprecated use-case we don't mind not fully supporting in newer code
|
| 183 |
+
pytest.param(
|
| 184 |
+
"stdlib", "setuptools._distutils", marks=skip_without_stdlib_distutils
|
| 185 |
+
),
|
| 186 |
+
],
|
| 187 |
+
)
|
| 188 |
+
def test_consistent_error_from_modified_py(distutils_version, imported_module, venv):
|
| 189 |
+
env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
|
| 190 |
+
cmd = [
|
| 191 |
+
'python',
|
| 192 |
+
'-c',
|
| 193 |
+
ENSURE_CONSISTENT_ERROR_FROM_MODIFIED_PY.format(
|
| 194 |
+
imported_module=imported_module
|
| 195 |
+
),
|
| 196 |
+
]
|
| 197 |
+
output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip()
|
| 198 |
+
assert output == "success"
|
venv/lib/python3.11/site-packages/setuptools/tests/test_easy_install.py
ADDED
|
@@ -0,0 +1,1476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Easy install Tests"""
|
| 2 |
+
|
| 3 |
+
import contextlib
|
| 4 |
+
import io
|
| 5 |
+
import itertools
|
| 6 |
+
import logging
|
| 7 |
+
import os
|
| 8 |
+
import pathlib
|
| 9 |
+
import re
|
| 10 |
+
import site
|
| 11 |
+
import subprocess
|
| 12 |
+
import sys
|
| 13 |
+
import tarfile
|
| 14 |
+
import tempfile
|
| 15 |
+
import time
|
| 16 |
+
import warnings
|
| 17 |
+
import zipfile
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
from typing import NamedTuple
|
| 20 |
+
from unittest import mock
|
| 21 |
+
|
| 22 |
+
import pytest
|
| 23 |
+
from jaraco import path
|
| 24 |
+
|
| 25 |
+
import pkg_resources
|
| 26 |
+
import setuptools.command.easy_install as ei
|
| 27 |
+
from pkg_resources import Distribution as PRDistribution, normalize_path, working_set
|
| 28 |
+
from setuptools import sandbox
|
| 29 |
+
from setuptools._normalization import safer_name
|
| 30 |
+
from setuptools.command.easy_install import PthDistributions
|
| 31 |
+
from setuptools.dist import Distribution
|
| 32 |
+
from setuptools.sandbox import run_setup
|
| 33 |
+
from setuptools.tests import fail_on_ascii
|
| 34 |
+
from setuptools.tests.server import MockServer, path_to_url
|
| 35 |
+
|
| 36 |
+
from . import contexts
|
| 37 |
+
from .textwrap import DALS
|
| 38 |
+
|
| 39 |
+
import distutils.errors
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@pytest.fixture(autouse=True)
|
| 43 |
+
def pip_disable_index(monkeypatch):
|
| 44 |
+
"""
|
| 45 |
+
Important: Disable the default index for pip to avoid
|
| 46 |
+
querying packages in the index and potentially resolving
|
| 47 |
+
and installing packages there.
|
| 48 |
+
"""
|
| 49 |
+
monkeypatch.setenv('PIP_NO_INDEX', 'true')
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class FakeDist:
|
| 53 |
+
def get_entry_map(self, group):
|
| 54 |
+
if group != 'console_scripts':
|
| 55 |
+
return {}
|
| 56 |
+
return {'name': 'ep'}
|
| 57 |
+
|
| 58 |
+
def as_requirement(self):
|
| 59 |
+
return 'spec'
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
SETUP_PY = DALS(
|
| 63 |
+
"""
|
| 64 |
+
from setuptools import setup
|
| 65 |
+
|
| 66 |
+
setup()
|
| 67 |
+
"""
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class TestEasyInstallTest:
|
| 72 |
+
def test_get_script_args(self):
|
| 73 |
+
header = ei.CommandSpec.best().from_environment().as_header()
|
| 74 |
+
dist = FakeDist()
|
| 75 |
+
args = next(ei.ScriptWriter.get_args(dist))
|
| 76 |
+
_name, script = itertools.islice(args, 2)
|
| 77 |
+
assert script.startswith(header)
|
| 78 |
+
assert "'spec'" in script
|
| 79 |
+
assert "'console_scripts'" in script
|
| 80 |
+
assert "'name'" in script
|
| 81 |
+
assert re.search('^# EASY-INSTALL-ENTRY-SCRIPT', script, flags=re.MULTILINE)
|
| 82 |
+
|
| 83 |
+
def test_no_find_links(self):
|
| 84 |
+
# new option '--no-find-links', that blocks find-links added at
|
| 85 |
+
# the project level
|
| 86 |
+
dist = Distribution()
|
| 87 |
+
cmd = ei.easy_install(dist)
|
| 88 |
+
cmd.check_pth_processing = lambda: True
|
| 89 |
+
cmd.no_find_links = True
|
| 90 |
+
cmd.find_links = ['link1', 'link2']
|
| 91 |
+
cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
|
| 92 |
+
cmd.args = ['ok']
|
| 93 |
+
cmd.ensure_finalized()
|
| 94 |
+
assert cmd.package_index.scanned_urls == {}
|
| 95 |
+
|
| 96 |
+
# let's try without it (default behavior)
|
| 97 |
+
cmd = ei.easy_install(dist)
|
| 98 |
+
cmd.check_pth_processing = lambda: True
|
| 99 |
+
cmd.find_links = ['link1', 'link2']
|
| 100 |
+
cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
|
| 101 |
+
cmd.args = ['ok']
|
| 102 |
+
cmd.ensure_finalized()
|
| 103 |
+
keys = sorted(cmd.package_index.scanned_urls.keys())
|
| 104 |
+
assert keys == ['link1', 'link2']
|
| 105 |
+
|
| 106 |
+
def test_write_exception(self):
|
| 107 |
+
"""
|
| 108 |
+
Test that `cant_write_to_target` is rendered as a DistutilsError.
|
| 109 |
+
"""
|
| 110 |
+
dist = Distribution()
|
| 111 |
+
cmd = ei.easy_install(dist)
|
| 112 |
+
cmd.install_dir = os.getcwd()
|
| 113 |
+
with pytest.raises(distutils.errors.DistutilsError):
|
| 114 |
+
cmd.cant_write_to_target()
|
| 115 |
+
|
| 116 |
+
def test_all_site_dirs(self, monkeypatch):
|
| 117 |
+
"""
|
| 118 |
+
get_site_dirs should always return site dirs reported by
|
| 119 |
+
site.getsitepackages.
|
| 120 |
+
"""
|
| 121 |
+
path = normalize_path('/setuptools/test/site-packages')
|
| 122 |
+
|
| 123 |
+
def mock_gsp():
|
| 124 |
+
return [path]
|
| 125 |
+
|
| 126 |
+
monkeypatch.setattr(site, 'getsitepackages', mock_gsp, raising=False)
|
| 127 |
+
assert path in ei.get_site_dirs()
|
| 128 |
+
|
| 129 |
+
def test_all_site_dirs_works_without_getsitepackages(self, monkeypatch):
|
| 130 |
+
monkeypatch.delattr(site, 'getsitepackages', raising=False)
|
| 131 |
+
assert ei.get_site_dirs()
|
| 132 |
+
|
| 133 |
+
@pytest.fixture
|
| 134 |
+
def sdist_unicode(self, tmpdir):
|
| 135 |
+
files = [
|
| 136 |
+
(
|
| 137 |
+
'setup.py',
|
| 138 |
+
DALS(
|
| 139 |
+
"""
|
| 140 |
+
import setuptools
|
| 141 |
+
setuptools.setup(
|
| 142 |
+
name="setuptools-test-unicode",
|
| 143 |
+
version="1.0",
|
| 144 |
+
packages=["mypkg"],
|
| 145 |
+
include_package_data=True,
|
| 146 |
+
)
|
| 147 |
+
"""
|
| 148 |
+
),
|
| 149 |
+
),
|
| 150 |
+
(
|
| 151 |
+
'mypkg/__init__.py',
|
| 152 |
+
"",
|
| 153 |
+
),
|
| 154 |
+
(
|
| 155 |
+
'mypkg/☃.txt',
|
| 156 |
+
"",
|
| 157 |
+
),
|
| 158 |
+
]
|
| 159 |
+
sdist_name = 'setuptools-test-unicode-1.0.zip'
|
| 160 |
+
sdist = tmpdir / sdist_name
|
| 161 |
+
# can't use make_sdist, because the issue only occurs
|
| 162 |
+
# with zip sdists.
|
| 163 |
+
sdist_zip = zipfile.ZipFile(str(sdist), 'w')
|
| 164 |
+
for filename, content in files:
|
| 165 |
+
sdist_zip.writestr(filename, content)
|
| 166 |
+
sdist_zip.close()
|
| 167 |
+
return str(sdist)
|
| 168 |
+
|
| 169 |
+
@fail_on_ascii
|
| 170 |
+
def test_unicode_filename_in_sdist(self, sdist_unicode, tmpdir, monkeypatch):
|
| 171 |
+
"""
|
| 172 |
+
The install command should execute correctly even if
|
| 173 |
+
the package has unicode filenames.
|
| 174 |
+
"""
|
| 175 |
+
dist = Distribution({'script_args': ['easy_install']})
|
| 176 |
+
target = (tmpdir / 'target').ensure_dir()
|
| 177 |
+
cmd = ei.easy_install(
|
| 178 |
+
dist,
|
| 179 |
+
install_dir=str(target),
|
| 180 |
+
args=['x'],
|
| 181 |
+
)
|
| 182 |
+
monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target))
|
| 183 |
+
cmd.ensure_finalized()
|
| 184 |
+
cmd.easy_install(sdist_unicode)
|
| 185 |
+
|
| 186 |
+
@pytest.fixture
|
| 187 |
+
def sdist_unicode_in_script(self, tmpdir):
|
| 188 |
+
files = [
|
| 189 |
+
(
|
| 190 |
+
"setup.py",
|
| 191 |
+
DALS(
|
| 192 |
+
"""
|
| 193 |
+
import setuptools
|
| 194 |
+
setuptools.setup(
|
| 195 |
+
name="setuptools-test-unicode",
|
| 196 |
+
version="1.0",
|
| 197 |
+
packages=["mypkg"],
|
| 198 |
+
include_package_data=True,
|
| 199 |
+
scripts=['mypkg/unicode_in_script'],
|
| 200 |
+
)
|
| 201 |
+
"""
|
| 202 |
+
),
|
| 203 |
+
),
|
| 204 |
+
("mypkg/__init__.py", ""),
|
| 205 |
+
(
|
| 206 |
+
"mypkg/unicode_in_script",
|
| 207 |
+
DALS(
|
| 208 |
+
"""
|
| 209 |
+
#!/bin/sh
|
| 210 |
+
# á
|
| 211 |
+
|
| 212 |
+
non_python_fn() {
|
| 213 |
+
}
|
| 214 |
+
"""
|
| 215 |
+
),
|
| 216 |
+
),
|
| 217 |
+
]
|
| 218 |
+
sdist_name = "setuptools-test-unicode-script-1.0.zip"
|
| 219 |
+
sdist = tmpdir / sdist_name
|
| 220 |
+
# can't use make_sdist, because the issue only occurs
|
| 221 |
+
# with zip sdists.
|
| 222 |
+
sdist_zip = zipfile.ZipFile(str(sdist), "w")
|
| 223 |
+
for filename, content in files:
|
| 224 |
+
sdist_zip.writestr(filename, content.encode('utf-8'))
|
| 225 |
+
sdist_zip.close()
|
| 226 |
+
return str(sdist)
|
| 227 |
+
|
| 228 |
+
@fail_on_ascii
|
| 229 |
+
def test_unicode_content_in_sdist(
|
| 230 |
+
self, sdist_unicode_in_script, tmpdir, monkeypatch
|
| 231 |
+
):
|
| 232 |
+
"""
|
| 233 |
+
The install command should execute correctly even if
|
| 234 |
+
the package has unicode in scripts.
|
| 235 |
+
"""
|
| 236 |
+
dist = Distribution({"script_args": ["easy_install"]})
|
| 237 |
+
target = (tmpdir / "target").ensure_dir()
|
| 238 |
+
cmd = ei.easy_install(dist, install_dir=str(target), args=["x"])
|
| 239 |
+
monkeypatch.setitem(os.environ, "PYTHONPATH", str(target))
|
| 240 |
+
cmd.ensure_finalized()
|
| 241 |
+
cmd.easy_install(sdist_unicode_in_script)
|
| 242 |
+
|
| 243 |
+
@pytest.fixture
|
| 244 |
+
def sdist_script(self, tmpdir):
|
| 245 |
+
files = [
|
| 246 |
+
(
|
| 247 |
+
'setup.py',
|
| 248 |
+
DALS(
|
| 249 |
+
"""
|
| 250 |
+
import setuptools
|
| 251 |
+
setuptools.setup(
|
| 252 |
+
name="setuptools-test-script",
|
| 253 |
+
version="1.0",
|
| 254 |
+
scripts=["mypkg_script"],
|
| 255 |
+
)
|
| 256 |
+
"""
|
| 257 |
+
),
|
| 258 |
+
),
|
| 259 |
+
(
|
| 260 |
+
'mypkg_script',
|
| 261 |
+
DALS(
|
| 262 |
+
"""
|
| 263 |
+
#/usr/bin/python
|
| 264 |
+
print('mypkg_script')
|
| 265 |
+
"""
|
| 266 |
+
),
|
| 267 |
+
),
|
| 268 |
+
]
|
| 269 |
+
sdist_name = 'setuptools-test-script-1.0.zip'
|
| 270 |
+
sdist = str(tmpdir / sdist_name)
|
| 271 |
+
make_sdist(sdist, files)
|
| 272 |
+
return sdist
|
| 273 |
+
|
| 274 |
+
@pytest.mark.skipif(
|
| 275 |
+
not sys.platform.startswith('linux'), reason="Test can only be run on Linux"
|
| 276 |
+
)
|
| 277 |
+
def test_script_install(self, sdist_script, tmpdir, monkeypatch):
|
| 278 |
+
"""
|
| 279 |
+
Check scripts are installed.
|
| 280 |
+
"""
|
| 281 |
+
dist = Distribution({'script_args': ['easy_install']})
|
| 282 |
+
target = (tmpdir / 'target').ensure_dir()
|
| 283 |
+
cmd = ei.easy_install(
|
| 284 |
+
dist,
|
| 285 |
+
install_dir=str(target),
|
| 286 |
+
args=['x'],
|
| 287 |
+
)
|
| 288 |
+
monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target))
|
| 289 |
+
cmd.ensure_finalized()
|
| 290 |
+
cmd.easy_install(sdist_script)
|
| 291 |
+
assert (target / 'mypkg_script').exists()
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
@pytest.mark.filterwarnings('ignore:Unbuilt egg')
|
| 295 |
+
class TestPTHFileWriter:
|
| 296 |
+
def test_add_from_cwd_site_sets_dirty(self):
|
| 297 |
+
"""a pth file manager should set dirty
|
| 298 |
+
if a distribution is in site but also the cwd
|
| 299 |
+
"""
|
| 300 |
+
pth = PthDistributions('does-not_exist', [os.getcwd()])
|
| 301 |
+
assert not pth.dirty
|
| 302 |
+
pth.add(PRDistribution(os.getcwd()))
|
| 303 |
+
assert pth.dirty
|
| 304 |
+
|
| 305 |
+
def test_add_from_site_is_ignored(self):
|
| 306 |
+
location = '/test/location/does-not-have-to-exist'
|
| 307 |
+
# PthDistributions expects all locations to be normalized
|
| 308 |
+
location = pkg_resources.normalize_path(location)
|
| 309 |
+
pth = PthDistributions(
|
| 310 |
+
'does-not_exist',
|
| 311 |
+
[
|
| 312 |
+
location,
|
| 313 |
+
],
|
| 314 |
+
)
|
| 315 |
+
assert not pth.dirty
|
| 316 |
+
pth.add(PRDistribution(location))
|
| 317 |
+
assert not pth.dirty
|
| 318 |
+
|
| 319 |
+
def test_many_pth_distributions_merge_together(self, tmpdir):
|
| 320 |
+
"""
|
| 321 |
+
If the pth file is modified under the hood, then PthDistribution
|
| 322 |
+
will refresh its content before saving, merging contents when
|
| 323 |
+
necessary.
|
| 324 |
+
"""
|
| 325 |
+
# putting the pth file in a dedicated sub-folder,
|
| 326 |
+
pth_subdir = tmpdir.join("pth_subdir")
|
| 327 |
+
pth_subdir.mkdir()
|
| 328 |
+
pth_path = str(pth_subdir.join("file1.pth"))
|
| 329 |
+
pth1 = PthDistributions(pth_path)
|
| 330 |
+
pth2 = PthDistributions(pth_path)
|
| 331 |
+
assert pth1.paths == pth2.paths == [], (
|
| 332 |
+
"unless there would be some default added at some point"
|
| 333 |
+
)
|
| 334 |
+
# and so putting the src_subdir in folder distinct than the pth one,
|
| 335 |
+
# so to keep it absolute by PthDistributions
|
| 336 |
+
new_src_path = tmpdir.join("src_subdir")
|
| 337 |
+
new_src_path.mkdir() # must exist to be accounted
|
| 338 |
+
new_src_path_str = str(new_src_path)
|
| 339 |
+
pth1.paths.append(new_src_path_str)
|
| 340 |
+
pth1.save()
|
| 341 |
+
assert pth1.paths, (
|
| 342 |
+
"the new_src_path added must still be present/valid in pth1 after save"
|
| 343 |
+
)
|
| 344 |
+
# now,
|
| 345 |
+
assert new_src_path_str not in pth2.paths, (
|
| 346 |
+
"right before we save the entry should still not be present"
|
| 347 |
+
)
|
| 348 |
+
pth2.save()
|
| 349 |
+
assert new_src_path_str in pth2.paths, (
|
| 350 |
+
"the new_src_path entry should have been added by pth2 with its save() call"
|
| 351 |
+
)
|
| 352 |
+
assert pth2.paths[-1] == new_src_path, (
|
| 353 |
+
"and it should match exactly on the last entry actually "
|
| 354 |
+
"given we append to it in save()"
|
| 355 |
+
)
|
| 356 |
+
# finally,
|
| 357 |
+
assert PthDistributions(pth_path).paths == pth2.paths, (
|
| 358 |
+
"and we should have the exact same list at the end "
|
| 359 |
+
"with a fresh PthDistributions instance"
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
@pytest.fixture
|
| 364 |
+
def setup_context(tmpdir):
|
| 365 |
+
with (tmpdir / 'setup.py').open('w', encoding="utf-8") as f:
|
| 366 |
+
f.write(SETUP_PY)
|
| 367 |
+
with tmpdir.as_cwd():
|
| 368 |
+
yield tmpdir
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
@pytest.mark.usefixtures("user_override")
|
| 372 |
+
@pytest.mark.usefixtures("setup_context")
|
| 373 |
+
class TestUserInstallTest:
|
| 374 |
+
# prevent check that site-packages is writable. easy_install
|
| 375 |
+
# shouldn't be writing to system site-packages during finalize
|
| 376 |
+
# options, but while it does, bypass the behavior.
|
| 377 |
+
prev_sp_write = mock.patch(
|
| 378 |
+
'setuptools.command.easy_install.easy_install.check_site_dir',
|
| 379 |
+
mock.Mock(),
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
# simulate setuptools installed in user site packages
|
| 383 |
+
@mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE)
|
| 384 |
+
@mock.patch('site.ENABLE_USER_SITE', True)
|
| 385 |
+
@prev_sp_write
|
| 386 |
+
def test_user_install_not_implied_user_site_enabled(self):
|
| 387 |
+
self.assert_not_user_site()
|
| 388 |
+
|
| 389 |
+
@mock.patch('site.ENABLE_USER_SITE', False)
|
| 390 |
+
@prev_sp_write
|
| 391 |
+
def test_user_install_not_implied_user_site_disabled(self):
|
| 392 |
+
self.assert_not_user_site()
|
| 393 |
+
|
| 394 |
+
@staticmethod
|
| 395 |
+
def assert_not_user_site():
|
| 396 |
+
# create a finalized easy_install command
|
| 397 |
+
dist = Distribution()
|
| 398 |
+
dist.script_name = 'setup.py'
|
| 399 |
+
cmd = ei.easy_install(dist)
|
| 400 |
+
cmd.args = ['py']
|
| 401 |
+
cmd.ensure_finalized()
|
| 402 |
+
assert not cmd.user, 'user should not be implied'
|
| 403 |
+
|
| 404 |
+
def test_multiproc_atexit(self):
|
| 405 |
+
pytest.importorskip('multiprocessing')
|
| 406 |
+
|
| 407 |
+
log = logging.getLogger('test_easy_install')
|
| 408 |
+
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
|
| 409 |
+
log.info('this should not break')
|
| 410 |
+
|
| 411 |
+
@pytest.fixture
|
| 412 |
+
def foo_package(self, tmpdir):
|
| 413 |
+
egg_file = tmpdir / 'foo-1.0.egg-info'
|
| 414 |
+
with egg_file.open('w') as f:
|
| 415 |
+
f.write('Name: foo\n')
|
| 416 |
+
return str(tmpdir)
|
| 417 |
+
|
| 418 |
+
@pytest.fixture
|
| 419 |
+
def install_target(self, tmpdir):
|
| 420 |
+
target = str(tmpdir)
|
| 421 |
+
with mock.patch('sys.path', sys.path + [target]):
|
| 422 |
+
python_path = os.path.pathsep.join(sys.path)
|
| 423 |
+
with mock.patch.dict(os.environ, PYTHONPATH=python_path):
|
| 424 |
+
yield target
|
| 425 |
+
|
| 426 |
+
def test_local_index(self, foo_package, install_target):
|
| 427 |
+
"""
|
| 428 |
+
The local index must be used when easy_install locates installed
|
| 429 |
+
packages.
|
| 430 |
+
"""
|
| 431 |
+
dist = Distribution()
|
| 432 |
+
dist.script_name = 'setup.py'
|
| 433 |
+
cmd = ei.easy_install(dist)
|
| 434 |
+
cmd.install_dir = install_target
|
| 435 |
+
cmd.args = ['foo']
|
| 436 |
+
cmd.ensure_finalized()
|
| 437 |
+
cmd.local_index.scan([foo_package])
|
| 438 |
+
res = cmd.easy_install('foo')
|
| 439 |
+
actual = os.path.normcase(os.path.realpath(res.location))
|
| 440 |
+
expected = os.path.normcase(os.path.realpath(foo_package))
|
| 441 |
+
assert actual == expected
|
| 442 |
+
|
| 443 |
+
@contextlib.contextmanager
|
| 444 |
+
def user_install_setup_context(self, *args, **kwargs):
|
| 445 |
+
"""
|
| 446 |
+
Wrap sandbox.setup_context to patch easy_install in that context to
|
| 447 |
+
appear as user-installed.
|
| 448 |
+
"""
|
| 449 |
+
with self.orig_context(*args, **kwargs):
|
| 450 |
+
import setuptools.command.easy_install as ei
|
| 451 |
+
|
| 452 |
+
ei.__file__ = site.USER_SITE
|
| 453 |
+
yield
|
| 454 |
+
|
| 455 |
+
def patched_setup_context(self):
|
| 456 |
+
self.orig_context = sandbox.setup_context
|
| 457 |
+
|
| 458 |
+
return mock.patch(
|
| 459 |
+
'setuptools.sandbox.setup_context',
|
| 460 |
+
self.user_install_setup_context,
|
| 461 |
+
)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
@pytest.fixture
|
| 465 |
+
def distutils_package():
|
| 466 |
+
distutils_setup_py = SETUP_PY.replace(
|
| 467 |
+
'from setuptools import setup',
|
| 468 |
+
'from distutils.core import setup',
|
| 469 |
+
)
|
| 470 |
+
with contexts.tempdir(cd=os.chdir):
|
| 471 |
+
with open('setup.py', 'w', encoding="utf-8") as f:
|
| 472 |
+
f.write(distutils_setup_py)
|
| 473 |
+
yield
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
@pytest.mark.usefixtures("distutils_package")
|
| 477 |
+
class TestDistutilsPackage:
|
| 478 |
+
def test_bdist_egg_available_on_distutils_pkg(self):
|
| 479 |
+
run_setup('setup.py', ['bdist_egg'])
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
@pytest.fixture
|
| 483 |
+
def mock_index():
|
| 484 |
+
# set up a server which will simulate an alternate package index.
|
| 485 |
+
p_index = MockServer()
|
| 486 |
+
if p_index.server_port == 0:
|
| 487 |
+
# Some platforms (Jython) don't find a port to which to bind,
|
| 488 |
+
# so skip test for them.
|
| 489 |
+
pytest.skip("could not find a valid port")
|
| 490 |
+
p_index.start()
|
| 491 |
+
return p_index
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
class TestInstallRequires:
|
| 495 |
+
def test_setup_install_includes_dependencies(self, tmp_path, mock_index):
|
| 496 |
+
"""
|
| 497 |
+
When ``python setup.py install`` is called directly, it will use easy_install
|
| 498 |
+
to fetch dependencies.
|
| 499 |
+
"""
|
| 500 |
+
# TODO: Remove these tests once `setup.py install` is completely removed
|
| 501 |
+
project_root = tmp_path / "project"
|
| 502 |
+
project_root.mkdir(exist_ok=True)
|
| 503 |
+
install_root = tmp_path / "install"
|
| 504 |
+
install_root.mkdir(exist_ok=True)
|
| 505 |
+
|
| 506 |
+
self.create_project(project_root)
|
| 507 |
+
cmd = [
|
| 508 |
+
sys.executable,
|
| 509 |
+
'-c',
|
| 510 |
+
'__import__("setuptools").setup()',
|
| 511 |
+
'install',
|
| 512 |
+
'--install-base',
|
| 513 |
+
str(install_root),
|
| 514 |
+
'--install-lib',
|
| 515 |
+
str(install_root),
|
| 516 |
+
'--install-headers',
|
| 517 |
+
str(install_root),
|
| 518 |
+
'--install-scripts',
|
| 519 |
+
str(install_root),
|
| 520 |
+
'--install-data',
|
| 521 |
+
str(install_root),
|
| 522 |
+
'--install-purelib',
|
| 523 |
+
str(install_root),
|
| 524 |
+
'--install-platlib',
|
| 525 |
+
str(install_root),
|
| 526 |
+
]
|
| 527 |
+
env = {**os.environ, "__EASYINSTALL_INDEX": mock_index.url}
|
| 528 |
+
cp = subprocess.run(
|
| 529 |
+
cmd,
|
| 530 |
+
cwd=str(project_root),
|
| 531 |
+
env=env,
|
| 532 |
+
stdout=subprocess.PIPE,
|
| 533 |
+
stderr=subprocess.STDOUT,
|
| 534 |
+
text=True,
|
| 535 |
+
encoding="utf-8",
|
| 536 |
+
)
|
| 537 |
+
assert cp.returncode != 0
|
| 538 |
+
try:
|
| 539 |
+
assert '/does-not-exist/' in {r.path for r in mock_index.requests}
|
| 540 |
+
assert next(
|
| 541 |
+
line
|
| 542 |
+
for line in cp.stdout.splitlines()
|
| 543 |
+
if "not find suitable distribution for" in line
|
| 544 |
+
and "does-not-exist" in line
|
| 545 |
+
)
|
| 546 |
+
except Exception:
|
| 547 |
+
if "failed to get random numbers" in cp.stdout:
|
| 548 |
+
pytest.xfail(f"{sys.platform} failure - {cp.stdout}")
|
| 549 |
+
raise
|
| 550 |
+
|
| 551 |
+
def create_project(self, root):
|
| 552 |
+
config = """
|
| 553 |
+
[metadata]
|
| 554 |
+
name = project
|
| 555 |
+
version = 42
|
| 556 |
+
|
| 557 |
+
[options]
|
| 558 |
+
install_requires = does-not-exist
|
| 559 |
+
py_modules = mod
|
| 560 |
+
"""
|
| 561 |
+
(root / 'setup.cfg').write_text(DALS(config), encoding="utf-8")
|
| 562 |
+
(root / 'mod.py').touch()
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class TestSetupRequires:
|
| 566 |
+
def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch):
|
| 567 |
+
"""
|
| 568 |
+
When easy_install installs a source distribution which specifies
|
| 569 |
+
setup_requires, it should honor the fetch parameters (such as
|
| 570 |
+
index-url, and find-links).
|
| 571 |
+
"""
|
| 572 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 573 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 574 |
+
monkeypatch.setenv('PIP_NO_INDEX', 'false')
|
| 575 |
+
with contexts.quiet():
|
| 576 |
+
# create an sdist that has a build-time dependency.
|
| 577 |
+
with TestSetupRequires.create_sdist() as dist_file:
|
| 578 |
+
with contexts.tempdir() as temp_install_dir:
|
| 579 |
+
with contexts.environment(PYTHONPATH=temp_install_dir):
|
| 580 |
+
cmd = [
|
| 581 |
+
sys.executable,
|
| 582 |
+
'-c',
|
| 583 |
+
'__import__("setuptools").setup()',
|
| 584 |
+
'easy_install',
|
| 585 |
+
'--index-url',
|
| 586 |
+
mock_index.url,
|
| 587 |
+
'--exclude-scripts',
|
| 588 |
+
'--install-dir',
|
| 589 |
+
temp_install_dir,
|
| 590 |
+
dist_file,
|
| 591 |
+
]
|
| 592 |
+
subprocess.Popen(cmd).wait()
|
| 593 |
+
# there should have been one requests to the server
|
| 594 |
+
assert [r.path for r in mock_index.requests] == ['/does-not-exist/']
|
| 595 |
+
|
| 596 |
+
@staticmethod
|
| 597 |
+
@contextlib.contextmanager
|
| 598 |
+
def create_sdist():
|
| 599 |
+
"""
|
| 600 |
+
Return an sdist with a setup_requires dependency (of something that
|
| 601 |
+
doesn't exist)
|
| 602 |
+
"""
|
| 603 |
+
with contexts.tempdir() as dir:
|
| 604 |
+
dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz')
|
| 605 |
+
make_sdist(
|
| 606 |
+
dist_path,
|
| 607 |
+
[
|
| 608 |
+
(
|
| 609 |
+
'setup.py',
|
| 610 |
+
DALS(
|
| 611 |
+
"""
|
| 612 |
+
import setuptools
|
| 613 |
+
setuptools.setup(
|
| 614 |
+
name="setuptools-test-fetcher",
|
| 615 |
+
version="1.0",
|
| 616 |
+
setup_requires = ['does-not-exist'],
|
| 617 |
+
)
|
| 618 |
+
"""
|
| 619 |
+
),
|
| 620 |
+
),
|
| 621 |
+
('setup.cfg', ''),
|
| 622 |
+
],
|
| 623 |
+
)
|
| 624 |
+
yield dist_path
|
| 625 |
+
|
| 626 |
+
use_setup_cfg = (
|
| 627 |
+
(),
|
| 628 |
+
('dependency_links',),
|
| 629 |
+
('setup_requires',),
|
| 630 |
+
('dependency_links', 'setup_requires'),
|
| 631 |
+
)
|
| 632 |
+
|
| 633 |
+
@pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
|
| 634 |
+
def test_setup_requires_overrides_version_conflict(self, use_setup_cfg):
|
| 635 |
+
"""
|
| 636 |
+
Regression test for distribution issue 323:
|
| 637 |
+
https://bitbucket.org/tarek/distribute/issues/323
|
| 638 |
+
|
| 639 |
+
Ensures that a distribution's setup_requires requirements can still be
|
| 640 |
+
installed and used locally even if a conflicting version of that
|
| 641 |
+
requirement is already on the path.
|
| 642 |
+
"""
|
| 643 |
+
|
| 644 |
+
fake_dist = PRDistribution(
|
| 645 |
+
'does-not-matter', project_name='foobar', version='0.0'
|
| 646 |
+
)
|
| 647 |
+
working_set.add(fake_dist)
|
| 648 |
+
|
| 649 |
+
with contexts.save_pkg_resources_state():
|
| 650 |
+
with contexts.tempdir() as temp_dir:
|
| 651 |
+
test_pkg = create_setup_requires_package(
|
| 652 |
+
temp_dir, use_setup_cfg=use_setup_cfg
|
| 653 |
+
)
|
| 654 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 655 |
+
with contexts.quiet() as (stdout, _stderr):
|
| 656 |
+
# Don't even need to install the package, just
|
| 657 |
+
# running the setup.py at all is sufficient
|
| 658 |
+
run_setup(test_setup_py, ['--name'])
|
| 659 |
+
|
| 660 |
+
lines = stdout.readlines()
|
| 661 |
+
assert len(lines) > 0
|
| 662 |
+
assert lines[-1].strip() == 'test_pkg'
|
| 663 |
+
|
| 664 |
+
@pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
|
| 665 |
+
def test_setup_requires_override_nspkg(self, use_setup_cfg):
|
| 666 |
+
"""
|
| 667 |
+
Like ``test_setup_requires_overrides_version_conflict`` but where the
|
| 668 |
+
``setup_requires`` package is part of a namespace package that has
|
| 669 |
+
*already* been imported.
|
| 670 |
+
"""
|
| 671 |
+
|
| 672 |
+
with contexts.save_pkg_resources_state():
|
| 673 |
+
with contexts.tempdir() as temp_dir:
|
| 674 |
+
foobar_1_archive = os.path.join(temp_dir, 'foo_bar-0.1.tar.gz')
|
| 675 |
+
make_nspkg_sdist(foobar_1_archive, 'foo.bar', '0.1')
|
| 676 |
+
# Now actually go ahead an extract to the temp dir and add the
|
| 677 |
+
# extracted path to sys.path so foo.bar v0.1 is importable
|
| 678 |
+
foobar_1_dir = os.path.join(temp_dir, 'foo_bar-0.1')
|
| 679 |
+
os.mkdir(foobar_1_dir)
|
| 680 |
+
with tarfile.open(foobar_1_archive) as tf:
|
| 681 |
+
tf.extraction_filter = lambda member, path: member
|
| 682 |
+
tf.extractall(foobar_1_dir)
|
| 683 |
+
sys.path.insert(1, foobar_1_dir)
|
| 684 |
+
|
| 685 |
+
dist = PRDistribution(
|
| 686 |
+
foobar_1_dir, project_name='foo.bar', version='0.1'
|
| 687 |
+
)
|
| 688 |
+
working_set.add(dist)
|
| 689 |
+
|
| 690 |
+
template = DALS(
|
| 691 |
+
"""\
|
| 692 |
+
import foo # Even with foo imported first the
|
| 693 |
+
# setup_requires package should override
|
| 694 |
+
import setuptools
|
| 695 |
+
setuptools.setup(**%r)
|
| 696 |
+
|
| 697 |
+
if not (hasattr(foo, '__path__') and
|
| 698 |
+
len(foo.__path__) == 2):
|
| 699 |
+
print('FAIL')
|
| 700 |
+
|
| 701 |
+
if 'foo_bar-0.2' not in foo.__path__[0]:
|
| 702 |
+
print('FAIL')
|
| 703 |
+
"""
|
| 704 |
+
)
|
| 705 |
+
|
| 706 |
+
test_pkg = create_setup_requires_package(
|
| 707 |
+
temp_dir,
|
| 708 |
+
'foo.bar',
|
| 709 |
+
'0.2',
|
| 710 |
+
make_nspkg_sdist,
|
| 711 |
+
template,
|
| 712 |
+
use_setup_cfg=use_setup_cfg,
|
| 713 |
+
)
|
| 714 |
+
|
| 715 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 716 |
+
|
| 717 |
+
with contexts.quiet() as (stdout, _stderr):
|
| 718 |
+
try:
|
| 719 |
+
# Don't even need to install the package, just
|
| 720 |
+
# running the setup.py at all is sufficient
|
| 721 |
+
run_setup(test_setup_py, ['--name'])
|
| 722 |
+
except pkg_resources.VersionConflict: # pragma: nocover
|
| 723 |
+
pytest.fail(
|
| 724 |
+
'Installing setup.py requirements caused a VersionConflict'
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
assert 'FAIL' not in stdout.getvalue()
|
| 728 |
+
lines = stdout.readlines()
|
| 729 |
+
assert len(lines) > 0
|
| 730 |
+
assert lines[-1].strip() == 'test_pkg'
|
| 731 |
+
|
| 732 |
+
@pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
|
| 733 |
+
def test_setup_requires_with_attr_version(self, use_setup_cfg):
|
| 734 |
+
def make_dependency_sdist(dist_path, distname, version):
|
| 735 |
+
files = [
|
| 736 |
+
(
|
| 737 |
+
'setup.py',
|
| 738 |
+
DALS(
|
| 739 |
+
f"""
|
| 740 |
+
import setuptools
|
| 741 |
+
setuptools.setup(
|
| 742 |
+
name={distname!r},
|
| 743 |
+
version={version!r},
|
| 744 |
+
py_modules=[{distname!r}],
|
| 745 |
+
)
|
| 746 |
+
"""
|
| 747 |
+
),
|
| 748 |
+
),
|
| 749 |
+
(
|
| 750 |
+
distname + '.py',
|
| 751 |
+
DALS(
|
| 752 |
+
"""
|
| 753 |
+
version = 42
|
| 754 |
+
"""
|
| 755 |
+
),
|
| 756 |
+
),
|
| 757 |
+
]
|
| 758 |
+
make_sdist(dist_path, files)
|
| 759 |
+
|
| 760 |
+
with contexts.save_pkg_resources_state():
|
| 761 |
+
with contexts.tempdir() as temp_dir:
|
| 762 |
+
test_pkg = create_setup_requires_package(
|
| 763 |
+
temp_dir,
|
| 764 |
+
setup_attrs=dict(version='attr: foobar.version'),
|
| 765 |
+
make_package=make_dependency_sdist,
|
| 766 |
+
use_setup_cfg=use_setup_cfg + ('version',),
|
| 767 |
+
)
|
| 768 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 769 |
+
with contexts.quiet() as (stdout, _stderr):
|
| 770 |
+
run_setup(test_setup_py, ['--version'])
|
| 771 |
+
lines = stdout.readlines()
|
| 772 |
+
assert len(lines) > 0
|
| 773 |
+
assert lines[-1].strip() == '42'
|
| 774 |
+
|
| 775 |
+
def test_setup_requires_honors_pip_env(self, mock_index, monkeypatch):
|
| 776 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 777 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 778 |
+
monkeypatch.setenv('PIP_NO_INDEX', 'false')
|
| 779 |
+
monkeypatch.setenv('PIP_INDEX_URL', mock_index.url)
|
| 780 |
+
with contexts.save_pkg_resources_state():
|
| 781 |
+
with contexts.tempdir() as temp_dir:
|
| 782 |
+
test_pkg = create_setup_requires_package(
|
| 783 |
+
temp_dir,
|
| 784 |
+
'python-xlib',
|
| 785 |
+
'0.19',
|
| 786 |
+
setup_attrs=dict(dependency_links=[]),
|
| 787 |
+
)
|
| 788 |
+
test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
|
| 789 |
+
with open(test_setup_cfg, 'w', encoding="utf-8") as fp:
|
| 790 |
+
fp.write(
|
| 791 |
+
DALS(
|
| 792 |
+
"""
|
| 793 |
+
[easy_install]
|
| 794 |
+
index_url = https://pypi.org/legacy/
|
| 795 |
+
"""
|
| 796 |
+
)
|
| 797 |
+
)
|
| 798 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 799 |
+
with pytest.raises(distutils.errors.DistutilsError):
|
| 800 |
+
run_setup(test_setup_py, ['--version'])
|
| 801 |
+
assert len(mock_index.requests) == 1
|
| 802 |
+
assert mock_index.requests[0].path == '/python-xlib/'
|
| 803 |
+
|
| 804 |
+
def test_setup_requires_with_pep508_url(self, mock_index, monkeypatch):
|
| 805 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 806 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 807 |
+
monkeypatch.setenv('PIP_INDEX_URL', mock_index.url)
|
| 808 |
+
with contexts.save_pkg_resources_state():
|
| 809 |
+
with contexts.tempdir() as temp_dir:
|
| 810 |
+
dep_sdist = os.path.join(temp_dir, 'dep.tar.gz')
|
| 811 |
+
make_trivial_sdist(dep_sdist, 'dependency', '42')
|
| 812 |
+
dep_url = path_to_url(dep_sdist, authority='localhost')
|
| 813 |
+
test_pkg = create_setup_requires_package(
|
| 814 |
+
temp_dir,
|
| 815 |
+
# Ignored (overridden by setup_attrs)
|
| 816 |
+
'python-xlib',
|
| 817 |
+
'0.19',
|
| 818 |
+
setup_attrs=dict(setup_requires=f'dependency @ {dep_url}'),
|
| 819 |
+
)
|
| 820 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 821 |
+
run_setup(test_setup_py, ['--version'])
|
| 822 |
+
assert len(mock_index.requests) == 0
|
| 823 |
+
|
| 824 |
+
def test_setup_requires_with_allow_hosts(self, mock_index):
|
| 825 |
+
"""The `allow-hosts` option in not supported anymore."""
|
| 826 |
+
files = {
|
| 827 |
+
'test_pkg': {
|
| 828 |
+
'setup.py': DALS(
|
| 829 |
+
"""
|
| 830 |
+
from setuptools import setup
|
| 831 |
+
setup(setup_requires='python-xlib')
|
| 832 |
+
"""
|
| 833 |
+
),
|
| 834 |
+
'setup.cfg': DALS(
|
| 835 |
+
"""
|
| 836 |
+
[easy_install]
|
| 837 |
+
allow_hosts = *
|
| 838 |
+
"""
|
| 839 |
+
),
|
| 840 |
+
}
|
| 841 |
+
}
|
| 842 |
+
with contexts.save_pkg_resources_state():
|
| 843 |
+
with contexts.tempdir() as temp_dir:
|
| 844 |
+
path.build(files, prefix=temp_dir)
|
| 845 |
+
setup_py = str(pathlib.Path(temp_dir, 'test_pkg', 'setup.py'))
|
| 846 |
+
with pytest.raises(distutils.errors.DistutilsError):
|
| 847 |
+
run_setup(setup_py, ['--version'])
|
| 848 |
+
assert len(mock_index.requests) == 0
|
| 849 |
+
|
| 850 |
+
def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir):
|
| 851 |
+
"""Check `python_requires` is honored."""
|
| 852 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 853 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 854 |
+
monkeypatch.setenv('PIP_NO_INDEX', '1')
|
| 855 |
+
monkeypatch.setenv('PIP_VERBOSE', '1')
|
| 856 |
+
dep_1_0_sdist = 'dep-1.0.tar.gz'
|
| 857 |
+
dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist))
|
| 858 |
+
dep_1_0_python_requires = '>=2.7'
|
| 859 |
+
make_python_requires_sdist(
|
| 860 |
+
str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires
|
| 861 |
+
)
|
| 862 |
+
dep_2_0_sdist = 'dep-2.0.tar.gz'
|
| 863 |
+
dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist))
|
| 864 |
+
dep_2_0_python_requires = (
|
| 865 |
+
f'!={sys.version_info.major}.{sys.version_info.minor}.*'
|
| 866 |
+
)
|
| 867 |
+
make_python_requires_sdist(
|
| 868 |
+
str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires
|
| 869 |
+
)
|
| 870 |
+
index = tmpdir / 'index.html'
|
| 871 |
+
index.write_text(
|
| 872 |
+
DALS(
|
| 873 |
+
"""
|
| 874 |
+
<!DOCTYPE html>
|
| 875 |
+
<html><head><title>Links for dep</title></head>
|
| 876 |
+
<body>
|
| 877 |
+
<h1>Links for dep</h1>
|
| 878 |
+
<a href="{dep_1_0_url}"\
|
| 879 |
+
data-requires-python="{dep_1_0_python_requires}">{dep_1_0_sdist}</a><br/>
|
| 880 |
+
<a href="{dep_2_0_url}"\
|
| 881 |
+
data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/>
|
| 882 |
+
</body>
|
| 883 |
+
</html>
|
| 884 |
+
"""
|
| 885 |
+
).format(
|
| 886 |
+
dep_1_0_url=dep_1_0_url,
|
| 887 |
+
dep_1_0_sdist=dep_1_0_sdist,
|
| 888 |
+
dep_1_0_python_requires=dep_1_0_python_requires,
|
| 889 |
+
dep_2_0_url=dep_2_0_url,
|
| 890 |
+
dep_2_0_sdist=dep_2_0_sdist,
|
| 891 |
+
dep_2_0_python_requires=dep_2_0_python_requires,
|
| 892 |
+
),
|
| 893 |
+
'utf-8',
|
| 894 |
+
)
|
| 895 |
+
index_url = path_to_url(str(index))
|
| 896 |
+
with contexts.save_pkg_resources_state():
|
| 897 |
+
test_pkg = create_setup_requires_package(
|
| 898 |
+
str(tmpdir),
|
| 899 |
+
'python-xlib',
|
| 900 |
+
'0.19', # Ignored (overridden by setup_attrs).
|
| 901 |
+
setup_attrs=dict(setup_requires='dep', dependency_links=[index_url]),
|
| 902 |
+
)
|
| 903 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 904 |
+
run_setup(test_setup_py, ['--version'])
|
| 905 |
+
eggs = list(
|
| 906 |
+
map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))
|
| 907 |
+
)
|
| 908 |
+
assert eggs == ['dep 1.0']
|
| 909 |
+
|
| 910 |
+
@pytest.mark.parametrize('with_dependency_links_in_setup_py', (False, True))
|
| 911 |
+
def test_setup_requires_with_find_links_in_setup_cfg(
|
| 912 |
+
self, monkeypatch, with_dependency_links_in_setup_py
|
| 913 |
+
):
|
| 914 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 915 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 916 |
+
with contexts.save_pkg_resources_state():
|
| 917 |
+
with contexts.tempdir() as temp_dir:
|
| 918 |
+
make_trivial_sdist(
|
| 919 |
+
os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 'python-xlib', '42'
|
| 920 |
+
)
|
| 921 |
+
test_pkg = os.path.join(temp_dir, 'test_pkg')
|
| 922 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 923 |
+
test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
|
| 924 |
+
os.mkdir(test_pkg)
|
| 925 |
+
with open(test_setup_py, 'w', encoding="utf-8") as fp:
|
| 926 |
+
if with_dependency_links_in_setup_py:
|
| 927 |
+
dependency_links = [os.path.join(temp_dir, 'links')]
|
| 928 |
+
else:
|
| 929 |
+
dependency_links = []
|
| 930 |
+
fp.write(
|
| 931 |
+
DALS(
|
| 932 |
+
"""
|
| 933 |
+
from setuptools import installer, setup
|
| 934 |
+
setup(setup_requires='python-xlib==42',
|
| 935 |
+
dependency_links={dependency_links!r})
|
| 936 |
+
"""
|
| 937 |
+
).format(dependency_links=dependency_links)
|
| 938 |
+
)
|
| 939 |
+
with open(test_setup_cfg, 'w', encoding="utf-8") as fp:
|
| 940 |
+
fp.write(
|
| 941 |
+
DALS(
|
| 942 |
+
"""
|
| 943 |
+
[easy_install]
|
| 944 |
+
index_url = {index_url}
|
| 945 |
+
find_links = {find_links}
|
| 946 |
+
"""
|
| 947 |
+
).format(
|
| 948 |
+
index_url=os.path.join(temp_dir, 'index'),
|
| 949 |
+
find_links=temp_dir,
|
| 950 |
+
)
|
| 951 |
+
)
|
| 952 |
+
run_setup(test_setup_py, ['--version'])
|
| 953 |
+
|
| 954 |
+
def test_setup_requires_with_transitive_extra_dependency(self, monkeypatch):
|
| 955 |
+
"""
|
| 956 |
+
Use case: installing a package with a build dependency on
|
| 957 |
+
an already installed `dep[extra]`, which in turn depends
|
| 958 |
+
on `extra_dep` (whose is not already installed).
|
| 959 |
+
"""
|
| 960 |
+
with contexts.save_pkg_resources_state():
|
| 961 |
+
with contexts.tempdir() as temp_dir:
|
| 962 |
+
# Create source distribution for `extra_dep`.
|
| 963 |
+
make_trivial_sdist(
|
| 964 |
+
os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 'extra_dep', '1.0'
|
| 965 |
+
)
|
| 966 |
+
# Create source tree for `dep`.
|
| 967 |
+
dep_pkg = os.path.join(temp_dir, 'dep')
|
| 968 |
+
os.mkdir(dep_pkg)
|
| 969 |
+
path.build(
|
| 970 |
+
{
|
| 971 |
+
'setup.py': DALS(
|
| 972 |
+
"""
|
| 973 |
+
import setuptools
|
| 974 |
+
setuptools.setup(
|
| 975 |
+
name='dep', version='2.0',
|
| 976 |
+
extras_require={'extra': ['extra_dep']},
|
| 977 |
+
)
|
| 978 |
+
"""
|
| 979 |
+
),
|
| 980 |
+
'setup.cfg': '',
|
| 981 |
+
},
|
| 982 |
+
prefix=dep_pkg,
|
| 983 |
+
)
|
| 984 |
+
# "Install" dep.
|
| 985 |
+
run_setup(os.path.join(dep_pkg, 'setup.py'), ['dist_info'])
|
| 986 |
+
working_set.add_entry(dep_pkg)
|
| 987 |
+
# Create source tree for test package.
|
| 988 |
+
test_pkg = os.path.join(temp_dir, 'test_pkg')
|
| 989 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 990 |
+
os.mkdir(test_pkg)
|
| 991 |
+
with open(test_setup_py, 'w', encoding="utf-8") as fp:
|
| 992 |
+
fp.write(
|
| 993 |
+
DALS(
|
| 994 |
+
"""
|
| 995 |
+
from setuptools import installer, setup
|
| 996 |
+
setup(setup_requires='dep[extra]')
|
| 997 |
+
"""
|
| 998 |
+
)
|
| 999 |
+
)
|
| 1000 |
+
# Check...
|
| 1001 |
+
monkeypatch.setenv('PIP_FIND_LINKS', str(temp_dir))
|
| 1002 |
+
monkeypatch.setenv('PIP_NO_INDEX', '1')
|
| 1003 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 1004 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 1005 |
+
run_setup(test_setup_py, ['--version'])
|
| 1006 |
+
|
| 1007 |
+
def test_setup_requires_with_distutils_command_dep(self, monkeypatch):
|
| 1008 |
+
"""
|
| 1009 |
+
Use case: ensure build requirements' extras
|
| 1010 |
+
are properly installed and activated.
|
| 1011 |
+
"""
|
| 1012 |
+
with contexts.save_pkg_resources_state():
|
| 1013 |
+
with contexts.tempdir() as temp_dir:
|
| 1014 |
+
# Create source distribution for `extra_dep`.
|
| 1015 |
+
make_sdist(
|
| 1016 |
+
os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'),
|
| 1017 |
+
[
|
| 1018 |
+
(
|
| 1019 |
+
'setup.py',
|
| 1020 |
+
DALS(
|
| 1021 |
+
"""
|
| 1022 |
+
import setuptools
|
| 1023 |
+
setuptools.setup(
|
| 1024 |
+
name='extra_dep',
|
| 1025 |
+
version='1.0',
|
| 1026 |
+
py_modules=['extra_dep'],
|
| 1027 |
+
)
|
| 1028 |
+
"""
|
| 1029 |
+
),
|
| 1030 |
+
),
|
| 1031 |
+
('setup.cfg', ''),
|
| 1032 |
+
('extra_dep.py', ''),
|
| 1033 |
+
],
|
| 1034 |
+
)
|
| 1035 |
+
# Create source tree for `epdep`.
|
| 1036 |
+
dep_pkg = os.path.join(temp_dir, 'epdep')
|
| 1037 |
+
os.mkdir(dep_pkg)
|
| 1038 |
+
path.build(
|
| 1039 |
+
{
|
| 1040 |
+
'setup.py': DALS(
|
| 1041 |
+
"""
|
| 1042 |
+
import setuptools
|
| 1043 |
+
setuptools.setup(
|
| 1044 |
+
name='dep', version='2.0',
|
| 1045 |
+
py_modules=['epcmd'],
|
| 1046 |
+
extras_require={'extra': ['extra_dep']},
|
| 1047 |
+
entry_points='''
|
| 1048 |
+
[distutils.commands]
|
| 1049 |
+
epcmd = epcmd:epcmd [extra]
|
| 1050 |
+
''',
|
| 1051 |
+
)
|
| 1052 |
+
"""
|
| 1053 |
+
),
|
| 1054 |
+
'setup.cfg': '',
|
| 1055 |
+
'epcmd.py': DALS(
|
| 1056 |
+
"""
|
| 1057 |
+
from distutils.command.build_py import build_py
|
| 1058 |
+
|
| 1059 |
+
import extra_dep
|
| 1060 |
+
|
| 1061 |
+
class epcmd(build_py):
|
| 1062 |
+
pass
|
| 1063 |
+
"""
|
| 1064 |
+
),
|
| 1065 |
+
},
|
| 1066 |
+
prefix=dep_pkg,
|
| 1067 |
+
)
|
| 1068 |
+
# "Install" dep.
|
| 1069 |
+
run_setup(os.path.join(dep_pkg, 'setup.py'), ['dist_info'])
|
| 1070 |
+
working_set.add_entry(dep_pkg)
|
| 1071 |
+
# Create source tree for test package.
|
| 1072 |
+
test_pkg = os.path.join(temp_dir, 'test_pkg')
|
| 1073 |
+
test_setup_py = os.path.join(test_pkg, 'setup.py')
|
| 1074 |
+
os.mkdir(test_pkg)
|
| 1075 |
+
with open(test_setup_py, 'w', encoding="utf-8") as fp:
|
| 1076 |
+
fp.write(
|
| 1077 |
+
DALS(
|
| 1078 |
+
"""
|
| 1079 |
+
from setuptools import installer, setup
|
| 1080 |
+
setup(setup_requires='dep[extra]')
|
| 1081 |
+
"""
|
| 1082 |
+
)
|
| 1083 |
+
)
|
| 1084 |
+
# Check...
|
| 1085 |
+
monkeypatch.setenv('PIP_FIND_LINKS', str(temp_dir))
|
| 1086 |
+
monkeypatch.setenv('PIP_NO_INDEX', '1')
|
| 1087 |
+
monkeypatch.setenv('PIP_RETRIES', '0')
|
| 1088 |
+
monkeypatch.setenv('PIP_TIMEOUT', '0')
|
| 1089 |
+
run_setup(test_setup_py, ['epcmd'])
|
| 1090 |
+
|
| 1091 |
+
|
| 1092 |
+
def make_trivial_sdist(dist_path, distname, version):
|
| 1093 |
+
"""
|
| 1094 |
+
Create a simple sdist tarball at dist_path, containing just a simple
|
| 1095 |
+
setup.py.
|
| 1096 |
+
"""
|
| 1097 |
+
|
| 1098 |
+
make_sdist(
|
| 1099 |
+
dist_path,
|
| 1100 |
+
[
|
| 1101 |
+
(
|
| 1102 |
+
'setup.py',
|
| 1103 |
+
DALS(
|
| 1104 |
+
f"""\
|
| 1105 |
+
import setuptools
|
| 1106 |
+
setuptools.setup(
|
| 1107 |
+
name={distname!r},
|
| 1108 |
+
version={version!r}
|
| 1109 |
+
)
|
| 1110 |
+
"""
|
| 1111 |
+
),
|
| 1112 |
+
),
|
| 1113 |
+
('setup.cfg', ''),
|
| 1114 |
+
],
|
| 1115 |
+
)
|
| 1116 |
+
|
| 1117 |
+
|
| 1118 |
+
def make_nspkg_sdist(dist_path, distname, version):
|
| 1119 |
+
"""
|
| 1120 |
+
Make an sdist tarball with distname and version which also contains one
|
| 1121 |
+
package with the same name as distname. The top-level package is
|
| 1122 |
+
designated a namespace package).
|
| 1123 |
+
"""
|
| 1124 |
+
# Assert that the distname contains at least one period
|
| 1125 |
+
assert '.' in distname
|
| 1126 |
+
|
| 1127 |
+
parts = distname.split('.')
|
| 1128 |
+
nspackage = parts[0]
|
| 1129 |
+
|
| 1130 |
+
packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)]
|
| 1131 |
+
|
| 1132 |
+
setup_py = DALS(
|
| 1133 |
+
f"""\
|
| 1134 |
+
import setuptools
|
| 1135 |
+
setuptools.setup(
|
| 1136 |
+
name={distname!r},
|
| 1137 |
+
version={version!r},
|
| 1138 |
+
packages={packages!r},
|
| 1139 |
+
namespace_packages=[{nspackage!r}]
|
| 1140 |
+
)
|
| 1141 |
+
"""
|
| 1142 |
+
)
|
| 1143 |
+
|
| 1144 |
+
init = "__import__('pkg_resources').declare_namespace(__name__)"
|
| 1145 |
+
|
| 1146 |
+
files = [('setup.py', setup_py), (os.path.join(nspackage, '__init__.py'), init)]
|
| 1147 |
+
for package in packages[1:]:
|
| 1148 |
+
filename = os.path.join(*(package.split('.') + ['__init__.py']))
|
| 1149 |
+
files.append((filename, ''))
|
| 1150 |
+
|
| 1151 |
+
make_sdist(dist_path, files)
|
| 1152 |
+
|
| 1153 |
+
|
| 1154 |
+
def make_python_requires_sdist(dist_path, distname, version, python_requires):
|
| 1155 |
+
make_sdist(
|
| 1156 |
+
dist_path,
|
| 1157 |
+
[
|
| 1158 |
+
(
|
| 1159 |
+
'setup.py',
|
| 1160 |
+
DALS(
|
| 1161 |
+
"""\
|
| 1162 |
+
import setuptools
|
| 1163 |
+
setuptools.setup(
|
| 1164 |
+
name={name!r},
|
| 1165 |
+
version={version!r},
|
| 1166 |
+
python_requires={python_requires!r},
|
| 1167 |
+
)
|
| 1168 |
+
"""
|
| 1169 |
+
).format(
|
| 1170 |
+
name=distname, version=version, python_requires=python_requires
|
| 1171 |
+
),
|
| 1172 |
+
),
|
| 1173 |
+
('setup.cfg', ''),
|
| 1174 |
+
],
|
| 1175 |
+
)
|
| 1176 |
+
|
| 1177 |
+
|
| 1178 |
+
def make_sdist(dist_path, files):
|
| 1179 |
+
"""
|
| 1180 |
+
Create a simple sdist tarball at dist_path, containing the files
|
| 1181 |
+
listed in ``files`` as ``(filename, content)`` tuples.
|
| 1182 |
+
"""
|
| 1183 |
+
|
| 1184 |
+
# Distributions with only one file don't play well with pip.
|
| 1185 |
+
assert len(files) > 1
|
| 1186 |
+
with tarfile.open(dist_path, 'w:gz') as dist:
|
| 1187 |
+
for filename, content in files:
|
| 1188 |
+
file_bytes = io.BytesIO(content.encode('utf-8'))
|
| 1189 |
+
file_info = tarfile.TarInfo(name=filename)
|
| 1190 |
+
file_info.size = len(file_bytes.getvalue())
|
| 1191 |
+
file_info.mtime = int(time.time())
|
| 1192 |
+
dist.addfile(file_info, fileobj=file_bytes)
|
| 1193 |
+
|
| 1194 |
+
|
| 1195 |
+
def create_setup_requires_package(
|
| 1196 |
+
path,
|
| 1197 |
+
distname='foobar',
|
| 1198 |
+
version='0.1',
|
| 1199 |
+
make_package=make_trivial_sdist,
|
| 1200 |
+
setup_py_template=None,
|
| 1201 |
+
setup_attrs=None,
|
| 1202 |
+
use_setup_cfg=(),
|
| 1203 |
+
):
|
| 1204 |
+
"""Creates a source tree under path for a trivial test package that has a
|
| 1205 |
+
single requirement in setup_requires--a tarball for that requirement is
|
| 1206 |
+
also created and added to the dependency_links argument.
|
| 1207 |
+
|
| 1208 |
+
``distname`` and ``version`` refer to the name/version of the package that
|
| 1209 |
+
the test package requires via ``setup_requires``. The name of the test
|
| 1210 |
+
package itself is just 'test_pkg'.
|
| 1211 |
+
"""
|
| 1212 |
+
|
| 1213 |
+
normalized_distname = safer_name(distname)
|
| 1214 |
+
test_setup_attrs = {
|
| 1215 |
+
'name': 'test_pkg',
|
| 1216 |
+
'version': '0.0',
|
| 1217 |
+
'setup_requires': [f'{normalized_distname}=={version}'],
|
| 1218 |
+
'dependency_links': [os.path.abspath(path)],
|
| 1219 |
+
}
|
| 1220 |
+
if setup_attrs:
|
| 1221 |
+
test_setup_attrs.update(setup_attrs)
|
| 1222 |
+
|
| 1223 |
+
test_pkg = os.path.join(path, 'test_pkg')
|
| 1224 |
+
os.mkdir(test_pkg)
|
| 1225 |
+
|
| 1226 |
+
# setup.cfg
|
| 1227 |
+
if use_setup_cfg:
|
| 1228 |
+
options = []
|
| 1229 |
+
metadata = []
|
| 1230 |
+
for name in use_setup_cfg:
|
| 1231 |
+
value = test_setup_attrs.pop(name)
|
| 1232 |
+
if name in 'name version'.split():
|
| 1233 |
+
section = metadata
|
| 1234 |
+
else:
|
| 1235 |
+
section = options
|
| 1236 |
+
if isinstance(value, (tuple, list)):
|
| 1237 |
+
value = ';'.join(value)
|
| 1238 |
+
section.append(f'{name}: {value}')
|
| 1239 |
+
test_setup_cfg_contents = DALS(
|
| 1240 |
+
"""
|
| 1241 |
+
[metadata]
|
| 1242 |
+
{metadata}
|
| 1243 |
+
[options]
|
| 1244 |
+
{options}
|
| 1245 |
+
"""
|
| 1246 |
+
).format(
|
| 1247 |
+
options='\n'.join(options),
|
| 1248 |
+
metadata='\n'.join(metadata),
|
| 1249 |
+
)
|
| 1250 |
+
else:
|
| 1251 |
+
test_setup_cfg_contents = ''
|
| 1252 |
+
with open(os.path.join(test_pkg, 'setup.cfg'), 'w', encoding="utf-8") as f:
|
| 1253 |
+
f.write(test_setup_cfg_contents)
|
| 1254 |
+
|
| 1255 |
+
# setup.py
|
| 1256 |
+
if setup_py_template is None:
|
| 1257 |
+
setup_py_template = DALS(
|
| 1258 |
+
"""\
|
| 1259 |
+
import setuptools
|
| 1260 |
+
setuptools.setup(**%r)
|
| 1261 |
+
"""
|
| 1262 |
+
)
|
| 1263 |
+
with open(os.path.join(test_pkg, 'setup.py'), 'w', encoding="utf-8") as f:
|
| 1264 |
+
f.write(setup_py_template % test_setup_attrs)
|
| 1265 |
+
|
| 1266 |
+
foobar_path = os.path.join(path, f'{normalized_distname}-{version}.tar.gz')
|
| 1267 |
+
make_package(foobar_path, distname, version)
|
| 1268 |
+
|
| 1269 |
+
return test_pkg
|
| 1270 |
+
|
| 1271 |
+
|
| 1272 |
+
@pytest.mark.skipif(
|
| 1273 |
+
sys.platform.startswith('java') and ei.is_sh(sys.executable),
|
| 1274 |
+
reason="Test cannot run under java when executable is sh",
|
| 1275 |
+
)
|
| 1276 |
+
class TestScriptHeader:
|
| 1277 |
+
non_ascii_exe = '/Users/José/bin/python'
|
| 1278 |
+
exe_with_spaces = r'C:\Program Files\Python36\python.exe'
|
| 1279 |
+
|
| 1280 |
+
def test_get_script_header(self):
|
| 1281 |
+
expected = f'#!{ei.nt_quote_arg(os.path.normpath(sys.executable))}\n'
|
| 1282 |
+
actual = ei.ScriptWriter.get_header('#!/usr/local/bin/python')
|
| 1283 |
+
assert actual == expected
|
| 1284 |
+
|
| 1285 |
+
def test_get_script_header_args(self):
|
| 1286 |
+
expected = f'#!{ei.nt_quote_arg(os.path.normpath(sys.executable))} -x\n'
|
| 1287 |
+
actual = ei.ScriptWriter.get_header('#!/usr/bin/python -x')
|
| 1288 |
+
assert actual == expected
|
| 1289 |
+
|
| 1290 |
+
def test_get_script_header_non_ascii_exe(self):
|
| 1291 |
+
actual = ei.ScriptWriter.get_header(
|
| 1292 |
+
'#!/usr/bin/python', executable=self.non_ascii_exe
|
| 1293 |
+
)
|
| 1294 |
+
expected = f'#!{self.non_ascii_exe} -x\n'
|
| 1295 |
+
assert actual == expected
|
| 1296 |
+
|
| 1297 |
+
def test_get_script_header_exe_with_spaces(self):
|
| 1298 |
+
actual = ei.ScriptWriter.get_header(
|
| 1299 |
+
'#!/usr/bin/python', executable='"' + self.exe_with_spaces + '"'
|
| 1300 |
+
)
|
| 1301 |
+
expected = f'#!"{self.exe_with_spaces}"\n'
|
| 1302 |
+
assert actual == expected
|
| 1303 |
+
|
| 1304 |
+
|
| 1305 |
+
class TestCommandSpec:
|
| 1306 |
+
def test_custom_launch_command(self):
|
| 1307 |
+
"""
|
| 1308 |
+
Show how a custom CommandSpec could be used to specify a #! executable
|
| 1309 |
+
which takes parameters.
|
| 1310 |
+
"""
|
| 1311 |
+
cmd = ei.CommandSpec(['/usr/bin/env', 'python3'])
|
| 1312 |
+
assert cmd.as_header() == '#!/usr/bin/env python3\n'
|
| 1313 |
+
|
| 1314 |
+
def test_from_param_for_CommandSpec_is_passthrough(self):
|
| 1315 |
+
"""
|
| 1316 |
+
from_param should return an instance of a CommandSpec
|
| 1317 |
+
"""
|
| 1318 |
+
cmd = ei.CommandSpec(['python'])
|
| 1319 |
+
cmd_new = ei.CommandSpec.from_param(cmd)
|
| 1320 |
+
assert cmd is cmd_new
|
| 1321 |
+
|
| 1322 |
+
@mock.patch('sys.executable', TestScriptHeader.exe_with_spaces)
|
| 1323 |
+
@mock.patch.dict(os.environ)
|
| 1324 |
+
def test_from_environment_with_spaces_in_executable(self):
|
| 1325 |
+
os.environ.pop('__PYVENV_LAUNCHER__', None)
|
| 1326 |
+
cmd = ei.CommandSpec.from_environment()
|
| 1327 |
+
assert len(cmd) == 1
|
| 1328 |
+
assert cmd.as_header().startswith('#!"')
|
| 1329 |
+
|
| 1330 |
+
def test_from_simple_string_uses_shlex(self):
|
| 1331 |
+
"""
|
| 1332 |
+
In order to support `executable = /usr/bin/env my-python`, make sure
|
| 1333 |
+
from_param invokes shlex on that input.
|
| 1334 |
+
"""
|
| 1335 |
+
cmd = ei.CommandSpec.from_param('/usr/bin/env my-python')
|
| 1336 |
+
assert len(cmd) == 2
|
| 1337 |
+
assert '"' not in cmd.as_header()
|
| 1338 |
+
|
| 1339 |
+
def test_from_param_raises_expected_error(self) -> None:
|
| 1340 |
+
"""
|
| 1341 |
+
from_param should raise its own TypeError when the argument's type is unsupported
|
| 1342 |
+
"""
|
| 1343 |
+
with pytest.raises(TypeError) as exc_info:
|
| 1344 |
+
ei.CommandSpec.from_param(object()) # type: ignore[arg-type] # We want a type error here
|
| 1345 |
+
assert (
|
| 1346 |
+
str(exc_info.value) == "Argument has an unsupported type <class 'object'>"
|
| 1347 |
+
), exc_info.value
|
| 1348 |
+
|
| 1349 |
+
|
| 1350 |
+
class TestWindowsScriptWriter:
|
| 1351 |
+
def test_header(self):
|
| 1352 |
+
hdr = ei.WindowsScriptWriter.get_header('')
|
| 1353 |
+
assert hdr.startswith('#!')
|
| 1354 |
+
assert hdr.endswith('\n')
|
| 1355 |
+
hdr = hdr.lstrip('#!')
|
| 1356 |
+
hdr = hdr.rstrip('\n')
|
| 1357 |
+
# header should not start with an escaped quote
|
| 1358 |
+
assert not hdr.startswith('\\"')
|
| 1359 |
+
|
| 1360 |
+
|
| 1361 |
+
class VersionStub(NamedTuple):
|
| 1362 |
+
major: int
|
| 1363 |
+
minor: int
|
| 1364 |
+
micro: int
|
| 1365 |
+
releaselevel: str
|
| 1366 |
+
serial: int
|
| 1367 |
+
|
| 1368 |
+
|
| 1369 |
+
def test_use_correct_python_version_string(tmpdir, tmpdir_cwd, monkeypatch):
|
| 1370 |
+
# In issue #3001, easy_install wrongly uses the `python3.1` directory
|
| 1371 |
+
# when the interpreter is `python3.10` and the `--user` option is given.
|
| 1372 |
+
# See pypa/setuptools#3001.
|
| 1373 |
+
dist = Distribution()
|
| 1374 |
+
cmd = dist.get_command_obj('easy_install')
|
| 1375 |
+
cmd.args = ['ok']
|
| 1376 |
+
cmd.optimize = 0
|
| 1377 |
+
cmd.user = True
|
| 1378 |
+
cmd.install_userbase = str(tmpdir)
|
| 1379 |
+
cmd.install_usersite = None
|
| 1380 |
+
install_cmd = dist.get_command_obj('install')
|
| 1381 |
+
install_cmd.install_userbase = str(tmpdir)
|
| 1382 |
+
install_cmd.install_usersite = None
|
| 1383 |
+
|
| 1384 |
+
with monkeypatch.context() as patch, warnings.catch_warnings():
|
| 1385 |
+
warnings.simplefilter("ignore")
|
| 1386 |
+
version = '3.10.1 (main, Dec 21 2021, 09:17:12) [GCC 10.2.1 20210110]'
|
| 1387 |
+
info = VersionStub(3, 10, 1, "final", 0)
|
| 1388 |
+
patch.setattr('site.ENABLE_USER_SITE', True)
|
| 1389 |
+
patch.setattr('sys.version', version)
|
| 1390 |
+
patch.setattr('sys.version_info', info)
|
| 1391 |
+
patch.setattr(cmd, 'create_home_path', mock.Mock())
|
| 1392 |
+
cmd.finalize_options()
|
| 1393 |
+
|
| 1394 |
+
name = "pypy" if hasattr(sys, 'pypy_version_info') else "python"
|
| 1395 |
+
install_dir = cmd.install_dir.lower()
|
| 1396 |
+
|
| 1397 |
+
# In some platforms (e.g. Windows), install_dir is mostly determined
|
| 1398 |
+
# via `sysconfig`, which define constants eagerly at module creation.
|
| 1399 |
+
# This means that monkeypatching `sys.version` to emulate 3.10 for testing
|
| 1400 |
+
# may have no effect.
|
| 1401 |
+
# The safest test here is to rely on the fact that 3.1 is no longer
|
| 1402 |
+
# supported/tested, and make sure that if 'python3.1' ever appears in the string
|
| 1403 |
+
# it is followed by another digit (e.g. 'python3.10').
|
| 1404 |
+
if re.search(name + r'3\.?1', install_dir):
|
| 1405 |
+
assert re.search(name + r'3\.?1\d', install_dir)
|
| 1406 |
+
|
| 1407 |
+
# The following "variables" are used for interpolation in distutils
|
| 1408 |
+
# installation schemes, so it should be fair to treat them as "semi-public",
|
| 1409 |
+
# or at least public enough so we can have a test to make sure they are correct
|
| 1410 |
+
assert cmd.config_vars['py_version'] == '3.10.1'
|
| 1411 |
+
assert cmd.config_vars['py_version_short'] == '3.10'
|
| 1412 |
+
assert cmd.config_vars['py_version_nodot'] == '310'
|
| 1413 |
+
|
| 1414 |
+
|
| 1415 |
+
@pytest.mark.xfail(
|
| 1416 |
+
sys.platform == "darwin",
|
| 1417 |
+
reason="https://github.com/pypa/setuptools/pull/4716#issuecomment-2447624418",
|
| 1418 |
+
)
|
| 1419 |
+
def test_editable_user_and_build_isolation(setup_context, monkeypatch, tmp_path):
|
| 1420 |
+
"""`setup.py develop` should honor `--user` even under build isolation"""
|
| 1421 |
+
|
| 1422 |
+
# == Arrange ==
|
| 1423 |
+
# Pretend that build isolation was enabled
|
| 1424 |
+
# e.g pip sets the environment variable PYTHONNOUSERSITE=1
|
| 1425 |
+
monkeypatch.setattr('site.ENABLE_USER_SITE', False)
|
| 1426 |
+
|
| 1427 |
+
# Patching $HOME for 2 reasons:
|
| 1428 |
+
# 1. setuptools/command/easy_install.py:create_home_path
|
| 1429 |
+
# tries creating directories in $HOME.
|
| 1430 |
+
# Given::
|
| 1431 |
+
# self.config_vars['DESTDIRS'] = (
|
| 1432 |
+
# "/home/user/.pyenv/versions/3.9.10 "
|
| 1433 |
+
# "/home/user/.pyenv/versions/3.9.10/lib "
|
| 1434 |
+
# "/home/user/.pyenv/versions/3.9.10/lib/python3.9 "
|
| 1435 |
+
# "/home/user/.pyenv/versions/3.9.10/lib/python3.9/lib-dynload")
|
| 1436 |
+
# `create_home_path` will::
|
| 1437 |
+
# makedirs(
|
| 1438 |
+
# "/home/user/.pyenv/versions/3.9.10 "
|
| 1439 |
+
# "/home/user/.pyenv/versions/3.9.10/lib "
|
| 1440 |
+
# "/home/user/.pyenv/versions/3.9.10/lib/python3.9 "
|
| 1441 |
+
# "/home/user/.pyenv/versions/3.9.10/lib/python3.9/lib-dynload")
|
| 1442 |
+
#
|
| 1443 |
+
# 2. We are going to force `site` to update site.USER_BASE and site.USER_SITE
|
| 1444 |
+
# To point inside our new home
|
| 1445 |
+
monkeypatch.setenv('HOME', str(tmp_path / '.home'))
|
| 1446 |
+
monkeypatch.setenv('USERPROFILE', str(tmp_path / '.home'))
|
| 1447 |
+
monkeypatch.setenv('APPDATA', str(tmp_path / '.home'))
|
| 1448 |
+
monkeypatch.setattr('site.USER_BASE', None)
|
| 1449 |
+
monkeypatch.setattr('site.USER_SITE', None)
|
| 1450 |
+
user_site = Path(site.getusersitepackages())
|
| 1451 |
+
user_site.mkdir(parents=True, exist_ok=True)
|
| 1452 |
+
|
| 1453 |
+
sys_prefix = tmp_path / '.sys_prefix'
|
| 1454 |
+
sys_prefix.mkdir(parents=True, exist_ok=True)
|
| 1455 |
+
monkeypatch.setattr('sys.prefix', str(sys_prefix))
|
| 1456 |
+
|
| 1457 |
+
setup_script = (
|
| 1458 |
+
"__import__('setuptools').setup(name='aproj', version=42, packages=[])\n"
|
| 1459 |
+
)
|
| 1460 |
+
(tmp_path / "setup.py").write_text(setup_script, encoding="utf-8")
|
| 1461 |
+
|
| 1462 |
+
# == Sanity check ==
|
| 1463 |
+
assert list(sys_prefix.glob("*")) == []
|
| 1464 |
+
assert list(user_site.glob("*")) == []
|
| 1465 |
+
|
| 1466 |
+
# == Act ==
|
| 1467 |
+
run_setup('setup.py', ['develop', '--user'])
|
| 1468 |
+
|
| 1469 |
+
# == Assert ==
|
| 1470 |
+
# Should not install to sys.prefix
|
| 1471 |
+
assert list(sys_prefix.glob("*")) == []
|
| 1472 |
+
# Should install to user site
|
| 1473 |
+
installed = {f.name for f in user_site.glob("*")}
|
| 1474 |
+
# sometimes easy-install.pth is created and sometimes not
|
| 1475 |
+
installed = installed - {"easy-install.pth"}
|
| 1476 |
+
assert installed == {'aproj.egg-link'}
|
venv/lib/python3.11/site-packages/setuptools/tests/test_editable_install.py
ADDED
|
@@ -0,0 +1,1289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import platform
|
| 5 |
+
import stat
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
from copy import deepcopy
|
| 9 |
+
from importlib import import_module
|
| 10 |
+
from importlib.machinery import EXTENSION_SUFFIXES
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from textwrap import dedent
|
| 13 |
+
from typing import Any
|
| 14 |
+
from unittest.mock import Mock
|
| 15 |
+
from uuid import uuid4
|
| 16 |
+
|
| 17 |
+
import jaraco.envs
|
| 18 |
+
import jaraco.path
|
| 19 |
+
import pytest
|
| 20 |
+
from path import Path as _Path
|
| 21 |
+
|
| 22 |
+
from setuptools._importlib import resources as importlib_resources
|
| 23 |
+
from setuptools.command.editable_wheel import (
|
| 24 |
+
_DebuggingTips,
|
| 25 |
+
_encode_pth,
|
| 26 |
+
_find_namespaces,
|
| 27 |
+
_find_package_roots,
|
| 28 |
+
_find_virtual_namespaces,
|
| 29 |
+
_finder_template,
|
| 30 |
+
_LinkTree,
|
| 31 |
+
_TopLevelFinder,
|
| 32 |
+
editable_wheel,
|
| 33 |
+
)
|
| 34 |
+
from setuptools.dist import Distribution
|
| 35 |
+
from setuptools.extension import Extension
|
| 36 |
+
from setuptools.warnings import SetuptoolsDeprecationWarning
|
| 37 |
+
|
| 38 |
+
from . import contexts, namespaces
|
| 39 |
+
|
| 40 |
+
from distutils.core import run_setup
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@pytest.fixture(params=["strict", "lenient"])
|
| 44 |
+
def editable_opts(request):
|
| 45 |
+
if request.param == "strict":
|
| 46 |
+
return ["--config-settings", "editable-mode=strict"]
|
| 47 |
+
return []
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
EXAMPLE = {
|
| 51 |
+
'pyproject.toml': dedent(
|
| 52 |
+
"""\
|
| 53 |
+
[build-system]
|
| 54 |
+
requires = ["setuptools"]
|
| 55 |
+
build-backend = "setuptools.build_meta"
|
| 56 |
+
|
| 57 |
+
[project]
|
| 58 |
+
name = "mypkg"
|
| 59 |
+
version = "3.14159"
|
| 60 |
+
license = {text = "MIT"}
|
| 61 |
+
description = "This is a Python package"
|
| 62 |
+
dynamic = ["readme"]
|
| 63 |
+
classifiers = [
|
| 64 |
+
"Development Status :: 5 - Production/Stable",
|
| 65 |
+
"Intended Audience :: Developers"
|
| 66 |
+
]
|
| 67 |
+
urls = {Homepage = "https://github.com"}
|
| 68 |
+
|
| 69 |
+
[tool.setuptools]
|
| 70 |
+
package-dir = {"" = "src"}
|
| 71 |
+
packages = {find = {where = ["src"]}}
|
| 72 |
+
license-files = ["LICENSE*"]
|
| 73 |
+
|
| 74 |
+
[tool.setuptools.dynamic]
|
| 75 |
+
readme = {file = "README.rst"}
|
| 76 |
+
|
| 77 |
+
[tool.distutils.egg_info]
|
| 78 |
+
tag-build = ".post0"
|
| 79 |
+
"""
|
| 80 |
+
),
|
| 81 |
+
"MANIFEST.in": dedent(
|
| 82 |
+
"""\
|
| 83 |
+
global-include *.py *.txt
|
| 84 |
+
global-exclude *.py[cod]
|
| 85 |
+
prune dist
|
| 86 |
+
prune build
|
| 87 |
+
"""
|
| 88 |
+
).strip(),
|
| 89 |
+
"README.rst": "This is a ``README``",
|
| 90 |
+
"LICENSE.txt": "---- placeholder MIT license ----",
|
| 91 |
+
"src": {
|
| 92 |
+
"mypkg": {
|
| 93 |
+
"__init__.py": dedent(
|
| 94 |
+
"""\
|
| 95 |
+
import sys
|
| 96 |
+
from importlib.metadata import PackageNotFoundError, version
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
__version__ = version(__name__)
|
| 100 |
+
except PackageNotFoundError:
|
| 101 |
+
__version__ = "unknown"
|
| 102 |
+
"""
|
| 103 |
+
),
|
| 104 |
+
"__main__.py": dedent(
|
| 105 |
+
"""\
|
| 106 |
+
from importlib.resources import read_text
|
| 107 |
+
from . import __version__, __name__ as parent
|
| 108 |
+
from .mod import x
|
| 109 |
+
|
| 110 |
+
data = read_text(parent, "data.txt")
|
| 111 |
+
print(__version__, data, x)
|
| 112 |
+
"""
|
| 113 |
+
),
|
| 114 |
+
"mod.py": "x = ''",
|
| 115 |
+
"data.txt": "Hello World",
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")
|
| 125 |
+
@pytest.mark.parametrize(
|
| 126 |
+
"files",
|
| 127 |
+
[
|
| 128 |
+
{**EXAMPLE, "setup.py": SETUP_SCRIPT_STUB},
|
| 129 |
+
EXAMPLE, # No setup.py script
|
| 130 |
+
],
|
| 131 |
+
)
|
| 132 |
+
def test_editable_with_pyproject(tmp_path, venv, files, editable_opts):
|
| 133 |
+
project = tmp_path / "mypkg"
|
| 134 |
+
project.mkdir()
|
| 135 |
+
jaraco.path.build(files, prefix=project)
|
| 136 |
+
|
| 137 |
+
cmd = [
|
| 138 |
+
"python",
|
| 139 |
+
"-m",
|
| 140 |
+
"pip",
|
| 141 |
+
"install",
|
| 142 |
+
"--no-build-isolation", # required to force current version of setuptools
|
| 143 |
+
"-e",
|
| 144 |
+
str(project),
|
| 145 |
+
*editable_opts,
|
| 146 |
+
]
|
| 147 |
+
print(venv.run(cmd))
|
| 148 |
+
|
| 149 |
+
cmd = ["python", "-m", "mypkg"]
|
| 150 |
+
assert venv.run(cmd).strip() == "3.14159.post0 Hello World"
|
| 151 |
+
|
| 152 |
+
(project / "src/mypkg/data.txt").write_text("foobar", encoding="utf-8")
|
| 153 |
+
(project / "src/mypkg/mod.py").write_text("x = 42", encoding="utf-8")
|
| 154 |
+
assert venv.run(cmd).strip() == "3.14159.post0 foobar 42"
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def test_editable_with_flat_layout(tmp_path, venv, editable_opts):
|
| 158 |
+
files = {
|
| 159 |
+
"mypkg": {
|
| 160 |
+
"pyproject.toml": dedent(
|
| 161 |
+
"""\
|
| 162 |
+
[build-system]
|
| 163 |
+
requires = ["setuptools", "wheel"]
|
| 164 |
+
build-backend = "setuptools.build_meta"
|
| 165 |
+
|
| 166 |
+
[project]
|
| 167 |
+
name = "mypkg"
|
| 168 |
+
version = "3.14159"
|
| 169 |
+
|
| 170 |
+
[tool.setuptools]
|
| 171 |
+
packages = ["pkg"]
|
| 172 |
+
py-modules = ["mod"]
|
| 173 |
+
"""
|
| 174 |
+
),
|
| 175 |
+
"pkg": {"__init__.py": "a = 4"},
|
| 176 |
+
"mod.py": "b = 2",
|
| 177 |
+
},
|
| 178 |
+
}
|
| 179 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 180 |
+
project = tmp_path / "mypkg"
|
| 181 |
+
|
| 182 |
+
cmd = [
|
| 183 |
+
"python",
|
| 184 |
+
"-m",
|
| 185 |
+
"pip",
|
| 186 |
+
"install",
|
| 187 |
+
"--no-build-isolation", # required to force current version of setuptools
|
| 188 |
+
"-e",
|
| 189 |
+
str(project),
|
| 190 |
+
*editable_opts,
|
| 191 |
+
]
|
| 192 |
+
print(venv.run(cmd))
|
| 193 |
+
cmd = ["python", "-c", "import pkg, mod; print(pkg.a, mod.b)"]
|
| 194 |
+
assert venv.run(cmd).strip() == "4 2"
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def test_editable_with_single_module(tmp_path, venv, editable_opts):
|
| 198 |
+
files = {
|
| 199 |
+
"mypkg": {
|
| 200 |
+
"pyproject.toml": dedent(
|
| 201 |
+
"""\
|
| 202 |
+
[build-system]
|
| 203 |
+
requires = ["setuptools", "wheel"]
|
| 204 |
+
build-backend = "setuptools.build_meta"
|
| 205 |
+
|
| 206 |
+
[project]
|
| 207 |
+
name = "mod"
|
| 208 |
+
version = "3.14159"
|
| 209 |
+
|
| 210 |
+
[tool.setuptools]
|
| 211 |
+
py-modules = ["mod"]
|
| 212 |
+
"""
|
| 213 |
+
),
|
| 214 |
+
"mod.py": "b = 2",
|
| 215 |
+
},
|
| 216 |
+
}
|
| 217 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 218 |
+
project = tmp_path / "mypkg"
|
| 219 |
+
|
| 220 |
+
cmd = [
|
| 221 |
+
"python",
|
| 222 |
+
"-m",
|
| 223 |
+
"pip",
|
| 224 |
+
"install",
|
| 225 |
+
"--no-build-isolation", # required to force current version of setuptools
|
| 226 |
+
"-e",
|
| 227 |
+
str(project),
|
| 228 |
+
*editable_opts,
|
| 229 |
+
]
|
| 230 |
+
print(venv.run(cmd))
|
| 231 |
+
cmd = ["python", "-c", "import mod; print(mod.b)"]
|
| 232 |
+
assert venv.run(cmd).strip() == "2"
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class TestLegacyNamespaces:
|
| 236 |
+
# legacy => pkg_resources.declare_namespace(...) + setup(namespace_packages=...)
|
| 237 |
+
|
| 238 |
+
def test_nspkg_file_is_unique(self, tmp_path, monkeypatch):
|
| 239 |
+
deprecation = pytest.warns(
|
| 240 |
+
SetuptoolsDeprecationWarning, match=".*namespace_packages parameter.*"
|
| 241 |
+
)
|
| 242 |
+
installation_dir = tmp_path / ".installation_dir"
|
| 243 |
+
installation_dir.mkdir()
|
| 244 |
+
examples = (
|
| 245 |
+
"myns.pkgA",
|
| 246 |
+
"myns.pkgB",
|
| 247 |
+
"myns.n.pkgA",
|
| 248 |
+
"myns.n.pkgB",
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
for name in examples:
|
| 252 |
+
pkg = namespaces.build_namespace_package(tmp_path, name, version="42")
|
| 253 |
+
with deprecation, monkeypatch.context() as ctx:
|
| 254 |
+
ctx.chdir(pkg)
|
| 255 |
+
dist = run_setup("setup.py", stop_after="config")
|
| 256 |
+
cmd = editable_wheel(dist)
|
| 257 |
+
cmd.finalize_options()
|
| 258 |
+
editable_name = cmd.get_finalized_command("dist_info").name
|
| 259 |
+
cmd._install_namespaces(installation_dir, editable_name)
|
| 260 |
+
|
| 261 |
+
files = list(installation_dir.glob("*-nspkg.pth"))
|
| 262 |
+
assert len(files) == len(examples)
|
| 263 |
+
|
| 264 |
+
@pytest.mark.parametrize(
|
| 265 |
+
"impl",
|
| 266 |
+
(
|
| 267 |
+
"pkg_resources",
|
| 268 |
+
# "pkgutil", => does not work
|
| 269 |
+
),
|
| 270 |
+
)
|
| 271 |
+
@pytest.mark.parametrize("ns", ("myns.n",))
|
| 272 |
+
def test_namespace_package_importable(
|
| 273 |
+
self, venv, tmp_path, ns, impl, editable_opts
|
| 274 |
+
):
|
| 275 |
+
"""
|
| 276 |
+
Installing two packages sharing the same namespace, one installed
|
| 277 |
+
naturally using pip or `--single-version-externally-managed`
|
| 278 |
+
and the other installed in editable mode should leave the namespace
|
| 279 |
+
intact and both packages reachable by import.
|
| 280 |
+
(Ported from test_develop).
|
| 281 |
+
"""
|
| 282 |
+
build_system = """\
|
| 283 |
+
[build-system]
|
| 284 |
+
requires = ["setuptools"]
|
| 285 |
+
build-backend = "setuptools.build_meta"
|
| 286 |
+
"""
|
| 287 |
+
pkg_A = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgA", impl=impl)
|
| 288 |
+
pkg_B = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgB", impl=impl)
|
| 289 |
+
(pkg_A / "pyproject.toml").write_text(build_system, encoding="utf-8")
|
| 290 |
+
(pkg_B / "pyproject.toml").write_text(build_system, encoding="utf-8")
|
| 291 |
+
# use pip to install to the target directory
|
| 292 |
+
opts = editable_opts[:]
|
| 293 |
+
opts.append("--no-build-isolation") # force current version of setuptools
|
| 294 |
+
venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
|
| 295 |
+
venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
|
| 296 |
+
venv.run(["python", "-c", f"import {ns}.pkgA; import {ns}.pkgB"])
|
| 297 |
+
# additionally ensure that pkg_resources import works
|
| 298 |
+
venv.run(["python", "-c", "import pkg_resources"])
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
class TestPep420Namespaces:
|
| 302 |
+
def test_namespace_package_importable(self, venv, tmp_path, editable_opts):
|
| 303 |
+
"""
|
| 304 |
+
Installing two packages sharing the same namespace, one installed
|
| 305 |
+
normally using pip and the other installed in editable mode
|
| 306 |
+
should allow importing both packages.
|
| 307 |
+
"""
|
| 308 |
+
pkg_A = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgA')
|
| 309 |
+
pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
|
| 310 |
+
# use pip to install to the target directory
|
| 311 |
+
opts = editable_opts[:]
|
| 312 |
+
opts.append("--no-build-isolation") # force current version of setuptools
|
| 313 |
+
venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
|
| 314 |
+
venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
|
| 315 |
+
venv.run(["python", "-c", "import myns.n.pkgA; import myns.n.pkgB"])
|
| 316 |
+
|
| 317 |
+
def test_namespace_created_via_package_dir(self, venv, tmp_path, editable_opts):
|
| 318 |
+
"""Currently users can create a namespace by tweaking `package_dir`"""
|
| 319 |
+
files = {
|
| 320 |
+
"pkgA": {
|
| 321 |
+
"pyproject.toml": dedent(
|
| 322 |
+
"""\
|
| 323 |
+
[build-system]
|
| 324 |
+
requires = ["setuptools", "wheel"]
|
| 325 |
+
build-backend = "setuptools.build_meta"
|
| 326 |
+
|
| 327 |
+
[project]
|
| 328 |
+
name = "pkgA"
|
| 329 |
+
version = "3.14159"
|
| 330 |
+
|
| 331 |
+
[tool.setuptools]
|
| 332 |
+
package-dir = {"myns.n.pkgA" = "src"}
|
| 333 |
+
"""
|
| 334 |
+
),
|
| 335 |
+
"src": {"__init__.py": "a = 1"},
|
| 336 |
+
},
|
| 337 |
+
}
|
| 338 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 339 |
+
pkg_A = tmp_path / "pkgA"
|
| 340 |
+
pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
|
| 341 |
+
pkg_C = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgC')
|
| 342 |
+
|
| 343 |
+
# use pip to install to the target directory
|
| 344 |
+
opts = editable_opts[:]
|
| 345 |
+
opts.append("--no-build-isolation") # force current version of setuptools
|
| 346 |
+
venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
|
| 347 |
+
venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
|
| 348 |
+
venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts])
|
| 349 |
+
venv.run(["python", "-c", "from myns.n import pkgA, pkgB, pkgC"])
|
| 350 |
+
|
| 351 |
+
def test_namespace_accidental_config_in_lenient_mode(self, venv, tmp_path):
|
| 352 |
+
"""Sometimes users might specify an ``include`` pattern that ignores parent
|
| 353 |
+
packages. In a normal installation this would ignore all modules inside the
|
| 354 |
+
parent packages, and make them namespaces (reported in issue #3504),
|
| 355 |
+
so the editable mode should preserve this behaviour.
|
| 356 |
+
"""
|
| 357 |
+
files = {
|
| 358 |
+
"pkgA": {
|
| 359 |
+
"pyproject.toml": dedent(
|
| 360 |
+
"""\
|
| 361 |
+
[build-system]
|
| 362 |
+
requires = ["setuptools", "wheel"]
|
| 363 |
+
build-backend = "setuptools.build_meta"
|
| 364 |
+
|
| 365 |
+
[project]
|
| 366 |
+
name = "pkgA"
|
| 367 |
+
version = "3.14159"
|
| 368 |
+
|
| 369 |
+
[tool.setuptools]
|
| 370 |
+
packages.find.include = ["mypkg.*"]
|
| 371 |
+
"""
|
| 372 |
+
),
|
| 373 |
+
"mypkg": {
|
| 374 |
+
"__init__.py": "",
|
| 375 |
+
"other.py": "b = 1",
|
| 376 |
+
"n": {
|
| 377 |
+
"__init__.py": "",
|
| 378 |
+
"pkgA.py": "a = 1",
|
| 379 |
+
},
|
| 380 |
+
},
|
| 381 |
+
"MANIFEST.in": EXAMPLE["MANIFEST.in"],
|
| 382 |
+
},
|
| 383 |
+
}
|
| 384 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 385 |
+
pkg_A = tmp_path / "pkgA"
|
| 386 |
+
|
| 387 |
+
# use pip to install to the target directory
|
| 388 |
+
opts = ["--no-build-isolation"] # force current version of setuptools
|
| 389 |
+
venv.run(["python", "-m", "pip", "-v", "install", "-e", str(pkg_A), *opts])
|
| 390 |
+
out = venv.run(["python", "-c", "from mypkg.n import pkgA; print(pkgA.a)"])
|
| 391 |
+
assert out.strip() == "1"
|
| 392 |
+
cmd = """\
|
| 393 |
+
try:
|
| 394 |
+
import mypkg.other
|
| 395 |
+
except ImportError:
|
| 396 |
+
print("mypkg.other not defined")
|
| 397 |
+
"""
|
| 398 |
+
out = venv.run(["python", "-c", dedent(cmd)])
|
| 399 |
+
assert "mypkg.other not defined" in out
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def test_editable_with_prefix(tmp_path, sample_project, editable_opts):
|
| 403 |
+
"""
|
| 404 |
+
Editable install to a prefix should be discoverable.
|
| 405 |
+
"""
|
| 406 |
+
prefix = tmp_path / 'prefix'
|
| 407 |
+
|
| 408 |
+
# figure out where pip will likely install the package
|
| 409 |
+
site_packages_all = [
|
| 410 |
+
prefix / Path(path).relative_to(sys.prefix)
|
| 411 |
+
for path in sys.path
|
| 412 |
+
if 'site-packages' in path and path.startswith(sys.prefix)
|
| 413 |
+
]
|
| 414 |
+
|
| 415 |
+
for sp in site_packages_all:
|
| 416 |
+
sp.mkdir(parents=True)
|
| 417 |
+
|
| 418 |
+
# install workaround
|
| 419 |
+
_addsitedirs(site_packages_all)
|
| 420 |
+
|
| 421 |
+
env = dict(os.environ, PYTHONPATH=os.pathsep.join(map(str, site_packages_all)))
|
| 422 |
+
cmd = [
|
| 423 |
+
sys.executable,
|
| 424 |
+
'-m',
|
| 425 |
+
'pip',
|
| 426 |
+
'install',
|
| 427 |
+
'--editable',
|
| 428 |
+
str(sample_project),
|
| 429 |
+
'--prefix',
|
| 430 |
+
str(prefix),
|
| 431 |
+
'--no-build-isolation',
|
| 432 |
+
*editable_opts,
|
| 433 |
+
]
|
| 434 |
+
subprocess.check_call(cmd, env=env)
|
| 435 |
+
|
| 436 |
+
# now run 'sample' with the prefix on the PYTHONPATH
|
| 437 |
+
bin = 'Scripts' if platform.system() == 'Windows' else 'bin'
|
| 438 |
+
exe = prefix / bin / 'sample'
|
| 439 |
+
subprocess.check_call([exe], env=env)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
class TestFinderTemplate:
|
| 443 |
+
"""This test focus in getting a particular implementation detail right.
|
| 444 |
+
If at some point in time the implementation is changed for something different,
|
| 445 |
+
this test can be modified or even excluded.
|
| 446 |
+
"""
|
| 447 |
+
|
| 448 |
+
def install_finder(self, finder):
|
| 449 |
+
loc = {}
|
| 450 |
+
exec(finder, loc, loc)
|
| 451 |
+
loc["install"]()
|
| 452 |
+
|
| 453 |
+
def test_packages(self, tmp_path):
|
| 454 |
+
files = {
|
| 455 |
+
"src1": {
|
| 456 |
+
"pkg1": {
|
| 457 |
+
"__init__.py": "",
|
| 458 |
+
"subpkg": {"mod1.py": "a = 42"},
|
| 459 |
+
},
|
| 460 |
+
},
|
| 461 |
+
"src2": {"mod2.py": "a = 43"},
|
| 462 |
+
}
|
| 463 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 464 |
+
|
| 465 |
+
mapping = {
|
| 466 |
+
"pkg1": str(tmp_path / "src1/pkg1"),
|
| 467 |
+
"mod2": str(tmp_path / "src2/mod2"),
|
| 468 |
+
}
|
| 469 |
+
template = _finder_template(str(uuid4()), mapping, {})
|
| 470 |
+
|
| 471 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 472 |
+
for mod in ("pkg1", "pkg1.subpkg", "pkg1.subpkg.mod1", "mod2"):
|
| 473 |
+
sys.modules.pop(mod, None)
|
| 474 |
+
|
| 475 |
+
self.install_finder(template)
|
| 476 |
+
mod1 = import_module("pkg1.subpkg.mod1")
|
| 477 |
+
mod2 = import_module("mod2")
|
| 478 |
+
subpkg = import_module("pkg1.subpkg")
|
| 479 |
+
|
| 480 |
+
assert mod1.a == 42
|
| 481 |
+
assert mod2.a == 43
|
| 482 |
+
expected = str((tmp_path / "src1/pkg1/subpkg").resolve())
|
| 483 |
+
assert_path(subpkg, expected)
|
| 484 |
+
|
| 485 |
+
def test_namespace(self, tmp_path):
|
| 486 |
+
files = {"pkg": {"__init__.py": "a = 13", "text.txt": "abc"}}
|
| 487 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 488 |
+
|
| 489 |
+
mapping = {"ns.othername": str(tmp_path / "pkg")}
|
| 490 |
+
namespaces = {"ns": []}
|
| 491 |
+
|
| 492 |
+
template = _finder_template(str(uuid4()), mapping, namespaces)
|
| 493 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 494 |
+
for mod in ("ns", "ns.othername"):
|
| 495 |
+
sys.modules.pop(mod, None)
|
| 496 |
+
|
| 497 |
+
self.install_finder(template)
|
| 498 |
+
pkg = import_module("ns.othername")
|
| 499 |
+
text = importlib_resources.files(pkg) / "text.txt"
|
| 500 |
+
|
| 501 |
+
expected = str((tmp_path / "pkg").resolve())
|
| 502 |
+
assert_path(pkg, expected)
|
| 503 |
+
assert pkg.a == 13
|
| 504 |
+
|
| 505 |
+
# Make sure resources can also be found
|
| 506 |
+
assert text.read_text(encoding="utf-8") == "abc"
|
| 507 |
+
|
| 508 |
+
def test_combine_namespaces(self, tmp_path):
|
| 509 |
+
files = {
|
| 510 |
+
"src1": {"ns": {"pkg1": {"__init__.py": "a = 13"}}},
|
| 511 |
+
"src2": {"ns": {"mod2.py": "b = 37"}},
|
| 512 |
+
}
|
| 513 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 514 |
+
|
| 515 |
+
mapping = {
|
| 516 |
+
"ns.pkgA": str(tmp_path / "src1/ns/pkg1"),
|
| 517 |
+
"ns": str(tmp_path / "src2/ns"),
|
| 518 |
+
}
|
| 519 |
+
namespaces_ = {"ns": [str(tmp_path / "src1"), str(tmp_path / "src2")]}
|
| 520 |
+
template = _finder_template(str(uuid4()), mapping, namespaces_)
|
| 521 |
+
|
| 522 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 523 |
+
for mod in ("ns", "ns.pkgA", "ns.mod2"):
|
| 524 |
+
sys.modules.pop(mod, None)
|
| 525 |
+
|
| 526 |
+
self.install_finder(template)
|
| 527 |
+
pkgA = import_module("ns.pkgA")
|
| 528 |
+
mod2 = import_module("ns.mod2")
|
| 529 |
+
|
| 530 |
+
expected = str((tmp_path / "src1/ns/pkg1").resolve())
|
| 531 |
+
assert_path(pkgA, expected)
|
| 532 |
+
assert pkgA.a == 13
|
| 533 |
+
assert mod2.b == 37
|
| 534 |
+
|
| 535 |
+
def test_combine_namespaces_nested(self, tmp_path):
|
| 536 |
+
"""
|
| 537 |
+
Users may attempt to combine namespace packages in a nested way via
|
| 538 |
+
``package_dir`` as shown in pypa/setuptools#4248.
|
| 539 |
+
"""
|
| 540 |
+
|
| 541 |
+
files = {
|
| 542 |
+
"src": {"my_package": {"my_module.py": "a = 13"}},
|
| 543 |
+
"src2": {"my_package2": {"my_module2.py": "b = 37"}},
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
stack = jaraco.path.DirectoryStack()
|
| 547 |
+
with stack.context(tmp_path):
|
| 548 |
+
jaraco.path.build(files)
|
| 549 |
+
attrs = {
|
| 550 |
+
"script_name": "%PEP 517%",
|
| 551 |
+
"package_dir": {
|
| 552 |
+
"different_name": "src/my_package",
|
| 553 |
+
"different_name.subpkg": "src2/my_package2",
|
| 554 |
+
},
|
| 555 |
+
"packages": ["different_name", "different_name.subpkg"],
|
| 556 |
+
}
|
| 557 |
+
dist = Distribution(attrs)
|
| 558 |
+
finder = _TopLevelFinder(dist, str(uuid4()))
|
| 559 |
+
code = next(v for k, v in finder.get_implementation() if k.endswith(".py"))
|
| 560 |
+
|
| 561 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 562 |
+
for mod in attrs["packages"]:
|
| 563 |
+
sys.modules.pop(mod, None)
|
| 564 |
+
|
| 565 |
+
self.install_finder(code)
|
| 566 |
+
mod1 = import_module("different_name.my_module")
|
| 567 |
+
mod2 = import_module("different_name.subpkg.my_module2")
|
| 568 |
+
|
| 569 |
+
expected = str((tmp_path / "src/my_package/my_module.py").resolve())
|
| 570 |
+
assert str(Path(mod1.__file__).resolve()) == expected
|
| 571 |
+
|
| 572 |
+
expected = str((tmp_path / "src2/my_package2/my_module2.py").resolve())
|
| 573 |
+
assert str(Path(mod2.__file__).resolve()) == expected
|
| 574 |
+
|
| 575 |
+
assert mod1.a == 13
|
| 576 |
+
assert mod2.b == 37
|
| 577 |
+
|
| 578 |
+
def test_dynamic_path_computation(self, tmp_path):
|
| 579 |
+
# Follows the example in PEP 420
|
| 580 |
+
files = {
|
| 581 |
+
"project1": {"parent": {"child": {"one.py": "x = 1"}}},
|
| 582 |
+
"project2": {"parent": {"child": {"two.py": "x = 2"}}},
|
| 583 |
+
"project3": {"parent": {"child": {"three.py": "x = 3"}}},
|
| 584 |
+
}
|
| 585 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 586 |
+
mapping = {}
|
| 587 |
+
namespaces_ = {"parent": [str(tmp_path / "project1/parent")]}
|
| 588 |
+
template = _finder_template(str(uuid4()), mapping, namespaces_)
|
| 589 |
+
|
| 590 |
+
mods = (f"parent.child.{name}" for name in ("one", "two", "three"))
|
| 591 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 592 |
+
for mod in ("parent", "parent.child", "parent.child", *mods):
|
| 593 |
+
sys.modules.pop(mod, None)
|
| 594 |
+
|
| 595 |
+
self.install_finder(template)
|
| 596 |
+
|
| 597 |
+
one = import_module("parent.child.one")
|
| 598 |
+
assert one.x == 1
|
| 599 |
+
|
| 600 |
+
with pytest.raises(ImportError):
|
| 601 |
+
import_module("parent.child.two")
|
| 602 |
+
|
| 603 |
+
sys.path.append(str(tmp_path / "project2"))
|
| 604 |
+
two = import_module("parent.child.two")
|
| 605 |
+
assert two.x == 2
|
| 606 |
+
|
| 607 |
+
with pytest.raises(ImportError):
|
| 608 |
+
import_module("parent.child.three")
|
| 609 |
+
|
| 610 |
+
sys.path.append(str(tmp_path / "project3"))
|
| 611 |
+
three = import_module("parent.child.three")
|
| 612 |
+
assert three.x == 3
|
| 613 |
+
|
| 614 |
+
def test_no_recursion(self, tmp_path):
|
| 615 |
+
# See issue #3550
|
| 616 |
+
files = {
|
| 617 |
+
"pkg": {
|
| 618 |
+
"__init__.py": "from . import pkg",
|
| 619 |
+
},
|
| 620 |
+
}
|
| 621 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 622 |
+
|
| 623 |
+
mapping = {
|
| 624 |
+
"pkg": str(tmp_path / "pkg"),
|
| 625 |
+
}
|
| 626 |
+
template = _finder_template(str(uuid4()), mapping, {})
|
| 627 |
+
|
| 628 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 629 |
+
sys.modules.pop("pkg", None)
|
| 630 |
+
|
| 631 |
+
self.install_finder(template)
|
| 632 |
+
with pytest.raises(ImportError, match="pkg"):
|
| 633 |
+
import_module("pkg")
|
| 634 |
+
|
| 635 |
+
def test_similar_name(self, tmp_path):
|
| 636 |
+
files = {
|
| 637 |
+
"foo": {
|
| 638 |
+
"__init__.py": "",
|
| 639 |
+
"bar": {
|
| 640 |
+
"__init__.py": "",
|
| 641 |
+
},
|
| 642 |
+
},
|
| 643 |
+
}
|
| 644 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 645 |
+
|
| 646 |
+
mapping = {
|
| 647 |
+
"foo": str(tmp_path / "foo"),
|
| 648 |
+
}
|
| 649 |
+
template = _finder_template(str(uuid4()), mapping, {})
|
| 650 |
+
|
| 651 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 652 |
+
sys.modules.pop("foo", None)
|
| 653 |
+
sys.modules.pop("foo.bar", None)
|
| 654 |
+
|
| 655 |
+
self.install_finder(template)
|
| 656 |
+
with pytest.raises(ImportError, match="foobar"):
|
| 657 |
+
import_module("foobar")
|
| 658 |
+
|
| 659 |
+
def test_case_sensitivity(self, tmp_path):
|
| 660 |
+
files = {
|
| 661 |
+
"foo": {
|
| 662 |
+
"__init__.py": "",
|
| 663 |
+
"lowercase.py": "x = 1",
|
| 664 |
+
"bar": {
|
| 665 |
+
"__init__.py": "",
|
| 666 |
+
"lowercase.py": "x = 2",
|
| 667 |
+
},
|
| 668 |
+
},
|
| 669 |
+
}
|
| 670 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 671 |
+
mapping = {
|
| 672 |
+
"foo": str(tmp_path / "foo"),
|
| 673 |
+
}
|
| 674 |
+
template = _finder_template(str(uuid4()), mapping, {})
|
| 675 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 676 |
+
sys.modules.pop("foo", None)
|
| 677 |
+
|
| 678 |
+
self.install_finder(template)
|
| 679 |
+
with pytest.raises(ImportError, match="'FOO'"):
|
| 680 |
+
import_module("FOO")
|
| 681 |
+
|
| 682 |
+
with pytest.raises(ImportError, match="'foo\\.LOWERCASE'"):
|
| 683 |
+
import_module("foo.LOWERCASE")
|
| 684 |
+
|
| 685 |
+
with pytest.raises(ImportError, match="'foo\\.bar\\.Lowercase'"):
|
| 686 |
+
import_module("foo.bar.Lowercase")
|
| 687 |
+
|
| 688 |
+
with pytest.raises(ImportError, match="'foo\\.BAR'"):
|
| 689 |
+
import_module("foo.BAR.lowercase")
|
| 690 |
+
|
| 691 |
+
with pytest.raises(ImportError, match="'FOO'"):
|
| 692 |
+
import_module("FOO.bar.lowercase")
|
| 693 |
+
|
| 694 |
+
mod = import_module("foo.lowercase")
|
| 695 |
+
assert mod.x == 1
|
| 696 |
+
|
| 697 |
+
mod = import_module("foo.bar.lowercase")
|
| 698 |
+
assert mod.x == 2
|
| 699 |
+
|
| 700 |
+
def test_namespace_case_sensitivity(self, tmp_path):
|
| 701 |
+
files = {
|
| 702 |
+
"pkg": {
|
| 703 |
+
"__init__.py": "a = 13",
|
| 704 |
+
"foo": {
|
| 705 |
+
"__init__.py": "b = 37",
|
| 706 |
+
"bar.py": "c = 42",
|
| 707 |
+
},
|
| 708 |
+
},
|
| 709 |
+
}
|
| 710 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 711 |
+
|
| 712 |
+
mapping = {"ns.othername": str(tmp_path / "pkg")}
|
| 713 |
+
namespaces = {"ns": []}
|
| 714 |
+
|
| 715 |
+
template = _finder_template(str(uuid4()), mapping, namespaces)
|
| 716 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 717 |
+
for mod in ("ns", "ns.othername"):
|
| 718 |
+
sys.modules.pop(mod, None)
|
| 719 |
+
|
| 720 |
+
self.install_finder(template)
|
| 721 |
+
pkg = import_module("ns.othername")
|
| 722 |
+
expected = str((tmp_path / "pkg").resolve())
|
| 723 |
+
assert_path(pkg, expected)
|
| 724 |
+
assert pkg.a == 13
|
| 725 |
+
|
| 726 |
+
foo = import_module("ns.othername.foo")
|
| 727 |
+
assert foo.b == 37
|
| 728 |
+
|
| 729 |
+
bar = import_module("ns.othername.foo.bar")
|
| 730 |
+
assert bar.c == 42
|
| 731 |
+
|
| 732 |
+
with pytest.raises(ImportError, match="'NS'"):
|
| 733 |
+
import_module("NS.othername.foo")
|
| 734 |
+
|
| 735 |
+
with pytest.raises(ImportError, match="'ns\\.othername\\.FOO\\'"):
|
| 736 |
+
import_module("ns.othername.FOO")
|
| 737 |
+
|
| 738 |
+
with pytest.raises(ImportError, match="'ns\\.othername\\.foo\\.BAR\\'"):
|
| 739 |
+
import_module("ns.othername.foo.BAR")
|
| 740 |
+
|
| 741 |
+
def test_intermediate_packages(self, tmp_path):
|
| 742 |
+
"""
|
| 743 |
+
The finder should not import ``fullname`` if the intermediate segments
|
| 744 |
+
don't exist (see pypa/setuptools#4019).
|
| 745 |
+
"""
|
| 746 |
+
files = {
|
| 747 |
+
"src": {
|
| 748 |
+
"mypkg": {
|
| 749 |
+
"__init__.py": "",
|
| 750 |
+
"config.py": "a = 13",
|
| 751 |
+
"helloworld.py": "b = 13",
|
| 752 |
+
"components": {
|
| 753 |
+
"config.py": "a = 37",
|
| 754 |
+
},
|
| 755 |
+
},
|
| 756 |
+
}
|
| 757 |
+
}
|
| 758 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 759 |
+
|
| 760 |
+
mapping = {"mypkg": str(tmp_path / "src/mypkg")}
|
| 761 |
+
template = _finder_template(str(uuid4()), mapping, {})
|
| 762 |
+
|
| 763 |
+
with contexts.save_paths(), contexts.save_sys_modules():
|
| 764 |
+
for mod in (
|
| 765 |
+
"mypkg",
|
| 766 |
+
"mypkg.config",
|
| 767 |
+
"mypkg.helloworld",
|
| 768 |
+
"mypkg.components",
|
| 769 |
+
"mypkg.components.config",
|
| 770 |
+
"mypkg.components.helloworld",
|
| 771 |
+
):
|
| 772 |
+
sys.modules.pop(mod, None)
|
| 773 |
+
|
| 774 |
+
self.install_finder(template)
|
| 775 |
+
|
| 776 |
+
config = import_module("mypkg.components.config")
|
| 777 |
+
assert config.a == 37
|
| 778 |
+
|
| 779 |
+
helloworld = import_module("mypkg.helloworld")
|
| 780 |
+
assert helloworld.b == 13
|
| 781 |
+
|
| 782 |
+
with pytest.raises(ImportError):
|
| 783 |
+
import_module("mypkg.components.helloworld")
|
| 784 |
+
|
| 785 |
+
|
| 786 |
+
def test_pkg_roots(tmp_path):
|
| 787 |
+
"""This test focus in getting a particular implementation detail right.
|
| 788 |
+
If at some point in time the implementation is changed for something different,
|
| 789 |
+
this test can be modified or even excluded.
|
| 790 |
+
"""
|
| 791 |
+
files = {
|
| 792 |
+
"a": {"b": {"__init__.py": "ab = 1"}, "__init__.py": "a = 1"},
|
| 793 |
+
"d": {"__init__.py": "d = 1", "e": {"__init__.py": "de = 1"}},
|
| 794 |
+
"f": {"g": {"h": {"__init__.py": "fgh = 1"}}},
|
| 795 |
+
"other": {"__init__.py": "abc = 1"},
|
| 796 |
+
"another": {"__init__.py": "abcxyz = 1"},
|
| 797 |
+
"yet_another": {"__init__.py": "mnopq = 1"},
|
| 798 |
+
}
|
| 799 |
+
jaraco.path.build(files, prefix=tmp_path)
|
| 800 |
+
package_dir = {
|
| 801 |
+
"a.b.c": "other",
|
| 802 |
+
"a.b.c.x.y.z": "another",
|
| 803 |
+
"m.n.o.p.q": "yet_another",
|
| 804 |
+
}
|
| 805 |
+
packages = [
|
| 806 |
+
"a",
|
| 807 |
+
"a.b",
|
| 808 |
+
"a.b.c",
|
| 809 |
+
"a.b.c.x.y",
|
| 810 |
+
"a.b.c.x.y.z",
|
| 811 |
+
"d",
|
| 812 |
+
"d.e",
|
| 813 |
+
"f",
|
| 814 |
+
"f.g",
|
| 815 |
+
"f.g.h",
|
| 816 |
+
"m.n.o.p.q",
|
| 817 |
+
]
|
| 818 |
+
roots = _find_package_roots(packages, package_dir, tmp_path)
|
| 819 |
+
assert roots == {
|
| 820 |
+
"a": str(tmp_path / "a"),
|
| 821 |
+
"a.b.c": str(tmp_path / "other"),
|
| 822 |
+
"a.b.c.x.y.z": str(tmp_path / "another"),
|
| 823 |
+
"d": str(tmp_path / "d"),
|
| 824 |
+
"f": str(tmp_path / "f"),
|
| 825 |
+
"m.n.o.p.q": str(tmp_path / "yet_another"),
|
| 826 |
+
}
|
| 827 |
+
|
| 828 |
+
ns = set(dict(_find_namespaces(packages, roots)))
|
| 829 |
+
assert ns == {"f", "f.g"}
|
| 830 |
+
|
| 831 |
+
ns = set(_find_virtual_namespaces(roots))
|
| 832 |
+
assert ns == {"a.b", "a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"}
|
| 833 |
+
|
| 834 |
+
|
| 835 |
+
class TestOverallBehaviour:
|
| 836 |
+
PYPROJECT = """\
|
| 837 |
+
[build-system]
|
| 838 |
+
requires = ["setuptools"]
|
| 839 |
+
build-backend = "setuptools.build_meta"
|
| 840 |
+
|
| 841 |
+
[project]
|
| 842 |
+
name = "mypkg"
|
| 843 |
+
version = "3.14159"
|
| 844 |
+
"""
|
| 845 |
+
|
| 846 |
+
# Any: Would need a TypedDict. Keep it simple for tests
|
| 847 |
+
FLAT_LAYOUT: dict[str, Any] = {
|
| 848 |
+
"pyproject.toml": dedent(PYPROJECT),
|
| 849 |
+
"MANIFEST.in": EXAMPLE["MANIFEST.in"],
|
| 850 |
+
"otherfile.py": "",
|
| 851 |
+
"mypkg": {
|
| 852 |
+
"__init__.py": "",
|
| 853 |
+
"mod1.py": "var = 42",
|
| 854 |
+
"subpackage": {
|
| 855 |
+
"__init__.py": "",
|
| 856 |
+
"mod2.py": "var = 13",
|
| 857 |
+
"resource_file.txt": "resource 39",
|
| 858 |
+
},
|
| 859 |
+
},
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
EXAMPLES = {
|
| 863 |
+
"flat-layout": FLAT_LAYOUT,
|
| 864 |
+
"src-layout": {
|
| 865 |
+
"pyproject.toml": dedent(PYPROJECT),
|
| 866 |
+
"MANIFEST.in": EXAMPLE["MANIFEST.in"],
|
| 867 |
+
"otherfile.py": "",
|
| 868 |
+
"src": {"mypkg": FLAT_LAYOUT["mypkg"]},
|
| 869 |
+
},
|
| 870 |
+
"custom-layout": {
|
| 871 |
+
"pyproject.toml": dedent(PYPROJECT)
|
| 872 |
+
+ dedent(
|
| 873 |
+
"""\
|
| 874 |
+
[tool.setuptools]
|
| 875 |
+
packages = ["mypkg", "mypkg.subpackage"]
|
| 876 |
+
|
| 877 |
+
[tool.setuptools.package-dir]
|
| 878 |
+
"mypkg.subpackage" = "other"
|
| 879 |
+
"""
|
| 880 |
+
),
|
| 881 |
+
"MANIFEST.in": EXAMPLE["MANIFEST.in"],
|
| 882 |
+
"otherfile.py": "",
|
| 883 |
+
"mypkg": {
|
| 884 |
+
"__init__.py": "",
|
| 885 |
+
"mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],
|
| 886 |
+
},
|
| 887 |
+
"other": FLAT_LAYOUT["mypkg"]["subpackage"],
|
| 888 |
+
},
|
| 889 |
+
"namespace": {
|
| 890 |
+
"pyproject.toml": dedent(PYPROJECT),
|
| 891 |
+
"MANIFEST.in": EXAMPLE["MANIFEST.in"],
|
| 892 |
+
"otherfile.py": "",
|
| 893 |
+
"src": {
|
| 894 |
+
"mypkg": {
|
| 895 |
+
"mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],
|
| 896 |
+
"subpackage": FLAT_LAYOUT["mypkg"]["subpackage"],
|
| 897 |
+
},
|
| 898 |
+
},
|
| 899 |
+
},
|
| 900 |
+
}
|
| 901 |
+
|
| 902 |
+
@pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")
|
| 903 |
+
@pytest.mark.parametrize("layout", EXAMPLES.keys())
|
| 904 |
+
def test_editable_install(self, tmp_path, venv, layout, editable_opts):
|
| 905 |
+
project, _ = install_project(
|
| 906 |
+
"mypkg", venv, tmp_path, self.EXAMPLES[layout], *editable_opts
|
| 907 |
+
)
|
| 908 |
+
|
| 909 |
+
# Ensure stray files are not importable
|
| 910 |
+
cmd_import_error = """\
|
| 911 |
+
try:
|
| 912 |
+
import otherfile
|
| 913 |
+
except ImportError as ex:
|
| 914 |
+
print(ex)
|
| 915 |
+
"""
|
| 916 |
+
out = venv.run(["python", "-c", dedent(cmd_import_error)])
|
| 917 |
+
assert "No module named 'otherfile'" in out
|
| 918 |
+
|
| 919 |
+
# Ensure the modules are importable
|
| 920 |
+
cmd_get_vars = """\
|
| 921 |
+
import mypkg, mypkg.mod1, mypkg.subpackage.mod2
|
| 922 |
+
print(mypkg.mod1.var, mypkg.subpackage.mod2.var)
|
| 923 |
+
"""
|
| 924 |
+
out = venv.run(["python", "-c", dedent(cmd_get_vars)])
|
| 925 |
+
assert "42 13" in out
|
| 926 |
+
|
| 927 |
+
# Ensure resources are reachable
|
| 928 |
+
cmd_get_resource = """\
|
| 929 |
+
import mypkg.subpackage
|
| 930 |
+
from setuptools._importlib import resources as importlib_resources
|
| 931 |
+
text = importlib_resources.files(mypkg.subpackage) / "resource_file.txt"
|
| 932 |
+
print(text.read_text(encoding="utf-8"))
|
| 933 |
+
"""
|
| 934 |
+
out = venv.run(["python", "-c", dedent(cmd_get_resource)])
|
| 935 |
+
assert "resource 39" in out
|
| 936 |
+
|
| 937 |
+
# Ensure files are editable
|
| 938 |
+
mod1 = next(project.glob("**/mod1.py"))
|
| 939 |
+
mod2 = next(project.glob("**/mod2.py"))
|
| 940 |
+
resource_file = next(project.glob("**/resource_file.txt"))
|
| 941 |
+
|
| 942 |
+
mod1.write_text("var = 17", encoding="utf-8")
|
| 943 |
+
mod2.write_text("var = 781", encoding="utf-8")
|
| 944 |
+
resource_file.write_text("resource 374", encoding="utf-8")
|
| 945 |
+
|
| 946 |
+
out = venv.run(["python", "-c", dedent(cmd_get_vars)])
|
| 947 |
+
assert "42 13" not in out
|
| 948 |
+
assert "17 781" in out
|
| 949 |
+
|
| 950 |
+
out = venv.run(["python", "-c", dedent(cmd_get_resource)])
|
| 951 |
+
assert "resource 39" not in out
|
| 952 |
+
assert "resource 374" in out
|
| 953 |
+
|
| 954 |
+
|
| 955 |
+
class TestLinkTree:
|
| 956 |
+
FILES = deepcopy(TestOverallBehaviour.EXAMPLES["src-layout"])
|
| 957 |
+
FILES["pyproject.toml"] += dedent(
|
| 958 |
+
"""\
|
| 959 |
+
[tool.setuptools]
|
| 960 |
+
# Temporary workaround: both `include-package-data` and `package-data` configs
|
| 961 |
+
# can be removed after #3260 is fixed.
|
| 962 |
+
include-package-data = false
|
| 963 |
+
package-data = {"*" = ["*.txt"]}
|
| 964 |
+
|
| 965 |
+
[tool.setuptools.packages.find]
|
| 966 |
+
where = ["src"]
|
| 967 |
+
exclude = ["*.subpackage*"]
|
| 968 |
+
"""
|
| 969 |
+
)
|
| 970 |
+
FILES["src"]["mypkg"]["resource.not_in_manifest"] = "abc"
|
| 971 |
+
|
| 972 |
+
def test_generated_tree(self, tmp_path):
|
| 973 |
+
jaraco.path.build(self.FILES, prefix=tmp_path)
|
| 974 |
+
|
| 975 |
+
with _Path(tmp_path):
|
| 976 |
+
name = "mypkg-3.14159"
|
| 977 |
+
dist = Distribution({"script_name": "%PEP 517%"})
|
| 978 |
+
dist.parse_config_files()
|
| 979 |
+
|
| 980 |
+
wheel = Mock()
|
| 981 |
+
aux = tmp_path / ".aux"
|
| 982 |
+
build = tmp_path / ".build"
|
| 983 |
+
aux.mkdir()
|
| 984 |
+
build.mkdir()
|
| 985 |
+
|
| 986 |
+
build_py = dist.get_command_obj("build_py")
|
| 987 |
+
build_py.editable_mode = True
|
| 988 |
+
build_py.build_lib = str(build)
|
| 989 |
+
build_py.ensure_finalized()
|
| 990 |
+
outputs = build_py.get_outputs()
|
| 991 |
+
output_mapping = build_py.get_output_mapping()
|
| 992 |
+
|
| 993 |
+
make_tree = _LinkTree(dist, name, aux, build)
|
| 994 |
+
make_tree(wheel, outputs, output_mapping)
|
| 995 |
+
|
| 996 |
+
mod1 = next(aux.glob("**/mod1.py"))
|
| 997 |
+
expected = tmp_path / "src/mypkg/mod1.py"
|
| 998 |
+
assert_link_to(mod1, expected)
|
| 999 |
+
|
| 1000 |
+
assert next(aux.glob("**/subpackage"), None) is None
|
| 1001 |
+
assert next(aux.glob("**/mod2.py"), None) is None
|
| 1002 |
+
assert next(aux.glob("**/resource_file.txt"), None) is None
|
| 1003 |
+
|
| 1004 |
+
assert next(aux.glob("**/resource.not_in_manifest"), None) is None
|
| 1005 |
+
|
| 1006 |
+
def test_strict_install(self, tmp_path, venv):
|
| 1007 |
+
opts = ["--config-settings", "editable-mode=strict"]
|
| 1008 |
+
install_project("mypkg", venv, tmp_path, self.FILES, *opts)
|
| 1009 |
+
|
| 1010 |
+
out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
|
| 1011 |
+
assert "42" in out
|
| 1012 |
+
|
| 1013 |
+
# Ensure packages excluded from distribution are not importable
|
| 1014 |
+
cmd_import_error = """\
|
| 1015 |
+
try:
|
| 1016 |
+
from mypkg import subpackage
|
| 1017 |
+
except ImportError as ex:
|
| 1018 |
+
print(ex)
|
| 1019 |
+
"""
|
| 1020 |
+
out = venv.run(["python", "-c", dedent(cmd_import_error)])
|
| 1021 |
+
assert "cannot import name 'subpackage'" in out
|
| 1022 |
+
|
| 1023 |
+
# Ensure resource files excluded from distribution are not reachable
|
| 1024 |
+
cmd_get_resource = """\
|
| 1025 |
+
import mypkg
|
| 1026 |
+
from setuptools._importlib import resources as importlib_resources
|
| 1027 |
+
try:
|
| 1028 |
+
text = importlib_resources.files(mypkg) / "resource.not_in_manifest"
|
| 1029 |
+
print(text.read_text(encoding="utf-8"))
|
| 1030 |
+
except FileNotFoundError as ex:
|
| 1031 |
+
print(ex)
|
| 1032 |
+
"""
|
| 1033 |
+
out = venv.run(["python", "-c", dedent(cmd_get_resource)])
|
| 1034 |
+
assert "No such file or directory" in out
|
| 1035 |
+
assert "resource.not_in_manifest" in out
|
| 1036 |
+
|
| 1037 |
+
|
| 1038 |
+
@pytest.mark.filterwarnings("ignore:.*compat.*:setuptools.SetuptoolsDeprecationWarning")
|
| 1039 |
+
def test_compat_install(tmp_path, venv):
|
| 1040 |
+
# TODO: Remove `compat` after Dec/2022.
|
| 1041 |
+
opts = ["--config-settings", "editable-mode=compat"]
|
| 1042 |
+
files = TestOverallBehaviour.EXAMPLES["custom-layout"]
|
| 1043 |
+
install_project("mypkg", venv, tmp_path, files, *opts)
|
| 1044 |
+
|
| 1045 |
+
out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
|
| 1046 |
+
assert "42" in out
|
| 1047 |
+
|
| 1048 |
+
expected_path = comparable_path(str(tmp_path))
|
| 1049 |
+
|
| 1050 |
+
# Compatible behaviour will make spurious modules and excluded
|
| 1051 |
+
# files importable directly from the original path
|
| 1052 |
+
for cmd in (
|
| 1053 |
+
"import otherfile; print(otherfile)",
|
| 1054 |
+
"import other; print(other)",
|
| 1055 |
+
"import mypkg; print(mypkg)",
|
| 1056 |
+
):
|
| 1057 |
+
out = comparable_path(venv.run(["python", "-c", cmd]))
|
| 1058 |
+
assert expected_path in out
|
| 1059 |
+
|
| 1060 |
+
# Compatible behaviour will not consider custom mappings
|
| 1061 |
+
cmd = """\
|
| 1062 |
+
try:
|
| 1063 |
+
from mypkg import subpackage;
|
| 1064 |
+
except ImportError as ex:
|
| 1065 |
+
print(ex)
|
| 1066 |
+
"""
|
| 1067 |
+
out = venv.run(["python", "-c", dedent(cmd)])
|
| 1068 |
+
assert "cannot import name 'subpackage'" in out
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
+
def test_pbr_integration(tmp_path, venv, editable_opts):
|
| 1072 |
+
"""Ensure editable installs work with pbr, issue #3500"""
|
| 1073 |
+
files = {
|
| 1074 |
+
"pyproject.toml": dedent(
|
| 1075 |
+
"""\
|
| 1076 |
+
[build-system]
|
| 1077 |
+
requires = ["setuptools"]
|
| 1078 |
+
build-backend = "setuptools.build_meta"
|
| 1079 |
+
"""
|
| 1080 |
+
),
|
| 1081 |
+
"setup.py": dedent(
|
| 1082 |
+
"""\
|
| 1083 |
+
__import__('setuptools').setup(
|
| 1084 |
+
pbr=True,
|
| 1085 |
+
setup_requires=["pbr"],
|
| 1086 |
+
)
|
| 1087 |
+
"""
|
| 1088 |
+
),
|
| 1089 |
+
"setup.cfg": dedent(
|
| 1090 |
+
"""\
|
| 1091 |
+
[metadata]
|
| 1092 |
+
name = mypkg
|
| 1093 |
+
|
| 1094 |
+
[files]
|
| 1095 |
+
packages =
|
| 1096 |
+
mypkg
|
| 1097 |
+
"""
|
| 1098 |
+
),
|
| 1099 |
+
"mypkg": {
|
| 1100 |
+
"__init__.py": "",
|
| 1101 |
+
"hello.py": "print('Hello world!')",
|
| 1102 |
+
},
|
| 1103 |
+
"other": {"test.txt": "Another file in here."},
|
| 1104 |
+
}
|
| 1105 |
+
venv.run(["python", "-m", "pip", "install", "pbr"])
|
| 1106 |
+
|
| 1107 |
+
with contexts.environment(PBR_VERSION="0.42"):
|
| 1108 |
+
install_project("mypkg", venv, tmp_path, files, *editable_opts)
|
| 1109 |
+
|
| 1110 |
+
out = venv.run(["python", "-c", "import mypkg.hello"])
|
| 1111 |
+
assert "Hello world!" in out
|
| 1112 |
+
|
| 1113 |
+
|
| 1114 |
+
class TestCustomBuildPy:
|
| 1115 |
+
"""
|
| 1116 |
+
Issue #3501 indicates that some plugins/customizations might rely on:
|
| 1117 |
+
|
| 1118 |
+
1. ``build_py`` not running
|
| 1119 |
+
2. ``build_py`` always copying files to ``build_lib``
|
| 1120 |
+
|
| 1121 |
+
During the transition period setuptools should prevent potential errors from
|
| 1122 |
+
happening due to those assumptions.
|
| 1123 |
+
"""
|
| 1124 |
+
|
| 1125 |
+
# TODO: Remove tests after _run_build_steps is removed.
|
| 1126 |
+
|
| 1127 |
+
FILES = {
|
| 1128 |
+
**TestOverallBehaviour.EXAMPLES["flat-layout"],
|
| 1129 |
+
"setup.py": dedent(
|
| 1130 |
+
"""\
|
| 1131 |
+
import pathlib
|
| 1132 |
+
from setuptools import setup
|
| 1133 |
+
from setuptools.command.build_py import build_py as orig
|
| 1134 |
+
|
| 1135 |
+
class my_build_py(orig):
|
| 1136 |
+
def run(self):
|
| 1137 |
+
super().run()
|
| 1138 |
+
raise ValueError("TEST_RAISE")
|
| 1139 |
+
|
| 1140 |
+
setup(cmdclass={"build_py": my_build_py})
|
| 1141 |
+
"""
|
| 1142 |
+
),
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
def test_safeguarded_from_errors(self, tmp_path, venv):
|
| 1146 |
+
"""Ensure that errors in custom build_py are reported as warnings"""
|
| 1147 |
+
# Warnings should show up
|
| 1148 |
+
_, out = install_project("mypkg", venv, tmp_path, self.FILES)
|
| 1149 |
+
assert "SetuptoolsDeprecationWarning" in out
|
| 1150 |
+
assert "ValueError: TEST_RAISE" in out
|
| 1151 |
+
# but installation should be successful
|
| 1152 |
+
out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
|
| 1153 |
+
assert "42" in out
|
| 1154 |
+
|
| 1155 |
+
|
| 1156 |
+
class TestCustomBuildWheel:
|
| 1157 |
+
def install_custom_build_wheel(self, dist):
|
| 1158 |
+
bdist_wheel_cls = dist.get_command_class("bdist_wheel")
|
| 1159 |
+
|
| 1160 |
+
class MyBdistWheel(bdist_wheel_cls):
|
| 1161 |
+
def get_tag(self):
|
| 1162 |
+
# In issue #3513, we can see that some extensions may try to access
|
| 1163 |
+
# the `plat_name` property in bdist_wheel
|
| 1164 |
+
if self.plat_name.startswith("macosx-"):
|
| 1165 |
+
_ = "macOS platform"
|
| 1166 |
+
return super().get_tag()
|
| 1167 |
+
|
| 1168 |
+
dist.cmdclass["bdist_wheel"] = MyBdistWheel
|
| 1169 |
+
|
| 1170 |
+
def test_access_plat_name(self, tmpdir_cwd):
|
| 1171 |
+
# Even when a custom bdist_wheel tries to access plat_name the build should
|
| 1172 |
+
# be successful
|
| 1173 |
+
jaraco.path.build({"module.py": "x = 42"})
|
| 1174 |
+
dist = Distribution()
|
| 1175 |
+
dist.script_name = "setup.py"
|
| 1176 |
+
dist.set_defaults()
|
| 1177 |
+
self.install_custom_build_wheel(dist)
|
| 1178 |
+
cmd = editable_wheel(dist)
|
| 1179 |
+
cmd.ensure_finalized()
|
| 1180 |
+
cmd.run()
|
| 1181 |
+
wheel_file = str(next(Path().glob('dist/*.whl')))
|
| 1182 |
+
assert "editable" in wheel_file
|
| 1183 |
+
|
| 1184 |
+
|
| 1185 |
+
class TestCustomBuildExt:
|
| 1186 |
+
def install_custom_build_ext_distutils(self, dist):
|
| 1187 |
+
from distutils.command.build_ext import build_ext as build_ext_cls
|
| 1188 |
+
|
| 1189 |
+
class MyBuildExt(build_ext_cls):
|
| 1190 |
+
pass
|
| 1191 |
+
|
| 1192 |
+
dist.cmdclass["build_ext"] = MyBuildExt
|
| 1193 |
+
|
| 1194 |
+
@pytest.mark.skipif(
|
| 1195 |
+
sys.platform != "linux", reason="compilers may fail without correct setup"
|
| 1196 |
+
)
|
| 1197 |
+
def test_distutils_leave_inplace_files(self, tmpdir_cwd):
|
| 1198 |
+
jaraco.path.build({"module.c": ""})
|
| 1199 |
+
attrs = {
|
| 1200 |
+
"ext_modules": [Extension("module", ["module.c"])],
|
| 1201 |
+
}
|
| 1202 |
+
dist = Distribution(attrs)
|
| 1203 |
+
dist.script_name = "setup.py"
|
| 1204 |
+
dist.set_defaults()
|
| 1205 |
+
self.install_custom_build_ext_distutils(dist)
|
| 1206 |
+
cmd = editable_wheel(dist)
|
| 1207 |
+
cmd.ensure_finalized()
|
| 1208 |
+
cmd.run()
|
| 1209 |
+
wheel_file = str(next(Path().glob('dist/*.whl')))
|
| 1210 |
+
assert "editable" in wheel_file
|
| 1211 |
+
files = [p for p in Path().glob("module.*") if p.suffix != ".c"]
|
| 1212 |
+
assert len(files) == 1
|
| 1213 |
+
name = files[0].name
|
| 1214 |
+
assert any(name.endswith(ext) for ext in EXTENSION_SUFFIXES)
|
| 1215 |
+
|
| 1216 |
+
|
| 1217 |
+
def test_debugging_tips(tmpdir_cwd, monkeypatch):
|
| 1218 |
+
"""Make sure to display useful debugging tips to the user."""
|
| 1219 |
+
jaraco.path.build({"module.py": "x = 42"})
|
| 1220 |
+
dist = Distribution()
|
| 1221 |
+
dist.script_name = "setup.py"
|
| 1222 |
+
dist.set_defaults()
|
| 1223 |
+
cmd = editable_wheel(dist)
|
| 1224 |
+
cmd.ensure_finalized()
|
| 1225 |
+
|
| 1226 |
+
SimulatedErr = type("SimulatedErr", (Exception,), {})
|
| 1227 |
+
simulated_failure = Mock(side_effect=SimulatedErr())
|
| 1228 |
+
monkeypatch.setattr(cmd, "get_finalized_command", simulated_failure)
|
| 1229 |
+
|
| 1230 |
+
expected_msg = "following steps are recommended to help debug"
|
| 1231 |
+
with pytest.raises(SimulatedErr), pytest.warns(_DebuggingTips, match=expected_msg):
|
| 1232 |
+
cmd.run()
|
| 1233 |
+
|
| 1234 |
+
|
| 1235 |
+
@pytest.mark.filterwarnings("error")
|
| 1236 |
+
def test_encode_pth():
|
| 1237 |
+
"""Ensure _encode_pth function does not produce encoding warnings"""
|
| 1238 |
+
content = _encode_pth("tkmilan_ç_utf8") # no warnings (would be turned into errors)
|
| 1239 |
+
assert isinstance(content, bytes)
|
| 1240 |
+
|
| 1241 |
+
|
| 1242 |
+
def install_project(name, venv, tmp_path, files, *opts):
|
| 1243 |
+
project = tmp_path / name
|
| 1244 |
+
project.mkdir()
|
| 1245 |
+
jaraco.path.build(files, prefix=project)
|
| 1246 |
+
opts = [*opts, "--no-build-isolation"] # force current version of setuptools
|
| 1247 |
+
out = venv.run(
|
| 1248 |
+
["python", "-m", "pip", "-v", "install", "-e", str(project), *opts],
|
| 1249 |
+
stderr=subprocess.STDOUT,
|
| 1250 |
+
)
|
| 1251 |
+
return project, out
|
| 1252 |
+
|
| 1253 |
+
|
| 1254 |
+
def _addsitedirs(new_dirs):
|
| 1255 |
+
"""To use this function, it is necessary to insert new_dir in front of sys.path.
|
| 1256 |
+
The Python process will try to import a ``sitecustomize`` module on startup.
|
| 1257 |
+
If we manipulate sys.path/PYTHONPATH, we can force it to run our code,
|
| 1258 |
+
which invokes ``addsitedir`` and ensure ``.pth`` files are loaded.
|
| 1259 |
+
"""
|
| 1260 |
+
content = '\n'.join(
|
| 1261 |
+
("import site",)
|
| 1262 |
+
+ tuple(f"site.addsitedir({os.fspath(new_dir)!r})" for new_dir in new_dirs)
|
| 1263 |
+
)
|
| 1264 |
+
(new_dirs[0] / "sitecustomize.py").write_text(content, encoding="utf-8")
|
| 1265 |
+
|
| 1266 |
+
|
| 1267 |
+
# ---- Assertion Helpers ----
|
| 1268 |
+
|
| 1269 |
+
|
| 1270 |
+
def assert_path(pkg, expected):
|
| 1271 |
+
# __path__ is not guaranteed to exist, so we have to account for that
|
| 1272 |
+
if pkg.__path__:
|
| 1273 |
+
path = next(iter(pkg.__path__), None)
|
| 1274 |
+
if path:
|
| 1275 |
+
assert str(Path(path).resolve()) == expected
|
| 1276 |
+
|
| 1277 |
+
|
| 1278 |
+
def assert_link_to(file: Path, other: Path) -> None:
|
| 1279 |
+
if file.is_symlink():
|
| 1280 |
+
assert str(file.resolve()) == str(other.resolve())
|
| 1281 |
+
else:
|
| 1282 |
+
file_stat = file.stat()
|
| 1283 |
+
other_stat = other.stat()
|
| 1284 |
+
assert file_stat[stat.ST_INO] == other_stat[stat.ST_INO]
|
| 1285 |
+
assert file_stat[stat.ST_DEV] == other_stat[stat.ST_DEV]
|
| 1286 |
+
|
| 1287 |
+
|
| 1288 |
+
def comparable_path(str_with_path: str) -> str:
|
| 1289 |
+
return str_with_path.lower().replace(os.sep, "/").replace("//", "/")
|
venv/lib/python3.11/site-packages/setuptools/tests/test_egg_info.py
ADDED
|
@@ -0,0 +1,1305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import ast
|
| 4 |
+
import glob
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
import stat
|
| 8 |
+
import sys
|
| 9 |
+
import time
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from unittest import mock
|
| 12 |
+
|
| 13 |
+
import pytest
|
| 14 |
+
from jaraco import path
|
| 15 |
+
|
| 16 |
+
from setuptools import errors
|
| 17 |
+
from setuptools.command.egg_info import egg_info, manifest_maker, write_entries
|
| 18 |
+
from setuptools.dist import Distribution
|
| 19 |
+
|
| 20 |
+
from . import contexts, environment
|
| 21 |
+
from .textwrap import DALS
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Environment(str):
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@pytest.fixture
|
| 29 |
+
def env():
|
| 30 |
+
with contexts.tempdir(prefix='setuptools-test.') as env_dir:
|
| 31 |
+
env = Environment(env_dir)
|
| 32 |
+
os.chmod(env_dir, stat.S_IRWXU)
|
| 33 |
+
subs = 'home', 'lib', 'scripts', 'data', 'egg-base'
|
| 34 |
+
env.paths = dict((dirname, os.path.join(env_dir, dirname)) for dirname in subs)
|
| 35 |
+
list(map(os.mkdir, env.paths.values()))
|
| 36 |
+
path.build({
|
| 37 |
+
env.paths['home']: {
|
| 38 |
+
'.pydistutils.cfg': DALS(
|
| 39 |
+
"""
|
| 40 |
+
[egg_info]
|
| 41 |
+
egg-base = {egg-base}
|
| 42 |
+
""".format(**env.paths)
|
| 43 |
+
)
|
| 44 |
+
}
|
| 45 |
+
})
|
| 46 |
+
yield env
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class TestEggInfo:
|
| 50 |
+
setup_script = DALS(
|
| 51 |
+
"""
|
| 52 |
+
from setuptools import setup
|
| 53 |
+
|
| 54 |
+
setup(
|
| 55 |
+
name='foo',
|
| 56 |
+
py_modules=['hello'],
|
| 57 |
+
entry_points={'console_scripts': ['hi = hello.run']},
|
| 58 |
+
zip_safe=False,
|
| 59 |
+
)
|
| 60 |
+
"""
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
def _create_project(self):
|
| 64 |
+
path.build({
|
| 65 |
+
'setup.py': self.setup_script,
|
| 66 |
+
'hello.py': DALS(
|
| 67 |
+
"""
|
| 68 |
+
def run():
|
| 69 |
+
print('hello')
|
| 70 |
+
"""
|
| 71 |
+
),
|
| 72 |
+
})
|
| 73 |
+
|
| 74 |
+
@staticmethod
|
| 75 |
+
def _extract_mv_version(pkg_info_lines: list[str]) -> tuple[int, int]:
|
| 76 |
+
version_str = pkg_info_lines[0].split(' ')[1]
|
| 77 |
+
major, minor = map(int, version_str.split('.')[:2])
|
| 78 |
+
return major, minor
|
| 79 |
+
|
| 80 |
+
def test_egg_info_save_version_info_setup_empty(self, tmpdir_cwd, env):
|
| 81 |
+
"""
|
| 82 |
+
When the egg_info section is empty or not present, running
|
| 83 |
+
save_version_info should add the settings to the setup.cfg
|
| 84 |
+
in a deterministic order.
|
| 85 |
+
"""
|
| 86 |
+
setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
|
| 87 |
+
dist = Distribution()
|
| 88 |
+
ei = egg_info(dist)
|
| 89 |
+
ei.initialize_options()
|
| 90 |
+
ei.save_version_info(setup_cfg)
|
| 91 |
+
|
| 92 |
+
with open(setup_cfg, 'r', encoding="utf-8") as f:
|
| 93 |
+
content = f.read()
|
| 94 |
+
|
| 95 |
+
assert '[egg_info]' in content
|
| 96 |
+
assert 'tag_build =' in content
|
| 97 |
+
assert 'tag_date = 0' in content
|
| 98 |
+
|
| 99 |
+
expected_order = (
|
| 100 |
+
'tag_build',
|
| 101 |
+
'tag_date',
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
self._validate_content_order(content, expected_order)
|
| 105 |
+
|
| 106 |
+
@staticmethod
|
| 107 |
+
def _validate_content_order(content, expected):
|
| 108 |
+
"""
|
| 109 |
+
Assert that the strings in expected appear in content
|
| 110 |
+
in order.
|
| 111 |
+
"""
|
| 112 |
+
pattern = '.*'.join(expected)
|
| 113 |
+
flags = re.MULTILINE | re.DOTALL
|
| 114 |
+
assert re.search(pattern, content, flags)
|
| 115 |
+
|
| 116 |
+
def test_egg_info_save_version_info_setup_defaults(self, tmpdir_cwd, env):
|
| 117 |
+
"""
|
| 118 |
+
When running save_version_info on an existing setup.cfg
|
| 119 |
+
with the 'default' values present from a previous run,
|
| 120 |
+
the file should remain unchanged.
|
| 121 |
+
"""
|
| 122 |
+
setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
|
| 123 |
+
path.build({
|
| 124 |
+
setup_cfg: DALS(
|
| 125 |
+
"""
|
| 126 |
+
[egg_info]
|
| 127 |
+
tag_build =
|
| 128 |
+
tag_date = 0
|
| 129 |
+
"""
|
| 130 |
+
),
|
| 131 |
+
})
|
| 132 |
+
dist = Distribution()
|
| 133 |
+
ei = egg_info(dist)
|
| 134 |
+
ei.initialize_options()
|
| 135 |
+
ei.save_version_info(setup_cfg)
|
| 136 |
+
|
| 137 |
+
with open(setup_cfg, 'r', encoding="utf-8") as f:
|
| 138 |
+
content = f.read()
|
| 139 |
+
|
| 140 |
+
assert '[egg_info]' in content
|
| 141 |
+
assert 'tag_build =' in content
|
| 142 |
+
assert 'tag_date = 0' in content
|
| 143 |
+
|
| 144 |
+
expected_order = (
|
| 145 |
+
'tag_build',
|
| 146 |
+
'tag_date',
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
self._validate_content_order(content, expected_order)
|
| 150 |
+
|
| 151 |
+
def test_expected_files_produced(self, tmpdir_cwd, env):
|
| 152 |
+
self._create_project()
|
| 153 |
+
|
| 154 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 155 |
+
actual = os.listdir('foo.egg-info')
|
| 156 |
+
|
| 157 |
+
expected = [
|
| 158 |
+
'PKG-INFO',
|
| 159 |
+
'SOURCES.txt',
|
| 160 |
+
'dependency_links.txt',
|
| 161 |
+
'entry_points.txt',
|
| 162 |
+
'not-zip-safe',
|
| 163 |
+
'top_level.txt',
|
| 164 |
+
]
|
| 165 |
+
assert sorted(actual) == expected
|
| 166 |
+
|
| 167 |
+
def test_handling_utime_error(self, tmpdir_cwd, env):
|
| 168 |
+
dist = Distribution()
|
| 169 |
+
ei = egg_info(dist)
|
| 170 |
+
utime_patch = mock.patch('os.utime', side_effect=OSError("TEST"))
|
| 171 |
+
mkpath_patch = mock.patch(
|
| 172 |
+
'setuptools.command.egg_info.egg_info.mkpath', return_val=None
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
with utime_patch, mkpath_patch:
|
| 176 |
+
import distutils.errors
|
| 177 |
+
|
| 178 |
+
msg = r"Cannot update time stamp of directory 'None'"
|
| 179 |
+
with pytest.raises(distutils.errors.DistutilsFileError, match=msg):
|
| 180 |
+
ei.run()
|
| 181 |
+
|
| 182 |
+
def test_license_is_a_string(self, tmpdir_cwd, env):
|
| 183 |
+
setup_config = DALS(
|
| 184 |
+
"""
|
| 185 |
+
[metadata]
|
| 186 |
+
name=foo
|
| 187 |
+
version=0.0.1
|
| 188 |
+
license=file:MIT
|
| 189 |
+
"""
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
setup_script = DALS(
|
| 193 |
+
"""
|
| 194 |
+
from setuptools import setup
|
| 195 |
+
|
| 196 |
+
setup()
|
| 197 |
+
"""
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
path.build({
|
| 201 |
+
'setup.py': setup_script,
|
| 202 |
+
'setup.cfg': setup_config,
|
| 203 |
+
})
|
| 204 |
+
|
| 205 |
+
# This command should fail with a ValueError, but because it's
|
| 206 |
+
# currently configured to use a subprocess, the actual traceback
|
| 207 |
+
# object is lost and we need to parse it from stderr
|
| 208 |
+
with pytest.raises(AssertionError) as exc:
|
| 209 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 210 |
+
|
| 211 |
+
# The only argument to the assertion error should be a traceback
|
| 212 |
+
# containing a ValueError
|
| 213 |
+
assert 'ValueError' in exc.value.args[0]
|
| 214 |
+
|
| 215 |
+
def test_rebuilt(self, tmpdir_cwd, env):
|
| 216 |
+
"""Ensure timestamps are updated when the command is re-run."""
|
| 217 |
+
self._create_project()
|
| 218 |
+
|
| 219 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 220 |
+
timestamp_a = os.path.getmtime('foo.egg-info')
|
| 221 |
+
|
| 222 |
+
# arbitrary sleep just to handle *really* fast systems
|
| 223 |
+
time.sleep(0.001)
|
| 224 |
+
|
| 225 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 226 |
+
timestamp_b = os.path.getmtime('foo.egg-info')
|
| 227 |
+
|
| 228 |
+
assert timestamp_a != timestamp_b
|
| 229 |
+
|
| 230 |
+
def test_manifest_template_is_read(self, tmpdir_cwd, env):
|
| 231 |
+
self._create_project()
|
| 232 |
+
path.build({
|
| 233 |
+
'MANIFEST.in': DALS(
|
| 234 |
+
"""
|
| 235 |
+
recursive-include docs *.rst
|
| 236 |
+
"""
|
| 237 |
+
),
|
| 238 |
+
'docs': {
|
| 239 |
+
'usage.rst': "Run 'hi'",
|
| 240 |
+
},
|
| 241 |
+
})
|
| 242 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 243 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 244 |
+
sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt')
|
| 245 |
+
with open(sources_txt, encoding="utf-8") as f:
|
| 246 |
+
assert 'docs/usage.rst' in f.read().split('\n')
|
| 247 |
+
|
| 248 |
+
def _setup_script_with_requires(self, requires, use_setup_cfg=False):
|
| 249 |
+
setup_script = DALS(
|
| 250 |
+
"""
|
| 251 |
+
from setuptools import setup
|
| 252 |
+
|
| 253 |
+
setup(name='foo', zip_safe=False, %s)
|
| 254 |
+
"""
|
| 255 |
+
) % ('' if use_setup_cfg else requires)
|
| 256 |
+
setup_config = requires if use_setup_cfg else ''
|
| 257 |
+
path.build({
|
| 258 |
+
'setup.py': setup_script,
|
| 259 |
+
'setup.cfg': setup_config,
|
| 260 |
+
})
|
| 261 |
+
|
| 262 |
+
mismatch_marker = f"python_version<'{sys.version_info[0]}'"
|
| 263 |
+
# Alternate equivalent syntax.
|
| 264 |
+
mismatch_marker_alternate = f'python_version < "{sys.version_info[0]}"'
|
| 265 |
+
invalid_marker = "<=>++"
|
| 266 |
+
|
| 267 |
+
class RequiresTestHelper:
|
| 268 |
+
@staticmethod
|
| 269 |
+
def parametrize(*test_list, **format_dict):
|
| 270 |
+
idlist = []
|
| 271 |
+
argvalues = []
|
| 272 |
+
for test in test_list:
|
| 273 |
+
test_params = test.lstrip().split('\n\n', 3)
|
| 274 |
+
name_kwargs = test_params.pop(0).split('\n')
|
| 275 |
+
if len(name_kwargs) > 1:
|
| 276 |
+
val = name_kwargs[1].strip()
|
| 277 |
+
install_cmd_kwargs = ast.literal_eval(val)
|
| 278 |
+
else:
|
| 279 |
+
install_cmd_kwargs = {}
|
| 280 |
+
name = name_kwargs[0].strip()
|
| 281 |
+
setup_py_requires, setup_cfg_requires, expected_requires = [
|
| 282 |
+
DALS(a).format(**format_dict) for a in test_params
|
| 283 |
+
]
|
| 284 |
+
for id_, requires, use_cfg in (
|
| 285 |
+
(name, setup_py_requires, False),
|
| 286 |
+
(name + '_in_setup_cfg', setup_cfg_requires, True),
|
| 287 |
+
):
|
| 288 |
+
idlist.append(id_)
|
| 289 |
+
marks = ()
|
| 290 |
+
if requires.startswith('@xfail\n'):
|
| 291 |
+
requires = requires[7:]
|
| 292 |
+
marks = pytest.mark.xfail
|
| 293 |
+
argvalues.append(
|
| 294 |
+
pytest.param(
|
| 295 |
+
requires,
|
| 296 |
+
use_cfg,
|
| 297 |
+
expected_requires,
|
| 298 |
+
install_cmd_kwargs,
|
| 299 |
+
marks=marks,
|
| 300 |
+
)
|
| 301 |
+
)
|
| 302 |
+
return pytest.mark.parametrize(
|
| 303 |
+
(
|
| 304 |
+
"requires",
|
| 305 |
+
"use_setup_cfg",
|
| 306 |
+
"expected_requires",
|
| 307 |
+
"install_cmd_kwargs",
|
| 308 |
+
),
|
| 309 |
+
argvalues,
|
| 310 |
+
ids=idlist,
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
@RequiresTestHelper.parametrize(
|
| 314 |
+
# Format of a test:
|
| 315 |
+
#
|
| 316 |
+
# id
|
| 317 |
+
# install_cmd_kwargs [optional]
|
| 318 |
+
#
|
| 319 |
+
# requires block (when used in setup.py)
|
| 320 |
+
#
|
| 321 |
+
# requires block (when used in setup.cfg)
|
| 322 |
+
#
|
| 323 |
+
# expected contents of requires.txt
|
| 324 |
+
"""
|
| 325 |
+
install_requires_deterministic
|
| 326 |
+
|
| 327 |
+
install_requires=["wheel>=0.5", "pytest"]
|
| 328 |
+
|
| 329 |
+
[options]
|
| 330 |
+
install_requires =
|
| 331 |
+
wheel>=0.5
|
| 332 |
+
pytest
|
| 333 |
+
|
| 334 |
+
wheel>=0.5
|
| 335 |
+
pytest
|
| 336 |
+
""",
|
| 337 |
+
"""
|
| 338 |
+
install_requires_ordered
|
| 339 |
+
|
| 340 |
+
install_requires=["pytest>=3.0.2,!=10.9999"]
|
| 341 |
+
|
| 342 |
+
[options]
|
| 343 |
+
install_requires =
|
| 344 |
+
pytest>=3.0.2,!=10.9999
|
| 345 |
+
|
| 346 |
+
pytest!=10.9999,>=3.0.2
|
| 347 |
+
""",
|
| 348 |
+
"""
|
| 349 |
+
install_requires_with_marker
|
| 350 |
+
|
| 351 |
+
install_requires=["barbazquux;{mismatch_marker}"],
|
| 352 |
+
|
| 353 |
+
[options]
|
| 354 |
+
install_requires =
|
| 355 |
+
barbazquux; {mismatch_marker}
|
| 356 |
+
|
| 357 |
+
[:{mismatch_marker_alternate}]
|
| 358 |
+
barbazquux
|
| 359 |
+
""",
|
| 360 |
+
"""
|
| 361 |
+
install_requires_with_extra
|
| 362 |
+
{'cmd': ['egg_info']}
|
| 363 |
+
|
| 364 |
+
install_requires=["barbazquux [test]"],
|
| 365 |
+
|
| 366 |
+
[options]
|
| 367 |
+
install_requires =
|
| 368 |
+
barbazquux [test]
|
| 369 |
+
|
| 370 |
+
barbazquux[test]
|
| 371 |
+
""",
|
| 372 |
+
"""
|
| 373 |
+
install_requires_with_extra_and_marker
|
| 374 |
+
|
| 375 |
+
install_requires=["barbazquux [test]; {mismatch_marker}"],
|
| 376 |
+
|
| 377 |
+
[options]
|
| 378 |
+
install_requires =
|
| 379 |
+
barbazquux [test]; {mismatch_marker}
|
| 380 |
+
|
| 381 |
+
[:{mismatch_marker_alternate}]
|
| 382 |
+
barbazquux[test]
|
| 383 |
+
""",
|
| 384 |
+
"""
|
| 385 |
+
setup_requires_with_markers
|
| 386 |
+
|
| 387 |
+
setup_requires=["barbazquux;{mismatch_marker}"],
|
| 388 |
+
|
| 389 |
+
[options]
|
| 390 |
+
setup_requires =
|
| 391 |
+
barbazquux; {mismatch_marker}
|
| 392 |
+
|
| 393 |
+
""",
|
| 394 |
+
"""
|
| 395 |
+
extras_require_with_extra
|
| 396 |
+
{'cmd': ['egg_info']}
|
| 397 |
+
|
| 398 |
+
extras_require={{"extra": ["barbazquux [test]"]}},
|
| 399 |
+
|
| 400 |
+
[options.extras_require]
|
| 401 |
+
extra = barbazquux [test]
|
| 402 |
+
|
| 403 |
+
[extra]
|
| 404 |
+
barbazquux[test]
|
| 405 |
+
""",
|
| 406 |
+
"""
|
| 407 |
+
extras_require_with_extra_and_marker_in_req
|
| 408 |
+
|
| 409 |
+
extras_require={{"extra": ["barbazquux [test]; {mismatch_marker}"]}},
|
| 410 |
+
|
| 411 |
+
[options.extras_require]
|
| 412 |
+
extra =
|
| 413 |
+
barbazquux [test]; {mismatch_marker}
|
| 414 |
+
|
| 415 |
+
[extra]
|
| 416 |
+
|
| 417 |
+
[extra:{mismatch_marker_alternate}]
|
| 418 |
+
barbazquux[test]
|
| 419 |
+
""",
|
| 420 |
+
# FIXME: ConfigParser does not allow : in key names!
|
| 421 |
+
"""
|
| 422 |
+
extras_require_with_marker
|
| 423 |
+
|
| 424 |
+
extras_require={{":{mismatch_marker}": ["barbazquux"]}},
|
| 425 |
+
|
| 426 |
+
@xfail
|
| 427 |
+
[options.extras_require]
|
| 428 |
+
:{mismatch_marker} = barbazquux
|
| 429 |
+
|
| 430 |
+
[:{mismatch_marker}]
|
| 431 |
+
barbazquux
|
| 432 |
+
""",
|
| 433 |
+
"""
|
| 434 |
+
extras_require_with_marker_in_req
|
| 435 |
+
|
| 436 |
+
extras_require={{"extra": ["barbazquux; {mismatch_marker}"]}},
|
| 437 |
+
|
| 438 |
+
[options.extras_require]
|
| 439 |
+
extra =
|
| 440 |
+
barbazquux; {mismatch_marker}
|
| 441 |
+
|
| 442 |
+
[extra]
|
| 443 |
+
|
| 444 |
+
[extra:{mismatch_marker_alternate}]
|
| 445 |
+
barbazquux
|
| 446 |
+
""",
|
| 447 |
+
"""
|
| 448 |
+
extras_require_with_empty_section
|
| 449 |
+
|
| 450 |
+
extras_require={{"empty": []}},
|
| 451 |
+
|
| 452 |
+
[options.extras_require]
|
| 453 |
+
empty =
|
| 454 |
+
|
| 455 |
+
[empty]
|
| 456 |
+
""",
|
| 457 |
+
# Format arguments.
|
| 458 |
+
invalid_marker=invalid_marker,
|
| 459 |
+
mismatch_marker=mismatch_marker,
|
| 460 |
+
mismatch_marker_alternate=mismatch_marker_alternate,
|
| 461 |
+
)
|
| 462 |
+
def test_requires(
|
| 463 |
+
self,
|
| 464 |
+
tmpdir_cwd,
|
| 465 |
+
env,
|
| 466 |
+
requires,
|
| 467 |
+
use_setup_cfg,
|
| 468 |
+
expected_requires,
|
| 469 |
+
install_cmd_kwargs,
|
| 470 |
+
):
|
| 471 |
+
self._setup_script_with_requires(requires, use_setup_cfg)
|
| 472 |
+
self._run_egg_info_command(tmpdir_cwd, env, **install_cmd_kwargs)
|
| 473 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 474 |
+
requires_txt = os.path.join(egg_info_dir, 'requires.txt')
|
| 475 |
+
if os.path.exists(requires_txt):
|
| 476 |
+
with open(requires_txt, encoding="utf-8") as fp:
|
| 477 |
+
install_requires = fp.read()
|
| 478 |
+
else:
|
| 479 |
+
install_requires = ''
|
| 480 |
+
assert install_requires.lstrip() == expected_requires
|
| 481 |
+
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
|
| 482 |
+
|
| 483 |
+
def test_install_requires_unordered_disallowed(self, tmpdir_cwd, env):
|
| 484 |
+
"""
|
| 485 |
+
Packages that pass unordered install_requires sequences
|
| 486 |
+
should be rejected as they produce non-deterministic
|
| 487 |
+
builds. See #458.
|
| 488 |
+
"""
|
| 489 |
+
req = 'install_requires={"fake-factory==0.5.2", "pytz"}'
|
| 490 |
+
self._setup_script_with_requires(req)
|
| 491 |
+
with pytest.raises(AssertionError):
|
| 492 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 493 |
+
|
| 494 |
+
def test_extras_require_with_invalid_marker(self, tmpdir_cwd, env):
|
| 495 |
+
tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
|
| 496 |
+
req = tmpl.format(marker=self.invalid_marker)
|
| 497 |
+
self._setup_script_with_requires(req)
|
| 498 |
+
with pytest.raises(AssertionError):
|
| 499 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 500 |
+
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
|
| 501 |
+
|
| 502 |
+
def test_extras_require_with_invalid_marker_in_req(self, tmpdir_cwd, env):
|
| 503 |
+
tmpl = 'extras_require={{"extra": ["barbazquux; {marker}"]}},'
|
| 504 |
+
req = tmpl.format(marker=self.invalid_marker)
|
| 505 |
+
self._setup_script_with_requires(req)
|
| 506 |
+
with pytest.raises(AssertionError):
|
| 507 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 508 |
+
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
|
| 509 |
+
|
| 510 |
+
def test_provides_extra(self, tmpdir_cwd, env):
|
| 511 |
+
self._setup_script_with_requires('extras_require={"foobar": ["barbazquux"]},')
|
| 512 |
+
environ = os.environ.copy().update(
|
| 513 |
+
HOME=env.paths['home'],
|
| 514 |
+
)
|
| 515 |
+
environment.run_setup_py(
|
| 516 |
+
cmd=['egg_info'],
|
| 517 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 518 |
+
data_stream=1,
|
| 519 |
+
env=environ,
|
| 520 |
+
)
|
| 521 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 522 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 523 |
+
pkg_info_lines = fp.read().split('\n')
|
| 524 |
+
assert 'Provides-Extra: foobar' in pkg_info_lines
|
| 525 |
+
assert 'Metadata-Version: 2.4' in pkg_info_lines
|
| 526 |
+
|
| 527 |
+
def test_doesnt_provides_extra(self, tmpdir_cwd, env):
|
| 528 |
+
self._setup_script_with_requires(
|
| 529 |
+
"""install_requires=["spam ; python_version<'3.6'"]"""
|
| 530 |
+
)
|
| 531 |
+
environ = os.environ.copy().update(
|
| 532 |
+
HOME=env.paths['home'],
|
| 533 |
+
)
|
| 534 |
+
environment.run_setup_py(
|
| 535 |
+
cmd=['egg_info'],
|
| 536 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 537 |
+
data_stream=1,
|
| 538 |
+
env=environ,
|
| 539 |
+
)
|
| 540 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 541 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 542 |
+
pkg_info_text = fp.read()
|
| 543 |
+
assert 'Provides-Extra:' not in pkg_info_text
|
| 544 |
+
|
| 545 |
+
@pytest.mark.parametrize(
|
| 546 |
+
('files', 'license_in_sources'),
|
| 547 |
+
[
|
| 548 |
+
(
|
| 549 |
+
{
|
| 550 |
+
'setup.cfg': DALS(
|
| 551 |
+
"""
|
| 552 |
+
[metadata]
|
| 553 |
+
license_file = LICENSE
|
| 554 |
+
"""
|
| 555 |
+
),
|
| 556 |
+
'LICENSE': "Test license",
|
| 557 |
+
},
|
| 558 |
+
True,
|
| 559 |
+
), # with license
|
| 560 |
+
(
|
| 561 |
+
{
|
| 562 |
+
'setup.cfg': DALS(
|
| 563 |
+
"""
|
| 564 |
+
[metadata]
|
| 565 |
+
license_file = INVALID_LICENSE
|
| 566 |
+
"""
|
| 567 |
+
),
|
| 568 |
+
'LICENSE': "Test license",
|
| 569 |
+
},
|
| 570 |
+
False,
|
| 571 |
+
), # with an invalid license
|
| 572 |
+
(
|
| 573 |
+
{
|
| 574 |
+
'setup.cfg': DALS(
|
| 575 |
+
"""
|
| 576 |
+
"""
|
| 577 |
+
),
|
| 578 |
+
'LICENSE': "Test license",
|
| 579 |
+
},
|
| 580 |
+
True,
|
| 581 |
+
), # no license_file attribute, LICENSE auto-included
|
| 582 |
+
(
|
| 583 |
+
{
|
| 584 |
+
'setup.cfg': DALS(
|
| 585 |
+
"""
|
| 586 |
+
[metadata]
|
| 587 |
+
license_file = LICENSE
|
| 588 |
+
"""
|
| 589 |
+
),
|
| 590 |
+
'MANIFEST.in': "exclude LICENSE",
|
| 591 |
+
'LICENSE': "Test license",
|
| 592 |
+
},
|
| 593 |
+
True,
|
| 594 |
+
), # manifest is overwritten by license_file
|
| 595 |
+
pytest.param(
|
| 596 |
+
{
|
| 597 |
+
'setup.cfg': DALS(
|
| 598 |
+
"""
|
| 599 |
+
[metadata]
|
| 600 |
+
license_file = LICEN[CS]E*
|
| 601 |
+
"""
|
| 602 |
+
),
|
| 603 |
+
'LICENSE': "Test license",
|
| 604 |
+
},
|
| 605 |
+
True,
|
| 606 |
+
id="glob_pattern",
|
| 607 |
+
),
|
| 608 |
+
],
|
| 609 |
+
)
|
| 610 |
+
def test_setup_cfg_license_file(self, tmpdir_cwd, env, files, license_in_sources):
|
| 611 |
+
self._create_project()
|
| 612 |
+
path.build(files)
|
| 613 |
+
|
| 614 |
+
environment.run_setup_py(
|
| 615 |
+
cmd=['egg_info'],
|
| 616 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 617 |
+
)
|
| 618 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 619 |
+
|
| 620 |
+
sources_text = Path(egg_info_dir, "SOURCES.txt").read_text(encoding="utf-8")
|
| 621 |
+
|
| 622 |
+
if license_in_sources:
|
| 623 |
+
assert 'LICENSE' in sources_text
|
| 624 |
+
else:
|
| 625 |
+
assert 'LICENSE' not in sources_text
|
| 626 |
+
# for invalid license test
|
| 627 |
+
assert 'INVALID_LICENSE' not in sources_text
|
| 628 |
+
|
| 629 |
+
@pytest.mark.parametrize(
|
| 630 |
+
('files', 'incl_licenses', 'excl_licenses'),
|
| 631 |
+
[
|
| 632 |
+
(
|
| 633 |
+
{
|
| 634 |
+
'setup.cfg': DALS(
|
| 635 |
+
"""
|
| 636 |
+
[metadata]
|
| 637 |
+
license_files =
|
| 638 |
+
LICENSE-ABC
|
| 639 |
+
LICENSE-XYZ
|
| 640 |
+
"""
|
| 641 |
+
),
|
| 642 |
+
'LICENSE-ABC': "ABC license",
|
| 643 |
+
'LICENSE-XYZ': "XYZ license",
|
| 644 |
+
},
|
| 645 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 646 |
+
[],
|
| 647 |
+
), # with licenses
|
| 648 |
+
(
|
| 649 |
+
{
|
| 650 |
+
'setup.cfg': DALS(
|
| 651 |
+
"""
|
| 652 |
+
[metadata]
|
| 653 |
+
license_files = LICENSE-ABC, LICENSE-XYZ
|
| 654 |
+
"""
|
| 655 |
+
),
|
| 656 |
+
'LICENSE-ABC': "ABC license",
|
| 657 |
+
'LICENSE-XYZ': "XYZ license",
|
| 658 |
+
},
|
| 659 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 660 |
+
[],
|
| 661 |
+
), # with commas
|
| 662 |
+
(
|
| 663 |
+
{
|
| 664 |
+
'setup.cfg': DALS(
|
| 665 |
+
"""
|
| 666 |
+
[metadata]
|
| 667 |
+
license_files =
|
| 668 |
+
LICENSE-ABC
|
| 669 |
+
"""
|
| 670 |
+
),
|
| 671 |
+
'LICENSE-ABC': "ABC license",
|
| 672 |
+
'LICENSE-XYZ': "XYZ license",
|
| 673 |
+
},
|
| 674 |
+
['LICENSE-ABC'],
|
| 675 |
+
['LICENSE-XYZ'],
|
| 676 |
+
), # with one license
|
| 677 |
+
(
|
| 678 |
+
{
|
| 679 |
+
'setup.cfg': DALS(
|
| 680 |
+
"""
|
| 681 |
+
[metadata]
|
| 682 |
+
license_files =
|
| 683 |
+
"""
|
| 684 |
+
),
|
| 685 |
+
'LICENSE-ABC': "ABC license",
|
| 686 |
+
'LICENSE-XYZ': "XYZ license",
|
| 687 |
+
},
|
| 688 |
+
[],
|
| 689 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 690 |
+
), # empty
|
| 691 |
+
(
|
| 692 |
+
{
|
| 693 |
+
'setup.cfg': DALS(
|
| 694 |
+
"""
|
| 695 |
+
[metadata]
|
| 696 |
+
license_files = LICENSE-XYZ
|
| 697 |
+
"""
|
| 698 |
+
),
|
| 699 |
+
'LICENSE-ABC': "ABC license",
|
| 700 |
+
'LICENSE-XYZ': "XYZ license",
|
| 701 |
+
},
|
| 702 |
+
['LICENSE-XYZ'],
|
| 703 |
+
['LICENSE-ABC'],
|
| 704 |
+
), # on same line
|
| 705 |
+
(
|
| 706 |
+
{
|
| 707 |
+
'setup.cfg': DALS(
|
| 708 |
+
"""
|
| 709 |
+
[metadata]
|
| 710 |
+
license_files =
|
| 711 |
+
LICENSE-ABC
|
| 712 |
+
INVALID_LICENSE
|
| 713 |
+
"""
|
| 714 |
+
),
|
| 715 |
+
'LICENSE-ABC': "Test license",
|
| 716 |
+
},
|
| 717 |
+
['LICENSE-ABC'],
|
| 718 |
+
['INVALID_LICENSE'],
|
| 719 |
+
), # with an invalid license
|
| 720 |
+
(
|
| 721 |
+
{
|
| 722 |
+
'setup.cfg': DALS(
|
| 723 |
+
"""
|
| 724 |
+
"""
|
| 725 |
+
),
|
| 726 |
+
'LICENSE': "Test license",
|
| 727 |
+
},
|
| 728 |
+
['LICENSE'],
|
| 729 |
+
[],
|
| 730 |
+
), # no license_files attribute, LICENSE auto-included
|
| 731 |
+
(
|
| 732 |
+
{
|
| 733 |
+
'setup.cfg': DALS(
|
| 734 |
+
"""
|
| 735 |
+
[metadata]
|
| 736 |
+
license_files = LICENSE
|
| 737 |
+
"""
|
| 738 |
+
),
|
| 739 |
+
'MANIFEST.in': "exclude LICENSE",
|
| 740 |
+
'LICENSE': "Test license",
|
| 741 |
+
},
|
| 742 |
+
['LICENSE'],
|
| 743 |
+
[],
|
| 744 |
+
), # manifest is overwritten by license_files
|
| 745 |
+
(
|
| 746 |
+
{
|
| 747 |
+
'setup.cfg': DALS(
|
| 748 |
+
"""
|
| 749 |
+
[metadata]
|
| 750 |
+
license_files =
|
| 751 |
+
LICENSE-ABC
|
| 752 |
+
LICENSE-XYZ
|
| 753 |
+
"""
|
| 754 |
+
),
|
| 755 |
+
'MANIFEST.in': "exclude LICENSE-XYZ",
|
| 756 |
+
'LICENSE-ABC': "ABC license",
|
| 757 |
+
'LICENSE-XYZ': "XYZ license",
|
| 758 |
+
# manifest is overwritten by license_files
|
| 759 |
+
},
|
| 760 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 761 |
+
[],
|
| 762 |
+
),
|
| 763 |
+
pytest.param(
|
| 764 |
+
{
|
| 765 |
+
'setup.cfg': "",
|
| 766 |
+
'LICENSE-ABC': "ABC license",
|
| 767 |
+
'COPYING-ABC': "ABC copying",
|
| 768 |
+
'NOTICE-ABC': "ABC notice",
|
| 769 |
+
'AUTHORS-ABC': "ABC authors",
|
| 770 |
+
'LICENCE-XYZ': "XYZ license",
|
| 771 |
+
'LICENSE': "License",
|
| 772 |
+
'INVALID-LICENSE': "Invalid license",
|
| 773 |
+
},
|
| 774 |
+
[
|
| 775 |
+
'LICENSE-ABC',
|
| 776 |
+
'COPYING-ABC',
|
| 777 |
+
'NOTICE-ABC',
|
| 778 |
+
'AUTHORS-ABC',
|
| 779 |
+
'LICENCE-XYZ',
|
| 780 |
+
'LICENSE',
|
| 781 |
+
],
|
| 782 |
+
['INVALID-LICENSE'],
|
| 783 |
+
# ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
|
| 784 |
+
id="default_glob_patterns",
|
| 785 |
+
),
|
| 786 |
+
pytest.param(
|
| 787 |
+
{
|
| 788 |
+
'setup.cfg': DALS(
|
| 789 |
+
"""
|
| 790 |
+
[metadata]
|
| 791 |
+
license_files =
|
| 792 |
+
LICENSE*
|
| 793 |
+
"""
|
| 794 |
+
),
|
| 795 |
+
'LICENSE-ABC': "ABC license",
|
| 796 |
+
'NOTICE-XYZ': "XYZ notice",
|
| 797 |
+
},
|
| 798 |
+
['LICENSE-ABC'],
|
| 799 |
+
['NOTICE-XYZ'],
|
| 800 |
+
id="no_default_glob_patterns",
|
| 801 |
+
),
|
| 802 |
+
pytest.param(
|
| 803 |
+
{
|
| 804 |
+
'setup.cfg': DALS(
|
| 805 |
+
"""
|
| 806 |
+
[metadata]
|
| 807 |
+
license_files =
|
| 808 |
+
LICENSE-ABC
|
| 809 |
+
LICENSE*
|
| 810 |
+
"""
|
| 811 |
+
),
|
| 812 |
+
'LICENSE-ABC': "ABC license",
|
| 813 |
+
},
|
| 814 |
+
['LICENSE-ABC'],
|
| 815 |
+
[],
|
| 816 |
+
id="files_only_added_once",
|
| 817 |
+
),
|
| 818 |
+
pytest.param(
|
| 819 |
+
{
|
| 820 |
+
'setup.cfg': DALS(
|
| 821 |
+
"""
|
| 822 |
+
[metadata]
|
| 823 |
+
license_files = **/LICENSE
|
| 824 |
+
"""
|
| 825 |
+
),
|
| 826 |
+
'LICENSE': "ABC license",
|
| 827 |
+
'LICENSE-OTHER': "Don't include",
|
| 828 |
+
'vendor': {'LICENSE': "Vendor license"},
|
| 829 |
+
},
|
| 830 |
+
['LICENSE', 'vendor/LICENSE'],
|
| 831 |
+
['LICENSE-OTHER'],
|
| 832 |
+
id="recursive_glob",
|
| 833 |
+
),
|
| 834 |
+
],
|
| 835 |
+
)
|
| 836 |
+
def test_setup_cfg_license_files(
|
| 837 |
+
self, tmpdir_cwd, env, files, incl_licenses, excl_licenses
|
| 838 |
+
):
|
| 839 |
+
self._create_project()
|
| 840 |
+
path.build(files)
|
| 841 |
+
|
| 842 |
+
environment.run_setup_py(
|
| 843 |
+
cmd=['egg_info'],
|
| 844 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 845 |
+
)
|
| 846 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 847 |
+
|
| 848 |
+
sources_text = Path(egg_info_dir, "SOURCES.txt").read_text(encoding="utf-8")
|
| 849 |
+
sources_lines = [line.strip() for line in sources_text.splitlines()]
|
| 850 |
+
|
| 851 |
+
for lf in incl_licenses:
|
| 852 |
+
assert sources_lines.count(lf) == 1
|
| 853 |
+
|
| 854 |
+
for lf in excl_licenses:
|
| 855 |
+
assert sources_lines.count(lf) == 0
|
| 856 |
+
|
| 857 |
+
@pytest.mark.parametrize(
|
| 858 |
+
('files', 'incl_licenses', 'excl_licenses'),
|
| 859 |
+
[
|
| 860 |
+
(
|
| 861 |
+
{
|
| 862 |
+
'setup.cfg': DALS(
|
| 863 |
+
"""
|
| 864 |
+
[metadata]
|
| 865 |
+
license_file =
|
| 866 |
+
license_files =
|
| 867 |
+
"""
|
| 868 |
+
),
|
| 869 |
+
'LICENSE-ABC': "ABC license",
|
| 870 |
+
'LICENSE-XYZ': "XYZ license",
|
| 871 |
+
},
|
| 872 |
+
[],
|
| 873 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 874 |
+
), # both empty
|
| 875 |
+
(
|
| 876 |
+
{
|
| 877 |
+
'setup.cfg': DALS(
|
| 878 |
+
"""
|
| 879 |
+
[metadata]
|
| 880 |
+
license_file =
|
| 881 |
+
LICENSE-ABC
|
| 882 |
+
LICENSE-XYZ
|
| 883 |
+
"""
|
| 884 |
+
),
|
| 885 |
+
'LICENSE-ABC': "ABC license",
|
| 886 |
+
'LICENSE-XYZ': "XYZ license",
|
| 887 |
+
# license_file is still singular
|
| 888 |
+
},
|
| 889 |
+
[],
|
| 890 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 891 |
+
),
|
| 892 |
+
(
|
| 893 |
+
{
|
| 894 |
+
'setup.cfg': DALS(
|
| 895 |
+
"""
|
| 896 |
+
[metadata]
|
| 897 |
+
license_file = LICENSE-ABC
|
| 898 |
+
license_files =
|
| 899 |
+
LICENSE-XYZ
|
| 900 |
+
LICENSE-PQR
|
| 901 |
+
"""
|
| 902 |
+
),
|
| 903 |
+
'LICENSE-ABC': "ABC license",
|
| 904 |
+
'LICENSE-PQR': "PQR license",
|
| 905 |
+
'LICENSE-XYZ': "XYZ license",
|
| 906 |
+
},
|
| 907 |
+
['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'],
|
| 908 |
+
[],
|
| 909 |
+
), # combined
|
| 910 |
+
(
|
| 911 |
+
{
|
| 912 |
+
'setup.cfg': DALS(
|
| 913 |
+
"""
|
| 914 |
+
[metadata]
|
| 915 |
+
license_file = LICENSE-ABC
|
| 916 |
+
license_files =
|
| 917 |
+
LICENSE-ABC
|
| 918 |
+
LICENSE-XYZ
|
| 919 |
+
LICENSE-PQR
|
| 920 |
+
"""
|
| 921 |
+
),
|
| 922 |
+
'LICENSE-ABC': "ABC license",
|
| 923 |
+
'LICENSE-PQR': "PQR license",
|
| 924 |
+
'LICENSE-XYZ': "XYZ license",
|
| 925 |
+
# duplicate license
|
| 926 |
+
},
|
| 927 |
+
['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'],
|
| 928 |
+
[],
|
| 929 |
+
),
|
| 930 |
+
(
|
| 931 |
+
{
|
| 932 |
+
'setup.cfg': DALS(
|
| 933 |
+
"""
|
| 934 |
+
[metadata]
|
| 935 |
+
license_file = LICENSE-ABC
|
| 936 |
+
license_files =
|
| 937 |
+
LICENSE-XYZ
|
| 938 |
+
"""
|
| 939 |
+
),
|
| 940 |
+
'LICENSE-ABC': "ABC license",
|
| 941 |
+
'LICENSE-PQR': "PQR license",
|
| 942 |
+
'LICENSE-XYZ': "XYZ license",
|
| 943 |
+
# combined subset
|
| 944 |
+
},
|
| 945 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 946 |
+
['LICENSE-PQR'],
|
| 947 |
+
),
|
| 948 |
+
(
|
| 949 |
+
{
|
| 950 |
+
'setup.cfg': DALS(
|
| 951 |
+
"""
|
| 952 |
+
[metadata]
|
| 953 |
+
license_file = LICENSE-ABC
|
| 954 |
+
license_files =
|
| 955 |
+
LICENSE-XYZ
|
| 956 |
+
LICENSE-PQR
|
| 957 |
+
"""
|
| 958 |
+
),
|
| 959 |
+
'LICENSE-PQR': "Test license",
|
| 960 |
+
# with invalid licenses
|
| 961 |
+
},
|
| 962 |
+
['LICENSE-PQR'],
|
| 963 |
+
['LICENSE-ABC', 'LICENSE-XYZ'],
|
| 964 |
+
),
|
| 965 |
+
(
|
| 966 |
+
{
|
| 967 |
+
'setup.cfg': DALS(
|
| 968 |
+
"""
|
| 969 |
+
[metadata]
|
| 970 |
+
license_file = LICENSE-ABC
|
| 971 |
+
license_files =
|
| 972 |
+
LICENSE-PQR
|
| 973 |
+
LICENSE-XYZ
|
| 974 |
+
"""
|
| 975 |
+
),
|
| 976 |
+
'MANIFEST.in': "exclude LICENSE-ABC\nexclude LICENSE-PQR",
|
| 977 |
+
'LICENSE-ABC': "ABC license",
|
| 978 |
+
'LICENSE-PQR': "PQR license",
|
| 979 |
+
'LICENSE-XYZ': "XYZ license",
|
| 980 |
+
# manifest is overwritten
|
| 981 |
+
},
|
| 982 |
+
['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'],
|
| 983 |
+
[],
|
| 984 |
+
),
|
| 985 |
+
pytest.param(
|
| 986 |
+
{
|
| 987 |
+
'setup.cfg': DALS(
|
| 988 |
+
"""
|
| 989 |
+
[metadata]
|
| 990 |
+
license_file = LICENSE*
|
| 991 |
+
"""
|
| 992 |
+
),
|
| 993 |
+
'LICENSE-ABC': "ABC license",
|
| 994 |
+
'NOTICE-XYZ': "XYZ notice",
|
| 995 |
+
},
|
| 996 |
+
['LICENSE-ABC'],
|
| 997 |
+
['NOTICE-XYZ'],
|
| 998 |
+
id="no_default_glob_patterns",
|
| 999 |
+
),
|
| 1000 |
+
pytest.param(
|
| 1001 |
+
{
|
| 1002 |
+
'setup.cfg': DALS(
|
| 1003 |
+
"""
|
| 1004 |
+
[metadata]
|
| 1005 |
+
license_file = LICENSE*
|
| 1006 |
+
license_files =
|
| 1007 |
+
NOTICE*
|
| 1008 |
+
"""
|
| 1009 |
+
),
|
| 1010 |
+
'LICENSE-ABC': "ABC license",
|
| 1011 |
+
'NOTICE-ABC': "ABC notice",
|
| 1012 |
+
'AUTHORS-ABC': "ABC authors",
|
| 1013 |
+
},
|
| 1014 |
+
['LICENSE-ABC', 'NOTICE-ABC'],
|
| 1015 |
+
['AUTHORS-ABC'],
|
| 1016 |
+
id="combined_glob_patterrns",
|
| 1017 |
+
),
|
| 1018 |
+
],
|
| 1019 |
+
)
|
| 1020 |
+
def test_setup_cfg_license_file_license_files(
|
| 1021 |
+
self, tmpdir_cwd, env, files, incl_licenses, excl_licenses
|
| 1022 |
+
):
|
| 1023 |
+
self._create_project()
|
| 1024 |
+
path.build(files)
|
| 1025 |
+
|
| 1026 |
+
environment.run_setup_py(
|
| 1027 |
+
cmd=['egg_info'],
|
| 1028 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1029 |
+
)
|
| 1030 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1031 |
+
|
| 1032 |
+
sources_text = Path(egg_info_dir, "SOURCES.txt").read_text(encoding="utf-8")
|
| 1033 |
+
sources_lines = [line.strip() for line in sources_text.splitlines()]
|
| 1034 |
+
|
| 1035 |
+
for lf in incl_licenses:
|
| 1036 |
+
assert sources_lines.count(lf) == 1
|
| 1037 |
+
|
| 1038 |
+
for lf in excl_licenses:
|
| 1039 |
+
assert sources_lines.count(lf) == 0
|
| 1040 |
+
|
| 1041 |
+
def test_license_file_attr_pkg_info(self, tmpdir_cwd, env):
|
| 1042 |
+
"""All matched license files should have a corresponding License-File."""
|
| 1043 |
+
self._create_project()
|
| 1044 |
+
path.build({
|
| 1045 |
+
"setup.cfg": DALS(
|
| 1046 |
+
"""
|
| 1047 |
+
[metadata]
|
| 1048 |
+
license_files =
|
| 1049 |
+
NOTICE*
|
| 1050 |
+
LICENSE*
|
| 1051 |
+
**/LICENSE
|
| 1052 |
+
"""
|
| 1053 |
+
),
|
| 1054 |
+
"LICENSE-ABC": "ABC license",
|
| 1055 |
+
"LICENSE-XYZ": "XYZ license",
|
| 1056 |
+
"NOTICE": "included",
|
| 1057 |
+
"IGNORE": "not include",
|
| 1058 |
+
"vendor": {'LICENSE': "Vendor license"},
|
| 1059 |
+
})
|
| 1060 |
+
|
| 1061 |
+
environment.run_setup_py(
|
| 1062 |
+
cmd=['egg_info'],
|
| 1063 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1064 |
+
)
|
| 1065 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1066 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1067 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1068 |
+
license_file_lines = [
|
| 1069 |
+
line for line in pkg_info_lines if line.startswith('License-File:')
|
| 1070 |
+
]
|
| 1071 |
+
|
| 1072 |
+
# Only 'NOTICE', LICENSE-ABC', and 'LICENSE-XYZ' should have been matched
|
| 1073 |
+
# Also assert that order from license_files is keeped
|
| 1074 |
+
assert len(license_file_lines) == 4
|
| 1075 |
+
assert "License-File: NOTICE" == license_file_lines[0]
|
| 1076 |
+
assert "License-File: LICENSE-ABC" in license_file_lines[1:]
|
| 1077 |
+
assert "License-File: LICENSE-XYZ" in license_file_lines[1:]
|
| 1078 |
+
assert "License-File: vendor/LICENSE" in license_file_lines[3]
|
| 1079 |
+
|
| 1080 |
+
def test_metadata_version(self, tmpdir_cwd, env):
|
| 1081 |
+
"""Make sure latest metadata version is used by default."""
|
| 1082 |
+
self._setup_script_with_requires("")
|
| 1083 |
+
environment.run_setup_py(
|
| 1084 |
+
cmd=['egg_info'],
|
| 1085 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1086 |
+
data_stream=1,
|
| 1087 |
+
)
|
| 1088 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1089 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1090 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1091 |
+
# Update metadata version if changed
|
| 1092 |
+
assert self._extract_mv_version(pkg_info_lines) == (2, 4)
|
| 1093 |
+
|
| 1094 |
+
def test_long_description_content_type(self, tmpdir_cwd, env):
|
| 1095 |
+
# Test that specifying a `long_description_content_type` keyword arg to
|
| 1096 |
+
# the `setup` function results in writing a `Description-Content-Type`
|
| 1097 |
+
# line to the `PKG-INFO` file in the `<distribution>.egg-info`
|
| 1098 |
+
# directory.
|
| 1099 |
+
# `Description-Content-Type` is described at
|
| 1100 |
+
# https://github.com/pypa/python-packaging-user-guide/pull/258
|
| 1101 |
+
|
| 1102 |
+
self._setup_script_with_requires(
|
| 1103 |
+
"""long_description_content_type='text/markdown',"""
|
| 1104 |
+
)
|
| 1105 |
+
environ = os.environ.copy().update(
|
| 1106 |
+
HOME=env.paths['home'],
|
| 1107 |
+
)
|
| 1108 |
+
environment.run_setup_py(
|
| 1109 |
+
cmd=['egg_info'],
|
| 1110 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1111 |
+
data_stream=1,
|
| 1112 |
+
env=environ,
|
| 1113 |
+
)
|
| 1114 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1115 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1116 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1117 |
+
expected_line = 'Description-Content-Type: text/markdown'
|
| 1118 |
+
assert expected_line in pkg_info_lines
|
| 1119 |
+
assert 'Metadata-Version: 2.4' in pkg_info_lines
|
| 1120 |
+
|
| 1121 |
+
def test_long_description(self, tmpdir_cwd, env):
|
| 1122 |
+
# Test that specifying `long_description` and `long_description_content_type`
|
| 1123 |
+
# keyword args to the `setup` function results in writing
|
| 1124 |
+
# the description in the message payload of the `PKG-INFO` file
|
| 1125 |
+
# in the `<distribution>.egg-info` directory.
|
| 1126 |
+
self._setup_script_with_requires(
|
| 1127 |
+
"long_description='This is a long description\\nover multiple lines',"
|
| 1128 |
+
"long_description_content_type='text/markdown',"
|
| 1129 |
+
)
|
| 1130 |
+
environment.run_setup_py(
|
| 1131 |
+
cmd=['egg_info'],
|
| 1132 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1133 |
+
data_stream=1,
|
| 1134 |
+
)
|
| 1135 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1136 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1137 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1138 |
+
assert 'Metadata-Version: 2.4' in pkg_info_lines
|
| 1139 |
+
assert '' == pkg_info_lines[-1] # last line should be empty
|
| 1140 |
+
long_desc_lines = pkg_info_lines[pkg_info_lines.index('') :]
|
| 1141 |
+
assert 'This is a long description' in long_desc_lines
|
| 1142 |
+
assert 'over multiple lines' in long_desc_lines
|
| 1143 |
+
|
| 1144 |
+
def test_project_urls(self, tmpdir_cwd, env):
|
| 1145 |
+
# Test that specifying a `project_urls` dict to the `setup`
|
| 1146 |
+
# function results in writing multiple `Project-URL` lines to
|
| 1147 |
+
# the `PKG-INFO` file in the `<distribution>.egg-info`
|
| 1148 |
+
# directory.
|
| 1149 |
+
# `Project-URL` is described at https://packaging.python.org
|
| 1150 |
+
# /specifications/core-metadata/#project-url-multiple-use
|
| 1151 |
+
|
| 1152 |
+
self._setup_script_with_requires(
|
| 1153 |
+
"""project_urls={
|
| 1154 |
+
'Link One': 'https://example.com/one/',
|
| 1155 |
+
'Link Two': 'https://example.com/two/',
|
| 1156 |
+
},"""
|
| 1157 |
+
)
|
| 1158 |
+
environ = os.environ.copy().update(
|
| 1159 |
+
HOME=env.paths['home'],
|
| 1160 |
+
)
|
| 1161 |
+
environment.run_setup_py(
|
| 1162 |
+
cmd=['egg_info'],
|
| 1163 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1164 |
+
data_stream=1,
|
| 1165 |
+
env=environ,
|
| 1166 |
+
)
|
| 1167 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1168 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1169 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1170 |
+
expected_line = 'Project-URL: Link One, https://example.com/one/'
|
| 1171 |
+
assert expected_line in pkg_info_lines
|
| 1172 |
+
expected_line = 'Project-URL: Link Two, https://example.com/two/'
|
| 1173 |
+
assert expected_line in pkg_info_lines
|
| 1174 |
+
assert self._extract_mv_version(pkg_info_lines) >= (1, 2)
|
| 1175 |
+
|
| 1176 |
+
def test_license(self, tmpdir_cwd, env):
|
| 1177 |
+
"""Test single line license."""
|
| 1178 |
+
self._setup_script_with_requires("license='MIT',")
|
| 1179 |
+
environment.run_setup_py(
|
| 1180 |
+
cmd=['egg_info'],
|
| 1181 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1182 |
+
data_stream=1,
|
| 1183 |
+
)
|
| 1184 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1185 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1186 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1187 |
+
assert 'License: MIT' in pkg_info_lines
|
| 1188 |
+
|
| 1189 |
+
def test_license_escape(self, tmpdir_cwd, env):
|
| 1190 |
+
"""Test license is escaped correctly if longer than one line."""
|
| 1191 |
+
self._setup_script_with_requires(
|
| 1192 |
+
"license='This is a long license text \\nover multiple lines',"
|
| 1193 |
+
)
|
| 1194 |
+
environment.run_setup_py(
|
| 1195 |
+
cmd=['egg_info'],
|
| 1196 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1197 |
+
data_stream=1,
|
| 1198 |
+
)
|
| 1199 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1200 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1201 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1202 |
+
|
| 1203 |
+
assert 'License: This is a long license text ' in pkg_info_lines
|
| 1204 |
+
assert ' over multiple lines' in pkg_info_lines
|
| 1205 |
+
assert 'text \n over multiple' in '\n'.join(pkg_info_lines)
|
| 1206 |
+
|
| 1207 |
+
def test_python_requires_egg_info(self, tmpdir_cwd, env):
|
| 1208 |
+
self._setup_script_with_requires("""python_requires='>=2.7.12',""")
|
| 1209 |
+
environ = os.environ.copy().update(
|
| 1210 |
+
HOME=env.paths['home'],
|
| 1211 |
+
)
|
| 1212 |
+
environment.run_setup_py(
|
| 1213 |
+
cmd=['egg_info'],
|
| 1214 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1215 |
+
data_stream=1,
|
| 1216 |
+
env=environ,
|
| 1217 |
+
)
|
| 1218 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1219 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1220 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1221 |
+
assert 'Requires-Python: >=2.7.12' in pkg_info_lines
|
| 1222 |
+
assert self._extract_mv_version(pkg_info_lines) >= (1, 2)
|
| 1223 |
+
|
| 1224 |
+
def test_manifest_maker_warning_suppression(self):
|
| 1225 |
+
fixtures = [
|
| 1226 |
+
"standard file not found: should have one of foo.py, bar.py",
|
| 1227 |
+
"standard file 'setup.py' not found",
|
| 1228 |
+
]
|
| 1229 |
+
|
| 1230 |
+
for msg in fixtures:
|
| 1231 |
+
assert manifest_maker._should_suppress_warning(msg)
|
| 1232 |
+
|
| 1233 |
+
def test_egg_info_includes_setup_py(self, tmpdir_cwd):
|
| 1234 |
+
self._create_project()
|
| 1235 |
+
dist = Distribution({"name": "foo", "version": "0.0.1"})
|
| 1236 |
+
dist.script_name = "non_setup.py"
|
| 1237 |
+
egg_info_instance = egg_info(dist)
|
| 1238 |
+
egg_info_instance.finalize_options()
|
| 1239 |
+
egg_info_instance.run()
|
| 1240 |
+
|
| 1241 |
+
assert 'setup.py' in egg_info_instance.filelist.files
|
| 1242 |
+
|
| 1243 |
+
with open(egg_info_instance.egg_info + "/SOURCES.txt", encoding="utf-8") as f:
|
| 1244 |
+
sources = f.read().split('\n')
|
| 1245 |
+
assert 'setup.py' in sources
|
| 1246 |
+
|
| 1247 |
+
def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
|
| 1248 |
+
environ = os.environ.copy().update(
|
| 1249 |
+
HOME=env.paths['home'],
|
| 1250 |
+
)
|
| 1251 |
+
if cmd is None:
|
| 1252 |
+
cmd = [
|
| 1253 |
+
'egg_info',
|
| 1254 |
+
]
|
| 1255 |
+
code, data = environment.run_setup_py(
|
| 1256 |
+
cmd=cmd,
|
| 1257 |
+
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
|
| 1258 |
+
data_stream=1,
|
| 1259 |
+
env=environ,
|
| 1260 |
+
)
|
| 1261 |
+
assert not code, data
|
| 1262 |
+
|
| 1263 |
+
if output:
|
| 1264 |
+
assert output in data
|
| 1265 |
+
|
| 1266 |
+
def test_egg_info_tag_only_once(self, tmpdir_cwd, env):
|
| 1267 |
+
self._create_project()
|
| 1268 |
+
path.build({
|
| 1269 |
+
'setup.cfg': DALS(
|
| 1270 |
+
"""
|
| 1271 |
+
[egg_info]
|
| 1272 |
+
tag_build = dev
|
| 1273 |
+
tag_date = 0
|
| 1274 |
+
tag_svn_revision = 0
|
| 1275 |
+
"""
|
| 1276 |
+
),
|
| 1277 |
+
})
|
| 1278 |
+
self._run_egg_info_command(tmpdir_cwd, env)
|
| 1279 |
+
egg_info_dir = os.path.join('.', 'foo.egg-info')
|
| 1280 |
+
with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
|
| 1281 |
+
pkg_info_lines = fp.read().split('\n')
|
| 1282 |
+
assert 'Version: 0.0.0.dev0' in pkg_info_lines
|
| 1283 |
+
|
| 1284 |
+
|
| 1285 |
+
class TestWriteEntries:
|
| 1286 |
+
def test_invalid_entry_point(self, tmpdir_cwd, env):
|
| 1287 |
+
dist = Distribution({"name": "foo", "version": "0.0.1"})
|
| 1288 |
+
dist.entry_points = {"foo": "foo = invalid-identifier:foo"}
|
| 1289 |
+
cmd = dist.get_command_obj("egg_info")
|
| 1290 |
+
expected_msg = r"Problems to parse .*invalid-identifier.*"
|
| 1291 |
+
with pytest.raises(errors.OptionError, match=expected_msg) as ex:
|
| 1292 |
+
write_entries(cmd, "entry_points", "entry_points.txt")
|
| 1293 |
+
assert "ensure entry-point follows the spec" in ex.value.args[0]
|
| 1294 |
+
|
| 1295 |
+
def test_valid_entry_point(self, tmpdir_cwd, env):
|
| 1296 |
+
dist = Distribution({"name": "foo", "version": "0.0.1"})
|
| 1297 |
+
dist.entry_points = {
|
| 1298 |
+
"abc": "foo = bar:baz",
|
| 1299 |
+
"def": ["faa = bor:boz"],
|
| 1300 |
+
}
|
| 1301 |
+
cmd = dist.get_command_obj("egg_info")
|
| 1302 |
+
write_entries(cmd, "entry_points", "entry_points.txt")
|
| 1303 |
+
content = Path("entry_points.txt").read_text(encoding="utf-8")
|
| 1304 |
+
assert "[abc]\nfoo = bar:baz\n" in content
|
| 1305 |
+
assert "[def]\nfaa = bor:boz\n" in content
|
venv/lib/python3.11/site-packages/setuptools/tests/test_extern.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib
|
| 2 |
+
import pickle
|
| 3 |
+
|
| 4 |
+
import packaging
|
| 5 |
+
|
| 6 |
+
from setuptools import Distribution
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_reimport_extern():
|
| 10 |
+
packaging2 = importlib.import_module(packaging.__name__)
|
| 11 |
+
assert packaging is packaging2
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_distribution_picklable():
|
| 15 |
+
pickle.loads(pickle.dumps(Distribution()))
|
venv/lib/python3.11/site-packages/setuptools/tests/test_find_packages.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for automatic package discovery"""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import tempfile
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
|
| 9 |
+
from setuptools import find_namespace_packages, find_packages
|
| 10 |
+
from setuptools.discovery import FlatLayoutPackageFinder
|
| 11 |
+
|
| 12 |
+
from .compat.py39 import os_helper
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class TestFindPackages:
|
| 16 |
+
def setup_method(self, method):
|
| 17 |
+
self.dist_dir = tempfile.mkdtemp()
|
| 18 |
+
self._make_pkg_structure()
|
| 19 |
+
|
| 20 |
+
def teardown_method(self, method):
|
| 21 |
+
shutil.rmtree(self.dist_dir)
|
| 22 |
+
|
| 23 |
+
def _make_pkg_structure(self):
|
| 24 |
+
"""Make basic package structure.
|
| 25 |
+
|
| 26 |
+
dist/
|
| 27 |
+
docs/
|
| 28 |
+
conf.py
|
| 29 |
+
pkg/
|
| 30 |
+
__pycache__/
|
| 31 |
+
nspkg/
|
| 32 |
+
mod.py
|
| 33 |
+
subpkg/
|
| 34 |
+
assets/
|
| 35 |
+
asset
|
| 36 |
+
__init__.py
|
| 37 |
+
setup.py
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
self.docs_dir = self._mkdir('docs', self.dist_dir)
|
| 41 |
+
self._touch('conf.py', self.docs_dir)
|
| 42 |
+
self.pkg_dir = self._mkdir('pkg', self.dist_dir)
|
| 43 |
+
self._mkdir('__pycache__', self.pkg_dir)
|
| 44 |
+
self.ns_pkg_dir = self._mkdir('nspkg', self.pkg_dir)
|
| 45 |
+
self._touch('mod.py', self.ns_pkg_dir)
|
| 46 |
+
self.sub_pkg_dir = self._mkdir('subpkg', self.pkg_dir)
|
| 47 |
+
self.asset_dir = self._mkdir('assets', self.sub_pkg_dir)
|
| 48 |
+
self._touch('asset', self.asset_dir)
|
| 49 |
+
self._touch('__init__.py', self.sub_pkg_dir)
|
| 50 |
+
self._touch('setup.py', self.dist_dir)
|
| 51 |
+
|
| 52 |
+
def _mkdir(self, path, parent_dir=None):
|
| 53 |
+
if parent_dir:
|
| 54 |
+
path = os.path.join(parent_dir, path)
|
| 55 |
+
os.mkdir(path)
|
| 56 |
+
return path
|
| 57 |
+
|
| 58 |
+
def _touch(self, path, dir_=None):
|
| 59 |
+
if dir_:
|
| 60 |
+
path = os.path.join(dir_, path)
|
| 61 |
+
open(path, 'wb').close()
|
| 62 |
+
return path
|
| 63 |
+
|
| 64 |
+
def test_regular_package(self):
|
| 65 |
+
self._touch('__init__.py', self.pkg_dir)
|
| 66 |
+
packages = find_packages(self.dist_dir)
|
| 67 |
+
assert packages == ['pkg', 'pkg.subpkg']
|
| 68 |
+
|
| 69 |
+
def test_exclude(self):
|
| 70 |
+
self._touch('__init__.py', self.pkg_dir)
|
| 71 |
+
packages = find_packages(self.dist_dir, exclude=('pkg.*',))
|
| 72 |
+
assert packages == ['pkg']
|
| 73 |
+
|
| 74 |
+
def test_exclude_recursive(self):
|
| 75 |
+
"""
|
| 76 |
+
Excluding a parent package should not exclude child packages as well.
|
| 77 |
+
"""
|
| 78 |
+
self._touch('__init__.py', self.pkg_dir)
|
| 79 |
+
self._touch('__init__.py', self.sub_pkg_dir)
|
| 80 |
+
packages = find_packages(self.dist_dir, exclude=('pkg',))
|
| 81 |
+
assert packages == ['pkg.subpkg']
|
| 82 |
+
|
| 83 |
+
def test_include_excludes_other(self):
|
| 84 |
+
"""
|
| 85 |
+
If include is specified, other packages should be excluded.
|
| 86 |
+
"""
|
| 87 |
+
self._touch('__init__.py', self.pkg_dir)
|
| 88 |
+
alt_dir = self._mkdir('other_pkg', self.dist_dir)
|
| 89 |
+
self._touch('__init__.py', alt_dir)
|
| 90 |
+
packages = find_packages(self.dist_dir, include=['other_pkg'])
|
| 91 |
+
assert packages == ['other_pkg']
|
| 92 |
+
|
| 93 |
+
def test_dir_with_dot_is_skipped(self):
|
| 94 |
+
shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
|
| 95 |
+
data_dir = self._mkdir('some.data', self.pkg_dir)
|
| 96 |
+
self._touch('__init__.py', data_dir)
|
| 97 |
+
self._touch('file.dat', data_dir)
|
| 98 |
+
packages = find_packages(self.dist_dir)
|
| 99 |
+
assert 'pkg.some.data' not in packages
|
| 100 |
+
|
| 101 |
+
def test_dir_with_packages_in_subdir_is_excluded(self):
|
| 102 |
+
"""
|
| 103 |
+
Ensure that a package in a non-package such as build/pkg/__init__.py
|
| 104 |
+
is excluded.
|
| 105 |
+
"""
|
| 106 |
+
build_dir = self._mkdir('build', self.dist_dir)
|
| 107 |
+
build_pkg_dir = self._mkdir('pkg', build_dir)
|
| 108 |
+
self._touch('__init__.py', build_pkg_dir)
|
| 109 |
+
packages = find_packages(self.dist_dir)
|
| 110 |
+
assert 'build.pkg' not in packages
|
| 111 |
+
|
| 112 |
+
@pytest.mark.skipif(not os_helper.can_symlink(), reason='Symlink support required')
|
| 113 |
+
def test_symlinked_packages_are_included(self):
|
| 114 |
+
"""
|
| 115 |
+
A symbolically-linked directory should be treated like any other
|
| 116 |
+
directory when matched as a package.
|
| 117 |
+
|
| 118 |
+
Create a link from lpkg -> pkg.
|
| 119 |
+
"""
|
| 120 |
+
self._touch('__init__.py', self.pkg_dir)
|
| 121 |
+
linked_pkg = os.path.join(self.dist_dir, 'lpkg')
|
| 122 |
+
os.symlink('pkg', linked_pkg)
|
| 123 |
+
assert os.path.isdir(linked_pkg)
|
| 124 |
+
packages = find_packages(self.dist_dir)
|
| 125 |
+
assert 'lpkg' in packages
|
| 126 |
+
|
| 127 |
+
def _assert_packages(self, actual, expected):
|
| 128 |
+
assert set(actual) == set(expected)
|
| 129 |
+
|
| 130 |
+
def test_pep420_ns_package(self):
|
| 131 |
+
packages = find_namespace_packages(
|
| 132 |
+
self.dist_dir, include=['pkg*'], exclude=['pkg.subpkg.assets']
|
| 133 |
+
)
|
| 134 |
+
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
|
| 135 |
+
|
| 136 |
+
def test_pep420_ns_package_no_includes(self):
|
| 137 |
+
packages = find_namespace_packages(self.dist_dir, exclude=['pkg.subpkg.assets'])
|
| 138 |
+
self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
|
| 139 |
+
|
| 140 |
+
def test_pep420_ns_package_no_includes_or_excludes(self):
|
| 141 |
+
packages = find_namespace_packages(self.dist_dir)
|
| 142 |
+
expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
|
| 143 |
+
self._assert_packages(packages, expected)
|
| 144 |
+
|
| 145 |
+
def test_regular_package_with_nested_pep420_ns_packages(self):
|
| 146 |
+
self._touch('__init__.py', self.pkg_dir)
|
| 147 |
+
packages = find_namespace_packages(
|
| 148 |
+
self.dist_dir, exclude=['docs', 'pkg.subpkg.assets']
|
| 149 |
+
)
|
| 150 |
+
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
|
| 151 |
+
|
| 152 |
+
def test_pep420_ns_package_no_non_package_dirs(self):
|
| 153 |
+
shutil.rmtree(self.docs_dir)
|
| 154 |
+
shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
|
| 155 |
+
packages = find_namespace_packages(self.dist_dir)
|
| 156 |
+
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class TestFlatLayoutPackageFinder:
|
| 160 |
+
EXAMPLES = {
|
| 161 |
+
"hidden-folders": (
|
| 162 |
+
[".pkg/__init__.py", "pkg/__init__.py", "pkg/nested/file.txt"],
|
| 163 |
+
["pkg", "pkg.nested"],
|
| 164 |
+
),
|
| 165 |
+
"private-packages": (
|
| 166 |
+
["_pkg/__init__.py", "pkg/_private/__init__.py"],
|
| 167 |
+
["pkg", "pkg._private"],
|
| 168 |
+
),
|
| 169 |
+
"invalid-name": (
|
| 170 |
+
["invalid-pkg/__init__.py", "other.pkg/__init__.py", "yet,another/file.py"],
|
| 171 |
+
[],
|
| 172 |
+
),
|
| 173 |
+
"docs": (["pkg/__init__.py", "docs/conf.py", "docs/readme.rst"], ["pkg"]),
|
| 174 |
+
"tests": (
|
| 175 |
+
["pkg/__init__.py", "tests/test_pkg.py", "tests/__init__.py"],
|
| 176 |
+
["pkg"],
|
| 177 |
+
),
|
| 178 |
+
"examples": (
|
| 179 |
+
[
|
| 180 |
+
"pkg/__init__.py",
|
| 181 |
+
"examples/__init__.py",
|
| 182 |
+
"examples/file.py",
|
| 183 |
+
"example/other_file.py",
|
| 184 |
+
# Sub-packages should always be fine
|
| 185 |
+
"pkg/example/__init__.py",
|
| 186 |
+
"pkg/examples/__init__.py",
|
| 187 |
+
],
|
| 188 |
+
["pkg", "pkg.examples", "pkg.example"],
|
| 189 |
+
),
|
| 190 |
+
"tool-specific": (
|
| 191 |
+
[
|
| 192 |
+
"htmlcov/index.html",
|
| 193 |
+
"pkg/__init__.py",
|
| 194 |
+
"tasks/__init__.py",
|
| 195 |
+
"tasks/subpackage/__init__.py",
|
| 196 |
+
"fabfile/__init__.py",
|
| 197 |
+
"fabfile/subpackage/__init__.py",
|
| 198 |
+
# Sub-packages should always be fine
|
| 199 |
+
"pkg/tasks/__init__.py",
|
| 200 |
+
"pkg/fabfile/__init__.py",
|
| 201 |
+
],
|
| 202 |
+
["pkg", "pkg.tasks", "pkg.fabfile"],
|
| 203 |
+
),
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
@pytest.mark.parametrize("example", EXAMPLES.keys())
|
| 207 |
+
def test_unwanted_directories_not_included(self, tmp_path, example):
|
| 208 |
+
files, expected_packages = self.EXAMPLES[example]
|
| 209 |
+
ensure_files(tmp_path, files)
|
| 210 |
+
found_packages = FlatLayoutPackageFinder.find(str(tmp_path))
|
| 211 |
+
assert set(found_packages) == set(expected_packages)
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def ensure_files(root_path, files):
|
| 215 |
+
for file in files:
|
| 216 |
+
path = root_path / file
|
| 217 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 218 |
+
path.touch()
|
venv/lib/python3.11/site-packages/setuptools/tests/test_find_py_modules.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for automatic discovery of modules"""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
from setuptools.discovery import FlatLayoutModuleFinder, ModuleFinder
|
| 8 |
+
|
| 9 |
+
from .compat.py39 import os_helper
|
| 10 |
+
from .test_find_packages import ensure_files
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestModuleFinder:
|
| 14 |
+
def find(self, path, *args, **kwargs):
|
| 15 |
+
return set(ModuleFinder.find(str(path), *args, **kwargs))
|
| 16 |
+
|
| 17 |
+
EXAMPLES = {
|
| 18 |
+
# circumstance: (files, kwargs, expected_modules)
|
| 19 |
+
"simple_folder": (
|
| 20 |
+
["file.py", "other.py"],
|
| 21 |
+
{}, # kwargs
|
| 22 |
+
["file", "other"],
|
| 23 |
+
),
|
| 24 |
+
"exclude": (
|
| 25 |
+
["file.py", "other.py"],
|
| 26 |
+
{"exclude": ["f*"]},
|
| 27 |
+
["other"],
|
| 28 |
+
),
|
| 29 |
+
"include": (
|
| 30 |
+
["file.py", "fole.py", "other.py"],
|
| 31 |
+
{"include": ["f*"], "exclude": ["fo*"]},
|
| 32 |
+
["file"],
|
| 33 |
+
),
|
| 34 |
+
"invalid-name": (["my-file.py", "other.file.py"], {}, []),
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
@pytest.mark.parametrize("example", EXAMPLES.keys())
|
| 38 |
+
def test_finder(self, tmp_path, example):
|
| 39 |
+
files, kwargs, expected_modules = self.EXAMPLES[example]
|
| 40 |
+
ensure_files(tmp_path, files)
|
| 41 |
+
assert self.find(tmp_path, **kwargs) == set(expected_modules)
|
| 42 |
+
|
| 43 |
+
@pytest.mark.skipif(not os_helper.can_symlink(), reason='Symlink support required')
|
| 44 |
+
def test_symlinked_packages_are_included(self, tmp_path):
|
| 45 |
+
src = "_myfiles/file.py"
|
| 46 |
+
ensure_files(tmp_path, [src])
|
| 47 |
+
os.symlink(tmp_path / src, tmp_path / "link.py")
|
| 48 |
+
assert self.find(tmp_path) == {"link"}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class TestFlatLayoutModuleFinder:
|
| 52 |
+
def find(self, path, *args, **kwargs):
|
| 53 |
+
return set(FlatLayoutModuleFinder.find(str(path)))
|
| 54 |
+
|
| 55 |
+
EXAMPLES = {
|
| 56 |
+
# circumstance: (files, expected_modules)
|
| 57 |
+
"hidden-files": ([".module.py"], []),
|
| 58 |
+
"private-modules": (["_module.py"], []),
|
| 59 |
+
"common-names": (
|
| 60 |
+
["setup.py", "conftest.py", "test.py", "tests.py", "example.py", "mod.py"],
|
| 61 |
+
["mod"],
|
| 62 |
+
),
|
| 63 |
+
"tool-specific": (
|
| 64 |
+
["tasks.py", "fabfile.py", "noxfile.py", "dodo.py", "manage.py", "mod.py"],
|
| 65 |
+
["mod"],
|
| 66 |
+
),
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
@pytest.mark.parametrize("example", EXAMPLES.keys())
|
| 70 |
+
def test_unwanted_files_not_included(self, tmp_path, example):
|
| 71 |
+
files, expected_modules = self.EXAMPLES[example]
|
| 72 |
+
ensure_files(tmp_path, files)
|
| 73 |
+
assert self.find(tmp_path) == set(expected_modules)
|