Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hhhrrrttt222111
GitHub Repository: hhhrrrttt222111/Dorkify
Path: blob/master/venv/Lib/site-packages/pkg_resources/__init__.py
811 views
1
# coding: utf-8
2
"""
3
Package resource API
4
--------------------
5
6
A resource is a logical file contained within a package, or a logical
7
subdirectory thereof. The package resource API expects resource names
8
to have their path parts separated with ``/``, *not* whatever the local
9
path separator is. Do not use os.path operations to manipulate resource
10
names being passed into the API.
11
12
The package resource API is designed to work with normal filesystem packages,
13
.egg files, and unpacked .egg files. It can also work in a limited way with
14
.zip files and with custom PEP 302 loaders that support the ``get_data()``
15
method.
16
"""
17
18
from __future__ import absolute_import
19
20
import sys
21
import os
22
import io
23
import time
24
import re
25
import types
26
import zipfile
27
import zipimport
28
import warnings
29
import stat
30
import functools
31
import pkgutil
32
import operator
33
import platform
34
import collections
35
import plistlib
36
import email.parser
37
import errno
38
import tempfile
39
import textwrap
40
import itertools
41
import inspect
42
import ntpath
43
import posixpath
44
from pkgutil import get_importer
45
46
try:
47
import _imp
48
except ImportError:
49
# Python 3.2 compatibility
50
import imp as _imp
51
52
try:
53
FileExistsError
54
except NameError:
55
FileExistsError = OSError
56
57
from pkg_resources.extern import six
58
from pkg_resources.extern.six.moves import map, filter
59
60
# capture these to bypass sandboxing
61
from os import utime
62
try:
63
from os import mkdir, rename, unlink
64
WRITE_SUPPORT = True
65
except ImportError:
66
# no write support, probably under GAE
67
WRITE_SUPPORT = False
68
69
from os import open as os_open
70
from os.path import isdir, split
71
72
try:
73
import importlib.machinery as importlib_machinery
74
# access attribute to force import under delayed import mechanisms.
75
importlib_machinery.__name__
76
except ImportError:
77
importlib_machinery = None
78
79
from pkg_resources.extern import appdirs
80
from pkg_resources.extern import packaging
81
__import__('pkg_resources.extern.packaging.version')
82
__import__('pkg_resources.extern.packaging.specifiers')
83
__import__('pkg_resources.extern.packaging.requirements')
84
__import__('pkg_resources.extern.packaging.markers')
85
__import__('pkg_resources.py2_warn')
86
87
88
__metaclass__ = type
89
90
91
if (3, 0) < sys.version_info < (3, 5):
92
raise RuntimeError("Python 3.5 or later is required")
93
94
if six.PY2:
95
# Those builtin exceptions are only defined in Python 3
96
PermissionError = None
97
NotADirectoryError = None
98
99
# declare some globals that will be defined later to
100
# satisfy the linters.
101
require = None
102
working_set = None
103
add_activation_listener = None
104
resources_stream = None
105
cleanup_resources = None
106
resource_dir = None
107
resource_stream = None
108
set_extraction_path = None
109
resource_isdir = None
110
resource_string = None
111
iter_entry_points = None
112
resource_listdir = None
113
resource_filename = None
114
resource_exists = None
115
_distribution_finders = None
116
_namespace_handlers = None
117
_namespace_packages = None
118
119
120
class PEP440Warning(RuntimeWarning):
121
"""
122
Used when there is an issue with a version or specifier not complying with
123
PEP 440.
124
"""
125
126
127
def parse_version(v):
128
try:
129
return packaging.version.Version(v)
130
except packaging.version.InvalidVersion:
131
return packaging.version.LegacyVersion(v)
132
133
134
_state_vars = {}
135
136
137
def _declare_state(vartype, **kw):
138
globals().update(kw)
139
_state_vars.update(dict.fromkeys(kw, vartype))
140
141
142
def __getstate__():
143
state = {}
144
g = globals()
145
for k, v in _state_vars.items():
146
state[k] = g['_sget_' + v](g[k])
147
return state
148
149
150
def __setstate__(state):
151
g = globals()
152
for k, v in state.items():
153
g['_sset_' + _state_vars[k]](k, g[k], v)
154
return state
155
156
157
def _sget_dict(val):
158
return val.copy()
159
160
161
def _sset_dict(key, ob, state):
162
ob.clear()
163
ob.update(state)
164
165
166
def _sget_object(val):
167
return val.__getstate__()
168
169
170
def _sset_object(key, ob, state):
171
ob.__setstate__(state)
172
173
174
_sget_none = _sset_none = lambda *args: None
175
176
177
def get_supported_platform():
178
"""Return this platform's maximum compatible version.
179
180
distutils.util.get_platform() normally reports the minimum version
181
of macOS that would be required to *use* extensions produced by
182
distutils. But what we want when checking compatibility is to know the
183
version of macOS that we are *running*. To allow usage of packages that
184
explicitly require a newer version of macOS, we must also know the
185
current version of the OS.
186
187
If this condition occurs for any other platform with a version in its
188
platform strings, this function should be extended accordingly.
189
"""
190
plat = get_build_platform()
191
m = macosVersionString.match(plat)
192
if m is not None and sys.platform == "darwin":
193
try:
194
plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))
195
except ValueError:
196
# not macOS
197
pass
198
return plat
199
200
201
__all__ = [
202
# Basic resource access and distribution/entry point discovery
203
'require', 'run_script', 'get_provider', 'get_distribution',
204
'load_entry_point', 'get_entry_map', 'get_entry_info',
205
'iter_entry_points',
206
'resource_string', 'resource_stream', 'resource_filename',
207
'resource_listdir', 'resource_exists', 'resource_isdir',
208
209
# Environmental control
210
'declare_namespace', 'working_set', 'add_activation_listener',
211
'find_distributions', 'set_extraction_path', 'cleanup_resources',
212
'get_default_cache',
213
214
# Primary implementation classes
215
'Environment', 'WorkingSet', 'ResourceManager',
216
'Distribution', 'Requirement', 'EntryPoint',
217
218
# Exceptions
219
'ResolutionError', 'VersionConflict', 'DistributionNotFound',
220
'UnknownExtra', 'ExtractionError',
221
222
# Warnings
223
'PEP440Warning',
224
225
# Parsing functions and string utilities
226
'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
227
'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
228
'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker',
229
230
# filesystem utilities
231
'ensure_directory', 'normalize_path',
232
233
# Distribution "precedence" constants
234
'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST',
235
236
# "Provider" interfaces, implementations, and registration/lookup APIs
237
'IMetadataProvider', 'IResourceProvider', 'FileMetadata',
238
'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider',
239
'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider',
240
'register_finder', 'register_namespace_handler', 'register_loader_type',
241
'fixup_namespace_packages', 'get_importer',
242
243
# Warnings
244
'PkgResourcesDeprecationWarning',
245
246
# Deprecated/backward compatibility only
247
'run_main', 'AvailableDistributions',
248
]
249
250
251
class ResolutionError(Exception):
252
"""Abstract base for dependency resolution errors"""
253
254
def __repr__(self):
255
return self.__class__.__name__ + repr(self.args)
256
257
258
class VersionConflict(ResolutionError):
259
"""
260
An already-installed version conflicts with the requested version.
261
262
Should be initialized with the installed Distribution and the requested
263
Requirement.
264
"""
265
266
_template = "{self.dist} is installed but {self.req} is required"
267
268
@property
269
def dist(self):
270
return self.args[0]
271
272
@property
273
def req(self):
274
return self.args[1]
275
276
def report(self):
277
return self._template.format(**locals())
278
279
def with_context(self, required_by):
280
"""
281
If required_by is non-empty, return a version of self that is a
282
ContextualVersionConflict.
283
"""
284
if not required_by:
285
return self
286
args = self.args + (required_by,)
287
return ContextualVersionConflict(*args)
288
289
290
class ContextualVersionConflict(VersionConflict):
291
"""
292
A VersionConflict that accepts a third parameter, the set of the
293
requirements that required the installed Distribution.
294
"""
295
296
_template = VersionConflict._template + ' by {self.required_by}'
297
298
@property
299
def required_by(self):
300
return self.args[2]
301
302
303
class DistributionNotFound(ResolutionError):
304
"""A requested distribution was not found"""
305
306
_template = ("The '{self.req}' distribution was not found "
307
"and is required by {self.requirers_str}")
308
309
@property
310
def req(self):
311
return self.args[0]
312
313
@property
314
def requirers(self):
315
return self.args[1]
316
317
@property
318
def requirers_str(self):
319
if not self.requirers:
320
return 'the application'
321
return ', '.join(self.requirers)
322
323
def report(self):
324
return self._template.format(**locals())
325
326
def __str__(self):
327
return self.report()
328
329
330
class UnknownExtra(ResolutionError):
331
"""Distribution doesn't have an "extra feature" of the given name"""
332
333
334
_provider_factories = {}
335
336
PY_MAJOR = '{}.{}'.format(*sys.version_info)
337
EGG_DIST = 3
338
BINARY_DIST = 2
339
SOURCE_DIST = 1
340
CHECKOUT_DIST = 0
341
DEVELOP_DIST = -1
342
343
344
def register_loader_type(loader_type, provider_factory):
345
"""Register `provider_factory` to make providers for `loader_type`
346
347
`loader_type` is the type or class of a PEP 302 ``module.__loader__``,
348
and `provider_factory` is a function that, passed a *module* object,
349
returns an ``IResourceProvider`` for that module.
350
"""
351
_provider_factories[loader_type] = provider_factory
352
353
354
def get_provider(moduleOrReq):
355
"""Return an IResourceProvider for the named module or requirement"""
356
if isinstance(moduleOrReq, Requirement):
357
return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]
358
try:
359
module = sys.modules[moduleOrReq]
360
except KeyError:
361
__import__(moduleOrReq)
362
module = sys.modules[moduleOrReq]
363
loader = getattr(module, '__loader__', None)
364
return _find_adapter(_provider_factories, loader)(module)
365
366
367
def _macos_vers(_cache=[]):
368
if not _cache:
369
version = platform.mac_ver()[0]
370
# fallback for MacPorts
371
if version == '':
372
plist = '/System/Library/CoreServices/SystemVersion.plist'
373
if os.path.exists(plist):
374
if hasattr(plistlib, 'readPlist'):
375
plist_content = plistlib.readPlist(plist)
376
if 'ProductVersion' in plist_content:
377
version = plist_content['ProductVersion']
378
379
_cache.append(version.split('.'))
380
return _cache[0]
381
382
383
def _macos_arch(machine):
384
return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
385
386
387
def get_build_platform():
388
"""Return this platform's string for platform-specific distributions
389
390
XXX Currently this is the same as ``distutils.util.get_platform()``, but it
391
needs some hacks for Linux and macOS.
392
"""
393
from sysconfig import get_platform
394
395
plat = get_platform()
396
if sys.platform == "darwin" and not plat.startswith('macosx-'):
397
try:
398
version = _macos_vers()
399
machine = os.uname()[4].replace(" ", "_")
400
return "macosx-%d.%d-%s" % (
401
int(version[0]), int(version[1]),
402
_macos_arch(machine),
403
)
404
except ValueError:
405
# if someone is running a non-Mac darwin system, this will fall
406
# through to the default implementation
407
pass
408
return plat
409
410
411
macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
412
darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
413
# XXX backward compat
414
get_platform = get_build_platform
415
416
417
def compatible_platforms(provided, required):
418
"""Can code for the `provided` platform run on the `required` platform?
419
420
Returns true if either platform is ``None``, or the platforms are equal.
421
422
XXX Needs compatibility checks for Linux and other unixy OSes.
423
"""
424
if provided is None or required is None or provided == required:
425
# easy case
426
return True
427
428
# macOS special cases
429
reqMac = macosVersionString.match(required)
430
if reqMac:
431
provMac = macosVersionString.match(provided)
432
433
# is this a Mac package?
434
if not provMac:
435
# this is backwards compatibility for packages built before
436
# setuptools 0.6. All packages built after this point will
437
# use the new macOS designation.
438
provDarwin = darwinVersionString.match(provided)
439
if provDarwin:
440
dversion = int(provDarwin.group(1))
441
macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))
442
if dversion == 7 and macosversion >= "10.3" or \
443
dversion == 8 and macosversion >= "10.4":
444
return True
445
# egg isn't macOS or legacy darwin
446
return False
447
448
# are they the same major version and machine type?
449
if provMac.group(1) != reqMac.group(1) or \
450
provMac.group(3) != reqMac.group(3):
451
return False
452
453
# is the required OS major update >= the provided one?
454
if int(provMac.group(2)) > int(reqMac.group(2)):
455
return False
456
457
return True
458
459
# XXX Linux and other platforms' special cases should go here
460
return False
461
462
463
def run_script(dist_spec, script_name):
464
"""Locate distribution `dist_spec` and run its `script_name` script"""
465
ns = sys._getframe(1).f_globals
466
name = ns['__name__']
467
ns.clear()
468
ns['__name__'] = name
469
require(dist_spec)[0].run_script(script_name, ns)
470
471
472
# backward compatibility
473
run_main = run_script
474
475
476
def get_distribution(dist):
477
"""Return a current distribution object for a Requirement or string"""
478
if isinstance(dist, six.string_types):
479
dist = Requirement.parse(dist)
480
if isinstance(dist, Requirement):
481
dist = get_provider(dist)
482
if not isinstance(dist, Distribution):
483
raise TypeError("Expected string, Requirement, or Distribution", dist)
484
return dist
485
486
487
def load_entry_point(dist, group, name):
488
"""Return `name` entry point of `group` for `dist` or raise ImportError"""
489
return get_distribution(dist).load_entry_point(group, name)
490
491
492
def get_entry_map(dist, group=None):
493
"""Return the entry point map for `group`, or the full entry map"""
494
return get_distribution(dist).get_entry_map(group)
495
496
497
def get_entry_info(dist, group, name):
498
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
499
return get_distribution(dist).get_entry_info(group, name)
500
501
502
class IMetadataProvider:
503
def has_metadata(name):
504
"""Does the package's distribution contain the named metadata?"""
505
506
def get_metadata(name):
507
"""The named metadata resource as a string"""
508
509
def get_metadata_lines(name):
510
"""Yield named metadata resource as list of non-blank non-comment lines
511
512
Leading and trailing whitespace is stripped from each line, and lines
513
with ``#`` as the first non-blank character are omitted."""
514
515
def metadata_isdir(name):
516
"""Is the named metadata a directory? (like ``os.path.isdir()``)"""
517
518
def metadata_listdir(name):
519
"""List of metadata names in the directory (like ``os.listdir()``)"""
520
521
def run_script(script_name, namespace):
522
"""Execute the named script in the supplied namespace dictionary"""
523
524
525
class IResourceProvider(IMetadataProvider):
526
"""An object that provides access to package resources"""
527
528
def get_resource_filename(manager, resource_name):
529
"""Return a true filesystem path for `resource_name`
530
531
`manager` must be an ``IResourceManager``"""
532
533
def get_resource_stream(manager, resource_name):
534
"""Return a readable file-like object for `resource_name`
535
536
`manager` must be an ``IResourceManager``"""
537
538
def get_resource_string(manager, resource_name):
539
"""Return a string containing the contents of `resource_name`
540
541
`manager` must be an ``IResourceManager``"""
542
543
def has_resource(resource_name):
544
"""Does the package contain the named resource?"""
545
546
def resource_isdir(resource_name):
547
"""Is the named resource a directory? (like ``os.path.isdir()``)"""
548
549
def resource_listdir(resource_name):
550
"""List of resource names in the directory (like ``os.listdir()``)"""
551
552
553
class WorkingSet:
554
"""A collection of active distributions on sys.path (or a similar list)"""
555
556
def __init__(self, entries=None):
557
"""Create working set from list of path entries (default=sys.path)"""
558
self.entries = []
559
self.entry_keys = {}
560
self.by_key = {}
561
self.callbacks = []
562
563
if entries is None:
564
entries = sys.path
565
566
for entry in entries:
567
self.add_entry(entry)
568
569
@classmethod
570
def _build_master(cls):
571
"""
572
Prepare the master working set.
573
"""
574
ws = cls()
575
try:
576
from __main__ import __requires__
577
except ImportError:
578
# The main program does not list any requirements
579
return ws
580
581
# ensure the requirements are met
582
try:
583
ws.require(__requires__)
584
except VersionConflict:
585
return cls._build_from_requirements(__requires__)
586
587
return ws
588
589
@classmethod
590
def _build_from_requirements(cls, req_spec):
591
"""
592
Build a working set from a requirement spec. Rewrites sys.path.
593
"""
594
# try it without defaults already on sys.path
595
# by starting with an empty path
596
ws = cls([])
597
reqs = parse_requirements(req_spec)
598
dists = ws.resolve(reqs, Environment())
599
for dist in dists:
600
ws.add(dist)
601
602
# add any missing entries from sys.path
603
for entry in sys.path:
604
if entry not in ws.entries:
605
ws.add_entry(entry)
606
607
# then copy back to sys.path
608
sys.path[:] = ws.entries
609
return ws
610
611
def add_entry(self, entry):
612
"""Add a path item to ``.entries``, finding any distributions on it
613
614
``find_distributions(entry, True)`` is used to find distributions
615
corresponding to the path entry, and they are added. `entry` is
616
always appended to ``.entries``, even if it is already present.
617
(This is because ``sys.path`` can contain the same value more than
618
once, and the ``.entries`` of the ``sys.path`` WorkingSet should always
619
equal ``sys.path``.)
620
"""
621
self.entry_keys.setdefault(entry, [])
622
self.entries.append(entry)
623
for dist in find_distributions(entry, True):
624
self.add(dist, entry, False)
625
626
def __contains__(self, dist):
627
"""True if `dist` is the active distribution for its project"""
628
return self.by_key.get(dist.key) == dist
629
630
def find(self, req):
631
"""Find a distribution matching requirement `req`
632
633
If there is an active distribution for the requested project, this
634
returns it as long as it meets the version requirement specified by
635
`req`. But, if there is an active distribution for the project and it
636
does *not* meet the `req` requirement, ``VersionConflict`` is raised.
637
If there is no active distribution for the requested project, ``None``
638
is returned.
639
"""
640
dist = self.by_key.get(req.key)
641
if dist is not None and dist not in req:
642
# XXX add more info
643
raise VersionConflict(dist, req)
644
return dist
645
646
def iter_entry_points(self, group, name=None):
647
"""Yield entry point objects from `group` matching `name`
648
649
If `name` is None, yields all entry points in `group` from all
650
distributions in the working set, otherwise only ones matching
651
both `group` and `name` are yielded (in distribution order).
652
"""
653
return (
654
entry
655
for dist in self
656
for entry in dist.get_entry_map(group).values()
657
if name is None or name == entry.name
658
)
659
660
def run_script(self, requires, script_name):
661
"""Locate distribution for `requires` and run `script_name` script"""
662
ns = sys._getframe(1).f_globals
663
name = ns['__name__']
664
ns.clear()
665
ns['__name__'] = name
666
self.require(requires)[0].run_script(script_name, ns)
667
668
def __iter__(self):
669
"""Yield distributions for non-duplicate projects in the working set
670
671
The yield order is the order in which the items' path entries were
672
added to the working set.
673
"""
674
seen = {}
675
for item in self.entries:
676
if item not in self.entry_keys:
677
# workaround a cache issue
678
continue
679
680
for key in self.entry_keys[item]:
681
if key not in seen:
682
seen[key] = 1
683
yield self.by_key[key]
684
685
def add(self, dist, entry=None, insert=True, replace=False):
686
"""Add `dist` to working set, associated with `entry`
687
688
If `entry` is unspecified, it defaults to the ``.location`` of `dist`.
689
On exit from this routine, `entry` is added to the end of the working
690
set's ``.entries`` (if it wasn't already present).
691
692
`dist` is only added to the working set if it's for a project that
693
doesn't already have a distribution in the set, unless `replace=True`.
694
If it's added, any callbacks registered with the ``subscribe()`` method
695
will be called.
696
"""
697
if insert:
698
dist.insert_on(self.entries, entry, replace=replace)
699
700
if entry is None:
701
entry = dist.location
702
keys = self.entry_keys.setdefault(entry, [])
703
keys2 = self.entry_keys.setdefault(dist.location, [])
704
if not replace and dist.key in self.by_key:
705
# ignore hidden distros
706
return
707
708
self.by_key[dist.key] = dist
709
if dist.key not in keys:
710
keys.append(dist.key)
711
if dist.key not in keys2:
712
keys2.append(dist.key)
713
self._added_new(dist)
714
715
def resolve(self, requirements, env=None, installer=None,
716
replace_conflicting=False, extras=None):
717
"""List all distributions needed to (recursively) meet `requirements`
718
719
`requirements` must be a sequence of ``Requirement`` objects. `env`,
720
if supplied, should be an ``Environment`` instance. If
721
not supplied, it defaults to all distributions available within any
722
entry or distribution in the working set. `installer`, if supplied,
723
will be invoked with each requirement that cannot be met by an
724
already-installed distribution; it should return a ``Distribution`` or
725
``None``.
726
727
Unless `replace_conflicting=True`, raises a VersionConflict exception
728
if
729
any requirements are found on the path that have the correct name but
730
the wrong version. Otherwise, if an `installer` is supplied it will be
731
invoked to obtain the correct version of the requirement and activate
732
it.
733
734
`extras` is a list of the extras to be used with these requirements.
735
This is important because extra requirements may look like `my_req;
736
extra = "my_extra"`, which would otherwise be interpreted as a purely
737
optional requirement. Instead, we want to be able to assert that these
738
requirements are truly required.
739
"""
740
741
# set up the stack
742
requirements = list(requirements)[::-1]
743
# set of processed requirements
744
processed = {}
745
# key -> dist
746
best = {}
747
to_activate = []
748
749
req_extras = _ReqExtras()
750
751
# Mapping of requirement to set of distributions that required it;
752
# useful for reporting info about conflicts.
753
required_by = collections.defaultdict(set)
754
755
while requirements:
756
# process dependencies breadth-first
757
req = requirements.pop(0)
758
if req in processed:
759
# Ignore cyclic or redundant dependencies
760
continue
761
762
if not req_extras.markers_pass(req, extras):
763
continue
764
765
dist = best.get(req.key)
766
if dist is None:
767
# Find the best distribution and add it to the map
768
dist = self.by_key.get(req.key)
769
if dist is None or (dist not in req and replace_conflicting):
770
ws = self
771
if env is None:
772
if dist is None:
773
env = Environment(self.entries)
774
else:
775
# Use an empty environment and workingset to avoid
776
# any further conflicts with the conflicting
777
# distribution
778
env = Environment([])
779
ws = WorkingSet([])
780
dist = best[req.key] = env.best_match(
781
req, ws, installer,
782
replace_conflicting=replace_conflicting
783
)
784
if dist is None:
785
requirers = required_by.get(req, None)
786
raise DistributionNotFound(req, requirers)
787
to_activate.append(dist)
788
if dist not in req:
789
# Oops, the "best" so far conflicts with a dependency
790
dependent_req = required_by[req]
791
raise VersionConflict(dist, req).with_context(dependent_req)
792
793
# push the new requirements onto the stack
794
new_requirements = dist.requires(req.extras)[::-1]
795
requirements.extend(new_requirements)
796
797
# Register the new requirements needed by req
798
for new_requirement in new_requirements:
799
required_by[new_requirement].add(req.project_name)
800
req_extras[new_requirement] = req.extras
801
802
processed[req] = True
803
804
# return list of distros to activate
805
return to_activate
806
807
def find_plugins(
808
self, plugin_env, full_env=None, installer=None, fallback=True):
809
"""Find all activatable distributions in `plugin_env`
810
811
Example usage::
812
813
distributions, errors = working_set.find_plugins(
814
Environment(plugin_dirlist)
815
)
816
# add plugins+libs to sys.path
817
map(working_set.add, distributions)
818
# display errors
819
print('Could not load', errors)
820
821
The `plugin_env` should be an ``Environment`` instance that contains
822
only distributions that are in the project's "plugin directory" or
823
directories. The `full_env`, if supplied, should be an ``Environment``
824
contains all currently-available distributions. If `full_env` is not
825
supplied, one is created automatically from the ``WorkingSet`` this
826
method is called on, which will typically mean that every directory on
827
``sys.path`` will be scanned for distributions.
828
829
`installer` is a standard installer callback as used by the
830
``resolve()`` method. The `fallback` flag indicates whether we should
831
attempt to resolve older versions of a plugin if the newest version
832
cannot be resolved.
833
834
This method returns a 2-tuple: (`distributions`, `error_info`), where
835
`distributions` is a list of the distributions found in `plugin_env`
836
that were loadable, along with any other distributions that are needed
837
to resolve their dependencies. `error_info` is a dictionary mapping
838
unloadable plugin distributions to an exception instance describing the
839
error that occurred. Usually this will be a ``DistributionNotFound`` or
840
``VersionConflict`` instance.
841
"""
842
843
plugin_projects = list(plugin_env)
844
# scan project names in alphabetic order
845
plugin_projects.sort()
846
847
error_info = {}
848
distributions = {}
849
850
if full_env is None:
851
env = Environment(self.entries)
852
env += plugin_env
853
else:
854
env = full_env + plugin_env
855
856
shadow_set = self.__class__([])
857
# put all our entries in shadow_set
858
list(map(shadow_set.add, self))
859
860
for project_name in plugin_projects:
861
862
for dist in plugin_env[project_name]:
863
864
req = [dist.as_requirement()]
865
866
try:
867
resolvees = shadow_set.resolve(req, env, installer)
868
869
except ResolutionError as v:
870
# save error info
871
error_info[dist] = v
872
if fallback:
873
# try the next older version of project
874
continue
875
else:
876
# give up on this project, keep going
877
break
878
879
else:
880
list(map(shadow_set.add, resolvees))
881
distributions.update(dict.fromkeys(resolvees))
882
883
# success, no need to try any more versions of this project
884
break
885
886
distributions = list(distributions)
887
distributions.sort()
888
889
return distributions, error_info
890
891
def require(self, *requirements):
892
"""Ensure that distributions matching `requirements` are activated
893
894
`requirements` must be a string or a (possibly-nested) sequence
895
thereof, specifying the distributions and versions required. The
896
return value is a sequence of the distributions that needed to be
897
activated to fulfill the requirements; all relevant distributions are
898
included, even if they were already activated in this working set.
899
"""
900
needed = self.resolve(parse_requirements(requirements))
901
902
for dist in needed:
903
self.add(dist)
904
905
return needed
906
907
def subscribe(self, callback, existing=True):
908
"""Invoke `callback` for all distributions
909
910
If `existing=True` (default),
911
call on all existing ones, as well.
912
"""
913
if callback in self.callbacks:
914
return
915
self.callbacks.append(callback)
916
if not existing:
917
return
918
for dist in self:
919
callback(dist)
920
921
def _added_new(self, dist):
922
for callback in self.callbacks:
923
callback(dist)
924
925
def __getstate__(self):
926
return (
927
self.entries[:], self.entry_keys.copy(), self.by_key.copy(),
928
self.callbacks[:]
929
)
930
931
def __setstate__(self, e_k_b_c):
932
entries, keys, by_key, callbacks = e_k_b_c
933
self.entries = entries[:]
934
self.entry_keys = keys.copy()
935
self.by_key = by_key.copy()
936
self.callbacks = callbacks[:]
937
938
939
class _ReqExtras(dict):
940
"""
941
Map each requirement to the extras that demanded it.
942
"""
943
944
def markers_pass(self, req, extras=None):
945
"""
946
Evaluate markers for req against each extra that
947
demanded it.
948
949
Return False if the req has a marker and fails
950
evaluation. Otherwise, return True.
951
"""
952
extra_evals = (
953
req.marker.evaluate({'extra': extra})
954
for extra in self.get(req, ()) + (extras or (None,))
955
)
956
return not req.marker or any(extra_evals)
957
958
959
class Environment:
960
"""Searchable snapshot of distributions on a search path"""
961
962
def __init__(
963
self, search_path=None, platform=get_supported_platform(),
964
python=PY_MAJOR):
965
"""Snapshot distributions available on a search path
966
967
Any distributions found on `search_path` are added to the environment.
968
`search_path` should be a sequence of ``sys.path`` items. If not
969
supplied, ``sys.path`` is used.
970
971
`platform` is an optional string specifying the name of the platform
972
that platform-specific distributions must be compatible with. If
973
unspecified, it defaults to the current platform. `python` is an
974
optional string naming the desired version of Python (e.g. ``'3.6'``);
975
it defaults to the current version.
976
977
You may explicitly set `platform` (and/or `python`) to ``None`` if you
978
wish to map *all* distributions, not just those compatible with the
979
running platform or Python version.
980
"""
981
self._distmap = {}
982
self.platform = platform
983
self.python = python
984
self.scan(search_path)
985
986
def can_add(self, dist):
987
"""Is distribution `dist` acceptable for this environment?
988
989
The distribution must match the platform and python version
990
requirements specified when this environment was created, or False
991
is returned.
992
"""
993
py_compat = (
994
self.python is None
995
or dist.py_version is None
996
or dist.py_version == self.python
997
)
998
return py_compat and compatible_platforms(dist.platform, self.platform)
999
1000
def remove(self, dist):
1001
"""Remove `dist` from the environment"""
1002
self._distmap[dist.key].remove(dist)
1003
1004
def scan(self, search_path=None):
1005
"""Scan `search_path` for distributions usable in this environment
1006
1007
Any distributions found are added to the environment.
1008
`search_path` should be a sequence of ``sys.path`` items. If not
1009
supplied, ``sys.path`` is used. Only distributions conforming to
1010
the platform/python version defined at initialization are added.
1011
"""
1012
if search_path is None:
1013
search_path = sys.path
1014
1015
for item in search_path:
1016
for dist in find_distributions(item):
1017
self.add(dist)
1018
1019
def __getitem__(self, project_name):
1020
"""Return a newest-to-oldest list of distributions for `project_name`
1021
1022
Uses case-insensitive `project_name` comparison, assuming all the
1023
project's distributions use their project's name converted to all
1024
lowercase as their key.
1025
1026
"""
1027
distribution_key = project_name.lower()
1028
return self._distmap.get(distribution_key, [])
1029
1030
def add(self, dist):
1031
"""Add `dist` if we ``can_add()`` it and it has not already been added
1032
"""
1033
if self.can_add(dist) and dist.has_version():
1034
dists = self._distmap.setdefault(dist.key, [])
1035
if dist not in dists:
1036
dists.append(dist)
1037
dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)
1038
1039
def best_match(
1040
self, req, working_set, installer=None, replace_conflicting=False):
1041
"""Find distribution best matching `req` and usable on `working_set`
1042
1043
This calls the ``find(req)`` method of the `working_set` to see if a
1044
suitable distribution is already active. (This may raise
1045
``VersionConflict`` if an unsuitable version of the project is already
1046
active in the specified `working_set`.) If a suitable distribution
1047
isn't active, this method returns the newest distribution in the
1048
environment that meets the ``Requirement`` in `req`. If no suitable
1049
distribution is found, and `installer` is supplied, then the result of
1050
calling the environment's ``obtain(req, installer)`` method will be
1051
returned.
1052
"""
1053
try:
1054
dist = working_set.find(req)
1055
except VersionConflict:
1056
if not replace_conflicting:
1057
raise
1058
dist = None
1059
if dist is not None:
1060
return dist
1061
for dist in self[req.key]:
1062
if dist in req:
1063
return dist
1064
# try to download/install
1065
return self.obtain(req, installer)
1066
1067
def obtain(self, requirement, installer=None):
1068
"""Obtain a distribution matching `requirement` (e.g. via download)
1069
1070
Obtain a distro that matches requirement (e.g. via download). In the
1071
base ``Environment`` class, this routine just returns
1072
``installer(requirement)``, unless `installer` is None, in which case
1073
None is returned instead. This method is a hook that allows subclasses
1074
to attempt other ways of obtaining a distribution before falling back
1075
to the `installer` argument."""
1076
if installer is not None:
1077
return installer(requirement)
1078
1079
def __iter__(self):
1080
"""Yield the unique project names of the available distributions"""
1081
for key in self._distmap.keys():
1082
if self[key]:
1083
yield key
1084
1085
def __iadd__(self, other):
1086
"""In-place addition of a distribution or environment"""
1087
if isinstance(other, Distribution):
1088
self.add(other)
1089
elif isinstance(other, Environment):
1090
for project in other:
1091
for dist in other[project]:
1092
self.add(dist)
1093
else:
1094
raise TypeError("Can't add %r to environment" % (other,))
1095
return self
1096
1097
def __add__(self, other):
1098
"""Add an environment or distribution to an environment"""
1099
new = self.__class__([], platform=None, python=None)
1100
for env in self, other:
1101
new += env
1102
return new
1103
1104
1105
# XXX backward compatibility
1106
AvailableDistributions = Environment
1107
1108
1109
class ExtractionError(RuntimeError):
1110
"""An error occurred extracting a resource
1111
1112
The following attributes are available from instances of this exception:
1113
1114
manager
1115
The resource manager that raised this exception
1116
1117
cache_path
1118
The base directory for resource extraction
1119
1120
original_error
1121
The exception instance that caused extraction to fail
1122
"""
1123
1124
1125
class ResourceManager:
1126
"""Manage resource extraction and packages"""
1127
extraction_path = None
1128
1129
def __init__(self):
1130
self.cached_files = {}
1131
1132
def resource_exists(self, package_or_requirement, resource_name):
1133
"""Does the named resource exist?"""
1134
return get_provider(package_or_requirement).has_resource(resource_name)
1135
1136
def resource_isdir(self, package_or_requirement, resource_name):
1137
"""Is the named resource an existing directory?"""
1138
return get_provider(package_or_requirement).resource_isdir(
1139
resource_name
1140
)
1141
1142
def resource_filename(self, package_or_requirement, resource_name):
1143
"""Return a true filesystem path for specified resource"""
1144
return get_provider(package_or_requirement).get_resource_filename(
1145
self, resource_name
1146
)
1147
1148
def resource_stream(self, package_or_requirement, resource_name):
1149
"""Return a readable file-like object for specified resource"""
1150
return get_provider(package_or_requirement).get_resource_stream(
1151
self, resource_name
1152
)
1153
1154
def resource_string(self, package_or_requirement, resource_name):
1155
"""Return specified resource as a string"""
1156
return get_provider(package_or_requirement).get_resource_string(
1157
self, resource_name
1158
)
1159
1160
def resource_listdir(self, package_or_requirement, resource_name):
1161
"""List the contents of the named resource directory"""
1162
return get_provider(package_or_requirement).resource_listdir(
1163
resource_name
1164
)
1165
1166
def extraction_error(self):
1167
"""Give an error message for problems extracting file(s)"""
1168
1169
old_exc = sys.exc_info()[1]
1170
cache_path = self.extraction_path or get_default_cache()
1171
1172
tmpl = textwrap.dedent("""
1173
Can't extract file(s) to egg cache
1174
1175
The following error occurred while trying to extract file(s)
1176
to the Python egg cache:
1177
1178
{old_exc}
1179
1180
The Python egg cache directory is currently set to:
1181
1182
{cache_path}
1183
1184
Perhaps your account does not have write access to this directory?
1185
You can change the cache directory by setting the PYTHON_EGG_CACHE
1186
environment variable to point to an accessible directory.
1187
""").lstrip()
1188
err = ExtractionError(tmpl.format(**locals()))
1189
err.manager = self
1190
err.cache_path = cache_path
1191
err.original_error = old_exc
1192
raise err
1193
1194
def get_cache_path(self, archive_name, names=()):
1195
"""Return absolute location in cache for `archive_name` and `names`
1196
1197
The parent directory of the resulting path will be created if it does
1198
not already exist. `archive_name` should be the base filename of the
1199
enclosing egg (which may not be the name of the enclosing zipfile!),
1200
including its ".egg" extension. `names`, if provided, should be a
1201
sequence of path name parts "under" the egg's extraction location.
1202
1203
This method should only be called by resource providers that need to
1204
obtain an extraction location, and only for names they intend to
1205
extract, as it tracks the generated names for possible cleanup later.
1206
"""
1207
extract_path = self.extraction_path or get_default_cache()
1208
target_path = os.path.join(extract_path, archive_name + '-tmp', *names)
1209
try:
1210
_bypass_ensure_directory(target_path)
1211
except Exception:
1212
self.extraction_error()
1213
1214
self._warn_unsafe_extraction_path(extract_path)
1215
1216
self.cached_files[target_path] = 1
1217
return target_path
1218
1219
@staticmethod
1220
def _warn_unsafe_extraction_path(path):
1221
"""
1222
If the default extraction path is overridden and set to an insecure
1223
location, such as /tmp, it opens up an opportunity for an attacker to
1224
replace an extracted file with an unauthorized payload. Warn the user
1225
if a known insecure location is used.
1226
1227
See Distribute #375 for more details.
1228
"""
1229
if os.name == 'nt' and not path.startswith(os.environ['windir']):
1230
# On Windows, permissions are generally restrictive by default
1231
# and temp directories are not writable by other users, so
1232
# bypass the warning.
1233
return
1234
mode = os.stat(path).st_mode
1235
if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
1236
msg = (
1237
"Extraction path is writable by group/others "
1238
"and vulnerable to attack when "
1239
"used with get_resource_filename ({path}). "
1240
"Consider a more secure "
1241
"location (set with .set_extraction_path or the "
1242
"PYTHON_EGG_CACHE environment variable)."
1243
).format(**locals())
1244
warnings.warn(msg, UserWarning)
1245
1246
def postprocess(self, tempname, filename):
1247
"""Perform any platform-specific postprocessing of `tempname`
1248
1249
This is where Mac header rewrites should be done; other platforms don't
1250
have anything special they should do.
1251
1252
Resource providers should call this method ONLY after successfully
1253
extracting a compressed resource. They must NOT call it on resources
1254
that are already in the filesystem.
1255
1256
`tempname` is the current (temporary) name of the file, and `filename`
1257
is the name it will be renamed to by the caller after this routine
1258
returns.
1259
"""
1260
1261
if os.name == 'posix':
1262
# Make the resource executable
1263
mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777
1264
os.chmod(tempname, mode)
1265
1266
def set_extraction_path(self, path):
1267
"""Set the base path where resources will be extracted to, if needed.
1268
1269
If you do not call this routine before any extractions take place, the
1270
path defaults to the return value of ``get_default_cache()``. (Which
1271
is based on the ``PYTHON_EGG_CACHE`` environment variable, with various
1272
platform-specific fallbacks. See that routine's documentation for more
1273
details.)
1274
1275
Resources are extracted to subdirectories of this path based upon
1276
information given by the ``IResourceProvider``. You may set this to a
1277
temporary directory, but then you must call ``cleanup_resources()`` to
1278
delete the extracted files when done. There is no guarantee that
1279
``cleanup_resources()`` will be able to remove all extracted files.
1280
1281
(Note: you may not change the extraction path for a given resource
1282
manager once resources have been extracted, unless you first call
1283
``cleanup_resources()``.)
1284
"""
1285
if self.cached_files:
1286
raise ValueError(
1287
"Can't change extraction path, files already extracted"
1288
)
1289
1290
self.extraction_path = path
1291
1292
def cleanup_resources(self, force=False):
1293
"""
1294
Delete all extracted resource files and directories, returning a list
1295
of the file and directory names that could not be successfully removed.
1296
This function does not have any concurrency protection, so it should
1297
generally only be called when the extraction path is a temporary
1298
directory exclusive to a single process. This method is not
1299
automatically called; you must call it explicitly or register it as an
1300
``atexit`` function if you wish to ensure cleanup of a temporary
1301
directory used for extractions.
1302
"""
1303
# XXX
1304
1305
1306
def get_default_cache():
1307
"""
1308
Return the ``PYTHON_EGG_CACHE`` environment variable
1309
or a platform-relevant user cache dir for an app
1310
named "Python-Eggs".
1311
"""
1312
return (
1313
os.environ.get('PYTHON_EGG_CACHE')
1314
or appdirs.user_cache_dir(appname='Python-Eggs')
1315
)
1316
1317
1318
def safe_name(name):
1319
"""Convert an arbitrary string to a standard distribution name
1320
1321
Any runs of non-alphanumeric/. characters are replaced with a single '-'.
1322
"""
1323
return re.sub('[^A-Za-z0-9.]+', '-', name)
1324
1325
1326
def safe_version(version):
1327
"""
1328
Convert an arbitrary string to a standard version string
1329
"""
1330
try:
1331
# normalize the version
1332
return str(packaging.version.Version(version))
1333
except packaging.version.InvalidVersion:
1334
version = version.replace(' ', '.')
1335
return re.sub('[^A-Za-z0-9.]+', '-', version)
1336
1337
1338
def safe_extra(extra):
1339
"""Convert an arbitrary string to a standard 'extra' name
1340
1341
Any runs of non-alphanumeric characters are replaced with a single '_',
1342
and the result is always lowercased.
1343
"""
1344
return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower()
1345
1346
1347
def to_filename(name):
1348
"""Convert a project or version name to its filename-escaped form
1349
1350
Any '-' characters are currently replaced with '_'.
1351
"""
1352
return name.replace('-', '_')
1353
1354
1355
def invalid_marker(text):
1356
"""
1357
Validate text as a PEP 508 environment marker; return an exception
1358
if invalid or False otherwise.
1359
"""
1360
try:
1361
evaluate_marker(text)
1362
except SyntaxError as e:
1363
e.filename = None
1364
e.lineno = None
1365
return e
1366
return False
1367
1368
1369
def evaluate_marker(text, extra=None):
1370
"""
1371
Evaluate a PEP 508 environment marker.
1372
Return a boolean indicating the marker result in this environment.
1373
Raise SyntaxError if marker is invalid.
1374
1375
This implementation uses the 'pyparsing' module.
1376
"""
1377
try:
1378
marker = packaging.markers.Marker(text)
1379
return marker.evaluate()
1380
except packaging.markers.InvalidMarker as e:
1381
raise SyntaxError(e) from e
1382
1383
1384
class NullProvider:
1385
"""Try to implement resources and metadata for arbitrary PEP 302 loaders"""
1386
1387
egg_name = None
1388
egg_info = None
1389
loader = None
1390
1391
def __init__(self, module):
1392
self.loader = getattr(module, '__loader__', None)
1393
self.module_path = os.path.dirname(getattr(module, '__file__', ''))
1394
1395
def get_resource_filename(self, manager, resource_name):
1396
return self._fn(self.module_path, resource_name)
1397
1398
def get_resource_stream(self, manager, resource_name):
1399
return io.BytesIO(self.get_resource_string(manager, resource_name))
1400
1401
def get_resource_string(self, manager, resource_name):
1402
return self._get(self._fn(self.module_path, resource_name))
1403
1404
def has_resource(self, resource_name):
1405
return self._has(self._fn(self.module_path, resource_name))
1406
1407
def _get_metadata_path(self, name):
1408
return self._fn(self.egg_info, name)
1409
1410
def has_metadata(self, name):
1411
if not self.egg_info:
1412
return self.egg_info
1413
1414
path = self._get_metadata_path(name)
1415
return self._has(path)
1416
1417
def get_metadata(self, name):
1418
if not self.egg_info:
1419
return ""
1420
path = self._get_metadata_path(name)
1421
value = self._get(path)
1422
if six.PY2:
1423
return value
1424
try:
1425
return value.decode('utf-8')
1426
except UnicodeDecodeError as exc:
1427
# Include the path in the error message to simplify
1428
# troubleshooting, and without changing the exception type.
1429
exc.reason += ' in {} file at path: {}'.format(name, path)
1430
raise
1431
1432
def get_metadata_lines(self, name):
1433
return yield_lines(self.get_metadata(name))
1434
1435
def resource_isdir(self, resource_name):
1436
return self._isdir(self._fn(self.module_path, resource_name))
1437
1438
def metadata_isdir(self, name):
1439
return self.egg_info and self._isdir(self._fn(self.egg_info, name))
1440
1441
def resource_listdir(self, resource_name):
1442
return self._listdir(self._fn(self.module_path, resource_name))
1443
1444
def metadata_listdir(self, name):
1445
if self.egg_info:
1446
return self._listdir(self._fn(self.egg_info, name))
1447
return []
1448
1449
def run_script(self, script_name, namespace):
1450
script = 'scripts/' + script_name
1451
if not self.has_metadata(script):
1452
raise ResolutionError(
1453
"Script {script!r} not found in metadata at {self.egg_info!r}"
1454
.format(**locals()),
1455
)
1456
script_text = self.get_metadata(script).replace('\r\n', '\n')
1457
script_text = script_text.replace('\r', '\n')
1458
script_filename = self._fn(self.egg_info, script)
1459
namespace['__file__'] = script_filename
1460
if os.path.exists(script_filename):
1461
with open(script_filename) as fid:
1462
source = fid.read()
1463
code = compile(source, script_filename, 'exec')
1464
exec(code, namespace, namespace)
1465
else:
1466
from linecache import cache
1467
cache[script_filename] = (
1468
len(script_text), 0, script_text.split('\n'), script_filename
1469
)
1470
script_code = compile(script_text, script_filename, 'exec')
1471
exec(script_code, namespace, namespace)
1472
1473
def _has(self, path):
1474
raise NotImplementedError(
1475
"Can't perform this operation for unregistered loader type"
1476
)
1477
1478
def _isdir(self, path):
1479
raise NotImplementedError(
1480
"Can't perform this operation for unregistered loader type"
1481
)
1482
1483
def _listdir(self, path):
1484
raise NotImplementedError(
1485
"Can't perform this operation for unregistered loader type"
1486
)
1487
1488
def _fn(self, base, resource_name):
1489
self._validate_resource_path(resource_name)
1490
if resource_name:
1491
return os.path.join(base, *resource_name.split('/'))
1492
return base
1493
1494
@staticmethod
1495
def _validate_resource_path(path):
1496
"""
1497
Validate the resource paths according to the docs.
1498
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
1499
1500
>>> warned = getfixture('recwarn')
1501
>>> warnings.simplefilter('always')
1502
>>> vrp = NullProvider._validate_resource_path
1503
>>> vrp('foo/bar.txt')
1504
>>> bool(warned)
1505
False
1506
>>> vrp('../foo/bar.txt')
1507
>>> bool(warned)
1508
True
1509
>>> warned.clear()
1510
>>> vrp('/foo/bar.txt')
1511
>>> bool(warned)
1512
True
1513
>>> vrp('foo/../../bar.txt')
1514
>>> bool(warned)
1515
True
1516
>>> warned.clear()
1517
>>> vrp('foo/f../bar.txt')
1518
>>> bool(warned)
1519
False
1520
1521
Windows path separators are straight-up disallowed.
1522
>>> vrp(r'\\foo/bar.txt')
1523
Traceback (most recent call last):
1524
...
1525
ValueError: Use of .. or absolute path in a resource path \
1526
is not allowed.
1527
1528
>>> vrp(r'C:\\foo/bar.txt')
1529
Traceback (most recent call last):
1530
...
1531
ValueError: Use of .. or absolute path in a resource path \
1532
is not allowed.
1533
1534
Blank values are allowed
1535
1536
>>> vrp('')
1537
>>> bool(warned)
1538
False
1539
1540
Non-string values are not.
1541
1542
>>> vrp(None)
1543
Traceback (most recent call last):
1544
...
1545
AttributeError: ...
1546
"""
1547
invalid = (
1548
os.path.pardir in path.split(posixpath.sep) or
1549
posixpath.isabs(path) or
1550
ntpath.isabs(path)
1551
)
1552
if not invalid:
1553
return
1554
1555
msg = "Use of .. or absolute path in a resource path is not allowed."
1556
1557
# Aggressively disallow Windows absolute paths
1558
if ntpath.isabs(path) and not posixpath.isabs(path):
1559
raise ValueError(msg)
1560
1561
# for compatibility, warn; in future
1562
# raise ValueError(msg)
1563
warnings.warn(
1564
msg[:-1] + " and will raise exceptions in a future release.",
1565
DeprecationWarning,
1566
stacklevel=4,
1567
)
1568
1569
def _get(self, path):
1570
if hasattr(self.loader, 'get_data'):
1571
return self.loader.get_data(path)
1572
raise NotImplementedError(
1573
"Can't perform this operation for loaders without 'get_data()'"
1574
)
1575
1576
1577
register_loader_type(object, NullProvider)
1578
1579
1580
def _parents(path):
1581
"""
1582
yield all parents of path including path
1583
"""
1584
last = None
1585
while path != last:
1586
yield path
1587
last = path
1588
path, _ = os.path.split(path)
1589
1590
1591
class EggProvider(NullProvider):
1592
"""Provider based on a virtual filesystem"""
1593
1594
def __init__(self, module):
1595
NullProvider.__init__(self, module)
1596
self._setup_prefix()
1597
1598
def _setup_prefix(self):
1599
# Assume that metadata may be nested inside a "basket"
1600
# of multiple eggs and use module_path instead of .archive.
1601
eggs = filter(_is_egg_path, _parents(self.module_path))
1602
egg = next(eggs, None)
1603
egg and self._set_egg(egg)
1604
1605
def _set_egg(self, path):
1606
self.egg_name = os.path.basename(path)
1607
self.egg_info = os.path.join(path, 'EGG-INFO')
1608
self.egg_root = path
1609
1610
1611
class DefaultProvider(EggProvider):
1612
"""Provides access to package resources in the filesystem"""
1613
1614
def _has(self, path):
1615
return os.path.exists(path)
1616
1617
def _isdir(self, path):
1618
return os.path.isdir(path)
1619
1620
def _listdir(self, path):
1621
return os.listdir(path)
1622
1623
def get_resource_stream(self, manager, resource_name):
1624
return open(self._fn(self.module_path, resource_name), 'rb')
1625
1626
def _get(self, path):
1627
with open(path, 'rb') as stream:
1628
return stream.read()
1629
1630
@classmethod
1631
def _register(cls):
1632
loader_names = 'SourceFileLoader', 'SourcelessFileLoader',
1633
for name in loader_names:
1634
loader_cls = getattr(importlib_machinery, name, type(None))
1635
register_loader_type(loader_cls, cls)
1636
1637
1638
DefaultProvider._register()
1639
1640
1641
class EmptyProvider(NullProvider):
1642
"""Provider that returns nothing for all requests"""
1643
1644
module_path = None
1645
1646
_isdir = _has = lambda self, path: False
1647
1648
def _get(self, path):
1649
return ''
1650
1651
def _listdir(self, path):
1652
return []
1653
1654
def __init__(self):
1655
pass
1656
1657
1658
empty_provider = EmptyProvider()
1659
1660
1661
class ZipManifests(dict):
1662
"""
1663
zip manifest builder
1664
"""
1665
1666
@classmethod
1667
def build(cls, path):
1668
"""
1669
Build a dictionary similar to the zipimport directory
1670
caches, except instead of tuples, store ZipInfo objects.
1671
1672
Use a platform-specific path separator (os.sep) for the path keys
1673
for compatibility with pypy on Windows.
1674
"""
1675
with zipfile.ZipFile(path) as zfile:
1676
items = (
1677
(
1678
name.replace('/', os.sep),
1679
zfile.getinfo(name),
1680
)
1681
for name in zfile.namelist()
1682
)
1683
return dict(items)
1684
1685
load = build
1686
1687
1688
class MemoizedZipManifests(ZipManifests):
1689
"""
1690
Memoized zipfile manifests.
1691
"""
1692
manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime')
1693
1694
def load(self, path):
1695
"""
1696
Load a manifest at path or return a suitable manifest already loaded.
1697
"""
1698
path = os.path.normpath(path)
1699
mtime = os.stat(path).st_mtime
1700
1701
if path not in self or self[path].mtime != mtime:
1702
manifest = self.build(path)
1703
self[path] = self.manifest_mod(manifest, mtime)
1704
1705
return self[path].manifest
1706
1707
1708
class ZipProvider(EggProvider):
1709
"""Resource support for zips and eggs"""
1710
1711
eagers = None
1712
_zip_manifests = MemoizedZipManifests()
1713
1714
def __init__(self, module):
1715
EggProvider.__init__(self, module)
1716
self.zip_pre = self.loader.archive + os.sep
1717
1718
def _zipinfo_name(self, fspath):
1719
# Convert a virtual filename (full path to file) into a zipfile subpath
1720
# usable with the zipimport directory cache for our target archive
1721
fspath = fspath.rstrip(os.sep)
1722
if fspath == self.loader.archive:
1723
return ''
1724
if fspath.startswith(self.zip_pre):
1725
return fspath[len(self.zip_pre):]
1726
raise AssertionError(
1727
"%s is not a subpath of %s" % (fspath, self.zip_pre)
1728
)
1729
1730
def _parts(self, zip_path):
1731
# Convert a zipfile subpath into an egg-relative path part list.
1732
# pseudo-fs path
1733
fspath = self.zip_pre + zip_path
1734
if fspath.startswith(self.egg_root + os.sep):
1735
return fspath[len(self.egg_root) + 1:].split(os.sep)
1736
raise AssertionError(
1737
"%s is not a subpath of %s" % (fspath, self.egg_root)
1738
)
1739
1740
@property
1741
def zipinfo(self):
1742
return self._zip_manifests.load(self.loader.archive)
1743
1744
def get_resource_filename(self, manager, resource_name):
1745
if not self.egg_name:
1746
raise NotImplementedError(
1747
"resource_filename() only supported for .egg, not .zip"
1748
)
1749
# no need to lock for extraction, since we use temp names
1750
zip_path = self._resource_to_zip(resource_name)
1751
eagers = self._get_eager_resources()
1752
if '/'.join(self._parts(zip_path)) in eagers:
1753
for name in eagers:
1754
self._extract_resource(manager, self._eager_to_zip(name))
1755
return self._extract_resource(manager, zip_path)
1756
1757
@staticmethod
1758
def _get_date_and_size(zip_stat):
1759
size = zip_stat.file_size
1760
# ymdhms+wday, yday, dst
1761
date_time = zip_stat.date_time + (0, 0, -1)
1762
# 1980 offset already done
1763
timestamp = time.mktime(date_time)
1764
return timestamp, size
1765
1766
def _extract_resource(self, manager, zip_path):
1767
1768
if zip_path in self._index():
1769
for name in self._index()[zip_path]:
1770
last = self._extract_resource(
1771
manager, os.path.join(zip_path, name)
1772
)
1773
# return the extracted directory name
1774
return os.path.dirname(last)
1775
1776
timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])
1777
1778
if not WRITE_SUPPORT:
1779
raise IOError('"os.rename" and "os.unlink" are not supported '
1780
'on this platform')
1781
try:
1782
1783
real_path = manager.get_cache_path(
1784
self.egg_name, self._parts(zip_path)
1785
)
1786
1787
if self._is_current(real_path, zip_path):
1788
return real_path
1789
1790
outf, tmpnam = _mkstemp(
1791
".$extract",
1792
dir=os.path.dirname(real_path),
1793
)
1794
os.write(outf, self.loader.get_data(zip_path))
1795
os.close(outf)
1796
utime(tmpnam, (timestamp, timestamp))
1797
manager.postprocess(tmpnam, real_path)
1798
1799
try:
1800
rename(tmpnam, real_path)
1801
1802
except os.error:
1803
if os.path.isfile(real_path):
1804
if self._is_current(real_path, zip_path):
1805
# the file became current since it was checked above,
1806
# so proceed.
1807
return real_path
1808
# Windows, del old file and retry
1809
elif os.name == 'nt':
1810
unlink(real_path)
1811
rename(tmpnam, real_path)
1812
return real_path
1813
raise
1814
1815
except os.error:
1816
# report a user-friendly error
1817
manager.extraction_error()
1818
1819
return real_path
1820
1821
def _is_current(self, file_path, zip_path):
1822
"""
1823
Return True if the file_path is current for this zip_path
1824
"""
1825
timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])
1826
if not os.path.isfile(file_path):
1827
return False
1828
stat = os.stat(file_path)
1829
if stat.st_size != size or stat.st_mtime != timestamp:
1830
return False
1831
# check that the contents match
1832
zip_contents = self.loader.get_data(zip_path)
1833
with open(file_path, 'rb') as f:
1834
file_contents = f.read()
1835
return zip_contents == file_contents
1836
1837
def _get_eager_resources(self):
1838
if self.eagers is None:
1839
eagers = []
1840
for name in ('native_libs.txt', 'eager_resources.txt'):
1841
if self.has_metadata(name):
1842
eagers.extend(self.get_metadata_lines(name))
1843
self.eagers = eagers
1844
return self.eagers
1845
1846
def _index(self):
1847
try:
1848
return self._dirindex
1849
except AttributeError:
1850
ind = {}
1851
for path in self.zipinfo:
1852
parts = path.split(os.sep)
1853
while parts:
1854
parent = os.sep.join(parts[:-1])
1855
if parent in ind:
1856
ind[parent].append(parts[-1])
1857
break
1858
else:
1859
ind[parent] = [parts.pop()]
1860
self._dirindex = ind
1861
return ind
1862
1863
def _has(self, fspath):
1864
zip_path = self._zipinfo_name(fspath)
1865
return zip_path in self.zipinfo or zip_path in self._index()
1866
1867
def _isdir(self, fspath):
1868
return self._zipinfo_name(fspath) in self._index()
1869
1870
def _listdir(self, fspath):
1871
return list(self._index().get(self._zipinfo_name(fspath), ()))
1872
1873
def _eager_to_zip(self, resource_name):
1874
return self._zipinfo_name(self._fn(self.egg_root, resource_name))
1875
1876
def _resource_to_zip(self, resource_name):
1877
return self._zipinfo_name(self._fn(self.module_path, resource_name))
1878
1879
1880
register_loader_type(zipimport.zipimporter, ZipProvider)
1881
1882
1883
class FileMetadata(EmptyProvider):
1884
"""Metadata handler for standalone PKG-INFO files
1885
1886
Usage::
1887
1888
metadata = FileMetadata("/path/to/PKG-INFO")
1889
1890
This provider rejects all data and metadata requests except for PKG-INFO,
1891
which is treated as existing, and will be the contents of the file at
1892
the provided location.
1893
"""
1894
1895
def __init__(self, path):
1896
self.path = path
1897
1898
def _get_metadata_path(self, name):
1899
return self.path
1900
1901
def has_metadata(self, name):
1902
return name == 'PKG-INFO' and os.path.isfile(self.path)
1903
1904
def get_metadata(self, name):
1905
if name != 'PKG-INFO':
1906
raise KeyError("No metadata except PKG-INFO is available")
1907
1908
with io.open(self.path, encoding='utf-8', errors="replace") as f:
1909
metadata = f.read()
1910
self._warn_on_replacement(metadata)
1911
return metadata
1912
1913
def _warn_on_replacement(self, metadata):
1914
# Python 2.7 compat for: replacement_char = '�'
1915
replacement_char = b'\xef\xbf\xbd'.decode('utf-8')
1916
if replacement_char in metadata:
1917
tmpl = "{self.path} could not be properly decoded in UTF-8"
1918
msg = tmpl.format(**locals())
1919
warnings.warn(msg)
1920
1921
def get_metadata_lines(self, name):
1922
return yield_lines(self.get_metadata(name))
1923
1924
1925
class PathMetadata(DefaultProvider):
1926
"""Metadata provider for egg directories
1927
1928
Usage::
1929
1930
# Development eggs:
1931
1932
egg_info = "/path/to/PackageName.egg-info"
1933
base_dir = os.path.dirname(egg_info)
1934
metadata = PathMetadata(base_dir, egg_info)
1935
dist_name = os.path.splitext(os.path.basename(egg_info))[0]
1936
dist = Distribution(basedir, project_name=dist_name, metadata=metadata)
1937
1938
# Unpacked egg directories:
1939
1940
egg_path = "/path/to/PackageName-ver-pyver-etc.egg"
1941
metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO'))
1942
dist = Distribution.from_filename(egg_path, metadata=metadata)
1943
"""
1944
1945
def __init__(self, path, egg_info):
1946
self.module_path = path
1947
self.egg_info = egg_info
1948
1949
1950
class EggMetadata(ZipProvider):
1951
"""Metadata provider for .egg files"""
1952
1953
def __init__(self, importer):
1954
"""Create a metadata provider from a zipimporter"""
1955
1956
self.zip_pre = importer.archive + os.sep
1957
self.loader = importer
1958
if importer.prefix:
1959
self.module_path = os.path.join(importer.archive, importer.prefix)
1960
else:
1961
self.module_path = importer.archive
1962
self._setup_prefix()
1963
1964
1965
_declare_state('dict', _distribution_finders={})
1966
1967
1968
def register_finder(importer_type, distribution_finder):
1969
"""Register `distribution_finder` to find distributions in sys.path items
1970
1971
`importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
1972
handler), and `distribution_finder` is a callable that, passed a path
1973
item and the importer instance, yields ``Distribution`` instances found on
1974
that path item. See ``pkg_resources.find_on_path`` for an example."""
1975
_distribution_finders[importer_type] = distribution_finder
1976
1977
1978
def find_distributions(path_item, only=False):
1979
"""Yield distributions accessible via `path_item`"""
1980
importer = get_importer(path_item)
1981
finder = _find_adapter(_distribution_finders, importer)
1982
return finder(importer, path_item, only)
1983
1984
1985
def find_eggs_in_zip(importer, path_item, only=False):
1986
"""
1987
Find eggs in zip files; possibly multiple nested eggs.
1988
"""
1989
if importer.archive.endswith('.whl'):
1990
# wheels are not supported with this finder
1991
# they don't have PKG-INFO metadata, and won't ever contain eggs
1992
return
1993
metadata = EggMetadata(importer)
1994
if metadata.has_metadata('PKG-INFO'):
1995
yield Distribution.from_filename(path_item, metadata=metadata)
1996
if only:
1997
# don't yield nested distros
1998
return
1999
for subitem in metadata.resource_listdir(''):
2000
if _is_egg_path(subitem):
2001
subpath = os.path.join(path_item, subitem)
2002
dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
2003
for dist in dists:
2004
yield dist
2005
elif subitem.lower().endswith('.dist-info'):
2006
subpath = os.path.join(path_item, subitem)
2007
submeta = EggMetadata(zipimport.zipimporter(subpath))
2008
submeta.egg_info = subpath
2009
yield Distribution.from_location(path_item, subitem, submeta)
2010
2011
2012
register_finder(zipimport.zipimporter, find_eggs_in_zip)
2013
2014
2015
def find_nothing(importer, path_item, only=False):
2016
return ()
2017
2018
2019
register_finder(object, find_nothing)
2020
2021
2022
def _by_version_descending(names):
2023
"""
2024
Given a list of filenames, return them in descending order
2025
by version number.
2026
2027
>>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
2028
>>> _by_version_descending(names)
2029
['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar']
2030
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
2031
>>> _by_version_descending(names)
2032
['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
2033
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg'
2034
>>> _by_version_descending(names)
2035
['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
2036
"""
2037
def _by_version(name):
2038
"""
2039
Parse each component of the filename
2040
"""
2041
name, ext = os.path.splitext(name)
2042
parts = itertools.chain(name.split('-'), [ext])
2043
return [packaging.version.parse(part) for part in parts]
2044
2045
return sorted(names, key=_by_version, reverse=True)
2046
2047
2048
def find_on_path(importer, path_item, only=False):
2049
"""Yield distributions accessible on a sys.path directory"""
2050
path_item = _normalize_cached(path_item)
2051
2052
if _is_unpacked_egg(path_item):
2053
yield Distribution.from_filename(
2054
path_item, metadata=PathMetadata(
2055
path_item, os.path.join(path_item, 'EGG-INFO')
2056
)
2057
)
2058
return
2059
2060
entries = safe_listdir(path_item)
2061
2062
# for performance, before sorting by version,
2063
# screen entries for only those that will yield
2064
# distributions
2065
filtered = (
2066
entry
2067
for entry in entries
2068
if dist_factory(path_item, entry, only)
2069
)
2070
2071
# scan for .egg and .egg-info in directory
2072
path_item_entries = _by_version_descending(filtered)
2073
for entry in path_item_entries:
2074
fullpath = os.path.join(path_item, entry)
2075
factory = dist_factory(path_item, entry, only)
2076
for dist in factory(fullpath):
2077
yield dist
2078
2079
2080
def dist_factory(path_item, entry, only):
2081
"""Return a dist_factory for the given entry."""
2082
lower = entry.lower()
2083
is_egg_info = lower.endswith('.egg-info')
2084
is_dist_info = (
2085
lower.endswith('.dist-info') and
2086
os.path.isdir(os.path.join(path_item, entry))
2087
)
2088
is_meta = is_egg_info or is_dist_info
2089
return (
2090
distributions_from_metadata
2091
if is_meta else
2092
find_distributions
2093
if not only and _is_egg_path(entry) else
2094
resolve_egg_link
2095
if not only and lower.endswith('.egg-link') else
2096
NoDists()
2097
)
2098
2099
2100
class NoDists:
2101
"""
2102
>>> bool(NoDists())
2103
False
2104
2105
>>> list(NoDists()('anything'))
2106
[]
2107
"""
2108
def __bool__(self):
2109
return False
2110
if six.PY2:
2111
__nonzero__ = __bool__
2112
2113
def __call__(self, fullpath):
2114
return iter(())
2115
2116
2117
def safe_listdir(path):
2118
"""
2119
Attempt to list contents of path, but suppress some exceptions.
2120
"""
2121
try:
2122
return os.listdir(path)
2123
except (PermissionError, NotADirectoryError):
2124
pass
2125
except OSError as e:
2126
# Ignore the directory if does not exist, not a directory or
2127
# permission denied
2128
ignorable = (
2129
e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
2130
# Python 2 on Windows needs to be handled this way :(
2131
or getattr(e, "winerror", None) == 267
2132
)
2133
if not ignorable:
2134
raise
2135
return ()
2136
2137
2138
def distributions_from_metadata(path):
2139
root = os.path.dirname(path)
2140
if os.path.isdir(path):
2141
if len(os.listdir(path)) == 0:
2142
# empty metadata dir; skip
2143
return
2144
metadata = PathMetadata(root, path)
2145
else:
2146
metadata = FileMetadata(path)
2147
entry = os.path.basename(path)
2148
yield Distribution.from_location(
2149
root, entry, metadata, precedence=DEVELOP_DIST,
2150
)
2151
2152
2153
def non_empty_lines(path):
2154
"""
2155
Yield non-empty lines from file at path
2156
"""
2157
with open(path) as f:
2158
for line in f:
2159
line = line.strip()
2160
if line:
2161
yield line
2162
2163
2164
def resolve_egg_link(path):
2165
"""
2166
Given a path to an .egg-link, resolve distributions
2167
present in the referenced path.
2168
"""
2169
referenced_paths = non_empty_lines(path)
2170
resolved_paths = (
2171
os.path.join(os.path.dirname(path), ref)
2172
for ref in referenced_paths
2173
)
2174
dist_groups = map(find_distributions, resolved_paths)
2175
return next(dist_groups, ())
2176
2177
2178
register_finder(pkgutil.ImpImporter, find_on_path)
2179
2180
if hasattr(importlib_machinery, 'FileFinder'):
2181
register_finder(importlib_machinery.FileFinder, find_on_path)
2182
2183
_declare_state('dict', _namespace_handlers={})
2184
_declare_state('dict', _namespace_packages={})
2185
2186
2187
def register_namespace_handler(importer_type, namespace_handler):
2188
"""Register `namespace_handler` to declare namespace packages
2189
2190
`importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
2191
handler), and `namespace_handler` is a callable like this::
2192
2193
def namespace_handler(importer, path_entry, moduleName, module):
2194
# return a path_entry to use for child packages
2195
2196
Namespace handlers are only called if the importer object has already
2197
agreed that it can handle the relevant path item, and they should only
2198
return a subpath if the module __path__ does not already contain an
2199
equivalent subpath. For an example namespace handler, see
2200
``pkg_resources.file_ns_handler``.
2201
"""
2202
_namespace_handlers[importer_type] = namespace_handler
2203
2204
2205
def _handle_ns(packageName, path_item):
2206
"""Ensure that named package includes a subpath of path_item (if needed)"""
2207
2208
importer = get_importer(path_item)
2209
if importer is None:
2210
return None
2211
2212
# use find_spec (PEP 451) and fall-back to find_module (PEP 302)
2213
try:
2214
loader = importer.find_spec(packageName).loader
2215
except AttributeError:
2216
# capture warnings due to #1111
2217
with warnings.catch_warnings():
2218
warnings.simplefilter("ignore")
2219
loader = importer.find_module(packageName)
2220
2221
if loader is None:
2222
return None
2223
module = sys.modules.get(packageName)
2224
if module is None:
2225
module = sys.modules[packageName] = types.ModuleType(packageName)
2226
module.__path__ = []
2227
_set_parent_ns(packageName)
2228
elif not hasattr(module, '__path__'):
2229
raise TypeError("Not a package:", packageName)
2230
handler = _find_adapter(_namespace_handlers, importer)
2231
subpath = handler(importer, path_item, packageName, module)
2232
if subpath is not None:
2233
path = module.__path__
2234
path.append(subpath)
2235
loader.load_module(packageName)
2236
_rebuild_mod_path(path, packageName, module)
2237
return subpath
2238
2239
2240
def _rebuild_mod_path(orig_path, package_name, module):
2241
"""
2242
Rebuild module.__path__ ensuring that all entries are ordered
2243
corresponding to their sys.path order
2244
"""
2245
sys_path = [_normalize_cached(p) for p in sys.path]
2246
2247
def safe_sys_path_index(entry):
2248
"""
2249
Workaround for #520 and #513.
2250
"""
2251
try:
2252
return sys_path.index(entry)
2253
except ValueError:
2254
return float('inf')
2255
2256
def position_in_sys_path(path):
2257
"""
2258
Return the ordinal of the path based on its position in sys.path
2259
"""
2260
path_parts = path.split(os.sep)
2261
module_parts = package_name.count('.') + 1
2262
parts = path_parts[:-module_parts]
2263
return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
2264
2265
new_path = sorted(orig_path, key=position_in_sys_path)
2266
new_path = [_normalize_cached(p) for p in new_path]
2267
2268
if isinstance(module.__path__, list):
2269
module.__path__[:] = new_path
2270
else:
2271
module.__path__ = new_path
2272
2273
2274
def declare_namespace(packageName):
2275
"""Declare that package 'packageName' is a namespace package"""
2276
2277
_imp.acquire_lock()
2278
try:
2279
if packageName in _namespace_packages:
2280
return
2281
2282
path = sys.path
2283
parent, _, _ = packageName.rpartition('.')
2284
2285
if parent:
2286
declare_namespace(parent)
2287
if parent not in _namespace_packages:
2288
__import__(parent)
2289
try:
2290
path = sys.modules[parent].__path__
2291
except AttributeError as e:
2292
raise TypeError("Not a package:", parent) from e
2293
2294
# Track what packages are namespaces, so when new path items are added,
2295
# they can be updated
2296
_namespace_packages.setdefault(parent or None, []).append(packageName)
2297
_namespace_packages.setdefault(packageName, [])
2298
2299
for path_item in path:
2300
# Ensure all the parent's path items are reflected in the child,
2301
# if they apply
2302
_handle_ns(packageName, path_item)
2303
2304
finally:
2305
_imp.release_lock()
2306
2307
2308
def fixup_namespace_packages(path_item, parent=None):
2309
"""Ensure that previously-declared namespace packages include path_item"""
2310
_imp.acquire_lock()
2311
try:
2312
for package in _namespace_packages.get(parent, ()):
2313
subpath = _handle_ns(package, path_item)
2314
if subpath:
2315
fixup_namespace_packages(subpath, package)
2316
finally:
2317
_imp.release_lock()
2318
2319
2320
def file_ns_handler(importer, path_item, packageName, module):
2321
"""Compute an ns-package subpath for a filesystem or zipfile importer"""
2322
2323
subpath = os.path.join(path_item, packageName.split('.')[-1])
2324
normalized = _normalize_cached(subpath)
2325
for item in module.__path__:
2326
if _normalize_cached(item) == normalized:
2327
break
2328
else:
2329
# Only return the path if it's not already there
2330
return subpath
2331
2332
2333
register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
2334
register_namespace_handler(zipimport.zipimporter, file_ns_handler)
2335
2336
if hasattr(importlib_machinery, 'FileFinder'):
2337
register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler)
2338
2339
2340
def null_ns_handler(importer, path_item, packageName, module):
2341
return None
2342
2343
2344
register_namespace_handler(object, null_ns_handler)
2345
2346
2347
def normalize_path(filename):
2348
"""Normalize a file/dir name for comparison purposes"""
2349
return os.path.normcase(os.path.realpath(os.path.normpath(
2350
_cygwin_patch(filename))))
2351
2352
2353
def _cygwin_patch(filename): # pragma: nocover
2354
"""
2355
Contrary to POSIX 2008, on Cygwin, getcwd (3) contains
2356
symlink components. Using
2357
os.path.abspath() works around this limitation. A fix in os.getcwd()
2358
would probably better, in Cygwin even more so, except
2359
that this seems to be by design...
2360
"""
2361
return os.path.abspath(filename) if sys.platform == 'cygwin' else filename
2362
2363
2364
def _normalize_cached(filename, _cache={}):
2365
try:
2366
return _cache[filename]
2367
except KeyError:
2368
_cache[filename] = result = normalize_path(filename)
2369
return result
2370
2371
2372
def _is_egg_path(path):
2373
"""
2374
Determine if given path appears to be an egg.
2375
"""
2376
return path.lower().endswith('.egg')
2377
2378
2379
def _is_unpacked_egg(path):
2380
"""
2381
Determine if given path appears to be an unpacked egg.
2382
"""
2383
return (
2384
_is_egg_path(path) and
2385
os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO'))
2386
)
2387
2388
2389
def _set_parent_ns(packageName):
2390
parts = packageName.split('.')
2391
name = parts.pop()
2392
if parts:
2393
parent = '.'.join(parts)
2394
setattr(sys.modules[parent], name, sys.modules[packageName])
2395
2396
2397
def yield_lines(strs):
2398
"""Yield non-empty/non-comment lines of a string or sequence"""
2399
if isinstance(strs, six.string_types):
2400
for s in strs.splitlines():
2401
s = s.strip()
2402
# skip blank lines/comments
2403
if s and not s.startswith('#'):
2404
yield s
2405
else:
2406
for ss in strs:
2407
for s in yield_lines(ss):
2408
yield s
2409
2410
2411
MODULE = re.compile(r"\w+(\.\w+)*$").match
2412
EGG_NAME = re.compile(
2413
r"""
2414
(?P<name>[^-]+) (
2415
-(?P<ver>[^-]+) (
2416
-py(?P<pyver>[^-]+) (
2417
-(?P<plat>.+)
2418
)?
2419
)?
2420
)?
2421
""",
2422
re.VERBOSE | re.IGNORECASE,
2423
).match
2424
2425
2426
class EntryPoint:
2427
"""Object representing an advertised importable object"""
2428
2429
def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
2430
if not MODULE(module_name):
2431
raise ValueError("Invalid module name", module_name)
2432
self.name = name
2433
self.module_name = module_name
2434
self.attrs = tuple(attrs)
2435
self.extras = tuple(extras)
2436
self.dist = dist
2437
2438
def __str__(self):
2439
s = "%s = %s" % (self.name, self.module_name)
2440
if self.attrs:
2441
s += ':' + '.'.join(self.attrs)
2442
if self.extras:
2443
s += ' [%s]' % ','.join(self.extras)
2444
return s
2445
2446
def __repr__(self):
2447
return "EntryPoint.parse(%r)" % str(self)
2448
2449
def load(self, require=True, *args, **kwargs):
2450
"""
2451
Require packages for this EntryPoint, then resolve it.
2452
"""
2453
if not require or args or kwargs:
2454
warnings.warn(
2455
"Parameters to load are deprecated. Call .resolve and "
2456
".require separately.",
2457
PkgResourcesDeprecationWarning,
2458
stacklevel=2,
2459
)
2460
if require:
2461
self.require(*args, **kwargs)
2462
return self.resolve()
2463
2464
def resolve(self):
2465
"""
2466
Resolve the entry point from its module and attrs.
2467
"""
2468
module = __import__(self.module_name, fromlist=['__name__'], level=0)
2469
try:
2470
return functools.reduce(getattr, self.attrs, module)
2471
except AttributeError as exc:
2472
raise ImportError(str(exc)) from exc
2473
2474
def require(self, env=None, installer=None):
2475
if self.extras and not self.dist:
2476
raise UnknownExtra("Can't require() without a distribution", self)
2477
2478
# Get the requirements for this entry point with all its extras and
2479
# then resolve them. We have to pass `extras` along when resolving so
2480
# that the working set knows what extras we want. Otherwise, for
2481
# dist-info distributions, the working set will assume that the
2482
# requirements for that extra are purely optional and skip over them.
2483
reqs = self.dist.requires(self.extras)
2484
items = working_set.resolve(reqs, env, installer, extras=self.extras)
2485
list(map(working_set.add, items))
2486
2487
pattern = re.compile(
2488
r'\s*'
2489
r'(?P<name>.+?)\s*'
2490
r'=\s*'
2491
r'(?P<module>[\w.]+)\s*'
2492
r'(:\s*(?P<attr>[\w.]+))?\s*'
2493
r'(?P<extras>\[.*\])?\s*$'
2494
)
2495
2496
@classmethod
2497
def parse(cls, src, dist=None):
2498
"""Parse a single entry point from string `src`
2499
2500
Entry point syntax follows the form::
2501
2502
name = some.module:some.attr [extra1, extra2]
2503
2504
The entry name and module name are required, but the ``:attrs`` and
2505
``[extras]`` parts are optional
2506
"""
2507
m = cls.pattern.match(src)
2508
if not m:
2509
msg = "EntryPoint must be in 'name=module:attrs [extras]' format"
2510
raise ValueError(msg, src)
2511
res = m.groupdict()
2512
extras = cls._parse_extras(res['extras'])
2513
attrs = res['attr'].split('.') if res['attr'] else ()
2514
return cls(res['name'], res['module'], attrs, extras, dist)
2515
2516
@classmethod
2517
def _parse_extras(cls, extras_spec):
2518
if not extras_spec:
2519
return ()
2520
req = Requirement.parse('x' + extras_spec)
2521
if req.specs:
2522
raise ValueError()
2523
return req.extras
2524
2525
@classmethod
2526
def parse_group(cls, group, lines, dist=None):
2527
"""Parse an entry point group"""
2528
if not MODULE(group):
2529
raise ValueError("Invalid group name", group)
2530
this = {}
2531
for line in yield_lines(lines):
2532
ep = cls.parse(line, dist)
2533
if ep.name in this:
2534
raise ValueError("Duplicate entry point", group, ep.name)
2535
this[ep.name] = ep
2536
return this
2537
2538
@classmethod
2539
def parse_map(cls, data, dist=None):
2540
"""Parse a map of entry point groups"""
2541
if isinstance(data, dict):
2542
data = data.items()
2543
else:
2544
data = split_sections(data)
2545
maps = {}
2546
for group, lines in data:
2547
if group is None:
2548
if not lines:
2549
continue
2550
raise ValueError("Entry points must be listed in groups")
2551
group = group.strip()
2552
if group in maps:
2553
raise ValueError("Duplicate group name", group)
2554
maps[group] = cls.parse_group(group, lines, dist)
2555
return maps
2556
2557
2558
def _version_from_file(lines):
2559
"""
2560
Given an iterable of lines from a Metadata file, return
2561
the value of the Version field, if present, or None otherwise.
2562
"""
2563
def is_version_line(line):
2564
return line.lower().startswith('version:')
2565
version_lines = filter(is_version_line, lines)
2566
line = next(iter(version_lines), '')
2567
_, _, value = line.partition(':')
2568
return safe_version(value.strip()) or None
2569
2570
2571
class Distribution:
2572
"""Wrap an actual or potential sys.path entry w/metadata"""
2573
PKG_INFO = 'PKG-INFO'
2574
2575
def __init__(
2576
self, location=None, metadata=None, project_name=None,
2577
version=None, py_version=PY_MAJOR, platform=None,
2578
precedence=EGG_DIST):
2579
self.project_name = safe_name(project_name or 'Unknown')
2580
if version is not None:
2581
self._version = safe_version(version)
2582
self.py_version = py_version
2583
self.platform = platform
2584
self.location = location
2585
self.precedence = precedence
2586
self._provider = metadata or empty_provider
2587
2588
@classmethod
2589
def from_location(cls, location, basename, metadata=None, **kw):
2590
project_name, version, py_version, platform = [None] * 4
2591
basename, ext = os.path.splitext(basename)
2592
if ext.lower() in _distributionImpl:
2593
cls = _distributionImpl[ext.lower()]
2594
2595
match = EGG_NAME(basename)
2596
if match:
2597
project_name, version, py_version, platform = match.group(
2598
'name', 'ver', 'pyver', 'plat'
2599
)
2600
return cls(
2601
location, metadata, project_name=project_name, version=version,
2602
py_version=py_version, platform=platform, **kw
2603
)._reload_version()
2604
2605
def _reload_version(self):
2606
return self
2607
2608
@property
2609
def hashcmp(self):
2610
return (
2611
self.parsed_version,
2612
self.precedence,
2613
self.key,
2614
self.location,
2615
self.py_version or '',
2616
self.platform or '',
2617
)
2618
2619
def __hash__(self):
2620
return hash(self.hashcmp)
2621
2622
def __lt__(self, other):
2623
return self.hashcmp < other.hashcmp
2624
2625
def __le__(self, other):
2626
return self.hashcmp <= other.hashcmp
2627
2628
def __gt__(self, other):
2629
return self.hashcmp > other.hashcmp
2630
2631
def __ge__(self, other):
2632
return self.hashcmp >= other.hashcmp
2633
2634
def __eq__(self, other):
2635
if not isinstance(other, self.__class__):
2636
# It's not a Distribution, so they are not equal
2637
return False
2638
return self.hashcmp == other.hashcmp
2639
2640
def __ne__(self, other):
2641
return not self == other
2642
2643
# These properties have to be lazy so that we don't have to load any
2644
# metadata until/unless it's actually needed. (i.e., some distributions
2645
# may not know their name or version without loading PKG-INFO)
2646
2647
@property
2648
def key(self):
2649
try:
2650
return self._key
2651
except AttributeError:
2652
self._key = key = self.project_name.lower()
2653
return key
2654
2655
@property
2656
def parsed_version(self):
2657
if not hasattr(self, "_parsed_version"):
2658
self._parsed_version = parse_version(self.version)
2659
2660
return self._parsed_version
2661
2662
def _warn_legacy_version(self):
2663
LV = packaging.version.LegacyVersion
2664
is_legacy = isinstance(self._parsed_version, LV)
2665
if not is_legacy:
2666
return
2667
2668
# While an empty version is technically a legacy version and
2669
# is not a valid PEP 440 version, it's also unlikely to
2670
# actually come from someone and instead it is more likely that
2671
# it comes from setuptools attempting to parse a filename and
2672
# including it in the list. So for that we'll gate this warning
2673
# on if the version is anything at all or not.
2674
if not self.version:
2675
return
2676
2677
tmpl = textwrap.dedent("""
2678
'{project_name} ({version})' is being parsed as a legacy,
2679
non PEP 440,
2680
version. You may find odd behavior and sort order.
2681
In particular it will be sorted as less than 0.0. It
2682
is recommended to migrate to PEP 440 compatible
2683
versions.
2684
""").strip().replace('\n', ' ')
2685
2686
warnings.warn(tmpl.format(**vars(self)), PEP440Warning)
2687
2688
@property
2689
def version(self):
2690
try:
2691
return self._version
2692
except AttributeError as e:
2693
version = self._get_version()
2694
if version is None:
2695
path = self._get_metadata_path_for_display(self.PKG_INFO)
2696
msg = (
2697
"Missing 'Version:' header and/or {} file at path: {}"
2698
).format(self.PKG_INFO, path)
2699
raise ValueError(msg, self) from e
2700
2701
return version
2702
2703
@property
2704
def _dep_map(self):
2705
"""
2706
A map of extra to its list of (direct) requirements
2707
for this distribution, including the null extra.
2708
"""
2709
try:
2710
return self.__dep_map
2711
except AttributeError:
2712
self.__dep_map = self._filter_extras(self._build_dep_map())
2713
return self.__dep_map
2714
2715
@staticmethod
2716
def _filter_extras(dm):
2717
"""
2718
Given a mapping of extras to dependencies, strip off
2719
environment markers and filter out any dependencies
2720
not matching the markers.
2721
"""
2722
for extra in list(filter(None, dm)):
2723
new_extra = extra
2724
reqs = dm.pop(extra)
2725
new_extra, _, marker = extra.partition(':')
2726
fails_marker = marker and (
2727
invalid_marker(marker)
2728
or not evaluate_marker(marker)
2729
)
2730
if fails_marker:
2731
reqs = []
2732
new_extra = safe_extra(new_extra) or None
2733
2734
dm.setdefault(new_extra, []).extend(reqs)
2735
return dm
2736
2737
def _build_dep_map(self):
2738
dm = {}
2739
for name in 'requires.txt', 'depends.txt':
2740
for extra, reqs in split_sections(self._get_metadata(name)):
2741
dm.setdefault(extra, []).extend(parse_requirements(reqs))
2742
return dm
2743
2744
def requires(self, extras=()):
2745
"""List of Requirements needed for this distro if `extras` are used"""
2746
dm = self._dep_map
2747
deps = []
2748
deps.extend(dm.get(None, ()))
2749
for ext in extras:
2750
try:
2751
deps.extend(dm[safe_extra(ext)])
2752
except KeyError as e:
2753
raise UnknownExtra(
2754
"%s has no such extra feature %r" % (self, ext)
2755
) from e
2756
return deps
2757
2758
def _get_metadata_path_for_display(self, name):
2759
"""
2760
Return the path to the given metadata file, if available.
2761
"""
2762
try:
2763
# We need to access _get_metadata_path() on the provider object
2764
# directly rather than through this class's __getattr__()
2765
# since _get_metadata_path() is marked private.
2766
path = self._provider._get_metadata_path(name)
2767
2768
# Handle exceptions e.g. in case the distribution's metadata
2769
# provider doesn't support _get_metadata_path().
2770
except Exception:
2771
return '[could not detect]'
2772
2773
return path
2774
2775
def _get_metadata(self, name):
2776
if self.has_metadata(name):
2777
for line in self.get_metadata_lines(name):
2778
yield line
2779
2780
def _get_version(self):
2781
lines = self._get_metadata(self.PKG_INFO)
2782
version = _version_from_file(lines)
2783
2784
return version
2785
2786
def activate(self, path=None, replace=False):
2787
"""Ensure distribution is importable on `path` (default=sys.path)"""
2788
if path is None:
2789
path = sys.path
2790
self.insert_on(path, replace=replace)
2791
if path is sys.path:
2792
fixup_namespace_packages(self.location)
2793
for pkg in self._get_metadata('namespace_packages.txt'):
2794
if pkg in sys.modules:
2795
declare_namespace(pkg)
2796
2797
def egg_name(self):
2798
"""Return what this distribution's standard .egg filename should be"""
2799
filename = "%s-%s-py%s" % (
2800
to_filename(self.project_name), to_filename(self.version),
2801
self.py_version or PY_MAJOR
2802
)
2803
2804
if self.platform:
2805
filename += '-' + self.platform
2806
return filename
2807
2808
def __repr__(self):
2809
if self.location:
2810
return "%s (%s)" % (self, self.location)
2811
else:
2812
return str(self)
2813
2814
def __str__(self):
2815
try:
2816
version = getattr(self, 'version', None)
2817
except ValueError:
2818
version = None
2819
version = version or "[unknown version]"
2820
return "%s %s" % (self.project_name, version)
2821
2822
def __getattr__(self, attr):
2823
"""Delegate all unrecognized public attributes to .metadata provider"""
2824
if attr.startswith('_'):
2825
raise AttributeError(attr)
2826
return getattr(self._provider, attr)
2827
2828
def __dir__(self):
2829
return list(
2830
set(super(Distribution, self).__dir__())
2831
| set(
2832
attr for attr in self._provider.__dir__()
2833
if not attr.startswith('_')
2834
)
2835
)
2836
2837
if not hasattr(object, '__dir__'):
2838
# python 2.7 not supported
2839
del __dir__
2840
2841
@classmethod
2842
def from_filename(cls, filename, metadata=None, **kw):
2843
return cls.from_location(
2844
_normalize_cached(filename), os.path.basename(filename), metadata,
2845
**kw
2846
)
2847
2848
def as_requirement(self):
2849
"""Return a ``Requirement`` that matches this distribution exactly"""
2850
if isinstance(self.parsed_version, packaging.version.Version):
2851
spec = "%s==%s" % (self.project_name, self.parsed_version)
2852
else:
2853
spec = "%s===%s" % (self.project_name, self.parsed_version)
2854
2855
return Requirement.parse(spec)
2856
2857
def load_entry_point(self, group, name):
2858
"""Return the `name` entry point of `group` or raise ImportError"""
2859
ep = self.get_entry_info(group, name)
2860
if ep is None:
2861
raise ImportError("Entry point %r not found" % ((group, name),))
2862
return ep.load()
2863
2864
def get_entry_map(self, group=None):
2865
"""Return the entry point map for `group`, or the full entry map"""
2866
try:
2867
ep_map = self._ep_map
2868
except AttributeError:
2869
ep_map = self._ep_map = EntryPoint.parse_map(
2870
self._get_metadata('entry_points.txt'), self
2871
)
2872
if group is not None:
2873
return ep_map.get(group, {})
2874
return ep_map
2875
2876
def get_entry_info(self, group, name):
2877
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
2878
return self.get_entry_map(group).get(name)
2879
2880
def insert_on(self, path, loc=None, replace=False):
2881
"""Ensure self.location is on path
2882
2883
If replace=False (default):
2884
- If location is already in path anywhere, do nothing.
2885
- Else:
2886
- If it's an egg and its parent directory is on path,
2887
insert just ahead of the parent.
2888
- Else: add to the end of path.
2889
If replace=True:
2890
- If location is already on path anywhere (not eggs)
2891
or higher priority than its parent (eggs)
2892
do nothing.
2893
- Else:
2894
- If it's an egg and its parent directory is on path,
2895
insert just ahead of the parent,
2896
removing any lower-priority entries.
2897
- Else: add it to the front of path.
2898
"""
2899
2900
loc = loc or self.location
2901
if not loc:
2902
return
2903
2904
nloc = _normalize_cached(loc)
2905
bdir = os.path.dirname(nloc)
2906
npath = [(p and _normalize_cached(p) or p) for p in path]
2907
2908
for p, item in enumerate(npath):
2909
if item == nloc:
2910
if replace:
2911
break
2912
else:
2913
# don't modify path (even removing duplicates) if
2914
# found and not replace
2915
return
2916
elif item == bdir and self.precedence == EGG_DIST:
2917
# if it's an .egg, give it precedence over its directory
2918
# UNLESS it's already been added to sys.path and replace=False
2919
if (not replace) and nloc in npath[p:]:
2920
return
2921
if path is sys.path:
2922
self.check_version_conflict()
2923
path.insert(p, loc)
2924
npath.insert(p, nloc)
2925
break
2926
else:
2927
if path is sys.path:
2928
self.check_version_conflict()
2929
if replace:
2930
path.insert(0, loc)
2931
else:
2932
path.append(loc)
2933
return
2934
2935
# p is the spot where we found or inserted loc; now remove duplicates
2936
while True:
2937
try:
2938
np = npath.index(nloc, p + 1)
2939
except ValueError:
2940
break
2941
else:
2942
del npath[np], path[np]
2943
# ha!
2944
p = np
2945
2946
return
2947
2948
def check_version_conflict(self):
2949
if self.key == 'setuptools':
2950
# ignore the inevitable setuptools self-conflicts :(
2951
return
2952
2953
nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))
2954
loc = normalize_path(self.location)
2955
for modname in self._get_metadata('top_level.txt'):
2956
if (modname not in sys.modules or modname in nsp
2957
or modname in _namespace_packages):
2958
continue
2959
if modname in ('pkg_resources', 'setuptools', 'site'):
2960
continue
2961
fn = getattr(sys.modules[modname], '__file__', None)
2962
if fn and (normalize_path(fn).startswith(loc) or
2963
fn.startswith(self.location)):
2964
continue
2965
issue_warning(
2966
"Module %s was already imported from %s, but %s is being added"
2967
" to sys.path" % (modname, fn, self.location),
2968
)
2969
2970
def has_version(self):
2971
try:
2972
self.version
2973
except ValueError:
2974
issue_warning("Unbuilt egg for " + repr(self))
2975
return False
2976
return True
2977
2978
def clone(self, **kw):
2979
"""Copy this distribution, substituting in any changed keyword args"""
2980
names = 'project_name version py_version platform location precedence'
2981
for attr in names.split():
2982
kw.setdefault(attr, getattr(self, attr, None))
2983
kw.setdefault('metadata', self._provider)
2984
return self.__class__(**kw)
2985
2986
@property
2987
def extras(self):
2988
return [dep for dep in self._dep_map if dep]
2989
2990
2991
class EggInfoDistribution(Distribution):
2992
def _reload_version(self):
2993
"""
2994
Packages installed by distutils (e.g. numpy or scipy),
2995
which uses an old safe_version, and so
2996
their version numbers can get mangled when
2997
converted to filenames (e.g., 1.11.0.dev0+2329eae to
2998
1.11.0.dev0_2329eae). These distributions will not be
2999
parsed properly
3000
downstream by Distribution and safe_version, so
3001
take an extra step and try to get the version number from
3002
the metadata file itself instead of the filename.
3003
"""
3004
md_version = self._get_version()
3005
if md_version:
3006
self._version = md_version
3007
return self
3008
3009
3010
class DistInfoDistribution(Distribution):
3011
"""
3012
Wrap an actual or potential sys.path entry
3013
w/metadata, .dist-info style.
3014
"""
3015
PKG_INFO = 'METADATA'
3016
EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])")
3017
3018
@property
3019
def _parsed_pkg_info(self):
3020
"""Parse and cache metadata"""
3021
try:
3022
return self._pkg_info
3023
except AttributeError:
3024
metadata = self.get_metadata(self.PKG_INFO)
3025
self._pkg_info = email.parser.Parser().parsestr(metadata)
3026
return self._pkg_info
3027
3028
@property
3029
def _dep_map(self):
3030
try:
3031
return self.__dep_map
3032
except AttributeError:
3033
self.__dep_map = self._compute_dependencies()
3034
return self.__dep_map
3035
3036
def _compute_dependencies(self):
3037
"""Recompute this distribution's dependencies."""
3038
dm = self.__dep_map = {None: []}
3039
3040
reqs = []
3041
# Including any condition expressions
3042
for req in self._parsed_pkg_info.get_all('Requires-Dist') or []:
3043
reqs.extend(parse_requirements(req))
3044
3045
def reqs_for_extra(extra):
3046
for req in reqs:
3047
if not req.marker or req.marker.evaluate({'extra': extra}):
3048
yield req
3049
3050
common = frozenset(reqs_for_extra(None))
3051
dm[None].extend(common)
3052
3053
for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:
3054
s_extra = safe_extra(extra.strip())
3055
dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common)
3056
3057
return dm
3058
3059
3060
_distributionImpl = {
3061
'.egg': Distribution,
3062
'.egg-info': EggInfoDistribution,
3063
'.dist-info': DistInfoDistribution,
3064
}
3065
3066
3067
def issue_warning(*args, **kw):
3068
level = 1
3069
g = globals()
3070
try:
3071
# find the first stack frame that is *not* code in
3072
# the pkg_resources module, to use for the warning
3073
while sys._getframe(level).f_globals is g:
3074
level += 1
3075
except ValueError:
3076
pass
3077
warnings.warn(stacklevel=level + 1, *args, **kw)
3078
3079
3080
def parse_requirements(strs):
3081
"""Yield ``Requirement`` objects for each specification in `strs`
3082
3083
`strs` must be a string, or a (possibly-nested) iterable thereof.
3084
"""
3085
# create a steppable iterator, so we can handle \-continuations
3086
lines = iter(yield_lines(strs))
3087
3088
for line in lines:
3089
# Drop comments -- a hash without a space may be in a URL.
3090
if ' #' in line:
3091
line = line[:line.find(' #')]
3092
# If there is a line continuation, drop it, and append the next line.
3093
if line.endswith('\\'):
3094
line = line[:-2].strip()
3095
try:
3096
line += next(lines)
3097
except StopIteration:
3098
return
3099
yield Requirement(line)
3100
3101
3102
class RequirementParseError(packaging.requirements.InvalidRequirement):
3103
"Compatibility wrapper for InvalidRequirement"
3104
3105
3106
class Requirement(packaging.requirements.Requirement):
3107
def __init__(self, requirement_string):
3108
"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
3109
super(Requirement, self).__init__(requirement_string)
3110
self.unsafe_name = self.name
3111
project_name = safe_name(self.name)
3112
self.project_name, self.key = project_name, project_name.lower()
3113
self.specs = [
3114
(spec.operator, spec.version) for spec in self.specifier]
3115
self.extras = tuple(map(safe_extra, self.extras))
3116
self.hashCmp = (
3117
self.key,
3118
self.url,
3119
self.specifier,
3120
frozenset(self.extras),
3121
str(self.marker) if self.marker else None,
3122
)
3123
self.__hash = hash(self.hashCmp)
3124
3125
def __eq__(self, other):
3126
return (
3127
isinstance(other, Requirement) and
3128
self.hashCmp == other.hashCmp
3129
)
3130
3131
def __ne__(self, other):
3132
return not self == other
3133
3134
def __contains__(self, item):
3135
if isinstance(item, Distribution):
3136
if item.key != self.key:
3137
return False
3138
3139
item = item.version
3140
3141
# Allow prereleases always in order to match the previous behavior of
3142
# this method. In the future this should be smarter and follow PEP 440
3143
# more accurately.
3144
return self.specifier.contains(item, prereleases=True)
3145
3146
def __hash__(self):
3147
return self.__hash
3148
3149
def __repr__(self):
3150
return "Requirement.parse(%r)" % str(self)
3151
3152
@staticmethod
3153
def parse(s):
3154
req, = parse_requirements(s)
3155
return req
3156
3157
3158
def _always_object(classes):
3159
"""
3160
Ensure object appears in the mro even
3161
for old-style classes.
3162
"""
3163
if object not in classes:
3164
return classes + (object,)
3165
return classes
3166
3167
3168
def _find_adapter(registry, ob):
3169
"""Return an adapter factory for `ob` from `registry`"""
3170
types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob))))
3171
for t in types:
3172
if t in registry:
3173
return registry[t]
3174
3175
3176
def ensure_directory(path):
3177
"""Ensure that the parent directory of `path` exists"""
3178
dirname = os.path.dirname(path)
3179
os.makedirs(dirname, exist_ok=True)
3180
3181
3182
def _bypass_ensure_directory(path):
3183
"""Sandbox-bypassing version of ensure_directory()"""
3184
if not WRITE_SUPPORT:
3185
raise IOError('"os.mkdir" not supported on this platform.')
3186
dirname, filename = split(path)
3187
if dirname and filename and not isdir(dirname):
3188
_bypass_ensure_directory(dirname)
3189
try:
3190
mkdir(dirname, 0o755)
3191
except FileExistsError:
3192
pass
3193
3194
3195
def split_sections(s):
3196
"""Split a string or iterable thereof into (section, content) pairs
3197
3198
Each ``section`` is a stripped version of the section header ("[section]")
3199
and each ``content`` is a list of stripped lines excluding blank lines and
3200
comment-only lines. If there are any such lines before the first section
3201
header, they're returned in a first ``section`` of ``None``.
3202
"""
3203
section = None
3204
content = []
3205
for line in yield_lines(s):
3206
if line.startswith("["):
3207
if line.endswith("]"):
3208
if section or content:
3209
yield section, content
3210
section = line[1:-1].strip()
3211
content = []
3212
else:
3213
raise ValueError("Invalid section heading", line)
3214
else:
3215
content.append(line)
3216
3217
# wrap up last segment
3218
yield section, content
3219
3220
3221
def _mkstemp(*args, **kw):
3222
old_open = os.open
3223
try:
3224
# temporarily bypass sandboxing
3225
os.open = os_open
3226
return tempfile.mkstemp(*args, **kw)
3227
finally:
3228
# and then put it back
3229
os.open = old_open
3230
3231
3232
# Silence the PEP440Warning by default, so that end users don't get hit by it
3233
# randomly just because they use pkg_resources. We want to append the rule
3234
# because we want earlier uses of filterwarnings to take precedence over this
3235
# one.
3236
warnings.filterwarnings("ignore", category=PEP440Warning, append=True)
3237
3238
3239
# from jaraco.functools 1.3
3240
def _call_aside(f, *args, **kwargs):
3241
f(*args, **kwargs)
3242
return f
3243
3244
3245
@_call_aside
3246
def _initialize(g=globals()):
3247
"Set up global resource manager (deliberately not state-saved)"
3248
manager = ResourceManager()
3249
g['_manager'] = manager
3250
g.update(
3251
(name, getattr(manager, name))
3252
for name in dir(manager)
3253
if not name.startswith('_')
3254
)
3255
3256
3257
@_call_aside
3258
def _initialize_master_working_set():
3259
"""
3260
Prepare the master working set and make the ``require()``
3261
API available.
3262
3263
This function has explicit effects on the global state
3264
of pkg_resources. It is intended to be invoked once at
3265
the initialization of this module.
3266
3267
Invocation by other packages is unsupported and done
3268
at their own risk.
3269
"""
3270
working_set = WorkingSet._build_master()
3271
_declare_state('object', working_set=working_set)
3272
3273
require = working_set.require
3274
iter_entry_points = working_set.iter_entry_points
3275
add_activation_listener = working_set.subscribe
3276
run_script = working_set.run_script
3277
# backward compatibility
3278
run_main = run_script
3279
# Activate all distributions already on sys.path with replace=False and
3280
# ensure that all distributions added to the working set in the future
3281
# (e.g. by calling ``require()``) will get activated as well,
3282
# with higher priority (replace=True).
3283
tuple(
3284
dist.activate(replace=False)
3285
for dist in working_set
3286
)
3287
add_activation_listener(
3288
lambda dist: dist.activate(replace=True),
3289
existing=False,
3290
)
3291
working_set.entries = []
3292
# match order
3293
list(map(working_set.add_entry, sys.path))
3294
globals().update(locals())
3295
3296
3297
class PkgResourcesDeprecationWarning(Warning):
3298
"""
3299
Base class for warning about deprecations in ``pkg_resources``
3300
3301
This class is not derived from ``DeprecationWarning``, and as such is
3302
visible by default.
3303
"""
3304
3305