deepnote-python-lsp-server 0.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. deepnote_python_lsp_server-0.0.0.dist-info/METADATA +263 -0
  2. deepnote_python_lsp_server-0.0.0.dist-info/RECORD +43 -0
  3. deepnote_python_lsp_server-0.0.0.dist-info/WHEEL +6 -0
  4. deepnote_python_lsp_server-0.0.0.dist-info/entry_points.txt +24 -0
  5. deepnote_python_lsp_server-0.0.0.dist-info/licenses/LICENSE +22 -0
  6. deepnote_python_lsp_server-0.0.0.dist-info/top_level.txt +1 -0
  7. pylsp/__init__.py +33 -0
  8. pylsp/__main__.py +132 -0
  9. pylsp/_utils.py +325 -0
  10. pylsp/config/__init__.py +2 -0
  11. pylsp/config/config.py +202 -0
  12. pylsp/config/flake8_conf.py +64 -0
  13. pylsp/config/pycodestyle_conf.py +34 -0
  14. pylsp/config/source.py +88 -0
  15. pylsp/hookspecs.py +140 -0
  16. pylsp/lsp.py +115 -0
  17. pylsp/plugins/__init__.py +2 -0
  18. pylsp/plugins/_resolvers.py +132 -0
  19. pylsp/plugins/_rope_task_handle.py +106 -0
  20. pylsp/plugins/autopep8_format.py +95 -0
  21. pylsp/plugins/definition.py +84 -0
  22. pylsp/plugins/flake8_lint.py +243 -0
  23. pylsp/plugins/folding.py +210 -0
  24. pylsp/plugins/highlight.py +36 -0
  25. pylsp/plugins/hover.py +57 -0
  26. pylsp/plugins/jedi_completion.py +318 -0
  27. pylsp/plugins/jedi_rename.py +56 -0
  28. pylsp/plugins/mccabe_lint.py +56 -0
  29. pylsp/plugins/preload_imports.py +38 -0
  30. pylsp/plugins/pycodestyle_lint.py +113 -0
  31. pylsp/plugins/pydocstyle_lint.py +127 -0
  32. pylsp/plugins/pyflakes_lint.py +97 -0
  33. pylsp/plugins/pylint_lint.py +367 -0
  34. pylsp/plugins/references.py +33 -0
  35. pylsp/plugins/rope_autoimport.py +407 -0
  36. pylsp/plugins/rope_completion.py +161 -0
  37. pylsp/plugins/signature.py +81 -0
  38. pylsp/plugins/symbols.py +214 -0
  39. pylsp/plugins/yapf_format.py +199 -0
  40. pylsp/python_lsp.py +921 -0
  41. pylsp/text_edit.py +97 -0
  42. pylsp/uris.py +139 -0
  43. pylsp/workspace.py +730 -0
pylsp/config/source.py ADDED
@@ -0,0 +1,88 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import configparser
5
+ import logging
6
+ import os
7
+ import sys
8
+
9
+ log = logging.getLogger(__name__)
10
+
11
+
12
+ class ConfigSource:
13
+ """Base class for implementing a config source."""
14
+
15
+ def __init__(self, root_path) -> None:
16
+ self.root_path = root_path
17
+ self.is_windows = sys.platform == "win32"
18
+ self.xdg_home = os.environ.get(
19
+ "XDG_CONFIG_HOME", os.path.expanduser("~/.config")
20
+ )
21
+
22
+ def user_config(self) -> None:
23
+ """Return user-level (i.e. home directory) configuration."""
24
+ raise NotImplementedError()
25
+
26
+ def project_config(self, document_path) -> None:
27
+ """Return project-level (i.e. workspace directory) configuration."""
28
+ raise NotImplementedError()
29
+
30
+ @classmethod
31
+ def read_config_from_files(cls, files):
32
+ config = configparser.RawConfigParser()
33
+ for filename in files:
34
+ if os.path.exists(filename) and not os.path.isdir(filename):
35
+ config.read(filename)
36
+
37
+ return config
38
+
39
+ @classmethod
40
+ def parse_config(cls, config, key, options):
41
+ """Parse the config with the given options."""
42
+ conf = {}
43
+ for source, destination, opt_type in options:
44
+ opt_value = cls._get_opt(config, key, source, opt_type)
45
+ if opt_value is not None:
46
+ cls._set_opt(conf, destination, opt_value)
47
+ return conf
48
+
49
+ @classmethod
50
+ def _get_opt(cls, config, key, option, opt_type):
51
+ """Get an option from a configparser with the given type."""
52
+ for opt_key in [option, option.replace("-", "_")]:
53
+ if not config.has_option(key, opt_key):
54
+ continue
55
+
56
+ if opt_type is bool:
57
+ return config.getboolean(key, opt_key)
58
+
59
+ if opt_type is int:
60
+ return config.getint(key, opt_key)
61
+
62
+ if opt_type is str:
63
+ return config.get(key, opt_key)
64
+
65
+ if opt_type is list:
66
+ return cls._parse_list_opt(config.get(key, opt_key))
67
+
68
+ raise ValueError("Unknown option type: %s" % opt_type)
69
+
70
+ @classmethod
71
+ def _parse_list_opt(cls, string):
72
+ return [s.strip() for s in string.split(",") if s.strip()]
73
+
74
+ @classmethod
75
+ def _set_opt(cls, config_dict, path, value):
76
+ """Set the value in the dictionary at the given path if the value is not None."""
77
+ if value is None:
78
+ return
79
+
80
+ if "." not in path:
81
+ config_dict[path] = value
82
+ return
83
+
84
+ key, rest = path.split(".", 1)
85
+ if key not in config_dict:
86
+ config_dict[key] = {}
87
+
88
+ cls._set_opt(config_dict[key], rest, value)
pylsp/hookspecs.py ADDED
@@ -0,0 +1,140 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ from pylsp import hookspec
5
+
6
+
7
+ @hookspec
8
+ def pylsp_code_actions(config, workspace, document, range, context):
9
+ pass
10
+
11
+
12
+ @hookspec
13
+ def pylsp_code_lens(config, workspace, document) -> None:
14
+ pass
15
+
16
+
17
+ @hookspec
18
+ def pylsp_commands(config, workspace) -> None:
19
+ """The list of command strings supported by the server.
20
+
21
+ Returns:
22
+ List[str]: The supported commands.
23
+ """
24
+
25
+
26
+ @hookspec
27
+ def pylsp_completions(config, workspace, document, position, ignored_names) -> None:
28
+ pass
29
+
30
+
31
+ @hookspec
32
+ def pylsp_completion_detail(config, item) -> None:
33
+ pass
34
+
35
+
36
+ @hookspec(firstresult=True)
37
+ def pylsp_completion_item_resolve(config, workspace, document, completion_item) -> None:
38
+ pass
39
+
40
+
41
+ @hookspec
42
+ def pylsp_definitions(config, workspace, document, position) -> None:
43
+ pass
44
+
45
+
46
+ @hookspec
47
+ def pylsp_dispatchers(config, workspace) -> None:
48
+ pass
49
+
50
+
51
+ @hookspec
52
+ def pylsp_document_did_open(config, workspace, document) -> None:
53
+ pass
54
+
55
+
56
+ @hookspec
57
+ def pylsp_document_did_save(config, workspace, document) -> None:
58
+ pass
59
+
60
+
61
+ @hookspec
62
+ def pylsp_document_highlight(config, workspace, document, position) -> None:
63
+ pass
64
+
65
+
66
+ @hookspec
67
+ def pylsp_document_symbols(config, workspace, document) -> None:
68
+ pass
69
+
70
+
71
+ @hookspec(firstresult=True)
72
+ def pylsp_execute_command(config, workspace, command, arguments) -> None:
73
+ pass
74
+
75
+
76
+ @hookspec
77
+ def pylsp_experimental_capabilities(config, workspace) -> None:
78
+ pass
79
+
80
+
81
+ @hookspec
82
+ def pylsp_folding_range(config, workspace, document) -> None:
83
+ pass
84
+
85
+
86
+ @hookspec(firstresult=True)
87
+ def pylsp_format_document(config, workspace, document, options) -> None:
88
+ pass
89
+
90
+
91
+ @hookspec(firstresult=True)
92
+ def pylsp_format_range(config, workspace, document, range, options) -> None:
93
+ pass
94
+
95
+
96
+ @hookspec(firstresult=True)
97
+ def pylsp_hover(config, workspace, document, position) -> None:
98
+ pass
99
+
100
+
101
+ @hookspec
102
+ def pylsp_initialize(config, workspace) -> None:
103
+ pass
104
+
105
+
106
+ @hookspec
107
+ def pylsp_initialized() -> None:
108
+ pass
109
+
110
+
111
+ @hookspec
112
+ def pylsp_lint(config, workspace, document, is_saved) -> None:
113
+ pass
114
+
115
+
116
+ @hookspec
117
+ def pylsp_references(
118
+ config, workspace, document, position, exclude_declaration
119
+ ) -> None:
120
+ pass
121
+
122
+
123
+ @hookspec(firstresult=True)
124
+ def pylsp_rename(config, workspace, document, position, new_name) -> None:
125
+ pass
126
+
127
+
128
+ @hookspec
129
+ def pylsp_settings(config) -> None:
130
+ pass
131
+
132
+
133
+ @hookspec(firstresult=True)
134
+ def pylsp_signature_help(config, workspace, document, position) -> None:
135
+ pass
136
+
137
+
138
+ @hookspec
139
+ def pylsp_workspace_configuration_changed(config, workspace) -> None:
140
+ pass
pylsp/lsp.py ADDED
@@ -0,0 +1,115 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ """Some Language Server Protocol constants
5
+
6
+ https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md
7
+ """
8
+
9
+
10
+ class CompletionItemKind:
11
+ Text = 1
12
+ Method = 2
13
+ Function = 3
14
+ Constructor = 4
15
+ Field = 5
16
+ Variable = 6
17
+ Class = 7
18
+ Interface = 8
19
+ Module = 9
20
+ Property = 10
21
+ Unit = 11
22
+ Value = 12
23
+ Enum = 13
24
+ Keyword = 14
25
+ Snippet = 15
26
+ Color = 16
27
+ File = 17
28
+ Reference = 18
29
+ Folder = 19
30
+ EnumMember = 20
31
+ Constant = 21
32
+ Struct = 22
33
+ Event = 23
34
+ Operator = 24
35
+ TypeParameter = 25
36
+
37
+
38
+ class DocumentHighlightKind:
39
+ Text = 1
40
+ Read = 2
41
+ Write = 3
42
+
43
+
44
+ class DiagnosticSeverity:
45
+ Error = 1
46
+ Warning = 2
47
+ Information = 3
48
+ Hint = 4
49
+
50
+
51
+ class DiagnosticTag:
52
+ Unnecessary = 1
53
+ Deprecated = 2
54
+
55
+
56
+ class InsertTextFormat:
57
+ PlainText = 1
58
+ Snippet = 2
59
+
60
+
61
+ class MessageType:
62
+ Error = 1
63
+ Warning = 2
64
+ Info = 3
65
+ Log = 4
66
+
67
+
68
+ class SymbolKind:
69
+ File = 1
70
+ Module = 2
71
+ Namespace = 3
72
+ Package = 4
73
+ Class = 5
74
+ Method = 6
75
+ Property = 7
76
+ Field = 8
77
+ Constructor = 9
78
+ Enum = 10
79
+ Interface = 11
80
+ Function = 12
81
+ Variable = 13
82
+ Constant = 14
83
+ String = 15
84
+ Number = 16
85
+ Boolean = 17
86
+ Array = 18
87
+
88
+
89
+ class TextDocumentSyncKind:
90
+ NONE = 0
91
+ FULL = 1
92
+ INCREMENTAL = 2
93
+
94
+
95
+ class NotebookCellKind:
96
+ Markup = 1
97
+ Code = 2
98
+
99
+
100
+ # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#errorCodes
101
+ class ErrorCodes:
102
+ ParseError = -32700
103
+ InvalidRequest = -32600
104
+ MethodNotFound = -32601
105
+ InvalidParams = -32602
106
+ InternalError = -32603
107
+ jsonrpcReservedErrorRangeStart = -32099
108
+ ServerNotInitialized = -32002
109
+ UnknownErrorCode = -32001
110
+ jsonrpcReservedErrorRangeEnd = -32000
111
+ lspReservedErrorRangeStart = -32899
112
+ ServerCancelled = -32802
113
+ ContentModified = -32801
114
+ RequestCancelled = -32800
115
+ lspReservedErrorRangeEnd = -32800
@@ -0,0 +1,2 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
@@ -0,0 +1,132 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import logging
5
+ from collections import defaultdict
6
+ from time import time
7
+
8
+ from jedi.api.classes import Completion
9
+
10
+ from pylsp import lsp
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+
15
+ # ---- Base class
16
+ # -----------------------------------------------------------------------------
17
+ class Resolver:
18
+ def __init__(self, callback, resolve_on_error, time_to_live=60 * 30) -> None:
19
+ self.callback = callback
20
+ self.resolve_on_error = resolve_on_error
21
+ self._cache = {}
22
+ self._time_to_live = time_to_live
23
+ self._cache_ttl = defaultdict(set)
24
+ self._clear_every = 2
25
+ # see https://github.com/davidhalter/jedi/blob/master/jedi/inference/helpers.py#L194-L202
26
+ self._cached_modules = {"pandas", "numpy", "tensorflow", "matplotlib"}
27
+
28
+ @property
29
+ def cached_modules(self):
30
+ return self._cached_modules
31
+
32
+ @cached_modules.setter
33
+ def cached_modules(self, new_value):
34
+ self._cached_modules = set(new_value)
35
+
36
+ def clear_outdated(self) -> None:
37
+ now = self.time_key()
38
+ to_clear = [timestamp for timestamp in self._cache_ttl if timestamp < now]
39
+ for time_key in to_clear:
40
+ for key in self._cache_ttl[time_key]:
41
+ del self._cache[key]
42
+ del self._cache_ttl[time_key]
43
+
44
+ def time_key(self):
45
+ return int(time() / self._time_to_live)
46
+
47
+ def get_or_create(self, completion: Completion):
48
+ if not completion.full_name:
49
+ use_cache = False
50
+ else:
51
+ module_parts = completion.full_name.split(".")
52
+ use_cache = module_parts and module_parts[0] in self._cached_modules
53
+
54
+ if use_cache:
55
+ key = self._create_completion_id(completion)
56
+ if key not in self._cache:
57
+ if self.time_key() % self._clear_every == 0:
58
+ self.clear_outdated()
59
+
60
+ self._cache[key] = self.resolve(completion)
61
+ self._cache_ttl[self.time_key()].add(key)
62
+ return self._cache[key]
63
+
64
+ return self.resolve(completion)
65
+
66
+ def _create_completion_id(self, completion: Completion):
67
+ return (
68
+ completion.full_name,
69
+ completion.module_path,
70
+ completion.line,
71
+ completion.column,
72
+ self.time_key(),
73
+ )
74
+
75
+ def resolve(self, completion):
76
+ try:
77
+ sig = completion.get_signatures()
78
+ return self.callback(completion, sig)
79
+ except Exception as e:
80
+ log.warning(
81
+ f"Something went wrong when resolving label for {completion}: {e}"
82
+ )
83
+ return self.resolve_on_error
84
+
85
+
86
+ # ---- Label resolver
87
+ # -----------------------------------------------------------------------------
88
+ def format_label(completion, sig):
89
+ if sig and completion.type in ("function", "method"):
90
+ params = ", ".join(param.name for param in sig[0].params)
91
+ label = "{}({})".format(completion.name, params)
92
+ return label
93
+ return completion.name
94
+
95
+
96
+ LABEL_RESOLVER = Resolver(callback=format_label, resolve_on_error="")
97
+
98
+
99
+ # ---- Snippets resolver
100
+ # -----------------------------------------------------------------------------
101
+ def format_snippet(completion, sig):
102
+ if not sig:
103
+ return {}
104
+
105
+ snippet_completion = {}
106
+
107
+ positional_args = [
108
+ param
109
+ for param in sig[0].params
110
+ if "=" not in param.description and param.name not in {"/", "*"}
111
+ ]
112
+
113
+ if len(positional_args) > 1:
114
+ # For completions with params, we can generate a snippet instead
115
+ snippet_completion["insertTextFormat"] = lsp.InsertTextFormat.Snippet
116
+ snippet = completion.name + "("
117
+ for i, param in enumerate(positional_args):
118
+ snippet += "${%s:%s}" % (i + 1, param.name)
119
+ if i < len(positional_args) - 1:
120
+ snippet += ", "
121
+ snippet += ")$0"
122
+ snippet_completion["insertText"] = snippet
123
+ elif len(positional_args) == 1:
124
+ snippet_completion["insertTextFormat"] = lsp.InsertTextFormat.Snippet
125
+ snippet_completion["insertText"] = completion.name + "($0)"
126
+ else:
127
+ snippet_completion["insertText"] = completion.name + "()"
128
+
129
+ return snippet_completion
130
+
131
+
132
+ SNIPPET_RESOLVER = Resolver(callback=format_snippet, resolve_on_error={})
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Callable, ContextManager, List, Optional, Sequence
5
+
6
+ from rope.base.taskhandle import BaseJobSet, BaseTaskHandle
7
+
8
+ from pylsp._utils import throttle
9
+ from pylsp.workspace import Workspace
10
+
11
+ log = logging.getLogger(__name__)
12
+ Report = Callable[[str, int], None]
13
+
14
+
15
+ class PylspJobSet(BaseJobSet):
16
+ count: int = 0
17
+ done: int = 0
18
+ _reporter: Report
19
+ _report_iter: ContextManager
20
+ job_name: str = ""
21
+
22
+ def __init__(self, count: Optional[int], report_iter: ContextManager) -> None:
23
+ if count is not None:
24
+ self.count = count
25
+ self._reporter = report_iter.__enter__()
26
+ self._report_iter = report_iter
27
+
28
+ def started_job(self, name: Optional[str]) -> None:
29
+ if name:
30
+ self.job_name = name
31
+
32
+ def finished_job(self) -> None:
33
+ self.done += 1
34
+ if self.get_percent_done() is not None and int(self.get_percent_done()) >= 100:
35
+ if self._report_iter is None:
36
+ return
37
+ self._report_iter.__exit__(None, None, None)
38
+ self._report_iter = None
39
+ else:
40
+ self._report()
41
+
42
+ def check_status(self) -> None:
43
+ pass
44
+
45
+ def get_percent_done(self) -> Optional[float]:
46
+ if self.count == 0:
47
+ return 0
48
+ return (self.done / self.count) * 100
49
+
50
+ def increment(self) -> None:
51
+ """
52
+ Increment the number of tasks to complete.
53
+
54
+ This is used if the number is not known ahead of time.
55
+ """
56
+ self.count += 1
57
+ self._report()
58
+
59
+ @throttle(0.5)
60
+ def _report(self) -> None:
61
+ percent = int(self.get_percent_done())
62
+ message = f"{self.job_name} {self.done}/{self.count}"
63
+ log.debug(f"Reporting {message} {percent}%")
64
+ self._reporter(message, percent)
65
+
66
+
67
+ class PylspTaskHandle(BaseTaskHandle):
68
+ name: str
69
+ observers: List
70
+ job_sets: List[PylspJobSet]
71
+ stopped: bool
72
+ workspace: Workspace
73
+ _report: Callable[[str, str], None]
74
+
75
+ def __init__(self, workspace: Workspace) -> None:
76
+ self.workspace = workspace
77
+ self.job_sets = []
78
+ self.observers = []
79
+
80
+ def create_jobset(self, name="JobSet", count: Optional[int] = None):
81
+ report_iter = self.workspace.report_progress(
82
+ name, None, None, skip_token_initialization=True
83
+ )
84
+ result = PylspJobSet(count, report_iter)
85
+ self.job_sets.append(result)
86
+ self._inform_observers()
87
+ return result
88
+
89
+ def stop(self) -> None:
90
+ pass
91
+
92
+ def current_jobset(self) -> Optional[BaseJobSet]:
93
+ pass
94
+
95
+ def add_observer(self) -> None:
96
+ pass
97
+
98
+ def is_stopped(self) -> bool:
99
+ pass
100
+
101
+ def get_jobsets(self) -> Sequence[BaseJobSet]:
102
+ pass
103
+
104
+ def _inform_observers(self) -> None:
105
+ for observer in self.observers:
106
+ observer()
@@ -0,0 +1,95 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import logging
5
+
6
+ import pycodestyle
7
+ from autopep8 import continued_indentation as autopep8_c_i
8
+ from autopep8 import fix_code
9
+
10
+ from pylsp import hookimpl
11
+ from pylsp._utils import get_eol_chars
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+
16
+ @hookimpl(tryfirst=True) # Prefer autopep8 over YAPF
17
+ def pylsp_format_document(config, workspace, document, options):
18
+ with workspace.report_progress("format: autopep8"):
19
+ log.info("Formatting document %s with autopep8", document)
20
+ return _format(config, document)
21
+
22
+
23
+ @hookimpl(tryfirst=True) # Prefer autopep8 over YAPF
24
+ def pylsp_format_range(config, workspace, document, range, options):
25
+ log.info("Formatting document %s in range %s with autopep8", document, range)
26
+
27
+ # First we 'round' the range up/down to full lines only
28
+ range["start"]["character"] = 0
29
+ range["end"]["line"] += 1
30
+ range["end"]["character"] = 0
31
+
32
+ # Add 1 for 1-indexing vs LSP's 0-indexing
33
+ line_range = (range["start"]["line"] + 1, range["end"]["line"])
34
+ return _format(config, document, line_range=line_range)
35
+
36
+
37
+ def _format(config, document, line_range=None):
38
+ options = _autopep8_config(config, document)
39
+ if line_range:
40
+ options["line_range"] = list(line_range)
41
+
42
+ # Temporarily re-monkey-patch the continued_indentation checker - #771
43
+ del pycodestyle._checks["logical_line"][pycodestyle.continued_indentation]
44
+ pycodestyle.register_check(autopep8_c_i)
45
+
46
+ # Autopep8 doesn't work with CR line endings, so we replace them by '\n'
47
+ # and restore them below.
48
+ replace_cr = False
49
+ source = document.source
50
+ eol_chars = get_eol_chars(source)
51
+ if eol_chars == "\r":
52
+ replace_cr = True
53
+ source = source.replace("\r", "\n")
54
+
55
+ new_source = fix_code(source, options=options)
56
+
57
+ # Switch it back
58
+ del pycodestyle._checks["logical_line"][autopep8_c_i]
59
+ pycodestyle.register_check(pycodestyle.continued_indentation)
60
+
61
+ if new_source == source:
62
+ return []
63
+
64
+ if replace_cr:
65
+ new_source = new_source.replace("\n", "\r")
66
+
67
+ # I'm too lazy at the moment to parse diffs into TextEdit items
68
+ # So let's just return the entire file...
69
+ return [
70
+ {
71
+ "range": {
72
+ "start": {"line": 0, "character": 0},
73
+ # End char 0 of the line after our document
74
+ "end": {"line": len(document.lines), "character": 0},
75
+ },
76
+ "newText": new_source,
77
+ }
78
+ ]
79
+
80
+
81
+ def _autopep8_config(config, document=None):
82
+ # We user pycodestyle settings to avoid redefining things
83
+ path = document.path if document is not None else None
84
+ settings = config.plugin_settings("pycodestyle", document_path=path)
85
+ options = {
86
+ "exclude": settings.get("exclude"),
87
+ "hang_closing": settings.get("hangClosing"),
88
+ "ignore": settings.get("ignore"),
89
+ "max_line_length": settings.get("maxLineLength"),
90
+ "select": settings.get("select"),
91
+ "aggressive": settings.get("aggressive"),
92
+ }
93
+
94
+ # Filter out null options
95
+ return {k: v for k, v in options.items() if v}