Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
0bab02d
switch to sqllite3
bagel897 Apr 2, 2022
0df9167
use ast
bagel897 Apr 4, 2022
3f2c1a8
refactor to use ProcessPoolExecutor
bagel897 Apr 4, 2022
55de3ae
actually somewhat working implementation
bagel897 Apr 4, 2022
fa945e4
parse __all__
bagel897 Apr 4, 2022
0a20abd
improve parsing of modules with submodules
bagel897 Apr 4, 2022
56460d5
fix resource parsing
bagel897 Apr 4, 2022
b622adc
add package finder and update_module
bagel897 Apr 4, 2022
70e2b99
propogate underlined more
bagel897 Apr 4, 2022
42ef20d
match public api more closely
bagel897 Apr 4, 2022
f57fdbb
migrate to pytest
bagel897 Apr 4, 2022
b49dda1
handle observer module names correctly
bagel897 Apr 4, 2022
3f93257
error propogation
bagel897 Apr 5, 2022
9290528
get name locations
bagel897 Apr 5, 2022
b37fad5
update generate_module_cache to exclude the project
bagel897 Apr 5, 2022
65f0c72
add builtin handling
bagel897 Apr 5, 2022
08e8768
split into multiple files
bagel897 Apr 5, 2022
c0aba3d
make private api public within autoimport
bagel897 Apr 5, 2022
ab11c36
add so parsing
bagel897 Apr 5, 2022
2155385
fix builtin handling
bagel897 Apr 5, 2022
bc21e92
reformat
bagel897 Apr 5, 2022
03585be
fix project location issue
bagel897 Apr 5, 2022
ebfc3b8
update documentation
bagel897 Apr 5, 2022
f719f95
use code block
bagel897 Apr 5, 2022
90a1600
use code block
bagel897 Apr 5, 2022
a9ddf65
improve formatting and change example to Dict
bagel897 Apr 5, 2022
ed12e20
use old testsuite(mostly)
bagel897 Apr 5, 2022
b197ce4
move packages to dedicated database and improve detection of bogus pa…
bagel897 Apr 5, 2022
7e41f57
improve detection of bogus packages
bagel897 Apr 5, 2022
2182bfe
improve detection of bogus packages
bagel897 Apr 5, 2022
f77a63b
fix amend issue
bagel897 Apr 5, 2022
24d0517
Move compiled modules to single thread, add single threaded option
bagel897 Apr 7, 2022
fbc1cd3
propogate underlined, handle directories without __init__, detect com…
bagel897 Apr 7, 2022
ed5876a
update docs on manual sources
bagel897 Apr 7, 2022
056ec2c
reformat with black
bagel897 Apr 7, 2022
73b4d32
Merge branch 'master' into master
bagel897 Apr 7, 2022
2dfa97b
python 3.6 compatibility
bagel897 Apr 7, 2022
c4ce0aa
Merge branch 'master' of github.com:bageljrkhanofemus/rope
bagel897 Apr 7, 2022
92dff73
add some more tests
bagel897 Apr 7, 2022
1dcc8f6
Merge branch 'master' into master
lieryan Apr 8, 2022
1511451
add utils tests
bagel897 Apr 8, 2022
50d8f9e
fix several issues with handling non-python files
bagel897 Apr 9, 2022
5ef7608
add exact_match toggle
bagel897 Apr 9, 2022
c3eaf26
handle python_crun, return module name with search
bagel897 Apr 11, 2022
2a7077f
add lsp search
bagel897 Apr 12, 2022
46252f6
add nametype
bagel897 Apr 12, 2022
54f9dcb
Merge branch 'master' of github.com:bageljrkhanofemus/rope
bagel897 Apr 12, 2022
b793a85
Update workflows to install rope in editable mode
bagel897 Apr 12, 2022
8fe2817
WIP use generators
bagel897 Apr 13, 2022
9e6d956
itemkind infrence for compiled modules
bagel897 Apr 13, 2022
277edf1
add more tuples and use them more
bagel897 Apr 13, 2022
9f09410
WIP on autoimport using namedTuples
bagel897 Apr 13, 2022
780b52b
mostly working threaded implementation
bagel897 Apr 13, 2022
7035b6e
reformat and update tests
bagel897 Apr 13, 2022
30595cd
blacken
bagel897 Apr 13, 2022
292df31
check underlined on submodules
bagel897 Apr 13, 2022
7c4d3c2
fix some typos
bagel897 Apr 14, 2022
dea4f42
fix: basic job set implementation, improve list comprehension of utils
bagel897 Apr 14, 2022
fc8d3d7
hack to make the job_set work
bagel897 Apr 14, 2022
f52695b
fix: don't increment on null job sets
bagel897 Apr 14, 2022
0471996
Restore the pickle-based implementation as default autoimport
lieryan Apr 28, 2022
78183da
Elaborate on the state of pickle and sqlite3-based autoimport impleme…
lieryan Apr 28, 2022
e52b60d
Black
lieryan Apr 28, 2022
5596ee5
Fix autoimport test to run against the sqlite3 implementation
lieryan Apr 28, 2022
4c3dbca
Fix type hint construct that are not supported in Python 3.8 and older
lieryan Apr 28, 2022
842b5d3
Merge remote-tracking branch 'origin/master' into bageljrkhanofemus-4…
lieryan Apr 28, 2022
7fc0c3d
typing.Dict do not exist in Python 3.6
lieryan Apr 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip pytest pytest-timeout
python -m pip install -e .[dev]
- name: Test with pytest
run: |
pytest -v
Expand Down
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# **Upcoming release**

