import-completer 0.4.0__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.
File without changes
@@ -0,0 +1,202 @@
1
+ import re
2
+ from collections.abc import Generator, Iterator
3
+ from pathlib import Path
4
+ from typing import Literal
5
+
6
+ import tree_sitter_python as tspython
7
+ from tree_sitter import Language, Node, Parser
8
+
9
+ from import_completer.types import ScannedSymbol
10
+
11
+ PYTHON_TS_LANGUAGE = Language(tspython.language())
12
+ PYTHON_TS_PARSER = Parser()
13
+ PYTHON_TS_PARSER.language = PYTHON_TS_LANGUAGE
14
+
15
+ PYTHON_TOP_LEVEL_SYMBOLS_QUERY = PYTHON_TS_LANGUAGE.query(
16
+ """
17
+ [
18
+ ; Direct module children
19
+ (module
20
+ (function_definition name: (identifier) @function-name))
21
+ (module
22
+ (class_definition name: (identifier) @class-name))
23
+ (module
24
+ (decorated_definition
25
+ (function_definition name: (identifier) @function-name)))
26
+ (module
27
+ (decorated_definition
28
+ (class_definition name: (identifier) @class-name)))
29
+ (module
30
+ (expression_statement
31
+ (assignment left: (identifier) @assignment-name)))
32
+
33
+ ; Inside top-level if statements
34
+ (module
35
+ (if_statement
36
+ (block
37
+ (function_definition name: (identifier) @function-name))))
38
+ (module
39
+ (if_statement
40
+ (block
41
+ (class_definition name: (identifier) @class-name))))
42
+ (module
43
+ (if_statement
44
+ (block
45
+ (decorated_definition
46
+ (function_definition name: (identifier) @function-name)))))
47
+ (module
48
+ (if_statement
49
+ (block
50
+ (decorated_definition
51
+ (class_definition name: (identifier) @class-name)))))
52
+ (module
53
+ (if_statement
54
+ (block
55
+ (expression_statement
56
+ (assignment left: (identifier) @assignment-name)))))
57
+ ; Else clause
58
+ (module
59
+ (if_statement
60
+ (else_clause
61
+ (block
62
+ (function_definition name: (identifier) @function-name)))))
63
+ (module
64
+ (if_statement
65
+ (else_clause
66
+ (block
67
+ (class_definition name: (identifier) @class-name)))))
68
+ (module
69
+ (if_statement
70
+ (else_clause
71
+ (block
72
+ (decorated_definition
73
+ (function_definition name: (identifier) @function-name))))))
74
+ (module
75
+ (if_statement
76
+ (else_clause
77
+ (block
78
+ (decorated_definition
79
+ (class_definition name: (identifier) @class-name))))))
80
+ (module
81
+ (if_statement
82
+ (else_clause
83
+ (block
84
+ (expression_statement
85
+ (assignment left: (identifier) @assignment-name))))))
86
+ ]
87
+ """
88
+ )
89
+
90
+ RE_PYTHON_VALID_MODULE_FILE_NAME = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*\.pyi?$")
91
+ RE_PYTHON_VALID_MODULE_DIRECTORY_NAME = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
92
+
93
+
94
+ def discover_python_modules(directory: str | Path) -> Generator[Path]:
95
+ """
96
+ Walk through directory and yield Python module files (.py and .pyi).
97
+ Descends into subdirectories only if they are Python modules (contain __init__.py).
98
+
99
+ Args:
100
+ directory: Root directory to start scanning from
101
+
102
+ Yields:
103
+ Path objects for Python module files
104
+ """
105
+
106
+ directory = Path(directory)
107
+
108
+ if not directory.is_dir():
109
+ return
110
+
111
+ for child_entity_path in directory.iterdir():
112
+ if (
113
+ child_entity_path.is_file()
114
+ and RE_PYTHON_VALID_MODULE_FILE_NAME.match(child_entity_path.name)
115
+ is not None
116
+ ):
117
+ yield child_entity_path
118
+ elif (
119
+ child_entity_path.is_dir()
120
+ and RE_PYTHON_VALID_MODULE_DIRECTORY_NAME.match(child_entity_path.name)
121
+ is not None
122
+ ):
123
+ # Recursively process subdirectories if they are Python packages
124
+ if (child_entity_path / "__init__.py").exists() or (
125
+ child_entity_path / "__init__.pyi"
126
+ ).exists():
127
+ yield from discover_python_modules(child_entity_path)
128
+ else:
129
+ pass
130
+ # print(f"Skipping {child_entity_path}")
131
+
132
+
133
+ def extract_symbols_from_origin(origin_path: Path) -> Iterator[ScannedSymbol]:
134
+ for module_path in discover_python_modules(origin_path):
135
+ yield from extract_symbols_from_file(module_path, origin_path)
136
+
137
+
138
+ def extract_symbols_from_file(
139
+ file_path: Path, origin_path: Path
140
+ ) -> Iterator[ScannedSymbol]:
141
+ """
142
+ Extract all top-level symbols from a given Python file.
143
+ """
144
+
145
+ with open(file_path, "rb") as fh:
146
+ source_code = fh.read()
147
+
148
+ yield from extract_symbols(
149
+ source_code, annotated_file_path=file_path, origin_path=origin_path
150
+ )
151
+
152
+
153
+ def extract_symbols(
154
+ source_code: bytes, annotated_file_path: Path, origin_path: Path
155
+ ) -> Iterator[ScannedSymbol]:
156
+ """
157
+ Extract all top-level symbols from a given Python source code.
158
+ """
159
+
160
+ module_relative_path = Path(annotated_file_path.relative_to(origin_path))
161
+ parent_module = ".".join(module_relative_path.with_suffix("").parts)
162
+
163
+ def _make_scanned_symbol(
164
+ kind: Literal["function", "class", "variable"], node: Node
165
+ ) -> ScannedSymbol:
166
+ assert node.text is not None
167
+ return ScannedSymbol(
168
+ kind=kind,
169
+ name=node.text.decode("utf-8"),
170
+ parent_module=parent_module,
171
+ metadata={},
172
+ )
173
+
174
+ tree = PYTHON_TS_PARSER.parse(source_code)
175
+ root_node = tree.root_node
176
+
177
+ captures = PYTHON_TOP_LEVEL_SYMBOLS_QUERY.captures(root_node)
178
+ for node in captures.get("function-name", []):
179
+ if node.text:
180
+ yield _make_scanned_symbol("function", node)
181
+
182
+ for node in captures.get("class-name", []):
183
+ if node.text:
184
+ yield _make_scanned_symbol("class", node)
185
+
186
+ for node in captures.get("assignment-name", []):
187
+ if node.text:
188
+ yield _make_scanned_symbol("variable", node)
189
+
190
+
191
+ if __name__ == "__main__":
192
+ import sys
193
+
194
+ for path in (Path(p) for p in sys.path):
195
+ if not path.is_dir():
196
+ continue
197
+ for module_path in discover_python_modules(path):
198
+ print(f"Discovered module: {module_path}")
199
+ for symbol in extract_symbols_from_file(
200
+ module_path, annotated_source_sys_path=path
201
+ ):
202
+ print(f" + {symbol.kind}: {symbol.name}")
@@ -0,0 +1,80 @@
1
+ import sys
2
+ from collections.abc import AsyncGenerator, AsyncIterable
3
+ from contextlib import asynccontextmanager
4
+ from pathlib import Path
5
+
6
+ from loguru import logger
7
+
8
+ from import_completer.code_scanner import extract_symbols_from_origin
9
+ from import_completer.path_discovery import get_internal_python_paths
10
+ from import_completer.symbols_database import DatabaseHandler
11
+ from import_completer.types import ScannedSymbol
12
+
13
+
14
+ class CompletionEngine:
15
+ def __init__(
16
+ self, symbols_database: DatabaseHandler, origin_paths: list[Path]
17
+ ) -> None:
18
+ self._symbols_database = symbols_database
19
+ self._origin_paths = origin_paths
20
+ self._is_fully_initialized = False
21
+
22
+ @property
23
+ def is_fully_initialized(self) -> bool:
24
+ return self._is_fully_initialized
25
+
26
+ async def initialize_database(self) -> None:
27
+ self._symbols_database = DatabaseHandler()
28
+ await self._symbols_database.__aenter__()
29
+ await self._symbols_database.create_schema()
30
+ await self._symbols_database.create_indices()
31
+
32
+ async def autoload_symbols(self) -> None:
33
+ assert self._symbols_database is not None, "Database not initialized!"
34
+ for origin_idx, path in enumerate((Path(p) for p in sys.path), 1):
35
+ if not path.is_dir():
36
+ continue
37
+ logger.info("Rebuilding symbols for path: {}", path)
38
+ await self._symbols_database.perform_rebuild_for_origin(
39
+ origin_idx, extract_symbols_from_origin(path)
40
+ )
41
+ self._is_fully_initialized = True
42
+
43
+ async def lookup_symbol_prefix(self, prefix: str) -> AsyncIterable[ScannedSymbol]:
44
+ assert self._symbols_database is not None, "Database not initialized!"
45
+ async for symbol in self._symbols_database.lookup_symbol_name(prefix):
46
+ yield symbol
47
+
48
+
49
+ @asynccontextmanager
50
+ async def create_completion_engine(
51
+ origin_paths: list[Path],
52
+ ) -> AsyncGenerator[CompletionEngine]:
53
+ symbols_database = DatabaseHandler()
54
+ async with symbols_database:
55
+ await symbols_database.create_schema()
56
+ await symbols_database.create_indices()
57
+ completion_engine = CompletionEngine(symbols_database, origin_paths)
58
+ yield completion_engine
59
+
60
+
61
+ if __name__ == "__main__":
62
+ import asyncio
63
+ import sys
64
+
65
+ async def _main():
66
+ async with create_completion_engine(
67
+ list(get_internal_python_paths())
68
+ ) as completion_engine:
69
+ await completion_engine.autoload_symbols()
70
+ print("Database fully initialized!")
71
+
72
+ for word_to_complete in sys.argv[1:]:
73
+ print(f"Completing for '{word_to_complete}':")
74
+ async for symbol in completion_engine.lookup_symbol_prefix(
75
+ word_to_complete
76
+ ):
77
+ print(f" - {symbol.kind}: {symbol.name} ({symbol.parent_module})")
78
+ print()
79
+
80
+ asyncio.run(_main())
@@ -0,0 +1,26 @@
1
+ import dataclasses
2
+ from importlib.metadata import PackageNotFoundError, version
3
+ from pathlib import Path
4
+
5
+ from import_completer.path_discovery import (
6
+ discover_paths_for_python_executable,
7
+ )
8
+
9
+
10
+ def get_version() -> str:
11
+ """Get the package version from installed metadata, or 'dev' if not installed."""
12
+ try:
13
+ return version("import-completer")
14
+ except PackageNotFoundError:
15
+ return "dev"
16
+
17
+
18
+ @dataclasses.dataclass
19
+ class Config:
20
+ origin_paths: list[Path]
21
+
22
+ @classmethod
23
+ def default(cls) -> "Config":
24
+ return cls(
25
+ origin_paths=list(discover_paths_for_python_executable()),
26
+ )
@@ -0,0 +1,203 @@
1
+ import re
2
+ from contextlib import AsyncExitStack
3
+ from typing import Any
4
+
5
+ from loguru import logger
6
+ from lsprotocol.types import (
7
+ INITIALIZED,
8
+ SHUTDOWN,
9
+ TEXT_DOCUMENT_COMPLETION,
10
+ CompletionItem,
11
+ CompletionItemKind,
12
+ CompletionList,
13
+ CompletionParams,
14
+ InitializeParams,
15
+ Position,
16
+ ProgressParams,
17
+ Range,
18
+ TextEdit,
19
+ WorkDoneProgressBegin,
20
+ WorkDoneProgressEnd,
21
+ WorkDoneProgressReport,
22
+ )
23
+ from pygls.lsp.server import LanguageServer
24
+
25
+ from import_completer.completion_engine import (
26
+ CompletionEngine,
27
+ create_completion_engine,
28
+ )
29
+ from import_completer.config import Config, get_version
30
+
31
+
32
+ def _handler_exception_wrapper(func):
33
+ async def wrapper(*args, **kwargs):
34
+ try:
35
+ return await func(*args, **kwargs)
36
+ except Exception:
37
+ logger.exception("Error occurred during handling of {}.", func.__name__)
38
+ raise
39
+
40
+ return wrapper
41
+
42
+
43
+ class ImportCompleterLanguageServer(LanguageServer):
44
+ def __init__(self, *args: Any, config: Config, **kwargs: Any) -> None:
45
+ super().__init__(*args, **kwargs)
46
+ self._exit_stack = AsyncExitStack()
47
+ self._config = config
48
+ self._completion_engine: CompletionEngine | None = None
49
+
50
+ self.feature(INITIALIZED)(
51
+ _handler_exception_wrapper(self.initialize_completion_engine)
52
+ )
53
+ self.feature(TEXT_DOCUMENT_COMPLETION)(
54
+ _handler_exception_wrapper(self.handle_completion)
55
+ )
56
+ self.feature(SHUTDOWN)(
57
+ _handler_exception_wrapper(self.shutdown_completion_engine)
58
+ )
59
+
60
+ @property
61
+ def completion_engine(self) -> CompletionEngine:
62
+ assert self._completion_engine is not None, "Completion engine not initialized!"
63
+ return self._completion_engine
64
+
65
+ async def initialize_completion_engine(self, params: InitializeParams) -> None:
66
+ logger.info("Initializing language server.")
67
+
68
+ # Begin progress
69
+ token = "import-completer-init"
70
+ self.protocol.notify(
71
+ "$/progress",
72
+ ProgressParams(
73
+ token=token,
74
+ value=WorkDoneProgressBegin(
75
+ title="Import Completer", message="Initializing...", percentage=0
76
+ ),
77
+ ),
78
+ )
79
+
80
+ try:
81
+ # Report progress - creating engine
82
+ self.protocol.notify(
83
+ "$/progress",
84
+ ProgressParams(
85
+ token=token,
86
+ value=WorkDoneProgressReport(
87
+ message="Creating completion engine...", percentage=30
88
+ ),
89
+ ),
90
+ )
91
+
92
+ self._completion_engine = await self._exit_stack.enter_async_context(
93
+ create_completion_engine(self._config.origin_paths)
94
+ )
95
+
96
+ # Report progress - loading symbols
97
+ self.protocol.notify(
98
+ "$/progress",
99
+ ProgressParams(
100
+ token=token,
101
+ value=WorkDoneProgressReport(
102
+ message="Loading symbols...", percentage=60
103
+ ),
104
+ ),
105
+ )
106
+
107
+ await self._completion_engine.autoload_symbols()
108
+
109
+ # End progress
110
+ self.protocol.notify(
111
+ "$/progress",
112
+ ProgressParams(
113
+ token=token, value=WorkDoneProgressEnd(message="Ready!")
114
+ ),
115
+ )
116
+ logger.info("Language server initialization complete.")
117
+
118
+ except Exception:
119
+ # End progress with error
120
+ self.protocol.notify(
121
+ "$/progress",
122
+ ProgressParams(
123
+ token=token,
124
+ value=WorkDoneProgressEnd(message="Initialization failed"),
125
+ ),
126
+ )
127
+ logger.exception("Failed to initialize language server")
128
+ raise
129
+
130
+ async def shutdown_completion_engine(self, params) -> None:
131
+ """Clean up resources before server shutdown"""
132
+ logger.info("Shutting down language server, cleaning up resources...")
133
+
134
+ try:
135
+ # Close all async context managers (including completion engine)
136
+ await self._exit_stack.aclose()
137
+ self._completion_engine = None
138
+ logger.info("Resources cleaned up successfully.")
139
+ except Exception:
140
+ logger.exception("Error during shutdown cleanup")
141
+
142
+ return None
143
+
144
+ async def handle_completion(self, params: CompletionParams) -> CompletionList:
145
+ logger.debug("Handling completion, with params: {}", params)
146
+ document = self.workspace.get_text_document(params.text_document.uri)
147
+ current_line = document.lines[params.position.line]
148
+ last_word = re.split(
149
+ r"[^a-zA-Z0-9_.]", current_line[: params.position.character]
150
+ )[-1]
151
+ logger.debug("Completing for word: '{}'", last_word)
152
+
153
+ if "." in last_word:
154
+ logger.debug("Ignoring completions for dotted names.")
155
+ return CompletionList(is_incomplete=False, items=[])
156
+
157
+ completions = [
158
+ symbol
159
+ async for symbol in self.completion_engine.lookup_symbol_prefix(last_word)
160
+ ]
161
+
162
+ items = []
163
+ for symbol in completions:
164
+ match symbol.kind:
165
+ case "variable":
166
+ kind = CompletionItemKind.Variable
167
+ case "function":
168
+ kind = CompletionItemKind.Function
169
+ case "class":
170
+ kind = CompletionItemKind.Class
171
+ case _:
172
+ kind = CompletionItemKind.Text
173
+ items.append(
174
+ CompletionItem(
175
+ label=symbol.name,
176
+ detail=f"from {symbol.parent_module} import {symbol.name}\n# import_completer",
177
+ kind=kind,
178
+ additional_text_edits=[
179
+ TextEdit(
180
+ new_text=f"from {symbol.parent_module} import {symbol.name}\n",
181
+ range=Range(
182
+ start=Position(line=0, character=0),
183
+ end=Position(line=0, character=0),
184
+ ),
185
+ ),
186
+ ],
187
+ ),
188
+ )
189
+
190
+ logger.debug("Generated {} completions.", len(items))
191
+ return CompletionList(is_incomplete=False, items=items)
192
+
193
+
194
+ def start_server():
195
+ server = ImportCompleterLanguageServer(
196
+ "import-completer", get_version(), config=Config.default()
197
+ )
198
+ logger.info("Starting ImportCompleter language server...")
199
+ server.start_io()
200
+
201
+
202
+ if __name__ == "__main__":
203
+ start_server()
@@ -0,0 +1,28 @@
1
+ import subprocess
2
+ import sys
3
+ from collections.abc import Iterable
4
+ from pathlib import Path
5
+
6
+
7
+ def discover_paths_for_python_executable(
8
+ python_executable: str = "python",
9
+ ) -> Iterable[Path]:
10
+ output = subprocess.check_output(
11
+ [python_executable, "-c", r"import sys; print('\n'.join(sys.path))"]
12
+ )
13
+ for output_line in output.decode().splitlines():
14
+ path = Path(output_line).absolute()
15
+ if path.is_dir():
16
+ yield path
17
+
18
+
19
+ def get_internal_python_paths() -> Iterable[Path]:
20
+ return (Path(p) for p in sys.path)
21
+
22
+
23
+ if __name__ == "__main__":
24
+ import sys
25
+
26
+ python_executable = sys.argv[1] if len(sys.argv) > 1 else sys.executable
27
+ for path in discover_paths_for_python_executable(python_executable):
28
+ print(path)
@@ -0,0 +1,98 @@
1
+ from collections.abc import AsyncIterable, Iterable
2
+ from pathlib import Path
3
+
4
+ import aiosqlite
5
+ from loguru import logger
6
+
7
+ from import_completer.types import ScannedSymbol
8
+
9
+ DB_SYMBOLS_NAME = "symbols"
10
+ DB_SYMBOLS_SCHEMA = [
11
+ ("generation", "INTEGER"),
12
+ ("kind", "TEXT"),
13
+ ("name", "TEXT"),
14
+ ("origin_index", "INTEGER"),
15
+ ("parent_module", "TEXT"),
16
+ ("metadata_json", "TEXT"),
17
+ ]
18
+ DB_INDICES = [
19
+ ("idx_symbols_name", DB_SYMBOLS_NAME, ["name"]),
20
+ ("idx_symbols_parent_module", DB_SYMBOLS_NAME, ["origin_index", "parent_module"]),
21
+ ]
22
+
23
+
24
+ class DatabaseHandler:
25
+ def __init__(self, database_path: str | Path = ":memory:") -> None:
26
+ self._database_path = database_path
27
+ self._connection = None
28
+
29
+ async def __aenter__(self):
30
+ self._connection = await aiosqlite.connect(str(self._database_path))
31
+ return self
32
+
33
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
34
+ if self._connection:
35
+ await self._connection.close()
36
+
37
+ async def create_schema(self) -> None:
38
+ assert self._connection is not None, "Database connection not initialized!"
39
+ await self._connection.execute(
40
+ f"CREATE TABLE IF NOT EXISTS {DB_SYMBOLS_NAME} ({', '.join(f'{name} {type}' for name, type in DB_SYMBOLS_SCHEMA)})"
41
+ )
42
+ await self._connection.commit()
43
+
44
+ async def create_indices(self) -> None:
45
+ assert self._connection is not None, "Database connection not initialized!"
46
+ for index_name, table_name, index_columns in DB_INDICES:
47
+ await self._connection.execute(
48
+ f"CREATE INDEX IF NOT EXISTS {index_name} ON {table_name} ({', '.join(index_columns)})"
49
+ )
50
+ await self._connection.commit()
51
+
52
+ async def drop_indices(self) -> None:
53
+ assert self._connection is not None, "Database connection not initialized!"
54
+ for index_name, _, _ in DB_INDICES:
55
+ await self._connection.execute(f"DROP INDEX IF EXISTS {index_name}")
56
+ await self._connection.commit()
57
+
58
+ async def perform_rebuild_for_origin(
59
+ self, origin_index: int, symbols: Iterable[ScannedSymbol]
60
+ ) -> None:
61
+ assert self._connection is not None, "Database connection not initialized!"
62
+
63
+ async with self._connection.cursor() as cursor:
64
+ await cursor.execute("BEGIN")
65
+ logger.info(f"Rebuilding symbols for origin_index: {origin_index}")
66
+ await cursor.execute(
67
+ f"""
68
+ DELETE FROM {DB_SYMBOLS_NAME} WHERE origin_index = ?
69
+ """,
70
+ (origin_index,),
71
+ )
72
+ await cursor.executemany(
73
+ """
74
+ INSERT INTO symbols (generation, kind, name, origin_index, parent_module, metadata_json)
75
+ VALUES (1, ?, ?, ?, ?, ?)
76
+ """,
77
+ (
78
+ (symbol.kind, symbol.name, origin_index, symbol.parent_module, "")
79
+ for symbol in symbols
80
+ ),
81
+ )
82
+ logger.info(
83
+ f"Added {cursor.rowcount} symbols for origin_index: {origin_index}"
84
+ )
85
+ await cursor.execute("COMMIT")
86
+
87
+ async def lookup_symbol_name(self, name_part: str) -> AsyncIterable[ScannedSymbol]:
88
+ assert self._connection is not None, "Database connection not initialized!"
89
+ cursor = await self._connection.execute(
90
+ """
91
+ SELECT kind, name, parent_module
92
+ FROM symbols
93
+ WHERE name >= ? AND name < ?
94
+ """,
95
+ (f"{name_part}", f"{name_part}~"),
96
+ )
97
+ async for row in cursor:
98
+ yield ScannedSymbol(*row, metadata={})
@@ -0,0 +1,8 @@
1
+ from typing import Literal, NamedTuple
2
+
3
+
4
+ class ScannedSymbol(NamedTuple):
5
+ kind: Literal["variable", "function", "class"] # TODO: add support for modules
6
+ name: str
7
+ parent_module: str
8
+ metadata: dict
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: import-completer
3
+ Version: 0.4.0
4
+ Requires-Python: >=3.13
5
+ Requires-Dist: aiosqlite==0.21.0
6
+ Requires-Dist: click>=8.0.0
7
+ Requires-Dist: loguru==0.7.3
8
+ Requires-Dist: pygls==2.0.0
9
+ Requires-Dist: tree-sitter-python==0.23.6
10
+ Requires-Dist: tree-sitter==0.24.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
13
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
@@ -0,0 +1,12 @@
1
+ import_completer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ import_completer/code_scanner.py,sha256=99P9Zn2PVN-JUV6jq1ATExfRA7xU3394ZO_Pntl5c-M,6256
3
+ import_completer/completion_engine.py,sha256=R8QhTsdU7DUfbiINpfGz-7azncDj1hWPFEyTzwb1I14,2929
4
+ import_completer/config.py,sha256=EQJdEPwiTjD52rsqPuPipHRvG8_V3LpYZmJK8Xf6Kzo,637
5
+ import_completer/language_server.py,sha256=uM3kgaRfM_mMvR7nGTNJuOKmiS9oGvDPQf5CasonVdo,6764
6
+ import_completer/path_discovery.py,sha256=QFtzI229QR_4hkww8EAShbLOBCWadbFOTKfvuVK57vk,769
7
+ import_completer/symbols_database.py,sha256=xhnqXc094doy1V4cVJqkuqAXFHMvAkpMpWCP1rBiArw,3677
8
+ import_completer/types.py,sha256=KYpDfQ6MTPCD1S2zAO81nXWLr-yvW3ZbC3MGGYdArrk,214
9
+ import_completer-0.4.0.dist-info/METADATA,sha256=7UK9c9gslfBpR4R5cDtQAugSlCMGae4bQazjZyJIlMU,398
10
+ import_completer-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ import_completer-0.4.0.dist-info/entry_points.txt,sha256=TVRn0ssT9CrESNAP4vRIsEqykaHVsrGC2dKKH-XcYKk,93
12
+ import_completer-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ py-import-completer-server = import_completer.language_server:start_server