Commit cfbefe57 authored by Jason R. Coombs's avatar Jason R. Coombs

Merge branch 'master' into jorikdima-master

parents 7843688b 060445bf
[bumpversion]
current_version = 46.0.0
commit = True
tag = True
[bumpversion:file:setup.cfg]
[flake8]
exclude=
.tox
setuptools/_vendor,
pkg_resources/_vendor
ignore =
# W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513
W503
# W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545
W504
setuptools/site-patch.py F821
setuptools/py*compat.py F811
name: >-
👷
Test suite
on:
push:
pull_request:
schedule:
- cron: 1 0 * * * # Run daily at 0:01 UTC
jobs:
tests:
name: >-
${{ matrix.python-version }}
/
${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
# max-parallel: 5
matrix:
python-version:
- 3.8
- pypy3
- 3.7
- 3.6
- 3.5
os:
- ubuntu-latest
- ubuntu-16.04
- macOS-latest
# - windows-2019
# - windows-2016
env:
NETWORK_REQUIRED: 1
TOX_PARALLEL_NO_SPINNER: 1
TOXENV: python
steps:
- uses: actions/checkout@master
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1.1.1
with:
python-version: ${{ matrix.python-version }}
- name: Log Python version
run: >-
python --version
- name: Log Python location
run: >-
which python
- name: Log Python env
run: >-
python -m sysconfig
- name: Pip cache
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Upgrade pip/setuptools/wheel
run: >-
python
-m pip install
--disable-pip-version-check
--upgrade
pip setuptools wheel
- name: Install tox
run: >-
python -m pip install --upgrade tox tox-venv
- name: Log installed dists
run: >-
python -m pip freeze --all
- name: Adjust TOXENV for PyPy
if: startsWith(matrix.python-version, 'pypy')
run: >-
echo "::set-env name=TOXENV::${{ matrix.python-version }}"
- name: Log env vars
run: >-
env
- name: Verify that there's no cached Python modules in sources
if: >-
! startsWith(matrix.os, 'windows-')
run: >-
! grep pyc setuptools.egg-info/SOURCES.txt
- name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}'
run: >-
python -m
tox
--parallel auto
--notest
--skip-missing-interpreters false
- name: Test with tox
run: >-
python -m
tox
--parallel auto
--
--cov
pull_request_rules:
- name: auto-merge
conditions:
- base=master
- label=auto-merge
- status-success=continuous-integration/appveyor/pr
- status-success=continuous-integration/travis-ci/pr
- status-success=deploy/netlify
actions:
merge:
method: merge
python: python:
version: 3 version: 3
requirements_file: docs/requirements.txt extra_requirements:
- docs
pip_install: false pip_install: false
requirements: docs/requirements.txt
dist: trusty dist: xenial
language: python language: python
jobs: jobs:
...@@ -6,44 +6,29 @@ jobs: ...@@ -6,44 +6,29 @@ jobs:
include: include:
- &latest_py2 - &latest_py2
python: 2.7 python: 2.7
env: TOXENV=py27
- <<: *latest_py2 - <<: *latest_py2
env: LANG=C env: LANG=C TOXENV=py27
- python: pypy
env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow).
- python: pypy3 - python: pypy3
env: DISABLE_COVERAGE=1 env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow).
- python: 3.4
- python: 3.5 - python: 3.5
- &default_py - python: 3.6
python: 3.6 - python: 3.7
- &latest_py3 - &latest_py3
python: 3.7 python: 3.8
dist: xenial
- <<: *latest_py3 - <<: *latest_py3
env: LANG=C env: LANG=C
- python: 3.8-dev - python: 3.8-dev
dist: xenial - <<: *latest_py3
env: DISABLE_COVERAGE=1 # Ignore invalid coverage data. env: TOXENV=docs DISABLE_COVERAGE=1
- <<: *default_py - <<: *latest_py3
stage: deploy (to PyPI for tagged commits) stage: deploy
if: tag IS present if: tag IS present
install: skip script: tox -e release
script: skip after_success: skip
after_success: true allow_failures:
before_deploy: # suppress failures due to pypa/setuptools#2000
- python bootstrap.py - python: pypy3
- "! grep pyc setuptools.egg-info/SOURCES.txt"
deploy:
provider: pypi
on:
tags: true
all_branches: true
user: jaraco
password:
secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g=
distributions: release
skip_cleanup: true
skip_upload_docs: true
cache: pip cache: pip
...@@ -58,11 +43,10 @@ install: ...@@ -58,11 +43,10 @@ install:
- pip freeze --all - pip freeze --all
- env - env
# update egg_info based on setup.py in checkout
- python bootstrap.py
- "! grep pyc setuptools.egg-info/SOURCES.txt" - "! grep pyc setuptools.egg-info/SOURCES.txt"
script: script:
- export NETWORK_REQUIRED=1
- | - |
( # Run testsuite. ( # Run testsuite.
if [ -z "$DISABLE_COVERAGE" ] if [ -z "$DISABLE_COVERAGE" ]
......
This diff is collapsed.
...@@ -4,6 +4,7 @@ recursive-include setuptools/tests *.html ...@@ -4,6 +4,7 @@ recursive-include setuptools/tests *.html
recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html
recursive-include setuptools/_vendor *.py *.txt recursive-include setuptools/_vendor *.py *.txt
recursive-include pkg_resources *.py *.txt recursive-include pkg_resources *.py *.txt
recursive-include pkg_resources/tests/data *
include *.py include *.py
include *.rst include *.rst
include MANIFEST.in include MANIFEST.in
...@@ -12,3 +13,4 @@ include launcher.c ...@@ -12,3 +13,4 @@ include launcher.c
include msvc-build-launcher.cmd include msvc-build-launcher.cmd
include pytest.ini include pytest.ini
include tox.ini include tox.ini
exclude pyproject.toml # Temporary workaround for #1644.
.. image:: https://img.shields.io/pypi/v/setuptools.svg .. image:: https://img.shields.io/pypi/v/setuptools.svg
:target: https://pypi.org/project/setuptools :target: https://pypi.org/project/setuptools
.. image:: https://readthedocs.org/projects/setuptools/badge/?version=latest .. image:: https://img.shields.io/readthedocs/setuptools/latest.svg
:target: https://setuptools.readthedocs.io :target: https://setuptools.readthedocs.io
.. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20build%20%40%20Travis%20CI .. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20CI&logo=travis&logoColor=white
:target: https://travis-ci.org/pypa/setuptools :target: https://travis-ci.org/pypa/setuptools
.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20build%20%40%20Appveyor .. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20CI&logo=appveyor&logoColor=white
:target: https://ci.appveyor.com/project/pypa/setuptools/branch/master :target: https://ci.appveyor.com/project/pypa/setuptools/branch/master
.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg .. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white
:target: https://codecov.io/gh/pypa/setuptools :target: https://codecov.io/gh/pypa/setuptools
.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg .. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat
.. image:: https://tidelift.com/badges/github/pypa/setuptools
:target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme
.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
See the `Installation Instructions See the `Installation Instructions
<https://packaging.python.org/installing/>`_ in the Python Packaging <https://packaging.python.org/installing/>`_ in the Python Packaging
User's Guide for instructions on installing, upgrading, and uninstalling User's Guide for instructions on installing, upgrading, and uninstalling
...@@ -29,9 +29,22 @@ Bug reports and especially tested patches may be ...@@ -29,9 +29,22 @@ Bug reports and especially tested patches may be
submitted directly to the `bug tracker submitted directly to the `bug tracker
<https://github.com/pypa/setuptools/issues>`_. <https://github.com/pypa/setuptools/issues>`_.
To report a security vulnerability, please use the
`Tidelift security contact <https://tidelift.com/security>`_.
Tidelift will coordinate the fix and disclosure.
For Enterprise
==============
Available as part of the Tidelift Subscription.
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.
`Learn more <https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=referral&utm_campaign=github>`_.
Code of Conduct Code of Conduct
--------------- ===============
Everyone interacting in the setuptools project's codebases, issue trackers, Everyone interacting in the setuptools project's codebases, issue trackers,
chat rooms, and mailing lists is expected to follow the chat rooms, and mailing lists is expected to follow the
......
...@@ -3,13 +3,23 @@ clone_depth: 50 ...@@ -3,13 +3,23 @@ clone_depth: 50
environment: environment:
APPVEYOR: True APPVEYOR: True
NETWORK_REQUIRED: True
CODECOV_ENV: APPVEYOR_JOB_NAME CODECOV_ENV: APPVEYOR_JOB_NAME
matrix: matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
APPVEYOR_JOB_NAME: "python35-x64-vs2015"
PYTHON: "C:\\Python35-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
APPVEYOR_JOB_NAME: "python35-x64-vs2017"
PYTHON: "C:\\Python35-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
APPVEYOR_JOB_NAME: "python35-x64-vs2019"
PYTHON: "C:\\Python35-x64"
- APPVEYOR_JOB_NAME: "python36-x64" - APPVEYOR_JOB_NAME: "python36-x64"
PYTHON: "C:\\Python36-x64" PYTHON: "C:\\Python36-x64"
- APPVEYOR_JOB_NAME: "python27-x64" - APPVEYOR_JOB_NAME: "python37-x64"
PYTHON: "C:\\Python27-x64" PYTHON: "C:\\Python37-x64"
install: install:
# symlink python from a directory with a space # symlink python from a directory with a space
...@@ -25,9 +35,8 @@ cache: ...@@ -25,9 +35,8 @@ cache:
test_script: test_script:
- python --version - python --version
- python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel - python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel
- pip install --upgrade tox tox-venv - pip install --upgrade tox tox-venv virtualenv
- pip freeze --all - pip freeze --all
- python bootstrap.py
- tox -- --cov - tox -- --cov
after_test: after_test:
......
# Create the project in Azure with:
# az devops project create --name $name --organization https://dev.azure.com/$org/ --visibility public
# then configure the pipelines (through web UI)
trigger:
branches:
include:
- '*'
tags:
include:
- '*'
pool:
vmimage: 'Ubuntu-18.04'
variables:
- group: Azure secrets
stages:
- stage: Test
jobs:
- job: 'Test'
strategy:
matrix:
Python36:
python.version: '3.6'
Python38:
python.version: '3.8'
maxParallel: 4
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
architecture: 'x64'
- script: python -m pip install tox
displayName: 'Install tox'
- script: |
tox -- --junit-xml=test-results.xml
displayName: 'run tests'
- task: PublishTestResults@2
inputs:
testResultsFiles: '**/test-results.xml'
testRunTitle: 'Python $(python.version)'
condition: succeededOrFailed()
- stage: Publish
dependsOn: Test
jobs:
- job: 'Publish'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
architecture: 'x64'
- script: python -m pip install tox
displayName: 'Install tox'
- script: |
tox -e release
env:
TWINE_PASSWORD: $(PyPI-token)
displayName: 'publish to PyPI'
condition: contains(variables['Build.SourceBranch'], 'tags')
...@@ -25,6 +25,7 @@ minimal_egg_info = textwrap.dedent(""" ...@@ -25,6 +25,7 @@ minimal_egg_info = textwrap.dedent("""
entry_points = setuptools.dist:check_entry_points entry_points = setuptools.dist:check_entry_points
[egg_info.writers] [egg_info.writers]
PKG-INFO = setuptools.command.egg_info:write_pkg_info
dependency_links.txt = setuptools.command.egg_info:overwrite_arg dependency_links.txt = setuptools.command.egg_info:overwrite_arg
entry_points.txt = setuptools.command.egg_info:write_entries entry_points.txt = setuptools.command.egg_info:write_entries
requires.txt = setuptools.command.egg_info:write_requirements requires.txt = setuptools.command.egg_info:write_requirements
...@@ -35,10 +36,11 @@ def ensure_egg_info(): ...@@ -35,10 +36,11 @@ def ensure_egg_info():
if os.path.exists('setuptools.egg-info'): if os.path.exists('setuptools.egg-info'):
return return
print("adding minimal entry_points") print("adding minimal entry_points")
build_egg_info() add_minimal_info()
run_egg_info()
def build_egg_info(): def add_minimal_info():
""" """
Build a minimal egg-info, enough to invoke egg_info Build a minimal egg-info, enough to invoke egg_info
""" """
...@@ -52,13 +54,6 @@ def run_egg_info(): ...@@ -52,13 +54,6 @@ def run_egg_info():
cmd = [sys.executable, 'setup.py', 'egg_info'] cmd = [sys.executable, 'setup.py', 'egg_info']
print("Regenerating egg_info") print("Regenerating egg_info")
subprocess.check_call(cmd) subprocess.check_call(cmd)
print("...and again.")
subprocess.check_call(cmd)
def main():
ensure_egg_info()
run_egg_info()
__name__ == '__main__' and main() __name__ == '__main__' and ensure_egg_info()
Allow version number normalization to be bypassed by wrapping in a 'setuptools.sic()' call.
...@@ -19,6 +19,7 @@ collect_ignore = [ ...@@ -19,6 +19,7 @@ collect_ignore = [
if sys.version_info < (3,): if sys.version_info < (3,):
collect_ignore.append('setuptools/lib2to3_ex.py') collect_ignore.append('setuptools/lib2to3_ex.py')
collect_ignore.append('setuptools/_imp.py')
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
......
<h3 class="donation">For Enterprise</h3>
<p>
Professionally-supported {{ project }} is available with the
<a href="https://tidelift.com/subscription/pkg/pypi-{{ project }}?utm_source=pypi-{{ project }}&utm_medium=referral">Tidelift Subscription</a>.
</p>
<h3>Download</h3> <h3>Download</h3>
<p>Current version: <b>{{ version }}</b></p> <p>Current version: <b>{{ version }}</b></p>
...@@ -5,11 +12,4 @@ ...@@ -5,11 +12,4 @@
<h3>Questions? Suggestions? Contributions?</h3> <h3>Questions? Suggestions? Contributions?</h3>
<p>Visit the <a href="https://github.com/pypa/setuptools">Setuptools project page</a> </p> <p>Visit the <a href="{{ package_url }}">Project page</a> </p>
<h3 class="donation">Professional support</h3>
<p>
Professionally-supported {{ project }} is available with the
<a href="https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme">Tidelift Subscription</a>.
</p>
# -*- coding: utf-8 -*-
#
# Setuptools documentation build configuration file, created by
# sphinx-quickstart on Fri Jul 17 14:22:37 2009.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# The contents of this file are pickled, so don't put values in the namespace
# that aren't pickleable (module imports are okay, they're removed automatically).
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
import subprocess import subprocess
import sys import sys
import os import os
...@@ -26,14 +6,12 @@ import os ...@@ -26,14 +6,12 @@ import os
# hack to run the bootstrap script so that jaraco.packaging.sphinx # hack to run the bootstrap script so that jaraco.packaging.sphinx
# can invoke setup.py # can invoke setup.py
'READTHEDOCS' in os.environ and subprocess.check_call( 'READTHEDOCS' in os.environ and subprocess.check_call(
[sys.executable, 'bootstrap.py'], [sys.executable, '-m', 'bootstrap'],
cwd=os.path.join(os.path.dirname(__file__), os.path.pardir), cwd=os.path.join(os.path.dirname(__file__), os.path.pardir),
) )
# -- General configuration ----------------------------------------------------- # -- General configuration --
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['jaraco.packaging.sphinx', 'rst.linker'] extensions = ['jaraco.packaging.sphinx', 'rst.linker']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
...@@ -45,7 +23,8 @@ source_suffix = '.txt' ...@@ -45,7 +23,8 @@ source_suffix = '.txt'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
# A list of glob-style patterns that should be excluded when looking for source files. # A list of glob-style patterns that should be excluded
# when looking for source files.
exclude_patterns = ['requirements.txt'] exclude_patterns = ['requirements.txt']
# List of directories, relative to source directory, that shouldn't be searched # List of directories, relative to source directory, that shouldn't be searched
...@@ -55,7 +34,7 @@ exclude_trees = [] ...@@ -55,7 +34,7 @@ exclude_trees = []
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output --
# The theme to use for HTML and HTML Help pages. Major themes that come with # The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'. # Sphinx are currently 'default' and 'sphinxdoc'.
...@@ -69,7 +48,10 @@ html_theme_path = ['_theme'] ...@@ -69,7 +48,10 @@ html_theme_path = ['_theme']
html_use_smartypants = True html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
html_sidebars = {'index': 'indexsidebar.html'} html_sidebars = {
'index': [
'relations.html', 'sourcelink.html', 'indexsidebar.html',
'searchbox.html']}
# If false, no module index is generated. # If false, no module index is generated.
html_use_modindex = False html_use_modindex = False
...@@ -77,14 +59,15 @@ html_use_modindex = False ...@@ -77,14 +59,15 @@ html_use_modindex = False
# If false, no index is generated. # If false, no index is generated.
html_use_index = False html_use_index = False
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author,
latex_documents = [ # documentclass [howto/manual]).
('index', 'Setuptools.tex', 'Setuptools Documentation', latex_documents = [(
'The fellowship of the packaging', 'manual'), 'index', 'Setuptools.tex', 'Setuptools Documentation',
] 'The fellowship of the packaging', 'manual',
)]
link_files = { link_files = {
'../CHANGES.rst': dict( '../CHANGES.rst': dict(
......
...@@ -67,8 +67,8 @@ All PRs with code changes should include tests. All changes should include a ...@@ -67,8 +67,8 @@ All PRs with code changes should include tests. All changes should include a
changelog entry. changelog entry.
``setuptools`` uses `towncrier <https://pypi.org/project/towncrier/>`_ ``setuptools`` uses `towncrier <https://pypi.org/project/towncrier/>`_
for changelog managment, so when making a PR, please add a news fragment in the for changelog management, so when making a PR, please add a news fragment in the
``changelog.d/`` folder. Changelog files are written in Restructured Text and ``changelog.d/`` folder. Changelog files are written in reStructuredText and
should be a 1 or 2 sentence description of the substantive changes in the PR. should be a 1 or 2 sentence description of the substantive changes in the PR.
They should be named ``<pr_number>.<category>.rst``, where the categories are: They should be named ``<pr_number>.<category>.rst``, where the categories are:
...@@ -76,7 +76,7 @@ They should be named ``<pr_number>.<category>.rst``, where the categories are: ...@@ -76,7 +76,7 @@ They should be named ``<pr_number>.<category>.rst``, where the categories are:
- ``breaking``: Any backwards-compatibility breaking change - ``breaking``: Any backwards-compatibility breaking change
- ``doc``: A change to the documentation - ``doc``: A change to the documentation
- ``misc``: Changes internal to the repo like CI, test and build changes - ``misc``: Changes internal to the repo like CI, test and build changes
- ``deprecation``: For deprecations of an existing feature of behavior - ``deprecation``: For deprecations of an existing feature or behavior
A pull request may have more than one of these components, for example a code A pull request may have more than one of these components, for example a code
change may introduce a new feature that deprecates an old feature, in which change may introduce a new feature that deprecates an old feature, in which
...@@ -89,16 +89,23 @@ code changes. See the following for an example news fragment: ...@@ -89,16 +89,23 @@ code changes. See the following for an example news fragment:
$ cat changelog.d/1288.change.rst $ cat changelog.d/1288.change.rst
Add support for maintainer in PKG-INFO Add support for maintainer in PKG-INFO
-------------------
Auto-Merge Requests
-------------------
To support running all code through CI, even lightweight contributions,
the project employs Mergify to auto-merge pull requests tagged as
auto-merge.
Use ``hub pull-request -l auto-merge`` to create such a pull request
from the command line after pushing a new branch.
------- -------
Testing Testing
------- -------
The primary tests are run using tox. To run the tests, first create the metadata The primary tests are run using tox. Make sure you have tox installed,
needed to run the tests:: and invoke it::
$ python bootstrap.py
Then make sure you have tox installed, and invoke it::
$ tox $ tox
...@@ -126,3 +133,17 @@ To build the docs locally, use tox:: ...@@ -126,3 +133,17 @@ To build the docs locally, use tox::
.. _Sphinx: http://www.sphinx-doc.org/en/master/ .. _Sphinx: http://www.sphinx-doc.org/en/master/
.. _published documentation: https://setuptools.readthedocs.io/en/latest/ .. _published documentation: https://setuptools.readthedocs.io/en/latest/
---------------------
Vendored Dependencies
---------------------
Setuptools has some dependencies, but due to `bootstrapping issues
<https://github.com/pypa/setuptools/issues/980>`, those dependencies
cannot be declared as they won't be resolved soon enough to build
setuptools from source. Eventually, this limitation may be lifted as
PEP 517/518 reach ubiquitous adoption, but for now, Setuptools
cannot declare dependencies other than through
``setuptools/_vendor/vendored.txt`` and
``pkg_reosurces/_vendor/vendored.txt`` and refreshed by way of
``paver update_vendored`` (pavement.py).
...@@ -7,7 +7,7 @@ Authority (PyPA) and led by Jason R. Coombs. ...@@ -7,7 +7,7 @@ Authority (PyPA) and led by Jason R. Coombs.
This document describes the process by which Setuptools is developed. This document describes the process by which Setuptools is developed.
This document assumes the reader has some passing familiarity with This document assumes the reader has some passing familiarity with
*using* setuptools, the ``pkg_resources`` module, and EasyInstall. It *using* setuptools, the ``pkg_resources`` module, and pip. It
does not attempt to explain basic concepts like inter-project does not attempt to explain basic concepts like inter-project
dependencies, nor does it contain detailed lexical syntax for most dependencies, nor does it contain detailed lexical syntax for most
file formats. Neither does it explain concepts like "namespace file formats. Neither does it explain concepts like "namespace
......
...@@ -41,7 +41,7 @@ Please see the `setuptools PyPI page <https://pypi.org/project/setuptools/>`_ ...@@ -41,7 +41,7 @@ Please see the `setuptools PyPI page <https://pypi.org/project/setuptools/>`_
for download links and basic installation instructions for each of the for download links and basic installation instructions for each of the
supported platforms. supported platforms.
You will need at least Python 3.4 or 2.7. An ``easy_install`` script will be You will need at least Python 3.5 or 2.7. An ``easy_install`` script will be
installed in the normal location for Python scripts on your platform. installed in the normal location for Python scripts on your platform.
Note that the instructions on the setuptools PyPI page assume that you are Note that the instructions on the setuptools PyPI page assume that you are
...@@ -317,8 +317,8 @@ Note that instead of changing your ``PATH`` to include the Python scripts ...@@ -317,8 +317,8 @@ Note that instead of changing your ``PATH`` to include the Python scripts
directory, you can also retarget the installation location for scripts so they directory, you can also retarget the installation location for scripts so they
go on a directory that's already on the ``PATH``. For more information see go on a directory that's already on the ``PATH``. For more information see
`Command-Line Options`_ and `Configuration Files`_. During installation, `Command-Line Options`_ and `Configuration Files`_. During installation,
pass command line options (such as ``--script-dir``) to pass command line options (such as ``--script-dir``) to control where
``ez_setup.py`` to control where ``easy_install.exe`` will be installed. scripts will be installed.
Windows Executable Launcher Windows Executable Launcher
......
:orphan:
``ez_setup`` distribution guide
===============================
Using ``setuptools``... Without bundling it!
---------------------------------------------
.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py
.. _EasyInstall Installation Instructions: easy_install.html
.. _Custom Installation Locations: easy_install.html
Your users might not have ``setuptools`` installed on their machines, or even
if they do, it might not be the right version. Fixing this is easy; just
download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
script. (Be sure to add it to your revision control system, too.) Then add
these two lines to the very top of your setup script, before the script imports
anything from setuptools:
.. code-block:: python
import ez_setup
ez_setup.use_setuptools()
That's it. The ``ez_setup`` module will automatically download a matching
version of ``setuptools`` from PyPI, if it isn't present on the target system.
Whenever you install an updated version of setuptools, you should also update
your projects' ``ez_setup.py`` files, so that a matching version gets installed
on the target machine(s).
(By the way, if you need to distribute a specific version of ``setuptools``,
you can specify the exact version and base download URL as parameters to the
``use_setuptools()`` function. See the function's docstring for details.)
What Your Users Should Know
---------------------------
In general, a setuptools-based project looks just like any distutils-based
project -- as long as your users have an internet connection and are installing
to ``site-packages``, that is. But for some users, these conditions don't
apply, and they may become frustrated if this is their first encounter with
a setuptools-based project. To keep these users happy, you should review the
following topics in your project's installation instructions, if they are
relevant to your project and your target audience isn't already familiar with
setuptools and ``easy_install``.
Network Access
If your project is using ``ez_setup``, you should inform users of the
need to either have network access, or to preinstall the correct version of
setuptools using the `EasyInstall installation instructions`_. Those
instructions also have tips for dealing with firewalls as well as how to
manually download and install setuptools.
Custom Installation Locations
You should inform your users that if they are installing your project to
somewhere other than the main ``site-packages`` directory, they should
first install setuptools using the instructions for `Custom Installation
Locations`_, before installing your project.
Your Project's Dependencies
If your project depends on other projects that may need to be downloaded
from PyPI or elsewhere, you should list them in your installation
instructions, or tell users how to find out what they are. While most
users will not need this information, any users who don't have unrestricted
internet access may have to find, download, and install the other projects
manually. (Note, however, that they must still install those projects
using ``easy_install``, or your project will not know they are installed,
and your setup script will try to download them again.)
If you want to be especially friendly to users with limited network access,
you may wish to build eggs for your project and its dependencies, making
them all available for download from your site, or at least create a page
with links to all of the needed eggs. In this way, users with limited
network access can manually download all the eggs to a single directory,
then use the ``-f`` option of ``easy_install`` to specify the directory
to find eggs in. Users who have full network access can just use ``-f``
with the URL of your download page, and ``easy_install`` will find all the
needed eggs using your links directly. This is also useful when your
target audience isn't able to compile packages (e.g. most Windows users)
and your package or some of its dependencies include C code.
Revision Control System Users and Co-Developers
Users and co-developers who are tracking your in-development code using
a revision control system should probably read this manual's sections
regarding such development. Alternately, you may wish to create a
quick-reference guide containing the tips from this manual that apply to
your particular situation. For example, if you recommend that people use
``setup.py develop`` when tracking your in-development code, you should let
them know that this needs to be run after every update or commit.
Similarly, if you remove modules or data files from your project, you
should remind them to run ``setup.py clean --all`` and delete any obsolete
``.pyc`` or ``.pyo``. (This tip applies to the distutils in general, not
just setuptools, but not everybody knows about them; be kind to your users
by spelling out your project's best practices rather than leaving them
guessing.)
Creating System Packages
Some users want to manage all Python packages using a single package
manager, and sometimes that package manager isn't ``easy_install``!
Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and
``bdist_dumb`` formats for system packaging. If a user has a locally-
installed "bdist" packaging tool that internally uses the distutils
``install`` command, it should be able to work with ``setuptools``. Some
examples of "bdist" formats that this should work with include the
``bdist_nsi`` and ``bdist_msi`` formats for Windows.
However, packaging tools that build binary distributions by running
``setup.py install`` on the command line or as a subprocess will require
modification to work with setuptools. They should use the
``--single-version-externally-managed`` option to the ``install`` command,
combined with the standard ``--root`` or ``--record`` options.
See the `install command`_ documentation below for more details. The
``bdist_deb`` command is an example of a command that currently requires
this kind of patching to work with setuptools.
Please note that building system packages may require you to install
some system software, for example ``bdist_rpm`` requires the ``rpmbuild``
command to be installed.
If you or your users have a problem building a usable system package for
your project, please report the problem via the mailing list so that
either the "bdist" tool in question or setuptools can be modified to
resolve the issue.
Your users might not have ``setuptools`` installed on their machines, or even
if they do, it might not be the right version. Fixing this is easy; just
download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
script. (Be sure to add it to your revision control system, too.) Then add
these two lines to the very top of your setup script, before the script imports
anything from setuptools:
.. code-block:: python
import ez_setup
ez_setup.use_setuptools()
That's it. The ``ez_setup`` module will automatically download a matching
version of ``setuptools`` from PyPI, if it isn't present on the target system.
Whenever you install an updated version of setuptools, you should also update
your projects' ``ez_setup.py`` files, so that a matching version gets installed
on the target machine(s).
(By the way, if you need to distribute a specific version of ``setuptools``,
you can specify the exact version and base download URL as parameters to the
``use_setuptools()`` function. See the function's docstring for details.)
.. _install command:
``install`` - Run ``easy_install`` or old-style installation
============================================================
The setuptools ``install`` command is basically a shortcut to run the
``easy_install`` command on the current project. However, for convenience
in creating "system packages" of setuptools-based projects, you can also
use this option:
``--single-version-externally-managed``
This boolean option tells the ``install`` command to perform an "old style"
installation, with the addition of an ``.egg-info`` directory so that the
installed project will still have its metadata available and operate
normally. If you use this option, you *must* also specify the ``--root``
or ``--record`` options (or both), because otherwise you will have no way
to identify and remove the installed files.
This option is automatically in effect when ``install`` is invoked by another
distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm``
will create system packages of eggs. It is also automatically in effect if
you specify the ``--root`` option.
``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages``
==============================================================================
Setuptools runs this command as part of ``install`` operations that use the
``--single-version-externally-managed`` options. You should not invoke it
directly; it is documented here for completeness and so that distutils
extensions such as system package builders can make use of it. This command
has only one option:
``--install-dir=DIR, -d DIR``
The parent directory where the ``.egg-info`` directory will be placed.
Defaults to the same as the ``--install-dir`` option specified for the
``install_lib`` command, which is usually the system ``site-packages``
directory.
This command assumes that the ``egg_info`` command has been given valid options
via the command line or ``setup.cfg``, as it will invoke the ``egg_info``
command and use its options to locate the project's source ``.egg-info``
directory.
...@@ -299,11 +299,8 @@ specified by the ``setup_requires`` parameter to the Distribution. ...@@ -299,11 +299,8 @@ specified by the ``setup_requires`` parameter to the Distribution.
A list of dependency URLs, one per line, as specified using the A list of dependency URLs, one per line, as specified using the
``dependency_links`` keyword to ``setup()``. These may be direct ``dependency_links`` keyword to ``setup()``. These may be direct
download URLs, or the URLs of web pages containing direct download download URLs, or the URLs of web pages containing direct download
links, and will be used by EasyInstall to find dependencies, as though links. Please see the setuptools manual for more information on
the user had manually provided them via the ``--find-links`` command specifying this option.
line option. Please see the setuptools manual and EasyInstall manual
for more information on specifying this option, and for information on
how EasyInstall processes ``--find-links`` URLs.
``depends.txt`` -- Obsolete, do not create! ``depends.txt`` -- Obsolete, do not create!
......
...@@ -245,8 +245,8 @@ abbreviation for ``pkg_resources.working_set.require()``: ...@@ -245,8 +245,8 @@ abbreviation for ``pkg_resources.working_set.require()``:
interactive interpreter hacking than for production use. If you're creating interactive interpreter hacking than for production use. If you're creating
an actual library or application, it's strongly recommended that you create an actual library or application, it's strongly recommended that you create
a "setup.py" script using ``setuptools``, and declare all your requirements a "setup.py" script using ``setuptools``, and declare all your requirements
there. That way, tools like EasyInstall can automatically detect what there. That way, tools like pip can automatically detect what requirements
requirements your package has, and deal with them accordingly. your package has, and deal with them accordingly.
Note that calling ``require('SomePackage')`` will not install Note that calling ``require('SomePackage')`` will not install
``SomePackage`` if it isn't already present. If you need to do this, you ``SomePackage`` if it isn't already present. If you need to do this, you
...@@ -611,9 +611,9 @@ Requirements Parsing ...@@ -611,9 +611,9 @@ Requirements Parsing
or activation of both Report-O-Rama and any libraries it needs in order to or activation of both Report-O-Rama and any libraries it needs in order to
provide PDF support. For example, you could use:: provide PDF support. For example, you could use::
easy_install.py Report-O-Rama[PDF] pip install Report-O-Rama[PDF]
To install the necessary packages using the EasyInstall program, or call To install the necessary packages using pip, or call
``pkg_resources.require('Report-O-Rama[PDF]')`` to add the necessary ``pkg_resources.require('Report-O-Rama[PDF]')`` to add the necessary
distributions to sys.path at runtime. distributions to sys.path at runtime.
...@@ -1132,8 +1132,8 @@ relative to the root of the identified distribution; i.e. its first path ...@@ -1132,8 +1132,8 @@ relative to the root of the identified distribution; i.e. its first path
segment will be treated as a peer of the top-level modules or packages in the segment will be treated as a peer of the top-level modules or packages in the
distribution. distribution.
Note that resource names must be ``/``-separated paths and cannot be absolute Note that resource names must be ``/``-separated paths rooted at the package,
(i.e. no leading ``/``) or contain relative names like ``".."``. Do *not* use cannot contain relative names like ``".."``, and cannot be absolute. Do *not* use
``os.path`` routines to manipulate resource paths, as they are *not* filesystem ``os.path`` routines to manipulate resource paths, as they are *not* filesystem
paths. paths.
...@@ -1843,9 +1843,9 @@ History ...@@ -1843,9 +1843,9 @@ History
because it isn't necessarily a filesystem path (and hasn't been for some because it isn't necessarily a filesystem path (and hasn't been for some
time now). The ``location`` of ``Distribution`` objects in the filesystem time now). The ``location`` of ``Distribution`` objects in the filesystem
should always be normalized using ``pkg_resources.normalize_path()``; all should always be normalized using ``pkg_resources.normalize_path()``; all
of the setuptools and EasyInstall code that generates distributions from of the setuptools' code that generates distributions from the filesystem
the filesystem (including ``Distribution.from_filename()``) ensure this (including ``Distribution.from_filename()``) ensure this invariant, but if
invariant, but if you use a more generic API like ``Distribution()`` or you use a more generic API like ``Distribution()`` or
``Distribution.from_location()`` you should take care that you don't ``Distribution.from_location()`` you should take care that you don't
create a distribution with an un-normalized filesystem path. create a distribution with an un-normalized filesystem path.
......
...@@ -3,39 +3,13 @@ Release Process ...@@ -3,39 +3,13 @@ Release Process
=============== ===============
In order to allow for rapid, predictable releases, Setuptools uses a In order to allow for rapid, predictable releases, Setuptools uses a
mechanical technique for releases, enacted by Travis following a mechanical technique for releases, enacted on tagged commits by
successful build of a tagged release per continuous integration.
`PyPI deployment <https://docs.travis-ci.com/user/deployment/pypi>`_.
To finalize a release, run ``tox -e finalize``, review, then push
Prior to cutting a release, please use `towncrier`_ to update the changes.
``CHANGES.rst`` to summarize the changes since the last release.
To update the changelog: If tests pass, the release will be uploaded to PyPI.
1. Install towncrier via ``pip install towncrier`` if not already installed.
2. Preview the new ``CHANGES.rst`` entry by running
``towncrier --draft --version {new.version.number}`` (enter the desired
version number for the next release). If any changes are needed, make
them and generate a new preview until the output is acceptable. Run
``git add`` for any modified files.
3. Run ``towncrier --version {new.version.number}`` to stage the changelog
updates in git.
4. Verify that there are no remaining ``changelog.d/*.rst`` files. If a
file was named incorrectly, it may be ignored by towncrier.
5. Review the updated ``CHANGES.rst`` file. If any changes are needed,
make the edits and stage them via ``git add CHANGES.rst``.
Once the changelog edits are staged and ready to commit, cut a release by
installing and running ``bump2version --allow-dirty {part}`` where ``part``
is major, minor, or patch based on the scope of the changes in the
release. Then, push the commits to the master branch::
$ git push origin master
$ git push --tags
If tests pass, the release will be uploaded to PyPI (from the Python 3.6
tests).
.. _towncrier: https://pypi.org/project/towncrier/
Release Frequency Release Frequency
----------------- -----------------
......
sphinx!=1.8.0 # keep these in sync with setup.cfg
sphinx
jaraco.packaging>=6.1
rst.linker>=1.9 rst.linker>=1.9
jaraco.packaging>=3.2
setuptools>=34
This diff is collapsed.
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include <windows.h> #include <windows.h>
#include <tchar.h> #include <tchar.h>
#include <fcntl.h> #include <fcntl.h>
#include <process.h>
int child_pid=0; int child_pid=0;
......
# Configuration for pull request documentation previews via Netlify # Configuration for pull request documentation previews via Netlify
# Netlify relies on there being a ./runtime.txt to indicate Python 3.
[build] [build]
publish = "docs/build/html" publish = "build/html"
command = "pip install tox && tox -e docs" command = "pip install tox && tox -e docs"
...@@ -39,6 +39,8 @@ import tempfile ...@@ -39,6 +39,8 @@ import tempfile
import textwrap import textwrap
import itertools import itertools
import inspect import inspect
import ntpath
import posixpath
from pkgutil import get_importer from pkgutil import get_importer
try: try:
...@@ -81,13 +83,14 @@ __import__('pkg_resources.extern.packaging.version') ...@@ -81,13 +83,14 @@ __import__('pkg_resources.extern.packaging.version')
__import__('pkg_resources.extern.packaging.specifiers') __import__('pkg_resources.extern.packaging.specifiers')
__import__('pkg_resources.extern.packaging.requirements') __import__('pkg_resources.extern.packaging.requirements')
__import__('pkg_resources.extern.packaging.markers') __import__('pkg_resources.extern.packaging.markers')
__import__('pkg_resources.py2_warn')
__metaclass__ = type __metaclass__ = type
if (3, 0) < sys.version_info < (3, 4): if (3, 0) < sys.version_info < (3, 5):
raise RuntimeError("Python 3.4 or later is required") raise RuntimeError("Python 3.5 or later is required")
if six.PY2: if six.PY2:
# Those builtin exceptions are only defined in Python 3 # Those builtin exceptions are only defined in Python 3
...@@ -331,7 +334,7 @@ class UnknownExtra(ResolutionError): ...@@ -331,7 +334,7 @@ class UnknownExtra(ResolutionError):
_provider_factories = {} _provider_factories = {}
PY_MAJOR = sys.version[:3] PY_MAJOR = '{}.{}'.format(*sys.version_info)
EGG_DIST = 3 EGG_DIST = 3
BINARY_DIST = 2 BINARY_DIST = 2
SOURCE_DIST = 1 SOURCE_DIST = 1
...@@ -1232,12 +1235,13 @@ class ResourceManager: ...@@ -1232,12 +1235,13 @@ class ResourceManager:
mode = os.stat(path).st_mode mode = os.stat(path).st_mode
if mode & stat.S_IWOTH or mode & stat.S_IWGRP: if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
msg = ( msg = (
"%s is writable by group/others and vulnerable to attack " "Extraction path is writable by group/others "
"when " "and vulnerable to attack when "
"used with get_resource_filename. Consider a more secure " "used with get_resource_filename ({path}). "
"Consider a more secure "
"location (set with .set_extraction_path or the " "location (set with .set_extraction_path or the "
"PYTHON_EGG_CACHE environment variable)." % path "PYTHON_EGG_CACHE environment variable)."
) ).format(**locals())
warnings.warn(msg, UserWarning) warnings.warn(msg, UserWarning)
def postprocess(self, tempname, filename): def postprocess(self, tempname, filename):
...@@ -1401,14 +1405,30 @@ class NullProvider: ...@@ -1401,14 +1405,30 @@ class NullProvider:
def has_resource(self, resource_name): def has_resource(self, resource_name):
return self._has(self._fn(self.module_path, resource_name)) return self._has(self._fn(self.module_path, resource_name))
def _get_metadata_path(self, name):
return self._fn(self.egg_info, name)
def has_metadata(self, name): def has_metadata(self, name):
return self.egg_info and self._has(self._fn(self.egg_info, name)) if not self.egg_info:
return self.egg_info
path = self._get_metadata_path(name)
return self._has(path)
def get_metadata(self, name): def get_metadata(self, name):
if not self.egg_info: if not self.egg_info:
return "" return ""
value = self._get(self._fn(self.egg_info, name)) path = self._get_metadata_path(name)
return value.decode('utf-8') if six.PY3 else value value = self._get(path)
if six.PY2:
return value
try:
return value.decode('utf-8')
except UnicodeDecodeError as exc:
# Include the path in the error message to simplify
# troubleshooting, and without changing the exception type.
exc.reason += ' in {} file at path: {}'.format(name, path)
raise
def get_metadata_lines(self, name): def get_metadata_lines(self, name):
return yield_lines(self.get_metadata(name)) return yield_lines(self.get_metadata(name))
...@@ -1466,10 +1486,86 @@ class NullProvider: ...@@ -1466,10 +1486,86 @@ class NullProvider:
) )
def _fn(self, base, resource_name): def _fn(self, base, resource_name):
self._validate_resource_path(resource_name)
if resource_name: if resource_name:
return os.path.join(base, *resource_name.split('/')) return os.path.join(base, *resource_name.split('/'))
return base return base
@staticmethod
def _validate_resource_path(path):
"""
Validate the resource paths according to the docs.
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
>>> warned = getfixture('recwarn')
>>> warnings.simplefilter('always')
>>> vrp = NullProvider._validate_resource_path
>>> vrp('foo/bar.txt')
>>> bool(warned)
False
>>> vrp('../foo/bar.txt')
>>> bool(warned)
True
>>> warned.clear()
>>> vrp('/foo/bar.txt')
>>> bool(warned)
True
>>> vrp('foo/../../bar.txt')
>>> bool(warned)
True
>>> warned.clear()
>>> vrp('foo/f../bar.txt')
>>> bool(warned)
False
Windows path separators are straight-up disallowed.
>>> vrp(r'\\foo/bar.txt')
Traceback (most recent call last):
...
ValueError: Use of .. or absolute path in a resource path \
is not allowed.
>>> vrp(r'C:\\foo/bar.txt')
Traceback (most recent call last):
...
ValueError: Use of .. or absolute path in a resource path \
is not allowed.
Blank values are allowed
>>> vrp('')
>>> bool(warned)
False
Non-string values are not.
>>> vrp(None)
Traceback (most recent call last):
...
AttributeError: ...
"""
invalid = (
os.path.pardir in path.split(posixpath.sep) or
posixpath.isabs(path) or
ntpath.isabs(path)
)
if not invalid:
return
msg = "Use of .. or absolute path in a resource path is not allowed."
# Aggressively disallow Windows absolute paths
if ntpath.isabs(path) and not posixpath.isabs(path):
raise ValueError(msg)
# for compatibility, warn; in future
# raise ValueError(msg)
warnings.warn(
msg[:-1] + " and will raise exceptions in a future release.",
DeprecationWarning,
stacklevel=4,
)
def _get(self, path): def _get(self, path):
if hasattr(self.loader, 'get_data'): if hasattr(self.loader, 'get_data'):
return self.loader.get_data(path) return self.loader.get_data(path)
...@@ -1790,6 +1886,9 @@ class FileMetadata(EmptyProvider): ...@@ -1790,6 +1886,9 @@ class FileMetadata(EmptyProvider):
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
def _get_metadata_path(self, name):
return self.path
def has_metadata(self, name): def has_metadata(self, name):
return name == 'PKG-INFO' and os.path.isfile(self.path) return name == 'PKG-INFO' and os.path.isfile(self.path)
...@@ -1888,7 +1987,7 @@ def find_eggs_in_zip(importer, path_item, only=False): ...@@ -1888,7 +1987,7 @@ def find_eggs_in_zip(importer, path_item, only=False):
if only: if only:
# don't yield nested distros # don't yield nested distros
return return
for subitem in metadata.resource_listdir('/'): for subitem in metadata.resource_listdir(''):
if _is_egg_path(subitem): if _is_egg_path(subitem):
subpath = os.path.join(path_item, subitem) subpath = os.path.join(path_item, subitem)
dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
...@@ -2231,7 +2330,8 @@ register_namespace_handler(object, null_ns_handler) ...@@ -2231,7 +2330,8 @@ register_namespace_handler(object, null_ns_handler)
def normalize_path(filename): def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes""" """Normalize a file/dir name for comparison purposes"""
return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) return os.path.normcase(os.path.realpath(os.path.normpath(
_cygwin_patch(filename))))
def _cygwin_patch(filename): # pragma: nocover def _cygwin_patch(filename): # pragma: nocover
...@@ -2583,10 +2683,14 @@ class Distribution: ...@@ -2583,10 +2683,14 @@ class Distribution:
try: try:
return self._version return self._version
except AttributeError: except AttributeError:
version = _version_from_file(self._get_metadata(self.PKG_INFO)) version = self._get_version()
if version is None: if version is None:
tmpl = "Missing 'Version:' header and/or %s file" path = self._get_metadata_path_for_display(self.PKG_INFO)
raise ValueError(tmpl % self.PKG_INFO, self) msg = (
"Missing 'Version:' header and/or {} file at path: {}"
).format(self.PKG_INFO, path)
raise ValueError(msg, self)
return version return version
@property @property
...@@ -2644,11 +2748,34 @@ class Distribution: ...@@ -2644,11 +2748,34 @@ class Distribution:
) )
return deps return deps
def _get_metadata_path_for_display(self, name):
"""
Return the path to the given metadata file, if available.
"""
try:
# We need to access _get_metadata_path() on the provider object
# directly rather than through this class's __getattr__()
# since _get_metadata_path() is marked private.
path = self._provider._get_metadata_path(name)
# Handle exceptions e.g. in case the distribution's metadata
# provider doesn't support _get_metadata_path().
except Exception:
return '[could not detect]'
return path
def _get_metadata(self, name): def _get_metadata(self, name):
if self.has_metadata(name): if self.has_metadata(name):
for line in self.get_metadata_lines(name): for line in self.get_metadata_lines(name):
yield line yield line
def _get_version(self):
lines = self._get_metadata(self.PKG_INFO)
version = _version_from_file(lines)
return version
def activate(self, path=None, replace=False): def activate(self, path=None, replace=False):
"""Ensure distribution is importable on `path` (default=sys.path)""" """Ensure distribution is importable on `path` (default=sys.path)"""
if path is None: if path is None:
...@@ -2867,7 +2994,7 @@ class EggInfoDistribution(Distribution): ...@@ -2867,7 +2994,7 @@ class EggInfoDistribution(Distribution):
take an extra step and try to get the version number from take an extra step and try to get the version number from
the metadata file itself instead of the filename. the metadata file itself instead of the filename.
""" """
md_version = _version_from_file(self._get_metadata(self.PKG_INFO)) md_version = self._get_version()
if md_version: if md_version:
self._version = md_version self._version = md_version
return self return self
...@@ -2985,6 +3112,7 @@ class Requirement(packaging.requirements.Requirement): ...@@ -2985,6 +3112,7 @@ class Requirement(packaging.requirements.Requirement):
self.extras = tuple(map(safe_extra, self.extras)) self.extras = tuple(map(safe_extra, self.extras))
self.hashCmp = ( self.hashCmp = (
self.key, self.key,
self.url,
self.specifier, self.specifier,
frozenset(self.extras), frozenset(self.extras),
str(self.marker) if self.marker else None, str(self.marker) if self.marker else None,
...@@ -3162,6 +3290,7 @@ def _initialize_master_working_set(): ...@@ -3162,6 +3290,7 @@ def _initialize_master_working_set():
list(map(working_set.add_entry, sys.path)) list(map(working_set.add_entry, sys.path))
globals().update(locals()) globals().update(locals())
class PkgResourcesDeprecationWarning(Warning): class PkgResourcesDeprecationWarning(Warning):
""" """
Base class for warning about deprecations in ``pkg_resources`` Base class for warning about deprecations in ``pkg_resources``
......
...@@ -36,7 +36,7 @@ Distributions have various introspectable attributes:: ...@@ -36,7 +36,7 @@ Distributions have various introspectable attributes::
>>> dist.version >>> dist.version
'0.9' '0.9'
>>> dist.py_version == sys.version[:3] >>> dist.py_version == '{}.{}'.format(*sys.version_info)
True True
>>> print(dist.platform) >>> print(dist.platform)
......
...@@ -43,13 +43,6 @@ class VendorImporter: ...@@ -43,13 +43,6 @@ class VendorImporter:
__import__(extant) __import__(extant)
mod = sys.modules[extant] mod = sys.modules[extant]
sys.modules[fullname] = mod sys.modules[fullname] = mod
# mysterious hack:
# Remove the reference to the extant package/module
# on later Python versions to cause relative imports
# in the vendor package to resolve the same modules
# as those going through this importer.
if prefix and sys.version_info > (3, 3):
del sys.modules[extant]
return mod return mod
except ImportError: except ImportError:
pass pass
......
import sys
import warnings
import textwrap
msg = textwrap.dedent("""
You are running Setuptools on Python 2, which is no longer
supported and
>>> SETUPTOOLS WILL STOP WORKING <<<
in a subsequent release (no sooner than 2020-04-20).
Please ensure you are installing
Setuptools using pip 9.x or later or pin to `setuptools<45`
in your environment.
If you have done those things and are still encountering
this message, please comment in
https://github.com/pypa/setuptools/issues/1458
about the steps that led to this unsupported combination.
""")
pre = "Setuptools will stop working on Python 2\n"
sys.version_info < (3,) and warnings.warn(pre + "*" * 60 + msg + "*" * 60)
import setuptools
setuptools.setup(
name="my-test-package",
version="1.0",
zip_safe=True,
)
Metadata-Version: 1.0
Name: my-test-package
Version: 1.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
setup.cfg
setup.py
my_test_package.egg-info/PKG-INFO
my_test_package.egg-info/SOURCES.txt
my_test_package.egg-info/dependency_links.txt
my_test_package.egg-info/top_level.txt
my_test_package.egg-info/zip-safe
\ No newline at end of file
import subprocess import py
import sys
import pytest import pytest
import pkg_resources import pkg_resources
SETUP_TEMPLATE = """
import setuptools TESTS_DATA_DIR = py.path.local(__file__).dirpath('data')
setuptools.setup(
name="my-test-package",
version="1.0",
zip_safe=True,
)
""".lstrip()
class TestFindDistributions: class TestFindDistributions:
...@@ -21,46 +13,22 @@ class TestFindDistributions: ...@@ -21,46 +13,22 @@ class TestFindDistributions:
target_dir = tmpdir.mkdir('target') target_dir = tmpdir.mkdir('target')
# place a .egg named directory in the target that is not an egg: # place a .egg named directory in the target that is not an egg:
target_dir.mkdir('not.an.egg') target_dir.mkdir('not.an.egg')
return str(target_dir) return target_dir
@pytest.fixture
def project_dir(self, tmpdir):
project_dir = tmpdir.mkdir('my-test-package')
(project_dir / "setup.py").write(SETUP_TEMPLATE)
return str(project_dir)
def test_non_egg_dir_named_egg(self, target_dir): def test_non_egg_dir_named_egg(self, target_dir):
dists = pkg_resources.find_distributions(target_dir) dists = pkg_resources.find_distributions(str(target_dir))
assert not list(dists) assert not list(dists)
def test_standalone_egg_directory(self, project_dir, target_dir): def test_standalone_egg_directory(self, target_dir):
# install this distro as an unpacked egg: (TESTS_DATA_DIR / 'my-test-package_unpacked-egg').copy(target_dir)
args = [ dists = pkg_resources.find_distributions(str(target_dir))
sys.executable,
'-c', 'from setuptools.command.easy_install import main; main()',
'-mNx',
'-d', target_dir,
'--always-unzip',
project_dir,
]
subprocess.check_call(args)
dists = pkg_resources.find_distributions(target_dir)
assert [dist.project_name for dist in dists] == ['my-test-package'] assert [dist.project_name for dist in dists] == ['my-test-package']
dists = pkg_resources.find_distributions(target_dir, only=True) dists = pkg_resources.find_distributions(str(target_dir), only=True)
assert not list(dists) assert not list(dists)
def test_zipped_egg(self, project_dir, target_dir): def test_zipped_egg(self, target_dir):
# install this distro as an unpacked egg: (TESTS_DATA_DIR / 'my-test-package_zipped-egg').copy(target_dir)
args = [ dists = pkg_resources.find_distributions(str(target_dir))
sys.executable,
'-c', 'from setuptools.command.easy_install import main; main()',
'-mNx',
'-d', target_dir,
'--zip-ok',
project_dir,
]
subprocess.check_call(args)
dists = pkg_resources.find_distributions(target_dir)
assert [dist.project_name for dist in dists] == ['my-test-package'] assert [dist.project_name for dist in dists] == ['my-test-package']
dists = pkg_resources.find_distributions(target_dir, only=True) dists = pkg_resources.find_distributions(str(target_dir), only=True)
assert not list(dists) assert not list(dists)
...@@ -17,6 +17,10 @@ try: ...@@ -17,6 +17,10 @@ try:
except ImportError: except ImportError:
import mock import mock
from pkg_resources import (
DistInfoDistribution, Distribution, EggInfoDistribution,
)
from setuptools.extern import six
from pkg_resources.extern.six.moves import map from pkg_resources.extern.six.moves import map
from pkg_resources.extern.six import text_type, string_types from pkg_resources.extern.six import text_type, string_types
...@@ -93,7 +97,6 @@ class TestZipProvider: ...@@ -93,7 +97,6 @@ class TestZipProvider:
expected_root = ['data.dat', 'mod.py', 'subdir'] expected_root = ['data.dat', 'mod.py', 'subdir']
assert sorted(zp.resource_listdir('')) == expected_root assert sorted(zp.resource_listdir('')) == expected_root
assert sorted(zp.resource_listdir('/')) == expected_root
expected_subdir = ['data2.dat', 'mod2.py'] expected_subdir = ['data2.dat', 'mod2.py']
assert sorted(zp.resource_listdir('subdir')) == expected_subdir assert sorted(zp.resource_listdir('subdir')) == expected_subdir
...@@ -106,7 +109,6 @@ class TestZipProvider: ...@@ -106,7 +109,6 @@ class TestZipProvider:
zp2 = pkg_resources.ZipProvider(mod2) zp2 = pkg_resources.ZipProvider(mod2)
assert sorted(zp2.resource_listdir('')) == expected_subdir assert sorted(zp2.resource_listdir('')) == expected_subdir
assert sorted(zp2.resource_listdir('/')) == expected_subdir
assert zp2.resource_listdir('subdir') == [] assert zp2.resource_listdir('subdir') == []
assert zp2.resource_listdir('subdir/') == [] assert zp2.resource_listdir('subdir/') == []
...@@ -192,6 +194,142 @@ class TestResourceManager: ...@@ -192,6 +194,142 @@ class TestResourceManager:
subprocess.check_call(cmd) subprocess.check_call(cmd)
def make_test_distribution(metadata_path, metadata):
"""
Make a test Distribution object, and return it.
:param metadata_path: the path to the metadata file that should be
created. This should be inside a distribution directory that should
also be created. For example, an argument value might end with
"<project>.dist-info/METADATA".
:param metadata: the desired contents of the metadata file, as bytes.
"""
dist_dir = os.path.dirname(metadata_path)
os.mkdir(dist_dir)
with open(metadata_path, 'wb') as f:
f.write(metadata)
dists = list(pkg_resources.distributions_from_metadata(dist_dir))
dist, = dists
return dist
def test_get_metadata__bad_utf8(tmpdir):
"""
Test a metadata file with bytes that can't be decoded as utf-8.
"""
filename = 'METADATA'
# Convert the tmpdir LocalPath object to a string before joining.
metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename)
# Encode a non-ascii string with the wrong encoding (not utf-8).
metadata = 'née'.encode('iso-8859-1')
dist = make_test_distribution(metadata_path, metadata=metadata)
if six.PY2:
# In Python 2, get_metadata() doesn't do any decoding.
actual = dist.get_metadata(filename)
assert actual == metadata
return
# Otherwise, we are in the Python 3 case.
with pytest.raises(UnicodeDecodeError) as excinfo:
dist.get_metadata(filename)
exc = excinfo.value
actual = str(exc)
expected = (
# The error message starts with "'utf-8' codec ..." However, the
# spelling of "utf-8" can vary (e.g. "utf8") so we don't include it
"codec can't decode byte 0xe9 in position 1: "
'invalid continuation byte in METADATA file at path: '
)
assert expected in actual, 'actual: {}'.format(actual)
assert actual.endswith(metadata_path), 'actual: {}'.format(actual)
# TODO: remove this in favor of Path.touch() when Python 2 is dropped.
def touch_file(path):
"""
Create an empty file.
"""
with open(path, 'w'):
pass
def make_distribution_no_version(tmpdir, basename):
"""
Create a distribution directory with no file containing the version.
"""
# Convert the LocalPath object to a string before joining.
dist_dir = os.path.join(str(tmpdir), basename)
os.mkdir(dist_dir)
# Make the directory non-empty so distributions_from_metadata()
# will detect it and yield it.
touch_file(os.path.join(dist_dir, 'temp.txt'))
dists = list(pkg_resources.distributions_from_metadata(dist_dir))
assert len(dists) == 1
dist, = dists
return dist, dist_dir
@pytest.mark.parametrize(
'suffix, expected_filename, expected_dist_type',
[
('egg-info', 'PKG-INFO', EggInfoDistribution),
('dist-info', 'METADATA', DistInfoDistribution),
],
)
def test_distribution_version_missing(
tmpdir, suffix, expected_filename, expected_dist_type):
"""
Test Distribution.version when the "Version" header is missing.
"""
basename = 'foo.{}'.format(suffix)
dist, dist_dir = make_distribution_no_version(tmpdir, basename)
expected_text = (
"Missing 'Version:' header and/or {} file at path: "
).format(expected_filename)
metadata_path = os.path.join(dist_dir, expected_filename)
# Now check the exception raised when the "version" attribute is accessed.
with pytest.raises(ValueError) as excinfo:
dist.version
err = str(excinfo.value)
# Include a string expression after the assert so the full strings
# will be visible for inspection on failure.
assert expected_text in err, str((expected_text, err))
# Also check the args passed to the ValueError.
msg, dist = excinfo.value.args
assert expected_text in msg
# Check that the message portion contains the path.
assert metadata_path in msg, str((metadata_path, msg))
assert type(dist) == expected_dist_type
def test_distribution_version_missing_undetected_path():
"""
Test Distribution.version when the "Version" header is missing and
the path can't be detected.
"""
# Create a Distribution object with no metadata argument, which results
# in an empty metadata provider.
dist = Distribution('/foo')
with pytest.raises(ValueError) as excinfo:
dist.version
msg, dist = excinfo.value.args
expected = (
"Missing 'Version:' header and/or PKG-INFO file at path: "
'[could not detect]'
)
assert msg == expected
class TestDeepVersionLookupDistutils: class TestDeepVersionLookupDistutils:
@pytest.fixture @pytest.fixture
def env(self, tmpdir): def env(self, tmpdir):
......
...@@ -15,7 +15,7 @@ import pkg_resources ...@@ -15,7 +15,7 @@ import pkg_resources
from pkg_resources import ( from pkg_resources import (
parse_requirements, VersionConflict, parse_version, parse_requirements, VersionConflict, parse_version,
Distribution, EntryPoint, Requirement, safe_version, safe_name, Distribution, EntryPoint, Requirement, safe_version, safe_name,
WorkingSet, PkgResourcesDeprecationWarning) WorkingSet)
# from Python 3.6 docs. # from Python 3.6 docs.
...@@ -116,7 +116,7 @@ class TestDistro: ...@@ -116,7 +116,7 @@ class TestDistro:
self.checkFooPkg(d) self.checkFooPkg(d)
d = Distribution("/some/path") d = Distribution("/some/path")
assert d.py_version == sys.version[:3] assert d.py_version == '{}.{}'.format(*sys.version_info)
assert d.platform is None assert d.platform is None
def testDistroParse(self): def testDistroParse(self):
...@@ -501,7 +501,6 @@ class TestEntryPoints: ...@@ -501,7 +501,6 @@ class TestEntryPoints:
ep.load(require=False) ep.load(require=False)
class TestRequirements: class TestRequirements:
def testBasics(self): def testBasics(self):
r = Requirement.parse("Twisted>=1.2") r = Requirement.parse("Twisted>=1.2")
...@@ -520,6 +519,11 @@ class TestRequirements: ...@@ -520,6 +519,11 @@ class TestRequirements:
assert r1 == r2 assert r1 == r2
assert str(r1) == str(r2) assert str(r1) == str(r2)
assert str(r2) == "Twisted==1.2c1,>=1.2" assert str(r2) == "Twisted==1.2c1,>=1.2"
assert (
Requirement("Twisted")
!=
Requirement("Twisted @ https://localhost/twisted.zip")
)
def testBasicContains(self): def testBasicContains(self):
r = Requirement("Twisted>=1.2") r = Requirement("Twisted>=1.2")
...@@ -546,11 +550,23 @@ class TestRequirements: ...@@ -546,11 +550,23 @@ class TestRequirements:
== ==
hash(( hash((
"twisted", "twisted",
None,
packaging.specifiers.SpecifierSet(">=1.2"), packaging.specifiers.SpecifierSet(">=1.2"),
frozenset(["foo", "bar"]), frozenset(["foo", "bar"]),
None None
)) ))
) )
assert (
hash(Requirement.parse("Twisted @ https://localhost/twisted.zip"))
==
hash((
"twisted",
"https://localhost/twisted.zip",
packaging.specifiers.SpecifierSet(),
frozenset(),
None
))
)
def testVersionEquality(self): def testVersionEquality(self):
r1 = Requirement.parse("foo==0.3a2") r1 = Requirement.parse("foo==0.3a2")
......
[build-system] [build-system]
requires = ["wheel"] requires = [
# avoid self install on Python 2; ref #1996
"setuptools >= 40.8; python_version > '3'",
"wheel",
]
build-backend = "setuptools.build_meta"
backend-path = ["."]
[tool.towncrier] [tool.towncrier]
package = "setuptools" package = "setuptools"
......
[pytest] [pytest]
addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX addopts=--doctest-modules --flake8 --doctest-glob=pkg_resources/api_tests.txt -r sxX
norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .* norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern pkg_resources/tests/data tools .* setuptools/_vendor pkg_resources/_vendor
flake8-ignore =
setuptools/site-patch.py F821
setuptools/py*compat.py F811
doctest_optionflags=ELLIPSIS ALLOW_UNICODE doctest_optionflags=ELLIPSIS ALLOW_UNICODE
filterwarnings =
# Fail on warnings
error
# https://github.com/pypa/setuptools/issues/1823
ignore:bdist_wininst command is deprecated
# Suppress this error; unimportant for CI tests
ignore:Extraction path is writable by group/others:UserWarning
# Suppress Python 2 deprecation warning
ignore:Setuptools will stop working on Python 2:UserWarning
# Suppress weird RuntimeWarning.
ignore:Parent module 'setuptools' not found while handling absolute import:RuntimeWarning
# Suppress use of bytes for filenames on Windows until fixed #2016
ignore:The Windows bytes API has been deprecated:DeprecationWarning
# Suppress other Python 2 UnicodeWarnings
ignore:Unicode equal comparison failed to convert:UnicodeWarning
ignore:Unicode unequal comparison failed to convert:UnicodeWarning
# https://github.com/pypa/setuptools/issues/2025
ignore:direct construction of .*Item has been deprecated:DeprecationWarning
[bumpversion]
current_version = 40.6.2
commit = True
tag = True
[egg_info] [egg_info]
tag_build = .post tag_build = .post
tag_date = 1 tag_date = 1
...@@ -19,11 +14,67 @@ repository = https://upload.pypi.org/legacy/ ...@@ -19,11 +14,67 @@ repository = https://upload.pypi.org/legacy/
[sdist] [sdist]
formats = zip formats = zip
[bdist_wheel]
universal = 1
[metadata] [metadata]
name = setuptools
version = 46.0.0
description = Easily download, build, install, upgrade, and uninstall Python packages
author = Python Packaging Authority
author_email = distutils-sig@python.org
long_description = file: README.rst
long_description_content_type = text/x-rst; charset=UTF-8
license_file = LICENSE license_file = LICENSE
keywords = CPAN PyPI distutils eggs package management
url = https://github.com/pypa/setuptools
project_urls =
Documentation = https://setuptools.readthedocs.io/
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Archiving :: Packaging
Topic :: System :: Systems Administration
Topic :: Utilities
[options]
zip_safe = True
python_requires = >=3.5
py_modules = easy_install
packages = find:
[options.packages.find]
exclude = *.tests
[options.extras_require]
ssl =
wincertstore==0.2; sys_platform=='win32'
certs =
certifi==2016.9.26
[bumpversion:file:setup.py] tests =
mock
pytest-flake8
flake8-2020; python_version>="3.6"
virtualenv>=13.0.0
pytest-virtualenv>=1.2.7
pytest>=3.7
wheel
coverage>=4.5.1
pytest-cov>=2.5.1
paver; python_version>="3.6"
futures; python_version=="2.7"
pip>=19.1 # For proper file:// URLs support.
docs =
# Keep these in sync with docs/requirements.txt
sphinx
jaraco.packaging>=6.1
rst.linker>=1.9
...@@ -3,10 +3,8 @@ ...@@ -3,10 +3,8 @@
Distutils setup file, used to install or test 'setuptools' Distutils setup file, used to install or test 'setuptools'
""" """
import io
import os import os
import sys import sys
import textwrap
import setuptools import setuptools
...@@ -46,13 +44,9 @@ def _gen_console_scripts(): ...@@ -46,13 +44,9 @@ def _gen_console_scripts():
if any(os.environ.get(var) not in (None, "", "0") for var in var_names): if any(os.environ.get(var) not in (None, "", "0") for var in var_names):
return return
tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main" tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main"
yield tmpl.format(shortver=sys.version[:3]) yield tmpl.format(shortver='{}.{}'.format(*sys.version_info))
readme_path = os.path.join(here, 'README.rst')
with io.open(readme_path, encoding='utf-8') as readme_file:
long_description = readme_file.read()
package_data = dict( package_data = dict(
setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'],
) )
...@@ -88,31 +82,19 @@ def pypi_link(pkg_filename): ...@@ -88,31 +82,19 @@ def pypi_link(pkg_filename):
setup_params = dict( setup_params = dict(
name="setuptools",
version="40.6.2",
description=(
"Easily download, build, install, upgrade, and uninstall "
"Python packages"
),
author="Python Packaging Authority",
author_email="distutils-sig@python.org",
long_description=long_description,
long_description_content_type='text/x-rst; charset=UTF-8',
keywords="CPAN PyPI distutils eggs package management",
url="https://github.com/pypa/setuptools",
project_urls={
"Documentation": "https://setuptools.readthedocs.io/",
},
src_root=None, src_root=None,
packages=setuptools.find_packages(exclude=['*.tests']),
package_data=package_data, package_data=package_data,
py_modules=['easy_install'],
zip_safe=True,
entry_points={ entry_points={
"distutils.commands": [ "distutils.commands": [
"%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals() "%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals()
for cmd in read_commands() for cmd in read_commands()
], ],
"setuptools.finalize_distribution_options": [
"parent_finalize = setuptools.dist:_Distribution.finalize_options",
"keywords = setuptools.dist:Distribution._finalize_setup_keywords",
"2to3_doctests = "
"setuptools.dist:Distribution._finalize_2to3_doctests",
],
"distutils.setup_keywords": [ "distutils.setup_keywords": [
"eager_resources = setuptools.dist:assert_string_list", "eager_resources = setuptools.dist:assert_string_list",
"namespace_packages = setuptools.dist:check_nsp", "namespace_packages = setuptools.dist:check_nsp",
...@@ -153,28 +135,6 @@ setup_params = dict( ...@@ -153,28 +135,6 @@ setup_params = dict(
"setuptools.installation": "setuptools.installation":
['eggsecutable = setuptools.command.easy_install:bootstrap'], ['eggsecutable = setuptools.command.easy_install:bootstrap'],
}, },
classifiers=textwrap.dedent("""
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Archiving :: Packaging
Topic :: System :: Systems Administration
Topic :: Utilities
""").strip().splitlines(),
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
extras_require={
"ssl:sys_platform=='win32'": "wincertstore==0.2",
"certs": "certifi==2016.9.26",
},
dependency_links=[ dependency_links=[
pypi_link( pypi_link(
'certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d', 'certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d',
...@@ -183,7 +143,6 @@ setup_params = dict( ...@@ -183,7 +143,6 @@ setup_params = dict(
'wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2', 'wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2',
), ),
], ],
scripts=[],
setup_requires=[ setup_requires=[
] + wheel, ] + wheel,
) )
......
"""Extensions to the 'distutils' for large or complex distributions""" """Extensions to the 'distutils' for large or complex distributions"""
import os import os
import sys
import functools import functools
import distutils.core import distutils.core
import distutils.filelist import distutils.filelist
import re
from distutils.errors import DistutilsOptionError
from distutils.util import convert_path from distutils.util import convert_path
from fnmatch import fnmatchcase from fnmatch import fnmatchcase
from ._deprecation_warning import SetuptoolsDeprecationWarning from ._deprecation_warning import SetuptoolsDeprecationWarning
from setuptools.extern.six import PY3 from setuptools.extern.six import PY3, string_types
from setuptools.extern.six.moves import filter, map from setuptools.extern.six.moves import filter, map
import setuptools.version import setuptools.version
from setuptools.extension import Extension from setuptools.extension import Extension
from setuptools.dist import Distribution, Feature from setuptools.dist import Distribution
from setuptools.depends import Require from setuptools.depends import Require
from . import monkey from . import monkey
...@@ -23,13 +24,13 @@ __metaclass__ = type ...@@ -23,13 +24,13 @@ __metaclass__ = type
__all__ = [ __all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'setup', 'Distribution', 'Command', 'Extension', 'Require',
'SetuptoolsDeprecationWarning', 'SetuptoolsDeprecationWarning',
'find_packages' 'find_packages'
] ]
if PY3: if PY3:
__all__.append('find_namespace_packages') __all__.append('find_namespace_packages')
__version__ = setuptools.version.__version__ __version__ = setuptools.version.__version__
...@@ -121,7 +122,7 @@ class PEP420PackageFinder(PackageFinder): ...@@ -121,7 +122,7 @@ class PEP420PackageFinder(PackageFinder):
find_packages = PackageFinder.find find_packages = PackageFinder.find
if PY3: if PY3:
find_namespace_packages = PEP420PackageFinder.find find_namespace_packages = PEP420PackageFinder.find
def _install_setup_requires(attrs): def _install_setup_requires(attrs):
...@@ -142,6 +143,7 @@ def setup(**attrs): ...@@ -142,6 +143,7 @@ def setup(**attrs):
_install_setup_requires(attrs) _install_setup_requires(attrs)
return distutils.core.setup(**attrs) return distutils.core.setup(**attrs)
setup.__doc__ = distutils.core.setup.__doc__ setup.__doc__ = distutils.core.setup.__doc__
...@@ -161,6 +163,37 @@ class Command(_Command): ...@@ -161,6 +163,37 @@ class Command(_Command):
_Command.__init__(self, dist) _Command.__init__(self, dist)
vars(self).update(kw) vars(self).update(kw)
def _ensure_stringlike(self, option, what, default=None):
val = getattr(self, option)
if val is None:
setattr(self, option, default)
return default
elif not isinstance(val, string_types):
raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
% (option, what, val))
return val
def ensure_string_list(self, option):
r"""Ensure that 'option' is a list of strings. If 'option' is
currently a string, we split it either on /,\s*/ or /\s+/, so
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
["foo", "bar", "baz"].
"""
val = getattr(self, option)
if val is None:
return
elif isinstance(val, string_types):
setattr(self, option, re.split(r',\s*|\s+', val))
else:
if isinstance(val, list):
ok = all(isinstance(v, string_types) for v in val)
else:
ok = False
if not ok:
raise DistutilsOptionError(
"'%s' must be a list of strings (got %r)"
% (option, val))
def reinitialize_command(self, command, reinit_subcommands=0, **kw): def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands) cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw) vars(cmd).update(kw)
...@@ -191,5 +224,9 @@ def findall(dir=os.curdir): ...@@ -191,5 +224,9 @@ def findall(dir=os.curdir):
return list(files) return list(files)
class sic(str):
"""Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
# Apply monkey patches # Apply monkey patches
monkey.patch_all() monkey.patch_all()
"""
Re-implementation of find_module and get_frozen_object
from the deprecated imp module.
"""
import os
import importlib.util
import importlib.machinery
from .py34compat import module_from_spec
PY_SOURCE = 1
PY_COMPILED = 2
C_EXTENSION = 3
C_BUILTIN = 6
PY_FROZEN = 7
def find_spec(module, paths):
finder = (
importlib.machinery.PathFinder().find_spec
if isinstance(paths, list) else
importlib.util.find_spec
)
return finder(module, paths)
def find_module(module, paths=None):
"""Just like 'imp.find_module()', but with package support"""
spec = find_spec(module, paths)
if spec is None:
raise ImportError("Can't find %s" % module)
if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
spec = importlib.util.spec_from_loader('__init__.py', spec.loader)
kind = -1
file = None
static = isinstance(spec.loader, type)
if spec.origin == 'frozen' or static and issubclass(
spec.loader, importlib.machinery.FrozenImporter):
kind = PY_FROZEN
path = None # imp compabilty
suffix = mode = '' # imp compability
elif spec.origin == 'built-in' or static and issubclass(
spec.loader, importlib.machinery.BuiltinImporter):
kind = C_BUILTIN
path = None # imp compabilty
suffix = mode = '' # imp compability
elif spec.has_location:
path = spec.origin
suffix = os.path.splitext(path)[1]
mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb'
if suffix in importlib.machinery.SOURCE_SUFFIXES:
kind = PY_SOURCE
elif suffix in importlib.machinery.BYTECODE_SUFFIXES:
kind = PY_COMPILED
elif suffix in importlib.machinery.EXTENSION_SUFFIXES:
kind = C_EXTENSION
if kind in {PY_SOURCE, PY_COMPILED}:
file = open(path, mode)
else:
path = None
suffix = mode = ''
return file, path, (suffix, mode, kind)
def get_frozen_object(module, paths=None):
spec = find_spec(module, paths)
if not spec:
raise ImportError("Can't find %s" % module)
return spec.loader.get_code(module)
def get_module(module, paths, info):
spec = find_spec(module, paths)
if not spec:
raise ImportError("Can't find %s" % module)
return module_from_spec(spec)
This diff is collapsed.
...@@ -4,18 +4,24 @@ ...@@ -4,18 +4,24 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__all__ = [ __all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__", "__title__",
"__email__", "__license__", "__copyright__", "__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
] ]
__title__ = "packaging" __title__ = "packaging"
__summary__ = "Core utilities for Python packages" __summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging" __uri__ = "https://github.com/pypa/packaging"
__version__ = "16.8" __version__ = "19.2"
__author__ = "Donald Stufft and individual contributors" __author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io" __email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0" __license__ = "BSD or Apache License, Version 2.0"
__copyright__ = "Copyright 2014-2016 %s" % __author__ __copyright__ = "Copyright 2014-2019 %s" % __author__
...@@ -4,11 +4,23 @@ ...@@ -4,11 +4,23 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
from .__about__ import ( from .__about__ import (
__author__, __copyright__, __email__, __license__, __summary__, __title__, __author__,
__uri__, __version__ __copyright__,
__email__,
__license__,
__summary__,
__title__,
__uri__,
__version__,
) )
__all__ = [ __all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__", "__title__",
"__email__", "__license__", "__copyright__", "__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
] ]
...@@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3 ...@@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3
# flake8: noqa # flake8: noqa
if PY3: if PY3:
string_types = str, string_types = (str,)
else: else:
string_types = basestring, string_types = (basestring,)
def with_metaclass(meta, *bases): def with_metaclass(meta, *bases):
...@@ -27,4 +27,5 @@ def with_metaclass(meta, *bases): ...@@ -27,4 +27,5 @@ def with_metaclass(meta, *bases):
class metaclass(meta): class metaclass(meta):
def __new__(cls, name, this_bases, d): def __new__(cls, name, this_bases, d):
return meta(name, bases, d) return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
return type.__new__(metaclass, "temporary_class", (), {})
...@@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function ...@@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function
class Infinity(object): class Infinity(object):
def __repr__(self): def __repr__(self):
return "Infinity" return "Infinity"
...@@ -33,11 +32,11 @@ class Infinity(object): ...@@ -33,11 +32,11 @@ class Infinity(object):
def __neg__(self): def __neg__(self):
return NegativeInfinity return NegativeInfinity
Infinity = Infinity() Infinity = Infinity()
class NegativeInfinity(object): class NegativeInfinity(object):
def __repr__(self): def __repr__(self):
return "-Infinity" return "-Infinity"
...@@ -65,4 +64,5 @@ class NegativeInfinity(object): ...@@ -65,4 +64,5 @@ class NegativeInfinity(object):
def __neg__(self): def __neg__(self):
return Infinity return Infinity
NegativeInfinity = NegativeInfinity() NegativeInfinity = NegativeInfinity()
...@@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier ...@@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier
__all__ = [ __all__ = [
"InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", "InvalidMarker",
"Marker", "default_environment", "UndefinedComparison",
"UndefinedEnvironmentName",
"Marker",
"default_environment",
] ]
...@@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError): ...@@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError):
class Node(object): class Node(object):
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
...@@ -57,62 +59,52 @@ class Node(object): ...@@ -57,62 +59,52 @@ class Node(object):
class Variable(Node): class Variable(Node):
def serialize(self): def serialize(self):
return str(self) return str(self)
class Value(Node): class Value(Node):
def serialize(self): def serialize(self):
return '"{0}"'.format(self) return '"{0}"'.format(self)
class Op(Node): class Op(Node):
def serialize(self): def serialize(self):
return str(self) return str(self)
VARIABLE = ( VARIABLE = (
L("implementation_version") | L("implementation_version")
L("platform_python_implementation") | | L("platform_python_implementation")
L("implementation_name") | | L("implementation_name")
L("python_full_version") | | L("python_full_version")
L("platform_release") | | L("platform_release")
L("platform_version") | | L("platform_version")
L("platform_machine") | | L("platform_machine")
L("platform_system") | | L("platform_system")
L("python_version") | | L("python_version")
L("sys_platform") | | L("sys_platform")
L("os_name") | | L("os_name")
L("os.name") | # PEP-345 | L("os.name")
L("sys.platform") | # PEP-345 | L("sys.platform") # PEP-345
L("platform.version") | # PEP-345 | L("platform.version") # PEP-345
L("platform.machine") | # PEP-345 | L("platform.machine") # PEP-345
L("platform.python_implementation") | # PEP-345 | L("platform.python_implementation") # PEP-345
L("python_implementation") | # undocumented setuptools legacy | L("python_implementation") # PEP-345
L("extra") | L("extra") # undocumented setuptools legacy
) )
ALIASES = { ALIASES = {
'os.name': 'os_name', "os.name": "os_name",
'sys.platform': 'sys_platform', "sys.platform": "sys_platform",
'platform.version': 'platform_version', "platform.version": "platform_version",
'platform.machine': 'platform_machine', "platform.machine": "platform_machine",
'platform.python_implementation': 'platform_python_implementation', "platform.python_implementation": "platform_python_implementation",
'python_implementation': 'platform_python_implementation' "python_implementation": "platform_python_implementation",
} }
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = ( VERSION_CMP = (
L("===") | L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
L("==") |
L(">=") |
L("<=") |
L("!=") |
L("~=") |
L(">") |
L("<")
) )
MARKER_OP = VERSION_CMP | L("not in") | L("in") MARKER_OP = VERSION_CMP | L("not in") | L("in")
...@@ -152,8 +144,11 @@ def _format_marker(marker, first=True): ...@@ -152,8 +144,11 @@ def _format_marker(marker, first=True):
# where the single item is itself it's own list. In that case we want skip # where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the # the rest of this function so that we don't get extraneous () on the
# outside. # outside.
if (isinstance(marker, list) and len(marker) == 1 and if (
isinstance(marker[0], (list, tuple))): isinstance(marker, list)
and len(marker) == 1
and isinstance(marker[0], (list, tuple))
):
return _format_marker(marker[0]) return _format_marker(marker[0])
if isinstance(marker, list): if isinstance(marker, list):
...@@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment): ...@@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment):
def format_full_version(info): def format_full_version(info):
version = '{0.major}.{0.minor}.{0.micro}'.format(info) version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel kind = info.releaselevel
if kind != 'final': if kind != "final":
version += kind[0] + str(info.serial) version += kind[0] + str(info.serial)
return version return version
def default_environment(): def default_environment():
if hasattr(sys, 'implementation'): if hasattr(sys, "implementation"):
iver = format_full_version(sys.implementation.version) iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name implementation_name = sys.implementation.name
else: else:
iver = '0' iver = "0"
implementation_name = '' implementation_name = ""
return { return {
"implementation_name": implementation_name, "implementation_name": implementation_name,
...@@ -264,19 +259,19 @@ def default_environment(): ...@@ -264,19 +259,19 @@ def default_environment():
"platform_version": platform.version(), "platform_version": platform.version(),
"python_full_version": platform.python_version(), "python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(), "platform_python_implementation": platform.python_implementation(),
"python_version": platform.python_version()[:3], "python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform, "sys_platform": sys.platform,
} }
class Marker(object): class Marker(object):
def __init__(self, marker): def __init__(self, marker):
try: try:
self._markers = _coerce_parse_result(MARKER.parseString(marker)) self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e: except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc:e.loc + 8]) marker, marker[e.loc : e.loc + 8]
)
raise InvalidMarker(err_str) raise InvalidMarker(err_str)
def __str__(self): def __str__(self):
......
...@@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) ...@@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name") NAME = IDENTIFIER("name")
EXTRA = IDENTIFIER EXTRA = IDENTIFIER
URI = Regex(r'[^ ]+')("url") URI = Regex(r"[^ ]+")("url")
URL = (AT + URI) URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
...@@ -48,28 +48,31 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) ...@@ -48,28 +48,31 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), VERSION_MANY = Combine(
joinString=",", adjacent=False)("_raw_spec") VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
)("_raw_spec")
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) _VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') _VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction( MARKER_EXPR.setParseAction(
lambda s, l, t: Marker(s[t._original_start:t._original_end]) lambda s, l, t: Marker(s[t._original_start : t._original_end])
) )
MARKER_SEPERATOR = SEMICOLON MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPERATOR + MARKER_EXPR MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER) URL_AND_MARKER = URL + Optional(MARKER)
NAMED_REQUIREMENT = \ NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see
# issue #104
REQUIREMENT.parseString("x[]")
class Requirement(object): class Requirement(object):
...@@ -90,15 +93,21 @@ class Requirement(object): ...@@ -90,15 +93,21 @@ class Requirement(object):
req = REQUIREMENT.parseString(requirement_string) req = REQUIREMENT.parseString(requirement_string)
except ParseException as e: except ParseException as e:
raise InvalidRequirement( raise InvalidRequirement(
"Invalid requirement, parse error at \"{0!r}\"".format( 'Parse error at "{0!r}": {1}'.format(
requirement_string[e.loc:e.loc + 8])) requirement_string[e.loc : e.loc + 8], e.msg
)
)
self.name = req.name self.name = req.name
if req.url: if req.url:
parsed_url = urlparse.urlparse(req.url) parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or ( if parsed_url.scheme == "file":
not parsed_url.scheme and not parsed_url.netloc): if urlparse.urlunparse(parsed_url) != req.url:
raise InvalidRequirement("Invalid URL given") raise InvalidRequirement("Invalid URL given")
elif not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc
):
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
self.url = req.url self.url = req.url
else: else:
self.url = None self.url = None
...@@ -117,6 +126,8 @@ class Requirement(object): ...@@ -117,6 +126,8 @@ class Requirement(object):
if self.url: if self.url:
parts.append("@ {0}".format(self.url)) parts.append("@ {0}".format(self.url))
if self.marker:
parts.append(" ")
if self.marker: if self.marker:
parts.append("; {0}".format(self.marker)) parts.append("; {0}".format(self.marker))
......
...@@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError): ...@@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError):
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod @abc.abstractmethod
def __str__(self): def __str__(self):
""" """
...@@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier): ...@@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier):
if not match: if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
self._spec = ( self._spec = (match.group("operator").strip(), match.group("version").strip())
match.group("operator").strip(),
match.group("version").strip(),
)
# Store whether or not this Specifier should accept prereleases # Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases self._prereleases = prereleases
...@@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier): ...@@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier):
else "" else ""
) )
return "<{0}({1!r}{2})>".format( return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
self.__class__.__name__,
str(self),
pre,
)
def __str__(self): def __str__(self):
return "{0}{1}".format(*self._spec) return "{0}{1}".format(*self._spec)
...@@ -194,11 +186,12 @@ class _IndividualSpecifier(BaseSpecifier): ...@@ -194,11 +186,12 @@ class _IndividualSpecifier(BaseSpecifier):
# If our version is a prerelease, and we were not set to allow # If our version is a prerelease, and we were not set to allow
# prereleases, then we'll store it for later incase nothing # prereleases, then we'll store it for later incase nothing
# else matches this specifier. # else matches this specifier.
if (parsed_version.is_prerelease and not if parsed_version.is_prerelease and not (
(prereleases or self.prereleases)): prereleases or self.prereleases
):
found_prereleases.append(version) found_prereleases.append(version)
# Either this is not a prerelease, or we should have been # Either this is not a prerelease, or we should have been
# accepting prereleases from the begining. # accepting prereleases from the beginning.
else: else:
yielded = True yielded = True
yield version yield version
...@@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier): ...@@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier):
class LegacySpecifier(_IndividualSpecifier): class LegacySpecifier(_IndividualSpecifier):
_regex_str = ( _regex_str = r"""
r"""
(?P<operator>(==|!=|<=|>=|<|>)) (?P<operator>(==|!=|<=|>=|<|>))
\s* \s*
(?P<version> (?P<version>
...@@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier): ...@@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier):
# them, and a comma since it's a version separator. # them, and a comma since it's a version separator.
) )
""" """
)
_regex = re.compile( _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = { _operators = {
"==": "equal", "==": "equal",
...@@ -269,13 +259,13 @@ def _require_version_compare(fn): ...@@ -269,13 +259,13 @@ def _require_version_compare(fn):
if not isinstance(prospective, Version): if not isinstance(prospective, Version):
return False return False
return fn(self, prospective, spec) return fn(self, prospective, spec)
return wrapped return wrapped
class Specifier(_IndividualSpecifier): class Specifier(_IndividualSpecifier):
_regex_str = ( _regex_str = r"""
r"""
(?P<operator>(~=|==|!=|<=|>=|<|>|===)) (?P<operator>(~=|==|!=|<=|>=|<|>|===))
(?P<version> (?P<version>
(?: (?:
...@@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier): ...@@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier):
) )
) )
""" """
)
_regex = re.compile( _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = { _operators = {
"~=": "compatible", "~=": "compatible",
...@@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier): ...@@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier):
prefix = ".".join( prefix = ".".join(
list( list(
itertools.takewhile( itertools.takewhile(
lambda x: (not x.startswith("post") and not lambda x: (not x.startswith("post") and not x.startswith("dev")),
x.startswith("dev")),
_version_split(spec), _version_split(spec),
) )
)[:-1] )[:-1]
...@@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier): ...@@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier):
# Add the prefix notation to the end of our string # Add the prefix notation to the end of our string
prefix += ".*" prefix += ".*"
return (self._get_operator(">=")(prospective, spec) and return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
self._get_operator("==")(prospective, prefix)) prospective, prefix
)
@_require_version_compare @_require_version_compare
def _compare_equal(self, prospective, spec): def _compare_equal(self, prospective, spec):
...@@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier): ...@@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier):
# Shorten the prospective version to be the same length as the spec # Shorten the prospective version to be the same length as the spec
# so that we can determine if the specifier is a prefix of the # so that we can determine if the specifier is a prefix of the
# prospective version or not. # prospective version or not.
prospective = prospective[:len(spec)] prospective = prospective[: len(spec)]
# Pad out our two sides with zeros so that they both equal the same # Pad out our two sides with zeros so that they both equal the same
# length. # length.
...@@ -503,7 +491,7 @@ class Specifier(_IndividualSpecifier): ...@@ -503,7 +491,7 @@ class Specifier(_IndividualSpecifier):
return False return False
# Ensure that we do not allow a local version of the version mentioned # Ensure that we do not allow a local version of the version mentioned
# in the specifier, which is techincally greater than, to match. # in the specifier, which is technically greater than, to match.
if prospective.local is not None: if prospective.local is not None:
if Version(prospective.base_version) == Version(spec.base_version): if Version(prospective.base_version) == Version(spec.base_version):
return False return False
...@@ -567,27 +555,17 @@ def _pad_version(left, right): ...@@ -567,27 +555,17 @@ def _pad_version(left, right):
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions # Get the rest of our versions
left_split.append(left[len(left_split[0]):]) left_split.append(left[len(left_split[0]) :])
right_split.append(right[len(right_split[0]):]) right_split.append(right[len(right_split[0]) :])
# Insert our padding # Insert our padding
left_split.insert( left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
1, right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
["0"] * max(0, len(right_split[0]) - len(left_split[0])),
)
right_split.insert(
1,
["0"] * max(0, len(left_split[0]) - len(right_split[0])),
)
return ( return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
list(itertools.chain(*left_split)),
list(itertools.chain(*right_split)),
)
class SpecifierSet(BaseSpecifier): class SpecifierSet(BaseSpecifier):
def __init__(self, specifiers="", prereleases=None): def __init__(self, specifiers="", prereleases=None):
# Split on , to break each indidivual specifier into it's own item, and # Split on , to break each indidivual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace. # strip each item to remove leading/trailing whitespace.
...@@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier): ...@@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier):
# given version is contained within all of them. # given version is contained within all of them.
# Note: This use of all() here means that an empty set of specifiers # Note: This use of all() here means that an empty set of specifiers
# will always return True, this is an explicit design decision. # will always return True, this is an explicit design decision.
return all( return all(s.contains(item, prereleases=prereleases) for s in self._specs)
s.contains(item, prereleases=prereleases)
for s in self._specs
)
def filter(self, iterable, prereleases=None): def filter(self, iterable, prereleases=None):
# Determine if we're forcing a prerelease or not, if we're not forcing # Determine if we're forcing a prerelease or not, if we're not forcing
......
This diff is collapsed.
...@@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function ...@@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function
import re import re
from .version import InvalidVersion, Version
_canonicalize_regex = re.compile(r"[-_.]+") _canonicalize_regex = re.compile(r"[-_.]+")
...@@ -12,3 +14,44 @@ _canonicalize_regex = re.compile(r"[-_.]+") ...@@ -12,3 +14,44 @@ _canonicalize_regex = re.compile(r"[-_.]+")
def canonicalize_name(name): def canonicalize_name(name):
# This is taken from PEP 503. # This is taken from PEP 503.
return _canonicalize_regex.sub("-", name).lower() return _canonicalize_regex.sub("-", name).lower()
def canonicalize_version(version):
"""
This is very similar to Version.__str__, but has one subtle differences
with the way it handles the release segment.
"""
try:
version = Version(version)
except InvalidVersion:
# Legacy versions cannot be normalized
return version
parts = []
# Epoch
if version.epoch != 0:
parts.append("{0}!".format(version.epoch))
# Release segment
# NB: This strips trailing '.0's to normalize
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release)))
# Pre-release
if version.pre is not None:
parts.append("".join(str(x) for x in version.pre))
# Post-release
if version.post is not None:
parts.append(".post{0}".format(version.post))
# Development release
if version.dev is not None:
parts.append(".dev{0}".format(version.dev))
# Local version segment
if version.local is not None:
parts.append("+{0}".format(version.local))
return "".join(parts)
...@@ -10,14 +10,11 @@ import re ...@@ -10,14 +10,11 @@ import re
from ._structures import Infinity from ._structures import Infinity
__all__ = [ __all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
]
_Version = collections.namedtuple( _Version = collections.namedtuple(
"_Version", "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
["epoch", "release", "dev", "pre", "post", "local"],
) )
...@@ -40,7 +37,6 @@ class InvalidVersion(ValueError): ...@@ -40,7 +37,6 @@ class InvalidVersion(ValueError):
class _BaseVersion(object): class _BaseVersion(object):
def __hash__(self): def __hash__(self):
return hash(self._key) return hash(self._key)
...@@ -70,7 +66,6 @@ class _BaseVersion(object): ...@@ -70,7 +66,6 @@ class _BaseVersion(object):
class LegacyVersion(_BaseVersion): class LegacyVersion(_BaseVersion):
def __init__(self, version): def __init__(self, version):
self._version = str(version) self._version = str(version)
self._key = _legacy_cmpkey(self._version) self._key = _legacy_cmpkey(self._version)
...@@ -89,6 +84,26 @@ class LegacyVersion(_BaseVersion): ...@@ -89,6 +84,26 @@ class LegacyVersion(_BaseVersion):
def base_version(self): def base_version(self):
return self._version return self._version
@property
def epoch(self):
return -1
@property
def release(self):
return None
@property
def pre(self):
return None
@property
def post(self):
return None
@property
def dev(self):
return None
@property @property
def local(self): def local(self):
return None return None
...@@ -101,13 +116,19 @@ class LegacyVersion(_BaseVersion): ...@@ -101,13 +116,19 @@ class LegacyVersion(_BaseVersion):
def is_postrelease(self): def is_postrelease(self):
return False return False
@property
def is_devrelease(self):
return False
_legacy_version_component_re = re.compile(
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, _legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
)
_legacy_version_replacement_map = { _legacy_version_replacement_map = {
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", "pre": "c",
"preview": "c",
"-": "final-",
"rc": "c",
"dev": "@",
} }
...@@ -154,6 +175,7 @@ def _legacy_cmpkey(version): ...@@ -154,6 +175,7 @@ def _legacy_cmpkey(version):
return epoch, parts return epoch, parts
# Deliberately not anchored to the start and end of the string, to make it # Deliberately not anchored to the start and end of the string, to make it
# easier for 3rd party code to reuse # easier for 3rd party code to reuse
VERSION_PATTERN = r""" VERSION_PATTERN = r"""
...@@ -190,10 +212,7 @@ VERSION_PATTERN = r""" ...@@ -190,10 +212,7 @@ VERSION_PATTERN = r"""
class Version(_BaseVersion): class Version(_BaseVersion):
_regex = re.compile( _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
r"^\s*" + VERSION_PATTERN + r"\s*$",
re.VERBOSE | re.IGNORECASE,
)
def __init__(self, version): def __init__(self, version):
# Validate the version and parse it into pieces # Validate the version and parse it into pieces
...@@ -205,18 +224,11 @@ class Version(_BaseVersion): ...@@ -205,18 +224,11 @@ class Version(_BaseVersion):
self._version = _Version( self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0, epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")), release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version( pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
match.group("pre_l"),
match.group("pre_n"),
),
post=_parse_letter_version( post=_parse_letter_version(
match.group("post_l"), match.group("post_l"), match.group("post_n1") or match.group("post_n2")
match.group("post_n1") or match.group("post_n2"),
),
dev=_parse_letter_version(
match.group("dev_l"),
match.group("dev_n"),
), ),
dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")), local=_parse_local_version(match.group("local")),
) )
...@@ -237,32 +249,57 @@ class Version(_BaseVersion): ...@@ -237,32 +249,57 @@ class Version(_BaseVersion):
parts = [] parts = []
# Epoch # Epoch
if self._version.epoch != 0: if self.epoch != 0:
parts.append("{0}!".format(self._version.epoch)) parts.append("{0}!".format(self.epoch))
# Release segment # Release segment
parts.append(".".join(str(x) for x in self._version.release)) parts.append(".".join(str(x) for x in self.release))
# Pre-release # Pre-release
if self._version.pre is not None: if self.pre is not None:
parts.append("".join(str(x) for x in self._version.pre)) parts.append("".join(str(x) for x in self.pre))
# Post-release # Post-release
if self._version.post is not None: if self.post is not None:
parts.append(".post{0}".format(self._version.post[1])) parts.append(".post{0}".format(self.post))
# Development release # Development release
if self._version.dev is not None: if self.dev is not None:
parts.append(".dev{0}".format(self._version.dev[1])) parts.append(".dev{0}".format(self.dev))
# Local version segment # Local version segment
if self._version.local is not None: if self.local is not None:
parts.append( parts.append("+{0}".format(self.local))
"+{0}".format(".".join(str(x) for x in self._version.local))
)
return "".join(parts) return "".join(parts)
@property
def epoch(self):
return self._version.epoch
@property
def release(self):
return self._version.release
@property
def pre(self):
return self._version.pre
@property
def post(self):
return self._version.post[1] if self._version.post else None
@property
def dev(self):
return self._version.dev[1] if self._version.dev else None
@property
def local(self):
if self._version.local:
return ".".join(str(x) for x in self._version.local)
else:
return None
@property @property
def public(self): def public(self):
return str(self).split("+", 1)[0] return str(self).split("+", 1)[0]
...@@ -272,27 +309,25 @@ class Version(_BaseVersion): ...@@ -272,27 +309,25 @@ class Version(_BaseVersion):
parts = [] parts = []
# Epoch # Epoch
if self._version.epoch != 0: if self.epoch != 0:
parts.append("{0}!".format(self._version.epoch)) parts.append("{0}!".format(self.epoch))
# Release segment # Release segment
parts.append(".".join(str(x) for x in self._version.release)) parts.append(".".join(str(x) for x in self.release))
return "".join(parts) return "".join(parts)
@property
def local(self):
version_string = str(self)
if "+" in version_string:
return version_string.split("+", 1)[1]
@property @property
def is_prerelease(self): def is_prerelease(self):
return bool(self._version.dev or self._version.pre) return self.dev is not None or self.pre is not None
@property @property
def is_postrelease(self): def is_postrelease(self):
return bool(self._version.post) return self.post is not None
@property
def is_devrelease(self):
return self.dev is not None
def _parse_letter_version(letter, number): def _parse_letter_version(letter, number):
...@@ -326,7 +361,7 @@ def _parse_letter_version(letter, number): ...@@ -326,7 +361,7 @@ def _parse_letter_version(letter, number):
return letter, int(number) return letter, int(number)
_local_version_seperators = re.compile(r"[\._-]") _local_version_separators = re.compile(r"[\._-]")
def _parse_local_version(local): def _parse_local_version(local):
...@@ -336,7 +371,7 @@ def _parse_local_version(local): ...@@ -336,7 +371,7 @@ def _parse_local_version(local):
if local is not None: if local is not None:
return tuple( return tuple(
part.lower() if not part.isdigit() else int(part) part.lower() if not part.isdigit() else int(part)
for part in _local_version_seperators.split(local) for part in _local_version_separators.split(local)
) )
...@@ -347,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local): ...@@ -347,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# re-reverse it back into the correct order and make it a tuple and use # re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key. # that for our sorting key.
release = tuple( release = tuple(
reversed(list( reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
itertools.dropwhile(
lambda x: x == 0,
reversed(release),
)
))
) )
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
...@@ -385,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local): ...@@ -385,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# - Numeric segments sort numerically # - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes # - Shorter versions sort before longer versions when the prefixes
# match exactly # match exactly
local = tuple( local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
(i, "") if isinstance(i, int) else (-Infinity, i)
for i in local
)
return epoch, release, pre, post, dev, local return epoch, release, pre, post, dev, local
packaging==16.8 packaging==19.2
pyparsing==2.2.1 pyparsing==2.2.1
six==1.10.0 six==1.10.0
ordered-set==3.1.1
...@@ -25,7 +25,8 @@ def default_filter(src, dst): ...@@ -25,7 +25,8 @@ def default_filter(src, dst):
return dst return dst
def unpack_archive(filename, extract_dir, progress_filter=default_filter, def unpack_archive(
filename, extract_dir, progress_filter=default_filter,
drivers=None): drivers=None):
"""Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat``
...@@ -148,7 +149,8 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): ...@@ -148,7 +149,8 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
# resolve any links and to extract the link targets as normal # resolve any links and to extract the link targets as normal
# files # files
while member is not None and (member.islnk() or member.issym()): while member is not None and (
member.islnk() or member.issym()):
linkpath = member.linkname linkpath = member.linkname
if member.issym(): if member.issym():
base = posixpath.dirname(member.name) base = posixpath.dirname(member.name)
......
This diff is collapsed.
...@@ -2,8 +2,7 @@ __all__ = [ ...@@ -2,8 +2,7 @@ __all__ = [
'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info',
'dist_info',
] ]
from distutils.command.bdist import bdist from distutils.command.bdist import bdist
......
...@@ -11,13 +11,14 @@ import os ...@@ -11,13 +11,14 @@ import os
import re import re
import textwrap import textwrap
import marshal import marshal
import warnings
from setuptools.extern import six from setuptools.extern import six
from pkg_resources import get_build_platform, Distribution, ensure_directory from pkg_resources import get_build_platform, Distribution, ensure_directory
from pkg_resources import EntryPoint from pkg_resources import EntryPoint
from setuptools.extension import Library from setuptools.extension import Library
from setuptools import Command from setuptools import Command, SetuptoolsDeprecationWarning
try: try:
# Python 2.7 or >=3.2 # Python 2.7 or >=3.2
...@@ -278,13 +279,19 @@ class bdist_egg(Command): ...@@ -278,13 +279,19 @@ class bdist_egg(Command):
if ep is None: if ep is None:
return 'w' # not an eggsecutable, do it the usual way. return 'w' # not an eggsecutable, do it the usual way.
warnings.warn(
"Eggsecutables are deprecated and will be removed in a future "
"version.",
SetuptoolsDeprecationWarning
)
if not ep.attrs or ep.extras: if not ep.attrs or ep.extras:
raise DistutilsSetupError( raise DistutilsSetupError(
"eggsecutable entry point (%r) cannot have 'extras' " "eggsecutable entry point (%r) cannot have 'extras' "
"or refer to a module" % (ep,) "or refer to a module" % (ep,)
) )
pyver = sys.version[:3] pyver = '{}.{}'.format(*sys.version_info)
pkg = ep.module_name pkg = ep.module_name
full = '.'.join(ep.attrs) full = '.'.join(ep.attrs)
base = ep.attrs[0] base = ep.attrs[0]
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment