Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/distlib/compat.py: 90%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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) and not os.path.isdir(fn))
222 # If we're given a path with a directory part, look it up directly rather
223 # than referring to PATH directories. This includes checking relative to the
224 # current directory, e.g. ./script
225 if os.path.dirname(cmd):
226 if _access_check(cmd, mode):
227 return cmd
228 return None
230 if path is None:
231 path = os.environ.get("PATH", os.defpath)
232 if not path:
233 return None
234 path = path.split(os.pathsep)
236 if sys.platform == "win32":
237 # The current directory takes precedence on Windows.
238 if os.curdir not in path:
239 path.insert(0, os.curdir)
241 # PATHEXT is necessary to check on Windows.
242 pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
243 # See if the given file matches any of the expected path extensions.
244 # This will allow us to short circuit when given "python.exe".
245 # If it does match, only test that one, otherwise we have to try
246 # others.
247 if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
248 files = [cmd]
249 else:
250 files = [cmd + ext for ext in pathext]
251 else:
252 # On other platforms you don't have things like PATHEXT to tell you
253 # what file suffixes are executable, so just pass on cmd as-is.
254 files = [cmd]
256 seen = set()
257 for dir in path:
258 normdir = os.path.normcase(dir)
259 if normdir not in seen:
260 seen.add(normdir)
261 for thefile in files:
262 name = os.path.join(dir, thefile)
263 if _access_check(name, mode):
264 return name
265 return None
268# ZipFile is a context manager in 2.7, but not in 2.6
270from zipfile import ZipFile as BaseZipFile
272if hasattr(BaseZipFile, '__enter__'): # pragma: no cover
273 ZipFile = BaseZipFile
274else: # pragma: no cover
275 from zipfile import ZipExtFile as BaseZipExtFile
277 class ZipExtFile(BaseZipExtFile):
279 def __init__(self, base):
280 self.__dict__.update(base.__dict__)
282 def __enter__(self):
283 return self
285 def __exit__(self, *exc_info):
286 self.close()
287 # return None, so if an exception occurred, it will propagate
289 class ZipFile(BaseZipFile):
291 def __enter__(self):
292 return self
294 def __exit__(self, *exc_info):
295 self.close()
296 # return None, so if an exception occurred, it will propagate
298 def open(self, *args, **kwargs):
299 base = BaseZipFile.open(self, *args, **kwargs)
300 return ZipExtFile(base)
303try:
304 from platform import python_implementation
305except ImportError: # pragma: no cover
307 def python_implementation():
308 """Return a string identifying the Python implementation."""
309 if 'PyPy' in sys.version:
310 return 'PyPy'
311 if os.name == 'java':
312 return 'Jython'
313 if sys.version.startswith('IronPython'):
314 return 'IronPython'
315 return 'CPython'
318import sysconfig
320try:
321 callable = callable
322except NameError: # pragma: no cover
323 from collections.abc import Callable
325 def callable(obj):
326 return isinstance(obj, Callable)
329try:
330 fsencode = os.fsencode
331 fsdecode = os.fsdecode
332except AttributeError: # pragma: no cover
333 # Issue #99: on some systems (e.g. containerised),
334 # sys.getfilesystemencoding() returns None, and we need a real value,
335 # so fall back to utf-8. From the CPython 2.7 docs relating to Unix and
336 # sys.getfilesystemencoding(): the return value is "the user’s preference
337 # according to the result of nl_langinfo(CODESET), or None if the
338 # nl_langinfo(CODESET) failed."
339 _fsencoding = sys.getfilesystemencoding() or 'utf-8'
340 if _fsencoding == 'mbcs':
341 _fserrors = 'strict'
342 else:
343 _fserrors = 'surrogateescape'
345 def fsencode(filename):
346 if isinstance(filename, bytes):
347 return filename
348 elif isinstance(filename, text_type):
349 return filename.encode(_fsencoding, _fserrors)
350 else:
351 raise TypeError("expect bytes or str, not %s" %
352 type(filename).__name__)
354 def fsdecode(filename):
355 if isinstance(filename, text_type):
356 return filename
357 elif isinstance(filename, bytes):
358 return filename.decode(_fsencoding, _fserrors)
359 else:
360 raise TypeError("expect bytes or str, not %s" %
361 type(filename).__name__)
364try:
365 from tokenize import detect_encoding
366except ImportError: # pragma: no cover
367 from codecs import BOM_UTF8, lookup
369 cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
371 def _get_normal_name(orig_enc):
372 """Imitates get_normal_name in tokenizer.c."""
373 # Only care about the first 12 characters.
374 enc = orig_enc[:12].lower().replace("_", "-")
375 if enc == "utf-8" or enc.startswith("utf-8-"):
376 return "utf-8"
377 if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \
378 enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")):
379 return "iso-8859-1"
380 return orig_enc
382 def detect_encoding(readline):
383 """
384 The detect_encoding() function is used to detect the encoding that should
385 be used to decode a Python source file. It requires one argument, readline,
386 in the same way as the tokenize() generator.
388 It will call readline a maximum of twice, and return the encoding used
389 (as a string) and a list of any lines (left as bytes) it has read in.
391 It detects the encoding from the presence of a utf-8 bom or an encoding
392 cookie as specified in pep-0263. If both a bom and a cookie are present,
393 but disagree, a SyntaxError will be raised. If the encoding cookie is an
394 invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found,
395 'utf-8-sig' is returned.
397 If no encoding is specified, then the default of 'utf-8' will be returned.
398 """
399 try:
400 filename = readline.__self__.name
401 except AttributeError:
402 filename = None
403 bom_found = False
404 encoding = None
405 default = 'utf-8'
407 def read_or_stop():
408 try:
409 return readline()
410 except StopIteration:
411 return b''
413 def find_cookie(line):
414 try:
415 # Decode as UTF-8. Either the line is an encoding declaration,
416 # in which case it should be pure ASCII, or it must be UTF-8
417 # per default encoding.
418 line_string = line.decode('utf-8')
419 except UnicodeDecodeError:
420 msg = "invalid or missing encoding declaration"
421 if filename is not None:
422 msg = '{} for {!r}'.format(msg, filename)
423 raise SyntaxError(msg)
425 matches = cookie_re.findall(line_string)
426 if not matches:
427 return None
428 encoding = _get_normal_name(matches[0])
429 try:
430 codec = lookup(encoding)
431 except LookupError:
432 # This behaviour mimics the Python interpreter
433 if filename is None:
434 msg = "unknown encoding: " + encoding
435 else:
436 msg = "unknown encoding for {!r}: {}".format(
437 filename, encoding)
438 raise SyntaxError(msg)
440 if bom_found:
441 if codec.name != 'utf-8':
442 # This behaviour mimics the Python interpreter
443 if filename is None:
444 msg = 'encoding problem: utf-8'
445 else:
446 msg = 'encoding problem for {!r}: utf-8'.format(
447 filename)
448 raise SyntaxError(msg)
449 encoding += '-sig'
450 return encoding
452 first = read_or_stop()
453 if first.startswith(BOM_UTF8):
454 bom_found = True
455 first = first[3:]
456 default = 'utf-8-sig'
457 if not first:
458 return default, []
460 encoding = find_cookie(first)
461 if encoding:
462 return encoding, [first]
464 second = read_or_stop()
465 if not second:
466 return default, [first]
468 encoding = find_cookie(second)
469 if encoding:
470 return encoding, [first, second]
472 return default, [first, second]
475# For converting & <-> & etc.
476try:
477 from html import escape
478except ImportError:
479 from cgi import escape
480if sys.version_info[:2] < (3, 4):
481 unescape = HTMLParser().unescape
482else:
483 from html import unescape
485try:
486 from collections import ChainMap
487except ImportError: # pragma: no cover
488 from collections import MutableMapping
490 try:
491 from reprlib import recursive_repr as _recursive_repr
492 except ImportError:
494 def _recursive_repr(fillvalue='...'):
495 '''
496 Decorator to make a repr function return fillvalue for a recursive
497 call
498 '''
500 def decorating_function(user_function):
501 repr_running = set()
503 def wrapper(self):
504 key = id(self), get_ident()
505 if key in repr_running:
506 return fillvalue
507 repr_running.add(key)
508 try:
509 result = user_function(self)
510 finally:
511 repr_running.discard(key)
512 return result
514 # Can't use functools.wraps() here because of bootstrap issues
515 wrapper.__module__ = getattr(user_function, '__module__')
516 wrapper.__doc__ = getattr(user_function, '__doc__')
517 wrapper.__name__ = getattr(user_function, '__name__')
518 wrapper.__annotations__ = getattr(user_function,
519 '__annotations__', {})
520 return wrapper
522 return decorating_function
524 class ChainMap(MutableMapping):
525 '''
526 A ChainMap groups multiple dicts (or other mappings) together
527 to create a single, updateable view.
529 The underlying mappings are stored in a list. That list is public and can
530 accessed or updated using the *maps* attribute. There is no other state.
532 Lookups search the underlying mappings successively until a key is found.
533 In contrast, writes, updates, and deletions only operate on the first
534 mapping.
535 '''
537 def __init__(self, *maps):
538 '''Initialize a ChainMap by setting *maps* to the given mappings.
539 If no mappings are provided, a single empty dictionary is used.
541 '''
542 self.maps = list(maps) or [{}] # always at least one map
544 def __missing__(self, key):
545 raise KeyError(key)
547 def __getitem__(self, key):
548 for mapping in self.maps:
549 try:
550 return mapping[
551 key] # can't use 'key in mapping' with defaultdict
552 except KeyError:
553 pass
554 return self.__missing__(
555 key) # support subclasses that define __missing__
557 def get(self, key, default=None):
558 return self[key] if key in self else default
560 def __len__(self):
561 return len(set().union(
562 *self.maps)) # reuses stored hash values if possible
564 def __iter__(self):
565 return iter(set().union(*self.maps))
567 def __contains__(self, key):
568 return any(key in m for m in self.maps)
570 def __bool__(self):
571 return any(self.maps)
573 @_recursive_repr()
574 def __repr__(self):
575 return '{0.__class__.__name__}({1})'.format(
576 self, ', '.join(map(repr, self.maps)))
578 @classmethod
579 def fromkeys(cls, iterable, *args):
580 'Create a ChainMap with a single dict created from the iterable.'
581 return cls(dict.fromkeys(iterable, *args))
583 def copy(self):
584 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
585 return self.__class__(self.maps[0].copy(), *self.maps[1:])
587 __copy__ = copy
589 def new_child(self): # like Django's Context.push()
590 'New ChainMap with a new dict followed by all previous maps.'
591 return self.__class__({}, *self.maps)
593 @property
594 def parents(self): # like Django's Context.pop()
595 'New ChainMap from maps[1:].'
596 return self.__class__(*self.maps[1:])
598 def __setitem__(self, key, value):
599 self.maps[0][key] = value
601 def __delitem__(self, key):
602 try:
603 del self.maps[0][key]
604 except KeyError:
605 raise KeyError(
606 'Key not found in the first mapping: {!r}'.format(key))
608 def popitem(self):
609 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
610 try:
611 return self.maps[0].popitem()
612 except KeyError:
613 raise KeyError('No keys found in the first mapping.')
615 def pop(self, key, *args):
616 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
617 try:
618 return self.maps[0].pop(key, *args)
619 except KeyError:
620 raise KeyError(
621 'Key not found in the first mapping: {!r}'.format(key))
623 def clear(self):
624 'Clear maps[0], leaving maps[1:] intact.'
625 self.maps[0].clear()
628try:
629 from importlib.util import cache_from_source # Python >= 3.4
630except ImportError: # pragma: no cover
632 def cache_from_source(path, debug_override=None):
633 assert path.endswith('.py')
634 if debug_override is None:
635 debug_override = __debug__
636 if debug_override:
637 suffix = 'c'
638 else:
639 suffix = 'o'
640 return path + suffix
643try:
644 from collections import OrderedDict
645except ImportError: # pragma: no cover
646 # {{{ http://code.activestate.com/recipes/576693/ (r9)
647 # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
648 # Passes Python2.7's test suite and incorporates all the latest updates.
649 try:
650 from thread import get_ident as _get_ident
651 except ImportError:
652 from dummy_thread import get_ident as _get_ident
654 try:
655 from _abcoll import KeysView, ValuesView, ItemsView
656 except ImportError:
657 pass
659 class OrderedDict(dict):
660 'Dictionary that remembers insertion order'
662 # An inherited dict maps keys to values.
663 # The inherited dict provides __getitem__, __len__, __contains__, and get.
664 # The remaining methods are order-aware.
665 # Big-O running times for all methods are the same as for regular dictionaries.
667 # The internal self.__map dictionary maps keys to links in a doubly linked list.
668 # The circular doubly linked list starts and ends with a sentinel element.
669 # The sentinel element never gets deleted (this simplifies the algorithm).
670 # Each link is stored as a list of length three: [PREV, NEXT, KEY].
672 def __init__(self, *args, **kwds):
673 '''Initialize an ordered dictionary. Signature is the same as for
674 regular dictionaries, but keyword arguments are not recommended
675 because their insertion order is arbitrary.
677 '''
678 if len(args) > 1:
679 raise TypeError('expected at most 1 arguments, got %d' %
680 len(args))
681 try:
682 self.__root
683 except AttributeError:
684 self.__root = root = [] # sentinel node
685 root[:] = [root, root, None]
686 self.__map = {}
687 self.__update(*args, **kwds)
689 def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
690 'od.__setitem__(i, y) <==> od[i]=y'
691 # Setting a new item creates a new link which goes at the end of the linked
692 # list, and the inherited dictionary is updated with the new key/value pair.
693 if key not in self:
694 root = self.__root
695 last = root[0]
696 last[1] = root[0] = self.__map[key] = [last, root, key]
697 dict_setitem(self, key, value)
699 def __delitem__(self, key, dict_delitem=dict.__delitem__):
700 'od.__delitem__(y) <==> del od[y]'
701 # Deleting an existing item uses self.__map to find the link which is
702 # then removed by updating the links in the predecessor and successor nodes.
703 dict_delitem(self, key)
704 link_prev, link_next, key = self.__map.pop(key)
705 link_prev[1] = link_next
706 link_next[0] = link_prev
708 def __iter__(self):
709 'od.__iter__() <==> iter(od)'
710 root = self.__root
711 curr = root[1]
712 while curr is not root:
713 yield curr[2]
714 curr = curr[1]
716 def __reversed__(self):
717 'od.__reversed__() <==> reversed(od)'
718 root = self.__root
719 curr = root[0]
720 while curr is not root:
721 yield curr[2]
722 curr = curr[0]
724 def clear(self):
725 'od.clear() -> None. Remove all items from od.'
726 try:
727 for node in self.__map.itervalues():
728 del node[:]
729 root = self.__root
730 root[:] = [root, root, None]
731 self.__map.clear()
732 except AttributeError:
733 pass
734 dict.clear(self)
736 def popitem(self, last=True):
737 '''od.popitem() -> (k, v), return and remove a (key, value) pair.
738 Pairs are returned in LIFO order if last is true or FIFO order if false.
740 '''
741 if not self:
742 raise KeyError('dictionary is empty')
743 root = self.__root
744 if last:
745 link = root[0]
746 link_prev = link[0]
747 link_prev[1] = root
748 root[0] = link_prev
749 else:
750 link = root[1]
751 link_next = link[1]
752 root[1] = link_next
753 link_next[0] = root
754 key = link[2]
755 del self.__map[key]
756 value = dict.pop(self, key)
757 return key, value
759 # -- the following methods do not depend on the internal structure --
761 def keys(self):
762 'od.keys() -> list of keys in od'
763 return list(self)
765 def values(self):
766 'od.values() -> list of values in od'
767 return [self[key] for key in self]
769 def items(self):
770 'od.items() -> list of (key, value) pairs in od'
771 return [(key, self[key]) for key in self]
773 def iterkeys(self):
774 'od.iterkeys() -> an iterator over the keys in od'
775 return iter(self)
777 def itervalues(self):
778 'od.itervalues -> an iterator over the values in od'
779 for k in self:
780 yield self[k]
782 def iteritems(self):
783 'od.iteritems -> an iterator over the (key, value) items in od'
784 for k in self:
785 yield (k, self[k])
787 def update(*args, **kwds):
788 '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
790 If E is a dict instance, does: for k in E: od[k] = E[k]
791 If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
792 Or if E is an iterable of items, does: for k, v in E: od[k] = v
793 In either case, this is followed by: for k, v in F.items(): od[k] = v
795 '''
796 if len(args) > 2:
797 raise TypeError('update() takes at most 2 positional '
798 'arguments (%d given)' % (len(args), ))
799 elif not args:
800 raise TypeError('update() takes at least 1 argument (0 given)')
801 self = args[0]
802 # Make progressively weaker assumptions about "other"
803 other = ()
804 if len(args) == 2:
805 other = args[1]
806 if isinstance(other, dict):
807 for key in other:
808 self[key] = other[key]
809 elif hasattr(other, 'keys'):
810 for key in other.keys():
811 self[key] = other[key]
812 else:
813 for key, value in other:
814 self[key] = value
815 for key, value in kwds.items():
816 self[key] = value
818 __update = update # let subclasses override update without breaking __init__
820 __marker = object()
822 def pop(self, key, default=__marker):
823 '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
824 If key is not found, d is returned if given, otherwise KeyError is raised.
826 '''
827 if key in self:
828 result = self[key]
829 del self[key]
830 return result
831 if default is self.__marker:
832 raise KeyError(key)
833 return default
835 def setdefault(self, key, default=None):
836 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
837 if key in self:
838 return self[key]
839 self[key] = default
840 return default
842 def __repr__(self, _repr_running=None):
843 'od.__repr__() <==> repr(od)'
844 if not _repr_running:
845 _repr_running = {}
846 call_key = id(self), _get_ident()
847 if call_key in _repr_running:
848 return '...'
849 _repr_running[call_key] = 1
850 try:
851 if not self:
852 return '%s()' % (self.__class__.__name__, )
853 return '%s(%r)' % (self.__class__.__name__, self.items())
854 finally:
855 del _repr_running[call_key]
857 def __reduce__(self):
858 'Return state information for pickling'
859 items = [[k, self[k]] for k in self]
860 inst_dict = vars(self).copy()
861 for k in vars(OrderedDict()):
862 inst_dict.pop(k, None)
863 if inst_dict:
864 return (self.__class__, (items, ), inst_dict)
865 return self.__class__, (items, )
867 def copy(self):
868 'od.copy() -> a shallow copy of od'
869 return self.__class__(self)
871 @classmethod
872 def fromkeys(cls, iterable, value=None):
873 '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
874 and values equal to v (which defaults to None).
876 '''
877 d = cls()
878 for key in iterable:
879 d[key] = value
880 return d
882 def __eq__(self, other):
883 '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
884 while comparison to a regular mapping is order-insensitive.
886 '''
887 if isinstance(other, OrderedDict):
888 return len(self) == len(
889 other) and self.items() == other.items()
890 return dict.__eq__(self, other)
892 def __ne__(self, other):
893 return not self == other
895 # -- the following methods are only used in Python 2.7 --
897 def viewkeys(self):
898 "od.viewkeys() -> a set-like object providing a view on od's keys"
899 return KeysView(self)
901 def viewvalues(self):
902 "od.viewvalues() -> an object providing a view on od's values"
903 return ValuesView(self)
905 def viewitems(self):
906 "od.viewitems() -> a set-like object providing a view on od's items"
907 return ItemsView(self)
910try:
911 from logging.config import BaseConfigurator, valid_ident
912except ImportError: # pragma: no cover
913 IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
915 def valid_ident(s):
916 m = IDENTIFIER.match(s)
917 if not m:
918 raise ValueError('Not a valid Python identifier: %r' % s)
919 return True
921 # The ConvertingXXX classes are wrappers around standard Python containers,
922 # and they serve to convert any suitable values in the container. The
923 # conversion converts base dicts, lists and tuples to their wrapped
924 # equivalents, whereas strings which match a conversion format are converted
925 # appropriately.
926 #
927 # Each wrapper should have a configurator attribute holding the actual
928 # configurator to use for conversion.
930 class ConvertingDict(dict):
931 """A converting dictionary wrapper."""
933 def __getitem__(self, key):
934 value = dict.__getitem__(self, key)
935 result = self.configurator.convert(value)
936 # If the converted value is different, save for next time
937 if value is not result:
938 self[key] = result
939 if type(result) in (ConvertingDict, ConvertingList,
940 ConvertingTuple):
941 result.parent = self
942 result.key = key
943 return result
945 def get(self, key, default=None):
946 value = dict.get(self, key, default)
947 result = self.configurator.convert(value)
948 # If the converted value is different, save for next time
949 if value is not result:
950 self[key] = result
951 if type(result) in (ConvertingDict, ConvertingList,
952 ConvertingTuple):
953 result.parent = self
954 result.key = key
955 return result
957 def pop(self, key, default=None):
958 value = dict.pop(self, key, default)
959 result = self.configurator.convert(value)
960 if value is not result:
961 if type(result) in (ConvertingDict, ConvertingList,
962 ConvertingTuple):
963 result.parent = self
964 result.key = key
965 return result
967 class ConvertingList(list):
968 """A converting list wrapper."""
970 def __getitem__(self, key):
971 value = list.__getitem__(self, key)
972 result = self.configurator.convert(value)
973 # If the converted value is different, save for next time
974 if value is not result:
975 self[key] = result
976 if type(result) in (ConvertingDict, ConvertingList,
977 ConvertingTuple):
978 result.parent = self
979 result.key = key
980 return result
982 def pop(self, idx=-1):
983 value = list.pop(self, idx)
984 result = self.configurator.convert(value)
985 if value is not result:
986 if type(result) in (ConvertingDict, ConvertingList,
987 ConvertingTuple):
988 result.parent = self
989 return result
991 class ConvertingTuple(tuple):
992 """A converting tuple wrapper."""
994 def __getitem__(self, key):
995 value = tuple.__getitem__(self, key)
996 result = self.configurator.convert(value)
997 if value is not result:
998 if type(result) in (ConvertingDict, ConvertingList,
999 ConvertingTuple):
1000 result.parent = self
1001 result.key = key
1002 return result
1004 class BaseConfigurator(object):
1005 """
1006 The configurator base class which defines some useful defaults.
1007 """
1009 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
1011 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
1012 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
1013 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
1014 DIGIT_PATTERN = re.compile(r'^\d+$')
1016 value_converters = {
1017 'ext': 'ext_convert',
1018 'cfg': 'cfg_convert',
1019 }
1021 # We might want to use a different one, e.g. importlib
1022 importer = staticmethod(__import__)
1024 def __init__(self, config):
1025 self.config = ConvertingDict(config)
1026 self.config.configurator = self
1028 def resolve(self, s):
1029 """
1030 Resolve strings to objects using standard import and attribute
1031 syntax.
1032 """
1033 name = s.split('.')
1034 used = name.pop(0)
1035 try:
1036 found = self.importer(used)
1037 for frag in name:
1038 used += '.' + frag
1039 try:
1040 found = getattr(found, frag)
1041 except AttributeError:
1042 self.importer(used)
1043 found = getattr(found, frag)
1044 return found
1045 except ImportError:
1046 e, tb = sys.exc_info()[1:]
1047 v = ValueError('Cannot resolve %r: %s' % (s, e))
1048 v.__cause__, v.__traceback__ = e, tb
1049 raise v
1051 def ext_convert(self, value):
1052 """Default converter for the ext:// protocol."""
1053 return self.resolve(value)
1055 def cfg_convert(self, value):
1056 """Default converter for the cfg:// protocol."""
1057 rest = value
1058 m = self.WORD_PATTERN.match(rest)
1059 if m is None:
1060 raise ValueError("Unable to convert %r" % value)
1061 else:
1062 rest = rest[m.end():]
1063 d = self.config[m.groups()[0]]
1064 while rest:
1065 m = self.DOT_PATTERN.match(rest)
1066 if m:
1067 d = d[m.groups()[0]]
1068 else:
1069 m = self.INDEX_PATTERN.match(rest)
1070 if m:
1071 idx = m.groups()[0]
1072 if not self.DIGIT_PATTERN.match(idx):
1073 d = d[idx]
1074 else:
1075 try:
1076 n = int(
1077 idx
1078 ) # try as number first (most likely)
1079 d = d[n]
1080 except TypeError:
1081 d = d[idx]
1082 if m:
1083 rest = rest[m.end():]
1084 else:
1085 raise ValueError('Unable to convert '
1086 '%r at %r' % (value, rest))
1087 # rest should be empty
1088 return d
1090 def convert(self, value):
1091 """
1092 Convert values to an appropriate type. dicts, lists and tuples are
1093 replaced by their converting alternatives. Strings are checked to
1094 see if they have a conversion format and are converted if they do.
1095 """
1096 if not isinstance(value, ConvertingDict) and isinstance(
1097 value, dict):
1098 value = ConvertingDict(value)
1099 value.configurator = self
1100 elif not isinstance(value, ConvertingList) and isinstance(
1101 value, list):
1102 value = ConvertingList(value)
1103 value.configurator = self
1104 elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple):
1105 value = ConvertingTuple(value)
1106 value.configurator = self
1107 elif isinstance(value, string_types):
1108 m = self.CONVERT_PATTERN.match(value)
1109 if m:
1110 d = m.groupdict()
1111 prefix = d['prefix']
1112 converter = self.value_converters.get(prefix, None)
1113 if converter:
1114 suffix = d['suffix']
1115 converter = getattr(self, converter)
1116 value = converter(suffix)
1117 return value
1119 def configure_custom(self, config):
1120 """Configure an object with a user-supplied factory."""
1121 c = config.pop('()')
1122 if not callable(c):
1123 c = self.resolve(c)
1124 props = config.pop('.', None)
1125 # Check for valid identifiers
1126 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
1127 result = c(**kwargs)
1128 if props:
1129 for name, value in props.items():
1130 setattr(result, name, value)
1131 return result
1133 def as_tuple(self, value):
1134 """Utility function which converts lists to tuples."""
1135 if isinstance(value, list):
1136 value = tuple(value)
1137 return value