vscode-common-python-lsp 0.3.0__tar.gz → 0.5.0__tar.gz

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 (37) hide show
  1. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/PKG-INFO +1 -1
  2. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/pyproject.toml +1 -1
  3. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_process_runner.py +130 -1
  4. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/__init__.py +8 -1
  5. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/process_runner.py +65 -0
  6. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp.egg-info/PKG-INFO +1 -1
  7. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/README.md +0 -0
  8. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/setup.cfg +0 -0
  9. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_code_actions.py +0 -0
  10. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_context.py +0 -0
  11. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_debug.py +0 -0
  12. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_diagnostics.py +0 -0
  13. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_formatting.py +0 -0
  14. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_jsonrpc.py +0 -0
  15. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_linting.py +0 -0
  16. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_notebook.py +0 -0
  17. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_package.py +0 -0
  18. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_paths.py +0 -0
  19. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_runner.py +0 -0
  20. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_server.py +0 -0
  21. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/tests/test_version.py +0 -0
  22. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/code_actions.py +0 -0
  23. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/context.py +0 -0
  24. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/debug.py +0 -0
  25. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/diagnostics.py +0 -0
  26. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/formatting.py +0 -0
  27. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/jsonrpc.py +0 -0
  28. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/linting.py +0 -0
  29. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/notebook.py +0 -0
  30. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/paths.py +0 -0
  31. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/runner.py +0 -0
  32. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/server.py +0 -0
  33. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp/version.py +0 -0
  34. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp.egg-info/SOURCES.txt +0 -0
  35. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp.egg-info/dependency_links.txt +0 -0
  36. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp.egg-info/requires.txt +0 -0
  37. {vscode_common_python_lsp-0.3.0 → vscode_common_python_lsp-0.5.0}/vscode_common_python_lsp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vscode-common-python-lsp
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Shared Python utilities for VS Code Python tool extensions
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vscode-common-python-lsp"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "Shared Python utilities for VS Code Python tool extensions"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -8,7 +8,12 @@ import unittest
8
8
  from dataclasses import dataclass
9
9
  from unittest.mock import MagicMock
10
10
 
11
- from vscode_common_python_lsp.process_runner import run_message_loop, update_sys_path
11
+ from vscode_common_python_lsp.process_runner import (
12
+ resolve_bundle_path,
13
+ run_message_loop,
14
+ update_environ_path,
15
+ update_sys_path,
16
+ )
12
17
 
13
18
 
14
19
  class TestUpdateSysPath(unittest.TestCase):
@@ -60,6 +65,54 @@ class TestUpdateSysPath(unittest.TestCase):
60
65
  sys.path[:] = original
61
66
 
62
67
 
68
+ class TestUpdateEnvironPath(unittest.TestCase):
69
+ """Tests for update_environ_path."""
70
+
71
+ def test_adds_scripts_to_path(self):
72
+ import sysconfig
73
+
74
+ scripts = sysconfig.get_path("scripts")
75
+ if not scripts:
76
+ self.skipTest("sysconfig does not report scripts path")
77
+
78
+ # Remove scripts from PATH if present
79
+ original_env = os.environ.copy()
80
+ path_var = "PATH" if "PATH" in os.environ else "Path"
81
+ paths = os.environ.get(path_var, "").split(os.pathsep)
82
+ paths = [p for p in paths if p != scripts]
83
+ os.environ[path_var] = os.pathsep.join(paths)
84
+
85
+ try:
86
+ update_environ_path()
87
+ new_paths = os.environ[path_var].split(os.pathsep)
88
+ assert scripts in new_paths
89
+ assert new_paths[0] == scripts
90
+ finally:
91
+ os.environ.clear()
92
+ os.environ.update(original_env)
93
+
94
+ def test_does_not_duplicate(self):
95
+ import sysconfig
96
+
97
+ scripts = sysconfig.get_path("scripts")
98
+ if not scripts:
99
+ self.skipTest("sysconfig does not report scripts path")
100
+
101
+ original_env = os.environ.copy()
102
+ path_var = "PATH" if "PATH" in os.environ else "Path"
103
+ # Ensure scripts is already in PATH
104
+ os.environ[path_var] = scripts + os.pathsep + os.environ.get(path_var, "")
105
+
106
+ try:
107
+ count_before = os.environ[path_var].split(os.pathsep).count(scripts)
108
+ update_environ_path()
109
+ count_after = os.environ[path_var].split(os.pathsep).count(scripts)
110
+ assert count_after == count_before
111
+ finally:
112
+ os.environ.clear()
113
+ os.environ.update(original_env)
114
+
115
+
63
116
  @dataclass
