From 310fafe06aa2851b473c73c04be73ac4a1bdf772 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Tue, 9 Jan 2024 12:50:03 -0600 Subject: [PATCH 01/11] rebase onto master --- CHANGELOG.md | 2 +- docs/configuration.rst | 9 ++- rope/base/prefs.py | 10 ++++ rope/base/versioning.py | 5 +- rope/contrib/autoimport/sqlite.py | 83 ++++++++++++++++++-------- rope/contrib/autoimport/utils.py | 2 +- ropetest/contrib/autoimport/deptest.py | 54 +++++++++++++++++ 7 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 ropetest/contrib/autoimport/deptest.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cd0c47c1a..ab59f07b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # **Upcoming release** - +- #516 Autoimport Now automatically detects project dependencies and can read TOML configuration - #733 skip directories with perm error when building autoimport index (@MrBago) - #722, #723 Remove site-packages from packages search tree (@tkrabel) - #738 Implement os.PathLike on Resource (@lieryan) diff --git a/docs/configuration.rst b/docs/configuration.rst index 5aba53acb..fb703f862 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -14,6 +14,8 @@ Will be used if [tool.rope] is configured. [tool.rope] split_imports = true + [tool.rope.autoimport] + underlined = false config.py --------- @@ -56,9 +58,14 @@ It follows the exact same syntax of the pyproject.toml. Options -------- +======= .. autopytoolconfigtable:: rope.base.prefs.Prefs +Autoimport Options +------------------ +.. autopytoolconfigtable:: rope.base.prefs.AutoimportPrefs + + Old Configuration File ---------------------- This is a sample config.py. While this config.py works and all options here should be supported, the above documentation reflects the latest version of rope. diff --git a/rope/base/prefs.py b/rope/base/prefs.py index 9d4fd4a43..ae35d7d68 100644 --- a/rope/base/prefs.py +++ b/rope/base/prefs.py @@ -12,6 +12,14 @@ from rope.base.resources import Folder +@dataclass +class AutoimportPrefs: + underlined: bool = field( + default=False, description="Cache underlined (private) modules") + memory: bool = field(default=None, description="Cache in memory instead of disk") + parallel: bool = field(default=True, description="Use multiple processes to parse") + + @dataclass class Prefs: """Class to store rope preferences.""" @@ -206,6 +214,8 @@ class Prefs: Can only be set in config.py. """), ) + autoimport: AutoimportPrefs = field( + default_factory=lambda: AutoimportPrefs(), description="Preferences for Autoimport") def set(self, key: str, value: Any): """Set the value of `key` preference to `value`.""" diff --git a/rope/base/versioning.py b/rope/base/versioning.py index 985255e47..4f0615902 100644 --- a/rope/base/versioning.py +++ b/rope/base/versioning.py @@ -1,3 +1,4 @@ +import dataclasses import hashlib import importlib.util import json @@ -31,7 +32,9 @@ def _get_prefs_data(project) -> str: del prefs_data["project_opened"] del prefs_data["callbacks"] del prefs_data["dependencies"] - return json.dumps(prefs_data, sort_keys=True, indent=2) + return json.dumps( + prefs_data, sort_keys=True, indent=2, default=lambda o: o.__dict__ + ) def _get_file_content(module_name: str) -> str: diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index 3d5d35977..5e1f9c466 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -16,7 +16,10 @@ from threading import local from typing import Generator, Iterable, Iterator, List, Optional, Set, Tuple +from packaging.requirements import Requirement + from rope.base import exceptions, libutils, resourceobserver, taskhandle, versioning +from rope.base.prefs import AutoimportPrefs from rope.base.project import Project from rope.base.resources import Resource from rope.contrib.autoimport import models @@ -53,18 +56,36 @@ def get_future_names( def filter_packages( - packages: Iterable[Package], underlined: bool, existing: List[str] + packages: Iterable[Package], + underlined: bool, + existing: List[str], + dependencies: Optional[List[Requirement]], ) -> Iterable[Package]: """Filter list of packages to parse.""" + parsed_deps = ( + [dep.name for dep in dependencies] if dependencies is not None else None + ) + + def is_dep(package) -> bool: + return ( + parsed_deps is None + or package.name in parsed_deps + or package.source is not Source.SITE_PACKAGE + ) + if underlined: def filter_package(package: Package) -> bool: - return package.name not in existing + return package.name not in existing and is_dep(package) else: def filter_package(package: Package) -> bool: - return package.name not in existing and not package.name.startswith("_") + return ( + package.name not in existing + and not package.name.startswith("_") + and is_dep(package) + ) return filter(filter_package, packages) @@ -81,16 +102,15 @@ class AutoImport: """ connection: sqlite3.Connection - memory: bool project: Project project_package: Package - underlined: bool + prefs: AutoimportPrefs def __init__( self, project: Project, observe: bool = True, - underlined: bool = False, + underlined: Optional[bool] = None, memory: bool = _deprecated_default, ): """Construct an AutoImport object. @@ -113,25 +133,29 @@ def __init__( autoimport = AutoImport(..., memory=True) """ self.project = project + self.prefs = self.project.prefs.autoimport project_package = get_package_tuple(project.root.pathlib, project) assert project_package is not None assert project_package.path is not None + if underlined is not None: + self.prefs.underlined = underlined self.project_package = project_package - self.underlined = underlined - self.memory = memory if memory is _deprecated_default: - self.memory = True - warnings.warn( - "The default value for `AutoImport(memory)` argument will " - "change to use an on-disk database by default in the future. " - "If you want to use an in-memory database, you need to pass " - "`AutoImport(memory=True)` explicitly.", - DeprecationWarning, - ) + if self.prefs.memory is None: + self.prefs.memory = True + warnings.warn( + "The default value for `AutoImport(memory)` argument will " + "change to use an on-disk database by default in the future. " + "If you want to use an in-memory database, you need to pass " + "`AutoImport(memory=True)` explicitly or set it in the config file.", + DeprecationWarning, + ) + else: + self.prefs.memory = memory self.thread_local = local() self.connection = self.create_database_connection( project=project, - memory=memory, + memory=self.prefs.memory, ) self._setup_db() if observe: @@ -389,12 +413,13 @@ def generate_modules_cache( """ Generate global name cache for external modules listed in `modules`. - If no modules are provided, it will generate a cache for every module available. + If modules is not specified, uses PEP 621 metadata. + If modules aren't specified and PEP 621 is not present, caches every package This method searches in your sys.path and configured python folders. Do not use this for generating your own project's internal names, use generate_resource_cache for that instead. """ - underlined = self.underlined if underlined is None else underlined + underlined = self.prefs.underlined if underlined is None else underlined packages: List[Package] = ( self._get_available_packages() @@ -403,12 +428,16 @@ def generate_modules_cache( ) existing = self._get_packages_from_cache() - packages = list(filter_packages(packages, underlined, existing)) - if not packages: + packages = list( + filter_packages( + packages, underlined, existing, self.project.prefs.dependencies + ) + ) + if len(packages) == 0: return self._add_packages(packages) job_set = task_handle.create_jobset("Generating autoimport cache", 0) - if single_thread: + if single_thread or not self.prefs.parallel: for package in packages: for module in get_files(package, underlined): job_set.started_job(module.modname) @@ -512,7 +541,7 @@ def update_resource( self, resource: Resource, underlined: bool = False, commit: bool = True ): """Update the cache for global names in `resource`.""" - underlined = underlined if underlined else self.underlined + underlined = underlined if underlined else self.prefs.underlined module = self._resource_to_module(resource, underlined) self._del_if_exist(module_name=module.modname, commit=False) for name in get_names(module, self.project_package): @@ -537,7 +566,11 @@ def _del_if_exist(self, module_name, commit: bool = True): def _get_python_folders(self) -> List[Path]: def filter_folders(folder: Path) -> bool: - return folder.is_dir() and folder.as_posix() != "/usr/bin" + return ( + folder.is_dir() + and folder.as_posix() != "/usr/bin" + and str(folder) != self.project.address + ) folders = self.project.get_python_path_folders() folder_paths = filter(filter_folders, map(Path, folders)) @@ -623,7 +656,7 @@ def _resource_to_module( self, resource: Resource, underlined: bool = False ) -> ModuleFile: assert self.project_package.path - underlined = underlined if underlined else self.underlined + underlined = underlined if underlined else self.prefs.underlined resource_path: Path = resource.pathlib # The project doesn't need its name added to the path, # since the standard python file layout accounts for that diff --git a/rope/contrib/autoimport/utils.py b/rope/contrib/autoimport/utils.py index 5238339de..bc80403c5 100644 --- a/rope/contrib/autoimport/utils.py +++ b/rope/contrib/autoimport/utils.py @@ -118,7 +118,7 @@ def get_files( yield ModuleFile(package.path, package.path.stem, underlined, False) else: assert package.path - for file in package.path.glob("**/*.py"): + for file in package.path.rglob("*.py"): if file.name == "__init__.py": yield ModuleFile( file, diff --git a/ropetest/contrib/autoimport/deptest.py b/ropetest/contrib/autoimport/deptest.py new file mode 100644 index 000000000..9dcefd132 --- /dev/null +++ b/ropetest/contrib/autoimport/deptest.py @@ -0,0 +1,54 @@ +from pathlib import Path +from typing import Iterable + +import pytest + +from rope.base.project import Project +from rope.contrib.autoimport.sqlite import AutoImport + + +@pytest.fixture +def project(request, tmp_path: Path) -> Iterable[Project]: + doc = request.param + if doc is not None: + file = tmp_path / "pyproject.toml" + file.write_text(doc, encoding="utf-8") + print(file, doc) + project = Project(tmp_path) + yield project + project.close() + + +@pytest.fixture +def autoimport(project) -> Iterable[AutoImport]: + autoimport = AutoImport(project, memory=True) + autoimport.generate_modules_cache() + yield autoimport + autoimport.close() + + +@pytest.mark.parametrize("project", ((""),), indirect=True) +def test_blank(project, autoimport): + assert project.prefs.dependencies is None + assert autoimport.search("pytoolconfig") + + +@pytest.mark.parametrize("project", (("[project]\n dependencies=[]"),), indirect=True) +def test_empty(project, autoimport): + assert len(project.prefs.dependencies) == 0 + assert [] == autoimport.search("pytoolconfig") + + +FILE = """ +[project] +dependencies = [ + "pytoolconfig", + "bogus" +] +""" + + +@pytest.mark.parametrize("project", ((FILE),), indirect=True) +def test_not_empty(project, autoimport): + assert len(project.prefs.dependencies) == 2 + assert autoimport.search("pytoolconfig") From c498523a461231bdcaed7d17ec66bb6d02756558 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Tue, 9 Jan 2024 12:55:52 -0600 Subject: [PATCH 02/11] fix some memory things --- rope/contrib/autoimport/sqlite.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index 5e1f9c466..6b520d535 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -141,15 +141,14 @@ def __init__( self.prefs.underlined = underlined self.project_package = project_package if memory is _deprecated_default: - if self.prefs.memory is None: - self.prefs.memory = True - warnings.warn( - "The default value for `AutoImport(memory)` argument will " - "change to use an on-disk database by default in the future. " - "If you want to use an in-memory database, you need to pass " - "`AutoImport(memory=True)` explicitly or set it in the config file.", - DeprecationWarning, - ) + self.prefs.memory = True + warnings.warn( + "The default value for `AutoImport(memory)` argument will " + "change to use an on-disk database by default in the future. " + "If you want to use an in-memory database, you need to pass " + "`AutoImport(memory=True)` explicitly or set it in the config file.", + DeprecationWarning, + ) else: self.prefs.memory = memory self.thread_local = local() @@ -211,7 +210,7 @@ def connection(self): if not hasattr(self.thread_local, "connection"): self.thread_local.connection = self.create_database_connection( project=self.project, - memory=self.memory, + memory=self.prefs.memory, ) return self.thread_local.connection From 1317549339ab88f97c4cb26053952ecf0ed0f883 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Tue, 9 Jan 2024 13:00:57 -0600 Subject: [PATCH 03/11] avoid site-packages --- rope/contrib/autoimport/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rope/contrib/autoimport/utils.py b/rope/contrib/autoimport/utils.py index bc80403c5..a2e51d990 100644 --- a/rope/contrib/autoimport/utils.py +++ b/rope/contrib/autoimport/utils.py @@ -119,6 +119,8 @@ def get_files( else: assert package.path for file in package.path.rglob("*.py"): + if "site-packages" in file.parts: + continue if file.name == "__init__.py": yield ModuleFile( file, From 8650ff6388df42fe22bd1f0e7f94200aa0e99147 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Tue, 9 Jan 2024 13:07:17 -0600 Subject: [PATCH 04/11] pass memory=True for tests --- ropetest/contrib/autoimport/autoimporttest.py | 8 ++++---- ropetest/contrib/autoimporttest.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ropetest/contrib/autoimport/autoimporttest.py b/ropetest/contrib/autoimport/autoimporttest.py index 072ab1ed8..f9496db60 100644 --- a/ropetest/contrib/autoimport/autoimporttest.py +++ b/ropetest/contrib/autoimport/autoimporttest.py @@ -14,7 +14,7 @@ @pytest.fixture def autoimport(project: Project): - with closing(AutoImport(project)) as ai: + with closing(AutoImport(project, memory=True)) as ai: yield ai @@ -124,9 +124,9 @@ def foo(): def test_connection(project: Project, project2: Project): - ai1 = AutoImport(project) - ai2 = AutoImport(project) - ai3 = AutoImport(project2) + ai1 = AutoImport(project, memory=True) + ai2 = AutoImport(project, memory=True) + ai3 = AutoImport(project2, memory=True) assert ai1.connection is not ai2.connection assert ai1.connection is not ai3.connection diff --git a/ropetest/contrib/autoimporttest.py b/ropetest/contrib/autoimporttest.py index 94bb033bb..fb21bbd2b 100644 --- a/ropetest/contrib/autoimporttest.py +++ b/ropetest/contrib/autoimporttest.py @@ -11,7 +11,7 @@ def setUp(self): self.mod1 = testutils.create_module(self.project, "mod1") self.pkg = testutils.create_package(self.project, "pkg") self.mod2 = testutils.create_module(self.project, "mod2", self.pkg) - self.importer = autoimport.AutoImport(self.project, observe=False) + self.importer = autoimport.AutoImport(self.project, observe=False, memory=True) def tearDown(self): testutils.remove_project(self.project) @@ -86,7 +86,7 @@ def test_not_caching_underlined_names(self): self.assertEqual(["mod1"], self.importer.get_modules("_myvar")) def test_caching_underlined_names_passing_to_the_constructor(self): - importer = autoimport.AutoImport(self.project, False, True) + importer = autoimport.AutoImport(self.project, False, True, memory=True) self.mod1.write("_myvar = None\n") importer.update_resource(self.mod1) self.assertEqual(["mod1"], importer.get_modules("_myvar")) @@ -165,7 +165,7 @@ def setUp(self): self.mod1 = testutils.create_module(self.project, "mod1") self.pkg = testutils.create_package(self.project, "pkg") self.mod2 = testutils.create_module(self.project, "mod2", self.pkg) - self.importer = autoimport.AutoImport(self.project, observe=True) + self.importer = autoimport.AutoImport(self.project, observe=True, memory=True) def tearDown(self): testutils.remove_project(self.project) From e9fa30d9c5bf8d308419ab191b2ddab787162ce0 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Tue, 9 Jan 2024 13:08:51 -0600 Subject: [PATCH 05/11] update site-packages detection --- rope/contrib/autoimport/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rope/contrib/autoimport/utils.py b/rope/contrib/autoimport/utils.py index a2e51d990..85bbef805 100644 --- a/rope/contrib/autoimport/utils.py +++ b/rope/contrib/autoimport/utils.py @@ -119,7 +119,7 @@ def get_files( else: assert package.path for file in package.path.rglob("*.py"): - if "site-packages" in file.parts: + if "site-packages" in file.relative_to(package.path).parts: continue if file.name == "__init__.py": yield ModuleFile( From a05f7d5390b05ec5bc373e74dbfb56b6a73c067e Mon Sep 17 00:00:00 2001 From: bagel897 Date: Thu, 11 Jan 2024 11:30:00 -0600 Subject: [PATCH 06/11] use basic suggestions --- rope/base/prefs.py | 2 +- rope/contrib/autoimport/sqlite.py | 4 ++-- ropetest/contrib/autoimport/deptest.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/rope/base/prefs.py b/rope/base/prefs.py index ae35d7d68..6f3d69c0b 100644 --- a/rope/base/prefs.py +++ b/rope/base/prefs.py @@ -215,7 +215,7 @@ class Prefs: """), ) autoimport: AutoimportPrefs = field( - default_factory=lambda: AutoimportPrefs(), description="Preferences for Autoimport") + default_factory=AutoimportPrefs, description="Preferences for Autoimport") def set(self, key: str, value: Any): """Set the value of `key` preference to `value`.""" diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index 6b520d535..db6758419 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -17,7 +17,7 @@ from typing import Generator, Iterable, Iterator, List, Optional, Set, Tuple from packaging.requirements import Requirement - +from copy import deepcopy from rope.base import exceptions, libutils, resourceobserver, taskhandle, versioning from rope.base.prefs import AutoimportPrefs from rope.base.project import Project @@ -133,7 +133,7 @@ def __init__( autoimport = AutoImport(..., memory=True) """ self.project = project - self.prefs = self.project.prefs.autoimport + self.prefs = deepcopy(self.project.prefs.autoimport) project_package = get_package_tuple(project.root.pathlib, project) assert project_package is not None assert project_package.path is not None diff --git a/ropetest/contrib/autoimport/deptest.py b/ropetest/contrib/autoimport/deptest.py index 9dcefd132..f392c9493 100644 --- a/ropetest/contrib/autoimport/deptest.py +++ b/ropetest/contrib/autoimport/deptest.py @@ -13,7 +13,6 @@ def project(request, tmp_path: Path) -> Iterable[Project]: if doc is not None: file = tmp_path / "pyproject.toml" file.write_text(doc, encoding="utf-8") - print(file, doc) project = Project(tmp_path) yield project project.close() From 0a94b9201d23f55952e7eaa0a2d8ad78be58d606 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Mon, 22 Jan 2024 09:19:39 -0600 Subject: [PATCH 07/11] modify project fixture --- ropetest/conftest.py | 6 ++++-- ropetest/contrib/autoimport/deptest.py | 11 ----------- ropetest/testutils.py | 8 ++++++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ropetest/conftest.py b/ropetest/conftest.py index ce8a1eab5..53e7b4109 100644 --- a/ropetest/conftest.py +++ b/ropetest/conftest.py @@ -7,8 +7,9 @@ @pytest.fixture -def project(): - project = testutils.sample_project() +def project(request): + pyproject = request.param if hasattr(request, "param") else None + project = testutils.sample_project(pyproject=pyproject) yield project testutils.remove_project(project) @@ -39,6 +40,7 @@ def project2(): /pkg1/mod2.py -- mod2 """ + @pytest.fixture def mod1(project) -> resources.File: return testutils.create_module(project, "mod1") diff --git a/ropetest/contrib/autoimport/deptest.py b/ropetest/contrib/autoimport/deptest.py index f392c9493..146496176 100644 --- a/ropetest/contrib/autoimport/deptest.py +++ b/ropetest/contrib/autoimport/deptest.py @@ -1,21 +1,10 @@ -from pathlib import Path from typing import Iterable import pytest -from rope.base.project import Project from rope.contrib.autoimport.sqlite import AutoImport -@pytest.fixture -def project(request, tmp_path: Path) -> Iterable[Project]: - doc = request.param - if doc is not None: - file = tmp_path / "pyproject.toml" - file.write_text(doc, encoding="utf-8") - project = Project(tmp_path) - yield project - project.close() @pytest.fixture diff --git a/ropetest/testutils.py b/ropetest/testutils.py index 11f58ccfe..80af3f769 100644 --- a/ropetest/testutils.py +++ b/ropetest/testutils.py @@ -5,7 +5,7 @@ import tempfile import unittest from pathlib import Path - +from typing import Optional import rope.base.project from rope.contrib import generate @@ -14,10 +14,14 @@ RUN_TMP_DIR = tempfile.mkdtemp(prefix="ropetest-run-") -def sample_project(foldername=None, **kwds): +def sample_project(foldername=None, pyproject: Optional[str] = None, **kwds): root = Path(tempfile.mkdtemp(prefix="project-", dir=RUN_TMP_DIR)) root /= foldername if foldername else "sample_project" logging.debug("Using %s as root of the project.", root) + root.mkdir(exist_ok=True) + if pyproject is not None: + file = root / "pyproject.toml" + file.write_text(pyproject, encoding="utf-8") # Using these prefs for faster tests prefs = { "save_objectdb": False, From ef1c0a8b59b6bfea3e63e6c71ea40f4505a38fca Mon Sep 17 00:00:00 2001 From: bagel897 Date: Mon, 22 Jan 2024 09:21:45 -0600 Subject: [PATCH 08/11] reformat --- ropetest/contrib/autoimport/deptest.py | 2 -- ropetest/contrib/autoimporttest.py | 5 +++-- ropetest/testutils.py | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ropetest/contrib/autoimport/deptest.py b/ropetest/contrib/autoimport/deptest.py index 146496176..7021e3075 100644 --- a/ropetest/contrib/autoimport/deptest.py +++ b/ropetest/contrib/autoimport/deptest.py @@ -5,8 +5,6 @@ from rope.contrib.autoimport.sqlite import AutoImport - - @pytest.fixture def autoimport(project) -> Iterable[AutoImport]: autoimport = AutoImport(project, memory=True) diff --git a/ropetest/contrib/autoimporttest.py b/ropetest/contrib/autoimporttest.py index fb21bbd2b..1f2dfed49 100644 --- a/ropetest/contrib/autoimporttest.py +++ b/ropetest/contrib/autoimporttest.py @@ -146,9 +146,10 @@ def test_skipping_directories_not_accessible_because_of_permission_error(self): # The single thread test takes much longer than the multithread test but is easier to debug single_thread = False self.importer.generate_modules_cache(single_thread=single_thread) - + # Create a temporary directory and set permissions to 000 - import tempfile, sys + import sys + import tempfile with tempfile.TemporaryDirectory() as dir: import os os.chmod(dir, 0o000) diff --git a/ropetest/testutils.py b/ropetest/testutils.py index 80af3f769..46087ee56 100644 --- a/ropetest/testutils.py +++ b/ropetest/testutils.py @@ -6,6 +6,7 @@ import unittest from pathlib import Path from typing import Optional + import rope.base.project from rope.contrib import generate From f2fd9e6313b13c6c9b1cffaeccb3541e4b6d7cc1 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Wed, 31 Jan 2024 22:16:52 -0600 Subject: [PATCH 09/11] update prefs --- docs/configuration.rst | 7 +- rope/base/prefs.py | 102 ++++++++++++++++++++---------- rope/contrib/autoimport/sqlite.py | 22 +++---- 3 files changed, 81 insertions(+), 50 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 6d36a980a..f98a489c2 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -14,12 +14,11 @@ Will be used if [tool.rope] is configured. [tool.rope] split_imports = true - [tool.rope.autoimport] - underlined = false autoimport.aliases = [ ['dt', 'datetime'], ['mp', 'multiprocessing'], ] + autoimport.underlined = false config.py --------- @@ -54,9 +53,9 @@ Additionally, you can run an executable function at startup of rope. pytool.toml ----------- If neither a config.py or a pyproject.toml is present, rope will use a pytool.toml. -It follows the exact same syntax as ``pyproject.toml``. +It follows the exact same syntax of the pyproject.toml. -- Mac OS X: ``~/Library/Application Support/pytool.toml``. +- Mac OS X: ``~/Library/Application Support/pytool.toml.`` - Unix: ``~/.config/pytool.toml``` or in $XDG_CONFIG_HOME, if defined - Windows: ``C:\Users\\AppData\Local\pytool.toml`` diff --git a/rope/base/prefs.py b/rope/base/prefs.py index 8f7aa3c19..2d04fc5bd 100644 --- a/rope/base/prefs.py +++ b/rope/base/prefs.py @@ -15,11 +15,12 @@ @dataclass class AutoimportPrefs: underlined: bool = field( - default=False, description="Cache underlined (private) modules") - memory: bool = field(default=None, description="Cache in memory instead of disk") - parallel: bool = field(default=True, description="Use multiple processes to parse") + default=False, description="Cache underlined (private) modules" + ) + # memory: bool = field(default=None, description="Cache in memory instead of disk") + # parallel: bool = field(default=True, description="Use multiple processes to parse") aliases: List[Tuple[str, str]] = field( - default_factory=lambda : [ + default_factory=lambda: [ ("np", "numpy"), ("pd", "pandas"), ("plt", "matplotlib.pyplot"), @@ -28,10 +29,12 @@ class AutoimportPrefs: ("sk", "sklearn"), ("sm", "statsmodels"), ], - description=dedent(""" + description=dedent( + """ Aliases for module names. For example, `[('np', 'numpy')]` makes rope recommend ``import numpy as np``. - """), + """ + ), ) @@ -54,7 +57,8 @@ class Prefs: ".mypy_cache", ".pytest_cache", ], - description=dedent(""" + description=dedent( + """ Specify which files and folders to ignore in the project. Changes to ignored resources are not added to the history and VCSs. Also they are not returned in `Project.get_files()`. @@ -64,19 +68,23 @@ class Prefs: '.svn': matches 'pkg/.svn' and all of its children 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' - """), + """ + ), ) python_files: List[str] = field( default_factory=lambda: ["*.py"], - description=dedent(""" + description=dedent( + """ Specifies which files should be considered python files. It is useful when you have scripts inside your project. Only files ending with ``.py`` are considered to be python files by default. - """), + """ + ), ) source_folders: List[str] = field( - description=dedent(""" + description=dedent( + """ Custom source folders: By default rope searches the project for finding source folders (folders that should be searched for finding modules). You can add paths to that list. Note @@ -85,7 +93,8 @@ class Prefs: The folders should be relative to project root and use '/' for separating folders regardless of the platform rope is running on. 'src/my_source_folder' for instance. - """), + """ + ), default_factory=lambda: [], ) python_path: List[str] = field( @@ -107,10 +116,12 @@ class Prefs: ) perform_doa: bool = field( default=True, - description=dedent(""" + description=dedent( + """ If `False` when running modules or unit tests 'dynamic object analysis' is turned off. This makes them much faster. - """), + """ + ), ) validate_objectdb: bool = field( default=False, @@ -128,11 +139,13 @@ class Prefs: indent_size: int = field( default=4, - description=dedent(""" + description=dedent( + """ Set the number spaces used for indenting. According to :PEP:`8`, it is best to use 4 spaces. Since most of rope's unit-tests use 4 spaces it is more reliable, too. - """), + """ + ), ) extension_modules: List[str] = field( @@ -148,55 +161,68 @@ class Prefs: ) ignore_syntax_errors: bool = field( default=False, - description=dedent(""" + description=dedent( + """ If `True` modules with syntax errors are considered to be empty. The default value is `False`; When `False` syntax errors raise `rope.base.exceptions.ModuleSyntaxError` exception. - """), + """ + ), ) ignore_bad_imports: bool = field( default=False, - description=dedent(""" + description=dedent( + """ If `True`, rope ignores unresolvable imports. Otherwise, they appear in the importing namespace. - """), + """ + ), ) prefer_module_from_imports: bool = field( default=False, - description=dedent(""" + description=dedent( + """ If `True`, rope will insert new module imports as `from import `by default. - """), + """ + ), ) split_imports: bool = field( default=False, - description=dedent(""" + description=dedent( + """ If `True`, rope will transform a comma list of imports into multiple separate import statements when organizing imports. - """), + """ + ), ) pull_imports_to_top: bool = field( default=True, - description=dedent(""" + description=dedent( + """ If `True`, rope will remove all top-level import statements and reinsert them at the top of the module when making changes. - """), + """ + ), ) sort_imports_alphabetically: bool = field( default=False, - description=dedent(""" + description=dedent( + """ If `True`, rope will sort imports alphabetically by module name instead of alphabetically by import statement, with from imports after normal imports. - """), + """ + ), ) type_hinting_factory: str = field( "rope.base.oi.type_hinting.factory.default_type_hinting_factory", - description=dedent(""" + description=dedent( + """ Location of implementation of rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general case, you don't have to change this value, unless you're an rope expert. @@ -204,14 +230,17 @@ class Prefs: listed in module rope.base.oi.type_hinting.providers.interfaces For example, you can add you own providers for Django Models, or disable the search type-hinting in a class hierarchy, etc. - """), + """ + ), ) project_opened: Optional[Callable] = field( None, - description=dedent(""" + description=dedent( + """ This function is called after opening the project. Can only be set in config.py. - """), + """ + ), ) py_version: Optional[Tuple[int, int]] = field( default=None, @@ -223,13 +252,16 @@ class Prefs: ) callbacks: Dict[str, Callable[[Any], None]] = field( default_factory=lambda: {}, - description=dedent(""" + description=dedent( + """ Callbacks run when configuration values are changed. Can only be set in config.py. - """), + """ + ), ) autoimport: AutoimportPrefs = field( - default_factory=AutoimportPrefs, description="Preferences for Autoimport") + default_factory=AutoimportPrefs, description="Preferences for Autoimport" + ) def set(self, key: str, value: Any): """Set the value of `key` preference to `value`.""" diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index ac8f09423..f18878f35 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -103,15 +103,17 @@ class AutoImport: """ connection: sqlite3.Connection + memory: bool project: Project project_package: Package prefs: AutoimportPrefs + underlined: bool def __init__( self, project: Project, observe: bool = True, - underlined: Optional[bool] = None, + underlined: bool = False, memory: bool = _deprecated_default, ): """Construct an AutoImport object. @@ -138,24 +140,22 @@ def __init__( project_package = get_package_tuple(project.root.pathlib, project) assert project_package is not None assert project_package.path is not None - if underlined is not None: - self.prefs.underlined = underlined self.project_package = project_package + self.underlined = underlined + self.memory = memory if memory is _deprecated_default: - self.prefs.memory = True + self.memory = True warnings.warn( "The default value for `AutoImport(memory)` argument will " "change to use an on-disk database by default in the future. " "If you want to use an in-memory database, you need to pass " - "`AutoImport(memory=True)` explicitly or set it in the config file.", + "`AutoImport(memory=True)` explicitly.", DeprecationWarning, ) - else: - self.prefs.memory = memory self.thread_local = local() self.connection = self.create_database_connection( project=project, - memory=self.prefs.memory, + memory=memory, ) self._setup_db() if observe: @@ -211,7 +211,7 @@ def connection(self): if not hasattr(self.thread_local, "connection"): self.thread_local.connection = self.create_database_connection( project=self.project, - memory=self.prefs.memory, + memory=self.memory, ) return self.thread_local.connection @@ -440,11 +440,11 @@ def generate_modules_cache( packages, underlined, existing, self.project.prefs.dependencies ) ) - if len(packages) == 0: + if not packages: return self._add_packages(packages) job_set = task_handle.create_jobset("Generating autoimport cache", 0) - if single_thread or not self.prefs.parallel: + if single_thread: for package in packages: for module in get_files(package, underlined): job_set.started_job(module.modname) From 97cfe933760118d7e52f94f87937adcdb8db2ec5 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Wed, 31 Jan 2024 22:23:45 -0600 Subject: [PATCH 10/11] restore files from master --- docs/configuration.rst | 6 +- rope/base/prefs.py | 97 +++++++++++-------------------- rope/contrib/autoimport/sqlite.py | 9 +-- 3 files changed, 41 insertions(+), 71 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index f98a489c2..b77359765 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -53,15 +53,15 @@ Additionally, you can run an executable function at startup of rope. pytool.toml ----------- If neither a config.py or a pyproject.toml is present, rope will use a pytool.toml. -It follows the exact same syntax of the pyproject.toml. +It follows the exact same syntax as ``pyproject.toml``. -- Mac OS X: ``~/Library/Application Support/pytool.toml.`` +- Mac OS X: ``~/Library/Application Support/pytool.toml``. - Unix: ``~/.config/pytool.toml``` or in $XDG_CONFIG_HOME, if defined - Windows: ``C:\Users\\AppData\Local\pytool.toml`` Options -======= +------- .. autopytoolconfigtable:: rope.base.prefs.Prefs Autoimport Options diff --git a/rope/base/prefs.py b/rope/base/prefs.py index 2d04fc5bd..491746577 100644 --- a/rope/base/prefs.py +++ b/rope/base/prefs.py @@ -15,10 +15,10 @@ @dataclass class AutoimportPrefs: underlined: bool = field( - default=False, description="Cache underlined (private) modules" - ) + default=False, description="Cache underlined (private) modules") # memory: bool = field(default=None, description="Cache in memory instead of disk") # parallel: bool = field(default=True, description="Use multiple processes to parse") + aliases: List[Tuple[str, str]] = field( default_factory=lambda: [ ("np", "numpy"), @@ -29,12 +29,10 @@ class AutoimportPrefs: ("sk", "sklearn"), ("sm", "statsmodels"), ], - description=dedent( - """ + description=dedent(""" Aliases for module names. For example, `[('np', 'numpy')]` makes rope recommend ``import numpy as np``. - """ - ), + """), ) @@ -57,8 +55,7 @@ class Prefs: ".mypy_cache", ".pytest_cache", ], - description=dedent( - """ + description=dedent(""" Specify which files and folders to ignore in the project. Changes to ignored resources are not added to the history and VCSs. Also they are not returned in `Project.get_files()`. @@ -68,23 +65,19 @@ class Prefs: '.svn': matches 'pkg/.svn' and all of its children 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' - """ - ), + """), ) python_files: List[str] = field( default_factory=lambda: ["*.py"], - description=dedent( - """ + description=dedent(""" Specifies which files should be considered python files. It is useful when you have scripts inside your project. Only files ending with ``.py`` are considered to be python files by default. - """ - ), + """), ) source_folders: List[str] = field( - description=dedent( - """ + description=dedent(""" Custom source folders: By default rope searches the project for finding source folders (folders that should be searched for finding modules). You can add paths to that list. Note @@ -93,8 +86,7 @@ class Prefs: The folders should be relative to project root and use '/' for separating folders regardless of the platform rope is running on. 'src/my_source_folder' for instance. - """ - ), + """), default_factory=lambda: [], ) python_path: List[str] = field( @@ -116,12 +108,10 @@ class Prefs: ) perform_doa: bool = field( default=True, - description=dedent( - """ + description=dedent(""" If `False` when running modules or unit tests 'dynamic object analysis' is turned off. This makes them much faster. - """ - ), + """), ) validate_objectdb: bool = field( default=False, @@ -139,13 +129,11 @@ class Prefs: indent_size: int = field( default=4, - description=dedent( - """ + description=dedent(""" Set the number spaces used for indenting. According to :PEP:`8`, it is best to use 4 spaces. Since most of rope's unit-tests use 4 spaces it is more reliable, too. - """ - ), + """), ) extension_modules: List[str] = field( @@ -161,68 +149,55 @@ class Prefs: ) ignore_syntax_errors: bool = field( default=False, - description=dedent( - """ + description=dedent(""" If `True` modules with syntax errors are considered to be empty. The default value is `False`; When `False` syntax errors raise `rope.base.exceptions.ModuleSyntaxError` exception. - """ - ), + """), ) ignore_bad_imports: bool = field( default=False, - description=dedent( - """ + description=dedent(""" If `True`, rope ignores unresolvable imports. Otherwise, they appear in the importing namespace. - """ - ), + """), ) prefer_module_from_imports: bool = field( default=False, - description=dedent( - """ + description=dedent(""" If `True`, rope will insert new module imports as `from import `by default. - """ - ), + """), ) split_imports: bool = field( default=False, - description=dedent( - """ + description=dedent(""" If `True`, rope will transform a comma list of imports into multiple separate import statements when organizing imports. - """ - ), + """), ) pull_imports_to_top: bool = field( default=True, - description=dedent( - """ + description=dedent(""" If `True`, rope will remove all top-level import statements and reinsert them at the top of the module when making changes. - """ - ), + """), ) sort_imports_alphabetically: bool = field( default=False, - description=dedent( - """ + description=dedent(""" If `True`, rope will sort imports alphabetically by module name instead of alphabetically by import statement, with from imports after normal imports. - """ - ), + """), ) type_hinting_factory: str = field( "rope.base.oi.type_hinting.factory.default_type_hinting_factory", - description=dedent( - """ + description=dedent(""" Location of implementation of rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general case, you don't have to change this value, unless you're an rope expert. @@ -230,17 +205,14 @@ class Prefs: listed in module rope.base.oi.type_hinting.providers.interfaces For example, you can add you own providers for Django Models, or disable the search type-hinting in a class hierarchy, etc. - """ - ), + """), ) project_opened: Optional[Callable] = field( None, - description=dedent( - """ + description=dedent(""" This function is called after opening the project. Can only be set in config.py. - """ - ), + """), ) py_version: Optional[Tuple[int, int]] = field( default=None, @@ -252,16 +224,13 @@ class Prefs: ) callbacks: Dict[str, Callable[[Any], None]] = field( default_factory=lambda: {}, - description=dedent( - """ + description=dedent(""" Callbacks run when configuration values are changed. Can only be set in config.py. - """ - ), + """), ) autoimport: AutoimportPrefs = field( - default_factory=AutoimportPrefs, description="Preferences for Autoimport" - ) + default_factory=AutoimportPrefs, description="Preferences for Autoimport") def set(self, key: str, value: Any): """Set the value of `key` preference to `value`.""" diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index f18878f35..bdec6fb5f 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -2,30 +2,31 @@ import contextlib import json -from hashlib import sha256 -import secrets import re +import secrets import sqlite3 import sys import warnings from collections import OrderedDict from concurrent.futures import Future, ProcessPoolExecutor, as_completed +from copy import deepcopy from datetime import datetime +from hashlib import sha256 from itertools import chain from pathlib import Path from threading import local from typing import Generator, Iterable, Iterator, List, Optional, Set, Tuple from packaging.requirements import Requirement -from copy import deepcopy + from rope.base import exceptions, libutils, resourceobserver, taskhandle, versioning from rope.base.prefs import AutoimportPrefs from rope.base.project import Project from rope.base.resources import Resource from rope.contrib.autoimport import models from rope.contrib.autoimport.defs import ( - ModuleFile, Alias, + ModuleFile, Name, NameType, Package, From 5b78bb9f2c42186c88bcae11651d601ec794d4d3 Mon Sep 17 00:00:00 2001 From: bagel897 Date: Wed, 31 Jan 2024 22:26:09 -0600 Subject: [PATCH 11/11] cleanup --- rope/contrib/autoimport/sqlite.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index bdec6fb5f..21139b670 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -2,16 +2,15 @@ import contextlib import json -import re +from hashlib import sha256 import secrets +import re import sqlite3 import sys import warnings from collections import OrderedDict from concurrent.futures import Future, ProcessPoolExecutor, as_completed -from copy import deepcopy from datetime import datetime -from hashlib import sha256 from itertools import chain from pathlib import Path from threading import local @@ -671,7 +670,7 @@ def _resource_to_module( self, resource: Resource, underlined: bool = False ) -> ModuleFile: assert self.project_package.path - underlined = underlined if underlined else self.prefs.underlined + underlined = underlined if underlined else self.underlined resource_path: Path = resource.pathlib # The project doesn't need its name added to the path, # since the standard python file layout accounts for that