1"""
2Python 3 reorganized the standard library (PEP 3108). This module exposes
3several standard library modules to Python 2 under their new Python 3
4names.
5
6It is designed to be used as follows::
7
8 from future import standard_library
9 standard_library.install_aliases()
10
11And then these normal Py3 imports work on both Py3 and Py2::
12
13 import builtins
14 import copyreg
15 import queue
16 import reprlib
17 import socketserver
18 import winreg # on Windows only
19 import test.support
20 import html, html.parser, html.entities
21 import http, http.client, http.server
22 import http.cookies, http.cookiejar
23 import urllib.parse, urllib.request, urllib.response, urllib.error, urllib.robotparser
24 import xmlrpc.client, xmlrpc.server
25
26 import _thread
27 import _dummy_thread
28 import _markupbase
29
30 from itertools import filterfalse, zip_longest
31 from sys import intern
32 from collections import UserDict, UserList, UserString
33 from collections import OrderedDict, Counter, ChainMap # even on Py2.6
34 from subprocess import getoutput, getstatusoutput
35 from subprocess import check_output # even on Py2.6
36 from multiprocessing import SimpleQueue
37
38(The renamed modules and functions are still available under their old
39names on Python 2.)
40
41This is a cleaner alternative to this idiom (see
42http://docs.pythonsprints.com/python3_porting/py-porting.html)::
43
44 try:
45 import queue
46 except ImportError:
47 import Queue as queue
48
49
50Limitations
51-----------
52We don't currently support these modules, but would like to::
53
54 import dbm
55 import dbm.dumb
56 import dbm.gnu
57 import collections.abc # on Py33
58 import pickle # should (optionally) bring in cPickle on Python 2
59
60"""
61
62from __future__ import absolute_import, division, print_function
63
64import sys
65import logging
66# imp was deprecated in python 3.6
67if sys.version_info >= (3, 6):
68 import importlib as imp
69else:
70 import imp
71import contextlib
72import copy
73import os
74
75# Make a dedicated logger; leave the root logger to be configured
76# by the application.
77flog = logging.getLogger('future_stdlib')
78_formatter = logging.Formatter(logging.BASIC_FORMAT)
79_handler = logging.StreamHandler()
80_handler.setFormatter(_formatter)
81flog.addHandler(_handler)
82flog.setLevel(logging.WARN)
83
84from future.utils import PY2, PY3
85
86# The modules that are defined under the same names on Py3 but with
87# different contents in a significant way (e.g. submodules) are:
88# pickle (fast one)
89# dbm
90# urllib
91# test
92# email
93
94REPLACED_MODULES = set(['test', 'urllib', 'pickle', 'dbm']) # add email and dbm when we support it
95
96# The following module names are not present in Python 2.x, so they cause no
97# potential clashes between the old and new names:
98# http
99# html
100# tkinter
101# xmlrpc
102# Keys: Py2 / real module names
103# Values: Py3 / simulated module names
104RENAMES = {
105 # 'cStringIO': 'io', # there's a new io module in Python 2.6
106 # that provides StringIO and BytesIO
107 # 'StringIO': 'io', # ditto
108 # 'cPickle': 'pickle',
109 '__builtin__': 'builtins',
110 'copy_reg': 'copyreg',
111 'Queue': 'queue',
112 'future.moves.socketserver': 'socketserver',
113 'ConfigParser': 'configparser',
114 'repr': 'reprlib',
115 'multiprocessing.queues': 'multiprocessing',
116 # 'FileDialog': 'tkinter.filedialog',
117 # 'tkFileDialog': 'tkinter.filedialog',
118 # 'SimpleDialog': 'tkinter.simpledialog',
119 # 'tkSimpleDialog': 'tkinter.simpledialog',
120 # 'tkColorChooser': 'tkinter.colorchooser',
121 # 'tkCommonDialog': 'tkinter.commondialog',
122 # 'Dialog': 'tkinter.dialog',
123 # 'Tkdnd': 'tkinter.dnd',
124 # 'tkFont': 'tkinter.font',
125 # 'tkMessageBox': 'tkinter.messagebox',
126 # 'ScrolledText': 'tkinter.scrolledtext',
127 # 'Tkconstants': 'tkinter.constants',
128 # 'Tix': 'tkinter.tix',
129 # 'ttk': 'tkinter.ttk',
130 # 'Tkinter': 'tkinter',
131 '_winreg': 'winreg',
132 'thread': '_thread',
133 'dummy_thread': '_dummy_thread' if sys.version_info < (3, 9) else '_thread',
134 # 'anydbm': 'dbm', # causes infinite import loop
135 # 'whichdb': 'dbm', # causes infinite import loop
136 # anydbm and whichdb are handled by fix_imports2
137 # 'dbhash': 'dbm.bsd',
138 # 'dumbdbm': 'dbm.dumb',
139 # 'dbm': 'dbm.ndbm',
140 # 'gdbm': 'dbm.gnu',
141 'future.moves.xmlrpc': 'xmlrpc',
142 # 'future.backports.email': 'email', # for use by urllib
143 # 'DocXMLRPCServer': 'xmlrpc.server',
144 # 'SimpleXMLRPCServer': 'xmlrpc.server',
145 # 'httplib': 'http.client',
146 # 'htmlentitydefs' : 'html.entities',
147 # 'HTMLParser' : 'html.parser',
148 # 'Cookie': 'http.cookies',
149 # 'cookielib': 'http.cookiejar',
150 # 'BaseHTTPServer': 'http.server',
151 # 'SimpleHTTPServer': 'http.server',
152 # 'CGIHTTPServer': 'http.server',
153 # 'future.backports.test': 'test', # primarily for renaming test_support to support
154 # 'commands': 'subprocess',
155 # 'urlparse' : 'urllib.parse',
156 # 'robotparser' : 'urllib.robotparser',
157 # 'abc': 'collections.abc', # for Py33
158 # 'future.utils.six.moves.html': 'html',
159 # 'future.utils.six.moves.http': 'http',
160 'future.moves.html': 'html',
161 'future.moves.http': 'http',
162 # 'future.backports.urllib': 'urllib',
163 # 'future.utils.six.moves.urllib': 'urllib',
164 'future.moves._markupbase': '_markupbase',
165 }
166
167
168# It is complicated and apparently brittle to mess around with the
169# ``sys.modules`` cache in order to support "import urllib" meaning two
170# different things (Py2.7 urllib and backported Py3.3-like urllib) in different
171# contexts. So we require explicit imports for these modules.
172assert len(set(RENAMES.values()) & set(REPLACED_MODULES)) == 0
173
174
175# Harmless renames that we can insert.
176# These modules need names from elsewhere being added to them:
177# subprocess: should provide getoutput and other fns from commands
178# module but these fns are missing: getstatus, mk2arg,
179# mkarg
180# re: needs an ASCII constant that works compatibly with Py3
181
182# etc: see lib2to3/fixes/fix_imports.py
183
184# (New module name, new object name, old module name, old object name)
185MOVES = [('collections', 'UserList', 'UserList', 'UserList'),
186 ('collections', 'UserDict', 'UserDict', 'UserDict'),
187 ('collections', 'UserString','UserString', 'UserString'),
188 ('collections', 'ChainMap', 'future.backports.misc', 'ChainMap'),
189 ('itertools', 'filterfalse','itertools', 'ifilterfalse'),
190 ('itertools', 'zip_longest','itertools', 'izip_longest'),
191 ('sys', 'intern','__builtin__', 'intern'),
192 ('multiprocessing', 'SimpleQueue', 'multiprocessing.queues', 'SimpleQueue'),
193 # The re module has no ASCII flag in Py2, but this is the default.
194 # Set re.ASCII to a zero constant. stat.ST_MODE just happens to be one
195 # (and it exists on Py2.6+).
196 ('re', 'ASCII','stat', 'ST_MODE'),
197 ('base64', 'encodebytes','base64', 'encodestring'),
198 ('base64', 'decodebytes','base64', 'decodestring'),
199 ('subprocess', 'getoutput', 'commands', 'getoutput'),
200 ('subprocess', 'getstatusoutput', 'commands', 'getstatusoutput'),
201 ('subprocess', 'check_output', 'future.backports.misc', 'check_output'),
202 ('math', 'ceil', 'future.backports.misc', 'ceil'),
203 ('collections', 'OrderedDict', 'future.backports.misc', 'OrderedDict'),
204 ('collections', 'Counter', 'future.backports.misc', 'Counter'),
205 ('collections', 'ChainMap', 'future.backports.misc', 'ChainMap'),
206 ('itertools', 'count', 'future.backports.misc', 'count'),
207 ('reprlib', 'recursive_repr', 'future.backports.misc', 'recursive_repr'),
208 ('functools', 'cmp_to_key', 'future.backports.misc', 'cmp_to_key'),
209
210# This is no use, since "import urllib.request" etc. still fails:
211# ('urllib', 'error', 'future.moves.urllib', 'error'),
212# ('urllib', 'parse', 'future.moves.urllib', 'parse'),
213# ('urllib', 'request', 'future.moves.urllib', 'request'),
214# ('urllib', 'response', 'future.moves.urllib', 'response'),
215# ('urllib', 'robotparser', 'future.moves.urllib', 'robotparser'),
216 ]
217
218
219# A minimal example of an import hook:
220# class WarnOnImport(object):
221# def __init__(self, *args):
222# self.module_names = args
223#
224# def find_module(self, fullname, path=None):
225# if fullname in self.module_names:
226# self.path = path
227# return self
228# return None
229#
230# def load_module(self, name):
231# if name in sys.modules:
232# return sys.modules[name]
233# module_info = imp.find_module(name, self.path)
234# module = imp.load_module(name, *module_info)
235# sys.modules[name] = module
236# flog.warning("Imported deprecated module %s", name)
237# return module
238
239
240class RenameImport(object):
241 """
242 A class for import hooks mapping Py3 module names etc. to the Py2 equivalents.
243 """
244 # Different RenameImport classes are created when importing this module from
245 # different source files. This causes isinstance(hook, RenameImport) checks
246 # to produce inconsistent results. We add this RENAMER attribute here so
247 # remove_hooks() and install_hooks() can find instances of these classes
248 # easily:
249 RENAMER = True
250
251 def __init__(self, old_to_new):
252 '''
253 Pass in a dictionary-like object mapping from old names to new
254 names. E.g. {'ConfigParser': 'configparser', 'cPickle': 'pickle'}
255 '''
256 self.old_to_new = old_to_new
257 both = set(old_to_new.keys()) & set(old_to_new.values())
258 assert (len(both) == 0 and
259 len(set(old_to_new.values())) == len(old_to_new.values())), \
260 'Ambiguity in renaming (handler not implemented)'
261 self.new_to_old = dict((new, old) for (old, new) in old_to_new.items())
262
263 def find_module(self, fullname, path=None):
264 # Handles hierarchical importing: package.module.module2
265 new_base_names = set([s.split('.')[0] for s in self.new_to_old])
266 # Before v0.12: Was: if fullname in set(self.old_to_new) | new_base_names:
267 if fullname in new_base_names:
268 return self
269 return None
270
271 def load_module(self, name):
272 path = None
273 if name in sys.modules:
274 return sys.modules[name]
275 elif name in self.new_to_old:
276 # New name. Look up the corresponding old (Py2) name:
277 oldname = self.new_to_old[name]
278 module = self._find_and_load_module(oldname)
279 # module.__future_module__ = True
280 else:
281 module = self._find_and_load_module(name)
282 # In any case, make it available under the requested (Py3) name
283 sys.modules[name] = module
284 return module
285
286 def _find_and_load_module(self, name, path=None):
287 """
288 Finds and loads it. But if there's a . in the name, handles it
289 properly.
290 """
291 bits = name.split('.')
292 while len(bits) > 1:
293 # Treat the first bit as a package
294 packagename = bits.pop(0)
295 package = self._find_and_load_module(packagename, path)
296 try:
297 path = package.__path__
298 except AttributeError:
299 # This could be e.g. moves.
300 flog.debug('Package {0} has no __path__.'.format(package))
301 if name in sys.modules:
302 return sys.modules[name]
303 flog.debug('What to do here?')
304
305 name = bits[0]
306 module_info = imp.find_module(name, path)
307 return imp.load_module(name, *module_info)
308
309
310class hooks(object):
311 """
312 Acts as a context manager. Saves the state of sys.modules and restores it
313 after the 'with' block.
314
315 Use like this:
316
317 >>> from future import standard_library
318 >>> with standard_library.hooks():
319 ... import http.client
320 >>> import requests
321
322 For this to work, http.client will be scrubbed from sys.modules after the
323 'with' block. That way the modules imported in the 'with' block will
324 continue to be accessible in the current namespace but not from any
325 imported modules (like requests).
326 """
327 def __enter__(self):
328 # flog.debug('Entering hooks context manager')
329 self.old_sys_modules = copy.copy(sys.modules)
330 self.hooks_were_installed = detect_hooks()
331 # self.scrubbed = scrub_py2_sys_modules()
332 install_hooks()
333 return self
334
335 def __exit__(self, *args):
336 # flog.debug('Exiting hooks context manager')
337 # restore_sys_modules(self.scrubbed)
338 if not self.hooks_were_installed:
339 remove_hooks()
340 # scrub_future_sys_modules()
341
342# Sanity check for is_py2_stdlib_module(): We aren't replacing any
343# builtin modules names:
344if PY2:
345 assert len(set(RENAMES.values()) & set(sys.builtin_module_names)) == 0
346
347
348def is_py2_stdlib_module(m):
349 """
350 Tries to infer whether the module m is from the Python 2 standard library.
351 This may not be reliable on all systems.
352 """
353 if PY3:
354 return False
355 if not 'stdlib_path' in is_py2_stdlib_module.__dict__:
356 stdlib_files = [contextlib.__file__, os.__file__, copy.__file__]
357 stdlib_paths = [os.path.split(f)[0] for f in stdlib_files]
358 if not len(set(stdlib_paths)) == 1:
359 # This seems to happen on travis-ci.org. Very strange. We'll try to
360 # ignore it.
361 flog.warn('Multiple locations found for the Python standard '
362 'library: %s' % stdlib_paths)
363 # Choose the first one arbitrarily
364 is_py2_stdlib_module.stdlib_path = stdlib_paths[0]
365
366 if m.__name__ in sys.builtin_module_names:
367 return True
368
369 if hasattr(m, '__file__'):
370 modpath = os.path.split(m.__file__)
371 if (modpath[0].startswith(is_py2_stdlib_module.stdlib_path) and
372 'site-packages' not in modpath[0]):
373 return True
374
375 return False
376
377
378def scrub_py2_sys_modules():
379 """
380 Removes any Python 2 standard library modules from ``sys.modules`` that
381 would interfere with Py3-style imports using import hooks. Examples are
382 modules with the same names (like urllib or email).
383
384 (Note that currently import hooks are disabled for modules like these
385 with ambiguous names anyway ...)
386 """
387 if PY3:
388 return {}
389 scrubbed = {}
390 for modulename in REPLACED_MODULES & set(RENAMES.keys()):
391 if not modulename in sys.modules:
392 continue
393
394 module = sys.modules[modulename]
395
396 if is_py2_stdlib_module(module):
397 flog.debug('Deleting (Py2) {} from sys.modules'.format(modulename))
398 scrubbed[modulename] = sys.modules[modulename]
399 del sys.modules[modulename]
400 return scrubbed
401
402
403def scrub_future_sys_modules():
404 """
405 Deprecated.
406 """
407 return {}
408
409class suspend_hooks(object):
410 """
411 Acts as a context manager. Use like this:
412
413 >>> from future import standard_library
414 >>> standard_library.install_hooks()
415 >>> import http.client
416 >>> # ...
417 >>> with standard_library.suspend_hooks():
418 >>> import requests # incompatible with ``future``'s standard library hooks
419
420 If the hooks were disabled before the context, they are not installed when
421 the context is left.
422 """
423 def __enter__(self):
424 self.hooks_were_installed = detect_hooks()
425 remove_hooks()
426 # self.scrubbed = scrub_future_sys_modules()
427 return self
428
429 def __exit__(self, *args):
430 if self.hooks_were_installed:
431 install_hooks()
432 # restore_sys_modules(self.scrubbed)
433
434
435def restore_sys_modules(scrubbed):
436 """
437 Add any previously scrubbed modules back to the sys.modules cache,
438 but only if it's safe to do so.
439 """
440 clash = set(sys.modules) & set(scrubbed)
441 if len(clash) != 0:
442 # If several, choose one arbitrarily to raise an exception about
443 first = list(clash)[0]
444 raise ImportError('future module {} clashes with Py2 module'
445 .format(first))
446 sys.modules.update(scrubbed)
447
448
449def install_aliases():
450 """
451 Monkey-patches the standard library in Py2.6/7 to provide
452 aliases for better Py3 compatibility.
453 """
454 if PY3:
455 return
456 # if hasattr(install_aliases, 'run_already'):
457 # return
458 for (newmodname, newobjname, oldmodname, oldobjname) in MOVES:
459 __import__(newmodname)
460 # We look up the module in sys.modules because __import__ just returns the
461 # top-level package:
462 newmod = sys.modules[newmodname]
463 # newmod.__future_module__ = True
464
465 __import__(oldmodname)
466 oldmod = sys.modules[oldmodname]
467
468 obj = getattr(oldmod, oldobjname)
469 setattr(newmod, newobjname, obj)
470
471 # Hack for urllib so it appears to have the same structure on Py2 as on Py3
472 import urllib
473 from future.backports.urllib import request
474 from future.backports.urllib import response
475 from future.backports.urllib import parse
476 from future.backports.urllib import error
477 from future.backports.urllib import robotparser
478 urllib.request = request
479 urllib.response = response
480 urllib.parse = parse
481 urllib.error = error
482 urllib.robotparser = robotparser
483 sys.modules['urllib.request'] = request
484 sys.modules['urllib.response'] = response
485 sys.modules['urllib.parse'] = parse
486 sys.modules['urllib.error'] = error
487 sys.modules['urllib.robotparser'] = robotparser
488
489 # Patch the test module so it appears to have the same structure on Py2 as on Py3
490 try:
491 import test
492 except ImportError:
493 pass
494 try:
495 from future.moves.test import support
496 except ImportError:
497 pass
498 else:
499 test.support = support
500 sys.modules['test.support'] = support
501
502 # Patch the dbm module so it appears to have the same structure on Py2 as on Py3
503 try:
504 import dbm
505 except ImportError:
506 pass
507 else:
508 from future.moves.dbm import dumb
509 dbm.dumb = dumb
510 sys.modules['dbm.dumb'] = dumb
511 try:
512 from future.moves.dbm import gnu
513 except ImportError:
514 pass
515 else:
516 dbm.gnu = gnu
517 sys.modules['dbm.gnu'] = gnu
518 try:
519 from future.moves.dbm import ndbm
520 except ImportError:
521 pass
522 else:
523 dbm.ndbm = ndbm
524 sys.modules['dbm.ndbm'] = ndbm
525
526 # install_aliases.run_already = True
527
528
529def install_hooks():
530 """
531 This function installs the future.standard_library import hook into
532 sys.meta_path.
533 """
534 if PY3:
535 return
536
537 install_aliases()
538
539 flog.debug('sys.meta_path was: {0}'.format(sys.meta_path))
540 flog.debug('Installing hooks ...')
541
542 # Add it unless it's there already
543 newhook = RenameImport(RENAMES)
544 if not detect_hooks():
545 sys.meta_path.append(newhook)
546 flog.debug('sys.meta_path is now: {0}'.format(sys.meta_path))
547
548
549def enable_hooks():
550 """
551 Deprecated. Use install_hooks() instead. This will be removed by
552 ``future`` v1.0.
553 """
554 install_hooks()
555
556
557def remove_hooks(scrub_sys_modules=False):
558 """
559 This function removes the import hook from sys.meta_path.
560 """
561 if PY3:
562 return
563 flog.debug('Uninstalling hooks ...')
564 # Loop backwards, so deleting items keeps the ordering:
565 for i, hook in list(enumerate(sys.meta_path))[::-1]:
566 if hasattr(hook, 'RENAMER'):
567 del sys.meta_path[i]
568
569 # Explicit is better than implicit. In the future the interface should
570 # probably change so that scrubbing the import hooks requires a separate
571 # function call. Left as is for now for backward compatibility with
572 # v0.11.x.
573 if scrub_sys_modules:
574 scrub_future_sys_modules()
575
576
577def disable_hooks():
578 """
579 Deprecated. Use remove_hooks() instead. This will be removed by
580 ``future`` v1.0.
581 """
582 remove_hooks()
583
584
585def detect_hooks():
586 """
587 Returns True if the import hooks are installed, False if not.
588 """
589 flog.debug('Detecting hooks ...')
590 present = any([hasattr(hook, 'RENAMER') for hook in sys.meta_path])
591 if present:
592 flog.debug('Detected.')
593 else:
594 flog.debug('Not detected.')
595 return present
596
597
598# As of v0.12, this no longer happens implicitly:
599# if not PY3:
600# install_hooks()
601
602
603if not hasattr(sys, 'py2_modules'):
604 sys.py2_modules = {}
605
606def cache_py2_modules():
607 """
608 Currently this function is unneeded, as we are not attempting to provide import hooks
609 for modules with ambiguous names: email, urllib, pickle.
610 """
611 if len(sys.py2_modules) != 0:
612 return
613 assert not detect_hooks()
614 import urllib
615 sys.py2_modules['urllib'] = urllib
616
617 import email
618 sys.py2_modules['email'] = email
619
620 import pickle
621 sys.py2_modules['pickle'] = pickle
622
623 # Not all Python installations have test module. (Anaconda doesn't, for example.)
624 # try:
625 # import test
626 # except ImportError:
627 # sys.py2_modules['test'] = None
628 # sys.py2_modules['test'] = test
629
630 # import dbm
631 # sys.py2_modules['dbm'] = dbm
632
633
634def import_(module_name, backport=False):
635 """
636 Pass a (potentially dotted) module name of a Python 3 standard library
637 module. This function imports the module compatibly on Py2 and Py3 and
638 returns the top-level module.
639
640 Example use:
641 >>> http = import_('http.client')
642 >>> http = import_('http.server')
643 >>> urllib = import_('urllib.request')
644
645 Then:
646 >>> conn = http.client.HTTPConnection(...)
647 >>> response = urllib.request.urlopen('http://mywebsite.com')
648 >>> # etc.
649
650 Use as follows:
651 >>> package_name = import_(module_name)
652
653 On Py3, equivalent to this:
654
655 >>> import module_name
656
657 On Py2, equivalent to this if backport=False:
658
659 >>> from future.moves import module_name
660
661 or to this if backport=True:
662
663 >>> from future.backports import module_name
664
665 except that it also handles dotted module names such as ``http.client``
666 The effect then is like this:
667
668 >>> from future.backports import module
669 >>> from future.backports.module import submodule
670 >>> module.submodule = submodule
671
672 Note that this would be a SyntaxError in Python:
673
674 >>> from future.backports import http.client
675
676 """
677 # Python 2.6 doesn't have importlib in the stdlib, so it requires
678 # the backported ``importlib`` package from PyPI as a dependency to use
679 # this function:
680 import importlib
681
682 if PY3:
683 return __import__(module_name)
684 else:
685 # client.blah = blah
686 # Then http.client = client
687 # etc.
688 if backport:
689 prefix = 'future.backports'
690 else:
691 prefix = 'future.moves'
692 parts = prefix.split('.') + module_name.split('.')
693
694 modules = []
695 for i, part in enumerate(parts):
696 sofar = '.'.join(parts[:i+1])
697 modules.append(importlib.import_module(sofar))
698 for i, part in reversed(list(enumerate(parts))):
699 if i == 0:
700 break
701 setattr(modules[i-1], part, modules[i])
702
703 # Return the next-most top-level module after future.backports / future.moves:
704 return modules[2]
705
706
707def from_import(module_name, *symbol_names, **kwargs):
708 """
709 Example use:
710 >>> HTTPConnection = from_import('http.client', 'HTTPConnection')
711 >>> HTTPServer = from_import('http.server', 'HTTPServer')
712 >>> urlopen, urlparse = from_import('urllib.request', 'urlopen', 'urlparse')
713
714 Equivalent to this on Py3:
715
716 >>> from module_name import symbol_names[0], symbol_names[1], ...
717
718 and this on Py2:
719
720 >>> from future.moves.module_name import symbol_names[0], ...
721
722 or:
723
724 >>> from future.backports.module_name import symbol_names[0], ...
725
726 except that it also handles dotted module names such as ``http.client``.
727 """
728
729 if PY3:
730 return __import__(module_name)
731 else:
732 if 'backport' in kwargs and bool(kwargs['backport']):
733 prefix = 'future.backports'
734 else:
735 prefix = 'future.moves'
736 parts = prefix.split('.') + module_name.split('.')
737 module = importlib.import_module(prefix + '.' + module_name)
738 output = [getattr(module, name) for name in symbol_names]
739 if len(output) == 1:
740 return output[0]
741 else:
742 return output
743
744
745class exclude_local_folder_imports(object):
746 """
747 A context-manager that prevents standard library modules like configparser
748 from being imported from the local python-future source folder on Py3.
749
750 (This was need prior to v0.16.0 because the presence of a configparser
751 folder would otherwise have prevented setuptools from running on Py3. Maybe
752 it's not needed any more?)
753 """
754 def __init__(self, *args):
755 assert len(args) > 0
756 self.module_names = args
757 # Disallow dotted module names like http.client:
758 if any(['.' in m for m in self.module_names]):
759 raise NotImplementedError('Dotted module names are not supported')
760
761 def __enter__(self):
762 self.old_sys_path = copy.copy(sys.path)
763 self.old_sys_modules = copy.copy(sys.modules)
764 if sys.version_info[0] < 3:
765 return
766 # The presence of all these indicates we've found our source folder,
767 # because `builtins` won't have been installed in site-packages by setup.py:
768 FUTURE_SOURCE_SUBFOLDERS = ['future', 'past', 'libfuturize', 'libpasteurize', 'builtins']
769
770 # Look for the future source folder:
771 for folder in self.old_sys_path:
772 if all([os.path.exists(os.path.join(folder, subfolder))
773 for subfolder in FUTURE_SOURCE_SUBFOLDERS]):
774 # Found it. Remove it.
775 sys.path.remove(folder)
776
777 # Ensure we import the system module:
778 for m in self.module_names:
779 # Delete the module and any submodules from sys.modules:
780 # for key in list(sys.modules):
781 # if key == m or key.startswith(m + '.'):
782 # try:
783 # del sys.modules[key]
784 # except KeyError:
785 # pass
786 try:
787 module = __import__(m, level=0)
788 except ImportError:
789 # There's a problem importing the system module. E.g. the
790 # winreg module is not available except on Windows.
791 pass
792
793 def __exit__(self, *args):
794 # Restore sys.path and sys.modules:
795 sys.path = self.old_sys_path
796 for m in set(self.old_sys_modules.keys()) - set(sys.modules.keys()):
797 sys.modules[m] = self.old_sys_modules[m]
798
799TOP_LEVEL_MODULES = ['builtins',
800 'copyreg',
801 'html',
802 'http',
803 'queue',
804 'reprlib',
805 'socketserver',
806 'test',
807 'tkinter',
808 'winreg',
809 'xmlrpc',
810 '_dummy_thread',
811 '_markupbase',
812 '_thread',
813 ]
814
815def import_top_level_modules():
816 with exclude_local_folder_imports(*TOP_LEVEL_MODULES):
817 for m in TOP_LEVEL_MODULES:
818 try:
819 __import__(m)
820 except ImportError: # e.g. winreg
821 pass