64
117
  class _MockResult:
65
118
  """Minimal result object for testing."""
@@ -254,5 +307,81 @@ class TestRunMessageLoop(unittest.TestCase):
254
307
  assert "Unknown method: unknown_method" in sent["error"]
255
308
 
256
309
 
310
+ class TestBootstrapSysPath(unittest.TestCase):
311
+ """Tests for resolve_bundle_path (and its alias bootstrap_sys_path)."""
312
+
313
+ def test_adds_tool_and_libs_dirs(self):
314
+ """resolve_bundle_path adds both tool/ and libs/ to sys.path."""
315
+ import tempfile
316
+
317
+ original = sys.path[:]
318
+ with tempfile.TemporaryDirectory() as tmp:
319
+ # Create the expected directory layout:
320
+ # <tmp>/tool/lsp_server.py
321
+ # <tmp>/libs/
322
+ tool_dir = os.path.join(tmp, "tool")
323
+ libs_dir = os.path.join(tmp, "libs")
324
+ os.makedirs(tool_dir)
325
+ os.makedirs(libs_dir)
326
+ script = os.path.join(tool_dir, "lsp_server.py")
327
+ open(script, "w").close()
328
+
329
+ try:
330
+ result = resolve_bundle_path(script)
331
+ assert result == tmp
332
+ assert tool_dir in sys.path
333
+ assert libs_dir in sys.path
334
+ # Both should be at the front of sys.path (before original entries)
335
+ assert sys.path.index(tool_dir) < len(original)
336
+ assert sys.path.index(libs_dir) < len(original)
337
+ finally:
338
+ sys.path[:] = original
339
+
340
+ def test_returns_bundle_dir_path(self):
341
+ """resolve_bundle_path returns the bundle directory."""
342
+ import tempfile
343
+
344
+ original = sys.path[:]
345
+ with tempfile.TemporaryDirectory() as tmp:
346
+ tool_dir = os.path.join(tmp, "tool")
347
+ libs_dir = os.path.join(tmp, "libs")
348
+ os.makedirs(tool_dir)
349
+ os.makedirs(libs_dir)
350
+ script = os.path.join(tool_dir, "lsp_server.py")
351
+ open(script, "w").close()
352
+
353
+ try:
354
+ result = resolve_bundle_path(script)
355
+ assert result == tmp
356
+ finally:
357
+ sys.path[:] = original
358
+
359
+ def test_respects_ls_import_strategy_env(self):
360
+ """When LS_IMPORT_STRATEGY=fromEnvironment, libs are appended."""
361
+ import tempfile
362
+
363
+ original = sys.path[:]
364
+ original_env = os.environ.copy()
365
+ with tempfile.TemporaryDirectory() as tmp:
366
+ tool_dir = os.path.join(tmp, "tool")
367
+ libs_dir = os.path.join(tmp, "libs")
368
+ os.makedirs(tool_dir)
369
+ os.makedirs(libs_dir)
370
+ script = os.path.join(tool_dir, "lsp_server.py")
371
+ open(script, "w").close()
372
+
373
+ os.environ["LS_IMPORT_STRATEGY"] = "fromEnvironment"
374
+ try:
375
+ len_before = len(sys.path)
376
+ resolve_bundle_path(script)
377
+ assert libs_dir in sys.path
378
+ # libs should be appended after existing entries
379
+ assert sys.path.index(libs_dir) >= len_before
380
+ finally:
381
+ sys.path[:] = original
382
+ os.environ.clear()
383
+ os.environ.update(original_env)
384
+
385
+
257
386
  if __name__ == "__main__":
258
387
  unittest.main()
@@ -59,7 +59,12 @@ from .paths import (
59
59
  normalize_path,
60
60
  reset_caches,
61
61
  )
