This commit is contained in:
Waylon Walker 2022-03-31 20:20:07 -05:00
commit 38355d2442
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
9083 changed files with 1225834 additions and 0 deletions

View file

@ -0,0 +1,38 @@
"""Rope object analysis and inference package
Rope makes some simplifying assumptions about a python program. It
assumes that a program only performs assignments and function calls.
Tracking assignments is simple and `PyName` objects handle that. The
main problem is function calls. Rope uses these two approaches for
obtaining call information:
* Static object analysis: `rope.base.pycore.PyCore.analyze_module()`
It can analyze modules to obtain information about functions. This
is done by analyzing function calls in a module or scope. Currently
SOA analyzes the scopes that are changed while saving or when the
user asks to analyze a module. That is mainly because static
analysis is time-consuming.
* Dynamic object analysis: `rope.base.pycore.PyCore.run_module()`
When you run a module or your testsuite, when DOA is enabled, it
collects information about parameters passed to and objects returned
from functions. The main problem with this approach is that it is
quite slow; Not when looking up the information but when collecting
them.
An instance of `rope.base.oi.objectinfo.ObjectInfoManager` can be used
for accessing these information. It saves the data in a
`rope.base.oi.objectdb.ObjectDB` internally.
Now if our objectdb does not know anything about a function and we
need the value returned by it, static object inference, SOI, comes
into play. It analyzes function body and tries to infer the object
that is returned from it (we usually need the returned value for the
given parameter objects).
Rope might collect and store information for other `PyName`, too.
For instance rope stores the object builtin containers hold.
"""

View file

@ -0,0 +1,233 @@
import base64
import hashlib
import hmac
try:
import cPickle as pickle
except ImportError:
import pickle
import marshal
import os
import socket
import subprocess
import sys
import tempfile
import threading
def _compat_compare_digest(a, b):
"""Implementation of hmac.compare_digest for python < 2.7.7.
This function uses an approach designed to prevent timing analysis by
avoiding content-based short circuiting behaviour, making it appropriate
for cryptography.
"""
if len(a) != len(b):
return False
# Computes the bitwise difference of all characters in the two strings
# before returning whether or not they are equal.
difference = 0
for (a_char, b_char) in zip(a, b):
difference |= ord(a_char) ^ ord(b_char)
return difference == 0
try:
from hmac import compare_digest
except ImportError:
compare_digest = _compat_compare_digest
class PythonFileRunner(object):
"""A class for running python project files"""
def __init__(
self, pycore, file_, args=None, stdin=None, stdout=None, analyze_data=None
):
self.pycore = pycore
self.file = file_
self.analyze_data = analyze_data
self.observers = []
self.args = args
self.stdin = stdin
self.stdout = stdout
def run(self):
"""Execute the process"""
env = dict(os.environ)
file_path = self.file.real_path
path_folders = (
self.pycore.project.get_source_folders()
+ self.pycore.project.get_python_path_folders()
)
env["PYTHONPATH"] = os.pathsep.join(folder.real_path for folder in path_folders)
runmod_path = self.pycore.project.find_module("rope.base.oi.runmod").real_path
self.receiver = None
self._init_data_receiving()
send_info = "-"
if self.receiver:
send_info = self.receiver.get_send_info()
args = [
sys.executable,
runmod_path,
send_info,
self.pycore.project.address,
self.file.real_path,
]
if self.analyze_data is None:
del args[1:4]
if self.args is not None:
args.extend(self.args)
self.process = subprocess.Popen(
executable=sys.executable,
args=args,
env=env,
cwd=os.path.split(file_path)[0],
stdin=self.stdin,
stdout=self.stdout,
stderr=self.stdout,
close_fds=os.name != "nt",
)
def _init_data_receiving(self):
if self.analyze_data is None:
return
# Disabling FIFO data transfer due to blocking when running
# unittests in the GUI.
# XXX: Handle FIFO data transfer for `rope.ui.testview`
if True or os.name == "nt":
self.receiver = _SocketReceiver()
else:
self.receiver = _FIFOReceiver()
self.receiving_thread = threading.Thread(target=self._receive_information)
self.receiving_thread.setDaemon(True)
self.receiving_thread.start()
def _receive_information(self):
# temp = open('/dev/shm/info', 'wb')
for data in self.receiver.receive_data():
self.analyze_data(data)
# temp.write(str(data) + '\n')
# temp.close()
for observer in self.observers:
observer()
def wait_process(self):
"""Wait for the process to finish"""
self.process.wait()
if self.analyze_data:
self.receiving_thread.join()
def kill_process(self):
"""Stop the process"""
if self.process.poll() is not None:
return
try:
if hasattr(self.process, "terminate"):
self.process.terminate()
elif os.name != "nt":
os.kill(self.process.pid, 9)
else:
import ctypes
handle = int(self.process._handle)
ctypes.windll.kernel32.TerminateProcess(handle, -1)
except OSError:
pass
def add_finishing_observer(self, observer):
"""Notify this observer when execution finishes"""
self.observers.append(observer)
class _MessageReceiver(object):
def receive_data(self):
pass
def get_send_info(self):
pass
class _SocketReceiver(_MessageReceiver):
def __init__(self):
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.data_port = 3037
self.key = os.urandom(32)
while self.data_port < 4000:
try:
self.server_socket.bind(("localhost", self.data_port))
break
except socket.error:
self.data_port += 1
self.server_socket.listen(1)
def get_send_info(self):
return "%d:%s" % (self.data_port, base64.b64encode(self.key).decode("utf-8"))
def receive_data(self):
conn, addr = self.server_socket.accept()
self.server_socket.close()
my_file = conn.makefile("rb")
while True:
# Received messages must meet the following criteria:
# 1. Must be contained on a single line.
# 2. Must be prefixed with a base64 encoded sha256 message digest
# of the base64 encoded pickle data.
# 3. Message digest must be computed using the correct key.
#
# Any messages received that do not meet these criteria will never
# be unpickled and will be dropped silently.
try:
buf = my_file.readline()
if len(buf) == 0:
break
try:
digest_end = buf.index(b":")
buf_digest = base64.b64decode(buf[:digest_end])
buf_data = buf[digest_end + 1 : -1]
decoded_buf_data = base64.b64decode(buf_data)
except:
# Corrupted data; the payload cannot be trusted and just has
# to be dropped. See CVE-2014-3539.
continue
digest = hmac.new(self.key, buf_data, hashlib.sha256).digest()
if not compare_digest(buf_digest, digest):
# Signature mismatch; the payload cannot be trusted and just
# has to be dropped. See CVE-2014-3539.
continue
yield pickle.loads(decoded_buf_data)
except EOFError:
break
my_file.close()
conn.close()
class _FIFOReceiver(_MessageReceiver):
def __init__(self):
# XXX: this is insecure and might cause race conditions
self.file_name = self._get_file_name()
os.mkfifo(self.file_name)
def _get_file_name(self):
prefix = tempfile.gettempdir() + "/__rope_"
i = 0
while os.path.exists(prefix + str(i).rjust(4, "0")):
i += 1
return prefix + str(i).rjust(4, "0")
def get_send_info(self):
return self.file_name
def receive_data(self):
my_file = open(self.file_name, "rb")
while True:
try:
yield marshal.load(my_file)
except EOFError:
break
my_file.close()
os.remove(self.file_name)

View file

@ -0,0 +1,123 @@
from rope.base.oi import objectdb
class MemoryDB(objectdb.FileDict):
def __init__(self, project, persist=None):
self.project = project
self._persist = persist
self.files = self
self._load_files()
self.project.data_files.add_write_hook(self.write)
def _load_files(self):
self._files = {}
if self.persist:
result = self.project.data_files.read_data(
"objectdb", compress=self.compress, import_=True
)
if result is not None:
self._files = result
def keys(self):
return self._files.keys()
def __iter__(self):
for f in self._files:
yield f
def __len__(self):
return len(self._files)
def __setitem__(self):
raise NotImplementedError()
def __contains__(self, key):
return key in self._files
def __getitem__(self, key):
return FileInfo(self._files[key])
def create(self, path):
self._files[path] = {}
def rename(self, file, newfile):
if file not in self._files:
return
self._files[newfile] = self._files[file]
del self[file]
def __delitem__(self, file):
del self._files[file]
def write(self):
if self.persist:
self.project.data_files.write_data("objectdb", self._files, self.compress)
@property
def compress(self):
return self.project.prefs.get("compress_objectdb", False)
@property
def persist(self):
if self._persist is not None:
return self._persist
else:
return self.project.prefs.get("save_objectdb", False)
class FileInfo(objectdb.FileInfo):
def __init__(self, scopes):
self.scopes = scopes
def create_scope(self, key):
self.scopes[key] = ScopeInfo()
def keys(self):
return self.scopes.keys()
def __contains__(self, key):
return key in self.scopes
def __getitem__(self, key):
return self.scopes[key]
def __delitem__(self, key):
del self.scopes[key]
def __iter__(self):
for s in self.scopes:
yield s
def __len__(self):
return len(self.scopes)
def __setitem__(self):
raise NotImplementedError()
class ScopeInfo(objectdb.ScopeInfo):
def __init__(self):
self.call_info = {}
self.per_name = {}
def get_per_name(self, name):
return self.per_name.get(name, None)
def save_per_name(self, name, value):
self.per_name[name] = value
def get_returned(self, parameters):
return self.call_info.get(parameters, None)
def get_call_infos(self):
for args, returned in self.call_info.items():
yield objectdb.CallInfo(args, returned)
def add_call(self, parameters, returned):
self.call_info[parameters] = returned
def __getstate__(self):
return (self.call_info, self.per_name)
def __setstate__(self, data):
self.call_info, self.per_name = data

