Skip to content

Restore the JSON schema, add human-readable configuration #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 9, 2021
Merged
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
8 changes: 7 additions & 1 deletion .github/workflows/static.yml
Original file line number Diff line number Diff line change
@@ -30,11 +30,17 @@ jobs:
# errors first
python-version: '3.6'
architecture: 'x64'
- run: python -m pip install --upgrade pip setuptools
- run: python -m pip install --upgrade pip setuptools jsonschema
- run: pip install -e .[pylint,pycodestyle,pyflakes]
- name: Pylint checks
run: pylint pylsp test
- name: Code style checks
run: pycodestyle pylsp test
- name: Pyflakes checks
run: pyflakes pylsp test
- name: Validate JSON schema
run: jsonschema pylsp/config/schema.json
- name: Ensure JSON schema and Markdown docs are in sync
run: |
python scripts/jsonschema2md.py pylsp/config/schema.json EXPECTED_CONFIGURATION.md
diff EXPECTED_CONFIGURATION.md CONFIGURATION.md
50 changes: 50 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Python Language Server Configuration
This server can be configured using `workspace/didChangeConfiguration` method. Each configuration option is described below:

| **Configuration Key** | **Type** | **Description** | **Default**
|----|----|----|----|
| `pylsp.configurationSources` | `array` of unique `string` items | List of configuration sources to use. | `["pycodestyle"]` |
| `pylsp.plugins.jedi.extra_paths` | `array` | Define extra paths for jedi.Script. | `[]` |
| `pylsp.plugins.jedi.env_vars` | `object` | Define environment variables for jedi.Script and Jedi.names. | `null` |
| `pylsp.plugins.jedi.environment` | `string` | Define environment for jedi.Script and Jedi.names. | `null` |
| `pylsp.plugins.jedi_completion.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_completion.include_params` | `boolean` | Auto-completes methods and classes with tabstops for each parameter. | `true` |
| `pylsp.plugins.jedi_completion.include_class_objects` | `boolean` | Adds class objects as a separate completion item. | `true` |
| `pylsp.plugins.jedi_completion.fuzzy` | `boolean` | Enable fuzzy when requesting autocomplete. | `false` |
| `pylsp.plugins.jedi_definition.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_definition.follow_imports` | `boolean` | The goto call will follow imports. | `true` |
| `pylsp.plugins.jedi_definition.follow_builtin_imports` | `boolean` | If follow_imports is True will decide if it follow builtin imports. | `true` |
| `pylsp.plugins.jedi_hover.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_references.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_signature_help.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `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.mccabe.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.mccabe.threshold` | `number` | The minimum threshold that triggers warnings about cyclomatic complexity. | `15` |
| `pylsp.plugins.preload.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.preload.modules` | `array` of unique `string` items | List of modules to import on startup | `null` |
| `pylsp.plugins.pycodestyle.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.pycodestyle.exclude` | `array` of unique `string` items | Exclude files or directories which match these patterns. | `null` |
| `pylsp.plugins.pycodestyle.filename` | `array` of unique `string` items | When parsing directories, only check filenames matching these patterns. | `null` |
| `pylsp.plugins.pycodestyle.select` | `array` of unique `string` items | Select errors and warnings | `null` |
| `pylsp.plugins.pycodestyle.ignore` | `array` of unique `string` items | Ignore errors and warnings | `null` |
| `pylsp.plugins.pycodestyle.hangClosing` | `boolean` | Hang closing bracket instead of matching indentation of opening bracket's line. | `null` |
| `pylsp.plugins.pycodestyle.maxLineLength` | `number` | Set maximum allowed line length. | `null` |
| `pylsp.plugins.pydocstyle.enabled` | `boolean` | Enable or disable the plugin. | `false` |
| `pylsp.plugins.pydocstyle.convention` | `string` | Choose the basic list of checked errors by specifying an existing convention. | `null` |
| `pylsp.plugins.pydocstyle.addIgnore` | `array` of unique `string` items | Ignore errors and warnings in addition to the specified convention. | `null` |
| `pylsp.plugins.pydocstyle.addSelect` | `array` of unique `string` items | Select errors and warnings in addition to the specified convention. | `null` |
| `pylsp.plugins.pydocstyle.ignore` | `array` of unique `string` items | Ignore errors and warnings | `null` |
| `pylsp.plugins.pydocstyle.select` | `array` of unique `string` items | Select errors and warnings | `null` |
| `pylsp.plugins.pydocstyle.match` | `string` | Check only files that exactly match the given regular expression; default is to match files that don't start with 'test_' but end with '.py'. | `"(?!test_).*\\.py"` |
| `pylsp.plugins.pydocstyle.matchDir` | `string` | Search only dirs that exactly match the given regular expression; default is to match dirs which do not begin with a dot. | `"[^\\.].*"` |
| `pylsp.plugins.pyflakes.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.pylint.enabled` | `boolean` | Enable or disable the plugin. | `false` |
| `pylsp.plugins.pylint.args` | `array` of non-unique `string` items | Arguments to pass to pylint. | `null` |
| `pylsp.plugins.pylint.executable` | `string` | Executable to run pylint with. Enabling this will run pylint on unsaved files via stdin. Can slow down workflow. Only works with python3. | `null` |
| `pylsp.plugins.rope_completion.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `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` |

This documentation was generated from `pylsp/config/schema.json`. Please do not edit this file directly.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -60,11 +60,13 @@ Configuration is loaded from zero or more configuration sources. Currently impl

The default configuration source is pycodestyle. Change the `pylsp.configurationSources` setting to `['flake8']` in order to respect flake8 configuration instead.

Overall configuration is computed first from user configuration (in home directory), overridden by configuration passed in by the language client, and then overriden by configuration discovered in the workspace.
Overall configuration is computed first from user configuration (in home directory), overridden by configuration passed in by the language client, and then overridden by configuration discovered in the workspace.

To enable pydocstyle for linting docstrings add the following setting in your LSP configuration:
`"pylsp.plugins.pydocstyle.enabled": true`

All configuration options are described in [`CONFIGURATION.md`](https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md).

## LSP Server Features

* Auto Completion
@@ -86,6 +88,12 @@ To run the test suite:
pip install .[test] && pytest
```

After adding configuration options to `schema.json`, refresh the `CONFIGURATION.md` file with

```
python scripts/jsonschema2md.py pylsp/config/schema.json CONFIGURATION.md
```

## License

This project is made available under the MIT License.
276 changes: 276 additions & 0 deletions pylsp/config/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Python Language Server Configuration",
"description": "This server can be configured using `workspace/didChangeConfiguration` method. Each configuration option is described below:",
"type": "object",
"properties": {
"pylsp.configurationSources": {
"type": "array",
"default": ["pycodestyle"],
"description": "List of configuration sources to use.",
"items": {
"type": "string",
"enum": ["pycodestyle", "pyflakes"]
},
"uniqueItems": true
},
"pylsp.plugins.jedi.extra_paths": {
"type": "array",
"default": [],
"description": "Define extra paths for jedi.Script."
},
"pylsp.plugins.jedi.env_vars": {
"type": "object",
"default": null,
"description": "Define environment variables for jedi.Script and Jedi.names."
},
"pylsp.plugins.jedi.environment": {
"type": "string",
"default": null,
"description": "Define environment for jedi.Script and Jedi.names."
},
"pylsp.plugins.jedi_completion.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_completion.include_params": {
"type": "boolean",
"default": true,
"description": "Auto-completes methods and classes with tabstops for each parameter."
},
"pylsp.plugins.jedi_completion.include_class_objects": {
"type": "boolean",
"default": true,
"description": "Adds class objects as a separate completion item."
},
"pylsp.plugins.jedi_completion.fuzzy": {
"type": "boolean",
"default": false,
"description": "Enable fuzzy when requesting autocomplete."
},
"pylsp.plugins.jedi_definition.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_definition.follow_imports": {
"type": "boolean",
"default": true,
"description": "The goto call will follow imports."
},
"pylsp.plugins.jedi_definition.follow_builtin_imports": {
"type": "boolean",
"default": true,
"description": "If follow_imports is True will decide if it follow builtin imports."
},
"pylsp.plugins.jedi_hover.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_references.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_signature_help.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_symbols.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_symbols.all_scopes": {
"type": "boolean",
"default": true,
"description": "If True lists the names of all scopes instead of only the module namespace."
},
"pylsp.plugins.mccabe.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.mccabe.threshold": {
"type": "number",
"default": 15,
"description": "The minimum threshold that triggers warnings about cyclomatic complexity."
},
"pylsp.plugins.preload.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.preload.modules": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "List of modules to import on startup"
},
"pylsp.plugins.pycodestyle.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.pycodestyle.exclude": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Exclude files or directories which match these patterns."
},
"pylsp.plugins.pycodestyle.filename": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "When parsing directories, only check filenames matching these patterns."
},
"pylsp.plugins.pycodestyle.select": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Select errors and warnings"
},
"pylsp.plugins.pycodestyle.ignore": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Ignore errors and warnings"
},
"pylsp.plugins.pycodestyle.hangClosing": {
"type": "boolean",
"default": null,
"description": "Hang closing bracket instead of matching indentation of opening bracket's line."
},
"pylsp.plugins.pycodestyle.maxLineLength": {
"type": "number",
"default": null,
"description": "Set maximum allowed line length."
},
"pylsp.plugins.pydocstyle.enabled": {
"type": "boolean",
"default": false,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.pydocstyle.convention": {
"type": "string",
"default": null,
"enum": [
"pep257",
"numpy"
],
"description": "Choose the basic list of checked errors by specifying an existing convention."
},
"pylsp.plugins.pydocstyle.addIgnore": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Ignore errors and warnings in addition to the specified convention."
},
"pylsp.plugins.pydocstyle.addSelect": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Select errors and warnings in addition to the specified convention."
},
"pylsp.plugins.pydocstyle.ignore": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Ignore errors and warnings"
},
"pylsp.plugins.pydocstyle.select": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": true,
"description": "Select errors and warnings"
},
"pylsp.plugins.pydocstyle.match": {
"type": "string",
"default": "(?!test_).*\\.py",
"description": "Check only files that exactly match the given regular expression; default is to match files that don't start with 'test_' but end with '.py'."
},
"pylsp.plugins.pydocstyle.matchDir": {
"type": "string",
"default": "[^\\.].*",
"description": "Search only dirs that exactly match the given regular expression; default is to match dirs which do not begin with a dot."
},
"pylsp.plugins.pyflakes.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.pylint.enabled": {
"type": "boolean",
"default": false,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.pylint.args": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"uniqueItems": false,
"description": "Arguments to pass to pylint."
},
"pylsp.plugins.pylint.executable": {
"type": "string",
"default": null,
"description": "Executable to run pylint with. Enabling this will run pylint on unsaved files via stdin. Can slow down workflow. Only works with python3."
},
"pylsp.plugins.rope_completion.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.yapf.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.rope.extensionModules": {
"type": "string",
"default": null,
"description": "Builtin and c-extension modules that are allowed to be imported and inspected by rope."
},
"pylsp.rope.ropeFolder": {
"type": "array",
"default": null,
"items": {
"type": "string"
},
"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."
}
}
}
2 changes: 1 addition & 1 deletion pylsp/plugins/jedi_completion.py
Original file line number Diff line number Diff line change
@@ -114,7 +114,7 @@ def use_snippets(document, position):
break
if '(' in act_lines[-1].strip():
last_character = ')'
code = '\n'.join(act_lines).split(';')[-1].strip() + last_character
code = '\n'.join(act_lines).rsplit(';', maxsplit=1)[-1].strip() + last_character
tokens = parso.parse(code)
expr_type = tokens.children[0].type
return (expr_type not in _IMPORTS and
6 changes: 2 additions & 4 deletions pylsp/python_lsp.py
Original file line number Diff line number Diff line change
@@ -363,8 +363,7 @@ def m_text_document__signature_help(self, textDocument=None, position=None, **_k

def m_workspace__did_change_configuration(self, settings=None):
self.config.update((settings or {}).get('pylsp', {}))
for workspace_uri in self.workspaces:
workspace = self.workspaces[workspace_uri]
for workspace in self.workspaces.values():
workspace.update_config(settings)
for doc_uri in workspace.documents:
self.lint(doc_uri, is_saved=False)
@@ -433,8 +432,7 @@ def m_workspace__did_change_watched_files(self, changes=None, **_kwargs):
# Only externally changed python files and lint configs may result in changed diagnostics.
return

for workspace_uri in self.workspaces:
workspace = self.workspaces[workspace_uri]
for workspace in self.workspaces.values():
for doc_uri in workspace.documents:
# Changes in doc_uri are already handled by m_text_document__did_save
if doc_uri not in changed_py_files:
81 changes: 81 additions & 0 deletions scripts/jsonschema2md.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import json
import sys
from argparse import ArgumentParser, FileType


def describe_array(prop: dict) -> str:
extra = ""
if "items" in prop:
unique_qualifier = ""
if "uniqueItems" in prop:
unique_qualifier = "unique" if prop["uniqueItems"] else "non-unique"
item_type = describe_type(prop["items"])
extra += f" of {unique_qualifier} {item_type} items"
return extra


def describe_number(prop: dict) -> str:
extra = []
if "minimum" in prop:
extra.append(f">= {prop['minimum']}")
if "maximum" in prop:
extra.append(f"<= {prop['maximum']}")
return ",".join(extra)


EXTRA_DESCRIPTORS = {
"array": describe_array,
"number": describe_number,
}


def describe_type(prop: dict) -> str:
prop_type = prop["type"]
label = f"`{prop_type}`"
if prop_type in EXTRA_DESCRIPTORS:
label += " " + EXTRA_DESCRIPTORS[prop_type](prop)
if "enum" in prop:
allowed_values = [f"`{value}`" for value in prop["enum"]]
label += "one of: " + ", ".join(allowed_values)
return label


def convert_schema(schema: dict, source: str = None) -> str:
lines = [
f"# {schema['title']}",
schema["description"],
"",
"| **Configuration Key** | **Type** | **Description** | **Default** ",
"|----|----|----|----|",
]
for key, prop in schema["properties"].items():
description = prop.get("description", "")
default = json.dumps(prop.get("default", ""))
lines.append(
f"| `{key}` | {describe_type(prop)} | {description} | `{default}` |"
)

if source:
lines.append(
f"\nThis documentation was generated from `{source}`."
" Please do not edit this file directly."
)

# ensure empty line at the end
lines.append("")

return "\n".join(lines)


def main(argv):
parser = ArgumentParser()
parser.add_argument("schema", type=FileType())
parser.add_argument("markdown", type=FileType("w+"), default=sys.stdout)
arguments = parser.parse_args(argv[1:])
schema = json.loads(arguments.schema.read())
markdown = convert_schema(schema, source=arguments.schema.name)
arguments.markdown.write(markdown)


if __name__ == "__main__":
main(sys.argv)
2 changes: 1 addition & 1 deletion test/test_utils.py
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@
# Copyright 2021- Python Language Server Contributors.

import time
from unittest import mock

import unittest.mock as mock
from flaky import flaky

from pylsp import _utils