62
- from .process_runner import run_message_loop, update_sys_path
62
+ from .process_runner import (
63
+ resolve_bundle_path,
64
+ run_message_loop,
65
+ update_environ_path,
66
+ update_sys_path,
67
+ )
63
68
  from .runner import CustomIO, RunResult, run_api, run_module, run_path
64
69
  from .server import ToolServer, ToolServerConfig
65
70
  from .version import VersionInfo, check_min_version, extract_version, version_to_tuple
@@ -98,6 +103,8 @@ __all__ = [
98
103
  "shutdown_json_rpc",
99
104
  # process_runner
100
105
  "update_sys_path",
106
+ "update_environ_path",
107
+ "resolve_bundle_path",
101
108
  "run_message_loop",
102
109
  # debug
103
110
  "setup_debugpy",
@@ -5,7 +5,9 @@
5
5
  from __future__ import annotations
6
6
 
7
7
  import os
8
+ import pathlib
8
9
  import sys
10
+ import sysconfig
9
11
  import traceback
10
12
  from collections.abc import Callable
11
13
  from typing import TYPE_CHECKING
@@ -34,6 +36,69 @@ def update_sys_path(path_to_add: str, strategy: str) -> None:
34
36
  sys.path.append(path_to_add)
35
37
 
36
38
 
39
+ def resolve_bundle_path(script_file: str) -> str:
40
+ """Resolve the bundle directory and configure ``sys.path`` for a bundled LSP server.
41
+
42
+ Call this at the top of your ``lsp_server.py`` (before importing
43
+ any bundled libraries) to replace the standard 7-line boilerplate::
44
+
45
+ # Instead of:
46
+ BUNDLE_DIR = pathlib.Path(__file__).parent.parent
47
+ update_sys_path(os.fspath(BUNDLE_DIR / "tool"), "useBundled")
48
+ update_sys_path(
49
+ os.fspath(BUNDLE_DIR / "libs"),
50
+ os.getenv("LS_IMPORT_STRATEGY", "useBundled"),
51
+ )
52
+
53
+ # Use:
54
+ from vscode_common_python_lsp import resolve_bundle_path
55
+ BUNDLE_DIR = resolve_bundle_path(__file__)
56
+
57
+ Parameters
58
+ ----------
59
+ script_file:
60
+ The ``__file__`` of the calling script (expected to be at
61
+ ``<bundle>/tool/lsp_server.py``).
62
+
63
+ Returns
64
+ -------
65
+ str
66
+ The resolved bundle directory path (``<extension>/bundled/``),
67
+ for any further use by the caller.
68
+ """
69
+ bundle_dir = pathlib.Path(script_file).parent.parent
70
+ bundle_str = os.fspath(bundle_dir)
71
+
72
+ # Always put the tool directory first (bundled server modules)
73
+ update_sys_path(os.fspath(bundle_dir / "tool"), "useBundled")
74
+
75
+ # Libs follow the LS_IMPORT_STRATEGY env var
76
+ update_sys_path(
77
+ os.fspath(bundle_dir / "libs"),
78
+ os.getenv("LS_IMPORT_STRATEGY", "useBundled"),
79
+ )
80
+
81
+ return bundle_str
82
+
83
+
84
+ def update_environ_path() -> None:
85
+ """Update PATH environment variable with the ``scripts`` directory.
86
+
87
+ Ensures tool executables installed in the virtual environment's scripts
88
+ directory (``Scripts`` on Windows, ``bin`` on Unix) are discoverable.
89
+ """
90
+ scripts = sysconfig.get_path("scripts")
91
+ if not scripts:
92
+ return
93
+ for var_name in ("Path", "PATH"):
94
+ if var_name in os.environ:
95
+ paths = os.environ[var_name].split(os.pathsep)
96
+ if scripts not in paths:
97
+ paths.insert(0, scripts)
98
+ os.environ[var_name] = os.pathsep.join(paths)
99
+ break
100
+
101
+
37
102
  def run_message_loop(
38
103
  rpc: JsonRpc,
39
104
  run_fn: Callable[..., object],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vscode-common-python-lsp
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Shared Python utilities for VS Code Python tool extensions
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT