8000 Update rope to 1.11.0 for multi-threading capabilities (#498) · mirror-dump/python-lsp-server@73b945b · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 73b945b

Browse files
authored
Update rope to 1.11.0 for multi-threading capabilities (python-lsp#498)
1 parent 7347737 commit 73b945b

File tree

4 files changed

+108
-103
lines changed

4 files changed

+108
-103
lines changed

pylsp/plugins/rope_autoimport.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def pylsp_code_actions(
295295
word = get_name_or_module(document, diagnostic)
296296
log.debug(f"autoimport: searching for word: {word}")
297297
rope_config = config.settings(document_path=document.path).get("rope", {})
298-
autoimport = workspace._rope_autoimport(rope_config, feature="code_actions")
298+
autoimport = workspace._rope_autoimport(rope_config)
299299
suggestions = list(autoimport.search_full(word))
300300
log.debug("autoimport: suggestions: %s", suggestions)
301301
results = list(

pylsp/workspace.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import re
99
import uuid
1010
import functools
11-
from typing import Literal, Optional, Generator, Callable, List
11+
from typing import Optional, Generator, Callable, List
1212
from threading import RLock
1313

1414
import jedi
@@ -58,30 +58,20 @@ def __init__(self, root_uri, endpoint, config=None):
5858
# Whilst incubating, keep rope private
5959
self.__rope = None
6060
self.__rope_config = None
61-
62-
# We have a sperate AutoImport object for each feature to avoid sqlite errors
63-
# from accessing the same database from multiple threads.
64-
# An upstream fix discussion is here: https://github.com/python-rope/rope/issues/713
65-
self.__rope_autoimport = (
66-
{}
67-
) # Type: Dict[Literal["completions", "code_actions"], rope.contrib.autoimport.sqlite.AutoImport]
61+
self.__rope_autoimport = None
6862

6963
def _rope_autoimport(
7064
self,
7165
rope_config: Optional,
7266
memory: bool = False,
73-
feature: Literal["completions", "code_actions"] = "completions",
7467
):
7568
# pylint: disable=import-outside-toplevel
7669
from rope.contrib.autoimport.sqlite import AutoImport
7770

78-
if feature not in ["completions", "code_actions"]:
79-
raise ValueError(f"Unknown feature {feature}")
80-
81-
if self.__rope_autoimport.get(feature, None) is None:
71+
if self.__rope_autoimport is None:
8272
project = self._rope_project_builder(rope_config)
83-
self.__rope_autoimport[feature] = AutoImport(project, memory=memory)
84-
return self.__rope_autoimport[feature]
73+
self.__rope_autoimport = AutoImport(project, memory=memory)
74+
return self.__rope_autoimport
8575

8676
def _rope_project_builder(self, rope_config):
8777
# pylint: disable=import-outside-toplevel
@@ -388,8 +378,8 @@ def _create_cell_document(
388378
)
389379

390380
def close(self):
391-
for _, autoimport in self.__rope_autoimport.items():
392-
autoimport.close()
381+
if self.__rope_autoimport:
382+
self.__rope_autoimport.close()
393383

394384

395385
class Document:

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ all = [
3434
"pydocstyle>=6.3.0,<6.4.0",
3535
"pyflakes>=3.1.0,<3.2.0",
3636
"pylint>=2.5.0,<3.1",
37-
"rope>1.2.0",
37+
"rope>=1.11.0",
3838
"yapf>=0.33.0",
3939
"whatthepatch>=1.0.2,<2.0.0"
4040
]
@@ -45,7 +45,7 @@ pycodestyle = ["pycodestyle>=2.11.0,<2.12.0"]
4545
pydocstyle = ["pydocstyle>=6.3.0,<6.4.0"]
4646
pyflakes = ["pyflakes>=3.1.0,<3.2.0"]
4747
pylint = ["pylint>=2.5.0,<3.1"]
48-
rope = ["rope>1.2.0"]
48+
rope = ["rope>=1.11.0"]
4949
yapf = ["yapf>=0.33.0", "whatthepatch>=1.0.2,<2.0.0"]
5050
websockets = ["websockets>=10.3"]
5151
test = [

test/plugins/test_autoimport.py

Lines changed: 98 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# Copyright 2022- Python Language Server Contributors.
22

33
from typing import Any, Dict, List
4-
from unittest.mock import Mock
4+
from unittest.mock import Mock, patch
5+
6+
from test.test_notebook_document import wait_for_condition
7+
from test.test_utils import send_initialize_request, send_notebook_did_open
58

69
import jedi
710
import parso
811
import pytest
912

10-
from pylsp import lsp, uris
13+
from pylsp import IS_WIN, lsp, uris
1114
from pylsp.config.config import Config
1215
from pylsp.plugins.rope_autoimport import (
1316
_get_score,
@@ -25,13 +28,18 @@
2528
DOC_URI = uris.from_fs_path(__file__)
2629

2730

28-
def contains_autoimport(suggestion: Dict[str, Any], module: str) -> bool:
29-
"""Checks if `suggestion` contains an autoimport for `module`."""
31+
def contains_autoimport_completion(suggestion: Dict[str, Any], module: str) -> bool:
32+
"""Checks if `suggestion` contains an autoimport completion for `module`."""
3033
return suggestion.get("label", "") == module and "import" in suggestion.get(
3134
"detail", ""
3235
)
3336

3437

38+
def contains_autoimport_quickfix(suggestion: Dict[str, Any], module: str) -> bool:
39+
"""Checks if `suggestion` contains an autoimport quick fix for `module`."""
40+
return suggestion.get("title", "") == f"import {module}"
41+
42+
3543
@pytest.fixture(scope="session")
3644
def autoimport_workspace(tmp_path_factory) -> Workspace:
3745
"Special autoimport workspace. Persists across sessions to make in-memory sqlite3 database fast."
@@ -248,82 +256,89 @@ def test_autoimport_code_actions_get_correct_module_name(autoimport_workspace, m
248256
assert module_name == "os"
249257

250258

251-
# rope autoimport launches a sqlite database which checks from which thread it is called.
252-
# This makes the test below fail because we access the db from a different thread.
253-
# See https://stackoverflow.com/questions/48218065/objects-created-in-a-thread-can-only-be-used-in-that-same-thread
254-
# @pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
255-
# def test_autoimport_completions_for_notebook_document(
256-
# client_server_pair,
257-
# ):
258-
# client, server = client_server_pair
259-
# send_initialize_request(client)
260-
261-
# with patch.object(server._endpoint, "notify") as mock_notify:
262-
# # Expectations:
263-
# # 1. We receive an autoimport suggestion for "os" in the first cell because
264-
# # os is imported after that.
265-
# # 2. We don't receive an autoimport suggestion for "os" in the second cell because it's
266-
# # already imported in the second cell.
267-
# # 3. We don't receive an autoimport suggestion for "os" in the third cell because it's
268-
# # already imported in the second cell.
269-
# # 4. We receive an autoimport suggestion for "sys" because it's not already imported
270-
# send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"])
271-
# wait_for_condition(lambda: mock_notify.call_count >= 3)
272-
273-
# server.m_workspace__did_change_configuration(
274-
# settings={
275-
# "pylsp": {
276-
# "plugins": {
277-
# "rope_autoimport": {
278-
# "memory": True,
279-
# "completions": {"enabled": True},
280-
# },
281-
# }
282-
# }
283-
# }
284-
# )
285-
# rope_autoimport_settings = server.workspace._config.plugin_settings(
286-
# "rope_autoimport"
287-
# )
288-
# assert rope_autoimport_settings.get("completions", {}).get("enabled", False) is True
289-
# assert rope_autoimport_settings.get("memory", False) is True
290-
291-
# # 1.
292-
# suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get(
293-
# "items"
294-
# )
295-
# assert any(
296-
# suggestion
297-
# for suggestion in suggestions
298-
# if contains_autoimport(suggestion, "os")
299-
# )
300-
301-
# # 2.
302-
# suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get(
303-
# "items"
304-
# )
305-
# assert not any(
306-
# suggestion
307-
# for suggestion in suggestions
308-
# if contains_autoimport(suggestion, "os")
309-
# )
310-
311-
# # 3.
312-
# suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get(
313-
# "items"
314-
# )
315-
# assert not any(
316-
# suggestion
317-
# for suggestion in suggestions
318-
# if contains_autoimport(suggestion, "os")
319-
# )
320-
321-
# # 4.
322-
# suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get(
323-
# "items"
324-
# )
325-
# assert any(
326-
# suggestion
327-
# for suggestion in suggestions
328-
# if contains_autoimport(suggestion, "sys")
329-
# )
259+
def make_context(module_name, line, character_start, character_end):
260+
return {
261+
"diagnostics": [
262+
{
263+
"message": f"undefined name '{module_name}'",
264+
"range": {
265+
"start": {"line": line, "character": character_start},
266+
"end": {"line": line, "character": character_end},
267+
},
268+
}
269+
]
270+
}
271+
272+
273+
def position(line, character):
274+
return {"line": line, "character": character}
275+
276+
277+
@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
278+
def test_autoimport_code_actions_and_completions_for_notebook_document(
279+
client_server_pair,
280+
):
281+
client, server = client_server_pair
282+
send_initialize_request(
283+
client,
284+
{
285+
"pylsp": {
286+
"plugins": {
287+
"rope_autoimport": {
288+
"memory": True,
289+
"enabled": True,
290+
"completions": {"enabled": True},
291+
},
292+
}
293+
}
294+
},
295+
)
296+
297+
with patch.object(server._endpoint, "notify") as mock_notify:
298+
# Expectations:
299+
# 1. We receive an autoimport suggestion for "os" in the first cell because
300+
# os is imported after that.
301+
# 2. We don't receive an autoimport suggestion for "os" in the second cell because it's
302+
# already imported in the second cell.
303+
# 3. We don't receive an autoimport suggestion for "os" in the third cell because it's
304+
# already imported in the second cell.
305+
# 4. We receive an autoimport suggestion for "sys" because it's not already imported.
306+
# 5. If diagnostics doesn't contain "undefined name ...", we send empty quick fix suggestions.
307+
send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"])
308+
wait_for_condition(lambda: mock_notify.call_count >= 3)
309+
310+
rope_autoimport_settings = server.workspace._config.plugin_settings(
311+
"rope_autoimport"
312+
)
313+
assert rope_autoimport_settings.get("completions", {}).get("enabled", False) is True
314+
assert rope_autoimport_settings.get("memory", False) is True
315+
316+
# 1.
317+
quick_fixes = server.code_actions("cell_1_uri", {}, make_context("os", 0, 0, 2))
318+
assert any(s for s in quick_fixes if contains_autoimport_quickfix(s, "os"))
319+
320+
completions = server.completions("cell_1_uri", position(0, 2)).get("items")
321+
assert any(s for s in completions if contains_autoimport_completion(s, "os"))
322+
323+
# 2.
324+
# We don't test code actions here as in this case, there would be no code actions sent bc
325+
# there wouldn't be a diagnostics message.
326+
completions = server.completions("cell_2_uri", position(1, 2)).get("items")
327+
assert not any(s for s in completions if contains_autoimport_completion(s, "os"))
328+
329+
# 3.
330+
# Same as in 2.
331+
completions = server.completions("cell_3_uri", position(0, 2)).get("items")
332+
assert not any(s for s in completions if contains_autoimport_completion(s, "os"))
333+
334+
# 4.
335+
quick_fixes = server.code_actions("cell_4_uri", {}, make_context("sys", 0, 0, 3))
336+
assert any(s for s in quick_fixes if contains_autoimport_quickfix(s, "sys"))
337+
338+
completions = server.completions("cell_4_uri", position(0, 3)).get("items")
339+
assert any(s for s in completions if contains_autoimport_completion(s, "sys"))
340+
341+
# 5.
342+
context = {"diagnostics": [{"message": "A random message"}]}
343+
quick_fixes = server.code_actions("cell_4_uri", {}, context)
344+
assert len(quick_fixes) == 0

0 commit comments

Comments
 (0)
0