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