View file

@ -0,0 +1,170 @@
from __future__ import print_function
class ObjectDB(object):
def __init__(self, db, validation):
self.db = db
self.validation = validation
self.observers = []
self.files = db.files
def validate_files(self):
for file in list(self.files):
if not self.validation.is_file_valid(file):
del self.files[file]
self._file_removed(file)
def validate_file(self, file):
if file not in self.files:
return
for key in list(self.files[file]):
if not self.validation.is_scope_valid(file, key):
del self.files[file][key]
def file_moved(self, file, newfile):
if file not in self.files:
return
self.files.rename(file, newfile)
self._file_removed(file)
self._file_added(newfile)
def get_files(self):
return self.files.keys()
def get_returned(self, path, key, args):
scope_info = self._get_scope_info(path, key, readonly=True)
result = scope_info.get_returned(args)
if self.validation.is_value_valid(result):
return result
def get_pername(self, path, key, name):
scope_info = self._get_scope_info(path, key, readonly=True)
result = scope_info.get_per_name(name)
if self.validation.is_value_valid(result):
return result
def get_callinfos(self, path, key):
scope_info = self._get_scope_info(path, key, readonly=True)
return scope_info.get_call_infos()
def add_callinfo(self, path, key, args, returned):
scope_info = self._get_scope_info(path, key, readonly=False)
old_returned = scope_info.get_returned(args)
if self.validation.is_more_valid(returned, old_returned):
scope_info.add_call(args, returned)
def add_pername(self, path, key, name, value):
scope_info = self._get_scope_info(path, key, readonly=False)
old_value = scope_info.get_per_name(name)
if self.validation.is_more_valid(value, old_value):
scope_info.save_per_name(name, value)
def add_file_list_observer(self, observer):
self.observers.append(observer)
def write(self):
self.db.write()
def _get_scope_info(self, path, key, readonly=True):
if path not in self.files:
if readonly:
return _NullScopeInfo()
self.files.create(path)
self._file_added(path)
if key not in self.files[path]:
if readonly:
return _NullScopeInfo()
self.files[path].create_scope(key)
result = self.files[path][key]
if isinstance(result, dict):
print(self.files, self.files[path], self.files[path][key])
return result
def _file_removed(self, path):
for observer in self.observers:
observer.removed(path)
def _file_added(self, path):
for observer in self.observers:
observer.added(path)
def __str__(self):
scope_count = 0
for file_dict in self.files.values():
scope_count += len(file_dict)
return "ObjectDB holds %s file and %s scope infos" % (
len(self.files),
scope_count,
)
class _NullScopeInfo(object):
def __init__(self, error_on_write=True):
self.error_on_write = error_on_write
def get_per_name(self, name):
pass
def save_per_name(self, name, value):
if self.error_on_write:
raise NotImplementedError()
def get_returned(self, parameters):
pass
def get_call_infos(self):
return []
def add_call(self, parameters, returned):
if self.error_on_write:
raise NotImplementedError()
class FileInfo(dict):
def create_scope(self, key):
pass
class FileDict(dict):
def create(self, key):
pass
def rename(self, key, new_key):
pass
class ScopeInfo(object):
def get_per_name(self, name):
pass
def save_per_name(self, name, value):
pass
def get_returned(self, parameters):
pass
def get_call_infos(self):
pass
def add_call(self, parameters, returned):
pass
class CallInfo(object):
def __init__(self, args, returned):
self.args = args
self.returned = returned
def get_parameters(self):
return self.args
def get_returned(self):
return self.returned
class FileListObserver(object):
def added(self, path):
pass
def removed(self, path):
pass

View file

@ -0,0 +1,230 @@
import warnings
from rope.base import exceptions, resourceobserver
from rope.base.oi import objectdb, memorydb, transform
class ObjectInfoManager(object):
"""Stores object information
It uses an instance of `objectdb.ObjectDB` for storing
information.
"""
def __init__(self, project):
self.project = project
self.to_textual = transform.PyObjectToTextual(project)
self.to_pyobject = transform.TextualToPyObject(project)
self.doi_to_pyobject = transform.DOITextualToPyObject(project)
self._init_objectdb()
if project.prefs.get("validate_objectdb", False):
self._init_validation()
def _init_objectdb(self):
dbtype = self.project.get_prefs().get("objectdb_type", None)
persist = None
if dbtype is not None:
warnings.warn(
'"objectdb_type" project config is deprecated;\n'
'Use "save_objectdb" instead in your project '
'config file.\n(".ropeproject/config.py" by default)\n',
DeprecationWarning,
)
if dbtype != "memory" and self.project.ropefolder is not None:
persist = True
self.validation = TextualValidation(self.to_pyobject)
db = memorydb.MemoryDB(self.project, persist=persist)
self.objectdb = objectdb.ObjectDB(db, self.validation)
def _init_validation(self):
self.objectdb.validate_files()
observer = resourceobserver.ResourceObserver(
changed=self._resource_changed,
moved=self._resource_moved,
removed=self._resource_moved,
)
files = []
for path in self.objectdb.get_files():
resource = self.to_pyobject.path_to_resource(path)
if resource is not None and resource.project == self.project:
files.append(resource)
self.observer = resourceobserver.FilteredResourceObserver(observer, files)
self.objectdb.add_file_list_observer(_FileListObserver(self))
self.project.add_observer(self.observer)
def _resource_changed(self, resource):
try:
self.objectdb.validate_file(self.to_textual.resource_to_path(resource))
except exceptions.ModuleSyntaxError:
pass
def _resource_moved(self, resource, new_resource=None):
self.observer.remove_resource(resource)
if new_resource is not None:
old = self.to_textual.resource_to_path(resource)
new = self.to_textual.resource_to_path(new_resource)
self.objectdb.file_moved(old, new)
self.observer.add_resource(new_resource)
def get_returned(self, pyobject, args):
result = self.get_exact_returned(pyobject, args)
if result is not None:
return result
path, key = self._get_scope(pyobject)
if path is None:
return None
for call_info in self.objectdb.get_callinfos(path, key):
returned = call_info.get_returned()
if returned and returned[0] not in ("unknown", "none"):
result = returned
break
if result is None:
result = returned
if result is not None:
return self.to_pyobject(result)
def get_exact_returned(self, pyobject, args):
path, key = self._get_scope(pyobject)
if path is not None:
returned = self.objectdb.get_returned(
path, key, self._args_to_textual(pyobject, args)
)
if returned is not None:
return self.to_pyobject(returned)
def _args_to_textual(self, pyfunction, args):
parameters = list(pyfunction.get_param_names(special_args=False))
arguments = args.get_arguments(parameters)[: len(parameters)]
textual_args = tuple([self.to_textual(arg) for arg in arguments])
return textual_args
def get_parameter_objects(self, pyobject):
path, key = self._get_scope(pyobject)
if path is None:
return None
arg_count = len(pyobject.get_param_names(special_args=False))
unknowns = arg_count
parameters = [None] * arg_count
for call_info in self.objectdb.get_callinfos(path, key):
args = call_info.get_parameters()
for index, arg in enumerate(args[:arg_count]):
old = parameters[index]
if self.validation.is_more_valid(arg, old):
parameters[index] = arg
if self.validation.is_value_valid(arg):
unknowns -= 1
if unknowns == 0:
break
if unknowns < arg_count:
return [self.to_pyobject(parameter) for parameter in parameters]
def get_passed_objects(self, pyfunction, parameter_index):
path, key = self._get_scope(pyfunction)
if path is None:
return []
result = []
for call_info in self.objectdb.get_callinfos(path, key):
args = call_info.get_parameters()
if len(args) > parameter_index:
parameter = self.to_pyobject(args[parameter_index])
if parameter is not None:
result.append(parameter)
return result
def doa_data_received(self, data):
def doi_to_normal(textual):
pyobject = self.doi_to_pyobject(textual)
return self.to_textual(pyobject)
function = doi_to_normal(data[0])
args = tuple([doi_to_normal(textual) for textual in data[1]])
returned = doi_to_normal(data[2])
if function[0] == "defined" and len(function) == 3:
self._save_data(function, args, returned)
def function_called(self, pyfunction, params, returned=None):
function_text = self.to_textual(pyfunction)
params_text = tuple([self.to_textual(param) for param in params])
returned_text = ("unknown",)
if returned is not None:
returned_text = self.to_textual(returned)
self._save_data(function_text, params_text, returned_text)
def save_per_name(self, scope, name, data):
path, key = self._get_scope(scope.pyobject)
if path is not None:
self.objectdb.add_pername(path, key, name, self.to_textual(data))
def get_per_name(self, scope, name):
path, key = self._get_scope(scope.pyobject)
if path is not None:
result = self.objectdb.get_pername(path, key, name)
if result is not None:
return self.to_pyobject(result)
def _save_data(self, function, args, returned=("unknown",)):
self.objectdb.add_callinfo(function[1], function[2], args, returned)
def _get_scope(self, pyobject):
resource = pyobject.get_module().get_resource()
if resource is None:
return None, None
textual = self.to_textual(pyobject)
if textual[0] == "defined":
path = textual[1]
if len(textual) == 3:
key = textual[2]
else:
key = ""
return path, key
return None, None
def sync(self):
self.objectdb.sync()
def __str__(self):
return str(self.objectdb)
class TextualValidation(object):
def __init__(self, to_pyobject):
self.to_pyobject = to_pyobject
def is_value_valid(self, value):
# ???: Should none and unknown be considered valid?
if value is None or value[0] in ("none", "unknown"):
return False
return self.to_pyobject(value) is not None
def is_more_valid(self, new, old):
if old is None:
return True
return new[0] not in ("unknown", "none")
def is_file_valid(self, path):
return self.to_pyobject.path_to_resource(path) is not None
def is_scope_valid(self, path, key):
if key == "":
textual = ("defined", path)
else:
textual = ("defined", path, key)
return self.to_pyobject(textual) is not None
class _FileListObserver(object):
def __init__(self, object_info):
self.object_info = object_info
self.observer = self.object_info.observer
self.to_pyobject = self.object_info.to_pyobject
def removed(self, path):
resource = self.to_pyobject.path_to_resource(path)
if resource is not None:
self.observer.remove_resource(resource)
def added(self, path):
resource = self.to_pyobject.path_to_resource(path)
if resource is not None:
self.observer.add_resource(resource)

