brain_six.py 7.4 KB

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