From 08663c512ebdf9cfaea50b3c0de5b9d4c03e0d48 Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Sun, 21 Apr 2024 07:07:32 +0800 Subject: [PATCH] refactor: tidy codes Signed-off-by: Jack Cherng --- Makefile | 11 +++-- commands.py | 13 ++--- plugin.py | 125 +++++++++++++++++++++-------------------------- pyproject.toml | 11 ++--- requirements.txt | 2 +- 5 files changed, 73 insertions(+), 89 deletions(-) diff --git a/Makefile b/Makefile index f0d0be3..7a942ac 100644 --- a/Makefile +++ b/Makefile @@ -5,17 +5,18 @@ all: .PHONY: ci-check ci-check: - # mypy -p plugin - echo "Check: ruff (lint)" + # @echo "========== check: mypy ==========" + # mypy -p plugin + @echo "========== check: ruff (lint) ==========" ruff check --diff . - echo "Check: ruff (format)" + @echo "========== check: ruff (format) ==========" ruff format --diff . .PHONY: ci-fix ci-fix: - echo "Fix: ruff (lint)" + @echo "========== fix: ruff (lint) ==========" ruff check --fix . - echo "Fix: ruff (format)" + @echo "========== fix: ruff (format) ==========" ruff format . .PHONY: update-schema diff --git a/commands.py b/commands.py index 8ff78ad..4f59aac 100644 --- a/commands.py +++ b/commands.py @@ -1,8 +1,9 @@ -from os import path +from __future__ import annotations + +from pathlib import Path import sublime import sublime_plugin -from LSP.plugin.core.typing import List CONFIGURATION_FILENAME = "pyrightconfig.json" CONFIGURATION_CONTENTS = """{ @@ -27,14 +28,14 @@ def run(self) -> None: placeholder="Select a folder to create the configuration file in", ) - def _on_selected(self, folders: List[str], index: int) -> None: + def _on_selected(self, folders: list[str], index: int) -> None: if index > -1: self._create_configuration(folders[index]) def _create_configuration(self, folder_path: str) -> None: - config_path = path.join(folder_path, CONFIGURATION_FILENAME) - new_view = self.window.open_file(config_path) - if not path.isfile(config_path): + config_path = Path(folder_path) / CONFIGURATION_FILENAME + new_view = self.window.open_file(str(config_path)) + if not config_path.is_file(): self._poll_view_until_loaded(new_view) def _poll_view_until_loaded(self, view: sublime.View, attempt: int = 1) -> None: diff --git a/plugin.py b/plugin.py index ec6333b..a85023e 100644 --- a/plugin.py +++ b/plugin.py @@ -1,20 +1,22 @@ +from __future__ import annotations + import os import re import shutil import subprocess import sys +from collections.abc import Callable +from pathlib import Path +from typing import Any, cast import sublime from LSP.plugin import ClientConfig, DottedDict, MarkdownLangMap, Response, WorkspaceFolder from LSP.plugin.core.protocol import CompletionItem, Hover, SignatureHelp -from LSP.plugin.core.typing import Any, Callable, List, Optional, Tuple, cast from lsp_utils import NpmClientHandler from sublime_lib import ResourcePath assert __package__ -ST_PACKAGES_PATH = sublime.packages_path() - def plugin_loaded() -> None: LspBasedpyrightPlugin.setup() @@ -51,7 +53,7 @@ def on_settings_changed(self, settings: DottedDict) -> None: super().on_settings_changed(settings) dev_environment = settings.get("basedpyright.dev_environment") - extraPaths = settings.get("basedpyright.analysis.extraPaths") or [] # type: List[str] + extraPaths: list[str] = settings.get("basedpyright.analysis.extraPaths") or [] if dev_environment in {"sublime_text", "sublime_text_33", "sublime_text_38"}: py_ver = self.detect_st_py_ver(dev_environment) @@ -65,13 +67,13 @@ def on_pre_start( cls, window: sublime.Window, initiating_view: sublime.View, - workspace_folders: List[WorkspaceFolder], + workspace_folders: list[WorkspaceFolder], configuration: ClientConfig, - ) -> Optional[str]: + ) -> str | None: super().on_pre_start(window, initiating_view, workspace_folders, configuration) python_path = cls.python_path(configuration.settings, workspace_folders) - print('{}: INFO: Using python path "{}"'.format(cls.name(), python_path)) + print(f'{cls.name()}: INFO: Using python path "{python_path}"') configuration.settings.set("python.pythonPath", python_path) return None @@ -79,12 +81,12 @@ def on_pre_start( def install_or_update(cls) -> None: super().install_or_update() # Copy resources - src = "Packages/{}/resources/".format(cls.package_name) + src = f"Packages/{cls.package_name}/resources/" dest = os.path.join(cls.package_storage(), "resources") ResourcePath(src).copytree(dest, exist_ok=True) @classmethod - def markdown_language_id_to_st_syntax_map(cls) -> Optional[MarkdownLangMap]: + def markdown_language_id_to_st_syntax_map(cls) -> MarkdownLangMap | None: return {"python": (("python", "py"), ("LSP-basedpyright/syntaxes/basedpyright",))} def on_server_response_async(self, method: str, response: Response) -> None: @@ -124,7 +126,7 @@ def patch_markdown_content(self, content: str) -> str: r"\n:(\w+)[ \t]+([\w\\*.]+):", lambda m: "\n__{field}:__ `{name}`".format( field=m.group(1).title(), - name=m.group(2).replace("\\_", "_").replace("\\*", "*"), + name=m.group(2).replace(R"\_", "_").replace(R"\*", "*"), ), content, ) @@ -133,108 +135,92 @@ def patch_markdown_content(self, content: str) -> str: content = re.sub(r"\n:deprecated:", r"\n⚠️ __Deprecated:__", content) return content - def detect_st_py_ver(self, dev_environment: str) -> Tuple[int, int]: + def detect_st_py_ver(self, dev_environment: str) -> tuple[int, int]: default = (3, 3) if dev_environment == "sublime_text_33": return (3, 3) if dev_environment == "sublime_text_38": return (3, 8) - if dev_environment == "sublime_text": - session = self.weaksession() - if not session: + if not ((session := self.weaksession()) and (workspace_folders := session.get_workspace_folders())): return default - workspace_folders = session.get_workspace_folders() - if not workspace_folders: - return default - if workspace_folders[0].path == os.path.join(ST_PACKAGES_PATH, "User"): + # ST auto uses py38 for files in "Packages/User/" + if (first_folder := Path(workspace_folders[0].path).resolve()) == Path(sublime.packages_path()) / "User": return (3, 8) - python_version_file = os.path.join(workspace_folders[0].path, ".python-version") + # the project wants to use py38 try: - with open(python_version_file, "r") as file: - if file.read().strip() == "3.8": - return (3, 8) + if (first_folder / ".python-version").read_bytes().strip() == b"3.8": + return (3, 8) except Exception: pass + return default - return default + raise ValueError(f'Invalid "dev_environment" setting: {dev_environment}') - @classmethod - def get_plugin_setting(cls, key: str, default: Any = None) -> Any: - return sublime.load_settings(cls.package_name + ".sublime-settings").get(key, default) - - def find_package_dependency_dirs(self, py_ver: Tuple[int, int] = (3, 3)) -> List[str]: + def find_package_dependency_dirs(self, py_ver: tuple[int, int] = (3, 3)) -> list[str]: dep_dirs = sys.path.copy() # replace paths for target Python version # @see https://github.com/sublimelsp/LSP-pyright/issues/28 - re_pattern = r"(python3\.?)[38]" + re_pattern = re.compile(r"(python3\.?)[38]", flags=re.IGNORECASE) re_replacement = r"\g<1>8" if py_ver == (3, 8) else r"\g<1>3" - dep_dirs = [re.sub(re_pattern, re_replacement, d, flags=re.IGNORECASE) for d in dep_dirs] + dep_dirs = [re_pattern.sub(re_replacement, dep_dir) for dep_dir in dep_dirs] # move the "Packages/" to the last # @see https://github.com/sublimelsp/LSP-pyright/pull/26#discussion_r520747708 - dep_dirs.remove(ST_PACKAGES_PATH) - dep_dirs.append(ST_PACKAGES_PATH) + packages_path = sublime.packages_path() + dep_dirs.remove(packages_path) + dep_dirs.append(packages_path) + # sublime stubs - add as first if py_ver == (3, 3): - # sublime stubs - add as first dep_dirs.insert(0, os.path.join(self.package_storage(), "resources", "typings", "sublime_text")) - return [path for path in dep_dirs if os.path.isdir(path)] + return list(filter(os.path.isdir, dep_dirs)) @classmethod - def python_path(cls, settings: DottedDict, workspace_folders: List[WorkspaceFolder]) -> str: - python_path = settings.get("python.pythonPath") - if python_path: + def python_path(cls, settings: DottedDict, workspace_folders: list[WorkspaceFolder]) -> str: + if python_path := settings.get("python.pythonPath"): return python_path if workspace_folders: - workspace_folder = os.path.abspath(workspace_folders[0].path) - - while True: - python_path = cls.python_path_from_venv(workspace_folder) - if python_path: - return python_path - - parent = os.path.dirname(workspace_folder) - if workspace_folder != parent: - workspace_folder = parent - else: - break + workspace_folder = Path(workspace_folders[0].path) + for folder in (workspace_folder, *workspace_folder.parents): + if python_path := cls.python_path_from_venv(folder): + return str(python_path) return shutil.which("python") or shutil.which("python3") or "" @classmethod - def python_path_from_venv(cls, workspace_folder: str) -> Optional[str]: + def python_path_from_venv(cls, workspace_folder: str | Path) -> Path | None: """ Resolves the python binary path depending on environment variables and files in the workspace. @see https://github.com/fannheyward/coc-pyright/blob/d58a468b1d7479a1b56906e386f44b997181e307/src/configSettings.ts#L47 """ + workspace_folder = Path(workspace_folder) - def binary_from_python_path(path: str) -> Optional[str]: + def binary_from_python_path(path: str | Path) -> Path | None: + path = Path(path) if sublime.platform() == "windows": - binary_path = os.path.join(path, "Scripts", "python.exe") + binary_path = path / "Scripts/python.exe" else: - binary_path = os.path.join(path, "bin", "python") - - return binary_path if os.path.isfile(binary_path) else None + binary_path = path / "bin/python" + return binary_path if binary_path.is_file() else None # Config file, venv resolution command, post-processing - venv_config_files = [ + venv_config_files: list[tuple[str, str, Callable[[str], Path | None] | None]] = [ (".pdm-python", "pdm info --python", None), (".python-version", "pyenv which python", None), ("Pipfile", "pipenv --py", None), ("poetry.lock", "poetry env info -p", binary_from_python_path), - ] # type: List[Tuple[str, str, Optional[Callable[[str], Optional[str]]]]] + ] for config_file, command, post_processing in venv_config_files: - full_config_file_path = os.path.join(workspace_folder, config_file) - if not os.path.isfile(full_config_file_path): + if not (workspace_folder / config_file).is_file(): continue - print("{}: INFO: {} detected. Run subprocess command: {}".format(cls.name(), config_file, command)) + print(f"{cls.name()}: INFO: {config_file} detected. Run subprocess command: {command}") try: python_path = subprocess.check_output( command, @@ -244,20 +230,19 @@ def binary_from_python_path(path: str) -> Optional[str]: stderr=subprocess.STDOUT, universal_newlines=True, ).strip() - return post_processing(python_path) if post_processing else python_path + if post_processing: + python_path = post_processing(python_path) + return Path(python_path) if python_path else None except FileNotFoundError: - print("{}: WARN: subprocess failed with file not found: {}".format(cls.name(), command[0])) + print(f"{cls.name()}: WARN: subprocess failed with file not found: {command[0]}") except PermissionError as e: - print("{}: WARN: subprocess failed with permission error: {}".format(cls.name(), e)) + print(f"{cls.name()}: WARN: subprocess failed with permission error: {e}") except subprocess.CalledProcessError as e: - print("{}: WARN: subprocess failed: {}".format(cls.name(), str(e.output).strip())) + print(f"{cls.name()}: WARN: subprocess failed: {str(e.output).strip()}") # virtual environment as subfolder in project - for file in os.listdir(workspace_folder): - maybe_venv_path = os.path.join(workspace_folder, file) - if os.path.isfile(os.path.join(maybe_venv_path, "pyvenv.cfg")): - binary = binary_from_python_path(maybe_venv_path) - if binary is not None: - return binary # found a venv + for maybe_venv_path in workspace_folder.iterdir(): + if (maybe_venv_path / "pyvenv.cfg").is_file() and (binary := binary_from_python_path(maybe_venv_path)): + return binary # found a venv return None diff --git a/pyproject.toml b/pyproject.toml index 060dc25..89b910c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ -[tool.pyright] +[tool.basedpyright] pythonVersion = '3.11' -[tool.basedpyright] +[tool.pyright] pythonVersion = '3.11' [tool.ruff] @@ -25,11 +25,8 @@ exclude = [ ] [tool.ruff.lint] -select = ["E", "F", "W", "I"] # no "UP" because actually running py33 -ignore = [ - "E203", - "F401", # we still need to use type annotation in comments in py33 -] +select = ["E", "F", "W", "I", "UP"] +ignore = ["E203"] [tool.ruff.lint.per-file-ignores] "boot.py" = ["E402"] diff --git a/requirements.txt b/requirements.txt index 69569ac..576967f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ mypy -ruff>=0.3 +ruff>=0.4