PY-43972 FutureWarning in Python Console
[idea/community.git] / python / helpers / pydev / _pydev_bundle / _pydev_imports_tipper.py
1 import inspect
2 import os.path
3 import sys
4
5 from _pydev_bundle._pydev_tipper_common import do_find
6 from _pydevd_bundle.pydevd_constants import IS_PY2
7 from _pydevd_bundle.pydevd_resolver import suppress_warnings
8
9 if IS_PY2:
10     from inspect import getargspec as _originalgetargspec
11     def getargspec(*args, **kwargs):
12         ret = list(_originalgetargspec(*args, **kwargs))
13         ret.append([])
14         ret.append({})
15         return ret
16
17 else:
18     from inspect import getfullargspec
19
20     def getargspec(*args, **kwargs):
21         arg_spec = getfullargspec(*args, **kwargs)
22         return arg_spec.args, arg_spec.varargs, arg_spec.varkw, arg_spec.defaults, arg_spec.kwonlyargs or [], arg_spec.kwonlydefaults or {}
23
24 try:
25     xrange
26 except:
27     xrange = range
28
29 #completion types.
30 TYPE_IMPORT = '0'
31 TYPE_CLASS = '1'
32 TYPE_FUNCTION = '2'
33 TYPE_ATTR = '3'
34 TYPE_BUILTIN = '4'
35 TYPE_PARAM = '5'
36
37 # completion types for IPython console
38 TYPE_IPYTHON = '11'
39 TYPE_IPYTHON_MAGIC = '12'
40
41 def _imp(name, log=None):
42     try:
43         return __import__(name)
44     except:
45         if '.' in name:
46             sub = name[0:name.rfind('.')]
47
48             if log is not None:
49                 log.add_content('Unable to import', name, 'trying with', sub)
50                 log.add_exception()
51
52             return _imp(sub, log)
53         else:
54             s = 'Unable to import module: %s - sys.path: %s' % (str(name), sys.path)
55             if log is not None:
56                 log.add_content(s)
57                 log.add_exception()
58
59             raise ImportError(s)
60
61
62 IS_IPY = False
63 if sys.platform == 'cli':
64     IS_IPY = True
65     _old_imp = _imp
66     def _imp(name, log=None):
67         #We must add a reference in clr for .Net
68         import clr #@UnresolvedImport
69         initial_name = name
70         while '.' in name:
71             try:
72                 clr.AddReference(name)
73                 break #If it worked, that's OK.
74             except:
75                 name = name[0:name.rfind('.')]
76         else:
77             try:
78                 clr.AddReference(name)
79             except:
80                 pass #That's OK (not dot net module).
81
82         return _old_imp(initial_name, log)
83
84
85
86 def get_file(mod):
87     f = None
88     try:
89         f = inspect.getsourcefile(mod) or inspect.getfile(mod)
90     except:
91         if hasattr(mod, '__file__'):
92             f = mod.__file__
93             if f.lower(f[-4:]) in ['.pyc', '.pyo']:
94                 filename = f[:-4] + '.py'
95                 if os.path.exists(filename):
96                     f = filename
97
98     return f
99
100 def Find(name, log=None):
101     f = None
102
103     mod = _imp(name, log)
104     parent = mod
105     foundAs = ''
106
107     if inspect.ismodule(mod):
108         f = get_file(mod)
109
110     components = name.split('.')
111
112     old_comp = None
113     for comp in components[1:]:
114         try:
115             #this happens in the following case:
116             #we have mx.DateTime.mxDateTime.mxDateTime.pyd
117             #but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
118             mod = getattr(mod, comp)
119         except AttributeError:
120             if old_comp != comp:
121                 raise
122
123         if inspect.ismodule(mod):
124             f = get_file(mod)
125         else:
126             if len(foundAs) > 0:
127                 foundAs = foundAs + '.'
128             foundAs = foundAs + comp
129
130         old_comp = comp
131
132     return f, mod, parent, foundAs
133
134 def search_definition(data):
135     '''@return file, line, col
136     '''
137
138     data = data.replace('\n', '')
139     if data.endswith('.'):
140         data = data.rstrip('.')
141     f, mod, parent, foundAs = Find(data)
142     try:
143         return do_find(f, mod), foundAs
144     except:
145         return do_find(f, parent), foundAs
146
147
148 def generate_tip(data, log=None):
149     data = data.replace('\n', '')
150     if data.endswith('.'):
151         data = data.rstrip('.')
152
153     f, mod, parent, foundAs = Find(data, log)
154     #print_ >> open('temp.txt', 'w'), f
155     tips = generate_imports_tip_for_module(mod)
156     return f, tips
157
158
159 def check_char(c):
160     if c == '-' or c == '.':
161         return '_'
162     return c
163
164 _SENTINEL = object()
165
166 def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=getattr, filter=lambda name:True):
167     '''
168         @param obj_to_complete: the object from where we should get the completions
169         @param dir_comps: if passed, we should not 'dir' the object and should just iterate those passed as kwonly_arg parameter
170         @param getattr: the way to get kwonly_arg given object from the obj_to_complete (used for the completer)
171         @param filter: kwonly_arg callable that receives the name and decides if it should be appended or not to the results
172         @return: list of tuples, so that each tuple represents kwonly_arg completion with:
173             name, doc, args, type (from the TYPE_* constants)
174     '''
175     ret = []
176
177     if dir_comps is None:
178         dir_comps = dir(obj_to_complete)
179         if hasattr(obj_to_complete, '__dict__'):
180             dir_comps.append('__dict__')
181         if hasattr(obj_to_complete, '__class__'):
182             dir_comps.append('__class__')
183         # Fix for PY-38151 - From python doc, metaclass attributes are not in the list returned by `dir`.
184         # This is why e.g. __name__ does not appear. Let's add the metaclass attributes here.
185         try:
186             if inspect.isclass(obj_to_complete):
187                 dir_comps += dir(type(obj_to_complete))
188             # do not do it from instance, it might grab irrelevant items
189         except:
190             # ignore any error just in case
191             pass
192
193     get_complete_info = True
194
195     if len(dir_comps) > 1000:
196         #ok, we don't want to let our users wait forever...
197         #no complete info for you...
198
199         get_complete_info = False
200
201     dontGetDocsOn = (float, int, str, tuple, list)
202     for d in dir_comps:
203
204         if d is None:
205             continue
206
207         if not filter(d):
208             continue
209
210         args = ''
211
212         try:
213             # Fix for PY-38151: do not try to get `d` from the class, as it could be a descriptor
214             with suppress_warnings():
215                 obj = getattr(obj_to_complete, d)
216         except: #just ignore and get it without additional info
217             ret.append((d, '', args, TYPE_BUILTIN))
218         else:
219
220             if get_complete_info:
221                 try:
222                     retType = TYPE_BUILTIN
223
224                     #check if we have to get docs
225                     getDoc = True
226                     for class_ in dontGetDocsOn:
227
228                         if isinstance(obj, class_):
229                             getDoc = False
230                             break
231
232                     doc = ''
233                     if getDoc:
234                         #no need to get this info... too many constants are defined and
235                         #makes things much slower (passing all that through sockets takes quite some time)
236                         try:
237                             doc = inspect.getdoc(obj)
238                             if doc is None:
239                                 doc = ''
240                         except: #may happen on jython when checking java classes (so, just ignore it)
241                             doc = ''
242
243                     if inspect.isclass(obj_to_complete) and (
244                             inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj)
245                             or inspect.isgetsetdescriptor(obj) or inspect.ismemberdescriptor(obj)):
246                         # Fix for PY-38151: `obj` is a descriptor definition, not a called descriptor
247                         # (`obj_to_complete` is the class defining it).
248                         retType = TYPE_ATTR
249
250                     elif inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
251                         try:
252                             args, vargs, kwargs, defaults, kwonly_args, kwonly_defaults = getargspec(obj)
253
254                             args = args[:]
255
256                             for kwonly_arg in kwonly_args:
257                                 default = kwonly_defaults.get(kwonly_arg, _SENTINEL)
258                                 if default is not _SENTINEL:
259                                     args.append('%s=%s' % (kwonly_arg, default))
260                                 else:
261                                     args.append(str(kwonly_arg))
262
263                             args = '(%s)' % (', '.join(args))
264                         except TypeError:
265                             #ok, let's see if we can get the arguments from the doc
266                             args, doc = signature_from_docstring(doc, getattr(obj, '__name__', None))
267
268                         retType = TYPE_FUNCTION
269
270                     elif inspect.isclass(obj):
271                         retType = TYPE_CLASS
272
273                     elif inspect.ismodule(obj):
274                         retType = TYPE_IMPORT
275
276                     else:
277                         retType = TYPE_ATTR
278
279
280                     #add token and doc to return - assure only strings.
281                     ret.append((d, doc, args, retType))
282
283                 except: #just ignore and get it without aditional info
284                     ret.append((d, '', args, TYPE_BUILTIN))
285
286             else: #get_complete_info == False
287                 if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
288                     retType = TYPE_FUNCTION
289
290                 elif inspect.isclass(obj):
291                     retType = TYPE_CLASS
292
293                 elif inspect.ismodule(obj):
294                     retType = TYPE_IMPORT
295
296                 else:
297                     retType = TYPE_ATTR
298                 #ok, no complete info, let's try to do this as fast and clean as possible
299                 #so, no docs for this kind of information, only the signatures
300                 ret.append((d, '', str(args), retType))
301
302     return ret
303
304
305 def signature_from_docstring(doc, obj_name):
306     args = '()'
307     try:
308         found = False
309         if len(doc) > 0:
310             if IS_IPY:
311                 # Handle case where we have the situation below
312                 # sort(self, object cmp, object key)
313                 # sort(self, object cmp, object key, bool reverse)
314                 # sort(self)
315                 # sort(self, object cmp)
316
317                 # Or: sort(self: list, cmp: object, key: object)
318                 # sort(self: list, cmp: object, key: object, reverse: bool)
319                 # sort(self: list)
320                 # sort(self: list, cmp: object)
321                 if obj_name:
322                     name = obj_name + '('
323
324                     # Fix issue where it was appearing sort(aa)sort(bb)sort(cc) in the same line.
325                     lines = doc.splitlines()
326                     if len(lines) == 1:
327                         c = doc.count(name)
328                         if c > 1:
329                             doc = ('\n' + name).join(doc.split(name))
330
331                     major = ''
332                     for line in doc.splitlines():
333                         if line.startswith(name) and line.endswith(')'):
334                             if len(line) > len(major):
335                                 major = line
336                     if major:
337                         args = major[major.index('('):]
338                         found = True
339
340             if not found:
341                 i = doc.find('->')
342                 if i < 0:
343                     i = doc.find('--')
344                     if i < 0:
345                         i = doc.find('\n')
346                         if i < 0:
347                             i = doc.find('\r')
348
349                 if i > 0:
350                     s = doc[0:i]
351                     s = s.strip()
352
353                     # let's see if we have a docstring in the first line
354                     if s[-1] == ')':
355                         start = s.find('(')
356                         if start >= 0:
357                             end = s.find('[')
358                             if end <= 0:
359                                 end = s.find(')')
360                                 if end <= 0:
361                                     end = len(s)
362
363                             args = s[start:end]
364                             if not args[-1] == ')':
365                                 args = args + ')'
366
367                             # now, get rid of unwanted chars
368                             l = len(args) - 1
369                             r = []
370                             for i in xrange(len(args)):
371                                 if i == 0 or i == l:
372                                     r.append(args[i])
373                                 else:
374                                     r.append(check_char(args[i]))
375
376                             args = ''.join(r)
377
378             if IS_IPY:
379                 if args.startswith('(self:'):
380                     i = args.find(',')
381                     if i >= 0:
382                         args = '(self' + args[i:]
383                     else:
384                         args = '(self)'
385                 i = args.find(')')
386                 if i > 0:
387                     args = args[:i + 1]
388
389     except:
390         pass
391     return args, doc