diff options
45 files changed, 711 insertions, 1794 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0dc75e9b..40db5b03 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.5.1 +current_version = 41.6.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index bac35638..ba7b4647 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v41.6.0 +------- + +* #479: Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``. + + v41.5.1 ------- diff --git a/changelog.d/1861.change.rst b/changelog.d/1861.change.rst new file mode 100644 index 00000000..5a4e0a56 --- /dev/null +++ b/changelog.d/1861.change.rst @@ -0,0 +1 @@ +Fix empty namespace package installation from wheel. diff --git a/changelog.d/1898.breaking.rst b/changelog.d/1898.breaking.rst new file mode 100644 index 00000000..844a8a42 --- /dev/null +++ b/changelog.d/1898.breaking.rst @@ -0,0 +1 @@ +Removed the "upload" and "register" commands in favor of `twine <https://pypi.org/p/twine>`_. diff --git a/conftest.py b/conftest.py index 0d7b274c..1746bfb5 100644 --- a/conftest.py +++ b/conftest.py @@ -19,6 +19,7 @@ collect_ignore = [ if sys.version_info < (3,): collect_ignore.append('setuptools/lib2to3_ex.py') + collect_ignore.append('setuptools/_imp.py') if sys.version_info < (3, 6): diff --git a/docs/easy_install.txt b/docs/easy_install.txt deleted file mode 100644 index 544b9efd..00000000 --- a/docs/easy_install.txt +++ /dev/null @@ -1,1085 +0,0 @@ -============ -Easy Install -============ - -.. warning:: - Easy Install is deprecated. Do not use it. Instead use pip. If - you think you need Easy Install, please reach out to the PyPA - team (a ticket to pip or setuptools is fine), describing your - use-case. - -Easy Install is a python module (``easy_install``) bundled with ``setuptools`` -that lets you automatically download, build, install, and manage Python -packages. - -Please share your experiences with us! If you encounter difficulty installing -a package, please contact us via the `distutils mailing list -<http://mail.python.org/pipermail/distutils-sig/>`_. (Note: please DO NOT send -private email directly to the author of setuptools; it will be discarded. The -mailing list is a searchable archive of previously-asked and answered -questions; you should begin your research there before reporting something as a -bug -- and then do so via list discussion first.) - -(Also, if you'd like to learn about how you can use ``setuptools`` to make your -own packages work better with EasyInstall, or provide EasyInstall-like features -without requiring your users to use EasyInstall directly, you'll probably want -to check out the full documentation as well.) - -.. contents:: **Table of Contents** - - -Using "Easy Install" -==================== - - -.. _installation instructions: - -Installing "Easy Install" -------------------------- - -Please see the `setuptools PyPI page <https://pypi.org/project/setuptools/>`_ -for download links and basic installation instructions for each of the -supported platforms. - -You will need at least Python 3.4 or 2.7. An ``easy_install`` script will be -installed in the normal location for Python scripts on your platform. - -Note that the instructions on the setuptools PyPI page assume that you are -are installing to Python's primary ``site-packages`` directory. If this is -not the case, you should consult the section below on `Custom Installation -Locations`_ before installing. (And, on Windows, you should not use the -``.exe`` installer when installing to an alternate location.) - -Note that ``easy_install`` normally works by downloading files from the -internet. If you are behind an NTLM-based firewall that prevents Python -programs from accessing the net directly, you may wish to first install and use -the `APS proxy server <http://ntlmaps.sf.net/>`_, which lets you get past such -firewalls in the same way that your web browser(s) do. - -(Alternately, if you do not wish easy_install to actually download anything, you -can restrict it from doing so with the ``--allow-hosts`` option; see the -sections on `restricting downloads with --allow-hosts`_ and `command-line -options`_ for more details.) - - -Troubleshooting -~~~~~~~~~~~~~~~ - -If EasyInstall/setuptools appears to install correctly, and you can run the -``easy_install`` command but it fails with an ``ImportError``, the most likely -cause is that you installed to a location other than ``site-packages``, -without taking any of the steps described in the `Custom Installation -Locations`_ section below. Please see that section and follow the steps to -make sure that your custom location will work correctly. Then re-install. - -Similarly, if you can run ``easy_install``, and it appears to be installing -packages, but then you can't import them, the most likely issue is that you -installed EasyInstall correctly but are using it to install packages to a -non-standard location that hasn't been properly prepared. Again, see the -section on `Custom Installation Locations`_ for more details. - - -Windows Notes -~~~~~~~~~~~~~ - -Installing setuptools will provide an ``easy_install`` command according to -the techniques described in `Executables and Launchers`_. If the -``easy_install`` command is not available after installation, that section -provides details on how to configure Windows to make the commands available. - - -Downloading and Installing a Package ------------------------------------- - -For basic use of ``easy_install``, you need only supply the filename or URL of -a source distribution or .egg file (`Python Egg`__). - -__ http://peak.telecommunity.com/DevCenter/PythonEggs - -**Example 1**. Install a package by name, searching PyPI for the latest -version, and automatically downloading, building, and installing it:: - - easy_install SQLObject - -**Example 2**. Install or upgrade a package by name and version by finding -links on a given "download page":: - - easy_install -f http://pythonpaste.org/package_index.html SQLObject - -**Example 3**. Download a source distribution from a specified URL, -automatically building and installing it:: - - easy_install http://example.com/path/to/MyPackage-1.2.3.tgz - -**Example 4**. Install an already-downloaded .egg file:: - - easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg - -**Example 5**. Upgrade an already-installed package to the latest version -listed on PyPI:: - - easy_install --upgrade PyProtocols - -**Example 6**. Install a source distribution that's already downloaded and -extracted in the current directory (New in 0.5a9):: - - easy_install . - -**Example 7**. (New in 0.6a1) Find a source distribution or Subversion -checkout URL for a package, and extract it or check it out to -``~/projects/sqlobject`` (the name will always be in all-lowercase), where it -can be examined or edited. (The package will not be installed, but it can -easily be installed with ``easy_install ~/projects/sqlobject``. See `Editing -and Viewing Source Packages`_ below for more info.):: - - easy_install --editable --build-directory ~/projects SQLObject - -**Example 7**. (New in 0.6.11) Install a distribution within your home dir:: - - easy_install --user SQLAlchemy - -Easy Install accepts URLs, filenames, PyPI package names (i.e., ``distutils`` -"distribution" names), and package+version specifiers. In each case, it will -attempt to locate the latest available version that meets your criteria. - -When downloading or processing downloaded files, Easy Install recognizes -distutils source distribution files with extensions of .tgz, .tar, .tar.gz, -.tar.bz2, or .zip. And of course it handles already-built .egg -distributions as well as ``.win32.exe`` installers built using distutils. - -By default, packages are installed to the running Python installation's -``site-packages`` directory, unless you provide the ``-d`` or ``--install-dir`` -option to specify an alternative directory, or specify an alternate location -using distutils configuration files. (See `Configuration Files`_, below.) - -By default, any scripts included with the package are installed to the running -Python installation's standard script installation location. However, if you -specify an installation directory via the command line or a config file, then -the default directory for installing scripts will be the same as the package -installation directory, to ensure that the script will have access to the -installed package. You can override this using the ``-s`` or ``--script-dir`` -option. - -Installed packages are added to an ``easy-install.pth`` file in the install -directory, so that Python will always use the most-recently-installed version -of the package. If you would like to be able to select which version to use at -runtime, you should use the ``-m`` or ``--multi-version`` option. - - -Upgrading a Package -------------------- - -You don't need to do anything special to upgrade a package: just install the -new version, either by requesting a specific version, e.g.:: - - easy_install "SomePackage==2.0" - -a version greater than the one you have now:: - - easy_install "SomePackage>2.0" - -using the upgrade flag, to find the latest available version on PyPI:: - - easy_install --upgrade SomePackage - -or by using a download page, direct download URL, or package filename:: - - easy_install -f http://example.com/downloads ExamplePackage - - easy_install http://example.com/downloads/ExamplePackage-2.0-py2.4.egg - - easy_install my_downloads/ExamplePackage-2.0.tgz - -If you're using ``-m`` or ``--multi-version`` , using the ``require()`` -function at runtime automatically selects the newest installed version of a -package that meets your version criteria. So, installing a newer version is -the only step needed to upgrade such packages. - -If you're installing to a directory on PYTHONPATH, or a configured "site" -directory (and not using ``-m``), installing a package automatically replaces -any previous version in the ``easy-install.pth`` file, so that Python will -import the most-recently installed version by default. So, again, installing -the newer version is the only upgrade step needed. - -If you haven't suppressed script installation (using ``--exclude-scripts`` or -``-x``), then the upgraded version's scripts will be installed, and they will -be automatically patched to ``require()`` the corresponding version of the -package, so that you can use them even if they are installed in multi-version -mode. - -``easy_install`` never actually deletes packages (unless you're installing a -package with the same name and version number as an existing package), so if -you want to get rid of older versions of a package, please see `Uninstalling -Packages`_, below. - - -Changing the Active Version ---------------------------- - -If you've upgraded a package, but need to revert to a previously-installed -version, you can do so like this:: - - easy_install PackageName==1.2.3 - -Where ``1.2.3`` is replaced by the exact version number you wish to switch to. -If a package matching the requested name and version is not already installed -in a directory on ``sys.path``, it will be located via PyPI and installed. - -If you'd like to switch to the latest installed version of ``PackageName``, you -can do so like this:: - - easy_install PackageName - -This will activate the latest installed version. (Note: if you have set any -``find_links`` via distutils configuration files, those download pages will be -checked for the latest available version of the package, and it will be -downloaded and installed if it is newer than your current version.) - -Note that changing the active version of a package will install the newly -active version's scripts, unless the ``--exclude-scripts`` or ``-x`` option is -specified. - - -Uninstalling Packages ---------------------- - -If you have replaced a package with another version, then you can just delete -the package(s) you don't need by deleting the PackageName-versioninfo.egg file -or directory (found in the installation directory). - -If you want to delete the currently installed version of a package (or all -versions of a package), you should first run:: - - easy_install -m PackageName - -This will ensure that Python doesn't continue to search for a package you're -planning to remove. After you've done this, you can safely delete the .egg -files or directories, along with any scripts you wish to remove. - - -Managing Scripts ----------------- - -Whenever you install, upgrade, or change versions of a package, EasyInstall -automatically installs the scripts for the selected package version, unless -you tell it not to with ``-x`` or ``--exclude-scripts``. If any scripts in -the script directory have the same name, they are overwritten. - -Thus, you do not normally need to manually delete scripts for older versions of -a package, unless the newer version of the package does not include a script -of the same name. However, if you are completely uninstalling a package, you -may wish to manually delete its scripts. - -EasyInstall's default behavior means that you can normally only run scripts -from one version of a package at a time. If you want to keep multiple versions -of a script available, however, you can simply use the ``--multi-version`` or -``-m`` option, and rename the scripts that EasyInstall creates. This works -because EasyInstall installs scripts as short code stubs that ``require()`` the -matching version of the package the script came from, so renaming the script -has no effect on what it executes. - -For example, suppose you want to use two versions of the ``rst2html`` tool -provided by the `docutils <http://docutils.sf.net/>`_ package. You might -first install one version:: - - easy_install -m docutils==0.3.9 - -then rename the ``rst2html.py`` to ``r2h_039``, and install another version:: - - easy_install -m docutils==0.3.10 - -This will create another ``rst2html.py`` script, this one using docutils -version 0.3.10 instead of 0.3.9. You now have two scripts, each using a -different version of the package. (Notice that we used ``-m`` for both -installations, so that Python won't lock us out of using anything but the most -recently-installed version of the package.) - - -Executables and Launchers -------------------------- - -On Unix systems, scripts are installed with as natural files with a "#!" -header and no extension and they launch under the Python version indicated in -the header. - -On Windows, there is no mechanism to "execute" files without extensions, so -EasyInstall provides two techniques to mirror the Unix behavior. The behavior -is indicated by the SETUPTOOLS_LAUNCHER environment variable, which may be -"executable" (default) or "natural". - -Regardless of the technique used, the script(s) will be installed to a Scripts -directory (by default in the Python installation directory). It is recommended -for EasyInstall that you ensure this directory is in the PATH environment -variable. The easiest way to ensure the Scripts directory is in the PATH is -to run ``Tools\Scripts\win_add2path.py`` from the Python directory. - -Note that instead of changing your ``PATH`` to include the Python scripts -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 -`Command-Line Options`_ and `Configuration Files`_. During installation, -pass command line options (such as ``--script-dir``) to control where -scripts will be installed. - - -Windows Executable Launcher -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the "executable" launcher is used, EasyInstall will create a '.exe' -launcher of the same name beside each installed script (including -``easy_install`` itself). These small .exe files launch the script of the -same name using the Python version indicated in the '#!' header. - -This behavior is currently default. To force -the use of executable launchers, set ``SETUPTOOLS_LAUNCHER`` to "executable". - -Natural Script Launcher -~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall also supports deferring to an external launcher such as -`pylauncher <https://bitbucket.org/pypa/pylauncher>`_ for launching scripts. -Enable this experimental functionality by setting the -``SETUPTOOLS_LAUNCHER`` environment variable to "natural". EasyInstall will -then install scripts as simple -scripts with a .pya (or .pyw) extension appended. If these extensions are -associated with the pylauncher and listed in the PATHEXT environment variable, -these scripts can then be invoked simply and directly just like any other -executable. This behavior may become default in a future version. - -EasyInstall uses the .pya extension instead of simply -the typical '.py' extension. This distinct extension is necessary to prevent -Python -from treating the scripts as importable modules (where name conflicts exist). -Current releases of pylauncher do not yet associate with .pya files by -default, but future versions should do so. - - -Tips & Techniques ------------------ - -Multiple Python Versions -~~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall installs itself under two names: -``easy_install`` and ``easy_install-N.N``, where ``N.N`` is the Python version -used to install it. Thus, if you install EasyInstall for both Python 3.2 and -2.7, you can use the ``easy_install-3.2`` or ``easy_install-2.7`` scripts to -install packages for the respective Python version. - -Setuptools also supplies easy_install as a runnable module which may be -invoked using ``python -m easy_install`` for any Python with Setuptools -installed. - -Restricting Downloads with ``--allow-hosts`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can use the ``--allow-hosts`` (``-H``) option to restrict what domains -EasyInstall will look for links and downloads on. ``--allow-hosts=None`` -prevents downloading altogether. You can also use wildcards, for example -to restrict downloading to hosts in your own intranet. See the section below -on `Command-Line Options`_ for more details on the ``--allow-hosts`` option. - -By default, there are no host restrictions in effect, but you can change this -default by editing the appropriate `configuration files`_ and adding: - -.. code-block:: ini - - [easy_install] - allow_hosts = *.myintranet.example.com,*.python.org - -The above example would then allow downloads only from hosts in the -``python.org`` and ``myintranet.example.com`` domains, unless overridden on the -command line. - - -Installing on Un-networked Machines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Just copy the eggs or source packages you need to a directory on the target -machine, then use the ``-f`` or ``--find-links`` option to specify that -directory's location. For example:: - - easy_install -H None -f somedir SomePackage - -will attempt to install SomePackage using only eggs and source packages found -in ``somedir`` and disallowing all remote access. You should of course make -sure you have all of SomePackage's dependencies available in somedir. - -If you have another machine of the same operating system and library versions -(or if the packages aren't platform-specific), you can create the directory of -eggs using a command like this:: - - easy_install -zmaxd somedir SomePackage - -This will tell EasyInstall to put zipped eggs or source packages for -SomePackage and all its dependencies into ``somedir``, without creating any -scripts or .pth files. You can then copy the contents of ``somedir`` to the -target machine. (``-z`` means zipped eggs, ``-m`` means multi-version, which -prevents .pth files from being used, ``-a`` means to copy all the eggs needed, -even if they're installed elsewhere on the machine, and ``-d`` indicates the -directory to place the eggs in.) - -You can also build the eggs from local development packages that were installed -with the ``setup.py develop`` command, by including the ``-l`` option, e.g.:: - - easy_install -zmaxld somedir SomePackage - -This will use locally-available source distributions to build the eggs. - - -Packaging Others' Projects As Eggs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Need to distribute a package that isn't published in egg form? You can use -EasyInstall to build eggs for a project. You'll want to use the ``--zip-ok``, -``--exclude-scripts``, and possibly ``--no-deps`` options (``-z``, ``-x`` and -``-N``, respectively). Use ``-d`` or ``--install-dir`` to specify the location -where you'd like the eggs placed. By placing them in a directory that is -published to the web, you can then make the eggs available for download, either -in an intranet or to the internet at large. - -If someone distributes a package in the form of a single ``.py`` file, you can -wrap it in an egg by tacking an ``#egg=name-version`` suffix on the file's URL. -So, something like this:: - - easy_install -f "http://some.example.com/downloads/foo.py#egg=foo-1.0" foo - -will install the package as an egg, and this:: - - easy_install -zmaxd. \ - -f "http://some.example.com/downloads/foo.py#egg=foo-1.0" foo - -will create a ``.egg`` file in the current directory. - - -Creating your own Package Index -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In addition to local directories and the Python Package Index, EasyInstall can -find download links on most any web page whose URL is given to the ``-f`` -(``--find-links``) option. In the simplest case, you can simply have a web -page with links to eggs or Python source packages, even an automatically -generated directory listing (such as the Apache web server provides). - -If you are setting up an intranet site for package downloads, you may want to -configure the target machines to use your download site by default, adding -something like this to their `configuration files`_: - -.. code-block:: ini - - [easy_install] - find_links = http://mypackages.example.com/somedir/ - http://turbogears.org/download/ - http://peak.telecommunity.com/dist/ - -As you can see, you can list multiple URLs separated by whitespace, continuing -on multiple lines if necessary (as long as the subsequent lines are indented. - -If you are more ambitious, you can also create an entirely custom package index -or PyPI mirror. See the ``--index-url`` option under `Command-Line Options`_, -below, and also the section on `Package Index "API"`_. - - -Password-Protected Sites ------------------------- - -If a site you want to download from is password-protected using HTTP "Basic" -authentication, you can specify your credentials in the URL, like so:: - - http://some_userid:some_password@some.example.com/some_path/ - -You can do this with both index page URLs and direct download URLs. As long -as any HTML pages read by easy_install use *relative* links to point to the -downloads, the same user ID and password will be used to do the downloading. - -Using .pypirc Credentials -------------------------- - -In additional to supplying credentials in the URL, ``easy_install`` will also -honor credentials if present in the .pypirc file. Teams maintaining a private -repository of packages may already have defined access credentials for -uploading packages according to the distutils documentation. ``easy_install`` -will attempt to honor those if present. Refer to the distutils documentation -for Python 2.5 or later for details on the syntax. - -Controlling Build Options -~~~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall respects standard distutils `Configuration Files`_, so you can use -them to configure build options for packages that it installs from source. For -example, if you are on Windows using the MinGW compiler, you can configure the -default compiler by putting something like this: - -.. code-block:: ini - - [build] - compiler = mingw32 - -into the appropriate distutils configuration file. In fact, since this is just -normal distutils configuration, it will affect any builds using that config -file, not just ones done by EasyInstall. For example, if you add those lines -to ``distutils.cfg`` in the ``distutils`` package directory, it will be the -default compiler for *all* packages you build. See `Configuration Files`_ -below for a list of the standard configuration file locations, and links to -more documentation on using distutils configuration files. - - -Editing and Viewing Source Packages -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes a package's source distribution contains additional documentation, -examples, configuration files, etc., that are not part of its actual code. If -you want to be able to examine these files, you can use the ``--editable`` -option to EasyInstall, and EasyInstall will look for a source distribution -or Subversion URL for the package, then download and extract it or check it out -as a subdirectory of the ``--build-directory`` you specify. If you then wish -to install the package after editing or configuring it, you can do so by -rerunning EasyInstall with that directory as the target. - -Note that using ``--editable`` stops EasyInstall from actually building or -installing the package; it just finds, obtains, and possibly unpacks it for -you. This allows you to make changes to the package if necessary, and to -either install it in development mode using ``setup.py develop`` (if the -package uses setuptools, that is), or by running ``easy_install projectdir`` -(where ``projectdir`` is the subdirectory EasyInstall created for the -downloaded package. - -In order to use ``--editable`` (``-e`` for short), you *must* also supply a -``--build-directory`` (``-b`` for short). The project will be placed in a -subdirectory of the build directory. The subdirectory will have the same -name as the project itself, but in all-lowercase. If a file or directory of -that name already exists, EasyInstall will print an error message and exit. - -Also, when using ``--editable``, you cannot use URLs or filenames as arguments. -You *must* specify project names (and optional version requirements) so that -EasyInstall knows what directory name(s) to create. If you need to force -EasyInstall to use a particular URL or filename, you should specify it as a -``--find-links`` item (``-f`` for short), and then also specify -the project name, e.g.:: - - easy_install -eb ~/projects \ - -fhttp://prdownloads.sourceforge.net/ctypes/ctypes-0.9.6.tar.gz?download \ - ctypes==0.9.6 - - -Dealing with Installation Conflicts -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(NOTE: As of 0.6a11, this section is obsolete; it is retained here only so that -people using older versions of EasyInstall can consult it. As of version -0.6a11, installation conflicts are handled automatically without deleting the -old or system-installed packages, and without ignoring the issue. Instead, -eggs are automatically shifted to the front of ``sys.path`` using special -code added to the ``easy-install.pth`` file. So, if you are using version -0.6a11 or better of setuptools, you do not need to worry about conflicts, -and the following issues do not apply to you.) - -EasyInstall installs distributions in a "managed" way, such that each -distribution can be independently activated or deactivated on ``sys.path``. -However, packages that were not installed by EasyInstall are "unmanaged", -in that they usually live all in one directory and cannot be independently -activated or deactivated. - -As a result, if you are using EasyInstall to upgrade an existing package, or -to install a package with the same name as an existing package, EasyInstall -will warn you of the conflict. (This is an improvement over ``setup.py -install``, because the ``distutils`` just install new packages on top of old -ones, possibly combining two unrelated packages or leaving behind modules that -have been deleted in the newer version of the package.) - -EasyInstall will stop the installation if it detects a conflict -between an existing, "unmanaged" package, and a module or package in any of -the distributions you're installing. It will display a list of all of the -existing files and directories that would need to be deleted for the new -package to be able to function correctly. To proceed, you must manually -delete these conflicting files and directories and re-run EasyInstall. - -Of course, once you've replaced all of your existing "unmanaged" packages with -versions managed by EasyInstall, you won't have any more conflicts to worry -about! - - -Compressed Installation -~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall tries to install packages in zipped form, if it can. Zipping -packages can improve Python's overall import performance if you're not using -the ``--multi-version`` option, because Python processes zipfile entries on -``sys.path`` much faster than it does directories. - -As of version 0.5a9, EasyInstall analyzes packages to determine whether they -can be safely installed as a zipfile, and then acts on its analysis. (Previous -versions would not install a package as a zipfile unless you used the -``--zip-ok`` option.) - -The current analysis approach is fairly conservative; it currently looks for: - - * Any use of the ``__file__`` or ``__path__`` variables (which should be - replaced with ``pkg_resources`` API calls) - - * Possible use of ``inspect`` functions that expect to manipulate source files - (e.g. ``inspect.getsource()``) - - * Top-level modules that might be scripts used with ``python -m`` (Python 2.4) - -If any of the above are found in the package being installed, EasyInstall will -assume that the package cannot be safely run from a zipfile, and unzip it to -a directory instead. You can override this analysis with the ``-zip-ok`` flag, -which will tell EasyInstall to install the package as a zipfile anyway. Or, -you can use the ``--always-unzip`` flag, in which case EasyInstall will always -unzip, even if its analysis says the package is safe to run as a zipfile. - -Normally, however, it is simplest to let EasyInstall handle the determination -of whether to zip or unzip, and only specify overrides when needed to work -around a problem. If you find you need to override EasyInstall's guesses, you -may want to contact the package author and the EasyInstall maintainers, so that -they can make appropriate changes in future versions. - -(Note: If a package uses ``setuptools`` in its setup script, the package author -has the option to declare the package safe or unsafe for zipped usage via the -``zip_safe`` argument to ``setup()``. If the package author makes such a -declaration, EasyInstall believes the package's author and does not perform its -own analysis. However, your command-line option, if any, will still override -the package author's choice.) - - -Reference Manual -================ - -Configuration Files -------------------- - -(New in 0.4a2) - -You may specify default options for EasyInstall using the standard -distutils configuration files, under the command heading ``easy_install``. -EasyInstall will look first for a ``setup.cfg`` file in the current directory, -then a ``~/.pydistutils.cfg`` or ``$HOME\\pydistutils.cfg`` (on Unix-like OSes -and Windows, respectively), and finally a ``distutils.cfg`` file in the -``distutils`` package directory. Here's a simple example: - -.. code-block:: ini - - [easy_install] - - # set the default location to install packages - install_dir = /home/me/lib/python - - # Notice that indentation can be used to continue an option - # value; this is especially useful for the "--find-links" - # option, which tells easy_install to use download links on - # these pages before consulting PyPI: - # - find_links = http://sqlobject.org/ - http://peak.telecommunity.com/dist/ - -In addition to accepting configuration for its own options under -``[easy_install]``, EasyInstall also respects defaults specified for other -distutils commands. For example, if you don't set an ``install_dir`` for -``[easy_install]``, but *have* set an ``install_lib`` for the ``[install]`` -command, this will become EasyInstall's default installation directory. Thus, -if you are already using distutils configuration files to set default install -locations, build options, etc., EasyInstall will respect your existing settings -until and unless you override them explicitly in an ``[easy_install]`` section. - -For more information, see also the current Python documentation on the `use and -location of distutils configuration files <https://docs.python.org/install/index.html#inst-config-files>`_. - -Notice that ``easy_install`` will use the ``setup.cfg`` from the current -working directory only if it was triggered from ``setup.py`` through the -``install_requires`` option. The standalone command will not use that file. - -Command-Line Options --------------------- - -``--zip-ok, -z`` - Install all packages as zip files, even if they are marked as unsafe for - running as a zipfile. This can be useful when EasyInstall's analysis - of a non-setuptools package is too conservative, but keep in mind that - the package may not work correctly. (Changed in 0.5a9; previously this - option was required in order for zipped installation to happen at all.) - -``--always-unzip, -Z`` - Don't install any packages as zip files, even if the packages are marked - as safe for running as a zipfile. This can be useful if a package does - something unsafe, but not in a way that EasyInstall can easily detect. - EasyInstall's default analysis is currently very conservative, however, so - you should only use this option if you've had problems with a particular - package, and *after* reporting the problem to the package's maintainer and - to the EasyInstall maintainers. - - (Note: the ``-z/-Z`` options only affect the installation of newly-built - or downloaded packages that are not already installed in the target - directory; if you want to convert an existing installed version from - zipped to unzipped or vice versa, you'll need to delete the existing - version first, and re-run EasyInstall.) - -``--multi-version, -m`` - "Multi-version" mode. Specifying this option prevents ``easy_install`` from - adding an ``easy-install.pth`` entry for the package being installed, and - if an entry for any version the package already exists, it will be removed - upon successful installation. In multi-version mode, no specific version of - the package is available for importing, unless you use - ``pkg_resources.require()`` to put it on ``sys.path``. This can be as - simple as:: - - from pkg_resources import require - require("SomePackage", "OtherPackage", "MyPackage") - - which will put the latest installed version of the specified packages on - ``sys.path`` for you. (For more advanced uses, like selecting specific - versions and enabling optional dependencies, see the ``pkg_resources`` API - doc.) - - Changed in 0.6a10: this option is no longer silently enabled when - installing to a non-PYTHONPATH, non-"site" directory. You must always - explicitly use this option if you want it to be active. - -``--upgrade, -U`` (New in 0.5a4) - By default, EasyInstall only searches online if a project/version - requirement can't be met by distributions already installed - on sys.path or the installation directory. However, if you supply the - ``--upgrade`` or ``-U`` flag, EasyInstall will always check the package - index and ``--find-links`` URLs before selecting a version to install. In - this way, you can force EasyInstall to use the latest available version of - any package it installs (subject to any version requirements that might - exclude such later versions). - -``--install-dir=DIR, -d DIR`` - Set the installation directory. It is up to you to ensure that this - directory is on ``sys.path`` at runtime, and to use - ``pkg_resources.require()`` to enable the installed package(s) that you - need. - - (New in 0.4a2) If this option is not directly specified on the command line - or in a distutils configuration file, the distutils default installation - location is used. Normally, this would be the ``site-packages`` directory, - but if you are using distutils configuration files, setting things like - ``prefix`` or ``install_lib``, then those settings are taken into - account when computing the default installation directory, as is the - ``--prefix`` option. - -``--script-dir=DIR, -s DIR`` - Set the script installation directory. If you don't supply this option - (via the command line or a configuration file), but you *have* supplied - an ``--install-dir`` (via command line or config file), then this option - defaults to the same directory, so that the scripts will be able to find - their associated package installation. Otherwise, this setting defaults - to the location where the distutils would normally install scripts, taking - any distutils configuration file settings into account. - -``--exclude-scripts, -x`` - Don't install scripts. This is useful if you need to install multiple - versions of a package, but do not want to reset the version that will be - run by scripts that are already installed. - -``--user`` (New in 0.6.11) - Use the user-site-packages as specified in :pep:`370` - instead of the global site-packages. - -``--always-copy, -a`` (New in 0.5a4) - Copy all needed distributions to the installation directory, even if they - are already present in a directory on sys.path. In older versions of - EasyInstall, this was the default behavior, but now you must explicitly - request it. By default, EasyInstall will no longer copy such distributions - from other sys.path directories to the installation directory, unless you - explicitly gave the distribution's filename on the command line. - - Note that as of 0.6a10, using this option excludes "system" and - "development" eggs from consideration because they can't be reliably - copied. This may cause EasyInstall to choose an older version of a package - than what you expected, or it may cause downloading and installation of a - fresh copy of something that's already installed. You will see warning - messages for any eggs that EasyInstall skips, before it falls back to an - older version or attempts to download a fresh copy. - -``--find-links=URLS_OR_FILENAMES, -f URLS_OR_FILENAMES`` - Scan the specified "download pages" or directories for direct links to eggs - or other distributions. Any existing file or directory names or direct - download URLs are immediately added to EasyInstall's search cache, and any - indirect URLs (ones that don't point to eggs or other recognized archive - formats) are added to a list of additional places to search for download - links. As soon as EasyInstall has to go online to find a package (either - because it doesn't exist locally, or because ``--upgrade`` or ``-U`` was - used), the specified URLs will be downloaded and scanned for additional - direct links. - - Eggs and archives found by way of ``--find-links`` are only downloaded if - they are needed to meet a requirement specified on the command line; links - to unneeded packages are ignored. - - If all requested packages can be found using links on the specified - download pages, the Python Package Index will not be consulted unless you - also specified the ``--upgrade`` or ``-U`` option. - - (Note: if you want to refer to a local HTML file containing links, you must - use a ``file:`` URL, as filenames that do not refer to a directory, egg, or - archive are ignored.) - - You may specify multiple URLs or file/directory names with this option, - separated by whitespace. Note that on the command line, you will probably - have to surround the URL list with quotes, so that it is recognized as a - single option value. You can also specify URLs in a configuration file; - see `Configuration Files`_, above. - - Changed in 0.6a10: previously all URLs and directories passed to this - option were scanned as early as possible, but from 0.6a10 on, only - directories and direct archive links are scanned immediately; URLs are not - retrieved unless a package search was already going to go online due to a - package not being available locally, or due to the use of the ``--update`` - or ``-U`` option. - -``--no-find-links`` Blocks the addition of any link. - This parameter is useful if you want to avoid adding links defined in a - project easy_install is installing (whether it's a requested project or a - dependency). When used, ``--find-links`` is ignored. - - Added in Distribute 0.6.11 and Setuptools 0.7. - -``--index-url=URL, -i URL`` (New in 0.4a1; default changed in 0.6c7) - Specifies the base URL of the Python Package Index. The default is - https://pypi.org/simple/ if not specified. When a package is requested - that is not locally available or linked from a ``--find-links`` download - page, the package index will be searched for download pages for the needed - package, and those download pages will be searched for links to download - an egg or source distribution. - -``--editable, -e`` (New in 0.6a1) - Only find and download source distributions for the specified projects, - unpacking them to subdirectories of the specified ``--build-directory``. - EasyInstall will not actually build or install the requested projects or - their dependencies; it will just find and extract them for you. See - `Editing and Viewing Source Packages`_ above for more details. - -``--build-directory=DIR, -b DIR`` (UPDATED in 0.6a1) - Set the directory used to build source packages. If a package is built - from a source distribution or checkout, it will be extracted to a - subdirectory of the specified directory. The subdirectory will have the - same name as the extracted distribution's project, but in all-lowercase. - If a file or directory of that name already exists in the given directory, - a warning will be printed to the console, and the build will take place in - a temporary directory instead. - - This option is most useful in combination with the ``--editable`` option, - which forces EasyInstall to *only* find and extract (but not build and - install) source distributions. See `Editing and Viewing Source Packages`_, - above, for more information. - -``--verbose, -v, --quiet, -q`` (New in 0.4a4) - Control the level of detail of EasyInstall's progress messages. The - default detail level is "info", which prints information only about - relatively time-consuming operations like running a setup script, unpacking - an archive, or retrieving a URL. Using ``-q`` or ``--quiet`` drops the - detail level to "warn", which will only display installation reports, - warnings, and errors. Using ``-v`` or ``--verbose`` increases the detail - level to include individual file-level operations, link analysis messages, - and distutils messages from any setup scripts that get run. If you include - the ``-v`` option more than once, the second and subsequent uses are passed - down to any setup scripts, increasing the verbosity of their reporting as - well. - -``--dry-run, -n`` (New in 0.4a4) - Don't actually install the package or scripts. This option is passed down - to any setup scripts run, so packages should not actually build either. - This does *not* skip downloading, nor does it skip extracting source - distributions to a temporary/build directory. - -``--optimize=LEVEL``, ``-O LEVEL`` (New in 0.4a4) - If you are installing from a source distribution, and are *not* using the - ``--zip-ok`` option, this option controls the optimization level for - compiling installed ``.py`` files to ``.pyo`` files. It does not affect - the compilation of modules contained in ``.egg`` files, only those in - ``.egg`` directories. The optimization level can be set to 0, 1, or 2; - the default is 0 (unless it's set under ``install`` or ``install_lib`` in - one of your distutils configuration files). - -``--record=FILENAME`` (New in 0.5a4) - Write a record of all installed files to FILENAME. This is basically the - same as the same option for the standard distutils "install" command, and - is included for compatibility with tools that expect to pass this option - to "setup.py install". - -``--site-dirs=DIRLIST, -S DIRLIST`` (New in 0.6a1) - Specify one or more custom "site" directories (separated by commas). - "Site" directories are directories where ``.pth`` files are processed, such - as the main Python ``site-packages`` directory. As of 0.6a10, EasyInstall - automatically detects whether a given directory processes ``.pth`` files - (or can be made to do so), so you should not normally need to use this - option. It is is now only necessary if you want to override EasyInstall's - judgment and force an installation directory to be treated as if it - supported ``.pth`` files. - -``--no-deps, -N`` (New in 0.6a6) - Don't install any dependencies. This is intended as a convenience for - tools that wrap eggs in a platform-specific packaging system. (We don't - recommend that you use it for anything else.) - -``--allow-hosts=PATTERNS, -H PATTERNS`` (New in 0.6a6) - Restrict downloading and spidering to hosts matching the specified glob - patterns. E.g. ``-H *.python.org`` restricts web access so that only - packages listed and downloadable from machines in the ``python.org`` - domain. The glob patterns must match the *entire* user/host/port section of - the target URL(s). For example, ``*.python.org`` will NOT accept a URL - like ``http://python.org/foo`` or ``http://www.python.org:8080/``. - Multiple patterns can be specified by separating them with commas. The - default pattern is ``*``, which matches anything. - - In general, this option is mainly useful for blocking EasyInstall's web - access altogether (e.g. ``-Hlocalhost``), or to restrict it to an intranet - or other trusted site. EasyInstall will do the best it can to satisfy - dependencies given your host restrictions, but of course can fail if it - can't find suitable packages. EasyInstall displays all blocked URLs, so - that you can adjust your ``--allow-hosts`` setting if it is more strict - than you intended. Some sites may wish to define a restrictive default - setting for this option in their `configuration files`_, and then manually - override the setting on the command line as needed. - -``--prefix=DIR`` (New in 0.6a10) - Use the specified directory as a base for computing the default - installation and script directories. On Windows, the resulting default - directories will be ``prefix\\Lib\\site-packages`` and ``prefix\\Scripts``, - while on other platforms the defaults will be - ``prefix/lib/python2.X/site-packages`` (with the appropriate version - substituted) for libraries and ``prefix/bin`` for scripts. - - Note that the ``--prefix`` option only sets the *default* installation and - script directories, and does not override the ones set on the command line - or in a configuration file. - -``--local-snapshots-ok, -l`` (New in 0.6c6) - Normally, EasyInstall prefers to only install *released* versions of - projects, not in-development ones, because such projects may not - have a currently-valid version number. So, it usually only installs them - when their ``setup.py`` directory is explicitly passed on the command line. - - However, if this option is used, then any in-development projects that were - installed using the ``setup.py develop`` command, will be used to build - eggs, effectively upgrading the "in-development" project to a snapshot - release. Normally, this option is used only in conjunction with the - ``--always-copy`` option to create a distributable snapshot of every egg - needed to run an application. - - Note that if you use this option, you must make sure that there is a valid - version number (such as an SVN revision number tag) for any in-development - projects that may be used, as otherwise EasyInstall may not be able to tell - what version of the project is "newer" when future installations or - upgrades are attempted. - - -.. _non-root installation: - -Custom Installation Locations ------------------------------ - -By default, EasyInstall installs python packages into Python's main ``site-packages`` directory, -and manages them using a custom ``.pth`` file in that same directory. - -Very often though, a user or developer wants ``easy_install`` to install and manage python packages -in an alternative location, usually for one of 3 reasons: - -1. They don't have access to write to the main Python site-packages directory. - -2. They want a user-specific stash of packages, that is not visible to other users. - -3. They want to isolate a set of packages to a specific python application, usually to minimize - the possibility of version conflicts. - -Historically, there have been many approaches to achieve custom installation. -The following section lists only the easiest and most relevant approaches [1]_. - -`Use the "--user" option`_ - -`Use the "--user" option and customize "PYTHONUSERBASE"`_ - -`Use "virtualenv"`_ - -.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by `PEP-370`_. - -.. _PEP-370: http://www.python.org/dev/peps/pep-0370/ - - -Use the "--user" option -~~~~~~~~~~~~~~~~~~~~~~~ -Python provides a User scheme for installation, which means that all -python distributions support an alternative install location that is specific to a user [3]_. -The Default location for each OS is explained in the python documentation -for the ``site.USER_BASE`` variable. This mode of installation can be turned on by -specifying the ``--user`` option to ``setup.py install`` or ``easy_install``. -This approach serves the need to have a user-specific stash of packages. - -.. [3] Prior to the User scheme, there was the Home scheme, which is still available, but requires more effort than the User scheme to get packages recognized. - -Use the "--user" option and customize "PYTHONUSERBASE" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The User scheme install location can be customized by setting the ``PYTHONUSERBASE`` environment -variable, which updates the value of ``site.USER_BASE``. To isolate packages to a specific -application, simply set the OS environment of that application to a specific value of -``PYTHONUSERBASE``, that contains just those packages. - -Use "virtualenv" -~~~~~~~~~~~~~~~~ -"virtualenv" is a 3rd-party python package that effectively "clones" a python installation, thereby -creating an isolated location to install packages. The evolution of "virtualenv" started before the existence -of the User installation scheme. "virtualenv" provides a version of ``easy_install`` that is -scoped to the cloned python install and is used in the normal way. "virtualenv" does offer various features -that the User installation scheme alone does not provide, e.g. the ability to hide the main python site-packages. - -Please refer to the `virtualenv`_ documentation for more details. - -.. _virtualenv: https://pypi.org/project/virtualenv/ - - - -Package Index "API" -------------------- - -Custom package indexes (and PyPI) must follow the following rules for -EasyInstall to be able to look up and download packages: - -1. Except where stated otherwise, "pages" are HTML or XHTML, and "links" - refer to ``href`` attributes. - -2. Individual project version pages' URLs must be of the form - ``base/projectname/version``, where ``base`` is the package index's base URL. - -3. Omitting the ``/version`` part of a project page's URL (but keeping the - trailing ``/``) should result in a page that is either: - - a) The single active version of that project, as though the version had been - explicitly included, OR - - b) A page with links to all of the active version pages for that project. - -4. Individual project version pages should contain direct links to downloadable - distributions where possible. It is explicitly permitted for a project's - "long_description" to include URLs, and these should be formatted as HTML - links by the package index, as EasyInstall does no special processing to - identify what parts of a page are index-specific and which are part of the - project's supplied description. - -5. Where available, MD5 information should be added to download URLs by - appending a fragment identifier of the form ``#md5=...``, where ``...`` is - the 32-character hex MD5 digest. EasyInstall will verify that the - downloaded file's MD5 digest matches the given value. - -6. Individual project version pages should identify any "homepage" or - "download" URLs using ``rel="homepage"`` and ``rel="download"`` attributes - on the HTML elements linking to those URLs. Use of these attributes will - cause EasyInstall to always follow the provided links, unless it can be - determined by inspection that they are downloadable distributions. If the - links are not to downloadable distributions, they are retrieved, and if they - are HTML, they are scanned for download links. They are *not* scanned for - additional "homepage" or "download" links, as these are only processed for - pages that are part of a package index site. - -7. The root URL of the index, if retrieved with a trailing ``/``, must result - in a page containing links to *all* projects' active version pages. - - (Note: This requirement is a workaround for the absence of case-insensitive - ``safe_name()`` matching of project names in URL paths. If project names are - matched in this fashion (e.g. via the PyPI server, mod_rewrite, or a similar - mechanism), then it is not necessary to include this all-packages listing - page.) - -8. If a package index is accessed via a ``file://`` URL, then EasyInstall will - automatically use ``index.html`` files, if present, when trying to read a - directory with a trailing ``/`` on the URL. diff --git a/docs/index.txt b/docs/index.txt index 13a46e74..c251260d 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -21,5 +21,4 @@ Documentation content: python3 development roadmap - Deprecated: Easy Install <easy_install> history diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 22025f61..0dda5622 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -282,10 +282,11 @@ unless you need the associated ``setuptools`` feature. ``setup_requires`` A string or list of strings specifying what other distributions need to be present in order for the *setup script* to run. ``setuptools`` will - attempt to obtain these before processing the rest of the setup script or - commands. This argument is needed if you are using distutils extensions as - part of your build process; for example, extensions that process setup() - arguments and turn them into EGG-INFO metadata files. + attempt to obtain these (using pip if available) before processing the + rest of the setup script or commands. This argument is needed if you + are using distutils extensions as part of your build process; for + example, extensions that process setup() arguments and turn them into + EGG-INFO metadata files. (Note: projects listed in ``setup_requires`` will NOT be automatically installed on the system where the setup script is being run. They are @@ -332,10 +333,10 @@ unless you need the associated ``setuptools`` feature. needed to install it, you can use this option to specify them. It should be a string or list of strings specifying what other distributions need to be present for the package's tests to run. When you run the ``test`` - command, ``setuptools`` will attempt to obtain these. Note that these - required projects will *not* be installed on the system where the tests - are run, but only downloaded to the project's setup directory if they're - not already installed locally. + command, ``setuptools`` will attempt to obtain these (using pip if + available). Note that these required projects will *not* be installed on + the system where the tests are run, but only downloaded to the project's setup + directory if they're not already installed locally. New in 41.5.0: Deprecated the test command. @@ -1207,7 +1208,7 @@ the quoted part. Distributing a ``setuptools``-based project =========================================== -Detailed instructions to distribute a setuptools project can be found at +Detailed instructions to distribute a setuptools project can be found at `Packaging project tutorials`_. .. _Packaging project tutorials: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives @@ -1223,7 +1224,7 @@ setup.py is located:: This will generate distribution archives in the `dist` directory. -Before you upload the generated archives make sure you're registered on +Before you upload the generated archives make sure you're registered on https://test.pypi.org/account/register/. You will also need to verify your email to be able to upload any packages. You should install twine to be able to upload packages:: @@ -2087,16 +2088,13 @@ New in 41.5.0: Deprecated the test command. ``upload`` - Upload source and/or egg distributions to PyPI =========================================================== -.. warning:: - **upload** is deprecated in favor of using `twine - <https://pypi.org/p/twine>`_ - -The ``upload`` command is implemented and `documented -<https://docs.python.org/3.1/distutils/uploading.html>`_ -in distutils. +The ``upload`` command was deprecated in version 40.0 and removed in version +42.0. Use `twine <https://pypi.org/p/twine>`_ instead. -New in 20.1: Added keyring support. -New in 40.0: Deprecated the upload command. +For more information on the current best practices in uploading your packages +to PyPI, see the Python Packaging User Guide's "Packaging Python Projects" +tutorial specifically the section on `uploading the distribution archives +<https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives>`_. ----------------------------------------- diff --git a/easy_install.py b/easy_install.py deleted file mode 100644 index d87e9840..00000000 --- a/easy_install.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Run the EasyInstall command""" - -if __name__ == '__main__': - from setuptools.command.easy_install import main - main() diff --git a/pkg_resources/tests/data/my-test-package-source/setup.cfg b/pkg_resources/tests/data/my-test-package-source/setup.cfg new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pkg_resources/tests/data/my-test-package-source/setup.cfg diff --git a/pkg_resources/tests/data/my-test-package-source/setup.py b/pkg_resources/tests/data/my-test-package-source/setup.py new file mode 100644 index 00000000..fe80d28f --- /dev/null +++ b/pkg_resources/tests/data/my-test-package-source/setup.py @@ -0,0 +1,6 @@ +import setuptools +setuptools.setup( + name="my-test-package", + version="1.0", + zip_safe=True, +) diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO new file mode 100644 index 00000000..7328e3f7 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,10 @@ +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 diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt new file mode 100644 index 00000000..3c4ee167 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt @@ -0,0 +1,7 @@ +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 diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt @@ -0,0 +1 @@ + diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe @@ -0,0 +1 @@ + diff --git a/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg b/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg Binary files differnew file mode 100644 index 00000000..5115b895 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg diff --git a/pkg_resources/tests/test_find_distributions.py b/pkg_resources/tests/test_find_distributions.py index d735c590..f9594422 100644 --- a/pkg_resources/tests/test_find_distributions.py +++ b/pkg_resources/tests/test_find_distributions.py @@ -1,17 +1,9 @@ -import subprocess -import sys - +import py import pytest import pkg_resources -SETUP_TEMPLATE = """ -import setuptools -setuptools.setup( - name="my-test-package", - version="1.0", - zip_safe=True, -) -""".lstrip() + +TESTS_DATA_DIR = py.path.local(__file__).dirpath('data') class TestFindDistributions: @@ -21,46 +13,22 @@ class TestFindDistributions: target_dir = tmpdir.mkdir('target') # place a .egg named directory in the target that is not an egg: target_dir.mkdir('not.an.egg') - return str(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) + return 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) - def test_standalone_egg_directory(self, project_dir, target_dir): - # install this distro as an unpacked egg: - args = [ - 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) + def test_standalone_egg_directory(self, target_dir): + (TESTS_DATA_DIR / 'my-test-package_unpacked-egg').copy(target_dir) + dists = pkg_resources.find_distributions(str(target_dir)) 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) - def test_zipped_egg(self, project_dir, target_dir): - # install this distro as an unpacked egg: - args = [ - 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) + def test_zipped_egg(self, target_dir): + (TESTS_DATA_DIR / 'my-test-package_zipped-egg').copy(target_dir) + dists = pkg_resources.find_distributions(str(target_dir)) 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) @@ -1,7 +1,10 @@ [pytest] addopts=--doctest-modules --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 .* flake8-ignore = setuptools/site-patch.py F821 setuptools/py*compat.py F811 doctest_optionflags=ELLIPSIS ALLOW_UNICODE +filterwarnings = + # https://github.com/pypa/setuptools/issues/1823 + ignore:bdist_wininst command is deprecated @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.5.1 +version = 41.6.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org @@ -51,7 +51,6 @@ classifiers = [options] zip_safe = True python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* -py_modules = easy_install packages = find: [options.packages.find] @@ -31,22 +31,6 @@ def read_commands(): return command_ns['__all__'] -def _gen_console_scripts(): - yield "easy_install = setuptools.command.easy_install:main" - - # Gentoo distributions manage the python-version-specific scripts - # themselves, so those platforms define an environment variable to - # suppress the creation of the version-specific scripts. - var_names = ( - 'SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT', - 'DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT', - ) - if any(os.environ.get(var) not in (None, "", "0") for var in var_names): - return - tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main" - yield tmpl.format(shortver='{}.{}'.format(*sys.version_info)) - - package_data = dict( setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], ) @@ -125,9 +109,6 @@ setup_params = dict( "depends.txt = setuptools.command.egg_info:warn_depends_obsolete", "dependency_links.txt = setuptools.command.egg_info:overwrite_arg", ], - "console_scripts": list(_gen_console_scripts()), - "setuptools.installation": - ['eggsecutable = setuptools.command.easy_install:bootstrap'], }, dependency_links=[ pypi_link( diff --git a/setuptools/_imp.py b/setuptools/_imp.py new file mode 100644 index 00000000..a3cce9b2 --- /dev/null +++ b/setuptools/_imp.py @@ -0,0 +1,73 @@ +""" +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_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + spec = importlib.util.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 = importlib.util.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 = importlib.util.find_spec(module, paths) + if not spec: + raise ImportError("Can't find %s" % module) + return module_from_spec(spec) diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index fe619e2e..743f5588 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -2,8 +2,7 @@ __all__ = [ 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', - 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', - 'dist_info', + 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info', ] from distutils.command.bdist import bdist diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 545c3c44..d273bc10 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -73,7 +73,7 @@ warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) __all__ = [ 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', - 'main', 'get_exe_prefixes', + 'get_exe_prefixes', ] @@ -410,7 +410,13 @@ class easy_install(Command): ] self._expand_attrs(dirs) - def run(self): + def run(self, show_deprecation=True): + if show_deprecation: + self.announce( + "WARNING: The easy_install command is deprecated " + "and will be removed in a future version." + , log.WARN, + ) if self.verbose != self.distribution.verbose: log.set_verbosity(self.verbose) try: @@ -2283,59 +2289,6 @@ def current_umask(): return tmp -def bootstrap(): - # This function is called when setuptools*.egg is run using /bin/sh - import setuptools - - argv0 = os.path.dirname(setuptools.__path__[0]) - sys.argv[0] = argv0 - sys.argv.append(argv0) - main() - - -def main(argv=None, **kw): - from setuptools import setup - from setuptools.dist import Distribution - - class DistributionWithoutHelpCommands(Distribution): - common_usage = "" - - def _show_help(self, *args, **kw): - with _patch_usage(): - Distribution._show_help(self, *args, **kw) - - if argv is None: - argv = sys.argv[1:] - - with _patch_usage(): - setup( - script_args=['-q', 'easy_install', '-v'] + argv, - script_name=sys.argv[0] or 'easy_install', - distclass=DistributionWithoutHelpCommands, - **kw - ) - - -@contextlib.contextmanager -def _patch_usage(): - import distutils.core - USAGE = textwrap.dedent(""" - usage: %(script)s [options] requirement_or_url ... - or: %(script)s --help - """).lstrip() - - def gen_usage(script_name): - return USAGE % dict( - script=os.path.basename(script_name), - ) - - saved = distutils.core.gen_usage - distutils.core.gen_usage = gen_usage - try: - yield - finally: - distutils.core.gen_usage = saved - class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/setuptools/command/install.py b/setuptools/command/install.py index 31a5ddb5..72b9a3e4 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -114,7 +114,7 @@ class install(orig.install): args.insert(0, setuptools.bootstrap_install_from) cmd.args = args - cmd.run() + cmd.run(show_deprecation=False) setuptools.bootstrap_install_from = None diff --git a/setuptools/command/register.py b/setuptools/command/register.py index 98bc0156..b8266b9a 100644 --- a/setuptools/command/register.py +++ b/setuptools/command/register.py @@ -1,18 +1,18 @@ from distutils import log import distutils.command.register as orig +from setuptools.errors import RemovedCommandError + class register(orig.register): - __doc__ = orig.register.__doc__ + """Formerly used to register packages on PyPI.""" def run(self): - try: - # Make sure that we are using valid current name/version info - self.run_command('egg_info') - orig.register.run(self) - finally: - self.announce( - "WARNING: Registering is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) + msg = ( + "The register command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" + ) + + self.announce("ERROR: " + msg, log.ERROR) + + raise RemovedCommandError(msg) diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 6db8888b..ec7f81e2 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -1,196 +1,17 @@ -import io -import os -import hashlib -import getpass - -from base64 import standard_b64encode - from distutils import log from distutils.command import upload as orig -from distutils.spawn import spawn - -from distutils.errors import DistutilsError -from setuptools.extern.six.moves.urllib.request import urlopen, Request -from setuptools.extern.six.moves.urllib.error import HTTPError -from setuptools.extern.six.moves.urllib.parse import urlparse +from setuptools.errors import RemovedCommandError class upload(orig.upload): - """ - Override default upload behavior to obtain password - in a variety of different ways. - """ - def run(self): - try: - orig.upload.run(self) - finally: - self.announce( - "WARNING: Uploading via this command is deprecated, use twine " - "to upload instead (https://pypi.org/p/twine/)", - log.WARN - ) + """Formerly used to upload packages to PyPI.""" - def finalize_options(self): - orig.upload.finalize_options(self) - self.username = ( - self.username or - getpass.getuser() - ) - # Attempt to obtain password. Short circuit evaluation at the first - # sign of success. - self.password = ( - self.password or - self._load_password_from_keyring() or - self._prompt_for_password() + def run(self): + msg = ( + "The upload command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" ) - def upload_file(self, command, pyversion, filename): - # Makes sure the repository URL is compliant - schema, netloc, url, params, query, fragments = \ - urlparse(self.repository) - if params or query or fragments: - raise AssertionError("Incompatible url %s" % self.repository) - - if schema not in ('http', 'https'): - raise AssertionError("unsupported schema " + schema) - - # Sign if requested - if self.sign: - gpg_args = ["gpg", "--detach-sign", "-a", filename] - if self.identity: - gpg_args[2:2] = ["--local-user", self.identity] - spawn(gpg_args, - dry_run=self.dry_run) - - # Fill in the data - send all the meta-data in case we need to - # register a new release - with open(filename, 'rb') as f: - content = f.read() - - meta = self.distribution.metadata - - data = { - # action - ':action': 'file_upload', - 'protocol_version': '1', - - # identify release - 'name': meta.get_name(), - 'version': meta.get_version(), - - # file content - 'content': (os.path.basename(filename), content), - 'filetype': command, - 'pyversion': pyversion, - 'md5_digest': hashlib.md5(content).hexdigest(), - - # additional meta-data - 'metadata_version': str(meta.get_metadata_version()), - 'summary': meta.get_description(), - 'home_page': meta.get_url(), - 'author': meta.get_contact(), - 'author_email': meta.get_contact_email(), - 'license': meta.get_licence(), - 'description': meta.get_long_description(), - 'keywords': meta.get_keywords(), - 'platform': meta.get_platforms(), - 'classifiers': meta.get_classifiers(), - 'download_url': meta.get_download_url(), - # PEP 314 - 'provides': meta.get_provides(), - 'requires': meta.get_requires(), - 'obsoletes': meta.get_obsoletes(), - } - - data['comment'] = '' - - if self.sign: - data['gpg_signature'] = (os.path.basename(filename) + ".asc", - open(filename+".asc", "rb").read()) - - # set up the authentication - user_pass = (self.username + ":" + self.password).encode('ascii') - # The exact encoding of the authentication string is debated. - # Anyway PyPI only accepts ascii for both username or password. - auth = "Basic " + standard_b64encode(user_pass).decode('ascii') - - # Build up the MIME payload for the POST data - boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\r\n--' + boundary.encode('ascii') - end_boundary = sep_boundary + b'--\r\n' - body = io.BytesIO() - for key, value in data.items(): - title = '\r\nContent-Disposition: form-data; name="%s"' % key - # handle multiple entries for the same name - if not isinstance(value, list): - value = [value] - for value in value: - if type(value) is tuple: - title += '; filename="%s"' % value[0] - value = value[1] - else: - value = str(value).encode('utf-8') - body.write(sep_boundary) - body.write(title.encode('utf-8')) - body.write(b"\r\n\r\n") - body.write(value) - body.write(end_boundary) - body = body.getvalue() - - msg = "Submitting %s to %s" % (filename, self.repository) - self.announce(msg, log.INFO) - - # build the Request - headers = { - 'Content-type': 'multipart/form-data; boundary=%s' % boundary, - 'Content-length': str(len(body)), - 'Authorization': auth, - } - - request = Request(self.repository, data=body, - headers=headers) - # send the data - try: - result = urlopen(request) - status = result.getcode() - reason = result.msg - except HTTPError as e: - status = e.code - reason = e.msg - except OSError as e: - self.announce(str(e), log.ERROR) - raise - - if status == 200: - self.announce('Server response (%s): %s' % (status, reason), - log.INFO) - if self.show_response: - text = getattr(self, '_read_pypi_response', - lambda x: None)(result) - if text is not None: - msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) - else: - msg = 'Upload failed (%s): %s' % (status, reason) - self.announce(msg, log.ERROR) - raise DistutilsError(msg) - - def _load_password_from_keyring(self): - """ - Attempt to load password from keyring. Suppress Exceptions. - """ - try: - keyring = __import__('keyring') - return keyring.get_password(self.repository, self.username) - except Exception: - pass - - def _prompt_for_password(self): - """ - Prompt for a password on the tty. Suppress Exceptions. - """ - try: - return getpass.getpass() - except (Exception, KeyboardInterrupt): - pass + self.announce("ERROR: " + msg, log.ERROR) + raise RemovedCommandError(msg) diff --git a/setuptools/depends.py b/setuptools/depends.py index 45e7052d..a37675cb 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -1,11 +1,13 @@ import sys -import imp import marshal +import contextlib from distutils.version import StrictVersion -from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN from .py33compat import Bytecode +from .py27compat import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE +from . import py27compat + __all__ = [ 'Require', 'find_module', 'get_module_constant', 'extract_constant' @@ -15,7 +17,8 @@ __all__ = [ class Require: """A prerequisite to building or installing a distribution""" - def __init__(self, name, requested_version, module, homepage='', + def __init__( + self, name, requested_version, module, homepage='', attribute=None, format=None): if format is None and requested_version is not None: @@ -79,23 +82,15 @@ class Require: return self.version_ok(version) -def find_module(module, paths=None): - """Just like 'imp.find_module()', but with package support""" - - parts = module.split('.') - - while parts: - part = parts.pop(0) - f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) - - if kind == PKG_DIRECTORY: - parts = parts or ['__init__'] - paths = [path] - - elif parts: - raise ImportError("Can't find %r in %s" % (parts, module)) +def maybe_close(f): + @contextlib.contextmanager + def empty(): + yield + return + if not f: + return empty() - return info + return contextlib.closing(f) def get_module_constant(module, symbol, default=-1, paths=None): @@ -106,28 +101,23 @@ def get_module_constant(module, symbol, default=-1, paths=None): constant. Otherwise, return 'default'.""" try: - f, path, (suffix, mode, kind) = find_module(module, paths) + f, path, (suffix, mode, kind) = info = find_module(module, paths) except ImportError: # Module doesn't exist return None - try: + with maybe_close(f): if kind == PY_COMPILED: f.read(8) # skip magic & date code = marshal.load(f) elif kind == PY_FROZEN: - code = imp.get_frozen_object(module) + code = py27compat.get_frozen_object(module, paths) elif kind == PY_SOURCE: code = compile(f.read(), path, 'exec') else: # Not something we can parse; we'll have to import it. :( - if module not in sys.modules: - imp.load_module(module, f, path, (suffix, mode, kind)) - return getattr(sys.modules[module], symbol, None) - - finally: - if f: - f.close() + imported = py27compat.get_module(module, paths, info) + return getattr(imported, symbol, None) return extract_constant(code, symbol, default) diff --git a/setuptools/dist.py b/setuptools/dist.py index 0f3f7322..f0a30837 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -760,32 +760,8 @@ class Distribution(_Distribution): def fetch_build_egg(self, req): """Fetch an egg needed for building""" - from setuptools.command.easy_install import easy_install - dist = self.__class__({'script_args': ['easy_install']}) - opts = dist.get_option_dict('easy_install') - opts.clear() - opts.update( - (k, v) - for k, v in self.get_option_dict('easy_install').items() - if k in ( - # don't use any other settings - 'find_links', 'site_dirs', 'index_url', - 'optimize', 'site_dirs', 'allow_hosts', - )) - if self.dependency_links: - links = self.dependency_links[:] - if 'find_links' in opts: - links = opts['find_links'][1] + links - opts['find_links'] = ('setup', links) - install_dir = self.get_egg_cache_dir() - cmd = easy_install( - dist, args=["x"], install_dir=install_dir, - exclude_scripts=True, - always_copy=False, build_directory=None, editable=False, - upgrade=False, multi_version=True, no_report=True, user=False - ) - cmd.ensure_finalized() - return cmd.easy_install(req) + from setuptools.installer import fetch_build_egg + return fetch_build_egg(self, req) def _set_global_opts_from_features(self): """Add --with-X/--without-X options based on optional features""" diff --git a/setuptools/errors.py b/setuptools/errors.py new file mode 100644 index 00000000..2701747f --- /dev/null +++ b/setuptools/errors.py @@ -0,0 +1,16 @@ +"""setuptools.errors + +Provides exceptions used by setuptools modules. +""" + +from distutils.errors import DistutilsError + + +class RemovedCommandError(DistutilsError, RuntimeError): + """Error used for commands that have been removed in setuptools. + + Since ``setuptools`` is built on ``distutils``, simply removing a command + from ``setuptools`` will make the behavior fall back to ``distutils``; this + error is raised if a command exists in ``distutils`` but has been actively + removed in ``setuptools``. + """ diff --git a/setuptools/installer.py b/setuptools/installer.py new file mode 100644 index 00000000..35bc3cc5 --- /dev/null +++ b/setuptools/installer.py @@ -0,0 +1,129 @@ +import glob +import os +import subprocess +import sys +from distutils import log +from distutils.errors import DistutilsError + +import pkg_resources +from setuptools.command.easy_install import easy_install +from setuptools.wheel import Wheel + +from .py31compat import TemporaryDirectory + + +def _legacy_fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Legacy path using EasyInstall. + """ + tmp_dist = dist.__class__({'script_args': ['easy_install']}) + opts = tmp_dist.get_option_dict('easy_install') + opts.clear() + opts.update( + (k, v) + for k, v in dist.get_option_dict('easy_install').items() + if k in ( + # don't use any other settings + 'find_links', 'site_dirs', 'index_url', + 'optimize', 'site_dirs', 'allow_hosts', + )) + if dist.dependency_links: + links = dist.dependency_links[:] + if 'find_links' in opts: + links = opts['find_links'][1] + links + opts['find_links'] = ('setup', links) + install_dir = dist.get_egg_cache_dir() + cmd = easy_install( + tmp_dist, args=["x"], install_dir=install_dir, + exclude_scripts=True, + always_copy=False, build_directory=None, editable=False, + upgrade=False, multi_version=True, no_report=True, user=False + ) + cmd.ensure_finalized() + return cmd.easy_install(req) + + +def fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Use pip/wheel to fetch/build a wheel.""" + # Check pip is available. + try: + pkg_resources.get_distribution('pip') + except pkg_resources.DistributionNotFound: + dist.announce( + 'WARNING: The pip package is not available, falling back ' + 'to EasyInstall for handling setup_requires/test_requires; ' + 'this is deprecated and will be removed in a future version.' + , log.WARN + ) + return _legacy_fetch_build_egg(dist, req) + # Warn if wheel is not. + try: + pkg_resources.get_distribution('wheel') + except pkg_resources.DistributionNotFound: + dist.announce('WARNING: The wheel package is not available.', log.WARN) + if not isinstance(req, pkg_resources.Requirement): + req = pkg_resources.Requirement.parse(req) + # Take easy_install options into account, but do not override relevant + # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll + # take precedence. + opts = dist.get_option_dict('easy_install') + if 'allow_hosts' in opts: + raise DistutilsError('the `allow-hosts` option is not supported ' + 'when using pip to install requirements.') + if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ: + quiet = False + else: + quiet = True + if 'PIP_INDEX_URL' in os.environ: + index_url = None + elif 'index_url' in opts: + index_url = opts['index_url'][1] + else: + index_url = None + if 'find_links' in opts: + find_links = opts['find_links'][1][:] + else: + find_links = [] + if dist.dependency_links: + find_links.extend(dist.dependency_links) + eggs_dir = os.path.realpath(dist.get_egg_cache_dir()) + environment = pkg_resources.Environment() + for egg_dist in pkg_resources.find_distributions(eggs_dir): + if egg_dist in req and environment.can_add(egg_dist): + return egg_dist + with TemporaryDirectory() as tmpdir: + cmd = [ + sys.executable, '-m', 'pip', + '--disable-pip-version-check', + 'wheel', '--no-deps', + '-w', tmpdir, + ] + if quiet: + cmd.append('--quiet') + if index_url is not None: + cmd.extend(('--index-url', index_url)) + if find_links is not None: + for link in find_links: + cmd.extend(('--find-links', link)) + # If requirement is a PEP 508 direct URL, directly pass + # the URL to pip, as `req @ url` does not work on the + # command line. + if req.url: + cmd.append(req.url) + else: + cmd.append(str(req)) + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + raise DistutilsError(str(e)) + wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0]) + dist_location = os.path.join(eggs_dir, wheel.egg_name()) + wheel.install_as_egg(dist_location) + dist_metadata = pkg_resources.PathMetadata( + dist_location, os.path.join(dist_location, 'EGG-INFO')) + dist = pkg_resources.Distribution.from_filename( + dist_location, metadata=dist_metadata) + return dist diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py index 2985011b..1d57360f 100644 --- a/setuptools/py27compat.py +++ b/setuptools/py27compat.py @@ -2,6 +2,7 @@ Compatibility Support for Python 2.7 and earlier """ +import sys import platform from setuptools.extern import six @@ -26,3 +27,34 @@ linux_py2_ascii = ( rmtree_safe = str if linux_py2_ascii else lambda x: x """Workaround for http://bugs.python.org/issue24672""" + + +try: + from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE + from ._imp import get_frozen_object, get_module +except ImportError: + import imp + from imp import PY_COMPILED, PY_FROZEN, PY_SOURCE # noqa + + def find_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + parts = module.split('.') + while parts: + part = parts.pop(0) + f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) + + if kind == imp.PKG_DIRECTORY: + parts = parts or ['__init__'] + paths = [path] + + elif parts: + raise ImportError("Can't find %r in %s" % (parts, module)) + + return info + + def get_frozen_object(module, paths): + return imp.get_frozen_object(module) + + def get_module(module, paths, info): + imp.load_module(module, *info) + return sys.modules[module] diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py new file mode 100644 index 00000000..3ad91722 --- /dev/null +++ b/setuptools/py34compat.py @@ -0,0 +1,13 @@ +import importlib + +try: + import importlib.util +except ImportError: + pass + + +try: + module_from_spec = importlib.util.module_from_spec +except AttributeError: + def module_from_spec(spec): + return spec.loader.load_module(spec.name) diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index fc3a5975..8b17b081 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -1,10 +1,13 @@ """Basic http server for tests to simulate PyPI or custom indexes """ +import os import time import threading from setuptools.extern.six.moves import BaseHTTPServer, SimpleHTTPServer +from setuptools.extern.six.moves.urllib_parse import urljoin +from setuptools.extern.six.moves.urllib.request import pathname2url class IndexServer(BaseHTTPServer.HTTPServer): @@ -70,5 +73,19 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread): self.serve_forever() @property + def netloc(self): + return 'localhost:%s' % self.server_port + + @property def url(self): - return 'http://localhost:%(server_port)s/' % vars(self) + return 'http://%s/' % self.netloc + + +def path_to_url(path, authority=None): + """ Convert a path to a file: URL. """ + path = os.path.normpath(os.path.abspath(path)) + base = 'file:' + if authority is not None: + base += '//' + authority + url = urljoin(base, pathname2url(path)) + return url diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index c3fd1c6e..68319c2f 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -15,24 +15,24 @@ import distutils.errors import io import zipfile import mock -from setuptools.command.easy_install import ( - EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter, -) import time + from setuptools.extern import six -from setuptools.extern.six.moves import urllib import pytest from setuptools import sandbox from setuptools.sandbox import run_setup import setuptools.command.easy_install as ei -from setuptools.command.easy_install import PthDistributions +from setuptools.command.easy_install import ( + EasyInstallDeprecationWarning, ScriptWriter, PthDistributions, + WindowsScriptWriter, +) from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution from pkg_resources import normalize_path, working_set from pkg_resources import Distribution as PRDistribution -import setuptools.tests.server +from setuptools.tests.server import MockServer, path_to_url from setuptools.tests import fail_on_ascii import pkg_resources @@ -440,48 +440,53 @@ def distutils_package(): yield +@pytest.fixture +def mock_index(): + # set up a server which will simulate an alternate package index. + p_index = MockServer() + if p_index.server_port == 0: + # Some platforms (Jython) don't find a port to which to bind, + # so skip test for them. + pytest.skip("could not find a valid port") + p_index.start() + return p_index + + class TestDistutilsPackage: def test_bdist_egg_available_on_distutils_pkg(self, distutils_package): run_setup('setup.py', ['bdist_egg']) class TestSetupRequires: - def test_setup_requires_honors_fetch_params(self): + + def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch): """ When easy_install installs a source distribution which specifies setup_requires, it should honor the fetch parameters (such as - allow-hosts, index-url, and find-links). + index-url, and find-links). """ - # set up a server which will simulate an alternate package index. - p_index = setuptools.tests.server.MockServer() - p_index.start() - netloc = 1 - p_index_loc = urllib.parse.urlparse(p_index.url)[netloc] - if p_index_loc.endswith(':0'): - # Some platforms (Jython) don't find a port to which to bind, - # so skip this test for them. - return - with contexts.quiet(): - # create an sdist that has a build-time dependency. - with TestSetupRequires.create_sdist() as dist_file: - with contexts.tempdir() as temp_install_dir: - with contexts.environment(PYTHONPATH=temp_install_dir): - ei_params = [ - '--index-url', p_index.url, - '--allow-hosts', p_index_loc, - '--exclude-scripts', - '--install-dir', temp_install_dir, - dist_file, - ] - with sandbox.save_argv(['easy_install']): - # attempt to install the dist. It should - # fail because it doesn't exist. - with pytest.raises(SystemExit): - easy_install_pkg.main(ei_params) - # there should have been two or three requests to the server - # (three happens on Python 3.3a) - assert 2 <= len(p_index.requests) <= 3 - assert p_index.requests[0].path == '/does-not-exist/' + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_VERBOSE'), str('1')) + # create an sdist that has a build-time dependency. + with TestSetupRequires.create_sdist() as dist_file: + with contexts.tempdir() as temp_dir: + setup_py = os.path.join(temp_dir, 'setup.py') + with open(setup_py, 'w') as fp: + fp.write('__import__("setuptools").setup()') + temp_install_dir = os.path.join(temp_dir, 'target') + os.mkdir(temp_install_dir) + with contexts.environment(PYTHONPATH=temp_install_dir): + # attempt to install the dist. It should + # fail because it doesn't exist. + with pytest.raises(SystemExit): + run_setup(setup_py, ['easy_install', + '--exclude-scripts', + '--index-url', mock_index.url, + '--install-dir', temp_install_dir, + dist_file]) + # there should have been one requests to the server + assert [r.path for r in mock_index.requests] == ['/does-not-exist/'] @staticmethod @contextlib.contextmanager @@ -500,7 +505,9 @@ class TestSetupRequires: version="1.0", setup_requires = ['does-not-exist'], ) - """))]) + """)), + ('setup.cfg', ''), + ]) yield dist_path use_setup_cfg = ( @@ -632,6 +639,113 @@ class TestSetupRequires: assert len(lines) > 0 assert lines[-1].strip() == '42' + def test_setup_requires_honors_pip_env(self, mock_index, monkeypatch): + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url) + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + test_pkg = create_setup_requires_package( + temp_dir, 'python-xlib', '0.19', + setup_attrs=dict(dependency_links=[])) + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + with open(test_setup_cfg, 'w') as fp: + fp.write(DALS( + ''' + [easy_install] + index_url = https://pypi.org/legacy/ + ''')) + test_setup_py = os.path.join(test_pkg, 'setup.py') + with pytest.raises(distutils.errors.DistutilsError): + run_setup(test_setup_py, [str('--version')]) + assert len(mock_index.requests) == 1 + assert mock_index.requests[0].path == '/python-xlib/' + + def test_setup_requires_with_pep508_url(self, mock_index, monkeypatch): + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url) + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + dep_sdist = os.path.join(temp_dir, 'dep.tar.gz') + make_trivial_sdist(dep_sdist, 'dependency', '42') + dep_url = path_to_url(dep_sdist, authority='localhost') + test_pkg = create_setup_requires_package( + temp_dir, + 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). + setup_attrs=dict(setup_requires='dependency @ %s' % dep_url)) + test_setup_py = os.path.join(test_pkg, 'setup.py') + run_setup(test_setup_py, [str('--version')]) + assert len(mock_index.requests) == 0 + + def test_setup_requires_with_allow_hosts(self, mock_index): + ''' The `allow-hosts` option in not supported anymore. ''' + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + test_pkg = os.path.join(temp_dir, 'test_pkg') + test_setup_py = os.path.join(test_pkg, 'setup.py') + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + os.mkdir(test_pkg) + with open(test_setup_py, 'w') as fp: + fp.write(DALS( + ''' + from setuptools import setup + setup(setup_requires='python-xlib') + ''')) + with open(test_setup_cfg, 'w') as fp: + fp.write(DALS( + ''' + [easy_install] + allow_hosts = * + ''')) + with pytest.raises(distutils.errors.DistutilsError): + run_setup(test_setup_py, [str('--version')]) + assert len(mock_index.requests) == 0 + + def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir): + ''' Check `python_requires` is honored. ''' + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) + monkeypatch.setenv(str('PIP_VERBOSE'), str('1')) + dep_1_0_sdist = 'dep-1.0.tar.gz' + dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist)) + dep_1_0_python_requires = '>=2.7' + make_python_requires_sdist(str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires) + dep_2_0_sdist = 'dep-2.0.tar.gz' + dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist)) + dep_2_0_python_requires = '!=' + '.'.join(map(str, sys.version_info[:2])) + '.*' + make_python_requires_sdist(str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires) + index = tmpdir / 'index.html' + index.write_text(DALS( + ''' + <!DOCTYPE html> + <html><head><title>Links for dep</title></head> + <body> + <h1>Links for dep</h1> + <a href="{dep_1_0_url}" data-requires-python="{dep_1_0_python_requires}">{dep_1_0_sdist}</a><br/> + <a href="{dep_2_0_url}" data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/> + </body> + </html> + ''').format( + dep_1_0_url=dep_1_0_url, + dep_1_0_sdist=dep_1_0_sdist, + dep_1_0_python_requires=dep_1_0_python_requires, + dep_2_0_url=dep_2_0_url, + dep_2_0_sdist=dep_2_0_sdist, + dep_2_0_python_requires=dep_2_0_python_requires, + ), 'utf-8') + index_url = path_to_url(str(index)) + with contexts.save_pkg_resources_state(): + test_pkg = create_setup_requires_package( + str(tmpdir), + 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). + setup_attrs=dict(setup_requires='dep', dependency_links=[index_url])) + test_setup_py = os.path.join(test_pkg, 'setup.py') + run_setup(test_setup_py, [str('--version')]) + eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))) + assert eggs == ['dep 1.0'] + def make_trivial_sdist(dist_path, distname, version): """ @@ -647,7 +761,9 @@ def make_trivial_sdist(dist_path, distname, version): name=%r, version=%r ) - """ % (distname, version)))]) + """ % (distname, version))), + ('setup.cfg', ''), + ]) def make_nspkg_sdist(dist_path, distname, version): @@ -683,12 +799,29 @@ def make_nspkg_sdist(dist_path, distname, version): make_sdist(dist_path, files) +def make_python_requires_sdist(dist_path, distname, version, python_requires): + make_sdist(dist_path, [ + ('setup.py', DALS("""\ + import setuptools + setuptools.setup( + name={name!r}, + version={version!r}, + python_requires={python_requires!r}, + ) + """).format(name=distname, version=version, + python_requires=python_requires)), + ('setup.cfg', ''), + ]) + + def make_sdist(dist_path, files): """ Create a simple sdist tarball at dist_path, containing the files listed in ``files`` as ``(filename, content)`` tuples. """ + # Distributions with only one file don't play well with pip. + assert len(files) > 1 with tarfile.open(dist_path, 'w:gz') as dist: for filename, content in files: file_bytes = io.BytesIO(content.encode('utf-8')) @@ -721,8 +854,8 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', test_pkg = os.path.join(path, 'test_pkg') os.mkdir(test_pkg) + # setup.cfg if use_setup_cfg: - test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') options = [] metadata = [] for name in use_setup_cfg: @@ -734,8 +867,7 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', if isinstance(value, (tuple, list)): value = ';'.join(value) section.append('%s: %s' % (name, value)) - with open(test_setup_cfg, 'w') as f: - f.write(DALS( + test_setup_cfg_contents = DALS( """ [metadata] {metadata} @@ -745,16 +877,19 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', ).format( options='\n'.join(options), metadata='\n'.join(metadata), - )) - - test_setup_py = os.path.join(test_pkg, 'setup.py') + ) + else: + test_setup_cfg_contents = '' + with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f: + f.write(test_setup_cfg_contents) + # setup.py if setup_py_template is None: setup_py_template = DALS("""\ import setuptools setuptools.setup(**%r) """) - with open(test_setup_py, 'w') as f: + with open(os.path.join(test_pkg, 'setup.py'), 'w') as f: f.write(setup_py_template % test_setup_attrs) foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version)) diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 1c0b2b18..f1a27f8b 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -64,7 +64,7 @@ def install_context(request, tmpdir, monkeypatch): monkeypatch.setattr('site.USER_BASE', user_base.strpath) monkeypatch.setattr('site.USER_SITE', user_site.strpath) monkeypatch.setattr('sys.path', sys.path + [install_dir.strpath]) - monkeypatch.setenv('PYTHONPATH', os.path.pathsep.join(sys.path)) + monkeypatch.setenv(str('PYTHONPATH'), str(os.path.pathsep.join(sys.path))) # Set up the command for performing the installation. dist = Distribution() diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index f937d981..3c5df68a 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -64,9 +64,8 @@ class TestNamespaces: target.mkdir() install_cmd = [ sys.executable, - '-m', 'easy_install', - '-d', str(target), - str(pkg), + '-m', 'pip.__main__', 'install', + '-t', str(target), str(pkg), ] with test.test.paths_on_pythonpath([str(target)]): subprocess.check_call(install_cmd) diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py index 96114595..98605806 100644 --- a/setuptools/tests/test_register.py +++ b/setuptools/tests/test_register.py @@ -1,43 +1,22 @@ -import mock -from distutils import log - -import pytest - from setuptools.command.register import register from setuptools.dist import Distribution +from setuptools.errors import RemovedCommandError +try: + from unittest import mock +except ImportError: + import mock -class TestRegisterTest: - def test_warns_deprecation(self): - dist = Distribution() - - cmd = register(dist) - cmd.run_command = mock.Mock() - cmd.send_metadata = mock.Mock() - cmd.announce = mock.Mock() - - cmd.run() +import pytest - cmd.announce.assert_called_with( - "WARNING: Registering is deprecated, use twine to upload instead " - "(https://pypi.org/p/twine/)", - log.WARN - ) - def test_warns_deprecation_when_raising(self): +class TestRegister: + def test_register_exception(self): + """Ensure that the register command has been properly removed.""" dist = Distribution() + dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] cmd = register(dist) - cmd.run_command = mock.Mock() - cmd.send_metadata = mock.Mock() - cmd.send_metadata.side_effect = Exception - cmd.announce = mock.Mock() - with pytest.raises(Exception): + with pytest.raises(RemovedCommandError): cmd.run() - - cmd.announce.assert_called_with( - "WARNING: Registering is deprecated, use twine to upload instead " - "(https://pypi.org/p/twine/)", - log.WARN - ) diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py index 320c6959..7586cb26 100644 --- a/setuptools/tests/test_upload.py +++ b/setuptools/tests/test_upload.py @@ -1,213 +1,22 @@ -import mock -import os -import re - -from distutils import log -from distutils.errors import DistutilsError - -import pytest - from setuptools.command.upload import upload from setuptools.dist import Distribution -from setuptools.extern import six - - -def _parse_upload_body(body): - boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - entries = [] - name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"') - - for entry in body.split(boundary): - pair = entry.split(u'\r\n\r\n') - if not len(pair) == 2: - continue - - key, value = map(six.text_type.strip, pair) - m = name_re.match(key) - if m is not None: - key = m.group(1) - - entries.append((key, value)) - - return entries - - -@pytest.fixture -def patched_upload(tmpdir): - class Fix: - def __init__(self, cmd, urlopen): - self.cmd = cmd - self.urlopen = urlopen - - def __iter__(self): - return iter((self.cmd, self.urlopen)) - - def get_uploaded_metadata(self): - request = self.urlopen.call_args_list[0][0][0] - body = request.data.decode('utf-8') - entries = dict(_parse_upload_body(body)) - - return entries +from setuptools.errors import RemovedCommandError - class ResponseMock(mock.Mock): - def getheader(self, name, default=None): - """Mocked getheader method for response object""" - return { - 'content-type': 'text/plain; charset=utf-8', - }.get(name.lower(), default) +try: + from unittest import mock +except ImportError: + import mock - with mock.patch('setuptools.command.upload.urlopen') as urlopen: - urlopen.return_value = ResponseMock() - urlopen.return_value.getcode.return_value = 200 - urlopen.return_value.read.return_value = b'' - - content = os.path.join(str(tmpdir), "content_data") - - with open(content, 'w') as f: - f.write("Some content") - - dist = Distribution() - dist.dist_files = [('sdist', '3.7.0', content)] - - cmd = upload(dist) - cmd.announce = mock.Mock() - cmd.username = 'user' - cmd.password = 'hunter2' - - yield Fix(cmd, urlopen) - - -class TestUploadTest: - def test_upload_metadata(self, patched_upload): - cmd, patch = patched_upload - - # Set the metadata version to 2.1 - cmd.distribution.metadata.metadata_version = '2.1' - - # Run the command - cmd.ensure_finalized() - cmd.run() - - # Make sure we did the upload - patch.assert_called_once() - - # Make sure the metadata version is correct in the headers - entries = patched_upload.get_uploaded_metadata() - assert entries['metadata_version'] == '2.1' - - def test_warns_deprecation(self): - dist = Distribution() - dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] - - cmd = upload(dist) - cmd.upload_file = mock.Mock() - cmd.announce = mock.Mock() - - cmd.run() +import pytest - cmd.announce.assert_called_once_with( - "WARNING: Uploading via this command is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) - def test_warns_deprecation_when_raising(self): +class TestUpload: + def test_upload_exception(self): + """Ensure that the register command has been properly removed.""" dist = Distribution() dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] cmd = upload(dist) - cmd.upload_file = mock.Mock() - cmd.upload_file.side_effect = Exception - cmd.announce = mock.Mock() - - with pytest.raises(Exception): - cmd.run() - - cmd.announce.assert_called_once_with( - "WARNING: Uploading via this command is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) - - @pytest.mark.parametrize('url', [ - 'https://example.com/a;parameter', # Has parameters - 'https://example.com/a?query', # Has query - 'https://example.com/a#fragment', # Has fragment - 'ftp://example.com', # Invalid scheme - - ]) - def test_upload_file_invalid_url(self, url, patched_upload): - patched_upload.urlopen.side_effect = Exception("Should not be reached") - - cmd = patched_upload.cmd - cmd.repository = url - - cmd.ensure_finalized() - with pytest.raises(AssertionError): - cmd.run() - - def test_upload_file_http_error(self, patched_upload): - patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError( - 'https://example.com', - 404, - 'File not found', - None, - None - ) - - cmd = patched_upload.cmd - cmd.ensure_finalized() - with pytest.raises(DistutilsError): + with pytest.raises(RemovedCommandError): cmd.run() - - cmd.announce.assert_any_call( - 'Upload failed (404): File not found', - log.ERROR) - - def test_upload_file_os_error(self, patched_upload): - patched_upload.urlopen.side_effect = OSError("Invalid") - - cmd = patched_upload.cmd - cmd.ensure_finalized() - - with pytest.raises(OSError): - cmd.run() - - cmd.announce.assert_any_call('Invalid', log.ERROR) - - @mock.patch('setuptools.command.upload.spawn') - def test_upload_file_gpg(self, spawn, patched_upload): - cmd, urlopen = patched_upload - - cmd.sign = True - cmd.identity = "Alice" - cmd.dry_run = True - content_fname = cmd.distribution.dist_files[0][2] - signed_file = content_fname + '.asc' - - with open(signed_file, 'wb') as f: - f.write("signed-data".encode('utf-8')) - - cmd.ensure_finalized() - cmd.run() - - # Make sure that GPG was called - spawn.assert_called_once_with([ - "gpg", "--detach-sign", "--local-user", "Alice", "-a", - content_fname - ], dry_run=True) - - # Read the 'signed' data that was transmitted - entries = patched_upload.get_uploaded_metadata() - assert entries['gpg_signature'] == 'signed-data' - - def test_show_response_no_error(self, patched_upload): - # This test is just that show_response doesn't throw an error - # It is not really important what the printed response looks like - # in a deprecated command, but we don't want to introduce new - # errors when importing this function from distutils - - patched_upload.cmd.show_response = True - patched_upload.cmd.ensure_finalized() - patched_upload.cmd.run() diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 74a1284c..cd3d9313 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -121,14 +121,12 @@ def test_pip_upgrade_from_source(pip_version, virtualenv): virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist) -def test_test_command_install_requirements(bare_virtualenv, tmpdir): +def _check_test_command_install_requirements(virtualenv, tmpdir): """ Check the test command will install all required dependencies. """ - bare_virtualenv.run(' && '.join(( - 'cd {source}', - 'python setup.py develop', - )).format(source=SOURCE_DIR)) + # Install setuptools. + virtualenv.run('python setup.py develop', cd=SOURCE_DIR) def sdist(distname, version): dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version)) @@ -179,12 +177,20 @@ def test_test_command_install_requirements(bare_virtualenv, tmpdir): open('success', 'w').close() ''')) # Run test command for test package. - bare_virtualenv.run(' && '.join(( + virtualenv.run(' && '.join(( 'cd {tmpdir}', 'python setup.py test -s test', )).format(tmpdir=tmpdir)) assert tmpdir.join('success').check() +def test_test_command_install_requirements(virtualenv, tmpdir): + # Ensure pip/wheel packages are installed. + virtualenv.run("python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"") + _check_test_command_install_requirements(virtualenv, tmpdir) + +def test_test_command_install_requirements_when_using_easy_install(bare_virtualenv, tmpdir): + _check_test_command_install_requirements(bare_virtualenv, tmpdir) + def test_no_missing_dependencies(bare_virtualenv): """ diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index e85a4a7e..d50816c2 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -451,6 +451,34 @@ WHEEL_INSTALL_TESTS = ( ), dict( + id='empty_namespace_package', + file_defs={ + 'foobar': { + '__init__.py': "__import__('pkg_resources').declare_namespace(__name__)", + }, + }, + setup_kwargs=dict( + namespace_packages=['foobar'], + packages=['foobar'], + ), + install_tree=flatten_tree({ + 'foo-1.0-py{py_version}.egg': [ + 'foo-1.0-py{py_version}-nspkg.pth', + {'EGG-INFO': [ + 'PKG-INFO', + 'RECORD', + 'WHEEL', + 'namespace_packages.txt', + 'top_level.txt', + ]}, + {'foobar': [ + '__init__.py', + ]}, + ] + }), + ), + + dict( id='data_in_package', file_defs={ 'foo': { diff --git a/setuptools/wheel.py b/setuptools/wheel.py index e11f0a1d..22eec05e 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -1,6 +1,7 @@ """Wheels support.""" from distutils.util import get_platform +from distutils import log import email import itertools import os @@ -162,11 +163,17 @@ class Wheel: extras_require=extras_require, ), ) - write_requirements( - setup_dist.get_command_obj('egg_info'), - None, - os.path.join(egg_info, 'requires.txt'), - ) + # Temporarily disable info traces. + log_threshold = log._global_log.threshold + log.set_threshold(log.WARN) + try: + write_requirements( + setup_dist.get_command_obj('egg_info'), + None, + os.path.join(egg_info, 'requires.txt'), + ) + finally: + log.set_threshold(log_threshold) @staticmethod def _move_data_entries(destination_eggdir, dist_data): @@ -206,6 +213,8 @@ class Wheel: for mod in namespace_packages: mod_dir = os.path.join(destination_eggdir, *mod.split('.')) mod_init = os.path.join(mod_dir, '__init__.py') - if os.path.exists(mod_dir) and not os.path.exists(mod_init): + if not os.path.exists(mod_dir): + os.mkdir(mod_dir) + if not os.path.exists(mod_init): with open(mod_init, 'w') as fp: fp.write(NAMESPACE_PACKAGE_INIT) diff --git a/tests/requirements.txt b/tests/requirements.txt index cb3e6726..1f8bd19d 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -9,4 +9,4 @@ coverage>=4.5.1 pytest-cov>=2.5.1 paver; python_version>="3.6" futures; python_version=="2.7" -pip==18.1 # Temporary workaround for #1644. +pip>=19.1 # For proper file:// URLs support. diff --git a/tools/tox_pip.py b/tools/tox_pip.py new file mode 100644 index 00000000..5aeca805 --- /dev/null +++ b/tools/tox_pip.py @@ -0,0 +1,38 @@ +import os +import shutil +import subprocess +import sys +from glob import glob + +VIRTUAL_ENV = os.environ['VIRTUAL_ENV'] +TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip') + + +def pip(args): + # First things first, get a recent (stable) version of pip. + if not os.path.exists(TOX_PIP_DIR): + subprocess.check_call([sys.executable, '-m', 'pip', + '--disable-pip-version-check', + 'install', '-t', TOX_PIP_DIR, + 'pip']) + shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0]) + # And use that version. + pypath = os.environ.get('PYTHONPATH') + pypath = pypath.split(os.pathsep) if pypath is not None else [] + pypath.insert(0, TOX_PIP_DIR) + os.environ['PYTHONPATH'] = os.pathsep.join(pypath) + # Disable PEP 517 support when using editable installs. + for n, a in enumerate(args): + if not a.startswith('-'): + if a in 'install' and '-e' in args[n:]: + args.insert(n + 1, '--no-use-pep517') + break + # Fix call for setuptools editable install. + for n, a in enumerate(args): + if a == '.': + args[n] = os.getcwd() + subprocess.check_call([sys.executable, '-m', 'pip'] + args, cwd=TOX_PIP_DIR) + + +if __name__ == '__main__': + pip(sys.argv[1:]) @@ -7,14 +7,16 @@ [tox] envlist=python +[helpers] +# Wrapper for calls to pip that make sure the version being used is a +# up-to-date, and to prevent the current working directory from being +# added to `sys.path`. +pip = python {toxinidir}/tools/tox_pip.py + [testenv] -deps=-rtests/requirements.txt -# Changed from default (`python -m pip ...`) -# to prevent the current working directory -# from being added to `sys.path`. -install_command=python -c 'import sys; sys.path.remove(""); from pkg_resources import load_entry_point; load_entry_point("pip", "console_scripts", "pip")()' install {opts} {packages} -# Same as above. -list_dependencies_command={envbindir}/pip freeze --all +deps=-r{toxinidir}/tests/requirements.txt +install_command = {[helpers]pip} install {opts} {packages} +list_dependencies_command = {[helpers]pip} freeze --all setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. |