You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			425 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
			
		
		
	
	
			425 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
Pluggable Distributions of Python Software
 | 
						|
==========================================
 | 
						|
 | 
						|
Distributions
 | 
						|
-------------
 | 
						|
 | 
						|
A "Distribution" is a collection of files that represent a "Release" of a
 | 
						|
"Project" as of a particular point in time, denoted by a
 | 
						|
"Version"::
 | 
						|
 | 
						|
    >>> import sys, pkg_resources
 | 
						|
    >>> from pkg_resources import Distribution
 | 
						|
    >>> Distribution(project_name="Foo", version="1.2")
 | 
						|
    Foo 1.2
 | 
						|
 | 
						|
Distributions have a location, which can be a filename, URL, or really anything
 | 
						|
else you care to use::
 | 
						|
 | 
						|
    >>> dist = Distribution(
 | 
						|
    ...     location="http://example.com/something",
 | 
						|
    ...     project_name="Bar", version="0.9"
 | 
						|
    ... )
 | 
						|
 | 
						|
    >>> dist
 | 
						|
    Bar 0.9 (http://example.com/something)
 | 
						|
 | 
						|
 | 
						|
Distributions have various introspectable attributes::
 | 
						|
 | 
						|
    >>> dist.location
 | 
						|
    'http://example.com/something'
 | 
						|
 | 
						|
    >>> dist.project_name
 | 
						|
    'Bar'
 | 
						|
 | 
						|
    >>> dist.version
 | 
						|
    '0.9'
 | 
						|
 | 
						|
    >>> dist.py_version == '{}.{}'.format(*sys.version_info)
 | 
						|
    True
 | 
						|
 | 
						|
    >>> print(dist.platform)
 | 
						|
    None
 | 
						|
 | 
						|
Including various computed attributes::
 | 
						|
 | 
						|
    >>> from pkg_resources import parse_version
 | 
						|
    >>> dist.parsed_version == parse_version(dist.version)
 | 
						|
    True
 | 
						|
 | 
						|
    >>> dist.key    # case-insensitive form of the project name
 | 
						|
    'bar'
 | 
						|
 | 
						|
Distributions are compared (and hashed) by version first::
 | 
						|
 | 
						|
    >>> Distribution(version='1.0') == Distribution(version='1.0')
 | 
						|
    True
 | 
						|
    >>> Distribution(version='1.0') == Distribution(version='1.1')
 | 
						|
    False
 | 
						|
    >>> Distribution(version='1.0') <  Distribution(version='1.1')
 | 
						|
    True
 | 
						|
 | 
						|
but also by project name (case-insensitive), platform, Python version,
 | 
						|
location, etc.::
 | 
						|
 | 
						|
    >>> Distribution(project_name="Foo",version="1.0") == \
 | 
						|
    ... Distribution(project_name="Foo",version="1.0")
 | 
						|
    True
 | 
						|
 | 
						|
    >>> Distribution(project_name="Foo",version="1.0") == \
 | 
						|
    ... Distribution(project_name="foo",version="1.0")
 | 
						|
    True
 | 
						|
 | 
						|
    >>> Distribution(project_name="Foo",version="1.0") == \
 | 
						|
    ... Distribution(project_name="Foo",version="1.1")
 | 
						|
    False
 | 
						|
 | 
						|
    >>> Distribution(project_name="Foo",py_version="2.3",version="1.0") == \
 | 
						|
    ... Distribution(project_name="Foo",py_version="2.4",version="1.0")
 | 
						|
    False
 | 
						|
 | 
						|
    >>> Distribution(location="spam",version="1.0") == \
 | 
						|
    ... Distribution(location="spam",version="1.0")
 | 
						|
    True
 | 
						|
 | 
						|
    >>> Distribution(location="spam",version="1.0") == \
 | 
						|
    ... Distribution(location="baz",version="1.0")
 | 
						|
    False
 | 
						|
 | 
						|
 | 
						|
 | 
						|
Hash and compare distribution by prio/plat
 | 
						|
 | 
						|
Get version from metadata
 | 
						|
provider capabilities
 | 
						|
egg_name()
 | 
						|
as_requirement()
 | 
						|
from_location, from_filename (w/path normalization)
 | 
						|
 | 
						|
Releases may have zero or more "Requirements", which indicate
 | 
						|
what releases of another project the release requires in order to
 | 
						|
function.  A Requirement names the other project, expresses some criteria
 | 
						|
as to what releases of that project are acceptable, and lists any "Extras"
 | 
						|
that the requiring release may need from that project.  (An Extra is an
 | 
						|
optional feature of a Release, that can only be used if its additional
 | 
						|
Requirements are satisfied.)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
The Working Set
 | 
						|
---------------
 | 
						|
 | 
						|
A collection of active distributions is called a Working Set.  Note that a
 | 
						|
Working Set can contain any importable distribution, not just pluggable ones.
 | 
						|
For example, the Python standard library is an importable distribution that
 | 
						|
will usually be part of the Working Set, even though it is not pluggable.
 | 
						|
Similarly, when you are doing development work on a project, the files you are
 | 
						|
editing are also a Distribution.  (And, with a little attention to the
 | 
						|
directory names used,  and including some additional metadata, such a
 | 
						|
"development distribution" can be made pluggable as well.)
 | 
						|
 | 
						|
    >>> from pkg_resources import WorkingSet
 | 
						|
 | 
						|
A working set's entries are the sys.path entries that correspond to the active
 | 
						|
distributions.  By default, the working set's entries are the items on
 | 
						|
``sys.path``::
 | 
						|
 | 
						|
    >>> ws = WorkingSet()
 | 
						|
    >>> ws.entries == sys.path
 | 
						|
    True
 | 
						|
 | 
						|
But you can also create an empty working set explicitly, and add distributions
 | 
						|
to it::
 | 
						|
 | 
						|
    >>> ws = WorkingSet([])
 | 
						|
    >>> ws.add(dist)
 | 
						|
    >>> ws.entries
 | 
						|
    ['http://example.com/something']
 | 
						|
    >>> dist in ws
 | 
						|
    True
 | 
						|
    >>> Distribution('foo',version="") in ws
 | 
						|
    False
 | 
						|
 | 
						|
And you can iterate over its distributions::
 | 
						|
 | 
						|
    >>> list(ws)
 | 
						|
    [Bar 0.9 (http://example.com/something)]
 | 
						|
 | 
						|
Adding the same distribution more than once is a no-op::
 | 
						|
 | 
						|
    >>> ws.add(dist)
 | 
						|
    >>> list(ws)
 | 
						|
    [Bar 0.9 (http://example.com/something)]
 | 
						|
 | 
						|
For that matter, adding multiple distributions for the same project also does
 | 
						|
nothing, because a working set can only hold one active distribution per
 | 
						|
project -- the first one added to it::
 | 
						|
 | 
						|
    >>> ws.add(
 | 
						|
    ...     Distribution(
 | 
						|
    ...         'http://example.com/something', project_name="Bar",
 | 
						|
    ...         version="7.2"
 | 
						|
    ...     )
 | 
						|
    ... )
 | 
						|
    >>> list(ws)
 | 
						|
    [Bar 0.9 (http://example.com/something)]
 | 
						|
 | 
						|
You can append a path entry to a working set using ``add_entry()``::
 | 
						|
 | 
						|
    >>> ws.entries
 | 
						|
    ['http://example.com/something']
 | 
						|
    >>> ws.add_entry(pkg_resources.__file__)
 | 
						|
    >>> ws.entries
 | 
						|
    ['http://example.com/something', '...pkg_resources...']
 | 
						|
 | 
						|
Multiple additions result in multiple entries, even if the entry is already in
 | 
						|
the working set (because ``sys.path`` can contain the same entry more than
 | 
						|
once)::
 | 
						|
 | 
						|
    >>> ws.add_entry(pkg_resources.__file__)
 | 
						|
    >>> ws.entries
 | 
						|
    ['...example.com...', '...pkg_resources...', '...pkg_resources...']
 | 
						|
 | 
						|
And you can specify the path entry a distribution was found under, using the
 | 
						|
optional second parameter to ``add()``::
 | 
						|
 | 
						|
    >>> ws = WorkingSet([])
 | 
						|
    >>> ws.add(dist,"foo")
 | 
						|
    >>> ws.entries
 | 
						|
    ['foo']
 | 
						|
 | 
						|
But even if a distribution is found under multiple path entries, it still only
 | 
						|
shows up once when iterating the working set:
 | 
						|
 | 
						|
    >>> ws.add_entry(ws.entries[0])
 | 
						|
    >>> list(ws)
 | 
						|
    [Bar 0.9 (http://example.com/something)]
 | 
						|
 | 
						|
You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
 | 
						|
 | 
						|
    >>> from pkg_resources import Requirement
 | 
						|
    >>> print(ws.find(Requirement.parse("Foo==1.0")))   # no match, return None
 | 
						|
    None
 | 
						|
 | 
						|
    >>> ws.find(Requirement.parse("Bar==0.9"))  # match, return distribution
 | 
						|
    Bar 0.9 (http://example.com/something)
 | 
						|
 | 
						|
Note that asking for a conflicting version of a distribution already in a
 | 
						|
working set triggers a ``pkg_resources.VersionConflict`` error:
 | 
						|
 | 
						|
    >>> try:
 | 
						|
    ...     ws.find(Requirement.parse("Bar==1.0"))
 | 
						|
    ... except pkg_resources.VersionConflict as exc:
 | 
						|
    ...     print(str(exc))
 | 
						|
    ... else:
 | 
						|
    ...     raise AssertionError("VersionConflict was not raised")
 | 
						|
    (Bar 0.9 (http://example.com/something), Requirement.parse('Bar==1.0'))
 | 
						|
 | 
						|
You can subscribe a callback function to receive notifications whenever a new
 | 
						|
distribution is added to a working set.  The callback is immediately invoked
 | 
						|
once for each existing distribution in the working set, and then is called
 | 
						|
again for new distributions added thereafter::
 | 
						|
 | 
						|
    >>> def added(dist): print("Added %s" % dist)
 | 
						|
    >>> ws.subscribe(added)
 | 
						|
    Added Bar 0.9
 | 
						|
    >>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12")
 | 
						|
    >>> ws.add(foo12)
 | 
						|
    Added Foo 1.2
 | 
						|
 | 
						|
Note, however, that only the first distribution added for a given project name
 | 
						|
will trigger a callback, even during the initial ``subscribe()`` callback::
 | 
						|
 | 
						|
    >>> foo14 = Distribution(project_name="Foo", version="1.4", location="f14")
 | 
						|
    >>> ws.add(foo14)   # no callback, because Foo 1.2 is already active
 | 
						|
 | 
						|
    >>> ws = WorkingSet([])
 | 
						|
    >>> ws.add(foo12)
 | 
						|
    >>> ws.add(foo14)
 | 
						|
    >>> ws.subscribe(added)
 | 
						|
    Added Foo 1.2
 | 
						|
 | 
						|
And adding a callback more than once has no effect, either::
 | 
						|
 | 
						|
    >>> ws.subscribe(added)     # no callbacks
 | 
						|
 | 
						|
    # and no double-callbacks on subsequent additions, either
 | 
						|
    >>> just_a_test = Distribution(project_name="JustATest", version="0.99")
 | 
						|
    >>> ws.add(just_a_test)
 | 
						|
    Added JustATest 0.99
 | 
						|
 | 
						|
 | 
						|
Finding Plugins
 | 
						|
---------------
 | 
						|
 | 
						|
``WorkingSet`` objects can be used to figure out what plugins in an
 | 
						|
``Environment`` can be loaded without any resolution errors::
 | 
						|
 | 
						|
    >>> from pkg_resources import Environment
 | 
						|
 | 
						|
    >>> plugins = Environment([])   # normally, a list of plugin directories
 | 
						|
    >>> plugins.add(foo12)
 | 
						|
    >>> plugins.add(foo14)
 | 
						|
    >>> plugins.add(just_a_test)
 | 
						|
 | 
						|
In the simplest case, we just get the newest version of each distribution in
 | 
						|
the plugin environment::
 | 
						|
 | 
						|
    >>> ws = WorkingSet([])
 | 
						|
    >>> ws.find_plugins(plugins)
 | 
						|
    ([JustATest 0.99, Foo 1.4 (f14)], {})
 | 
						|
 | 
						|
But if there's a problem with a version conflict or missing requirements, the
 | 
						|
method falls back to older versions, and the error info dict will contain an
 | 
						|
exception instance for each unloadable plugin::
 | 
						|
 | 
						|
    >>> ws.add(foo12)   # this will conflict with Foo 1.4
 | 
						|
    >>> ws.find_plugins(plugins)
 | 
						|
    ([JustATest 0.99, Foo 1.2 (f12)], {Foo 1.4 (f14): VersionConflict(...)})
 | 
						|
 | 
						|
But if you disallow fallbacks, the failed plugin will be skipped instead of
 | 
						|
trying older versions::
 | 
						|
 | 
						|
    >>> ws.find_plugins(plugins, fallback=False)
 | 
						|
    ([JustATest 0.99], {Foo 1.4 (f14): VersionConflict(...)})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
Platform Compatibility Rules
 | 
						|
----------------------------
 | 
						|
 | 
						|
On the Mac, there are potential compatibility issues for modules compiled
 | 
						|
on newer versions of macOS than what the user is running. Additionally,
 | 
						|
macOS will soon have two platforms to contend with: Intel and PowerPC.
 | 
						|
 | 
						|
Basic equality works as on other platforms::
 | 
						|
 | 
						|
    >>> from pkg_resources import compatible_platforms as cp
 | 
						|
    >>> reqd = 'macosx-10.4-ppc'
 | 
						|
    >>> cp(reqd, reqd)
 | 
						|
    True
 | 
						|
    >>> cp("win32", reqd)
 | 
						|
    False
 | 
						|
 | 
						|
Distributions made on other machine types are not compatible::
 | 
						|
 | 
						|
    >>> cp("macosx-10.4-i386", reqd)
 | 
						|
    False
 | 
						|
 | 
						|
Distributions made on earlier versions of the OS are compatible, as
 | 
						|
long as they are from the same top-level version. The patchlevel version
 | 
						|
number does not matter::
 | 
						|
 | 
						|
    >>> cp("macosx-10.4-ppc", reqd)
 | 
						|
    True
 | 
						|
    >>> cp("macosx-10.3-ppc", reqd)
 | 
						|
    True
 | 
						|
    >>> cp("macosx-10.5-ppc", reqd)
 | 
						|
    False
 | 
						|
    >>> cp("macosx-9.5-ppc", reqd)
 | 
						|
    False
 | 
						|
 | 
						|
Backwards compatibility for packages made via earlier versions of
 | 
						|
setuptools is provided as well::
 | 
						|
 | 
						|
    >>> cp("darwin-8.2.0-Power_Macintosh", reqd)
 | 
						|
    True
 | 
						|
    >>> cp("darwin-7.2.0-Power_Macintosh", reqd)
 | 
						|
    True
 | 
						|
    >>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc")
 | 
						|
    False
 | 
						|
 | 
						|
 | 
						|
Environment Markers
 | 
						|
-------------------
 | 
						|
 | 
						|
    >>> from pkg_resources import invalid_marker as im, evaluate_marker as em
 | 
						|
    >>> import os
 | 
						|
 | 
						|
    >>> print(im("sys_platform"))
 | 
						|
    Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
 | 
						|
        sys_platform
 | 
						|
                    ^
 | 
						|
 | 
						|
    >>> print(im("sys_platform=="))
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        sys_platform==
 | 
						|
                      ^
 | 
						|
 | 
						|
    >>> print(im("sys_platform=='win32'"))
 | 
						|
    False
 | 
						|
 | 
						|
    >>> print(im("sys=='x'"))
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        sys=='x'
 | 
						|
        ^
 | 
						|
 | 
						|
    >>> print(im("(extra)"))
 | 
						|
    Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
 | 
						|
        (extra)
 | 
						|
              ^
 | 
						|
 | 
						|
    >>> print(im("(extra"))
 | 
						|
    Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
 | 
						|
        (extra
 | 
						|
              ^
 | 
						|
 | 
						|
    >>> print(im("os.open('foo')=='y'"))
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        os.open('foo')=='y'
 | 
						|
        ^
 | 
						|
 | 
						|
    >>> print(im("'x'=='y' and os.open('foo')=='y'"))   # no short-circuit!
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        'x'=='y' and os.open('foo')=='y'
 | 
						|
                     ^
 | 
						|
 | 
						|
    >>> print(im("'x'=='x' or os.open('foo')=='y'"))   # no short-circuit!
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        'x'=='x' or os.open('foo')=='y'
 | 
						|
                    ^
 | 
						|
 | 
						|
    >>> print(im("r'x'=='x'"))
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        r'x'=='x'
 | 
						|
        ^
 | 
						|
 | 
						|
    >>> print(im("'''x'''=='x'"))
 | 
						|
    Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
 | 
						|
        '''x'''=='x'
 | 
						|
          ^
 | 
						|
 | 
						|
    >>> print(im('"""x"""=="x"'))
 | 
						|
    Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
 | 
						|
        """x"""=="x"
 | 
						|
          ^
 | 
						|
 | 
						|
    >>> print(im(r"x\n=='x'"))
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        x\n=='x'
 | 
						|
        ^
 | 
						|
 | 
						|
    >>> print(im("os.open=='y'"))
 | 
						|
    Expected a marker variable or quoted string
 | 
						|
        os.open=='y'
 | 
						|
        ^
 | 
						|
 | 
						|
    >>> em("sys_platform=='win32'") == (sys.platform=='win32')
 | 
						|
    True
 | 
						|
 | 
						|
    >>> em("python_version >= '2.7'")
 | 
						|
    True
 | 
						|
 | 
						|
    >>> em("python_version > '2.6'")
 | 
						|
    True
 | 
						|
 | 
						|
    >>> im("implementation_name=='cpython'")
 | 
						|
    False
 | 
						|
 | 
						|
    >>> im("platform_python_implementation=='CPython'")
 | 
						|
    False
 | 
						|
 | 
						|
    >>> im("implementation_version=='3.5.1'")
 | 
						|
    False
 |