nanaj commited on
Commit
bc2e44d
·
verified ·
1 Parent(s): 39f79f4

Upload folder using huggingface_hub (part 19)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/INSTALLER +1 -0
  2. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/METADATA +141 -0
  3. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/RECORD +872 -0
  4. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/WHEEL +5 -0
  5. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/entry_points.txt +51 -0
  6. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/licenses/LICENSE +17 -0
  7. venv/lib/python3.11/site-packages/setuptools-78.1.0.dist-info/top_level.txt +3 -0
  8. venv/lib/python3.11/site-packages/setuptools/tests/config/__init__.py +0 -0
  9. venv/lib/python3.11/site-packages/setuptools/tests/config/downloads/__init__.py +59 -0
  10. venv/lib/python3.11/site-packages/setuptools/tests/config/downloads/preload.py +18 -0
  11. venv/lib/python3.11/site-packages/setuptools/tests/config/setupcfg_examples.txt +22 -0
  12. venv/lib/python3.11/site-packages/setuptools/tests/config/test_apply_pyprojecttoml.py +772 -0
  13. venv/lib/python3.11/site-packages/setuptools/tests/config/test_expand.py +247 -0
  14. venv/lib/python3.11/site-packages/setuptools/tests/config/test_pyprojecttoml.py +396 -0
  15. venv/lib/python3.11/site-packages/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py +109 -0
  16. venv/lib/python3.11/site-packages/setuptools/tests/config/test_setupcfg.py +980 -0
  17. venv/lib/python3.11/site-packages/setuptools/tests/contexts.py +145 -0
  18. venv/lib/python3.11/site-packages/setuptools/tests/environment.py +95 -0
  19. venv/lib/python3.11/site-packages/setuptools/tests/fixtures.py +157 -0
  20. venv/lib/python3.11/site-packages/setuptools/tests/indexes/test_links_priority/external.html +3 -0
  21. venv/lib/python3.11/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html +4 -0
  22. venv/lib/python3.11/site-packages/setuptools/tests/integration/__init__.py +0 -0
  23. venv/lib/python3.11/site-packages/setuptools/tests/integration/helpers.py +77 -0
  24. venv/lib/python3.11/site-packages/setuptools/tests/integration/test_pip_install_sdist.py +223 -0
  25. venv/lib/python3.11/site-packages/setuptools/tests/mod_with_constant.py +1 -0
  26. venv/lib/python3.11/site-packages/setuptools/tests/namespaces.py +90 -0
  27. venv/lib/python3.11/site-packages/setuptools/tests/script-with-bom.py +1 -0
  28. venv/lib/python3.11/site-packages/setuptools/tests/server.py +86 -0
  29. venv/lib/python3.11/site-packages/setuptools/tests/test_archive_util.py +36 -0
  30. venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_deprecations.py +28 -0
  31. venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_egg.py +73 -0
  32. venv/lib/python3.11/site-packages/setuptools/tests/test_bdist_wheel.py +708 -0
  33. venv/lib/python3.11/site-packages/setuptools/tests/test_build.py +33 -0
  34. venv/lib/python3.11/site-packages/setuptools/tests/test_build_clib.py +84 -0
  35. venv/lib/python3.11/site-packages/setuptools/tests/test_build_ext.py +293 -0
  36. venv/lib/python3.11/site-packages/setuptools/tests/test_build_meta.py +983 -0
  37. venv/lib/python3.11/site-packages/setuptools/tests/test_build_py.py +480 -0
  38. venv/lib/python3.11/site-packages/setuptools/tests/test_config_discovery.py +647 -0
  39. venv/lib/python3.11/site-packages/setuptools/tests/test_core_metadata.py +622 -0
  40. venv/lib/python3.11/site-packages/setuptools/tests/test_depends.py +15 -0
  41. venv/lib/python3.11/site-packages/setuptools/tests/test_develop.py +175 -0
  42. venv/lib/python3.11/site-packages/setuptools/tests/test_dist.py +278 -0
  43. venv/lib/python3.11/site-packages/setuptools/tests/test_dist_info.py +210 -0
  44. venv/lib/python3.11/site-packages/setuptools/tests/test_distutils_adoption.py +198 -0
  45. venv/lib/python3.11/site-packages/setuptools/tests/test_easy_install.py +1476 -0
  46. venv/lib/python3.11/site-packages/setuptools/tests/test_editable_install.py +1289 -0
  47. venv/lib/python3.11/site-packages/setuptools/tests/test_egg_info.py +1305 -0
  48. venv/lib/python3.11/site-packages/setuptools/tests/test_extern.py +15 -0
  49. venv/lib/python3.11/site-packages/setuptools/tests/test_find_packages.py +218 -0
  50. 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)