View file

@ -0,0 +1,240 @@
def __rope_start_everything():
import os
import sys
import socket
try:
import cPickle as pickle
except ImportError:
import pickle
import marshal
import inspect
import types
import threading
import rope.base.utils.pycompat as pycompat
import base64
import hashlib
import hmac
class _MessageSender(object):
def send_data(self, data):
pass
class _SocketSender(_MessageSender):
def __init__(self, port, key):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", port))
self.my_file = s.makefile("wb")
self.key = base64.b64decode(key)
def send_data(self, data):
if not self.my_file.closed:
pickled_data = base64.b64encode(
pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
)
dgst = hmac.new(self.key, pickled_data, hashlib.sha256).digest()
self.my_file.write(base64.b64encode(dgst) + b":" + pickled_data + b"\n")
def close(self):
self.my_file.close()
class _FileSender(_MessageSender):
def __init__(self, file_name):
self.my_file = open(file_name, "wb")
def send_data(self, data):
if not self.my_file.closed:
marshal.dump(data, self.my_file)
def close(self):
self.my_file.close()
def _cached(func):
cache = {}
def newfunc(self, arg):
if arg in cache:
return cache[arg]
result = func(self, arg)
cache[arg] = result
return result
return newfunc
class _FunctionCallDataSender(object):
def __init__(self, send_info, project_root):
self.project_root = project_root
if send_info[0].isdigit():
port, key = send_info.split(":", 1)
self.sender = _SocketSender(int(port), key)
else:
self.sender = _FileSender(send_info)
def global_trace(frame, event, arg):
# HACK: Ignoring out->in calls
# This might lose some information
if self._is_an_interesting_call(frame):
return self.on_function_call
sys.settrace(global_trace)
threading.settrace(global_trace)
def on_function_call(self, frame, event, arg):
if event != "return":
return
args = []
returned = ("unknown",)
code = frame.f_code
for argname in code.co_varnames[: code.co_argcount]:
try:
argvalue = self._object_to_persisted_form(frame.f_locals[argname])
args.append(argvalue)
except (TypeError, AttributeError):
args.append(("unknown",))
try:
returned = self._object_to_persisted_form(arg)
except (TypeError, AttributeError):
pass
try:
data = (
self._object_to_persisted_form(frame.f_code),
tuple(args),
returned,
)
self.sender.send_data(data)
except (TypeError):
pass
return self.on_function_call
def _is_an_interesting_call(self, frame):
# if frame.f_code.co_name in ['?', '<module>']:
# return False
# return not frame.f_back or
# not self._is_code_inside_project(frame.f_back.f_code)
if not self._is_code_inside_project(frame.f_code) and (
not frame.f_back
or not self._is_code_inside_project(frame.f_back.f_code)
):
return False
return True
def _is_code_inside_project(self, code):
source = self._path(code.co_filename)
return (
source is not None
and os.path.exists(source)
and _realpath(source).startswith(self.project_root)
)
@_cached
def _get_persisted_code(self, object_):
source = self._path(object_.co_filename)
if not os.path.exists(source):
raise TypeError("no source")
return ("defined", _realpath(source), str(object_.co_firstlineno))
@_cached
def _get_persisted_class(self, object_):
try:
return (
"defined",
_realpath(inspect.getsourcefile(object_)),
object_.__name__,
)
except (TypeError, AttributeError):
return ("unknown",)
def _get_persisted_builtin(self, object_):
if isinstance(object_, pycompat.string_types):
return ("builtin", "str")
if isinstance(object_, list):
holding = None
if len(object_) > 0:
holding = object_[0]
return ("builtin", "list", self._object_to_persisted_form(holding))
if isinstance(object_, dict):
keys = None
values = None
if len(object_) > 0:
# @todo - fix it properly, why is __locals__ being
# duplicated ?
keys = [key for key in object_.keys() if key != "__locals__"][0]
values = object_[keys]
return (
"builtin",
"dict",
self._object_to_persisted_form(keys),
self._object_to_persisted_form(values),
)
if isinstance(object_, tuple):
objects = []
if len(object_) < 3:
for holding in object_:
objects.append(self._object_to_persisted_form(holding))
else:
objects.append(self._object_to_persisted_form(object_[0]))
return tuple(["builtin", "tuple"] + objects)
if isinstance(object_, set):
holding = None
if len(object_) > 0:
for o in object_:
holding = o
break
return ("builtin", "set", self._object_to_persisted_form(holding))
return ("unknown",)
def _object_to_persisted_form(self, object_):
if object_ is None:
return ("none",)
if isinstance(object_, types.CodeType):
return self._get_persisted_code(object_)
if isinstance(object_, types.FunctionType):
return self._get_persisted_code(object_.__code__)
if isinstance(object_, types.MethodType):
return self._get_persisted_code(object_.__func__.__code__)
if isinstance(object_, types.ModuleType):
return self._get_persisted_module(object_)
if isinstance(object_, pycompat.string_types + (list, dict, tuple, set)):
return self._get_persisted_builtin(object_)
if isinstance(object_, type):
return self._get_persisted_class(object_)
return ("instance", self._get_persisted_class(type(object_)))
@_cached
def _get_persisted_module(self, object_):
path = self._path(object_.__file__)
if path and os.path.exists(path):
return ("defined", _realpath(path))
return ("unknown",)
def _path(self, path):
if path.endswith(".pyc"):
path = path[:-1]
if path.endswith(".py"):
return path
def close(self):
self.sender.close()
sys.settrace(None)
def _realpath(path):
return os.path.realpath(os.path.abspath(os.path.expanduser(path)))
send_info = sys.argv[1]
project_root = sys.argv[2]
file_to_run = sys.argv[3]
run_globals = globals()
run_globals.update(
{"__name__": "__main__", "__builtins__": __builtins__, "__file__": file_to_run}
)
if send_info != "-":
data_sender = _FunctionCallDataSender(send_info, project_root)
del sys.argv[1:4]
pycompat.execfile(file_to_run, run_globals)
if send_info != "-":
data_sender.close()
if __name__ == "__main__":
__rope_start_everything()

View file

