Important
This PEP is a historical document. The up-to-date, canonical spec,Core metadata specifications, is maintained on thePyPA specs page.
×
See thePyPA specification update process for how to propose changes.
The goal of this PEP is to provide a standard infrastructure to manageproject distributions installed on a system, so all tools that areinstalling or removing projects are interoperable.
To achieve this goal, the PEP proposes a new format to describe installeddistributions on a system. It also describes a reference implementationfor the standard library.
In the past an attempt was made to create an installation database(seePEP 262).
Combined withPEP 345, the current proposal supersedesPEP 262.
Note: the implementation plan didn’t go as expected, so it should beconsidered informative only for this PEP.
There are two problems right now in the way distributions are installed inPython:
Right now, when a distribution is installed in Python, every element canbe installed in a different directory.
For instance,Distutils installs the pure Python code in thepurelibdirectory, which islib/python2.6/site-packages for unix-like systems andMac OS X, orLib\site-packages under Python’s installation directory forWindows.
Additionally, theinstall_egg_info subcommand of the Distutilsinstallcommand adds an.egg-info file for the project into thepurelibdirectory.
For example, for thedocutils distribution, which contains one package anextra module and executable scripts, three elements are installed insite-packages:
docutils: Thedocutils package.roman.py: An extra module used bydocutils.docutils-0.5-py2.6.egg-info: A file containing the distribution metadataas described inPEP 314. This file corresponds to the filecalledPKG-INFO, built by thesdist command.Some executable scripts, such asrst2html.py, are also added in thebin directory of the Python installation.
Another project calledsetuptools[1] has two other formatsto install distributions, calledEggFormats[4]:
.egg directory, that contains all the distribution filesand the distribution metadata in a file calledPKG-INFO in a subdirectorycalledEGG-INFO.setuptools creates other files in that directory that canbe considered as complementary metadata..egg-info directory installed insite-packages, that contains the samefilesEGG-INFO has in the.egg format.The first format is automatically used when you install a distribution thatuses thesetuptools.setup function in its setup.py file, instead ofthedistutils.core.setup one.
setuptools also add a reference to the distribution into aneasy-install.pth file.
Last, thesetuptools project provides an executable script calledeasy_install[2] that installs all distributions, includingdistutils-based ones in self-contained.egg directories.
If you want to have standalone.egg-info directories for your distributions,e.g. the secondsetuptools format, you have to force it when you workwith a setuptools-based distribution or with theeasy_install script.You can force it by using the--single-version-externally-managed optionor the--root option. This will make thesetuptools project installthe project like distutils does.
This option is used by :
Distutils doesn’t provide anuninstall command. If you want to uninstalla distribution, you have to be a power user and remove the various elementsthat were installed, and then look over the.pth file to clean them ifnecessary.
And the process differs depending on the tools you have used to install thedistribution and if the distribution’ssetup.py uses Distutils orSetuptools.
Under some circumstances, you might not be able to know for sure that youhave removed everything, or that you didn’t break another distribution byremoving a file that is shared among several distributions.
But there’s a common behavior: when you install a distribution, files arecopied in your system. And it’s possible to keep track of these files forlater removal.
Moreover, the Pip project has gained anuninstall feature lately. Itrecords all installed files, using therecord option of theinstallcommand.
To address those issues, this PEP proposes a few changes:
.dist-info structure using a directory, inspired on one format oftheEggFormats standard fromsetuptools.pkgutil to be able to query the information of installeddistributions.This PEP proposes an installation format inspired by one of the options in theEggFormats standard, the one that uses a distinct directory located in thesite-packages directory.
This distinct directory is named as follows:
name+'-'+version+'.dist-info'
This.dist-info directory can contain these files:
METADATA: contains metadata, as described inPEP 345,PEP 314 andPEP 241.RECORD: records the list of installed filesINSTALLER: records the name of the tool used to install the projectREQUESTED: the presence of this file indicates that the projectinstallation was explicitly requested (i.e., not installed as a dependency).The METADATA, RECORD and INSTALLER files are mandatory, while REQUESTED maybe missing.
This proposal will not impact Python itself because the metadata files are notused anywhere yet in the standard library besides Distutils.
It will impact thesetuptools andpip projects but, given the fact thatthey already work with a directory that contains aPKG-INFO file, the changewill have no deep consequences.
ARECORD file is added inside the.dist-info directory at installationtime when installing a source distribution using theinstall command.Notice that when installing a binary distribution created withbdist commandor abdist-based command, theRECORD file will be installed as well sincethese commands use theinstall command to create binary distributions.
TheRECORD file holds the list of installed files. These correspondto the files listed by therecord option of theinstall command, and willbe generated by default. This allows the implementation of an uninstallationfeature, as explained later in this PEP. Theinstall command also providesan option to prevent theRECORD file from being written and this optionshould be used when creating system packages.
Third-party installation tools also should not overwrite or delete filesthat are not in a RECORD file without prompting or warning.
This RECORD file is inspired fromPEP 262 FILES.
TheRECORD file is a CSV file, composed of records, one line perinstalled file. Thecsv module is used to read the file, withthese options:
,".os.linesep (so\r\n or\n)When a distribution is installed, files can be installed under:
--install-lib option,which defaults to the site-packages directory.--prefix option, whichdefaults tosys.prefix.Each record is composed of three elements:
pyc andpyo generated files don’t have any hash becausethey are automatically produced frompy files. So checking the hashof the correspondingpy file is enough to decide if the file andits associatedpyc orpyo files have changed.The hash is either the empty string or the hash algorithm as named inhashlib.algorithms_guaranteed, followed by the equals character=, followed by the urlsafe-base64-nopad encoding of the digest(base64.urlsafe_b64encode(digest) with trailing= removed).
Thecsv module is used to generate this file, so the field separator is“,”. Any “,” character found within a field is escaped automatically bycsv.
When the file is read, theU option is used so the universal newlinesupport (seePEP 278) is activated, avoiding any troublereading a file produced on a platform that uses a different new lineterminator.
Here’s an example of a RECORD file (extract):
lib/python2.6/site-packages/docutils/__init__.py,md5=nWt-Dge1eug4iAgqLS_uWg,9544lib/python2.6/site-packages/docutils/__init__.pyc,,lib/python2.6/site-packages/docutils/core.py,md5=X90C_JLIcC78PL74iuhPnA,66188lib/python2.6/site-packages/docutils/core.pyc,,lib/python2.6/site-packages/roman.py,md5=7YhfNczihNjOY0FXlupwBg,234lib/python2.6/site-packages/roman.pyc,,/usr/local/bin/rst2html.py,md5=g22D3amDLJP-FhBzCi7EvA,234/usr/local/bin/rst2html.pyc,,python2.6/site-packages/docutils-0.5.dist-info/METADATA,md5=ovJyUNzXdArGfmVyb0onyA,195lib/python2.6/site-packages/docutils-0.5.dist-info/RECORD,,
Notice that theRECORD file can’t contain a hash of itself and is just mentioned here
A project that installs aconfig.ini file in/etc/myapp will be added like this:
/etc/myapp/config.ini,md5=gLfd6IANquzGLhOkW4Mfgg,9544
For a windows platform, the drive letter is added for the absolute paths,so a file that is copied in c:MyAppwill be:
c:\etc\myapp\config.ini,md5=gLfd6IANquzGLhOkW4Mfgg,9544
Theinstall command has a new option calledinstaller. This optionis the name of the tool used to invoke the installation. It’s a normalizedlower-case string matching[a-z0-9_\-\.].
$ python setup.py install –installer=pkg-system
It defaults todistutils if not provided.
When a distribution is installed, the INSTALLER file is generated in the.dist-info directory with this value, to keep track ofwho installed thedistribution. The file is a single-line text file.
Some install tools automatically detect unfulfilled dependencies andinstall them. In these cases, it is useful to track whichdistributions were installed purely as a dependency, so if theirdependent distribution is later uninstalled, the user can be alertedof the orphaned dependency.
If a distribution is installed by direct user request (the usualcase), a file REQUESTED is added to the .dist-info directory of theinstalled distribution. The REQUESTED file may be empty, or maycontain a marker comment line beginning with the “#” character.
If an install tool installs a distribution automatically, as adependency of another distribution, the REQUESTED file should not becreated.
Theinstall command of distutils by default creates the REQUESTEDfile. It accepts--requested and--no-requested options to explicitlyspecify whether the file is created.
If a distribution that was already installed on the system as a dependencyis later installed by name, the distutilsinstall command willcreate the REQUESTED file in the .dist-info directory of the existinginstallation.
Note: this section is non-normative. In the end, this PEP wasimplemented by third-party libraries and tools, not the standardlibrary.
To use the.dist-info directory content, we need to add in the standardlibrary a set of APIs. The best place to put these APIs ispkgutil.
The new functions added in thepkgutil module are :
distinfo_dirname(name,version) -> directory namenameis converted to a standard distribution name by replacing anyruns of non-alphanumeric characters with a single ‘-‘.
versionis converted to a standard version string. Spaces becomedots, and all other non-alphanumeric characters (except dots) becomedashes, with runs of multiple dashes condensed to a single dash.Both attributes are then converted into their filename-escaped form,i.e. any ‘-’ characters are replaced with ‘_’ other than the one in‘dist-info’ and the one separating the name from the version number.
get_distributions() -> iterator ofDistribution instances.Provides an iterator that looks for.dist-info directories insys.path and returnsDistribution instances foreach one of them.
get_distribution(name) ->Distribution or None.obsoletes_distribution(name,version=None) -> iterator ofDistributioninstances.Iterates over all distributions to find which distributionsobsoletename. If aversion is provided, it will be used to filter the results.
provides_distribution(name,version=None) -> iterator ofDistributioninstances.Iterates over all distributions to find which distributionsprovidename. If aversion is provided, it will be used to filter the results.Scans all elements insys.path and looks for all directories ending with.dist-info. Returns aDistribution corresponding to the.dist-info directory that contains a METADATA that matchesnamefor thename metadata.
This function only returns the first result founded, since no more than onevalues are expected. If the directory is not found, returns None.
get_file_users(path) -> iterator ofDistribution instances.Iterates over all distributions to find out which distributions usespath.path can be a local absolute path or a relative ‘/’-separated path.
A local absolute path is an absolute path in which occurrences of ‘/’have been replaced by the system separator given byos.sep.
A new class calledDistribution is created with the path of the.dist-info directory provided to the constructor. It reads the metadatacontained inMETADATA when it is instantiated.
Distribution(path) -> instance
Creates aDistributioninstance for the givenpath.
Distribution provides the following attributes:
name: The name of the distribution.metadata: ADistributionMetadata instance loaded with thedistribution’s METADATA file.requested: A boolean that indicates whether the REQUESTEDmetadata file is present (in other words, whether the distribution wasinstalled by user request).And following methods:
get_installed_files(local=False) -> iterator of (path, hash, size)Iterates over theRECORD entries and return a tuple(path,hash,size)for each line. Iflocal isTrue, the path is transformed into alocal absolute path. Otherwise the raw value fromRECORD is returned.
A local absolute path is an absolute path in which occurrences of ‘/’have been replaced by the system separator given byos.sep.
uses(path) -> BooleanReturnsTrue ifpath is listed inRECORD.pathcan be a local absolute path or a relative ‘/’-separated path.
get_distinfo_file(path,binary=False) -> file objectReturns a file located under the.dist-infodirectory.Returns a
fileinstance for the file pointed bypath.
pathhas to be a ‘/’-separated path relative to the.dist-infodirectory or an absolute path.If
pathis an absolute path and doesn’t start with the.dist-infodirectory path, aDistutilsErroris raised.If
binaryisTrue, opens the file in read-only binary mode (rb),otherwise opens it in read-only mode (r).
get_distinfo_files(local=False) -> iterator of pathsIterates over theRECORD entries and returns paths for each line if the pathis pointing to a file located in the.dist-info directory or one of itssubdirectories.
Iflocal isTrue, each path is transformed into alocal absolute path. Otherwise the raw value fromRECORD is returned.
Notice that the API is organized in five classes that work with directoriesand Zip files (so it works with files included in Zip files, seePEP 273 formore details). These classes are described in the documentationof the prototype implementation for interested readers[7].
Let’s use some of the new APIs with ourdocutils example:
>>>frompkgutilimportget_distribution,get_file_users,distinfo_dirname>>>dist=get_distribution('docutils')>>>dist.name'docutils'>>>dist.metadata.version'0.5'>>>distinfo_dirname('docutils','0.5')'docutils-0.5.dist-info'>>>distinfo_dirname('python-ldap','2.5')'python_ldap-2.5.dist-info'>>>distinfo_dirname('python-ldap','2.5 a---5')'python_ldap-2.5.a_5.dist-info'>>>forpath,hash,sizeindist.get_installed_files()::...print'%s%s%d'%(path,hash,size)...python2.6/site-packages/docutils/__init__.py,b690274f621402dda63bf11ba5373bf2,9544python2.6/site-packages/docutils/core.py,9c4b84aff68aa55f2e9bf70481b94333,66188python2.6/site-packages/roman.py,a4b84aff68aa55f2e9bf70481b943D3,234/usr/local/bin/rst2html.py,a4b84aff68aa55f2e9bf70481b943D3,234python2.6/site-packages/docutils-0.5.dist-info/METADATA,6fe57de576d749536082d8e205b77748,195python2.6/site-packages/docutils-0.5.dist-info/RECORD>>>dist.uses('docutils/core.py')True>>>dist.uses('/usr/local/bin/rst2html.py')True>>>dist.get_distinfo_file('METADATA')<open file at ...>>>>dist.requestedTrue
Distutils already provides a very basic way to install a distribution, whichis running theinstall command over thesetup.py script of thedistribution.
Distutils2 will provide a very basicuninstall function, thatis added indistutils2.util and takes the name of the distribution touninstall as its argument.uninstall uses the APIs described earlier andremove all unique files, as long as their hash didn’t change. Then it removesempty directories left behind.
uninstall returns a list of uninstalled files:
>>>fromdistutils2.utilimportuninstall>>>uninstall('docutils')['/opt/local/lib/python2.6/site-packages/docutils/core.py', ... '/opt/local/lib/python2.6/site-packages/docutils/__init__.py']
If the distribution is not found, aDistutilsUninstallError is raised.
To make it a reference API for third-party projects that wish to controlhowuninstall works, a second callable argument can be used. It’scalled for each file that is removed. If the callable returnsTrue, thefile is removed. If it returns False, it’s left alone.
Examples:
>>>def_remove_and_log(path):...logging.info('Removing%s'%path)...returnTrue...>>>uninstall('docutils',_remove_and_log)>>>def_dry_run(path):...logging.info('Removing%s (dry run)'%path)...returnFalse...>>>uninstall('docutils',_dry_run)
Of course, a third-party tool can use lower-levelpkgutil APIs toimplement its own uninstall feature.
As explained earlier in this PEP, theinstall command adds anINSTALLERfile in the.dist-info directory with the name of the installer.
To avoid removing distributions that were installed by another packagingsystem, theuninstall function takes an extra argumentinstaller whichdefaults todistutils2.
When called,uninstall controls that theINSTALLER file matchesthis argument. If not, it raises aDistutilsUninstallError:
>>>uninstall('docutils')Traceback (most recent call last):...DistutilsUninstallError:docutils was installed by 'cool-pkg-manager'>>>uninstall('docutils',installer='cool-pkg-manager')
This allows a third-party application to use theuninstall functionand strongly suggest that no other program remove a distribution it haspreviously installed. This is useful when a third-party program that relieson Distutils APIs does extra steps on the system at installation time,it has to undo at uninstallation time.
Anuninstall script is added in Distutils2. and is used like this:
$ python -m distutils2.uninstall projectname
Notice that script doesn’t control if the removal of a distribution breaksanother distribution. Although it makes sure that all the files it removesare not used by any other distribution, by using the uninstall function.
Also note that this uninstall script pays no attention to theREQUESTED metadata; that is provided only for use by external tools toprovide more advanced dependency management.
These changes don’t introduce any compatibility problems since theywill be implemented in:
The plan is to include the functionality outlined in this PEP in pkgutil forPython 3.2, and in Distutils2.
Distutils2 will also contain a backport of the new pgkutil, and can be used for2.4 onward.
Distributions installed using existing, pre-standardization formats do not havethe necessary metadata available for the new API, and thus will beignored. Third-party tools may of course to continue to support previousformats in addition to the new format, in order to ease the transition.
Jim Fulton, Ian Bicking, Phillip Eby, Rafael Villar Burke, and many people atPycon and Distutils-SIG.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0376.rst
Last modified:2024-12-15 20:57:13 GMT