## XXX

- XXX
## New feature
- #464 Improve autoimport code to use a sqllite3 database, cache all available modules quickly, search for names and produce import statements, sort import statements.

# Release 1.0.0

Expand Down
28 changes: 28 additions & 0 deletions docs/library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,34 @@ returns the list of modules with the given global name.
global name that starts with the given prefix.


There are currently two implementations of autoimport in rope, a deprecated
implementation that uses pickle-based storage
(rope.contrib.autoimport.pickle.AutoImport) and a new, experimental one that
uses sqlite3 database (rope.contrib.autoimport.sqlite.AutoImport). New and
existing integrations should migrate to the sqlite3 storage as the pickle-based
autoimport will be removed in the future.

By default, the sqlite3-based only stores autoimport cache in an in-memory
sqlite3 database, you can make it write the import cache to persistent storage
by passing memory=False to AutoImport constructor.

It must be closed when done with the ```AutoImport.close()``` method.

AutoImport can search for a name from both modules and statements you can import from them.

.. code-block:: python

from rope.base.project import Project
from rope.contrib.autoimport import AutoImport

project = Project("/path/to/project")
autoimport = AutoImport(project, memory=False)
autoimport.generate_resource_cache() # Generates a cache of the local modules, from the project you're working on
autoimport.generate_modules_cache() # Generates a cache of external modules
print(autoimport.search("Dict"))
autoimport.close()


Cross-Project Refactorings
--------------------------

Expand Down
8 changes: 8 additions & 0 deletions rope/contrib/autoimport/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""AutoImport module for rope."""
from .pickle import AutoImport as _PickleAutoImport
from .sqlite import AutoImport as _SqliteAutoImport


AutoImport = _PickleAutoImport

__all__ = ["AutoImport"]
109 changes: 109 additions & 0 deletions rope/contrib/autoimport/defs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Definitions of types for the Autoimport program."""
import pathlib
from enum import Enum
from typing import NamedTuple, Optional


class Source(Enum):
"""Describes the source of the package, for sorting purposes."""

PROJECT = 0 # Obviously any project packages come first
MANUAL = 1 # Placeholder since Autoimport classifies manually added modules
BUILTIN = 2
STANDARD = 3 # We want to favor standard library items
SITE_PACKAGE = 4
UNKNOWN = 5

# modified_time


class ModuleInfo(NamedTuple):
"""Descriptor of information to get names from a module."""

filepath: Optional[pathlib.Path]
modname: str
underlined: bool
process_imports: bool = False


class ModuleFile(ModuleInfo):
"""Descriptor of information to get names from a file using ast."""

filepath: pathlib.Path
modname: str
underlined: bool
process_imports: bool = False


class ModuleCompiled(ModuleInfo):
"""Descriptor of information to get names using imports."""

filepath = None
modname: str
underlined: bool
process_imports: bool = False


class PackageType(Enum):
"""Describes the type of package, to determine how to get the names from it."""

BUILTIN = 0 # No file exists, compiled into python. IE: Sys
STANDARD = 1 # Just a folder
COMPILED = 2 # .so module
SINGLE_FILE = 3 # a .py file


class NameType(Enum):
"""Describes the type of Name for lsp completions. Taken from python lsp server."""

Text = 1
Method = 2
Function = 3
Constructor = 4
Field = 5
Variable = 6
Class = 7
Interface = 8
Module = 9
Property = 10
Unit = 11
Value = 12
Enum = 13
Keyword = 14
Snippet = 15
Color = 16
File = 17
Reference = 18
Folder = 19
EnumMember = 20
Constant = 21
Struct = 22
Event = 23
Operator = 24
TypeParameter = 25


