Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
PYTHON_VERSION: ["3.10"]
PYTHON_VERSION: ["3.9"]
timeout-minutes: 10
permissions:
contents: read
Expand All @@ -30,7 +30,9 @@ jobs:
restore-keys: ${{ runner.os }}-${{ matrix.PYTHON_VERSION }}-pip-
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-depth: 0
fetch-tags: true
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.PYTHON_VERSION }}
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
# TODO: check with Python 3, but need to fix the
# errors first
python-version: '3.8'
python-version: '3.9'
architecture: 'x64'
- run: python -m pip install --upgrade pip setuptools jsonschema
# If we don't install pycodestyle, pylint will throw an unused-argument error in pylsp/plugins/pycodestyle_lint.py:72
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
PYTHON_VERSION: ['3.10', '3.9', '3.8']
PYTHON_VERSION: ['3.11', '3.10', '3.9']
timeout-minutes: 10
steps:
- uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
PYTHON_VERSION: ['3.10', '3.9', '3.8']
PYTHON_VERSION: ['3.11', '3.10', '3.9']
timeout-minutes: 10
steps:
- uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
PYTHON_VERSION: ['3.10', '3.9', '3.8']
PYTHON_VERSION: ['3.11', '3.10', '3.9']
timeout-minutes: 10
steps:
- uses: actions/cache@v4
Expand Down
1 change: 1 addition & 0 deletions .well-known/funding-manifest-urls
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://www.spyder-ide.org/funding.json
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# History of changes

## Version 1.13.1 (2025/08/26)

### Pull Requests Merged

* [PR 667](https://github.com/python-lsp/python-lsp-server/pull/667) - Use PyQt6 for testing, by [@WhyNotHugo](https://github.com/WhyNotHugo)
* [PR 666](https://github.com/python-lsp/python-lsp-server/pull/666) - Expose a shutdown hook, by [@dlax](https://github.com/dlax)
* [PR 663](https://github.com/python-lsp/python-lsp-server/pull/663) - Copy `LAST_JEDI_COMPLETIONS` to cell document so that `completionItem/resolve` will work, by [@hjr265](https://github.com/hjr265)

In this release 3 pull requests were closed.

----

## Version 1.13.0 (2025/07/07)

### New features
* Format signatures in docstrings.
* Add support for type definition.
* Send websocket payload using a queue.
* Fix getting symbols with inline comments that include the `import` word.
* Drop support for Python 3.8

### Issues Closed

* [Issue 640](https://github.com/python-lsp/python-lsp-server/issues/640) - Should we add `py.typed` marker? ([PR 641](https://github.com/python-lsp/python-lsp-server/pull/641) by [@krassowski](https://github.com/krassowski))
* [Issue 630](https://github.com/python-lsp/python-lsp-server/issues/630) - Formatting of signatures in docstrings
* [Issue 627](https://github.com/python-lsp/python-lsp-server/issues/627) - Do not call str.splitlines() twice in the same function
* [Issue 97](https://github.com/python-lsp/python-lsp-server/issues/97) - Failed to run lsp-goto-type-definition and lsp-goto-implementation.

In this release 4 issues were closed.

### Pull Requests Merged

* [PR 656](https://github.com/python-lsp/python-lsp-server/pull/656) - Add space between punctuation and next sentence, by [@spenserblack](https://github.com/spenserblack)
* [PR 650](https://github.com/python-lsp/python-lsp-server/pull/650) - Drop Python 3.8, add Python 3.11 to CI and run `pyupgrade`, by [@krassowski](https://github.com/krassowski)
* [PR 646](https://github.com/python-lsp/python-lsp-server/pull/646) - Enforce `setuptools` 69 or newer to ensure `py.typed` marker gets included, by [@krassowski](https://github.com/krassowski)
* [PR 645](https://github.com/python-lsp/python-lsp-server/pull/645) - Add support for type definition, by [@Hoblovski](https://github.com/Hoblovski)
* [PR 641](https://github.com/python-lsp/python-lsp-server/pull/641) - Add `py.typed` marker to `pylsp` imports to be analysed with `mypy`, by [@krassowski](https://github.com/krassowski) ([640](https://github.com/python-lsp/python-lsp-server/issues/640))
* [PR 639](https://github.com/python-lsp/python-lsp-server/pull/639) - Fix inline comments that include text with `import`, by [@jsbautista](https://github.com/jsbautista)
* [PR 633](https://github.com/python-lsp/python-lsp-server/pull/633) - Send websocket payload using a queue, by [@Raekkeri](https://github.com/Raekkeri)
* [PR 631](https://github.com/python-lsp/python-lsp-server/pull/631) - Allow to format signatures in docstrings, by [@krassowski](https://github.com/krassowski)
* [PR 628](https://github.com/python-lsp/python-lsp-server/pull/628) - Do not call `str.splitlines()` twice in the same function., by [@fukanchik](https://github.com/fukanchik)

In this release 9 pull requests were closed.

----

## Version 1.12.2 (2025/02/07)

### Pull Requests Merged
Expand Down
3 changes: 3 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This server can be configured using the `workspace/didChangeConfiguration` metho
| `pylsp.plugins.jedi_symbols.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_symbols.all_scopes` | `boolean` | If True lists the names of all scopes instead of only the module namespace. | `true` |
| `pylsp.plugins.jedi_symbols.include_import_symbols` | `boolean` | If True includes symbols imported from other libraries. | `true` |
| `pylsp.plugins.jedi_type_definition.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.mccabe.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.mccabe.threshold` | `integer` | The minimum threshold that triggers warnings about cyclomatic complexity. | `15` |
| `pylsp.plugins.preload.enabled` | `boolean` | Enable or disable the plugin. | `true` |
Expand Down Expand Up @@ -75,5 +76,7 @@ This server can be configured using the `workspace/didChangeConfiguration` metho
| `pylsp.plugins.yapf.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.rope.extensionModules` | `string` | Builtin and c-extension modules that are allowed to be imported and inspected by rope. | `null` |
| `pylsp.rope.ropeFolder` | `array` of unique `string` items | The name of the folder in which rope stores project configurations and data. Pass `null` for not using such a folder at all. | `null` |
| `pylsp.signature.formatter` | `string` (one of: `'black'`, `'ruff'`, `None`) | Formatter to use for reformatting signatures in docstrings. | `"black"` |
| `pylsp.signature.line_length` | `number` | Maximum line length in signatures. | `88` |

This documentation was generated from `pylsp/config/schema.json`. Please do not edit this file directly.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

[![image](https://github.com/python-ls/python-ls/workflows/Linux%20tests/badge.svg)](https://github.com/python-ls/python-ls/actions?query=workflow%3A%22Linux+tests%22) [![image](https://github.com/python-ls/python-ls/workflows/Mac%20tests/badge.svg)](https://github.com/python-ls/python-ls/actions?query=workflow%3A%22Mac+tests%22) [![image](https://github.com/python-ls/python-ls/workflows/Windows%20tests/badge.svg)](https://github.com/python-ls/python-ls/actions?query=workflow%3A%22Windows+tests%22) [![image](https://img.shields.io/github/license/python-ls/python-ls.svg)](https://github.com/python-ls/python-ls/blob/master/LICENSE)

A Python 3.8+ implementation of the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol).
(Note: versions <1.4 should still work with Python 3.6)
A Python 3.9+ implementation of the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol).

## Installation

Expand Down
17 changes: 17 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Security Policy


## Supported Versions

We normally support only the most recently released version with bug fixes, security updates and compatibility improvements.


## Reporting a Vulnerability

If you believe you've discovered a security vulnerability in this project, please open a new security advisory with [our GitHub repo's private vulnerability reporting](https://github.com/python-lsp/python-lsp-server/security/advisories/new).
Please be sure to carefully document the vulnerability, including a summary, describing the impacts, identifying the line(s) of code affected, stating the conditions under which it is exploitable and including a minimal reproducible test case.
Further information and advice or patches on how to mitigate it is always welcome.
You can usually expect to hear back within 1 week, at which point we'll inform you of our evaluation of the vulnerability and what steps we plan to take, and will reach out if we need further clarification from you.
We'll discuss and update the advisory thread, and are happy to update you on its status should you further inquire.
While this is a volunteer project and we don't have financial compensation to offer, we can certainly publicly thank and credit you for your help if you would like.
Thanks!
8 changes: 4 additions & 4 deletions pylsp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
start_ws_lang_server,
)

LOG_FORMAT = "%(asctime)s {0} - %(levelname)s - %(name)s - %(message)s".format(
LOG_FORMAT = "%(asctime)s {} - %(levelname)s - %(name)s - %(message)s".format(
time.localtime().tm_zone
)

Expand All @@ -40,7 +40,7 @@ def add_arguments(parser) -> None:
"--check-parent-process",
action="store_true",
help="Check whether parent process is still alive using os.kill(ppid, 0) "
"and auto shut down language server process when parent process is not alive."
"and auto shut down language server process when parent process is not alive. "
"Note that this may not work on a Windows machine.",
)

Expand All @@ -50,7 +50,7 @@ def add_arguments(parser) -> None:
)
log_group.add_argument(
"--log-file",
help="Redirect logs to the given file instead of writing to stderr."
help="Redirect logs to the given file instead of writing to stderr. "
"Has no effect if used with --log-config.",
)

Expand Down Expand Up @@ -100,7 +100,7 @@ def _configure_logger(verbose=0, log_config=None, log_file=None) -> None:
root_logger = logging.root

if log_config:
with open(log_config, "r", encoding="utf-8") as f:
with open(log_config, encoding="utf-8") as f:
logging.config.dictConfig(json.load(f))
else:
formatter = logging.Formatter(LOG_FORMAT)
Expand Down
105 changes: 99 additions & 6 deletions pylsp/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import os
import pathlib
import re
import subprocess
import sys
import threading
import time
from typing import List, Optional
from typing import Optional

import docstring_to_markdown
import jedi
Expand Down Expand Up @@ -57,7 +59,7 @@ def run():


def throttle(seconds=1):
"""Throttles calls to a function evey `seconds` seconds."""
"""Throttles calls to a function every `seconds` seconds."""

def decorator(func):
@functools.wraps(func)
Expand All @@ -78,7 +80,7 @@ def find_parents(root, path, names):

Args:
path (str): The file path to start searching up from.
names (List[str]): The file/directory names to look for.
names (list[str]): The file/directory names to look for.
root (str): The directory at which to stop recursing upwards.

Note:
Expand Down Expand Up @@ -198,7 +200,7 @@ def wrap_signature(signature):
SERVER_SUPPORTED_MARKUP_KINDS = {"markdown", "plaintext"}


def choose_markup_kind(client_supported_markup_kinds: List[str]):
def choose_markup_kind(client_supported_markup_kinds: list[str]):
"""Choose a markup kind supported by both client and the server.

This gives priority to the markup kinds provided earlier on the client preference list.
Expand All @@ -209,8 +211,96 @@ def choose_markup_kind(client_supported_markup_kinds: List[str]):
return "markdown"


class Formatter:
command: list[str]

@property
def is_installed(self) -> bool:
"""Returns whether formatter is available"""
if not hasattr(self, "_is_installed"):
self._is_installed = self._is_available_via_cli()
return self._is_installed

def format(self, code: str, line_length: int) -> str:
"""Formats code"""
return subprocess.check_output(
[
sys.executable,
"-m",
*self.command,
"--line-length",
str(line_length),
"-",
],
input=code,
text=True,
).strip()

def _is_available_via_cli(self) -> bool:
try:
subprocess.check_output(
[
sys.executable,
"-m",
*self.command,
"--help",
],
)
return True
except subprocess.CalledProcessError:
return False


class RuffFormatter(Formatter):
command = ["ruff", "format"]


class BlackFormatter(Formatter):
command = ["black"]


formatters = {"ruff": RuffFormatter(), "black": BlackFormatter()}


def format_signature(signature: str, config: dict, signature_formatter: str) -> str:
"""Formats signature using ruff or black if either is available."""
as_func = f"def {signature.strip()}:\n pass"
line_length = config.get("line_length", 88)
formatter = formatters[signature_formatter]
if formatter.is_installed:
try:
return (
formatter.format(as_func, line_length=line_length)
.removeprefix("def ")
.removesuffix(":\n pass")
)
except subprocess.CalledProcessError as e:
log.warning("Signature formatter failed %s", e)
else:
log.warning(
"Formatter %s was requested but it does not appear to be installed",
signature_formatter,
)
return signature


def convert_signatures_to_markdown(signatures: list[str], config: dict) -> str:
signature_formatter = config.get("formatter", "black")
if signature_formatter:
signatures = [
format_signature(
signature, signature_formatter=signature_formatter, config=config
)
for signature in signatures
]
return wrap_signature("\n".join(signatures))


def format_docstring(
contents: str, markup_kind: str, signatures: Optional[List[str]] = None
contents: str,
markup_kind: str,
signatures: Optional[list[str]] = None,
signature_config: Optional[dict] = None,
):
"""Transform the provided docstring into a MarkupContent object.

Expand All @@ -232,7 +322,10 @@ def format_docstring(
value = escape_markdown(contents)

if signatures:
value = wrap_signature("\n".join(signatures)) + "\n\n" + value
wrapped_signatures = convert_signatures_to_markdown(
signatures, config=signature_config or {}
)
value = wrapped_signatures + "\n\n" + value

return {"kind": "markdown", "value": value}
value = contents
Expand Down
5 changes: 3 additions & 2 deletions pylsp/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

import logging
import sys
from collections.abc import Mapping, Sequence
from functools import lru_cache
from typing import List, Mapping, Sequence, Union
from typing import Union

import pluggy
from pluggy._hooks import HookImpl
Expand Down Expand Up @@ -32,7 +33,7 @@ def _hookexec(
methods: Sequence[HookImpl],
kwargs: Mapping[str, object],
firstresult: bool,
) -> Union[object, List[object]]:
) -> Union[object, list[object]]:
# called from all hookcaller instances.
# enable_tracing will set its own wrapping function at self._inner_hookexec
try:
Expand Down
23 changes: 23 additions & 0 deletions pylsp/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@
"default": true,
"description": "If True includes symbols imported from other libraries."
},
"pylsp.plugins.jedi_type_definition.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.mccabe.enabled": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -511,6 +516,24 @@
},
"uniqueItems": true,
"description": "The name of the folder in which rope stores project configurations and data. Pass `null` for not using such a folder at all."
},
"pylsp.signature.formatter": {
"type": [
"string",
"null"
],
"enum": [
"black",
"ruff",
null
],
"default": "black",
"description": "Formatter to use for reformatting signatures in docstrings."
},
"pylsp.signature.line_length": {
"type": "number",
"default": 88,
"description": "Maximum line length in signatures."
}
}
}
Loading