@ -0,0 +1,151 @@
import rope.base.ast
import rope.base.oi.soi
import rope.base.pynames
from rope.base import pyobjects, evaluate, astutils, arguments
def analyze_module(pycore, pymodule, should_analyze, search_subscopes, followed_calls):
"""Analyze `pymodule` for static object inference
Analyzes scopes for collecting object information. The analysis
starts from inner scopes.
"""
_analyze_node(pycore, pymodule, should_analyze, search_subscopes, followed_calls)
def _analyze_node(pycore, pydefined, should_analyze, search_subscopes, followed_calls):
if search_subscopes(pydefined):
for scope in pydefined.get_scope().get_scopes():
_analyze_node(
pycore, scope.pyobject, should_analyze, search_subscopes, followed_calls
)
if should_analyze(pydefined):
new_followed_calls = max(0, followed_calls - 1)
return_true = lambda pydefined: True
return_false = lambda pydefined: False
def _follow(pyfunction):
_analyze_node(
pycore, pyfunction, return_true, return_false, new_followed_calls
)
if not followed_calls:
_follow = None
visitor = SOAVisitor(pycore, pydefined, _follow)
for child in rope.base.ast.get_child_nodes(pydefined.get_ast()):
rope.base.ast.walk(child, visitor)
class SOAVisitor(object):
def __init__(self, pycore, pydefined, follow_callback=None):
self.pycore = pycore
self.pymodule = pydefined.get_module()
self.scope = pydefined.get_scope()
self.follow = follow_callback
def _FunctionDef(self, node):
pass
def _ClassDef(self, node):
pass
def _Call(self, node):
for child in rope.base.ast.get_child_nodes(node):
rope.base.ast.walk(child, self)
primary, pyname = evaluate.eval_node2(self.scope, node.func)
if pyname is None:
return
pyfunction = pyname.get_object()
if isinstance(pyfunction, pyobjects.AbstractFunction):
args = arguments.create_arguments(primary, pyfunction, node, self.scope)
elif isinstance(pyfunction, pyobjects.PyClass):
pyclass = pyfunction
if "__init__" in pyfunction:
pyfunction = pyfunction["__init__"].get_object()
pyname = rope.base.pynames.UnboundName(pyobjects.PyObject(pyclass))
args = self._args_with_self(primary, pyname, pyfunction, node)
elif "__call__" in pyfunction:
pyfunction = pyfunction["__call__"].get_object()
args = self._args_with_self(primary, pyname, pyfunction, node)
else:
return
self._call(pyfunction, args)
def _args_with_self(self, primary, self_pyname, pyfunction, node):
base_args = arguments.create_arguments(primary, pyfunction, node, self.scope)
return arguments.MixedArguments(self_pyname, base_args, self.scope)
def _call(self, pyfunction, args):
if isinstance(pyfunction, pyobjects.PyFunction):
if self.follow is not None:
before = self._parameter_objects(pyfunction)
self.pycore.object_info.function_called(
pyfunction, args.get_arguments(pyfunction.get_param_names())
)
pyfunction._set_parameter_pyobjects(None)
if self.follow is not None:
after = self._parameter_objects(pyfunction)
if after != before:
self.follow(pyfunction)
# XXX: Maybe we should not call every builtin function
if isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
pyfunction.get_returned_object(args)
def _parameter_objects(self, pyfunction):
result = []
for i in range(len(pyfunction.get_param_names(False))):
result.append(pyfunction.get_parameter(i))
return result
def _AnnAssign(self, node):
for child in rope.base.ast.get_child_nodes(node):
rope.base.ast.walk(child, self)
visitor = _SOAAssignVisitor()
nodes = []
rope.base.ast.walk(node.target, visitor)
nodes.extend(visitor.nodes)
self._evaluate_assign_value(node, nodes, type_hint=node.annotation)
def _Assign(self, node):
for child in rope.base.ast.get_child_nodes(node):
rope.base.ast.walk(child, self)
visitor = _SOAAssignVisitor()
nodes = []
for child in node.targets:
rope.base.ast.walk(child, visitor)
nodes.extend(visitor.nodes)
self._evaluate_assign_value(node, nodes)
def _evaluate_assign_value(self, node, nodes, type_hint=False):
for subscript, levels in nodes:
instance = evaluate.eval_node(self.scope, subscript.value)
args_pynames = [evaluate.eval_node(self.scope, subscript.slice)]
value = rope.base.oi.soi._infer_assignment(
rope.base.pynames.AssignmentValue(
node.value, levels, type_hint=type_hint
),
self.pymodule,
)
args_pynames.append(rope.base.pynames.UnboundName(value))
if instance is not None and value is not None:
pyobject = instance.get_object()
if "__setitem__" in pyobject:
pyfunction = pyobject["__setitem__"].get_object()
args = arguments.ObjectArguments([instance] + args_pynames)
self._call(pyfunction, args)
# IDEA: handle `__setslice__`, too
class _SOAAssignVisitor(astutils._NodeNameCollector):
def __init__(self):
super(_SOAAssignVisitor, self).__init__()
self.nodes = []
def _added(self, node, levels):
if isinstance(node, rope.base.ast.Subscript) and isinstance(
node.slice, (rope.base.ast.Index, rope.base.ast.expr)
):
self.nodes.append((node, levels))

View file

@ -0,0 +1,231 @@
"""A module for inferring objects
For more information see the documentation in `rope.base.oi`
package.
"""
import rope.base.builtins
import rope.base.pynames
import rope.base.pyobjects
from rope.base import evaluate, utils, arguments
from rope.base.oi.type_hinting.factory import get_type_hinting_factory
_ignore_inferred = utils.ignore_exception(rope.base.pyobjects.IsBeingInferredError)
@_ignore_inferred
def infer_returned_object(pyfunction, args):
"""Infer the `PyObject` this `PyFunction` returns after calling"""
object_info = pyfunction.pycore.object_info
result = object_info.get_exact_returned(pyfunction, args)
if result is not None:
return result
result = _infer_returned(pyfunction, args)
if result is not None:
if args and pyfunction.get_module().get_resource() is not None:
params = args.get_arguments(pyfunction.get_param_names(special_args=False))
object_info.function_called(pyfunction, params, result)
return result
result = object_info.get_returned(pyfunction, args)
if result is not None:
return result
hint_return = get_type_hinting_factory(
pyfunction.pycore.project
).make_return_provider()
type_ = hint_return(pyfunction)
if type_ is not None:
return rope.base.pyobjects.PyObject(type_)
@_ignore_inferred
def infer_parameter_objects(pyfunction):
"""Infer the `PyObject` of parameters of this `PyFunction`"""
object_info = pyfunction.pycore.object_info
result = object_info.get_parameter_objects(pyfunction)
if result is None:
result = _parameter_objects(pyfunction)
_handle_first_parameter(pyfunction, result)
return result
def _handle_first_parameter(pyobject, parameters):
kind = pyobject.get_kind()
if parameters is None or kind not in ["method", "classmethod"]:
pass
if not parameters:
if not pyobject.get_param_names(special_args=False):
return
parameters.append(rope.base.pyobjects.get_unknown())
if kind == "method":
parameters[0] = rope.base.pyobjects.PyObject(pyobject.parent)
if kind == "classmethod":
parameters[0] = pyobject.parent
@_ignore_inferred
def infer_assigned_object(pyname):
if not pyname.assignments:
return
for assignment in reversed(pyname.assignments):
result = _infer_assignment(assignment, pyname.module)
if (
isinstance(result, rope.base.builtins.BuiltinUnknown)
and result.get_name() == "NotImplementedType"
):
break
elif result == rope.base.pyobjects.get_unknown():
break
elif result is not None:
return result
hint_assignment = get_type_hinting_factory(
pyname.module.pycore.project
).make_assignment_provider()
hinting_result = hint_assignment(pyname)
if hinting_result is not None:
return rope.base.pyobjects.PyObject(hinting_result)
return result
def get_passed_objects(pyfunction, parameter_index):
object_info = pyfunction.pycore.object_info
result = object_info.get_passed_objects(pyfunction, parameter_index)
if not result:
statically_inferred = _parameter_objects(pyfunction)
if len(statically_inferred) > parameter_index:
result.append(statically_inferred[parameter_index])
return result
def _infer_returned(pyobject, args):
if args:
# HACK: Setting parameter objects manually
# This is not thread safe and might cause problems if `args`
# does not come from a good call site
pyobject.get_scope().invalidate_data()
pyobject._set_parameter_pyobjects(
args.get_arguments(pyobject.get_param_names(special_args=False))
)
scope = pyobject.get_scope()
if not scope._get_returned_asts():
return
maxtries = 3
for returned_node in reversed(scope._get_returned_asts()[-maxtries:]):
try:
resulting_pyname = evaluate.eval_node(scope, returned_node)
if resulting_pyname is None:
continue
pyobject = resulting_pyname.get_object()
if pyobject == rope.base.pyobjects.get_unknown():
continue
if not scope._is_generator():
return pyobject
else:
return rope.base.builtins.get_generator(pyobject)
except rope.base.pyobjects.IsBeingInferredError:
pass
def _parameter_objects(pyobject):
result = []
params = pyobject.get_param_names(special_args=False)
hint_param = get_type_hinting_factory(pyobject.pycore.project).make_param_provider()
for name in params:
type_ = hint_param(pyobject, name)
if type_ is not None:
result.append(rope.base.pyobjects.PyObject(type_))
else:
result.append(rope.base.pyobjects.get_unknown())
return result
# handling `rope.base.pynames.AssignmentValue`
@_ignore_inferred
def _infer_assignment(assignment, pymodule):
result = _follow_pyname(assignment, pymodule)
if result is None:
return None
pyname, pyobject = result
pyobject = _follow_evaluations(assignment, pyname, pyobject)
if pyobject is None:
return None
return _follow_levels(assignment, pyobject)
def _follow_levels(assignment, pyobject):
for index in assignment.levels:
if isinstance(pyobject.get_type(), rope.base.builtins.Tuple):
holdings = pyobject.get_type().get_holding_objects()
if holdings:
pyobject = holdings[min(len(holdings) - 1, index)]
else:
pyobject = None
elif isinstance(pyobject.get_type(), rope.base.builtins.List):
pyobject = pyobject.get_type().holding
else:
pyobject = None
if pyobject is None:
break
return pyobject
@_ignore_inferred
def _follow_pyname(assignment, pymodule, lineno=None):
assign_node = assignment.type_hint or assignment.ast_node
if lineno is None:
lineno = _get_lineno_for_node(assign_node)
holding_scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
pyname = evaluate.eval_node(holding_scope, assign_node)
if pyname is not None:
result = pyname.get_object()
if (
isinstance(result.get_type(), rope.base.builtins.Property)
and holding_scope.get_kind() == "Class"
):
arg = rope.base.pynames.UnboundName(
rope.base.pyobjects.PyObject(holding_scope.pyobject)
)
return pyname, result.get_type().get_property_object(
arguments.ObjectArguments([arg])
)
return pyname, result
@_ignore_inferred
def _follow_evaluations(assignment, pyname, pyobject):
new_pyname = pyname
tokens = assignment.evaluation.split(".")
for token in tokens:
call = token.endswith("()")
if call:
token = token[:-2]
if token:
pyname = new_pyname
new_pyname = _get_attribute(pyobject, token)
if new_pyname is not None:
pyobject = new_pyname.get_object()
if pyobject is not None and call:
if isinstance(pyobject, rope.base.pyobjects.AbstractFunction):
args = arguments.ObjectArguments([pyname])
pyobject = pyobject.get_returned_object(args)
else:
pyobject = None
if pyobject is None:
break
if pyobject is not None and assignment.assign_type:
return rope.base.pyobjects.PyObject(pyobject)
return pyobject
def _get_lineno_for_node(assign_node):
if hasattr(assign_node, "lineno") and assign_node.lineno is not None:
return assign_node.lineno
return 1
def _get_attribute(pyobject, name):
if pyobject is not None and name in pyobject:
return pyobject[name]

