brain_six.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. # Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
  2. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
  3. # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
  4. # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
  5. # Copyright (c) 2020 Ram Rachum <ram@rachum.com>
  6. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  7. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  8. # Copyright (c) 2021 Artsiom Kaval <lezeroq@gmail.com>
  9. # Copyright (c) 2021 Francis Charette Migneault <francis.charette.migneault@gmail.com>
  10. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  11. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  12. """Astroid hooks for six module."""
  13. from textwrap import dedent
  14. from astroid import nodes
  15. from astroid.brain.helpers import register_module_extender
  16. from astroid.builder import AstroidBuilder
  17. from astroid.exceptions import (
  18. AstroidBuildingError,
  19. AttributeInferenceError,
  20. InferenceError,
  21. )
  22. from astroid.manager import AstroidManager
  23. SIX_ADD_METACLASS = "six.add_metaclass"
  24. SIX_WITH_METACLASS = "six.with_metaclass"
  25. def default_predicate(line):
  26. return line.strip()
  27. def _indent(text, prefix, predicate=default_predicate):
  28. """Adds 'prefix' to the beginning of selected lines in 'text'.
  29. If 'predicate' is provided, 'prefix' will only be added to the lines
  30. where 'predicate(line)' is True. If 'predicate' is not provided,
  31. it will default to adding 'prefix' to all non-empty lines that do not
  32. consist solely of whitespace characters.
  33. """
  34. def prefixed_lines():
  35. for line in text.splitlines(True):
  36. yield prefix + line if predicate(line) else line
  37. return "".join(prefixed_lines())
  38. _IMPORTS = """
  39. import _io
  40. cStringIO = _io.StringIO
  41. filter = filter
  42. from itertools import filterfalse
  43. input = input
  44. from sys import intern
  45. map = map
  46. range = range
  47. from importlib import reload
  48. reload_module = lambda module: reload(module)
  49. from functools import reduce
  50. from shlex import quote as shlex_quote
  51. from io import StringIO
  52. from collections import UserDict, UserList, UserString
  53. xrange = range
  54. zip = zip
  55. from itertools import zip_longest
  56. import builtins
  57. import configparser
  58. import copyreg
  59. import _dummy_thread
  60. import http.cookiejar as http_cookiejar
  61. import http.cookies as http_cookies
  62. import html.entities as html_entities
  63. import html.parser as html_parser
  64. import http.client as http_client
  65. import http.server as http_server
  66. BaseHTTPServer = CGIHTTPServer = SimpleHTTPServer = http.server
  67. import pickle as cPickle
  68. import queue
  69. import reprlib
  70. import socketserver
  71. import _thread
  72. import winreg
  73. import xmlrpc.server as xmlrpc_server
  74. import xmlrpc.client as xmlrpc_client
  75. import urllib.robotparser as urllib_robotparser
  76. import email.mime.multipart as email_mime_multipart
  77. import email.mime.nonmultipart as email_mime_nonmultipart
  78. import email.mime.text as email_mime_text
  79. import email.mime.base as email_mime_base
  80. import urllib.parse as urllib_parse
  81. import urllib.error as urllib_error
  82. import tkinter
  83. import tkinter.dialog as tkinter_dialog
  84. import tkinter.filedialog as tkinter_filedialog
  85. import tkinter.scrolledtext as tkinter_scrolledtext
  86. import tkinter.simpledialog as tkinder_simpledialog
  87. import tkinter.tix as tkinter_tix
  88. import tkinter.ttk as tkinter_ttk
  89. import tkinter.constants as tkinter_constants
  90. import tkinter.dnd as tkinter_dnd
  91. import tkinter.colorchooser as tkinter_colorchooser
  92. import tkinter.commondialog as tkinter_commondialog
  93. import tkinter.filedialog as tkinter_tkfiledialog
  94. import tkinter.font as tkinter_font
  95. import tkinter.messagebox as tkinter_messagebox
  96. import urllib
  97. import urllib.request as urllib_request
  98. import urllib.robotparser as urllib_robotparser
  99. import urllib.parse as urllib_parse
  100. import urllib.error as urllib_error
  101. """
  102. def six_moves_transform():
  103. code = dedent(
  104. """
  105. class Moves(object):
  106. {}
  107. moves = Moves()
  108. """
  109. ).format(_indent(_IMPORTS, " "))
  110. module = AstroidBuilder(AstroidManager()).string_build(code)
  111. module.name = "six.moves"
  112. return module
  113. def _six_fail_hook(modname):
  114. """Fix six.moves imports due to the dynamic nature of this
  115. class.
  116. Construct a pseudo-module which contains all the necessary imports
  117. for six
  118. :param modname: Name of failed module
  119. :type modname: str
  120. :return: An astroid module
  121. :rtype: nodes.Module
  122. """
  123. attribute_of = modname != "six.moves" and modname.startswith("six.moves")
  124. if modname != "six.moves" and not attribute_of:
  125. raise AstroidBuildingError(modname=modname)
  126. module = AstroidBuilder(AstroidManager()).string_build(_IMPORTS)
  127. module.name = "six.moves"
  128. if attribute_of:
  129. # Facilitate import of submodules in Moves
  130. start_index = len(module.name)
  131. attribute = modname[start_index:].lstrip(".").replace(".", "_")
  132. try:
  133. import_attr = module.getattr(attribute)[0]
  134. except AttributeInferenceError as exc:
  135. raise AstroidBuildingError(modname=modname) from exc
  136. if isinstance(import_attr, nodes.Import):
  137. submodule = AstroidManager().ast_from_module_name(import_attr.names[0][0])
  138. return submodule
  139. # Let dummy submodule imports pass through
  140. # This will cause an Uninferable result, which is okay
  141. return module
  142. def _looks_like_decorated_with_six_add_metaclass(node):
  143. if not node.decorators:
  144. return False
  145. for decorator in node.decorators.nodes:
  146. if not isinstance(decorator, nodes.Call):
  147. continue
  148. if decorator.func.as_string() == SIX_ADD_METACLASS:
  149. return True
  150. return False
  151. def transform_six_add_metaclass(node): # pylint: disable=inconsistent-return-statements
  152. """Check if the given class node is decorated with *six.add_metaclass*
  153. If so, inject its argument as the metaclass of the underlying class.
  154. """
  155. if not node.decorators:
  156. return
  157. for decorator in node.decorators.nodes:
  158. if not isinstance(decorator, nodes.Call):
  159. continue
  160. try:
  161. func = next(decorator.func.infer())
  162. except (InferenceError, StopIteration):
  163. continue
  164. if func.qname() == SIX_ADD_METACLASS and decorator.args:
  165. metaclass = decorator.args[0]
  166. node._metaclass = metaclass
  167. return node
  168. return
  169. def _looks_like_nested_from_six_with_metaclass(node):
  170. if len(node.bases) != 1:
  171. return False
  172. base = node.bases[0]
  173. if not isinstance(base, nodes.Call):
  174. return False
  175. try:
  176. if hasattr(base.func, "expr"):
  177. # format when explicit 'six.with_metaclass' is used
  178. mod = base.func.expr.name
  179. func = base.func.attrname
  180. func = f"{mod}.{func}"
  181. else:
  182. # format when 'with_metaclass' is used directly (local import from six)
  183. # check reference module to avoid 'with_metaclass' name clashes
  184. mod = base.parent.parent
  185. import_from = mod.locals["with_metaclass"][0]
  186. func = f"{import_from.modname}.{base.func.name}"
  187. except (AttributeError, KeyError, IndexError):
  188. return False
  189. return func == SIX_WITH_METACLASS
  190. def transform_six_with_metaclass(node):
  191. """Check if the given class node is defined with *six.with_metaclass*
  192. If so, inject its argument as the metaclass of the underlying class.
  193. """
  194. call = node.bases[0]
  195. node._metaclass = call.args[0]
  196. return node
  197. register_module_extender(AstroidManager(), "six", six_moves_transform)
  198. register_module_extender(
  199. AstroidManager(), "requests.packages.urllib3.packages.six", six_moves_transform
  200. )
  201. AstroidManager().register_failed_import_hook(_six_fail_hook)
  202. AstroidManager().register_transform(
  203. nodes.ClassDef,
  204. transform_six_add_metaclass,
  205. _looks_like_decorated_with_six_add_metaclass,
  206. )
  207. AstroidManager().register_transform(
  208. nodes.ClassDef,
  209. transform_six_with_metaclass,
  210. _looks_like_nested_from_six_with_metaclass,
  211. )