Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

compat.py 40KB

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