View file

@ -0,0 +1,292 @@
"""Provides classes for persisting `PyObject`"""
import os
import re
import rope.base.builtins
from rope.base import exceptions
class PyObjectToTextual(object):
"""For transforming `PyObject` to textual form
This can be used for storing `PyObjects` in files. Use
`TextualToPyObject` for converting back.
"""
def __init__(self, project):
self.project = project
def transform(self, pyobject):
"""Transform a `PyObject` to textual form"""
if pyobject is None:
return ("none",)
object_type = type(pyobject)
try:
method = getattr(self, object_type.__name__ + "_to_textual")
return method(pyobject)
except AttributeError:
return ("unknown",)
def __call__(self, pyobject):
return self.transform(pyobject)
def PyObject_to_textual(self, pyobject):
if isinstance(pyobject.get_type(), rope.base.pyobjects.AbstractClass):
result = self.transform(pyobject.get_type())
if result[0] == "defined":
return ("instance", result)
return result
return ("unknown",)
def PyFunction_to_textual(self, pyobject):
return self._defined_to_textual(pyobject)
def PyClass_to_textual(self, pyobject):
return self._defined_to_textual(pyobject)
def _defined_to_textual(self, pyobject):
address = []
while pyobject.parent is not None:
address.insert(0, pyobject.get_name())
pyobject = pyobject.parent
return (
"defined",
self._get_pymodule_path(pyobject.get_module()),
".".join(address),
)
def PyModule_to_textual(self, pyobject):
return ("defined", self._get_pymodule_path(pyobject))
def PyPackage_to_textual(self, pyobject):
return ("defined", self._get_pymodule_path(pyobject))
def List_to_textual(self, pyobject):
return ("builtin", "list", self.transform(pyobject.holding))
def Dict_to_textual(self, pyobject):
return (
"builtin",
"dict",
self.transform(pyobject.keys),
self.transform(pyobject.values),
)
def Tuple_to_textual(self, pyobject):
objects = [
self.transform(holding) for holding in pyobject.get_holding_objects()
]
return tuple(["builtin", "tuple"] + objects)
def Set_to_textual(self, pyobject):
return ("builtin", "set", self.transform(pyobject.holding))
def Iterator_to_textual(self, pyobject):
return ("builtin", "iter", self.transform(pyobject.holding))
def Generator_to_textual(self, pyobject):
return ("builtin", "generator", self.transform(pyobject.holding))
def Str_to_textual(self, pyobject):
return ("builtin", "str")
def File_to_textual(self, pyobject):
return ("builtin", "file")
def BuiltinFunction_to_textual(self, pyobject):
return ("builtin", "function", pyobject.get_name())
def _get_pymodule_path(self, pymodule):
return self.resource_to_path(pymodule.get_resource())
def resource_to_path(self, resource):
if resource.project == self.project:
return resource.path
else:
return resource.real_path
class TextualToPyObject(object):
"""For transforming textual form to `PyObject`"""
def __init__(self, project, allow_in_project_absolutes=False):
self.project = project
def __call__(self, textual):
return self.transform(textual)
def transform(self, textual):
"""Transform an object from textual form to `PyObject`"""
if textual is None:
return None
type = textual[0]
try:
method = getattr(self, type + "_to_pyobject")
return method(textual)
except AttributeError:
return None
def builtin_to_pyobject(self, textual):
method = getattr(self, "builtin_%s_to_pyobject" % textual[1], None)
if method is not None:
return method(textual)
def builtin_str_to_pyobject(self, textual):
return rope.base.builtins.get_str()
def builtin_list_to_pyobject(self, textual):
holding = self.transform(textual[2])
return rope.base.builtins.get_list(holding)
def builtin_dict_to_pyobject(self, textual):
keys = self.transform(textual[2])
values = self.transform(textual[3])
return rope.base.builtins.get_dict(keys, values)
def builtin_tuple_to_pyobject(self, textual):
objects = []
for holding in textual[2:]:
objects.append(self.transform(holding))
return rope.base.builtins.get_tuple(*objects)
def builtin_set_to_pyobject(self, textual):
holding = self.transform(textual[2])
return rope.base.builtins.get_set(holding)
def builtin_iter_to_pyobject(self, textual):
holding = self.transform(textual[2])
return rope.base.builtins.get_iterator(holding)
def builtin_generator_to_pyobject(self, textual):
holding = self.transform(textual[2])
return rope.base.builtins.get_generator(holding)
def builtin_file_to_pyobject(self, textual):
return rope.base.builtins.get_file()
def builtin_function_to_pyobject(self, textual):
if textual[2] in rope.base.builtins.builtins:
return rope.base.builtins.builtins[textual[2]].get_object()
def unknown_to_pyobject(self, textual):
return None
def none_to_pyobject(self, textual):
return None
def _module_to_pyobject(self, textual):
path = textual[1]
return self._get_pymodule(path)
def _hierarchical_defined_to_pyobject(self, textual):
path = textual[1]
names = textual[2].split(".")
pymodule = self._get_pymodule(path)
pyobject = pymodule
for name in names:
if pyobject is None:
return None
if isinstance(pyobject, rope.base.pyobjects.PyDefinedObject):
try:
pyobject = pyobject.get_scope()[name].get_object()
except exceptions.NameNotFoundError:
return None
else:
return None
return pyobject
def defined_to_pyobject(self, textual):
if len(textual) == 2 or textual[2] == "":
return self._module_to_pyobject(textual)
else:
return self._hierarchical_defined_to_pyobject(textual)
def instance_to_pyobject(self, textual):
type = self.transform(textual[1])
if type is not None:
return rope.base.pyobjects.PyObject(type)
def _get_pymodule(self, path):
resource = self.path_to_resource(path)
if resource is not None:
return self.project.get_pymodule(resource)
def path_to_resource(self, path):
try:
root = self.project.address
if not os.path.isabs(path):
return self.project.get_resource(path)
if path == root or path.startswith(root + os.sep):
# INFO: This is a project file; should not be absolute
return None
import rope.base.project
return rope.base.project.get_no_project().get_resource(path)
except exceptions.ResourceNotFoundError:
return None
class DOITextualToPyObject(TextualToPyObject):
"""For transforming textual form to `PyObject`
The textual form DOI uses is different from rope's standard
textual form. The reason is that we cannot find the needed
information by analyzing live objects. This class can be
used to transform DOI textual form to `PyObject` and later
we can convert it to standard textual form using
`TextualToPyObject` class.
"""
def _function_to_pyobject(self, textual):
path = textual[1]
lineno = int(textual[2])
pymodule = self._get_pymodule(path)
if pymodule is not None:
scope = pymodule.get_scope()
inner_scope = scope.get_inner_scope_for_line(lineno)
return inner_scope.pyobject
def _class_to_pyobject(self, textual):
path, name = textual[1:]
pymodule = self._get_pymodule(path)
if pymodule is None:
return None
module_scope = pymodule.get_scope()
suspected = None
if name in module_scope.get_names():
suspected = module_scope[name].get_object()
if suspected is not None and isinstance(suspected, rope.base.pyobjects.PyClass):
return suspected
else:
lineno = self._find_occurrence(name, pymodule.get_resource().read())
if lineno is not None:
inner_scope = module_scope.get_inner_scope_for_line(lineno)
return inner_scope.pyobject
def defined_to_pyobject(self, textual):
if len(textual) == 2:
return self._module_to_pyobject(textual)
else:
if textual[2].isdigit():
result = self._function_to_pyobject(textual)
else:
result = self._class_to_pyobject(textual)
if not isinstance(result, rope.base.pyobjects.PyModule):
return result
def _find_occurrence(self, name, source):
pattern = re.compile(r"^\s*class\s*" + name + r"\b")
lines = source.split("\n")
for i in range(len(lines)):
if pattern.match(lines[i]):
return i + 1
def path_to_resource(self, path):
import rope.base.libutils
relpath = rope.base.libutils.path_relative_to_project_root(self.project, path)
if relpath is not None:
path = relpath
return super(DOITextualToPyObject, self).path_to_resource(path)

