forked from spyder-ide/spyder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrope_patch.py
210 lines (191 loc) · 9.46 KB
/
rope_patch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# -*- coding: utf-8 -*-
#
# Copyright © 2011 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)
"""
Patching rope:
[1] For compatibility with Spyder's standalone version, built with py2exe or
cx_Freeze
[2] For better performance, see this thread:
http://groups.google.com/group/rope-dev/browse_thread/thread/57de5731f202537a
[3] To avoid considering folders without __init__.py as Python packages, thus
avoiding side effects as non-working introspection features on a Python
module or package when a folder in current directory has the same name.
See this thread:
http://groups.google.com/group/rope-dev/browse_thread/thread/924c4b5a6268e618
[4] To avoid rope adding a 2 spaces indent to every docstring it gets, because
it breaks the work of Sphinx on the Object Inspector. Also, to better
control how to get calltips and docstrings of forced builtin objects.
[5] To make matplotlib return its docstrings in proper rst, instead of a mix
of rst and plain text.
"""
def apply():
"""Monkey patching rope
See [1], [2], [3], [4] and [5] in module docstring."""
import rope
if rope.VERSION not in ('0.10.2', '0.9.4-1', '0.9.4', '0.9.3', '0.9.2'):
raise ImportError("rope %s can't be patched" % rope.VERSION)
# [1] Patching project.Project for compatibility with py2exe/cx_Freeze
# distributions
from spyderlib.baseconfig import is_py2exe_or_cx_Freeze
if is_py2exe_or_cx_Freeze():
from rope.base import project
class PatchedProject(project.Project):
def _default_config(self):
# py2exe/cx_Freeze distribution
from spyderlib.baseconfig import get_module_source_path
fname = get_module_source_path('spyderlib',
'default_config.py')
return open(fname, 'rb').read()
project.Project = PatchedProject
# Patching pycore.PyCore...
from rope.base import pycore
class PatchedPyCore(pycore.PyCore):
# [2] ...so that forced builtin modules (i.e. modules that were
# declared as 'extension_modules' in rope preferences) will be indeed
# recognized as builtins by rope, as expected
#
# This patch is included in rope 0.9.4+ but applying it anyway is ok
def get_module(self, name, folder=None):
"""Returns a `PyObject` if the module was found."""
# check if this is a builtin module
pymod = self._builtin_module(name)
if pymod is not None:
return pymod
module = self.find_module(name, folder)
if module is None:
raise pycore.ModuleNotFoundError(
'Module %s not found' % name)
return self.resource_to_pyobject(module)
# [3] ...to avoid considering folders without __init__.py as Python
# packages
def _find_module_in_folder(self, folder, modname):
module = folder
packages = modname.split('.')
for pkg in packages[:-1]:
if module.is_folder() and module.has_child(pkg):
module = module.get_child(pkg)
else:
return None
if module.is_folder():
if module.has_child(packages[-1]) and \
module.get_child(packages[-1]).is_folder() and \
module.get_child(packages[-1]).has_child('__init__.py'):
return module.get_child(packages[-1])
elif module.has_child(packages[-1] + '.py') and \
not module.get_child(packages[-1] + '.py').is_folder():
return module.get_child(packages[-1] + '.py')
pycore.PyCore = PatchedPyCore
# [2] Patching BuiltinName for the go to definition feature to simply work
# with forced builtins
from rope.base import builtins, libutils, pyobjects
import inspect
import os.path as osp
class PatchedBuiltinName(builtins.BuiltinName):
def _pycore(self):
p = self.pyobject
while p.parent is not None:
p = p.parent
if isinstance(p, builtins.BuiltinModule) and p.pycore is not None:
return p.pycore
def get_definition_location(self):
if not inspect.isbuiltin(self.pyobject):
_lines, lineno = inspect.getsourcelines(self.pyobject.builtin)
path = inspect.getfile(self.pyobject.builtin)
if path.endswith('pyc') and osp.isfile(path[:-1]):
path = path[:-1]
pycore = self._pycore()
if pycore and pycore.project:
resource = libutils.path_to_resource(pycore.project, path)
module = pyobjects.PyModule(pycore, None, resource)
return (module, lineno)
return (None, None)
builtins.BuiltinName = PatchedBuiltinName
# [4] Patching several PyDocExtractor methods:
# 1. get_doc:
# To force rope to return the docstring of any object which has one, even
# if it's not an instance of AbstractFunction, AbstractClass, or
# AbstractModule.
# Also, to use utils.dochelpers.getdoc to get docs from forced builtins.
#
# 2. _get_class_docstring and _get_single_function_docstring:
# To not let rope add a 2 spaces indentation to every docstring, which was
# breaking our rich text mode. The only value that we are modifying is the
# 'indents' keyword of those methods, from 2 to 0.
#
# 3. get_calltip
# To easily get calltips of forced builtins
from rope.contrib import codeassist
from spyderlib.utils.dochelpers import getdoc
from rope.base import exceptions
class PatchedPyDocExtractor(codeassist.PyDocExtractor):
def get_builtin_doc(self, pyobject):
buitin = pyobject.builtin
return getdoc(buitin)
def get_doc(self, pyobject):
if hasattr(pyobject, 'builtin'):
doc = self.get_builtin_doc(pyobject)
return doc
elif isinstance(pyobject, builtins.BuiltinModule):
docstring = pyobject.get_doc()
if docstring is not None:
docstring = self._trim_docstring(docstring)
else:
docstring = ''
# TODO: Add a module_name key, so that the name could appear
# on the OI text filed but not be used by sphinx to render
# the page
doc = {'name': '',
'argspec': '',
'note': '',
'docstring': docstring
}
return doc
elif isinstance(pyobject, pyobjects.AbstractFunction):
return self._get_function_docstring(pyobject)
elif isinstance(pyobject, pyobjects.AbstractClass):
return self._get_class_docstring(pyobject)
elif isinstance(pyobject, pyobjects.AbstractModule):
return self._trim_docstring(pyobject.get_doc())
elif pyobject.get_doc() is not None: # Spyder patch
return self._trim_docstring(pyobject.get_doc())
return None
def get_calltip(self, pyobject, ignore_unknown=False, remove_self=False):
if hasattr(pyobject, 'builtin'):
doc = self.get_builtin_doc(pyobject)
return doc['name'] + doc['argspec']
try:
if isinstance(pyobject, pyobjects.AbstractClass):
pyobject = pyobject['__init__'].get_object()
if not isinstance(pyobject, pyobjects.AbstractFunction):
pyobject = pyobject['__call__'].get_object()
except exceptions.AttributeNotFoundError:
return None
if ignore_unknown and not isinstance(pyobject, pyobjects.PyFunction):
return
if isinstance(pyobject, pyobjects.AbstractFunction):
result = self._get_function_signature(pyobject, add_module=True)
if remove_self and self._is_method(pyobject):
return result.replace('(self)', '()').replace('(self, ', '(')
return result
def _get_class_docstring(self, pyclass):
contents = self._trim_docstring(pyclass.get_doc(), indents=0)
supers = [super.get_name() for super in pyclass.get_superclasses()]
doc = 'class %s(%s):\n\n' % (pyclass.get_name(), ', '.join(supers)) + contents
if '__init__' in pyclass:
init = pyclass['__init__'].get_object()
if isinstance(init, pyobjects.AbstractFunction):
doc += '\n\n' + self._get_single_function_docstring(init)
return doc
def _get_single_function_docstring(self, pyfunction):
docs = pyfunction.get_doc()
docs = self._trim_docstring(docs, indents=0)
return docs
codeassist.PyDocExtractor = PatchedPyDocExtractor
# [5] Get the right matplotlib docstrings for our Object Inspector
try:
import matplotlib as mpl
mpl.rcParams['docstring.hardcopy'] = True
except ImportError:
pass