From 503dc4f42f7e26769b4b27b8c96fbcafbb3180a5 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 21 Jul 2024 18:47:55 +0100 Subject: [PATCH 01/43] Drop Python 3.8 This is a little premature, but by the time 1.0 is ready Python 3.8 will be end of life so we may as well drop support for it now --- .devcontainer/tools.mk | 2 +- .github/workflows/lsp-pr.yml | 2 +- .github/workflows/lsp-release.yml | 2 +- .github/workflows/vscode-pr.yml | 2 +- code/Makefile | 8 ++++---- code/hatch.toml | 2 +- code/requirements.in | 2 +- code/requirements.txt | 20 ++++++++++---------- code/src/common/constants.ts | 2 +- lib/esbonio/hatch.toml | 4 ++-- lib/esbonio/pyproject.toml | 5 ++--- 11 files changed, 25 insertions(+), 26 deletions(-) diff --git a/.devcontainer/tools.mk b/.devcontainer/tools.mk index 4b0c4cc0..9d01defe 100644 --- a/.devcontainer/tools.mk +++ b/.devcontainer/tools.mk @@ -9,7 +9,7 @@ HATCH_VERSION = 1.10.0 NODE_VERSION := 18.20.2 # The versions of Python we support -PYXX_versions := 3.8 3.9 3.10 3.11 3.12 +PYXX_versions := 3.9 3.10 3.11 3.12 PY_INTERPRETERS = # Hatch is not only used for building packages, but bootstrapping any missing diff --git a/.github/workflows/lsp-pr.yml b/.github/workflows/lsp-pr.yml index cd5246a2..f1114d72 100644 --- a/.github/workflows/lsp-pr.yml +++ b/.github/workflows/lsp-pr.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12"] os: [ubuntu-latest, windows-latest] steps: diff --git a/.github/workflows/lsp-release.yml b/.github/workflows/lsp-release.yml index 4190beec..5da4126f 100644 --- a/.github/workflows/lsp-release.yml +++ b/.github/workflows/lsp-release.yml @@ -121,7 +121,7 @@ jobs: - uses: 'actions/setup-python@v5' with: # This must be the minimum Python version we support - python-version: "3.8" + python-version: "3.9" - name: pip cache uses: actions/cache@v4 diff --git a/.github/workflows/vscode-pr.yml b/.github/workflows/vscode-pr.yml index bb51ddbb..3f821182 100644 --- a/.github/workflows/vscode-pr.yml +++ b/.github/workflows/vscode-pr.yml @@ -22,7 +22,7 @@ jobs: - uses: 'actions/setup-python@v5' with: # This must be the minimum Python version we support - python-version: "3.8" + python-version: "3.9" - name: Pip cache uses: actions/cache@v4 diff --git a/code/Makefile b/code/Makefile index 923c4b37..62392432 100644 --- a/code/Makefile +++ b/code/Makefile @@ -36,15 +36,15 @@ dev-deps: node_modules/.installed bundled/libs/.installed # Ensures the latest version of esbonio from PyPi is used. release-deps: node_modules/.installed bundled/libs/.installed -test -L bundled/libs/esbonio && rm bundled/libs/esbonio - test -d bundled/libs/esbonio-*.dist-info || $(PY38) -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade $(ESBONIO) + test -d bundled/libs/esbonio-*.dist-info || $(PY39) -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade $(ESBONIO) requirements.txt: $(HATCH) requirements.in $(HATCH) run deps:update -bundled/libs/.installed: $(PY38) requirements.txt +bundled/libs/.installed: $(PY39) requirements.txt -test -d bundled/libs && rm -r bundled/libs - $(PY38) --version - $(PY38) -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt + $(PY39) --version + $(PY39) -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt touch $@ node_modules/.installed: package.json package-lock.json $(NPM) diff --git a/code/hatch.toml b/code/hatch.toml index df070ec3..c7d3c6b3 100644 --- a/code/hatch.toml +++ b/code/hatch.toml @@ -1,5 +1,5 @@ [envs.deps] -python = "3.8" +python = "3.9" dependencies = ["pip-tools"] skip-install = true diff --git a/code/requirements.in b/code/requirements.in index 19202e2c..adc0fda0 100644 --- a/code/requirements.in +++ b/code/requirements.in @@ -1,7 +1,7 @@ # This file is used to generate requirements.txt. # # Only run the commands below to manually upgrade packages in requirements.txt: -# NOTE: Use the minimum version of Python we support when running these commands (currently 3.8) +# NOTE: Use the minimum version of Python we support when running these commands (currently 3.9) # # 1) python -m pip install pip-tools # 2) pip-compile --generate-hashes --upgrade ./requirements.in diff --git a/code/requirements.txt b/code/requirements.txt index 24d3a5fc..5ef6499c 100644 --- a/code/requirements.txt +++ b/code/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # # pip-compile --generate-hashes requirements.in @@ -20,13 +20,13 @@ cattrs==23.2.3 \ # via # lsprotocol # pygls -docutils==0.20.1 \ - --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ - --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b +docutils==0.21.2 \ + --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ + --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via -r requirements.in -exceptiongroup==1.2.1 \ - --hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \ - --hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16 +exceptiongroup==1.2.2 \ + --hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \ + --hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc # via cattrs lsprotocol==2023.0.1 \ --hash=sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2 \ @@ -44,9 +44,9 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via -r requirements.in -typing-extensions==4.11.0 \ - --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \ - --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 # via # aiosqlite # cattrs diff --git a/code/src/common/constants.ts b/code/src/common/constants.ts index 395c36d4..cf719039 100644 --- a/code/src/common/constants.ts +++ b/code/src/common/constants.ts @@ -1,5 +1,5 @@ export namespace Server { - export const REQUIRED_PYTHON = "3.8.0" + export const REQUIRED_PYTHON = "3.9.0" export const DEFAULT_SELECTOR = [ { scheme: 'file', language: 'restructuredtext' }, diff --git a/lib/esbonio/hatch.toml b/lib/esbonio/hatch.toml index 31c4bec5..de33d0aa 100644 --- a/lib/esbonio/hatch.toml +++ b/lib/esbonio/hatch.toml @@ -14,10 +14,10 @@ extra-dependencies = ["pytest-lsp>=0.3.1,<1"] matrix-name-format = "{variable}{value}" [[envs.hatch-test.matrix]] -python = ["3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.9", "3.10", "3.11", "3.12"] [[envs.hatch-test.matrix]] -python = ["3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.9", "3.10", "3.11", "3.12"] sphinx = ["5", "6", "7"] [envs.hatch-test.overrides] diff --git a/lib/esbonio/pyproject.toml b/lib/esbonio/pyproject.toml index a61afc97..7b1cb73e 100644 --- a/lib/esbonio/pyproject.toml +++ b/lib/esbonio/pyproject.toml @@ -7,7 +7,7 @@ name = "esbonio" dynamic = ["version"] description = "A language server for sphinx/docutils based documentation projects." readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "MIT" } authors = [{ name = "Alex Carney", email = "alcarneyme@gmail.com" }] classifiers = [ @@ -16,7 +16,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -71,7 +70,7 @@ check_untyped_defs = true venv = ".env" include = ["esbonio"] -pythonVersion = "3.8" +pythonVersion = "3.9" pythonPlatform = "All" [tool.towncrier] From fb1cc4a55cc21ea9429da6224129d75dd36dff17 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 21 Jul 2024 19:15:47 +0100 Subject: [PATCH 02/43] lsp: pyupgrade --py39-plus --- lib/esbonio/esbonio/server/_configuration.py | 45 ++-- lib/esbonio/esbonio/server/cli.py | 2 +- lib/esbonio/esbonio/server/events.py | 9 +- lib/esbonio/esbonio/server/feature.py | 30 ++- .../server/features/directives/__init__.py | 20 +- .../server/features/directives/completion.py | 14 +- lib/esbonio/esbonio/server/features/log.py | 9 +- .../server/features/myst/directives.py | 10 +- .../esbonio/server/features/myst/roles.py | 10 +- .../features/preview_manager/__init__.py | 6 +- .../features/preview_manager/preview.py | 9 +- .../features/preview_manager/webview.py | 14 +- .../features/project_manager/manager.py | 12 +- .../features/project_manager/project.py | 38 ++- .../esbonio/server/features/roles/__init__.py | 41 ++-- .../server/features/roles/completion.py | 22 +- .../esbonio/server/features/rst/directives.py | 10 +- .../esbonio/server/features/rst/roles.py | 12 +- .../server/features/sphinx_manager/client.py | 13 +- .../sphinx_manager/client_subprocess.py | 21 +- .../server/features/sphinx_manager/config.py | 17 +- .../server/features/sphinx_manager/manager.py | 12 +- .../features/sphinx_support/directives.py | 10 +- .../server/features/sphinx_support/roles.py | 19 +- .../server/features/sphinx_support/symbols.py | 14 +- lib/esbonio/esbonio/server/server.py | 28 +-- lib/esbonio/esbonio/server/setup.py | 14 +- lib/esbonio/esbonio/sphinx_agent/app.py | 9 +- lib/esbonio/esbonio/sphinx_agent/config.py | 12 +- lib/esbonio/esbonio/sphinx_agent/database.py | 21 +- .../esbonio/sphinx_agent/handlers/__init__.py | 18 +- .../sphinx_agent/handlers/directives.py | 10 +- .../esbonio/sphinx_agent/handlers/domains.py | 18 +- .../esbonio/sphinx_agent/handlers/files.py | 11 +- .../esbonio/sphinx_agent/handlers/roles.py | 3 +- .../esbonio/sphinx_agent/handlers/symbols.py | 7 +- .../esbonio/sphinx_agent/handlers/webview.py | 5 +- lib/esbonio/esbonio/sphinx_agent/log.py | 18 +- lib/esbonio/esbonio/sphinx_agent/patches.py | 9 +- lib/esbonio/esbonio/sphinx_agent/server.py | 9 +- .../esbonio/sphinx_agent/types/__init__.py | 35 ++- .../esbonio/sphinx_agent/types/roles.py | 25 +- lib/esbonio/tests/e2e/test_e2e_directives.py | 14 +- lib/esbonio/tests/e2e/test_e2e_roles.py | 22 +- lib/esbonio/tests/e2e/test_e2e_symbols.py | 227 +++++++++--------- lib/esbonio/tests/e2e/test_sphinx_manager.py | 3 +- .../tests/server/feature/test_completion.py | 9 +- .../features/test_directive_completion.py | 5 +- .../tests/server/features/test_logging.py | 8 +- .../server/features/test_role_completion.py | 5 +- .../tests/server/test_configuration.py | 15 +- .../sphinx-agent/handlers/test_diagnostics.py | 8 +- .../sphinx-agent/handlers/test_domains.py | 4 +- .../tests/sphinx-agent/test_sa_unit.py | 10 +- .../tests/sphinx-agent/test_sa_uri_class.py | 7 +- 55 files changed, 420 insertions(+), 588 deletions(-) diff --git a/lib/esbonio/esbonio/server/_configuration.py b/lib/esbonio/esbonio/server/_configuration.py index fad620a1..34f329dc 100644 --- a/lib/esbonio/esbonio/server/_configuration.py +++ b/lib/esbonio/esbonio/server/_configuration.py @@ -17,14 +17,9 @@ T = TypeVar("T") if typing.TYPE_CHECKING: + from collections.abc import Awaitable from typing import Any - from typing import Awaitable from typing import Callable - from typing import Dict - from typing import List - from typing import Optional - from typing import Set - from typing import Type from typing import Union from .server import EsbonioLanguageServer @@ -47,7 +42,7 @@ class Subscription(Generic[T]): section: str """The configuration section.""" - spec: Type[T] + spec: type[T] """The subscription's class definition.""" callback: ConfigurationCallback @@ -67,7 +62,7 @@ class ConfigChangeEvent(Generic[T]): value: T """The latest configuration value.""" - previous: Optional[T] = None + previous: T | None = None """The previous configuration value, (if any).""" @@ -90,13 +85,13 @@ def scope(self) -> str: return max([self.file_scope, self.workspace_scope], key=len) @property - def scope_path(self) -> Optional[str]: + def scope_path(self) -> str | None: """The scope uri as a path.""" uri = Uri.parse(self.scope) return uri.path @property - def scope_fs_path(self) -> Optional[str]: + def scope_fs_path(self) -> str | None: """The scope uri as an fs path.""" uri = Uri.parse(self.scope) return uri.fs_path @@ -158,16 +153,16 @@ def __init__(self, server: EsbonioLanguageServer): self.logger = server.logger.getChild("Configuration") """The logger instance to use""" - self._initialization_options: Dict[str, Any] = {} + self._initialization_options: dict[str, Any] = {} """The received initializaion options (if any)""" - self._workspace_config: Dict[str, Dict[str, Any]] = {} + self._workspace_config: dict[str, dict[str, Any]] = {} """The cached workspace configuration.""" - self._file_config: Dict[str, Dict[str, Any]] = {} + self._file_config: dict[str, dict[str, Any]] = {} """The cached configuration coming from configuration files.""" - self._subscriptions: Dict[Subscription, Any] = {} + self._subscriptions: dict[Subscription, Any] = {} """Subscriptions and their last known value""" @property @@ -203,9 +198,9 @@ def supports_workspace_config(self): def subscribe( self, section: str, - spec: Type[T], + spec: type[T], callback: ConfigurationCallback, - scope: Optional[Uri] = None, + scope: Uri | None = None, ): """Subscribe to updates to the given configuration section. @@ -276,7 +271,7 @@ def _notify_subscriptions(self, *args): exc_info=True, ) - def get(self, section: str, spec: Type[T], scope: Optional[Uri] = None) -> T: + def get(self, section: str, spec: type[T], scope: Uri | None = None) -> T: """Get the requested configuration section. Parameters @@ -325,7 +320,7 @@ def scope_for(self, uri: Uri) -> str: def _get_config( self, section: str, - spec: Type[T], + spec: type[T], context: ConfigurationContext, ) -> T: """Get the requested configuration section.""" @@ -364,11 +359,11 @@ def _get_config( ) return spec() - def _uri_to_file_scope(self, uri: Optional[Uri]) -> str: + def _uri_to_file_scope(self, uri: Uri | None) -> str: folder_uris = list(self._file_config.keys()) return _uri_to_scope(folder_uris, uri) - def _uri_to_workspace_scope(self, uri: Optional[Uri]) -> str: + def _uri_to_workspace_scope(self, uri: Uri | None) -> str: folder_uris = [f.uri for f in self.workspace.folders.values()] if (root_uri := self.workspace.root_uri) is not None: @@ -376,7 +371,7 @@ def _uri_to_workspace_scope(self, uri: Optional[Uri]) -> str: return _uri_to_scope(folder_uris, uri) - def _discover_config_files(self) -> List[pathlib.Path]: + def _discover_config_files(self) -> list[pathlib.Path]: """Scan the workspace for available configuration files.""" folder_uris = {f.uri for f in self.workspace.folders.values()} @@ -395,7 +390,7 @@ def _discover_config_files(self) -> List[pathlib.Path]: return paths - def update_file_configuration(self, paths: Optional[List[pathlib.Path]] = None): + def update_file_configuration(self, paths: list[pathlib.Path] | None = None): """Update the internal cache of configuration coming from files. Parameters @@ -466,7 +461,7 @@ async def update_workspace_configuration(self): self._notify_subscriptions() -def _uri_to_scope(known_scopes: List[str], uri: Optional[Uri]) -> str: +def _uri_to_scope(known_scopes: list[str], uri: Uri | None) -> str: """Convert the given uri to a scope or the empty string if none could be found. Parameters @@ -496,13 +491,13 @@ def _uri_to_scope(known_scopes: List[str], uri: Optional[Uri]) -> str: return sorted(candidates, key=len, reverse=True)[0] -def _merge_configs(*configs: Dict[str, Any]): +def _merge_configs(*configs: dict[str, Any]): """Recursively merge all the given configuration sources together. The last config given takes precedence. """ final = {} - all_keys: Set[str] = set() + all_keys: set[str] = set() for c in configs: all_keys.update(c.keys()) diff --git a/lib/esbonio/esbonio/server/cli.py b/lib/esbonio/esbonio/server/cli.py index 343a501f..65764d1f 100644 --- a/lib/esbonio/esbonio/server/cli.py +++ b/lib/esbonio/esbonio/server/cli.py @@ -2,9 +2,9 @@ import logging import sys import warnings +from collections.abc import Sequence from logging.handlers import MemoryHandler from typing import Optional -from typing import Sequence from pygls.protocol import default_converter diff --git a/lib/esbonio/esbonio/server/events.py b/lib/esbonio/esbonio/server/events.py index 255ee629..a2a2cae3 100644 --- a/lib/esbonio/esbonio/server/events.py +++ b/lib/esbonio/esbonio/server/events.py @@ -9,9 +9,6 @@ if typing.TYPE_CHECKING: from typing import Any - from typing import Dict - from typing import Optional - from typing import Set class EventSource: @@ -20,14 +17,14 @@ class EventSource: # TODO: It might be nice to do some fancy typing here so that type checkers # etc know which events are possible etc. - def __init__(self, logger: Optional[logging.Logger] = None): + def __init__(self, logger: logging.Logger | None = None): self.logger = logger or logging.getLogger(__name__) """The logging instance to use.""" - self.handlers: Dict[str, set] = {} + self.handlers: dict[str, set] = {} """Collection of handlers for various events.""" - self._tasks: Set[asyncio.Task] = set() + self._tasks: set[asyncio.Task] = set() """Holds tasks that are currently executing an async event handler.""" def add_listener(self, event: str, handler): diff --git a/lib/esbonio/esbonio/server/feature.py b/lib/esbonio/esbonio/server/feature.py index 78761020..1efff85f 100644 --- a/lib/esbonio/esbonio/server/feature.py +++ b/lib/esbonio/esbonio/server/feature.py @@ -12,23 +12,21 @@ if typing.TYPE_CHECKING: import re + from collections.abc import Coroutine from typing import Any - from typing import Coroutine - from typing import List from typing import Optional - from typing import Set from typing import Union from .server import EsbonioLanguageServer CompletionResult = Union[ - Optional[List[types.CompletionItem]], - Coroutine[Any, Any, Optional[List[types.CompletionItem]]], + Optional[list[types.CompletionItem]], + Coroutine[Any, Any, Optional[list[types.CompletionItem]]], ] DocumentSymbolResult = Union[ - Optional[List[types.DocumentSymbol]], - Coroutine[Any, Any, Optional[List[types.DocumentSymbol]]], + Optional[list[types.DocumentSymbol]], + Coroutine[Any, Any, Optional[list[types.DocumentSymbol]]], ] MaybeAsyncNone = Union[ @@ -37,8 +35,8 @@ ] WorkspaceSymbolResult = Union[ - Optional[List[types.WorkspaceSymbol]], - Coroutine[Any, Any, Optional[List[types.WorkspaceSymbol]]], + Optional[list[types.WorkspaceSymbol]], + Coroutine[Any, Any, Optional[list[types.WorkspaceSymbol]]], ] @@ -82,7 +80,7 @@ def document_open(self, params: types.DidOpenTextDocumentParams) -> MaybeAsyncNo def document_save(self, params: types.DidSaveTextDocumentParams) -> MaybeAsyncNone: """Called when a text document is saved.""" - completion_trigger: Optional[CompletionTrigger] = None + completion_trigger: CompletionTrigger | None = None def completion(self, context: CompletionContext) -> CompletionResult: """Called when a completion request matches one of the specified triggers.""" @@ -102,16 +100,16 @@ def workspace_symbol( class CompletionTrigger: """Define when the feature's completion method should be called.""" - patterns: List[re.Pattern] + patterns: list[re.Pattern] """A list of regular expressions to try""" - languages: Set[str] = attrs.field(factory=set) + languages: set[str] = attrs.field(factory=set) """Languages in which the completion trigger should fire. If empty, the document's language will be ignored. """ - characters: Set[str] = attrs.field(factory=set) + characters: set[str] = attrs.field(factory=set) """Characters which, when typed, should trigger a completion request. If empty, this trigger will ignore any trigger characters. @@ -124,7 +122,7 @@ def __call__( document: TextDocument, language: str, client_capabilities: types.ClientCapabilities, - ) -> Optional[CompletionContext]: + ) -> CompletionContext | None: """Determine if this completion trigger should fire. Parameters @@ -254,7 +252,7 @@ def deprecated_support(self) -> bool: ) @property - def documentation_formats(self) -> List[types.MarkupKind]: + def documentation_formats(self) -> list[types.MarkupKind]: """The list of documentation formats supported by the client.""" return get_capability( self.capabilities, @@ -291,7 +289,7 @@ def snippet_support(self) -> bool: ) @property - def supported_tags(self) -> List[types.CompletionItemTag]: + def supported_tags(self) -> list[types.CompletionItemTag]: """The list of ``CompletionItemTags`` supported by the client.""" capabilities = get_capability( self.capabilities, diff --git a/lib/esbonio/esbonio/server/features/directives/__init__.py b/lib/esbonio/esbonio/server/features/directives/__init__.py index 5d03bc22..839bb4e1 100644 --- a/lib/esbonio/esbonio/server/features/directives/__init__.py +++ b/lib/esbonio/esbonio/server/features/directives/__init__.py @@ -8,12 +8,8 @@ from esbonio import server if typing.TYPE_CHECKING: + from collections.abc import Coroutine from typing import Any - from typing import Coroutine - from typing import Dict - from typing import List - from typing import Optional - from typing import Union @attrs.define @@ -23,7 +19,7 @@ class Directive: name: str """The name of the directive, as the user would type in an rst file.""" - implementation: Optional[str] + implementation: str | None """The dotted name of the directive's implementation.""" @@ -32,9 +28,7 @@ class DirectiveProvider: def suggest_directives( self, context: server.CompletionContext - ) -> Union[ - Optional[List[Directive]], Coroutine[Any, Any, Optional[List[Directive]]] - ]: + ) -> list[Directive] | None | Coroutine[Any, Any, list[Directive] | None]: """Given a completion context, suggest directives that may be used.""" return None @@ -49,7 +43,7 @@ class DirectiveFeature(server.LanguageFeature): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._providers: Dict[int, DirectiveProvider] = {} + self._providers: dict[int, DirectiveProvider] = {} def add_provider(self, provider: DirectiveProvider): """Register a directive provider. @@ -63,7 +57,7 @@ def add_provider(self, provider: DirectiveProvider): async def suggest_directives( self, context: server.CompletionContext - ) -> List[Directive]: + ) -> list[Directive]: """Suggest directives that may be used, given a completion context. Parameters @@ -71,11 +65,11 @@ async def suggest_directives( context The completion context. """ - items: List[Directive] = [] + items: list[Directive] = [] for provider in self._providers.values(): try: - result: Optional[List[Directive]] = None + result: list[Directive] | None = None aresult = provider.suggest_directives(context) if inspect.isawaitable(aresult): diff --git a/lib/esbonio/esbonio/server/features/directives/completion.py b/lib/esbonio/esbonio/server/features/directives/completion.py index d93c12fe..0c8a2bb1 100644 --- a/lib/esbonio/esbonio/server/features/directives/completion.py +++ b/lib/esbonio/esbonio/server/features/directives/completion.py @@ -11,9 +11,7 @@ if typing.TYPE_CHECKING: from typing import Callable - from typing import Dict from typing import Optional - from typing import Tuple from . import Directive @@ -23,7 +21,7 @@ WORD = re.compile("[a-zA-Z]+") -_DIRECTIVE_RENDERERS: Dict[Tuple[str, str], DirectiveRenderer] = {} +_DIRECTIVE_RENDERERS: dict[tuple[str, str], DirectiveRenderer] = {} """CompletionItem rendering functions for directives.""" @@ -39,7 +37,7 @@ def fn(f: DirectiveRenderer) -> DirectiveRenderer: def get_directive_renderer( language: str, insert_behavior: str -) -> Optional[DirectiveRenderer]: +) -> DirectiveRenderer | None: """Return the directive renderer to use. Parameters @@ -62,7 +60,7 @@ def get_directive_renderer( def render_rst_directive_with_insert_text( context: server.CompletionContext, directive: Directive, -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` using ``insertText`` fields. This implements the ``insert`` behavior for directives. @@ -139,7 +137,7 @@ def render_rst_directive_with_insert_text( def render_rst_directive_with_text_edit( context: server.CompletionContext, directive: Directive, -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` for a reStructuredText directive using the ``textEdit`` field. @@ -189,7 +187,7 @@ def render_rst_directive_with_text_edit( def render_myst_directive_with_text_edit( context: server.CompletionContext, directive: Directive, -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` for a MyST directive using the ``textEdit`` field. Parameters @@ -240,7 +238,7 @@ def render_myst_directive_with_text_edit( def render_myst_directive_with_insert_text( context: server.CompletionContext, directive: Directive, -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` for a MyST directive using the ``insertText`` field. Parameters diff --git a/lib/esbonio/esbonio/server/features/log.py b/lib/esbonio/esbonio/server/features/log.py index 394919e8..9fd179a2 100644 --- a/lib/esbonio/esbonio/server/features/log.py +++ b/lib/esbonio/esbonio/server/features/log.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import enum import json import logging @@ -5,7 +7,6 @@ import textwrap from logging.handlers import MemoryHandler from typing import Any -from typing import Dict from typing import Optional import attrs @@ -205,7 +206,7 @@ def add_logger( self.loggers[name] = dict(level=level, propagate=False, handlers=handlers) - def finish(self) -> Dict[str, Any]: + def finish(self) -> dict[str, Any]: """Return the final configuration.""" return dict( version=1, @@ -235,13 +236,13 @@ class LoggingConfig: window: bool = attrs.field(default=False) """If set, send message as a ``window/logMessage`` notification""" - config: Dict[str, LoggerConfiguration] = attrs.field(factory=dict) + config: dict[str, LoggerConfiguration] = attrs.field(factory=dict) """Configuration of individual loggers""" show_deprecation_warnings: bool = attrs.field(default=False) """Developer flag to enable deprecation warnings.""" - def to_logging_config(self, server: server.EsbonioLanguageServer) -> Dict[str, Any]: + def to_logging_config(self, server: server.EsbonioLanguageServer) -> dict[str, Any]: """Convert the user's config into a config dict that can be passed to the ``logging.config.dictConfig()`` function. diff --git a/lib/esbonio/esbonio/server/features/myst/directives.py b/lib/esbonio/esbonio/server/features/myst/directives.py index aec577ac..0f638770 100644 --- a/lib/esbonio/esbonio/server/features/myst/directives.py +++ b/lib/esbonio/esbonio/server/features/myst/directives.py @@ -1,7 +1,5 @@ from __future__ import annotations -import typing - from lsprotocol import types from esbonio import server @@ -10,10 +8,6 @@ from esbonio.server.features.directives import completion from esbonio.sphinx_agent.types import MYST_DIRECTIVE -if typing.TYPE_CHECKING: - from typing import List - from typing import Optional - class MystDirectives(server.LanguageFeature): """A frontend to directives for MyST syntax.""" @@ -46,7 +40,7 @@ def update_configuration( async def completion( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Provide completion suggestions for directives.""" groups = context.match.groupdict() @@ -76,7 +70,7 @@ async def complete_arguments(self, context: server.CompletionContext): async def complete_directives( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Return completion suggestions for the available directives.""" render_func = completion.get_directive_renderer( diff --git a/lib/esbonio/esbonio/server/features/myst/roles.py b/lib/esbonio/esbonio/server/features/myst/roles.py index dbc01bda..9621b654 100644 --- a/lib/esbonio/esbonio/server/features/myst/roles.py +++ b/lib/esbonio/esbonio/server/features/myst/roles.py @@ -1,7 +1,5 @@ from __future__ import annotations -import typing - from lsprotocol import types from esbonio import server @@ -9,10 +7,6 @@ from esbonio.server.features.roles import completion from esbonio.sphinx_agent.types import MYST_ROLE -if typing.TYPE_CHECKING: - from typing import List - from typing import Optional - class MystRoles(server.LanguageFeature): """A frontend to roles for MyST syntax.""" @@ -45,7 +39,7 @@ def update_configuration( async def completion( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Provide completion suggestions for roles.""" groups = context.match.groupdict() @@ -84,7 +78,7 @@ async def complete_targets(self, context: server.CompletionContext): async def complete_roles( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Return completion suggestions for the available roles""" render_func = completion.get_role_renderer( diff --git a/lib/esbonio/esbonio/server/features/preview_manager/__init__.py b/lib/esbonio/esbonio/server/features/preview_manager/__init__.py index 723e9bbb..4b966b93 100644 --- a/lib/esbonio/esbonio/server/features/preview_manager/__init__.py +++ b/lib/esbonio/esbonio/server/features/preview_manager/__init__.py @@ -1,7 +1,5 @@ from typing import Any -from typing import Dict from typing import Optional -from typing import Set from urllib.parse import urlencode from lsprotocol import types @@ -34,7 +32,7 @@ def __init__( self.sphinx.add_listener("build", self.on_build) """The sphinx manager.""" - self.built_clients: Set[str] = set() + self.built_clients: set[str] = set() """Keeps track of which clients run a build at least once.""" self.build_path: Optional[str] = None @@ -190,7 +188,7 @@ async def show_preview_uri(self) -> Optional[Uri]: server = await self.preview webview = await self.webview - query_params: Dict[str, Any] = dict(ws=webview.port) + query_params: dict[str, Any] = dict(ws=webview.port) if self.config.show_line_markers: query_params["show-markers"] = True diff --git a/lib/esbonio/esbonio/server/features/preview_manager/preview.py b/lib/esbonio/esbonio/server/features/preview_manager/preview.py index 1bae234d..802a86bc 100644 --- a/lib/esbonio/esbonio/server/features/preview_manager/preview.py +++ b/lib/esbonio/esbonio/server/features/preview_manager/preview.py @@ -11,7 +11,6 @@ if typing.TYPE_CHECKING: from typing import Any - from typing import Optional from .config import PreviewConfig @@ -38,7 +37,7 @@ class RequestHandlerFactory: produce a request handler based on the current situation. """ - def __init__(self, logger: logging.Logger, build_uri: Optional[Uri] = None): + def __init__(self, logger: logging.Logger, build_uri: Uri | None = None): self.logger = logger self.build_uri = build_uri @@ -67,16 +66,16 @@ def __init__(self, logger: logging.Logger, config: PreviewConfig, executor: Any) self._handler_factory = RequestHandlerFactory(self.logger) """Factory for producing http request handlers.""" - self._startup_task: Optional[asyncio.Task] = None + self._startup_task: asyncio.Task | None = None """Task that resolves once the server is ready.""" self._executor: Any = executor """The executor in which to run the http server.""" - self._future: Optional[asyncio.Future] = None + self._future: asyncio.Future | None = None """The future representing the http server's "task".""" - self._server: Optional[HTTPServer] = None + self._server: HTTPServer | None = None """The http server itself.""" def __await__(self): diff --git a/lib/esbonio/esbonio/server/features/preview_manager/webview.py b/lib/esbonio/esbonio/server/features/preview_manager/webview.py index 78de9332..33de4b1b 100644 --- a/lib/esbonio/esbonio/server/features/preview_manager/webview.py +++ b/lib/esbonio/esbonio/server/features/preview_manager/webview.py @@ -16,8 +16,6 @@ from esbonio import server if typing.TYPE_CHECKING: - from typing import Optional - from websockets import WebSocketServer from .config import PreviewConfig @@ -39,21 +37,21 @@ def __init__(self, logger: logging.Logger, config: PreviewConfig, *args, **kwarg self.lsp._send_only_body = True self._connected = False - self._ws_server: Optional[WebSocketServer] = None + self._ws_server: WebSocketServer | None = None - self._startup_task: Optional[asyncio.Task] = None + self._startup_task: asyncio.Task | None = None """The task that resolves once startup is complete.""" - self._server_task: Optional[asyncio.Task] = None + self._server_task: asyncio.Task | None = None """The task hosting the server itself.""" - self._editor_in_control: Optional[asyncio.Task] = None + self._editor_in_control: asyncio.Task | None = None """If set, the editor is in control and the view should not emit scroll events""" - self._view_in_control: Optional[asyncio.Task] = None + self._view_in_control: asyncio.Task | None = None """If set, the view is in control and the editor should not emit scroll events""" - self._current_uri: Optional[str] = None + self._current_uri: str | None = None """If set, indicates the current uri the editor and view are scrolling.""" def __await__(self): diff --git a/lib/esbonio/esbonio/server/features/project_manager/manager.py b/lib/esbonio/esbonio/server/features/project_manager/manager.py index 052365b7..9747fffa 100644 --- a/lib/esbonio/esbonio/server/features/project_manager/manager.py +++ b/lib/esbonio/esbonio/server/features/project_manager/manager.py @@ -1,18 +1,12 @@ from __future__ import annotations import pathlib -import typing from esbonio import server from esbonio.server import Uri from .project import Project -if typing.TYPE_CHECKING: - from typing import Dict - from typing import Optional - from typing import Union - class ProjectManager(server.LanguageFeature): """Responsible for managing project instances.""" @@ -20,15 +14,15 @@ class ProjectManager(server.LanguageFeature): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.projects: Dict[str, Project] = {} + self.projects: dict[str, Project] = {} """Holds active project instances""" - def register_project(self, scope: str, dbpath: Union[str, pathlib.Path]): + def register_project(self, scope: str, dbpath: str | pathlib.Path): """Register a project.""" self.logger.debug("Registered project for scope '%s': '%s'", scope, dbpath) self.projects[scope] = Project(dbpath, self.converter) - def get_project(self, uri: Uri) -> Optional[Project]: + def get_project(self, uri: Uri) -> Project | None: """Return the project instance for the given uri, if available""" scope = self.server.configuration.scope_for(uri) diff --git a/lib/esbonio/esbonio/server/features/project_manager/project.py b/lib/esbonio/esbonio/server/features/project_manager/project.py index e06de622..d803614f 100644 --- a/lib/esbonio/esbonio/server/features/project_manager/project.py +++ b/lib/esbonio/esbonio/server/features/project_manager/project.py @@ -11,13 +11,7 @@ if typing.TYPE_CHECKING: from typing import Any - from typing import Dict - from typing import List - from typing import Optional - from typing import Tuple - from typing import Type from typing import TypeVar - from typing import Union import cattrs @@ -27,10 +21,10 @@ class Project: """Represents a documentation project.""" - def __init__(self, dbpath: Union[str, pathlib.Path], converter: cattrs.Converter): + def __init__(self, dbpath: str | pathlib.Path, converter: cattrs.Converter): self.converter = converter self.dbpath = dbpath - self._connection: Optional[aiosqlite.Connection] = None + self._connection: aiosqlite.Connection | None = None async def close(self): if self._connection is not None: @@ -42,10 +36,10 @@ async def get_db(self) -> aiosqlite.Connection: return self._connection - def load_as(self, o: str, t: Type[T]) -> T: + def load_as(self, o: str, t: type[T]) -> T: return self.converter.structure(json.loads(o), t) - async def get_src_uris(self) -> List[Uri]: + async def get_src_uris(self) -> list[Uri]: """Return all known source uris.""" db = await self.get_db() @@ -54,7 +48,7 @@ async def get_src_uris(self) -> List[Uri]: results = await cursor.fetchall() return [Uri.parse(s[0]) for s in results] - async def get_build_path(self, src_uri: Uri) -> Optional[str]: + async def get_build_path(self, src_uri: Uri) -> str | None: """Get the build path associated with the given ``src_uri``.""" db = await self.get_db() @@ -65,7 +59,7 @@ async def get_build_path(self, src_uri: Uri) -> Optional[str]: return result[0] - async def get_config_value(self, name: str) -> Optional[Any]: + async def get_config_value(self, name: str) -> Any | None: """Return the requested configuration value, if available.""" db = await self.get_db() @@ -78,7 +72,7 @@ async def get_config_value(self, name: str) -> Optional[Any]: (value,) = row return json.loads(value) - async def get_directives(self) -> List[Tuple[str, Optional[str]]]: + async def get_directives(self) -> list[tuple[str, str | None]]: """Get the directives known to Sphinx.""" db = await self.get_db() @@ -86,7 +80,7 @@ async def get_directives(self) -> List[Tuple[str, Optional[str]]]: cursor = await db.execute(query) return await cursor.fetchall() # type: ignore[return-value] - async def get_role(self, name: str) -> Optional[types.Role]: + async def get_role(self, name: str) -> types.Role | None: """Get the roles known to Sphinx.""" db = await self.get_db() @@ -96,7 +90,7 @@ async def get_role(self, name: str) -> Optional[types.Role]: return types.Role.from_db(self.load_as, *result) if result is not None else None - async def get_roles(self) -> List[Tuple[str, Optional[str]]]: + async def get_roles(self) -> list[tuple[str, str | None]]: """Get the roles known to Sphinx.""" db = await self.get_db() @@ -104,7 +98,7 @@ async def get_roles(self) -> List[Tuple[str, Optional[str]]]: cursor = await db.execute(query) return await cursor.fetchall() # type: ignore[return-value] - async def get_document_symbols(self, src_uri: Uri) -> List[types.Symbol]: + async def get_document_symbols(self, src_uri: Uri) -> list[types.Symbol]: """Get the symbols for the given file.""" db = await self.get_db() query = ( @@ -114,14 +108,14 @@ async def get_document_symbols(self, src_uri: Uri) -> List[types.Symbol]: cursor = await db.execute(query, (str(src_uri.resolve()),)) return await cursor.fetchall() # type: ignore[return-value] - async def find_symbols(self, **kwargs) -> List[types.Symbol]: + async def find_symbols(self, **kwargs) -> list[types.Symbol]: """Find symbols which match the given criteria.""" db = await self.get_db() base_query = ( "SELECT id, name, kind, detail, range, parent_id, order_id FROM symbols" ) - where: List[str] = [] - parameters: List[Any] = [] + where: list[str] = [] + parameters: list[Any] = [] for param, value in kwargs.items(): where.append(f"{param} = ?") @@ -138,7 +132,7 @@ async def find_symbols(self, **kwargs) -> List[types.Symbol]: async def get_workspace_symbols( self, query: str - ) -> List[Tuple[str, str, int, str, str, str]]: + ) -> list[tuple[str, str, int, str, str, str]]: """Return all the workspace symbols matching the given query string""" db = await self.get_db() @@ -161,11 +155,11 @@ async def get_workspace_symbols( cursor = await db.execute(sql_query, (query_str, query_str)) return await cursor.fetchall() # type: ignore[return-value] - async def get_diagnostics(self) -> Dict[Uri, List[Dict[str, Any]]]: + async def get_diagnostics(self) -> dict[Uri, list[dict[str, Any]]]: """Get diagnostics for the project.""" db = await self.get_db() cursor = await db.execute("SELECT * FROM diagnostics") - results: Dict[Uri, List[Dict[str, Any]]] = {} + results: dict[Uri, list[dict[str, Any]]] = {} for uri_str, item in await cursor.fetchall(): uri = Uri.parse(uri_str) diff --git a/lib/esbonio/esbonio/server/features/roles/__init__.py b/lib/esbonio/esbonio/server/features/roles/__init__.py index ad19eb57..58d08c56 100644 --- a/lib/esbonio/esbonio/server/features/roles/__init__.py +++ b/lib/esbonio/esbonio/server/features/roles/__init__.py @@ -9,12 +9,8 @@ from esbonio.sphinx_agent import types if typing.TYPE_CHECKING: + from collections.abc import Coroutine from typing import Any - from typing import Coroutine - from typing import Dict - from typing import List - from typing import Optional - from typing import Union from esbonio.server import Uri @@ -24,7 +20,7 @@ class RoleProvider: def get_role( self, uri: Uri, name: str - ) -> Union[Optional[types.Role], Coroutine[Any, Any, Optional[types.Role]]]: + ) -> types.Role | None | Coroutine[Any, Any, types.Role | None]: """Return the definition of the given role, if known. Parameters @@ -39,9 +35,7 @@ def get_role( def suggest_roles( self, context: server.CompletionContext - ) -> Union[ - Optional[List[types.Role]], Coroutine[Any, Any, Optional[List[types.Role]]] - ]: + ) -> list[types.Role] | None | Coroutine[Any, Any, list[types.Role] | None]: """Givem a completion context, suggest roles that may be used.""" return None @@ -51,10 +45,11 @@ class RoleTargetProvider: def suggest_targets( self, context: server.CompletionContext, **kwargs - ) -> Union[ - Optional[List[lsp.CompletionItem]], - Coroutine[Any, Any, Optional[List[lsp.CompletionItem]]], - ]: + ) -> ( + list[lsp.CompletionItem] + | None + | Coroutine[Any, Any, list[lsp.CompletionItem] | None] + ): """Givem a completion context, suggest role targets that may be used.""" return None @@ -69,8 +64,8 @@ class RolesFeature(server.LanguageFeature): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._role_providers: Dict[int, RoleProvider] = {} - self._target_providers: Dict[str, RoleTargetProvider] = {} + self._role_providers: dict[int, RoleProvider] = {} + self._target_providers: dict[str, RoleTargetProvider] = {} def add_role_provider(self, provider: RoleProvider): """Register a role provider. @@ -100,7 +95,7 @@ def add_target_provider(self, name: str, provider: RoleTargetProvider): async def suggest_roles( self, context: server.CompletionContext - ) -> List[types.Role]: + ) -> list[types.Role]: """Suggest roles that may be used, given a completion context. Parameters @@ -108,11 +103,11 @@ async def suggest_roles( context The completion context """ - items: List[types.Role] = [] + items: list[types.Role] = [] for provider in self._role_providers.values(): try: - result: Optional[List[types.Role]] = None + result: list[types.Role] | None = None aresult = provider.suggest_roles(context) if inspect.isawaitable(aresult): @@ -126,7 +121,7 @@ async def suggest_roles( return items - async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]: + async def get_role(self, uri: Uri, name: str) -> types.Role | None: """Return the definition of the given role name. Parameters @@ -139,12 +134,12 @@ async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]: Returns ------- - Optional[types.Role] + types.Role | None The role's definition, if known """ for provider in self._role_providers.values(): try: - result: Optional[types.Role] = None + result: types.Role | None = None aresult = provider.get_role(uri, name) if inspect.isawaitable(aresult): @@ -160,7 +155,7 @@ async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]: async def suggest_targets( self, context: server.CompletionContext, role_name: str - ) -> List[lsp.CompletionItem]: + ) -> list[lsp.CompletionItem]: """Suggest role targets that may be used, given a completion context. Parameters @@ -186,7 +181,7 @@ async def suggest_targets( continue try: - result: Optional[List[lsp.CompletionItem]] = None + result: list[lsp.CompletionItem] | None = None aresult = provider.suggest_targets(context, **spec.kwargs) if inspect.isawaitable(aresult): diff --git a/lib/esbonio/esbonio/server/features/roles/completion.py b/lib/esbonio/esbonio/server/features/roles/completion.py index 6c5db338..9b403ded 100644 --- a/lib/esbonio/esbonio/server/features/roles/completion.py +++ b/lib/esbonio/esbonio/server/features/roles/completion.py @@ -11,9 +11,7 @@ if typing.TYPE_CHECKING: from typing import Callable - from typing import Dict from typing import Optional - from typing import Tuple from esbonio.sphinx_agent.types import Role @@ -27,10 +25,10 @@ WORD = re.compile("[a-zA-Z]+") -_ROLE_RENDERERS: Dict[Tuple[str, str], RoleRenderer] = {} +_ROLE_RENDERERS: dict[tuple[str, str], RoleRenderer] = {} """CompletionItem rendering functions for roles.""" -_ROLE_TARGET_RENDERERS: Dict[Tuple[str, str], RoleTargetRenderer] = {} +_ROLE_TARGET_RENDERERS: dict[tuple[str, str], RoleTargetRenderer] = {} """CompletionItem rendering functions for role targets.""" @@ -54,7 +52,7 @@ def fn(f: RoleTargetRenderer) -> RoleTargetRenderer: return fn -def get_role_renderer(language: str, insert_behavior: str) -> Optional[RoleRenderer]: +def get_role_renderer(language: str, insert_behavior: str) -> RoleRenderer | None: """Return the role renderer to use. Parameters @@ -75,7 +73,7 @@ def get_role_renderer(language: str, insert_behavior: str) -> Optional[RoleRende def get_role_target_renderer( language: str, insert_behavior: str -) -> Optional[RoleTargetRenderer]: +) -> RoleTargetRenderer | None: """Return the role target renderer to use. Parameters @@ -97,7 +95,7 @@ def get_role_target_renderer( @role_renderer(language="rst", insert_behavior="insert") def render_rst_role_with_insert_text( context: server.CompletionContext, role: Role -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` using ``insertText``. This implements the ``insert`` insert behavior for roles. @@ -145,7 +143,7 @@ def render_rst_role_with_insert_text( @role_target_renderer(language="rst", insert_behavior="replace") def render_rst_target_with_text_edit( context: server.CompletionContext, item: types.CompletionItem -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` using ``insertText``. This implements the ``replace`` insert behavior for role targets. @@ -169,7 +167,7 @@ def render_rst_target_with_text_edit( @role_renderer(language="markdown", insert_behavior="insert") def render_myst_role_with_insert_text( context: server.CompletionContext, role: Role -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` using ``insertText``. This implements the ``insert`` insert behavior for roles. @@ -217,7 +215,7 @@ def render_myst_role_with_insert_text( @role_renderer(language="rst", insert_behavior="replace") def render_rst_role_with_text_edit( context: server.CompletionContext, role: Role -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a role's ``CompletionItem`` using ``textEdit``. This implements the ``replace`` insert behavior for roles. @@ -260,7 +258,7 @@ def render_rst_role_with_text_edit( @role_renderer(language="markdown", insert_behavior="replace") def render_myst_role_with_text_edit( context: server.CompletionContext, role: Role -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a role's ``CompletionItem`` using ``textEdit``. This implements the ``replace`` insert behavior for roles. @@ -303,7 +301,7 @@ def render_myst_role_with_text_edit( @role_target_renderer(language="markdown", insert_behavior="replace") def render_myst_target_with_text_edit( context: server.CompletionContext, item: types.CompletionItem -) -> Optional[types.CompletionItem]: +) -> types.CompletionItem | None: """Render a ``CompletionItem`` using ``textEdit``. This implements the ``replace`` insert behavior for role targets. diff --git a/lib/esbonio/esbonio/server/features/rst/directives.py b/lib/esbonio/esbonio/server/features/rst/directives.py index a125e147..be608203 100644 --- a/lib/esbonio/esbonio/server/features/rst/directives.py +++ b/lib/esbonio/esbonio/server/features/rst/directives.py @@ -1,7 +1,5 @@ from __future__ import annotations -import typing - from lsprotocol import types from esbonio import server @@ -9,10 +7,6 @@ from esbonio.server.features.directives import completion from esbonio.sphinx_agent.types import RST_DIRECTIVE -if typing.TYPE_CHECKING: - from typing import List - from typing import Optional - class RstDirectives(server.LanguageFeature): """A frontend to directives for reStructuredText syntax.""" @@ -45,7 +39,7 @@ def update_configuration( async def completion( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Provide completion suggestions for directives.""" groups = context.match.groupdict() @@ -75,7 +69,7 @@ async def complete_arguments(self, context: server.CompletionContext): async def complete_directives( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Return completion suggestions for the available directives.""" render_func = completion.get_directive_renderer( diff --git a/lib/esbonio/esbonio/server/features/rst/roles.py b/lib/esbonio/esbonio/server/features/rst/roles.py index 6c31d049..12bda497 100644 --- a/lib/esbonio/esbonio/server/features/rst/roles.py +++ b/lib/esbonio/esbonio/server/features/rst/roles.py @@ -1,7 +1,5 @@ from __future__ import annotations -import typing - from lsprotocol import types from esbonio import server @@ -10,10 +8,6 @@ from esbonio.sphinx_agent.types import RST_DIRECTIVE from esbonio.sphinx_agent.types import RST_ROLE -if typing.TYPE_CHECKING: - from typing import List - from typing import Optional - class RstRoles(server.LanguageFeature): """A frontend to roles for reStructuredText syntax.""" @@ -46,7 +40,7 @@ def update_configuration( async def completion( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Provide completion suggestions for roles.""" groups = context.match.groupdict() @@ -92,7 +86,7 @@ async def completion( async def complete_targets( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Provide completion suggestions for role targets.""" render_func = completion.get_role_target_renderer( @@ -111,7 +105,7 @@ async def complete_targets( async def complete_roles( self, context: server.CompletionContext - ) -> Optional[List[types.CompletionItem]]: + ) -> list[types.CompletionItem] | None: """Return completion suggestions for the available roles""" render_func = completion.get_role_renderer( diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/client.py b/lib/esbonio/esbonio/server/features/sphinx_manager/client.py index f83e4c8b..26d446bb 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/client.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/client.py @@ -6,11 +6,8 @@ if typing.TYPE_CHECKING: import pathlib + from collections.abc import Generator from typing import Any - from typing import Dict - from typing import Generator - from typing import List - from typing import Optional from esbonio.server import Uri from esbonio.sphinx_agent import types @@ -42,8 +39,8 @@ class SphinxClient(Protocol): """Describes the API language features can use to inspect/manipulate a Sphinx application instance.""" - state: Optional[ClientState] - sphinx_info: Optional[types.SphinxInfo] + state: ClientState | None + sphinx_info: types.SphinxInfo | None @property def id(self) -> str: @@ -91,9 +88,9 @@ async def restart(self) -> SphinxClient: async def build( self, *, - filenames: Optional[List[str]] = None, + filenames: list[str] | None = None, force_all: bool = False, - content_overrides: Optional[Dict[str, str]] = None, + content_overrides: dict[str, str] | None = None, ) -> types.BuildResult: """Trigger a Sphinx build.""" ... diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py index d642c68d..c972c055 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py @@ -23,9 +23,6 @@ if typing.TYPE_CHECKING: from typing import Any - from typing import Dict - from typing import List - from typing import Optional from .client import SphinxClient from .manager import SphinxManager @@ -54,7 +51,7 @@ class SubprocessSphinxClient(JsonRPCClient): def __init__( self, config: SphinxConfig, - logger: Optional[logging.Logger] = None, + logger: logging.Logger | None = None, protocol_cls=SphinxAgentProtocol, *args, **kwargs, @@ -70,22 +67,22 @@ def __init__( self.logger = logger or logging.getLogger(__name__) """The logger instance to use.""" - self.sphinx_info: Optional[types.SphinxInfo] = None + self.sphinx_info: types.SphinxInfo | None = None """Information about the Sphinx application the client is connected to.""" - self.state: Optional[ClientState] = None + self.state: ClientState | None = None """The current state of the client.""" - self.exception: Optional[Exception] = None + self.exception: Exception | None = None """The most recently encountered exception (if any)""" self._events = EventSource(self.logger) """The sphinx client can emit events.""" - self._startup_task: Optional[asyncio.Task] = None + self._startup_task: asyncio.Task | None = None """The startup task.""" - self._stderr_forwarder: Optional[asyncio.Task] = None + self._stderr_forwarder: asyncio.Task | None = None """A task that forwards the server's stderr to the test process.""" def __repr__(self): @@ -254,9 +251,9 @@ async def stop(self): async def build( self, *, - filenames: Optional[List[str]] = None, + filenames: list[str] | None = None, force_all: bool = False, - content_overrides: Optional[Dict[str, str]] = None, + content_overrides: dict[str, str] | None = None, ) -> types.BuildResult: """Trigger a Sphinx build.""" @@ -334,7 +331,7 @@ def _on_progress(params): return client -def get_sphinx_env(config: SphinxConfig) -> Dict[str, str]: +def get_sphinx_env(config: SphinxConfig) -> dict[str, str]: """Return the set of environment variables to use with the Sphinx process.""" env = { diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/config.py b/lib/esbonio/esbonio/server/features/sphinx_manager/config.py index 53e0c48f..e1dfc210 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/config.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/config.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import hashlib import importlib.util import logging import pathlib -from typing import List from typing import Optional import attrs @@ -45,19 +46,19 @@ class SphinxConfig: enable_dev_tools: bool = attrs.field(default=False) """Flag to enable dev tools.""" - python_command: List[str] = attrs.field(factory=list) + python_command: list[str] = attrs.field(factory=list) """The command to use when launching the python interpreter.""" - build_command: List[str] = attrs.field(factory=list) + build_command: list[str] = attrs.field(factory=list) """The sphinx-build command to use.""" - env_passthrough: List[str] = attrs.field(factory=list) + env_passthrough: list[str] = attrs.field(factory=list) """List of environment variables to pass through to the Sphinx subprocess""" cwd: str = attrs.field(default="${scopeFsPath}") """The working directory to use.""" - python_path: List[pathlib.Path] = attrs.field(factory=list) + python_path: list[pathlib.Path] = attrs.field(factory=list) """The value of ``PYTHONPATH`` to use when injecting the sphinx agent into the target environment""" @@ -66,7 +67,7 @@ def resolve( uri: Uri, workspace: Workspace, logger: logging.Logger, - ) -> "Optional[SphinxConfig]": + ) -> Optional[SphinxConfig]: """Resolve the configuration based on user provided values. Parameters @@ -154,7 +155,7 @@ def _resolve_cwd( return None - def _resolve_python_path(self, logger: logging.Logger) -> List[pathlib.Path]: + def _resolve_python_path(self, logger: logging.Logger) -> list[pathlib.Path]: """Return the list of paths to put on the sphinx agent's ``PYTHONPATH`` Using the ``PYTHONPATH`` environment variable, we can inject additional Python @@ -182,7 +183,7 @@ def _resolve_python_path(self, logger: logging.Logger) -> List[pathlib.Path]: python_path = [sphinx_agent] return python_path - def _resolve_build_command(self, uri: Uri, logger: logging.Logger) -> List[str]: + def _resolve_build_command(self, uri: Uri, logger: logging.Logger) -> list[str]: """Return the ``sphinx-build`` command to use. If no command is configured, this will attempt to guess the command to use based diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py b/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py index 39fa9ff0..1c91f7e7 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py @@ -18,8 +18,6 @@ if typing.TYPE_CHECKING: from typing import Callable - from typing import Dict - from typing import Optional from esbonio.server.features.project_manager import ProjectManager @@ -93,7 +91,7 @@ def __init__( self.project_manager = project_manager """The project manager instance to use.""" - self.clients: Dict[str, Optional[SphinxClient]] = { + self.clients: dict[str, SphinxClient | None] = { # Prevent any clients from being created in the global scope. "": None, } @@ -102,10 +100,10 @@ def __init__( self._events = server.EventSource(self.logger) """The SphinxManager can emit events.""" - self._pending_builds: Dict[str, asyncio.Task] = {} + self._pending_builds: dict[str, asyncio.Task] = {} """Holds tasks that will trigger a build after a given delay if not cancelled.""" - self._progress_tokens: Dict[str, str] = {} + self._progress_tokens: dict[str, str] = {} """Holds work done progress tokens.""" def add_listener(self, event: str, handler): @@ -183,7 +181,7 @@ async def trigger_build(self, uri: Uri): return # Pass through any unsaved content to the Sphinx agent. - content_overrides: Dict[str, str] = {} + content_overrides: dict[str, str] = {} known_src_uris = await project.get_src_uris() for src_uri in known_src_uris: @@ -226,7 +224,7 @@ async def restart_client(self, client_id: str): else: self.logger.error(f"No client with id {client_id!r} available to restart") - async def get_client(self, uri: Uri) -> Optional[SphinxClient]: + async def get_client(self, uri: Uri) -> SphinxClient | None: """Given a uri, return the relevant sphinx client instance for it.""" scope = self.server.configuration.scope_for(uri) diff --git a/lib/esbonio/esbonio/server/features/sphinx_support/directives.py b/lib/esbonio/esbonio/server/features/sphinx_support/directives.py index 861b19b7..2cd3c101 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_support/directives.py +++ b/lib/esbonio/esbonio/server/features/sphinx_support/directives.py @@ -1,17 +1,11 @@ from __future__ import annotations -import typing - from lsprotocol import types from esbonio import server from esbonio.server.features import directives from esbonio.server.features.project_manager import ProjectManager -if typing.TYPE_CHECKING: - from typing import List - from typing import Optional - class SphinxDirectives(directives.DirectiveProvider): """Support for directives in a sphinx project.""" @@ -21,7 +15,7 @@ def __init__(self, manager: ProjectManager): async def suggest_directives( self, context: server.CompletionContext - ) -> Optional[List[directives.Directive]]: + ) -> list[directives.Directive] | None: """Given a completion context, suggest directives that may be used.""" if (project := self.manager.get_project(context.uri)) is None: @@ -41,7 +35,7 @@ async def suggest_directives( primary_domain = await project.get_config_value("primary_domain") active_domain = default_domain or primary_domain or "py" - result: List[directives.Directive] = [] + result: list[directives.Directive] = [] for name, implementation in await project.get_directives(): # std: directives can be used unqualified if name.startswith("std:"): diff --git a/lib/esbonio/esbonio/server/features/sphinx_support/roles.py b/lib/esbonio/esbonio/server/features/sphinx_support/roles.py index 541fe8b2..bcce5d10 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_support/roles.py +++ b/lib/esbonio/esbonio/server/features/sphinx_support/roles.py @@ -11,9 +11,6 @@ from esbonio.sphinx_agent import types if typing.TYPE_CHECKING: - from typing import List - from typing import Optional - from esbonio.server import Uri from esbonio.server.features.project_manager import Project @@ -41,9 +38,9 @@ async def suggest_targets( # type: ignore[override] self, context: server.CompletionContext, *, - obj_types: List[str], - projects: Optional[List[str]], - ) -> Optional[List[lsp.CompletionItem]]: + obj_types: list[str], + projects: list[str] | None, + ) -> list[lsp.CompletionItem] | None: # TODO: Handle .. currentmodule if (project := self.manager.get_project(context.uri)) is None: @@ -67,9 +64,7 @@ async def suggest_targets( # type: ignore[override] return items - def _prepare_target_query( - self, projects: Optional[List[str]], obj_types: List[str] - ): + def _prepare_target_query(self, projects: list[str] | None, obj_types: list[str]): """Prepare the query to use when looking up targets.""" select = "SELECT name, display, objtype FROM objects" @@ -123,7 +118,7 @@ async def get_default_domain(self, project: Project, uri: Uri) -> str: primary_domain = await project.get_config_value("primary_domain") return default_domain or primary_domain or "py" - async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]: + async def get_role(self, uri: Uri, name: str) -> types.Role | None: """Return the role with the given name.""" if (project := self.manager.get_project(uri)) is None: @@ -140,7 +135,7 @@ async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]: async def suggest_roles( self, context: server.CompletionContext - ) -> Optional[List[types.Role]]: + ) -> list[types.Role] | None: """Given a completion context, suggest roles that may be used.""" if (project := self.manager.get_project(context.uri)) is None: @@ -148,7 +143,7 @@ async def suggest_roles( default_domain = await self.get_default_domain(project, context.uri) - result: List[types.Role] = [] + result: list[types.Role] = [] for name, implementation in await project.get_roles(): # std: directives can be used unqualified if name.startswith("std:"): diff --git a/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py b/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py index 0d39b6a0..f4b4f663 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py +++ b/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py @@ -1,7 +1,7 @@ +from __future__ import annotations + import asyncio import json -from typing import Dict -from typing import List from typing import Optional from lsprotocol import types @@ -21,7 +21,7 @@ def __init__(self, server: EsbonioLanguageServer, manager: ProjectManager): async def document_symbol( self, params: types.DocumentSymbolParams - ) -> Optional[List[types.DocumentSymbol]]: + ) -> Optional[list[types.DocumentSymbol]]: """Called when a document symbols request is received.""" uri = Uri.parse(params.text_document.uri) @@ -32,8 +32,8 @@ async def document_symbol( if len(symbols) == 0: return None - root: List[types.DocumentSymbol] = [] - index: Dict[int, types.DocumentSymbol] = {} + root: list[types.DocumentSymbol] = [] + index: dict[int, types.DocumentSymbol] = {} for id_, name, kind, detail, range_json, parent_id, _ in symbols: range_ = self.converter.structure(json.loads(range_json), types.Range) @@ -60,7 +60,7 @@ async def document_symbol( async def workspace_symbol( self, params: types.WorkspaceSymbolParams - ) -> Optional[List[types.WorkspaceSymbol]]: + ) -> Optional[list[types.WorkspaceSymbol]]: """Called when a workspace symbol request is received.""" tasks = [] @@ -70,7 +70,7 @@ async def workspace_symbol( ) symbols = await asyncio.gather(*tasks) - result: List[types.WorkspaceSymbol] = [] + result: list[types.WorkspaceSymbol] = [] for batch in symbols: for uri_str, name, kind, detail, range_json, container in batch: diff --git a/lib/esbonio/esbonio/server/server.py b/lib/esbonio/esbonio/server/server.py index 5e2a974d..7bba62a6 100644 --- a/lib/esbonio/esbonio/server/server.py +++ b/lib/esbonio/esbonio/server/server.py @@ -21,15 +21,9 @@ from ._configuration import Configuration if typing.TYPE_CHECKING: + from collections.abc import Coroutine from typing import Any from typing import Callable - from typing import Coroutine - from typing import Dict - from typing import List - from typing import Optional - from typing import Set - from typing import Tuple - from typing import Type from .feature import LanguageFeature @@ -65,7 +59,7 @@ def update_document( class EsbonioLanguageServer(LanguageServer): """The Esbonio language server""" - def __init__(self, logger: Optional[logging.Logger] = None, *args, **kwargs): + def __init__(self, logger: logging.Logger | None = None, *args, **kwargs): if "name" not in kwargs: kwargs["name"] = "esbonio" @@ -74,19 +68,19 @@ def __init__(self, logger: Optional[logging.Logger] = None, *args, **kwargs): super().__init__(*args, **kwargs) - self._diagnostics: Dict[Tuple[str, Uri], List[types.Diagnostic]] = {} + self._diagnostics: dict[tuple[str, Uri], list[types.Diagnostic]] = {} """Where we store and manage diagnostics.""" - self._loaded_extensions: Dict[str, Any] = {} + self._loaded_extensions: dict[str, Any] = {} """Record of server modules that have been loaded.""" - self._features: Dict[Type[LanguageFeature], LanguageFeature] = {} + self._features: dict[type[LanguageFeature], LanguageFeature] = {} """The collection of language features registered with the server.""" self._ready: asyncio.Future[bool] = asyncio.Future() """Indicates if the server is ready.""" - self._tasks: Set[asyncio.Task] = set() + self._tasks: set[asyncio.Task] = set() """Used to hold running tasks""" self.logger = logger or logging.getLogger(__name__) @@ -118,7 +112,7 @@ def _finish_task(self, task: asyncio.Task[Any]): traceback.format_exception(type(exc), exc, exc.__traceback__), ) - def run_task(self, coro: Coroutine, *, name: Optional[str] = None) -> asyncio.Task: + def run_task(self, coro: Coroutine, *, name: str | None = None) -> asyncio.Task: """Convert a given coroutine into a task and ensure it is executed.""" task = asyncio.create_task(coro, name=name) @@ -221,7 +215,7 @@ def add_feature(self, feature: LanguageFeature): self._features[feature_cls] = feature - def get_feature(self, feature_cls: Type[LF]) -> Optional[LF]: + def get_feature(self, feature_cls: type[LF]) -> LF | None: """Returns the requested language feature if it exists, otherwise it returns ``None``. @@ -232,7 +226,7 @@ def get_feature(self, feature_cls: Type[LF]) -> Optional[LF]: """ return self._features.get(feature_cls, None) # type: ignore - def clear_diagnostics(self, source: str, uri: Optional[Uri] = None) -> None: + def clear_diagnostics(self, source: str, uri: Uri | None = None) -> None: """Clear diagnostics from the given source. Parameters @@ -267,7 +261,7 @@ def add_diagnostics(self, source: str, uri: Uri, diagnostic: types.Diagnostic): self._diagnostics.setdefault(key, []).append(diagnostic) def set_diagnostics( - self, source: str, uri: Uri, diagnostics: List[types.Diagnostic] + self, source: str, uri: Uri, diagnostics: list[types.Diagnostic] ) -> None: """Set the diagnostics for the given source and uri. @@ -431,7 +425,7 @@ def append(self, item: types.Diagnostic): def _get_setup_arguments( server: EsbonioLanguageServer, setup: Callable, modname: str -) -> Optional[Dict[str, Any]]: +) -> dict[str, Any] | None: """Given a setup function, try to construct the collection of arguments to pass to it. """ diff --git a/lib/esbonio/esbonio/server/setup.py b/lib/esbonio/esbonio/server/setup.py index 38f10cb2..3b689447 100644 --- a/lib/esbonio/esbonio/server/setup.py +++ b/lib/esbonio/esbonio/server/setup.py @@ -4,12 +4,8 @@ import inspect import pathlib import typing +from collections.abc import Iterable from typing import Any -from typing import Dict -from typing import Iterable -from typing import List -from typing import Set -from typing import Type from lsprotocol import types @@ -20,7 +16,7 @@ def create_language_server( - server_cls: Type[EsbonioLanguageServer], modules: Iterable[str], *args, **kwargs + server_cls: type[EsbonioLanguageServer], modules: Iterable[str], *args, **kwargs ) -> EsbonioLanguageServer: """Create a new language server instance. @@ -123,7 +119,7 @@ async def on_workspace_diagnostic( ls: EsbonioLanguageServer, params: types.WorkspaceDiagnosticParams ): """Handle a ``workspace/diagnostic`` request.""" - diagnostics: Dict[Uri, List[types.Diagnostic]] = {} + diagnostics: dict[Uri, list[types.Diagnostic]] = {} for (_, uri), diags in ls._diagnostics.items(): diagnostics.setdefault(uri, []).extend(diags) @@ -180,7 +176,7 @@ async def on_did_change_watched_files( def _configure_completion(server: EsbonioLanguageServer): """Configuration completion handlers.""" - trigger_characters: Set[str] = set() + trigger_characters: set[str] = set() for _, feature in server: if feature.completion_trigger is None: @@ -271,7 +267,7 @@ async def call_features(ls: EsbonioLanguageServer, method: str, *args, **kwargs) async def gather_results(ls: EsbonioLanguageServer, method: str, *args, **kwargs): """Call all features, gathering all results into a list.""" - results: List[Any] = [] + results: list[Any] = [] for cls, feature in ls: try: impl = getattr(feature, method) diff --git a/lib/esbonio/esbonio/sphinx_agent/app.py b/lib/esbonio/esbonio/sphinx_agent/app.py index 6cbff3b6..f0518e42 100644 --- a/lib/esbonio/esbonio/sphinx_agent/app.py +++ b/lib/esbonio/esbonio/sphinx_agent/app.py @@ -16,11 +16,8 @@ if typing.TYPE_CHECKING: from typing import IO from typing import Any - from typing import List - from typing import Optional - from typing import Tuple - RoleDefinition = Tuple[str, Any, List[types.Role.TargetProvider]] + RoleDefinition = tuple[str, Any, list[types.Role.TargetProvider]] sphinx_logger = logging.getLogger(SPHINX_LOG_NAMESPACE) logger = sphinx_logger.getChild("esbonio") @@ -48,14 +45,14 @@ def __init__(self, dbpath: pathlib.Path, app: _Sphinx): self.db = Database(dbpath) self.log = DiagnosticFilter(app) - self._roles: List[RoleDefinition] = [] + self._roles: list[RoleDefinition] = [] """Roles captured during Sphinx startup.""" def add_role( self, name: str, role: Any, - target_providers: Optional[List[types.Role.TargetProvider]] = None, + target_providers: list[types.Role.TargetProvider] | None = None, ): """Register a role with esbonio. diff --git a/lib/esbonio/esbonio/sphinx_agent/config.py b/lib/esbonio/esbonio/sphinx_agent/config.py index 73507581..f4e3262e 100644 --- a/lib/esbonio/esbonio/sphinx_agent/config.py +++ b/lib/esbonio/esbonio/sphinx_agent/config.py @@ -1,10 +1,10 @@ +from __future__ import annotations + import dataclasses import inspect import pathlib import sys from typing import Any -from typing import Dict -from typing import List from typing import Literal from typing import Optional from typing import Union @@ -33,7 +33,7 @@ class SphinxConfig: doctree_dir: str """The directory to write doctrees into.""" - config_overrides: Dict[str, Any] = dataclasses.field(default_factory=dict) + config_overrides: dict[str, Any] = dataclasses.field(default_factory=dict) """Any overrides to configuration values.""" force_full_build: bool = dataclasses.field(default=False) @@ -51,7 +51,7 @@ class SphinxConfig: silent: bool = dataclasses.field(default=False) """Hide all Sphinx output.""" - tags: List[str] = dataclasses.field(default_factory=list) + tags: list[str] = dataclasses.field(default_factory=list) """Tags to enable during a build.""" verbosity: int = dataclasses.field(default=0) @@ -75,7 +75,7 @@ def parallel(self) -> int: return self.num_jobs @classmethod - def fromcli(cls, args: List[str]): + def fromcli(cls, args: list[str]): """Return the ``SphinxConfig`` instance that's equivalent to the given arguments. Parameters @@ -128,7 +128,7 @@ def fromcli(cls, args: List[str]): warning_is_error=sphinx_args.get("warningiserror", False), ) - def to_application_args(self) -> Dict[str, Any]: + def to_application_args(self) -> dict[str, Any]: """Convert this into the equivalent Sphinx application arguments.""" # On OSes like Fedora Silverblue, `/home` is symlinked to `/var/home`. This diff --git a/lib/esbonio/esbonio/sphinx_agent/database.py b/lib/esbonio/esbonio/sphinx_agent/database.py index db86c869..0d15424e 100644 --- a/lib/esbonio/esbonio/sphinx_agent/database.py +++ b/lib/esbonio/esbonio/sphinx_agent/database.py @@ -5,12 +5,7 @@ from dataclasses import dataclass from dataclasses import field from typing import Any -from typing import List from typing import Literal -from typing import Optional -from typing import Set -from typing import Tuple -from typing import Union class Database: @@ -19,7 +14,7 @@ class Column: name: str dtype: str notnull: bool = field(default=False) - default: Optional[Any] = field(default=None) + default: Any | None = field(default=None) pk: int = field(default=0) @property @@ -30,7 +25,7 @@ def definition(self): @dataclass class Table: name: str - columns: List[Database.Column] + columns: list[Database.Column] @property def create_statement(self): @@ -39,7 +34,7 @@ def create_statement(self): columns = ",".join([c.definition for c in self.columns]) return "".join([f"CREATE TABLE {self.name} (", columns, ");"]) - def __init__(self, dbpath: Union[pathlib.Path, Literal[":memory:"]]): + def __init__(self, dbpath: pathlib.Path | Literal[":memory:"]): self.path = dbpath if isinstance(self.path, pathlib.Path) and not self.path.parent.exists(): @@ -50,9 +45,9 @@ def __init__(self, dbpath: Union[pathlib.Path, Literal[":memory:"]]): # Ensure that Write Ahead Logging is enabled. self.db.execute("PRAGMA journal_mode(WAL)") - self._checked_tables: Set[str] = set() + self._checked_tables: set[str] = set() - def _get_table(self, name: str) -> Optional[Table]: + def _get_table(self, name: str) -> Table | None: """Get the table with the given name, if it exists.""" # TODO: SQLite does not seem to like '?' syntax in this statement... cursor = self.db.execute(f"PRAGMA table_info({name});") @@ -89,8 +84,8 @@ def clear_table(self, table: Table, **kwargs): # TODO: Is there a way to pass the table name as a '?' parameter? base_query = f"DELETE FROM {table.name}" # noqa: S608 - where: List[str] = [] - parameters: List[Any] = [] + where: list[str] = [] + parameters: list[Any] = [] for param, value in kwargs.items(): if value is None: @@ -134,7 +129,7 @@ def ensure_table(self, table: Table): self._checked_tables.add(table.name) - def insert_values(self, table: Table, values: List[Tuple]): + def insert_values(self, table: Table, values: list[tuple]): """Insert the given values into the given table.""" if len(values) == 0: diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py b/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py index 613cad2f..9ccdf8d4 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py @@ -1,14 +1,12 @@ +from __future__ import annotations + import inspect import logging import sys import traceback import typing from typing import Callable -from typing import Dict -from typing import List from typing import Optional -from typing import Tuple -from typing import Type import sphinx.application from sphinx import __version__ as __sphinx_version__ @@ -42,12 +40,12 @@ def __init__(self): self.app: Optional[Sphinx] = None """The sphinx application instance""" - self._content_overrides: Dict[Uri, str] = {} + self._content_overrides: dict[Uri, str] = {} """Holds any additional content to inject into a build.""" - self._handlers: Dict[str, Tuple[Type, Callable]] = self._register_handlers() + self._handlers: dict[str, tuple[type, Callable]] = self._register_handlers() - def get(self, method: str) -> Optional[Tuple[Type, Callable]]: + def get(self, method: str) -> Optional[tuple[type, Callable]]: """Return the handler for the given method - if possible. Parameters @@ -67,7 +65,7 @@ def get(self, method: str) -> Optional[Tuple[Type, Callable]]: """ return self._handlers.get(method) - def _register_handlers(self) -> Dict[str, Tuple[Type, Callable]]: + def _register_handlers(self) -> dict[str, tuple[type, Callable]]: """Return a map of all the handlers we provide. A handler @@ -85,7 +83,7 @@ class definition from the ``types`` module. representing the message body, the second element is the method which implements it. """ - handlers: Dict[str, Tuple[Type, Callable]] = {} + handlers: dict[str, tuple[type, Callable]] = {} for name in dir(self): method_func = getattr(self, name) @@ -135,7 +133,7 @@ def create_sphinx_app(self, request: types.CreateApplicationRequest): ) send_message(response) - def _cb_env_before_read_docs(self, app: Sphinx, env, docnames: List[str]): + def _cb_env_before_read_docs(self, app: Sphinx, env, docnames: list[str]): """Used to add additional documents to the "to build" list.""" is_building = set(docnames) diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py b/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py index 5eb69399..0b3606fa 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py @@ -1,8 +1,8 @@ +from __future__ import annotations + import importlib import inspect -from typing import List from typing import Optional -from typing import Type from docutils.parsers.rst import Directive from docutils.parsers.rst import directives as docutils_directives @@ -22,14 +22,14 @@ ) -def get_impl_name(directive: Type[Directive]) -> str: +def get_impl_name(directive: type[Directive]) -> str: try: return f"{directive.__module__}.{directive.__name__}" except AttributeError: return f"{directive.__module__}.{directive.__class__.__name__}" -def get_impl_location(impl: Type[Directive]) -> Optional[str]: +def get_impl_location(impl: type[Directive]) -> Optional[str]: """Get the implementation location of the given directive""" try: @@ -66,7 +66,7 @@ def index_directives(app: Sphinx): first time. """ - directives: List[types.Directive] = [] + directives: list[types.Directive] = [] ignored_directives = {"restructuredtext-test-directive"} found_directives = { diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py b/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py index 07d4b58e..d5abcd61 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py @@ -10,12 +10,6 @@ from ..util import as_json if typing.TYPE_CHECKING: - from typing import Dict - from typing import List - from typing import Optional - from typing import Set - from typing import Tuple - from sphinx.domains import Domain from sphinx.util.typing import Inventory @@ -50,9 +44,7 @@ class DomainObjects: """Discovers and indexes domain objects.""" def __init__(self, app: Sphinx): - self._info: Dict[ - Tuple[str, str, str, str], Tuple[Optional[str], Optional[str]] - ] = {} + self._info: dict[tuple[str, str, str, str], tuple[str | None, str | None]] = {} # Needs to run late, but before the handler in ./roles.py app.connect("builder-inited", self.init_db, priority=998) @@ -139,7 +131,7 @@ def object_defined( self._info[key] = (description, location) -def index_domain(app: Sphinx, domain: Domain, projects: Optional[List[str]]): +def index_domain(app: Sphinx, domain: Domain, projects: list[str] | None): """Index the roles in the given domain. Parameters @@ -153,7 +145,7 @@ def index_domain(app: Sphinx, domain: Domain, projects: Optional[List[str]]): projects The list of known intersphinx projects """ - target_types: Dict[str, Set[str]] = {} + target_types: dict[str, set[str]] = {} for obj_name, item_type in domain.object_types.items(): for role_name in item_type.roles: @@ -190,7 +182,7 @@ def index_domain(app: Sphinx, domain: Domain, projects: Optional[List[str]]): ) -def index_intersphinx_projects(app: Sphinx) -> List[Tuple[str, str, str, str]]: +def index_intersphinx_projects(app: Sphinx) -> list[tuple[str, str, str, str]]: """Index all the projects known to intersphinx. Parameters @@ -207,7 +199,7 @@ def index_intersphinx_projects(app: Sphinx) -> List[Tuple[str, str, str, str]]: app.esbonio.db.ensure_table(PROJECTS_TABLE) app.esbonio.db.clear_table(PROJECTS_TABLE) - projects: List[Tuple[str, str, str, str]] = [] + projects: list[tuple[str, str, str, str]] = [] objects = [] mapping = getattr(app.config, "intersphinx_mapping", {}) diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/files.py b/lib/esbonio/esbonio/sphinx_agent/handlers/files.py index eeab0909..4421690c 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/files.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/files.py @@ -10,9 +10,6 @@ if typing.TYPE_CHECKING: from typing import Any - from typing import List - from typing import Optional - from typing import Tuple from sphinx.config import Config @@ -47,7 +44,7 @@ def init_db(app: Sphinx, config: Config): app.esbonio.db.ensure_table(CONFIG_TABLE) -def value_to_db(name: str, item: Any) -> Tuple[str, str, Any]: +def value_to_db(name: str, item: Any) -> tuple[str, str, Any]: """Convert a single value to its DB representation""" try: @@ -62,7 +59,7 @@ def dump_config(app: Sphinx, *args): it.""" app.esbonio.db.clear_table(CONFIG_TABLE) - values: List[Tuple[str, str, str]] = [] + values: list[tuple[str, str, str]] = [] config = app.config.__getstate__() # For some reason, most config values are nested under 'values' @@ -89,13 +86,13 @@ def dump_config(app: Sphinx, *args): app.esbonio.db.insert_values(CONFIG_TABLE, values) -def build_file_mapping(app: Sphinx, exc: Optional[Exception]): +def build_file_mapping(app: Sphinx, exc: Exception | None): """Given a Sphinx application, return a mapping of all known source files to their corresponding output files.""" env = app.env builder = app.builder - files: List[Tuple[str, str, str]] = [] + files: list[tuple[str, str, str]] = [] for docname in env.found_docs: uri = Uri.for_file(env.doc2path(docname)).resolve() diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py b/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py index 1f96d33b..ad5e3c93 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py @@ -1,6 +1,5 @@ import inspect from typing import Any -from typing import Dict from typing import Optional from docutils.parsers.rst import roles as docutils_roles @@ -55,7 +54,7 @@ def get_impl_location(impl: Any) -> Optional[types.Location]: def index_roles(app: Sphinx): """Index all the roles that are available to this app.""" - roles: Dict[str, types.Role] = {} + roles: dict[str, types.Role] = {} # Process the roles registered through Sphinx for name, impl, providers in app.esbonio._roles: diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py b/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py index 258ef294..a91c9668 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py @@ -1,7 +1,6 @@ import logging import typing from typing import IO -from typing import List from docutils import nodes from docutils.core import Publisher @@ -212,13 +211,13 @@ class SymbolVisitor(nodes.NodeVisitor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.symbols: List[types.Symbol] = [] + self.symbols: list[types.Symbol] = [] """Holds the symbols for the document""" - self.parents: List[int] = [] + self.parents: list[int] = [] """Holds the ids of the current hierarchy.""" - self.order: List[int] = [0] + self.order: list[int] = [0] """Holds the current position at each level of the hierarchy.""" def push_symbol(self, name: str, kind: int, range_: types.Range, detail: str = ""): diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/webview.py b/lib/esbonio/esbonio/sphinx_agent/handlers/webview.py index 28092141..33aff9e0 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/webview.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/webview.py @@ -11,9 +11,6 @@ from ..log import source_to_uri_and_linum if typing.TYPE_CHECKING: - from typing import Dict - from typing import Tuple - from sphinx.application import Sphinx @@ -42,7 +39,7 @@ class source_locations(nodes.General, nodes.Element): def visit_source_locations(self, node): - source_index: Dict[int, Tuple[str, int]] = node["index"] + source_index: dict[int, tuple[str, int]] = node["index"] self.body.append('