View file

@ -0,0 +1,367 @@
# Based on super lightweight Simple Top-Down Parser from http://effbot.org/zone/simple-top-down-parsing.htm
# and https://bitbucket.org/emacsway/sqlbuilder/src/default/sqlbuilder/smartsql/contrib/evaluate.py
import re
from rope.base.utils import pycompat
from rope.base.oi.type_hinting import utils
from rope.base import utils as base_utils
class SymbolBase(object):
name = None # node/token type name
def __init__(self):
self.value = None # used by name and literals
self.first = None
self.second = None
self.third = None # used by tree nodes
def nud(self, parser):
raise SyntaxError("Syntax error (%r)." % self.name)
def led(self, left, parser):
raise SyntaxError("Unknown operator (%r)." % self.name)
def evaluate(self, pyobject):
raise NotImplementedError(self.name, self)
def __repr__(self):
if self.name == "(name)":
return "(%s %s)" % (self.name[1:-1], self.value)
out = [repr(self.name), self.first, self.second, self.third]
out = [str(i) for i in out if i]
return "(" + " ".join(out) + ")"
class SymbolTable(object):
def multi(func):
def _inner(self, names, *a, **kw):
for name in names.split():
func(self, name, *a, **kw)
return _inner
def __init__(self):
self.symbol_table = {}
def get(self, name, default=None):
return self.symbol_table.get(name, default)
def __getitem__(self, name):
return self.symbol_table[name]
def __iter__(self):
return iter(self.symbol_table)
def symbol(self, name, bp=0):
try:
s = self.symbol_table[name]
except KeyError:
class S(SymbolBase):
pass
s = S
s.__name__ = "symbol-" + name # for debugging
s.name = name
s.lbp = bp
self.symbol_table[name] = s
else:
s.lbp = max(bp, s.lbp)
return s
@multi
def infix(self, name, bp):
symbol = self.symbol(name, bp)
@method(symbol)
def led(self, left, parser):
self.first = left
self.second = parser.expression(bp)
return self
@multi
def infix_r(self, name, bp):
symbol = self.symbol(name, bp)
@method(symbol)
def led(self, left, parser):
self.first = left
self.second = parser.expression(bp - 0.1)
return self
def ternary(self, name, name2, bp):
symbol = self.symbol(name, bp)
symbol2 = self.symbol(name2)
@method(symbol)
def led(self, left, parser):
self.first = left
self.second = parser.expression(symbol2.lbp)
parser.advance(symbol2.name)
self.third = parser.expression(symbol2.lbp + 0.1)
return self
@multi
def prefix(self, name, bp):
symbol = self.symbol(name, bp)
@method(symbol)
def nud(self, parser):
self.first = parser.expression(bp)
return self
@multi
def postfix(self, name, bp):
symbol = self.symbol(name, bp)
@method(symbol)
def led(self, left, parser):
self.first = left
return self
multi = staticmethod(multi) # Just for code checker
symbol_table = SymbolTable()
class Lexer(object):
_token_pattern = re.compile(
r"""
\s*
(?:
(
[,()\[\]|]
| ->
| (?<=\s)(?:or)\b
) # operator
| ([a-zA-Z](?:\w|\.)*) # name
)
""",
re.U | re.S | re.X,
)
def __init__(self, symbol_table):
self.symbol_table = symbol_table
def tokenize(self, program):
for name, value in self._tokenize_expr(program):
symbol = symbol_table.get(value)
if symbol:
s = symbol()
elif name == "(name)":
symbol = symbol_table[name]
s = symbol()
s.value = value
else:
raise SyntaxError(
"Unknown operator ({0}). Possible operators are {1!r}".format(
value, list(self.symbol_table)
)
)
yield s
def _tokenize_expr(self, program):
if isinstance(program, bytes):
program = program.decode("utf-8")
# import pprint; pprint.pprint(self._token_pattern.findall(program))
for operator, name in self._token_pattern.findall(program):
if operator:
yield "(operator)", operator
elif name:
yield "(name)", name
else:
raise SyntaxError
yield "(end)", "(end)"
class Parser(object):
token = None
next = None
def __init__(self, lexer):
self.lexer = lexer
def parse(self, program):
generator = self.lexer.tokenize(program)
try:
self.next = generator.__next__ # PY3
except AttributeError:
self.next = generator.next
self.token = self.next()
return self.expression()
def expression(self, rbp=0):
t = self.token
self.token = self.next()
left = t.nud(self)
while rbp < self.token.lbp:
t = self.token
self.token = self.next()
left = t.led(left, self)
return left
def advance(self, name=None):
if name and self.token.name != name:
raise SyntaxError(
"Expected {0!r} but found {1!r}".format(name, self.token.name)
)
self.token = self.next()
def method(s):
assert issubclass(s, SymbolBase)
def bind(fn):
setattr(s, fn.__name__, fn)
return fn
return bind
symbol, infix, infix_r, prefix, postfix, ternary = (
symbol_table.symbol,
symbol_table.infix,
symbol_table.infix_r,
symbol_table.prefix,
symbol_table.postfix,
symbol_table.ternary,
)
symbol("(", 270)
symbol(")")
symbol("[", 250) # Parameters
symbol("]")
symbol("->", 230)
infix("|", 170)
infix("or", 170)
symbol(",")
symbol("(name)")
symbol("(end)")
@method(symbol("(name)"))
def nud(self, parser):
return self
@method(symbol("(name)"))
def evaluate(self, pyobject):
return utils.resolve_type(self.value, pyobject)
# Parametrized objects
@method(symbol("["))
def led(self, left, parser):
self.first = left
self.second = []
if parser.token.name != "]":
while 1:
if parser.token.name == "]":
break
self.second.append(parser.expression())
if parser.token.name != ",":
break
parser.advance(",")
parser.advance("]")
return self
@method(symbol("["))
def evaluate(self, pyobject):
return utils.parametrize_type(
self.first.evaluate(pyobject), *[i.evaluate(pyobject) for i in self.second]
)
# Anonymous Function Calls
@method(symbol("("))
def nud(self, parser):
self.second = []
if parser.token.name != ")":
while 1:
self.second.append(parser.expression())
if parser.token.name != ",":
break
parser.advance(",")
parser.advance(")")
parser.advance("->")
self.third = parser.expression(symbol("->").lbp + 0.1)
return self
# Function Calls
@method(symbol("("))
def led(self, left, parser):
self.first = left
self.second = []
if parser.token.name != ")":
while 1:
self.second.append(parser.expression())
if parser.token.name != ",":
break
parser.advance(",")
parser.advance(")")
parser.advance("->")
self.third = parser.expression(symbol("->").lbp + 0.1)
return self
@method(symbol("("))
def evaluate(self, pyobject):
# TODO: Implement me
raise NotImplementedError
@method(symbol("or"))
@method(symbol("|"))
def evaluate(self, pyobject):
# TODO: Implement me
raise NotImplementedError
class Compiler(object):
parser_factory = Parser
lexer_factory = Lexer
symbol_table = symbol_table
def _make_parser(self):
return self.parser_factory(self.lexer_factory(self.symbol_table))
@base_utils.cached(500)
def __call__(self, program):
"""
:type program: str
:rtype: rope.base.oi.type_hinting.evaluate.SymbolBase
"""
return self._make_parser().parse(program)
compile = Compiler()
class Evaluator(object):
compile = compile
def __call__(self, program, pyobject):
"""Evaluates the program string or AST
:type program: str or rope.base.oi.type_hinting.evaluate.SymbolBase
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
ast = (
self.compile(program)
if isinstance(program, pycompat.string_types)
else program
)
return ast.evaluate(pyobject)
evaluate = Evaluator()

View file

@ -0,0 +1,83 @@
from rope.base.oi.type_hinting import interfaces
from rope.base.oi.type_hinting.providers import (
composite,
inheritance,
docstrings,
numpydocstrings,
pep0484_type_comments,
)
from rope.base.oi.type_hinting.resolvers import composite as composite_resolvers, types
from rope.base import utils
class TypeHintingFactory(interfaces.ITypeHintingFactory):
@utils.saveit
def make_param_provider(self):
providers = [
docstrings.ParamProvider(
docstrings.DocstringParamParser(), self.make_resolver()
),
docstrings.ParamProvider(
numpydocstrings.NumPyDocstringParamParser(), self.make_resolver()
),
]
return inheritance.ParamProvider(composite.ParamProvider(*providers))
@utils.saveit
def make_return_provider(self):
providers = [
docstrings.ReturnProvider(
docstrings.DocstringReturnParser(), self.make_resolver()
),
]
return inheritance.ReturnProvider(composite.ReturnProvider(*providers))
@utils.saveit
def make_assignment_provider(self):
providers = [
pep0484_type_comments.AssignmentProvider(self.make_resolver()),
docstrings.AssignmentProvider(
docstrings.DocstringParamParser(), self.make_resolver()
),
docstrings.AssignmentProvider(
numpydocstrings.NumPyDocstringParamParser(), self.make_resolver()
),
]
return inheritance.AssignmentProvider(composite.AssignmentProvider(*providers))
@utils.saveit
def make_resolver(self):
"""
:rtype: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
resolvers = [
types.Resolver(),
]
return composite_resolvers.Resolver(*resolvers)
default_type_hinting_factory = TypeHintingFactory()
class TypeHintingFactoryAccessor(object):
def __call__(self, project):
"""
:type project: rope.base.project.Project
:rtype: rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
"""
factory_location = project.get_prefs().get(
"type_hinting_factory",
"rope.base.oi.type_hinting.factory.default_type_hinting_factory",
)
return self._get_factory(factory_location)
@utils.cached(10)
def _get_factory(self, factory_location):
"""
:type factory_location: str
:rtype: rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
"""
return utils.resolve(factory_location)
get_type_hinting_factory = TypeHintingFactoryAccessor()

