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