123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- 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
|