View file

@ -0,0 +1,24 @@
class ITypeHintingFactory(object):
def make_param_provider(self):
"""
:rtype: rope.base.oi.type_hinting.providers.interfaces.IParamProvider
"""
raise NotImplementedError
def make_return_provider(self):
"""
:rtype: rope.base.oi.type_hinting.providers.interfaces.IReturnProvider
"""
raise NotImplementedError
def make_assignment_provider(self):
"""
:rtype: rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider
"""
raise NotImplementedError
def make_resolver(self):
"""
:rtype: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
raise NotImplementedError

View file

@ -0,0 +1,56 @@
from rope.base.oi.type_hinting.providers import interfaces
class ParamProvider(interfaces.IParamProvider):
def __init__(self, *delegates):
"""
:type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IParamProvider]
"""
self._delegates = delegates
def __call__(self, pyfunc, param_name):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:type param_name: str
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
for delegate in self._delegates:
result = delegate(pyfunc, param_name)
if result:
return result
class ReturnProvider(interfaces.IReturnProvider):
def __init__(self, *delegates):
"""
:type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IReturnProvider]
"""
self._delegates = delegates
def __call__(self, pyfunc):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
for delegate in self._delegates:
result = delegate(pyfunc)
if result:
return result
class AssignmentProvider(interfaces.IAssignmentProvider):
def __init__(self, *delegates):
"""
:type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider]
"""
self._delegates = delegates
def __call__(self, pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
for delegate in self._delegates:
result = delegate(pyname)
if result:
return result

View file

@ -0,0 +1,189 @@
"""
Hinting the type using docstring of class/function.
It's an irreplaceable thing if you are using Dependency Injection with passive class:
http://www.martinfowler.com/articles/injection.html
Some code extracted (or based on code) from:
https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py
Thanks to @davidhalter for this utils under MIT License.
Similar solutions:
- https://www.jetbrains.com/pycharm/help/type-hinting-in-pycharm.html
- https://www.python.org/dev/peps/pep-0484/#type-comments
- http://www.pydev.org/manual_adv_type_hints.html
- https://jedi.readthedocs.org/en/latest/docs/features.html#type-hinting
Discussions:
- https://groups.google.com/d/topic/rope-dev/JlAzmZ83K1M/discussion
- https://groups.google.com/d/topic/rope-dev/LCFNN98vckI/discussion
"""
import re
from rope.base.oi.type_hinting import utils
from rope.base.oi.type_hinting.providers import interfaces
class ParamProvider(interfaces.IParamProvider):
def __init__(self, docstring_parser, resolver):
"""
:type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IParamParser
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
self._parse_docstring = docstring_parser
self._resolve = resolver
def __call__(self, pyfunc, param_name):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:type param_name: str
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
type_strs = self._parse_docstring(pyfunc.get_doc(), param_name)
if type_strs:
return self._resolve(type_strs[0], pyfunc)
class ReturnProvider(interfaces.IReturnProvider):
def __init__(self, docstring_parser, resolver):
"""
:type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IReturnParser
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
self._parse_docstring = docstring_parser
self._resolve = resolver
def __call__(self, pyfunc):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
type_strs = self._parse_docstring(pyfunc.get_doc())
if type_strs:
return self._resolve(type_strs[0], pyfunc)
class AssignmentProvider(interfaces.IAssignmentProvider):
def __init__(self, docstring_parser, resolver):
"""
:type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IParamParser
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
self._parse_docstring = docstring_parser
self._resolve = resolver
def __call__(self, pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
try:
pyclass, attr_name = utils.get_class_with_attr_name(pyname)
except TypeError:
return
else:
type_strs = self._parse_docstring(pyclass.get_doc(), attr_name)
if type_strs:
return self._resolve(type_strs[0], pyclass)
class IParamParser(object):
def __call__(self, docstring, param_name):
"""
:type docstring: str
:type param_name: str
"""
class IReturnParser(object):
def __call__(self, docstring):
"""
:type docstring: str
"""
class DocstringParamParser(IParamParser):
DOCSTRING_PARAM_PATTERNS = [
r"\s*:type\s+%s:\s*([^\n]+)", # Sphinx
r"\s*:param\s+(\w+)\s+%s:[^\n]+", # Sphinx param with type
r"\s*@type\s+%s:\s*([^\n]+)", # Epydoc
]
def __init__(self):
self._strip_rst_role = RSTRoleStrip()
def __call__(self, docstring, param_name):
"""Search `docstring` for type(-s) of `param_name`.
>>> DocstringParamParser()(':type param: int', 'param')
['int']
>>> DocstringParamParser()('@type param: int', 'param')
['int']
>>> DocstringParamParser()(':type param: :class:`threading.Thread`', 'param')
['threading.Thread']
>>> bool(DocstringParamParser()('no document', 'param'))
False
>>> DocstringParamParser()(':param int param: some description', 'param')
['int']
"""
if not docstring:
return []
patterns = [
re.compile(p % re.escape(param_name)) for p in self.DOCSTRING_PARAM_PATTERNS
]
for pattern in patterns:
match = pattern.search(docstring)
if match:
return [self._strip_rst_role(match.group(1))]
return []
class DocstringReturnParser(IReturnParser):
DOCSTRING_RETURN_PATTERNS = [
re.compile(r"\s*:rtype:\s*([^\n]+)", re.M), # Sphinx
re.compile(r"\s*@rtype:\s*([^\n]+)", re.M), # Epydoc
]
def __init__(self):
self._strip_rst_role = RSTRoleStrip()
def __call__(self, docstring):
if not docstring:
return []
for p in self.DOCSTRING_RETURN_PATTERNS:
match = p.search(docstring)
if match:
return [self._strip_rst_role(match.group(1))]
return []
class RSTRoleStrip(object):
RST_ROLE_PATTERN = re.compile(r":[^`]+:`([^`]+)`")
def __call__(self, type_str):
"""
Strip off the part looks like a ReST role in `type_str`.
>>> RSTRoleStrip()(':class:`ClassName`') # strip off :class:
'ClassName'
>>> RSTRoleStrip()(':py:obj:`module.Object`') # works with domain
'module.Object'
>>> RSTRoleStrip()('ClassName') # do nothing when not ReST role
'ClassName'
See also:
http://sphinx-doc.org/domains.html#cross-referencing-python-objects
"""
match = self.RST_ROLE_PATTERN.match(type_str)
if match:
return match.group(1)
else:
return type_str

View file

@ -0,0 +1,63 @@
from rope.base.oi.type_hinting import utils
from rope.base.oi.type_hinting.providers import interfaces
class ParamProvider(interfaces.IParamProvider):
def __init__(self, delegate):
"""
:type delegate: rope.base.oi.type_hinting.providers.interfaces.IParamProvider
"""
self._delegate = delegate
def __call__(self, pyfunc, param_name):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:type param_name: str
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
superfunc = pyfunc
while superfunc:
result = self._delegate(superfunc, param_name)
if result:
return result
superfunc = utils.get_super_func(superfunc)
class ReturnProvider(interfaces.IReturnProvider):
def __init__(self, delegate):
"""
:type delegate: rope.base.oi.type_hinting.providers.interfaces.IReturnProvider
"""
self._delegate = delegate
def __call__(self, pyfunc):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
superfunc = pyfunc
while superfunc:
result = self._delegate(superfunc)
if result:
return result
superfunc = utils.get_super_func(superfunc)
class AssignmentProvider(interfaces.IAssignmentProvider):
def __init__(self, delegate):
"""
:type delegate: rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider
"""
self._delegate = delegate
def __call__(self, pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
super_pyname = pyname
while super_pyname:
result = self._delegate(super_pyname)
if result:
return result
super_pyname = utils.get_super_assignment(super_pyname)

View file

@ -0,0 +1,38 @@
class IParamProvider(object):
def __call__(self, pyfunc, param_name):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:type param_name: str
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
raise NotImplementedError
class IReturnProvider(object):
"""
:type resolve: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
resolve = None
def __call__(self, pyfunc):
"""
:type pyfunc: rope.base.pyobjectsdef.PyFunction
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
raise NotImplementedError
class IAssignmentProvider(object):
"""
:type resolve: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
resolve = None
def __call__(self, pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
raise NotImplementedError

View file

@ -0,0 +1,42 @@
"""
Some code extracted (or based on code) from:
https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py
Thanks to @davidhalter for this utils under MIT License.
"""
import re
from ast import literal_eval
from rope.base.oi.type_hinting.providers import docstrings
try:
from numpydoc.docscrape import NumpyDocString
except ImportError:
NumpyDocString = None
class NumPyDocstringParamParser(docstrings.IParamParser):
def __call__(self, docstring, param_name):
"""Search `docstring` (in numpydoc format) for type(-s) of `param_name`."""
if not docstring:
return []
params = NumpyDocString(docstring)._parsed_data["Parameters"]
for p_name, p_type, p_descr in params:
if p_name == param_name:
m = re.match("([^,]+(,[^,]+)*?)(,[ ]*optional)?$", p_type)
if m:
p_type = m.group(1)
if p_type.startswith("{"):
types = set(type(x).__name__ for x in literal_eval(p_type))
return list(types)
else:
return [p_type]
return []
class _DummyParamParser(docstrings.IParamParser):
def __call__(self, docstring, param_name):
return []
if not NumpyDocString:
NumPyDocstringParamParser = _DummyParamParser

View file

@ -0,0 +1,40 @@
import re
from rope.base.oi.type_hinting import utils
from rope.base.oi.type_hinting.providers import interfaces
class AssignmentProvider(interfaces.IAssignmentProvider):
def __init__(self, resolver):
"""
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
"""
self._resolve = resolver
PEP0484_TYPE_COMMENT_PATTERNS = (re.compile(r"type:\s*([^\n]+)"),)
def __call__(self, pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
from rope.base.oi.soi import _get_lineno_for_node
lineno = _get_lineno_for_node(pyname.assignments[0].ast_node)
holding_scope = pyname.module.get_scope().get_inner_scope_for_line(lineno)
line = holding_scope._get_global_scope()._scope_finder.lines.get_line(lineno)
if "#" in line:
type_strs = self._search_type_in_type_comment(line.split("#", 1)[1])
if type_strs:
return self._resolve(type_strs[0], holding_scope.pyobject)
def _search_type_in_type_comment(self, code):
"""For more info see:
https://www.python.org/dev/peps/pep-0484/#type-comments
>>> AssignmentProvider()._search_type_in_type_comment('type: int')
['int']
"""
for p in self.PEP0484_TYPE_COMMENT_PATTERNS:
match = p.search(code)
if match:
return [match.group(1)]

View file

@ -0,0 +1,21 @@
from rope.base.oi.type_hinting.resolvers import interfaces
class Resolver(interfaces.IResolver):
def __init__(self, *delegates):
"""
:type delegates: list[rope.base.oi.type_hinting.resolvers.interfaces.IResolver]
"""
self._delegates = delegates
def __call__(self, hint, pyobject):
"""
:param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo"
:type hint: str
:type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
for delegate in self._delegates:
result = delegate(hint, pyobject)
if result:
return result

View file

@ -0,0 +1,9 @@
class IResolver(object):
def __call__(self, hint, pyobject):
"""
:param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo"
:type hint: str
:type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
raise NotImplementedError

View file

@ -0,0 +1,16 @@
from rope.base.oi.type_hinting import evaluate
from rope.base.oi.type_hinting.resolvers import interfaces
class Resolver(interfaces.IResolver):
def __call__(self, hint, pyobject):
"""
:param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo"
:type hint: str
:type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
try:
return evaluate.evaluate(hint, pyobject)
except (Exception):
pass

View file

@ -0,0 +1,165 @@
import logging
try:
from typing import Union, Optional
except ImportError:
pass
import rope.base.utils as base_utils
from rope.base.evaluate import ScopeNameFinder
from rope.base.exceptions import AttributeNotFoundError
from rope.base.pyobjects import PyClass, PyDefinedObject, PyFunction, PyObject
from rope.base.utils import pycompat
def get_super_func(pyfunc):
if not isinstance(pyfunc.parent, PyClass):
return
for cls in get_mro(pyfunc.parent)[1:]:
try:
superfunc = cls.get_attribute(pyfunc.get_name()).get_object()
except AttributeNotFoundError:
pass
else:
if isinstance(superfunc, PyFunction):
return superfunc
def get_super_assignment(pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:type: rope.base.pynamesdef.AssignedName
"""
try:
pyclass, attr_name = get_class_with_attr_name(pyname)
except TypeError:
return
else:
for super_pyclass in get_mro(pyclass)[1:]:
if attr_name in super_pyclass:
return super_pyclass[attr_name]
def get_class_with_attr_name(pyname):
"""
:type pyname: rope.base.pynamesdef.AssignedName
:return: rope.base.pyobjectsdef.PyClass, str
:rtype: tuple
"""
lineno = get_lineno_for_node(pyname.assignments[0].ast_node)
holding_scope = pyname.module.get_scope().get_inner_scope_for_line(lineno)
pyobject = holding_scope.pyobject
if isinstance(pyobject, PyClass):
pyclass = pyobject
elif isinstance(pyobject, PyFunction) and isinstance(pyobject.parent, PyClass):
pyclass = pyobject.parent
else:
return
for name, attr in pyclass.get_attributes().items():
if attr is pyname:
return (pyclass, name)
def get_lineno_for_node(assign_node):
if hasattr(assign_node, "lineno") and assign_node.lineno is not None:
return assign_node.lineno
return 1
def get_mro(pyclass):
# FIXME: to use real mro() result
class_list = [pyclass]
for cls in class_list:
for super_cls in cls.get_superclasses():
if isinstance(super_cls, PyClass) and super_cls not in class_list:
class_list.append(super_cls)
return class_list
def resolve_type(type_name, pyobject):
# type: (str, Union[PyDefinedObject, PyObject]) -> Optional[PyDefinedObject, PyObject]
"""
Find proper type object from its name.
"""
deprecated_aliases = {"collections": "collections.abc"}
ret_type = None
logging.debug("Looking for %s", type_name)
if "." not in type_name:
try:
ret_type = (
pyobject.get_module().get_scope().get_name(type_name).get_object()
)
except AttributeNotFoundError:
logging.exception("Cannot resolve type %s", type_name)
else:
mod_name, attr_name = type_name.rsplit(".", 1)
try:
mod_finder = ScopeNameFinder(pyobject.get_module())
mod = mod_finder._find_module(mod_name).get_object()
ret_type = mod.get_attribute(attr_name).get_object()
except AttributeNotFoundError:
if mod_name in deprecated_aliases:
try:
logging.debug(
"Looking for %s in %s", attr_name, deprecated_aliases[mod_name]
)
mod = mod_finder._find_module(
deprecated_aliases[mod_name]
).get_object()
ret_type = mod.get_attribute(attr_name).get_object()
except AttributeNotFoundError:
logging.exception(
"Cannot resolve type %s in %s", attr_name, dir(mod)
)
logging.debug("ret_type = %s", ret_type)
return ret_type
class ParametrizeType(object):
_supported_mapping = {
"builtins.list": "rope.base.builtins.get_list",
"builtins.tuple": "rope.base.builtins.get_tuple",
"builtins.set": "rope.base.builtins.get_set",
"builtins.dict": "rope.base.builtins.get_dict",
"_collections_abc.Iterable": "rope.base.builtins.get_iterator",
"_collections_abc.Iterator": "rope.base.builtins.get_iterator",
"collections.abc.Iterable": "rope.base.builtins.get_iterator", # Python3.3
"collections.abc.Iterator": "rope.base.builtins.get_iterator", # Python3.3
}
if pycompat.PY2:
_supported_mapping = dict(
(
(
k.replace("builtins.", "__builtin__.").replace(
"_collections_abc.", "_abcoll."
),
v,
)
for k, v in _supported_mapping.items()
)
)
def __call__(self, pyobject, *args, **kwargs):
"""
:type pyobject: rope.base.pyobjects.PyObject
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
"""
type_factory = self._get_type_factory(pyobject)
if type_factory:
parametrized_type = type_factory(*args, **kwargs)
if parametrized_type:
return parametrized_type
return pyobject
def _get_type_factory(self, pyobject):
type_str = "{0}.{1}".format(
pyobject.get_module().get_name(),
pyobject.get_name(),
)
if type_str in self._supported_mapping:
return base_utils.resolve(self._supported_mapping[type_str])
parametrize_type = ParametrizeType()