class Package(NamedTuple):
"""Attributes of a package."""

name: str
source: Source
path: Optional[pathlib.Path]
type: PackageType


class Name(NamedTuple):
"""A Name to be added to the database."""

name: str
modname: str
package: str
source: Source
name_type: NameType


class PartialName(NamedTuple):
"""Partial information of a Name."""

name: str
name_type: NameType
137 changes: 137 additions & 0 deletions rope/contrib/autoimport/parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
Functions to find importable names.

Can extract names from source code of a python file, .so object, or builtin module.
"""

import ast
import inspect
import logging
import pathlib
from importlib import import_module
from typing import Generator, List

from .defs import (
ModuleCompiled,
ModuleFile,
ModuleInfo,
Name,
NameType,
Package,
PartialName,
Source,
)

logger = logging.getLogger(__name__)


def get_type_ast(node: ast.AST) -> NameType:
"""Get the lsp type of a node."""
if isinstance(node, ast.ClassDef):
return NameType.Class
if isinstance(node, ast.FunctionDef):
return NameType.Function
if isinstance(node, ast.Assign):
return NameType.Variable
return NameType.Text # default value


def get_names_from_file(
module: pathlib.Path,
underlined: bool = False,
) -> Generator[PartialName, None, None]:
"""Get all the names from a given file using ast."""

try:
root_node = ast.parse(module.read_bytes())
except SyntaxError as error:
print(error)
return
for node in ast.iter_child_nodes(root_node):
if isinstance(node, ast.Assign):
for target in node.targets:
try:
assert isinstance(target, ast.Name)
if underlined or not target.id.startswith("_"):
yield PartialName(
target.id,
get_type_ast(node),
)
except (AttributeError, AssertionError):
# TODO handle tuple assignment
pass
elif isinstance(node, (ast.FunctionDef, ast.ClassDef)):
if underlined or not node.name.startswith("_"):
yield PartialName(
node.name,
get_type_ast(node),
)


def get_type_object(imported_object) -> NameType:
"""Determine the type of an object."""
if inspect.isclass(imported_object):
return NameType.Class
if inspect.isfunction(imported_object) or inspect.isbuiltin(imported_object):
return NameType.Function
return NameType.Constant


def get_names(module: ModuleInfo, package: Package) -> List[Name]:
"""Get all names from a module and package."""
if isinstance(module, ModuleCompiled):
return list(
get_names_from_compiled(package.name, package.source, module.underlined)
)
if isinstance(module, ModuleFile):
return [
combine(package, module, partial_name)
for partial_name in get_names_from_file(module.filepath, module.underlined)
]
return []


def get_names_from_compiled(
package: str,
source: Source,
underlined: bool = False,
) -> Generator[Name, None, None]:
"""
Get the names from a compiled module.

Instead of using ast, it imports the module.
Parameters
----------
package : str
package to import. Must be in sys.path
underlined : bool
include underlined names
"""
# builtins is banned because you never have to import it
# python_crun is banned because it crashes python
banned = ["builtins", "python_crun"]
if package in banned or (package.startswith("_") and not underlined):
return # Builtins is redundant since you don't have to import it.
if source not in (Source.BUILTIN, Source.STANDARD):
return
try:
module = import_module(str(package))
except ImportError:
logger.error(f"{package} could not be imported for autoimport analysis")
return
else:
for name, value in inspect.getmembers(module):
if underlined or not name.startswith("_"):
if (
inspect.isclass(value)
or inspect.isfunction(value)
or inspect.isbuiltin(value)
):
yield Name(
str(name), package, package, source, get_type_object(value)
)


def combine(package: Package, module: ModuleFile, name: PartialName) -> Name:
"""Combine information to form a full name."""
return Name(name.name, module.modname, package.name, package.source, name.name_type)
12 changes: 12 additions & 0 deletions rope/contrib/autoimport.py → rope/contrib/autoimport/pickle.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
"""
IMPORTANT: This is a deprecated implementation of autoimport using pickle-based
storage.

This pickle-based autoimport is provided only for backwards compatibility
purpose and will be removed and the sqlite backend will be the new default
implementation in the future.

If you are still using this module, you should migrate to the new and improved
sqlite-based storage backend (rope.contrib.autoimport.sqlite.AutoImport).
"""

import re

from rope.base import builtins
Expand Down
Loading