Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/distlib/compat.py: 92%
39 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-26 06:33 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-26 06:33 +0000
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2013-2017 Vinay Sajip.
4# Licensed to the Python Software Foundation under a contributor agreement.
5# See LICENSE.txt and CONTRIBUTORS.txt.
6#
7from __future__ import absolute_import
9import os
10import re
11import shutil
12import sys
14try:
15 import ssl
16except ImportError: # pragma: no cover
17 ssl = None
19if sys.version_info[0] < 3: # pragma: no cover
20 from StringIO import StringIO
21 string_types = basestring,
22 text_type = unicode
23 from types import FileType as file_type
24 import __builtin__ as builtins
25 import ConfigParser as configparser
26 from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
27 from urllib import (urlretrieve, quote as _quote, unquote, url2pathname,
28 pathname2url, ContentTooShortError, splittype)
30 def quote(s):
31 if isinstance(s, unicode):
32 s = s.encode('utf-8')
33 return _quote(s)
35 import urllib2
36 from urllib2 import (Request, urlopen, URLError, HTTPError,
37 HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler,
38 HTTPRedirectHandler, build_opener)
39 if ssl:
40 from urllib2 import HTTPSHandler
41 import httplib
42 import xmlrpclib
43 import Queue as queue
44 from HTMLParser import HTMLParser
45 import htmlentitydefs
46 raw_input = raw_input
47 from itertools import ifilter as filter
48 from itertools import ifilterfalse as filterfalse
50 # Leaving this around for now, in case it needs resurrecting in some way
51 # _userprog = None
52 # def splituser(host):
53 # """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
54 # global _userprog
55 # if _userprog is None:
56 # import re
57 # _userprog = re.compile('^(.*)@(.*)$')
59 # match = _userprog.match(host)
60 # if match: return match.group(1, 2)
61 # return None, host
63else: # pragma: no cover
64 from io import StringIO
65 string_types = str,
66 text_type = str
67 from io import TextIOWrapper as file_type
68 import builtins
69 import configparser
70 from urllib.parse import (urlparse, urlunparse, urljoin, quote, unquote,
71 urlsplit, urlunsplit, splittype)
72 from urllib.request import (urlopen, urlretrieve, Request, url2pathname,
73 pathname2url, HTTPBasicAuthHandler,
74 HTTPPasswordMgr, HTTPHandler,
75 HTTPRedirectHandler, build_opener)
76 if ssl:
77 from urllib.request import HTTPSHandler
78 from urllib.error import HTTPError, URLError, ContentTooShortError
79 import http.client as httplib
80 import urllib.request as urllib2
81 import xmlrpc.client as xmlrpclib
82 import queue
83 from html.parser import HTMLParser
84 import html.entities as htmlentitydefs
85 raw_input = input
86 from itertools import filterfalse
87 filter = filter
89try:
90 from ssl import match_hostname, CertificateError
91except ImportError: # pragma: no cover
93 class CertificateError(ValueError):
94 pass
96 def _dnsname_match(dn, hostname, max_wildcards=1):
97 """Matching according to RFC 6125, section 6.4.3
99 http://tools.ietf.org/html/rfc6125#section-6.4.3
100 """
101 pats = []
102 if not dn:
103 return False
105 parts = dn.split('.')
106 leftmost, remainder = parts[0], parts[1:]
108 wildcards = leftmost.count('*')
109 if wildcards > max_wildcards:
110 # Issue #17980: avoid denials of service by refusing more
111 # than one wildcard per fragment. A survey of established
112 # policy among SSL implementations showed it to be a
113 # reasonable choice.
114 raise CertificateError(
115 "too many wildcards in certificate DNS name: " + repr(dn))
117 # speed up common case w/o wildcards
118 if not wildcards:
119 return dn.lower() == hostname.lower()
121 # RFC 6125, section 6.4.3, subitem 1.
122 # The client SHOULD NOT attempt to match a presented identifier in which
123 # the wildcard character comprises a label other than the left-most label.
124 if leftmost == '*':
125 # When '*' is a fragment by itself, it matches a non-empty dotless
126 # fragment.
127 pats.append('[^.]+')
128 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
129 # RFC 6125, section 6.4.3, subitem 3.
130 # The client SHOULD NOT attempt to match a presented identifier
131 # where the wildcard character is embedded within an A-label or
132 # U-label of an internationalized domain name.
133 pats.append(re.escape(leftmost))
134 else:
135 # Otherwise, '*' matches any dotless string, e.g. www*
136 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
138 # add the remaining fragments, ignore any wildcards
139 for frag in remainder:
140 pats.append(re.escape(frag))
142 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
143 return pat.match(hostname)
145 def match_hostname(cert, hostname):
146 """Verify that *cert* (in decoded format as returned by
147 SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
148 rules are followed, but IP addresses are not accepted for *hostname*.
150 CertificateError is raised on failure. On success, the function
151 returns nothing.
152 """
153 if not cert:
154 raise ValueError("empty or no certificate, match_hostname needs a "
155 "SSL socket or SSL context with either "
156 "CERT_OPTIONAL or CERT_REQUIRED")
157 dnsnames = []
158 san = cert.get('subjectAltName', ())
159 for key, value in san:
160 if key == 'DNS':
161 if _dnsname_match(value, hostname):
162 return
163 dnsnames.append(value)
164 if not dnsnames:
165 # The subject is only checked when there is no dNSName entry
166 # in subjectAltName
167 for sub in cert.get('subject', ()):
168 for key, value in sub:
169 # XXX according to RFC 2818, the most specific Common Name
170 # must be used.
171 if key == 'commonName':
172 if _dnsname_match(value, hostname):
173 return
174 dnsnames.append(value)
175 if len(dnsnames) > 1:
176 raise CertificateError("hostname %r "
177 "doesn't match either of %s" %
178 (hostname, ', '.join(map(repr, dnsnames))))
179 elif len(dnsnames) == 1:
180 raise CertificateError("hostname %r "
181 "doesn't match %r" %
182 (hostname, dnsnames[0]))
183 else:
184 raise CertificateError("no appropriate commonName or "
185 "subjectAltName fields were found")
188try:
189 from types import SimpleNamespace as Container
190except ImportError: # pragma: no cover
192 class Container(object):
193 """
194 A generic container for when multiple values need to be returned
195 """
197 def __init__(self, **kwargs):
198 self.__dict__.update(kwargs)
201try:
202 from shutil import which
203except ImportError: # pragma: no cover
204 # Implementation from Python 3.3
205 def which(cmd, mode=os.F_OK | os.X_OK, path=None):
206 """Given a command, mode, and a PATH string, return the path which
207 conforms to the given mode on the PATH, or None if there is no such
208 file.
210 `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
211 of os.environ.get("PATH"), or can be overridden with a custom search
212 path.
214 """
216 # Check that a given file can be accessed with the correct mode.
217 # Additionally check that `file` is not a directory, as on Windows
218 # directories pass the os.access check.
219 def _access_check(fn, mode):
220 return (os.path.exists(fn) and os.access(fn, mode)
221 and not os.path.isdir(fn))
223 # If we're given a path with a directory part, look it up directly rather
224 # than referring to PATH directories. This includes checking relative to the
225 # current directory, e.g. ./script
226 if os.path.dirname(cmd):
227 if _access_check(cmd, mode):
228 return cmd
229 return None
231 if path is None:
232 path = os.environ.get("PATH", os.defpath)
233 if not path:
234 return None
235 path = path.split(os.pathsep)
237 if sys.platform == "win32":
238 # The current directory takes precedence on Windows.
239 if os.curdir not in path:
240 path.insert(0, os.curdir)
242 # PATHEXT is necessary to check on Windows.
243 pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
244 # See if the given file matches any of the expected path extensions.
245 # This will allow us to short circuit when given "python.exe".
246 # If it does match, only test that one, otherwise we have to try
247 # others.
248 if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
249 files = [cmd]
250 else:
251 files = [cmd + ext for ext in pathext]
252 else:
253 # On other platforms you don't have things like PATHEXT to tell you
254 # what file suffixes are executable, so just pass on cmd as-is.
255 files = [cmd]
257 seen = set()
258 for dir in path:
259 normdir = os.path.normcase(dir)
260 if normdir not in seen:
261 seen.add(normdir)
262 for thefile in files:
263 name = os.path.join(dir, thefile)
264 if _access_check(name, mode):
265 return name
266 return None
269# ZipFile is a context manager in 2.7, but not in 2.6
271from zipfile import ZipFile as BaseZipFile
273if hasattr(BaseZipFile, '__enter__'): # pragma: no cover
274 ZipFile = BaseZipFile
275else: # pragma: no cover
276 from zipfile import ZipExtFile as BaseZipExtFile
278 class ZipExtFile(BaseZipExtFile):
280 def __init__(self, base):
281 self.__dict__.update(base.__dict__)
283 def __enter__(self):
284 return self
286 def __exit__(self, *exc_info):
287 self.close()
288 # return None, so if an exception occurred, it will propagate
290 class ZipFile(BaseZipFile):
292 def __enter__(self):
293 return self
295 def __exit__(self, *exc_info):
296 self.close()
297 # return None, so if an exception occurred, it will propagate
299 def open(self, *args, **kwargs):
300 base = BaseZipFile.open(self, *args, **kwargs)
301 return ZipExtFile(base)
304try:
305 from platform import python_implementation
306except ImportError: # pragma: no cover
308 def python_implementation():
309 """Return a string identifying the Python implementation."""
310 if 'PyPy' in sys.version:
311 return 'PyPy'
312 if os.name == 'java':
313 return 'Jython'
314 if sys.version.startswith('IronPython'):
315 return 'IronPython'
316 return 'CPython'
319import sysconfig
321try:
322 callable = callable
323except NameError: # pragma: no cover
324 from collections.abc import Callable
326 def callable(obj):
327 return isinstance(obj, Callable)
330try:
331 fsencode = os.fsencode
332 fsdecode = os.fsdecode
333except AttributeError: # pragma: no cover
334 # Issue #99: on some systems (e.g. containerised),
335 # sys.getfilesystemencoding() returns None, and we need a real value,
336 # so fall back to utf-8. From the CPython 2.7 docs relating to Unix and
337 # sys.getfilesystemencoding(): the return value is "the user’s preference
338 # according to the result of nl_langinfo(CODESET), or None if the
339 # nl_langinfo(CODESET) failed."
340 _fsencoding = sys.getfilesystemencoding() or 'utf-8'
341 if _fsencoding == 'mbcs':
342 _fserrors = 'strict'
343 else:
344 _fserrors = 'surrogateescape'
346 def fsencode(filename):
347 if isinstance(filename, bytes):
348 return filename
349 elif isinstance(filename, text_type):
350 return filename.encode(_fsencoding, _fserrors)
351 else:
352 raise TypeError("expect bytes or str, not %s" %
353 type(filename).__name__)
355 def fsdecode(filename):
356 if isinstance(filename, text_type):
357 return filename
358 elif isinstance(filename, bytes):
359 return filename.decode(_fsencoding, _fserrors)
360 else:
361 raise TypeError("expect bytes or str, not %s" %
362 type(filename).__name__)
365try:
366 from tokenize import detect_encoding
367except ImportError: # pragma: no cover
368 from codecs import BOM_UTF8, lookup
370 cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
372 def _get_normal_name(orig_enc):
373 """Imitates get_normal_name in tokenizer.c."""
374 # Only care about the first 12 characters.
375 enc = orig_enc[:12].lower().replace("_", "-")
376 if enc == "utf-8" or enc.startswith("utf-8-"):
377 return "utf-8"
378 if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \
379 enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")):
380 return "iso-8859-1"
381 return orig_enc
383 def detect_encoding(readline):
384 """
385 The detect_encoding() function is used to detect the encoding that should
386 be used to decode a Python source file. It requires one argument, readline,
387 in the same way as the tokenize() generator.
389 It will call readline a maximum of twice, and return the encoding used
390 (as a string) and a list of any lines (left as bytes) it has read in.
392 It detects the encoding from the presence of a utf-8 bom or an encoding
393 cookie as specified in pep-0263. If both a bom and a cookie are present,
394 but disagree, a SyntaxError will be raised. If the encoding cookie is an
395 invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found,
396 'utf-8-sig' is returned.
398 If no encoding is specified, then the default of 'utf-8' will be returned.
399 """
400 try:
401 filename = readline.__self__.name
402 except AttributeError:
403 filename = None
404 bom_found = False
405 encoding = None
406 default = 'utf-8'
408 def read_or_stop():
409 try:
410 return readline()
411 except StopIteration:
412 return b''
414 def find_cookie(line):
415 try:
416 # Decode as UTF-8. Either the line is an encoding declaration,
417 # in which case it should be pure ASCII, or it must be UTF-8
418 # per default encoding.
419 line_string = line.decode('utf-8')
420 except UnicodeDecodeError:
421 msg = "invalid or missing encoding declaration"
422 if filename is not None:
423 msg = '{} for {!r}'.format(msg, filename)
424 raise SyntaxError(msg)
426 matches = cookie_re.findall(line_string)
427 if not matches:
428 return None
429 encoding = _get_normal_name(matches[0])
430 try:
431 codec = lookup(encoding)
432 except LookupError:
433 # This behaviour mimics the Python interpreter
434 if filename is None:
435 msg = "unknown encoding: " + encoding
436 else:
437 msg = "unknown encoding for {!r}: {}".format(
438 filename, encoding)
439 raise SyntaxError(msg)
441 if bom_found:
442 if codec.name != 'utf-8':
443 # This behaviour mimics the Python interpreter
444 if filename is None:
445 msg = 'encoding problem: utf-8'
446 else:
447 msg = 'encoding problem for {!r}: utf-8'.format(
448 filename)
449 raise SyntaxError(msg)
450 encoding += '-sig'
451 return encoding
453 first = read_or_stop()
454 if first.startswith(BOM_UTF8):
455 bom_found = True
456 first = first[3:]
457 default = 'utf-8-sig'
458 if not first:
459 return default, []
461 encoding = find_cookie(first)
462 if encoding:
463 return encoding, [first]
465 second = read_or_stop()
466 if not second:
467 return default, [first]
469 encoding = find_cookie(second)
470 if encoding:
471 return encoding, [first, second]
473 return default, [first, second]
476# For converting & <-> & etc.
477try:
478 from html import escape
479except ImportError:
480 from cgi import escape
481if sys.version_info[:2] < (3, 4):
482 unescape = HTMLParser().unescape
483else:
484 from html import unescape
486try:
487 from collections import ChainMap
488except ImportError: # pragma: no cover
489 from collections import MutableMapping
491 try:
492 from reprlib import recursive_repr as _recursive_repr
493 except ImportError:
495 def _recursive_repr(fillvalue='...'):
496 '''
497 Decorator to make a repr function return fillvalue for a recursive
498 call
499 '''
501 def decorating_function(user_function):
502 repr_running = set()
504 def wrapper(self):
505 key = id(self), get_ident()
506 if key in repr_running:
507 return fillvalue
508 repr_running.add(key)
509 try:
510 result = user_function(self)
511 finally:
512 repr_running.discard(key)
513 return result
515 # Can't use functools.wraps() here because of bootstrap issues
516 wrapper.__module__ = getattr(user_function, '__module__')
517 wrapper.__doc__ = getattr(user_function, '__doc__')
518 wrapper.__name__ = getattr(user_function, '__name__')
519 wrapper.__annotations__ = getattr(user_function,
520 '__annotations__', {})
521 return wrapper
523 return decorating_function
525 class ChainMap(MutableMapping):
526 '''
527 A ChainMap groups multiple dicts (or other mappings) together
528 to create a single, updateable view.
530 The underlying mappings are stored in a list. That list is public and can
531 accessed or updated using the *maps* attribute. There is no other state.
533 Lookups search the underlying mappings successively until a key is found.
534 In contrast, writes, updates, and deletions only operate on the first
535 mapping.
536 '''
538 def __init__(self, *maps):
539 '''Initialize a ChainMap by setting *maps* to the given mappings.
540 If no mappings are provided, a single empty dictionary is used.
542 '''
543 self.maps = list(maps) or [{}] # always at least one map
545 def __missing__(self, key):
546 raise KeyError(key)
548 def __getitem__(self, key):
549 for mapping in self.maps:
550 try:
551 return mapping[
552 key] # can't use 'key in mapping' with defaultdict
553 except KeyError:
554 pass
555 return self.__missing__(
556 key) # support subclasses that define __missing__
558 def get(self, key, default=None):
559 return self[key] if key in self else default
561 def __len__(self):
562 return len(set().union(
563 *self.maps)) # reuses stored hash values if possible
565 def __iter__(self):
566 return iter(set().union(*self.maps))
568 def __contains__(self, key):
569 return any(key in m for m in self.maps)
571 def __bool__(self):
572 return any(self.maps)
574 @_recursive_repr()
575 def __repr__(self):
576 return '{0.__class__.__name__}({1})'.format(
577 self, ', '.join(map(repr, self.maps)))
579 @classmethod
580 def fromkeys(cls, iterable, *args):
581 'Create a ChainMap with a single dict created from the iterable.'
582 return cls(dict.fromkeys(iterable, *args))
584 def copy(self):
585 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
586 return self.__class__(self.maps[0].copy(), *self.maps[1:])
588 __copy__ = copy
590 def new_child(self): # like Django's Context.push()
591 'New ChainMap with a new dict followed by all previous maps.'
592 return self.__class__({}, *self.maps)
594 @property
595 def parents(self): # like Django's Context.pop()
596 'New ChainMap from maps[1:].'
597 return self.__class__(*self.maps[1:])
599 def __setitem__(self, key, value):
600 self.maps[0][key] = value
602 def __delitem__(self, key):
603 try:
604 del self.maps[0][key]
605 except KeyError:
606 raise KeyError(
607 'Key not found in the first mapping: {!r}'.format(key))
609 def popitem(self):
610 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
611 try:
612 return self.maps[0].popitem()
613 except KeyError:
614 raise KeyError('No keys found in the first mapping.')
616 def pop(self, key, *args):
617 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
618 try:
619 return self.maps[0].pop(key, *args)
620 except KeyError:
621 raise KeyError(
622 'Key not found in the first mapping: {!r}'.format(key))
624 def clear(self):
625 'Clear maps[0], leaving maps[1:] intact.'
626 self.maps[0].clear()
629try:
630 from importlib.util import cache_from_source # Python >= 3.4
631except ImportError: # pragma: no cover
633 def cache_from_source(path, debug_override=None):
634 assert path.endswith('.py')
635 if debug_override is None:
636 debug_override = __debug__
637 if debug_override:
638 suffix = 'c'
639 else:
640 suffix = 'o'
641 return path + suffix
644try:
645 from collections import OrderedDict
646except ImportError: # pragma: no cover
647 # {{{ http://code.activestate.com/recipes/576693/ (r9)
648 # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
649 # Passes Python2.7's test suite and incorporates all the latest updates.
650 try:
651 from thread import get_ident as _get_ident
652 except ImportError:
653 from dummy_thread import get_ident as _get_ident
655 try:
656 from _abcoll import KeysView, ValuesView, ItemsView
657 except ImportError:
658 pass
660 class OrderedDict(dict):
661 'Dictionary that remembers insertion order'
663 # An inherited dict maps keys to values.
664 # The inherited dict provides __getitem__, __len__, __contains__, and get.
665 # The remaining methods are order-aware.
666 # Big-O running times for all methods are the same as for regular dictionaries.
668 # The internal self.__map dictionary maps keys to links in a doubly linked list.
669 # The circular doubly linked list starts and ends with a sentinel element.
670 # The sentinel element never gets deleted (this simplifies the algorithm).
671 # Each link is stored as a list of length three: [PREV, NEXT, KEY].
673 def __init__(self, *args, **kwds):
674 '''Initialize an ordered dictionary. Signature is the same as for
675 regular dictionaries, but keyword arguments are not recommended
676 because their insertion order is arbitrary.
678 '''
679 if len(args) > 1:
680 raise TypeError('expected at most 1 arguments, got %d' %
681 len(args))
682 try:
683 self.__root
684 except AttributeError:
685 self.__root = root = [] # sentinel node
686 root[:] = [root, root, None]
687 self.__map = {}
688 self.__update(*args, **kwds)
690 def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
691 'od.__setitem__(i, y) <==> od[i]=y'
692 # Setting a new item creates a new link which goes at the end of the linked
693 # list, and the inherited dictionary is updated with the new key/value pair.
694 if key not in self:
695 root = self.__root
696 last = root[0]
697 last[1] = root[0] = self.__map[key] = [last, root, key]
698 dict_setitem(self, key, value)
700 def __delitem__(self, key, dict_delitem=dict.__delitem__):
701 'od.__delitem__(y) <==> del od[y]'
702 # Deleting an existing item uses self.__map to find the link which is
703 # then removed by updating the links in the predecessor and successor nodes.
704 dict_delitem(self, key)
705 link_prev, link_next, key = self.__map.pop(key)
706 link_prev[1] = link_next
707 link_next[0] = link_prev
709 def __iter__(self):
710 'od.__iter__() <==> iter(od)'
711 root = self.__root
712 curr = root[1]
713 while curr is not root:
714 yield curr[2]
715 curr = curr[1]
717 def __reversed__(self):
718 'od.__reversed__() <==> reversed(od)'
719 root = self.__root
720 curr = root[0]
721 while curr is not root:
722 yield curr[2]
723 curr = curr[0]
725 def clear(self):
726 'od.clear() -> None. Remove all items from od.'
727 try:
728 for node in self.__map.itervalues():
729 del node[:]
730 root = self.__root
731 root[:] = [root, root, None]
732 self.__map.clear()
733 except AttributeError:
734 pass
735 dict.clear(self)
737 def popitem(self, last=True):
738 '''od.popitem() -> (k, v), return and remove a (key, value) pair.
739 Pairs are returned in LIFO order if last is true or FIFO order if false.
741 '''
742 if not self:
743 raise KeyError('dictionary is empty')
744 root = self.__root
745 if last:
746 link = root[0]
747 link_prev = link[0]
748 link_prev[1] = root
749 root[0] = link_prev
750 else:
751 link = root[1]
752 link_next = link[1]
753 root[1] = link_next
754 link_next[0] = root
755 key = link[2]
756 del self.__map[key]
757 value = dict.pop(self, key)
758 return key, value
760 # -- the following methods do not depend on the internal structure --
762 def keys(self):
763 'od.keys() -> list of keys in od'
764 return list(self)
766 def values(self):
767 'od.values() -> list of values in od'
768 return [self[key] for key in self]
770 def items(self):
771 'od.items() -> list of (key, value) pairs in od'
772 return [(key, self[key]) for key in self]
774 def iterkeys(self):
775 'od.iterkeys() -> an iterator over the keys in od'
776 return iter(self)
778 def itervalues(self):
779 'od.itervalues -> an iterator over the values in od'
780 for k in self:
781 yield self[k]
783 def iteritems(self):
784 'od.iteritems -> an iterator over the (key, value) items in od'
785 for k in self:
786 yield (k, self[k])
788 def update(*args, **kwds):
789 '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
791 If E is a dict instance, does: for k in E: od[k] = E[k]
792 If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
793 Or if E is an iterable of items, does: for k, v in E: od[k] = v
794 In either case, this is followed by: for k, v in F.items(): od[k] = v
796 '''
797 if len(args) > 2:
798 raise TypeError('update() takes at most 2 positional '
799 'arguments (%d given)' % (len(args), ))
800 elif not args:
801 raise TypeError('update() takes at least 1 argument (0 given)')
802 self = args[0]
803 # Make progressively weaker assumptions about "other"
804 other = ()
805 if len(args) == 2:
806 other = args[1]
807 if isinstance(other, dict):
808 for key in other:
809 self[key] = other[key]
810 elif hasattr(other, 'keys'):
811 for key in other.keys():
812 self[key] = other[key]
813 else:
814 for key, value in other:
815 self[key] = value
816 for key, value in kwds.items():
817 self[key] = value
819 __update = update # let subclasses override update without breaking __init__
821 __marker = object()
823 def pop(self, key, default=__marker):
824 '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
825 If key is not found, d is returned if given, otherwise KeyError is raised.
827 '''
828 if key in self:
829 result = self[key]
830 del self[key]
831 return result
832 if default is self.__marker:
833 raise KeyError(key)
834 return default
836 def setdefault(self, key, default=None):
837 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
838 if key in self:
839 return self[key]
840 self[key] = default
841 return default
843 def __repr__(self, _repr_running=None):
844 'od.__repr__() <==> repr(od)'
845 if not _repr_running:
846 _repr_running = {}
847 call_key = id(self), _get_ident()
848 if call_key in _repr_running:
849 return '...'
850 _repr_running[call_key] = 1
851 try:
852 if not self:
853 return '%s()' % (self.__class__.__name__, )
854 return '%s(%r)' % (self.__class__.__name__, self.items())
855 finally:
856 del _repr_running[call_key]
858 def __reduce__(self):
859 'Return state information for pickling'
860 items = [[k, self[k]] for k in self]
861 inst_dict = vars(self).copy()
862 for k in vars(OrderedDict()):
863 inst_dict.pop(k, None)
864 if inst_dict:
865 return (self.__class__, (items, ), inst_dict)
866 return self.__class__, (items, )
868 def copy(self):
869 'od.copy() -> a shallow copy of od'
870 return self.__class__(self)
872 @classmethod
873 def fromkeys(cls, iterable, value=None):
874 '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
875 and values equal to v (which defaults to None).
877 '''
878 d = cls()
879 for key in iterable:
880 d[key] = value
881 return d
883 def __eq__(self, other):
884 '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
885 while comparison to a regular mapping is order-insensitive.
887 '''
888 if isinstance(other, OrderedDict):
889 return len(self) == len(
890 other) and self.items() == other.items()
891 return dict.__eq__(self, other)
893 def __ne__(self, other):
894 return not self == other
896 # -- the following methods are only used in Python 2.7 --
898 def viewkeys(self):
899 "od.viewkeys() -> a set-like object providing a view on od's keys"
900 return KeysView(self)
902 def viewvalues(self):
903 "od.viewvalues() -> an object providing a view on od's values"
904 return ValuesView(self)
906 def viewitems(self):
907 "od.viewitems() -> a set-like object providing a view on od's items"
908 return ItemsView(self)
911try:
912 from logging.config import BaseConfigurator, valid_ident
913except ImportError: # pragma: no cover
914 IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
916 def valid_ident(s):
917 m = IDENTIFIER.match(s)
918 if not m:
919 raise ValueError('Not a valid Python identifier: %r' % s)
920 return True
922 # The ConvertingXXX classes are wrappers around standard Python containers,
923 # and they serve to convert any suitable values in the container. The
924 # conversion converts base dicts, lists and tuples to their wrapped
925 # equivalents, whereas strings which match a conversion format are converted
926 # appropriately.
927 #
928 # Each wrapper should have a configurator attribute holding the actual
929 # configurator to use for conversion.
931 class ConvertingDict(dict):
932 """A converting dictionary wrapper."""
934 def __getitem__(self, key):
935 value = dict.__getitem__(self, key)
936 result = self.configurator.convert(value)
937 # If the converted value is different, save for next time
938 if value is not result:
939 self[key] = result
940 if type(result) in (ConvertingDict, ConvertingList,
941 ConvertingTuple):
942 result.parent = self
943 result.key = key
944 return result
946 def get(self, key, default=None):
947 value = dict.get(self, key, default)
948 result = self.configurator.convert(value)
949 # If the converted value is different, save for next time
950 if value is not result:
951 self[key] = result
952 if type(result) in (ConvertingDict, ConvertingList,
953 ConvertingTuple):
954 result.parent = self
955 result.key = key
956 return result
958 def pop(self, key, default=None):
959 value = dict.pop(self, key, default)
960 result = self.configurator.convert(value)
961 if value is not result:
962 if type(result) in (ConvertingDict, ConvertingList,
963 ConvertingTuple):
964 result.parent = self
965 result.key = key
966 return result
968 class ConvertingList(list):
969 """A converting list wrapper."""
971 def __getitem__(self, key):
972 value = list.__getitem__(self, key)
973 result = self.configurator.convert(value)
974 # If the converted value is different, save for next time
975 if value is not result:
976 self[key] = result
977 if type(result) in (ConvertingDict, ConvertingList,
978 ConvertingTuple):
979 result.parent = self
980 result.key = key
981 return result
983 def pop(self, idx=-1):
984 value = list.pop(self, idx)
985 result = self.configurator.convert(value)
986 if value is not result:
987 if type(result) in (ConvertingDict, ConvertingList,
988 ConvertingTuple):
989 result.parent = self
990 return result
992 class ConvertingTuple(tuple):
993 """A converting tuple wrapper."""
995 def __getitem__(self, key):
996 value = tuple.__getitem__(self, key)
997 result = self.configurator.convert(value)
998 if value is not result:
999 if type(result) in (ConvertingDict, ConvertingList,
1000 ConvertingTuple):
1001 result.parent = self
1002 result.key = key
1003 return result
1005 class BaseConfigurator(object):
1006 """
1007 The configurator base class which defines some useful defaults.
1008 """
1010 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
1012 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
1013 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
1014 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
1015 DIGIT_PATTERN = re.compile(r'^\d+$')
1017 value_converters = {
1018 'ext': 'ext_convert',
1019 'cfg': 'cfg_convert',
1020 }
1022 # We might want to use a different one, e.g. importlib
1023 importer = staticmethod(__import__)
1025 def __init__(self, config):
1026 self.config = ConvertingDict(config)
1027 self.config.configurator = self
1029 def resolve(self, s):
1030 """
1031 Resolve strings to objects using standard import and attribute
1032 syntax.
1033 """
1034 name = s.split('.')
1035 used = name.pop(0)
1036 try:
1037 found = self.importer(used)
1038 for frag in name:
1039 used += '.' + frag
1040 try:
1041 found = getattr(found, frag)
1042 except AttributeError:
1043 self.importer(used)
1044 found = getattr(found, frag)
1045 return found
1046 except ImportError:
1047 e, tb = sys.exc_info()[1:]
1048 v = ValueError('Cannot resolve %r: %s' % (s, e))
1049 v.__cause__, v.__traceback__ = e, tb
1050 raise v
1052 def ext_convert(self, value):
1053 """Default converter for the ext:// protocol."""
1054 return self.resolve(value)
1056 def cfg_convert(self, value):
1057 """Default converter for the cfg:// protocol."""
1058 rest = value
1059 m = self.WORD_PATTERN.match(rest)
1060 if m is None:
1061 raise ValueError("Unable to convert %r" % value)
1062 else:
1063 rest = rest[m.end():]
1064 d = self.config[m.groups()[0]]
1065 while rest:
1066 m = self.DOT_PATTERN.match(rest)
1067 if m:
1068 d = d[m.groups()[0]]
1069 else:
1070 m = self.INDEX_PATTERN.match(rest)
1071 if m:
1072 idx = m.groups()[0]
1073 if not self.DIGIT_PATTERN.match(idx):
1074 d = d[idx]
1075 else:
1076 try:
1077 n = int(
1078 idx
1079 ) # try as number first (most likely)
1080 d = d[n]
1081 except TypeError:
1082 d = d[idx]
1083 if m:
1084 rest = rest[m.end():]
1085 else:
1086 raise ValueError('Unable to convert '
1087 '%r at %r' % (value, rest))
1088 # rest should be empty
1089 return d
1091 def convert(self, value):
1092 """
1093 Convert values to an appropriate type. dicts, lists and tuples are
1094 replaced by their converting alternatives. Strings are checked to
1095 see if they have a conversion format and are converted if they do.
1096 """
1097 if not isinstance(value, ConvertingDict) and isinstance(
1098 value, dict):
1099 value = ConvertingDict(value)
1100 value.configurator = self
1101 elif not isinstance(value, ConvertingList) and isinstance(
1102 value, list):
1103 value = ConvertingList(value)
1104 value.configurator = self
1105 elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple):
1106 value = ConvertingTuple(value)
1107 value.configurator = self
1108 elif isinstance(value, string_types):
1109 m = self.CONVERT_PATTERN.match(value)
1110 if m:
1111 d = m.groupdict()
1112 prefix = d['prefix']
1113 converter = self.value_converters.get(prefix, None)
1114 if converter:
1115 suffix = d['suffix']
1116 converter = getattr(self, converter)
1117 value = converter(suffix)
1118 return value
1120 def configure_custom(self, config):
1121 """Configure an object with a user-supplied factory."""
1122 c = config.pop('()')
1123 if not callable(c):
1124 c = self.resolve(c)
1125 props = config.pop('.', None)
1126 # Check for valid identifiers
1127 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
1128 result = c(**kwargs)
1129 if props:
1130 for name, value in props.items():
1131 setattr(result, name, value)
1132 return result
1134 def as_tuple(self, value):
1135 """Utility function which converts lists to tuples."""
1136 if isinstance(value, list):
1137 value = tuple(value)
1138 return value