api_tests.txt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. Pluggable Distributions of Python Software
  2. ==========================================
  3. Distributions
  4. -------------
  5. A "Distribution" is a collection of files that represent a "Release" of a
  6. "Project" as of a particular point in time, denoted by a
  7. "Version"::
  8. >>> import sys, pkg_resources
  9. >>> from pkg_resources import Distribution
  10. >>> Distribution(project_name="Foo", version="1.2")
  11. Foo 1.2
  12. Distributions have a location, which can be a filename, URL, or really anything
  13. else you care to use::
  14. >>> dist = Distribution(
  15. ... location="http://example.com/something",
  16. ... project_name="Bar", version="0.9"
  17. ... )
  18. >>> dist
  19. Bar 0.9 (http://example.com/something)
  20. Distributions have various introspectable attributes::
  21. >>> dist.location
  22. 'http://example.com/something'
  23. >>> dist.project_name
  24. 'Bar'
  25. >>> dist.version
  26. '0.9'
  27. >>> dist.py_version == '{}.{}'.format(*sys.version_info)
  28. True
  29. >>> print(dist.platform)
  30. None
  31. Including various computed attributes::
  32. >>> from pkg_resources import parse_version
  33. >>> dist.parsed_version == parse_version(dist.version)
  34. True
  35. >>> dist.key # case-insensitive form of the project name
  36. 'bar'
  37. Distributions are compared (and hashed) by version first::
  38. >>> Distribution(version='1.0') == Distribution(version='1.0')
  39. True
  40. >>> Distribution(version='1.0') == Distribution(version='1.1')
  41. False
  42. >>> Distribution(version='1.0') < Distribution(version='1.1')
  43. True
  44. but also by project name (case-insensitive), platform, Python version,
  45. location, etc.::
  46. >>> Distribution(project_name="Foo",version="1.0") == \
  47. ... Distribution(project_name="Foo",version="1.0")
  48. True
  49. >>> Distribution(project_name="Foo",version="1.0") == \
  50. ... Distribution(project_name="foo",version="1.0")
  51. True
  52. >>> Distribution(project_name="Foo",version="1.0") == \
  53. ... Distribution(project_name="Foo",version="1.1")
  54. False
  55. >>> Distribution(project_name="Foo",py_version="2.3",version="1.0") == \
  56. ... Distribution(project_name="Foo",py_version="2.4",version="1.0")
  57. False
  58. >>> Distribution(location="spam",version="1.0") == \
  59. ... Distribution(location="spam",version="1.0")
  60. True
  61. >>> Distribution(location="spam",version="1.0") == \
  62. ... Distribution(location="baz",version="1.0")
  63. False
  64. Hash and compare distribution by prio/plat
  65. Get version from metadata
  66. provider capabilities
  67. egg_name()
  68. as_requirement()
  69. from_location, from_filename (w/path normalization)
  70. Releases may have zero or more "Requirements", which indicate
  71. what releases of another project the release requires in order to
  72. function. A Requirement names the other project, expresses some criteria
  73. as to what releases of that project are acceptable, and lists any "Extras"
  74. that the requiring release may need from that project. (An Extra is an
  75. optional feature of a Release, that can only be used if its additional
  76. Requirements are satisfied.)
  77. The Working Set
  78. ---------------
  79. A collection of active distributions is called a Working Set. Note that a
  80. Working Set can contain any importable distribution, not just pluggable ones.
  81. For example, the Python standard library is an importable distribution that
  82. will usually be part of the Working Set, even though it is not pluggable.
  83. Similarly, when you are doing development work on a project, the files you are
  84. editing are also a Distribution. (And, with a little attention to the
  85. directory names used, and including some additional metadata, such a
  86. "development distribution" can be made pluggable as well.)
  87. >>> from pkg_resources import WorkingSet
  88. A working set's entries are the sys.path entries that correspond to the active
  89. distributions. By default, the working set's entries are the items on
  90. ``sys.path``::
  91. >>> ws = WorkingSet()
  92. >>> ws.entries == sys.path
  93. True
  94. But you can also create an empty working set explicitly, and add distributions
  95. to it::
  96. >>> ws = WorkingSet([])
  97. >>> ws.add(dist)
  98. >>> ws.entries
  99. ['http://example.com/something']
  100. >>> dist in ws
  101. True
  102. >>> Distribution('foo',version="") in ws
  103. False
  104. And you can iterate over its distributions::
  105. >>> list(ws)
  106. [Bar 0.9 (http://example.com/something)]
  107. Adding the same distribution more than once is a no-op::
  108. >>> ws.add(dist)
  109. >>> list(ws)
  110. [Bar 0.9 (http://example.com/something)]
  111. For that matter, adding multiple distributions for the same project also does
  112. nothing, because a working set can only hold one active distribution per
  113. project -- the first one added to it::
  114. >>> ws.add(
  115. ... Distribution(
  116. ... 'http://example.com/something', project_name="Bar",
  117. ... version="7.2"
  118. ... )
  119. ... )
  120. >>> list(ws)
  121. [Bar 0.9 (http://example.com/something)]
  122. You can append a path entry to a working set using ``add_entry()``::
  123. >>> ws.entries
  124. ['http://example.com/something']
  125. >>> ws.add_entry(pkg_resources.__file__)
  126. >>> ws.entries
  127. ['http://example.com/something', '...pkg_resources...']
  128. Multiple additions result in multiple entries, even if the entry is already in
  129. the working set (because ``sys.path`` can contain the same entry more than
  130. once)::
  131. >>> ws.add_entry(pkg_resources.__file__)
  132. >>> ws.entries
  133. ['...example.com...', '...pkg_resources...', '...pkg_resources...']
  134. And you can specify the path entry a distribution was found under, using the
  135. optional second parameter to ``add()``::
  136. >>> ws = WorkingSet([])
  137. >>> ws.add(dist,"foo")
  138. >>> ws.entries
  139. ['foo']
  140. But even if a distribution is found under multiple path entries, it still only
  141. shows up once when iterating the working set:
  142. >>> ws.add_entry(ws.entries[0])
  143. >>> list(ws)
  144. [Bar 0.9 (http://example.com/something)]
  145. You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
  146. >>> from pkg_resources import Requirement
  147. >>> print(ws.find(Requirement.parse("Foo==1.0"))) # no match, return None
  148. None
  149. >>> ws.find(Requirement.parse("Bar==0.9")) # match, return distribution
  150. Bar 0.9 (http://example.com/something)
  151. Note that asking for a conflicting version of a distribution already in a
  152. working set triggers a ``pkg_resources.VersionConflict`` error:
  153. >>> try:
  154. ... ws.find(Requirement.parse("Bar==1.0"))
  155. ... except pkg_resources.VersionConflict as exc:
  156. ... print(str(exc))
  157. ... else:
  158. ... raise AssertionError("VersionConflict was not raised")
  159. (Bar 0.9 (http://example.com/something), Requirement.parse('Bar==1.0'))
  160. You can subscribe a callback function to receive notifications whenever a new
  161. distribution is added to a working set. The callback is immediately invoked
  162. once for each existing distribution in the working set, and then is called
  163. again for new distributions added thereafter::
  164. >>> def added(dist): print("Added %s" % dist)
  165. >>> ws.subscribe(added)
  166. Added Bar 0.9
  167. >>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12")
  168. >>> ws.add(foo12)
  169. Added Foo 1.2
  170. Note, however, that only the first distribution added for a given project name
  171. will trigger a callback, even during the initial ``subscribe()`` callback::
  172. >>> foo14 = Distribution(project_name="Foo", version="1.4", location="f14")
  173. >>> ws.add(foo14) # no callback, because Foo 1.2 is already active
  174. >>> ws = WorkingSet([])
  175. >>> ws.add(foo12)
  176. >>> ws.add(foo14)
  177. >>> ws.subscribe(added)
  178. Added Foo 1.2
  179. And adding a callback more than once has no effect, either::
  180. >>> ws.subscribe(added) # no callbacks
  181. # and no double-callbacks on subsequent additions, either
  182. >>> just_a_test = Distribution(project_name="JustATest", version="0.99")
  183. >>> ws.add(just_a_test)
  184. Added JustATest 0.99
  185. Finding Plugins
  186. ---------------
  187. ``WorkingSet`` objects can be used to figure out what plugins in an
  188. ``Environment`` can be loaded without any resolution errors::
  189. >>> from pkg_resources import Environment
  190. >>> plugins = Environment([]) # normally, a list of plugin directories
  191. >>> plugins.add(foo12)
  192. >>> plugins.add(foo14)
  193. >>> plugins.add(just_a_test)
  194. In the simplest case, we just get the newest version of each distribution in
  195. the plugin environment::
  196. >>> ws = WorkingSet([])
  197. >>> ws.find_plugins(plugins)
  198. ([JustATest 0.99, Foo 1.4 (f14)], {})
  199. But if there's a problem with a version conflict or missing requirements, the
  200. method falls back to older versions, and the error info dict will contain an
  201. exception instance for each unloadable plugin::
  202. >>> ws.add(foo12) # this will conflict with Foo 1.4
  203. >>> ws.find_plugins(plugins)
  204. ([JustATest 0.99, Foo 1.2 (f12)], {Foo 1.4 (f14): VersionConflict(...)})
  205. But if you disallow fallbacks, the failed plugin will be skipped instead of
  206. trying older versions::
  207. >>> ws.find_plugins(plugins, fallback=False)
  208. ([JustATest 0.99], {Foo 1.4 (f14): VersionConflict(...)})
  209. Platform Compatibility Rules
  210. ----------------------------
  211. On the Mac, there are potential compatibility issues for modules compiled
  212. on newer versions of macOS than what the user is running. Additionally,
  213. macOS will soon have two platforms to contend with: Intel and PowerPC.
  214. Basic equality works as on other platforms::
  215. >>> from pkg_resources import compatible_platforms as cp
  216. >>> reqd = 'macosx-10.4-ppc'
  217. >>> cp(reqd, reqd)
  218. True
  219. >>> cp("win32", reqd)
  220. False
  221. Distributions made on other machine types are not compatible::
  222. >>> cp("macosx-10.4-i386", reqd)
  223. False
  224. Distributions made on earlier versions of the OS are compatible, as
  225. long as they are from the same top-level version. The patchlevel version
  226. number does not matter::
  227. >>> cp("macosx-10.4-ppc", reqd)
  228. True
  229. >>> cp("macosx-10.3-ppc", reqd)
  230. True
  231. >>> cp("macosx-10.5-ppc", reqd)
  232. False
  233. >>> cp("macosx-9.5-ppc", reqd)
  234. False
  235. Backwards compatibility for packages made via earlier versions of
  236. setuptools is provided as well::
  237. >>> cp("darwin-8.2.0-Power_Macintosh", reqd)
  238. True
  239. >>> cp("darwin-7.2.0-Power_Macintosh", reqd)
  240. True
  241. >>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc")
  242. False
  243. Environment Markers
  244. -------------------
  245. >>> from pkg_resources import invalid_marker as im, evaluate_marker as em
  246. >>> import os
  247. >>> print(im("sys_platform"))
  248. Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
  249. sys_platform
  250. ^
  251. >>> print(im("sys_platform=="))
  252. Expected a marker variable or quoted string
  253. sys_platform==
  254. ^
  255. >>> print(im("sys_platform=='win32'"))
  256. False
  257. >>> print(im("sys=='x'"))
  258. Expected a marker variable or quoted string
  259. sys=='x'
  260. ^
  261. >>> print(im("(extra)"))
  262. Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
  263. (extra)
  264. ^
  265. >>> print(im("(extra"))
  266. Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
  267. (extra
  268. ^
  269. >>> print(im("os.open('foo')=='y'"))
  270. Expected a marker variable or quoted string
  271. os.open('foo')=='y'
  272. ^
  273. >>> print(im("'x'=='y' and os.open('foo')=='y'")) # no short-circuit!
  274. Expected a marker variable or quoted string
  275. 'x'=='y' and os.open('foo')=='y'
  276. ^
  277. >>> print(im("'x'=='x' or os.open('foo')=='y'")) # no short-circuit!
  278. Expected a marker variable or quoted string
  279. 'x'=='x' or os.open('foo')=='y'
  280. ^
  281. >>> print(im("r'x'=='x'"))
  282. Expected a marker variable or quoted string
  283. r'x'=='x'
  284. ^
  285. >>> print(im("'''x'''=='x'"))
  286. Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
  287. '''x'''=='x'
  288. ^
  289. >>> print(im('"""x"""=="x"'))
  290. Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
  291. """x"""=="x"
  292. ^
  293. >>> print(im(r"x\n=='x'"))
  294. Expected a marker variable or quoted string
  295. x\n=='x'
  296. ^
  297. >>> print(im("os.open=='y'"))
  298. Expected a marker variable or quoted string
  299. os.open=='y'
  300. ^
  301. >>> em("sys_platform=='win32'") == (sys.platform=='win32')
  302. True
  303. >>> em("python_version >= '2.7'")
  304. True
  305. >>> em("python_version > '2.6'")
  306. True
  307. >>> im("implementation_name=='cpython'")
  308. False
  309. >>> im("platform_python_implementation=='CPython'")
  310. False
  311. >>> im("implementation_version=='3.5.1'")
  312. False