Skip to content

Commit f5da89a

Browse files
Modify VS Code settings for efficiency
1 parent 9889d78 commit f5da89a

File tree

4 files changed

+562
-3
lines changed

4 files changed

+562
-3
lines changed

.vscode/settings.json

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,41 @@
88
"./.venv/Scripts/python.exe"
99
],
1010
"jupyter.kernels.excludePythonEnvironments": [
11-
"apim-samples"
11+
"**/anaconda3/**",
12+
"**/conda/**",
13+
"**/miniconda3/**",
14+
"**/python3.*",
15+
"*/site-packages/*",
16+
"/bin/python",
17+
"/bin/python3",
18+
"/opt/python/*/bin/python*",
19+
"/usr/bin/python",
20+
"/usr/bin/python3",
21+
"/usr/local/bin/python",
22+
"/usr/local/bin/python3",
23+
"python",
24+
"python3",
25+
"**/.venv/**/python*",
26+
"**/Scripts/python*",
27+
"**/bin/python*"
28+
],
29+
"search.exclude": {
30+
"**/.venv": true,
31+
"**/.venv/**": true
32+
},
33+
"files.watcherExclude": {
34+
"**/.venv/**": true
35+
},
36+
"files.exclude": {
37+
"**/.venv": true
38+
},
39+
"python.analysis.exclude": [
40+
"**/node_modules",
41+
"**/__pycache__",
42+
".git",
43+
"**/build",
44+
"env/**",
45+
"**/.venv/**"
1246
],
1347
"files.trimTrailingWhitespace": true,
1448
"files.insertFinalNewline": true,
@@ -32,5 +66,11 @@
3266
"plantuml.exportFormat": "svg",
3367
"plantuml.java": "C:\\Program Files\\OpenJDK\\jdk-22.0.2\\bin\\java.exe",
3468
"plantuml.diagramsRoot": "assets/diagrams/src",
35-
"plantuml.exportOutDir": "assets/diagrams/out"
36-
}
69+
"plantuml.exportOutDir": "assets/diagrams/out",
70+
"jupyter.kernels.filter": [
71+
{
72+
"path": "apim-samples",
73+
"type": "pythonEnvironment"
74+
}
75+
]
76+
}

setup/setup_python_path.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,75 @@
2121
from pathlib import Path # Cross-platform path handling (Windows: \, Unix: /)
2222

2323

24+
DEFAULT_VSCODE_SEARCH_EXCLUDE = {
25+
"**/.venv": True,
26+
"**/.venv/**": True,
27+
}
28+
29+
DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE = {
30+
"**/.venv/**": True,
31+
}
32+
33+
DEFAULT_VSCODE_FILES_EXCLUDE = {
34+
"**/.venv": True,
35+
}
36+
37+
DEFAULT_PYTHON_ANALYSIS_EXCLUDE = [
38+
"**/node_modules",
39+
"**/__pycache__",
40+
".git",
41+
"**/build",
42+
"env/**",
43+
"**/.venv/**",
44+
]
45+
46+
47+
def _merge_bool_map(existing: object, required: dict[str, bool]) -> dict[str, bool]:
48+
"""Merge boolean map settings while enforcing required keys.
49+
50+
For VS Code exclude maps, required keys are forced to True.
51+
"""
52+
53+
if isinstance(existing, dict):
54+
merged: dict[str, bool] = {str(k): bool(v) for k, v in existing.items()}
55+
else:
56+
merged = {}
57+
58+
for key in required:
59+
merged[key] = True
60+
61+
return merged
62+
63+
64+
def _normalize_string_list(value: object) -> list[str]:
65+
if value is None:
66+
return []
67+
if isinstance(value, list):
68+
return [str(item) for item in value if str(item).strip()]
69+
if isinstance(value, str) and value.strip():
70+
return [value]
71+
return []
72+
73+
74+
def _merge_string_list(existing: object, required: list[str]) -> list[str]:
75+
"""Merge lists while preserving order and avoiding duplicates.
76+
77+
Required items come first, followed by any existing items.
78+
"""
79+
80+
existing_list = _normalize_string_list(existing)
81+
merged: list[str] = []
82+
83+
for item in required:
84+
if item not in merged:
85+
merged.append(item)
86+
for item in existing_list:
87+
if item not in merged:
88+
merged.append(item)
89+
90+
return merged
91+
92+
2493
def get_project_root() -> Path:
2594
"""
2695
Get the absolute path to the project root directory.
@@ -183,6 +252,8 @@ def create_vscode_settings():
183252
vscode_dir.mkdir(exist_ok=True)
184253

185254
# Settings to update for kernel and Python configuration
255+
# Note: exclude settings (search/files watcher/Pylance) are merged separately
256+
# to avoid clobbering any user customizations.
186257
required_settings = {
187258
"files.trimTrailingWhitespace": True,
188259
"files.insertFinalNewline": True,
@@ -246,6 +317,24 @@ def create_vscode_settings():
246317
# Merge required settings with existing ones
247318
existing_settings.update(required_settings)
248319

320+
# Merge performance excludes without overwriting other patterns
321+
existing_settings["search.exclude"] = _merge_bool_map(
322+
existing_settings.get("search.exclude"),
323+
DEFAULT_VSCODE_SEARCH_EXCLUDE,
324+
)
325+
existing_settings["files.watcherExclude"] = _merge_bool_map(
326+
existing_settings.get("files.watcherExclude"),
327+
DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE,
328+
)
329+
existing_settings["files.exclude"] = _merge_bool_map(
330+
existing_settings.get("files.exclude"),
331+
DEFAULT_VSCODE_FILES_EXCLUDE,
332+
)
333+
existing_settings["python.analysis.exclude"] = _merge_string_list(
334+
existing_settings.get("python.analysis.exclude"),
335+
DEFAULT_PYTHON_ANALYSIS_EXCLUDE,
336+
)
337+
249338
# Write back the merged settings
250339
with open(settings_file, 'w', encoding='utf-8') as f:
251340
json.dump(existing_settings, f, indent=4)
@@ -254,6 +343,7 @@ def create_vscode_settings():
254343
print(" - Existing settings preserved")
255344
print(" - Default kernel set to 'apim-samples'")
256345
print(" - Python interpreter configured for .venv")
346+
print(" - .venv excluded from search/watcher/Pylance indexing")
257347

258348
except (json.JSONDecodeError, IOError):
259349
print("⚠️ Existing settings.json has comments or formatting issues")
@@ -265,12 +355,18 @@ def create_vscode_settings():
265355
else:
266356
# Create new settings file
267357
try:
358+
required_settings["search.exclude"] = DEFAULT_VSCODE_SEARCH_EXCLUDE
359+
required_settings["files.watcherExclude"] = DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE
360+
required_settings["files.exclude"] = DEFAULT_VSCODE_FILES_EXCLUDE
361+
required_settings["python.analysis.exclude"] = DEFAULT_PYTHON_ANALYSIS_EXCLUDE
362+
268363
with open(settings_file, 'w', encoding='utf-8') as f:
269364
json.dump(required_settings, f, indent=4)
270365

271366
print(f"✅ VS Code settings created: {settings_file}")
272367
print(" - Default kernel set to 'apim-samples'")
273368
print(" - Python interpreter configured for .venv")
369+
print(" - .venv excluded from search/watcher/Pylance indexing")
274370
except (ImportError, IOError) as e:
275371
print(f"❌ Failed to create VS Code settings: {e}")
276372
return False
@@ -365,6 +461,13 @@ def force_kernel_consistency():
365461
]
366462
}
367463

464+
performance_exclude_settings = {
465+
"search.exclude": DEFAULT_VSCODE_SEARCH_EXCLUDE,
466+
"files.watcherExclude": DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE,
467+
"files.exclude": DEFAULT_VSCODE_FILES_EXCLUDE,
468+
"python.analysis.exclude": DEFAULT_PYTHON_ANALYSIS_EXCLUDE,
469+
}
470+
368471
try:
369472
# Read existing settings or create new ones
370473
existing_settings = {}
@@ -378,6 +481,24 @@ def force_kernel_consistency():
378481
# Merge settings, with our strict kernel settings taking priority
379482
existing_settings.update(strict_kernel_settings)
380483

484+
# Merge performance excludes without clobbering user patterns
485+
existing_settings["search.exclude"] = _merge_bool_map(
486+
existing_settings.get("search.exclude"),
487+
performance_exclude_settings["search.exclude"],
488+
)
489+
existing_settings["files.watcherExclude"] = _merge_bool_map(
490+
existing_settings.get("files.watcherExclude"),
491+
performance_exclude_settings["files.watcherExclude"],
492+
)
493+
existing_settings["files.exclude"] = _merge_bool_map(
494+
existing_settings.get("files.exclude"),
495+
performance_exclude_settings["files.exclude"],
496+
)
497+
existing_settings["python.analysis.exclude"] = _merge_string_list(
498+
existing_settings.get("python.analysis.exclude"),
499+
performance_exclude_settings["python.analysis.exclude"],
500+
)
501+
381502
# Write updated settings
382503
with open(settings_file, 'w', encoding='utf-8') as f:
383504
json.dump(existing_settings, f, indent=4)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""Unit tests for setup_python_path VS Code settings generation."""
2+
3+
from __future__ import annotations
4+
5+
import importlib
6+
import json
7+
import sys
8+
from pathlib import Path
9+
from types import ModuleType
10+
from typing import TYPE_CHECKING, cast
11+
12+
import pytest
13+
14+
# Ensure the setup folder is on sys.path so the setup script is importable.
15+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
16+
SETUP_PATH = PROJECT_ROOT / "setup"
17+
if str(SETUP_PATH) not in sys.path:
18+
sys.path.insert(0, str(SETUP_PATH))
19+
20+
if TYPE_CHECKING: # pragma: no cover
21+
sps = cast(ModuleType, None)
22+
else:
23+
sps = cast(ModuleType, importlib.import_module("setup_python_path"))
24+
25+
26+
@pytest.fixture
27+
def temp_project_root(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
28+
"""Create a temp project root and force setup script to use it."""
29+
30+
# The script expects these indicator files to exist at project root.
31+
(tmp_path / "README.md").write_text("x", encoding="utf-8")
32+
(tmp_path / "requirements.txt").write_text("x", encoding="utf-8")
33+
(tmp_path / "bicepconfig.json").write_text("{}", encoding="utf-8")
34+
35+
monkeypatch.setattr(sps, "get_project_root", lambda: tmp_path)
36+
return tmp_path
37+
38+
39+
def _read_settings(project_root: Path) -> dict:
40+
settings_path = project_root / ".vscode" / "settings.json"
41+
return json.loads(settings_path.read_text(encoding="utf-8"))
42+
43+
44+
def test_create_vscode_settings_creates_perf_excludes(temp_project_root: Path) -> None:
45+
assert sps.create_vscode_settings() is True
46+
47+
settings = _read_settings(temp_project_root)
48+
49+
assert settings["search.exclude"]["**/.venv"] is True
50+
assert settings["search.exclude"]["**/.venv/**"] is True
51+
assert settings["files.watcherExclude"]["**/.venv/**"] is True
52+
assert settings["files.exclude"]["**/.venv"] is True
53+
54+
assert settings["python.analysis.exclude"][: len(sps.DEFAULT_PYTHON_ANALYSIS_EXCLUDE)] == sps.DEFAULT_PYTHON_ANALYSIS_EXCLUDE
55+
56+
57+
def test_create_vscode_settings_merges_excludes(temp_project_root: Path) -> None:
58+
vscode_dir = temp_project_root / ".vscode"
59+
vscode_dir.mkdir(parents=True)
60+
61+
(vscode_dir / "settings.json").write_text(
62+
json.dumps(
63+
{
64+
"search.exclude": {"custom/**": True, "**/.venv": False},
65+
"python.analysis.exclude": ["custom2/**", "**/__pycache__"],
66+
},
67+
indent=4,
68+
),
69+
encoding="utf-8",
70+
)
71+
72+
assert sps.create_vscode_settings() is True
73+
74+
settings = _read_settings(temp_project_root)
75+
76+
# Required keys forced on, custom preserved
77+
assert settings["search.exclude"]["custom/**"] is True
78+
assert settings["search.exclude"]["**/.venv"] is True
79+
assert settings["search.exclude"]["**/.venv/**"] is True
80+
81+
# Required patterns come first, custom patterns preserved afterwards
82+
assert settings["python.analysis.exclude"][: len(sps.DEFAULT_PYTHON_ANALYSIS_EXCLUDE)] == sps.DEFAULT_PYTHON_ANALYSIS_EXCLUDE
83+
assert "custom2/**" in settings["python.analysis.exclude"]
84+
85+
86+
def test_force_kernel_consistency_merges_excludes(temp_project_root: Path, monkeypatch: pytest.MonkeyPatch) -> None:
87+
vscode_dir = temp_project_root / ".vscode"
88+
vscode_dir.mkdir(parents=True)
89+
90+
(vscode_dir / "settings.json").write_text(
91+
json.dumps(
92+
{
93+
"search.exclude": {"**/.venv/**": False},
94+
"files.watcherExclude": {"other/**": True},
95+
"python.analysis.exclude": ["custom3/**"],
96+
},
97+
indent=4,
98+
),
99+
encoding="utf-8",
100+
)
101+
102+
# Avoid calling jupyter/kernelspec subprocesses in tests.
103+
monkeypatch.setattr(sps, "validate_kernel_setup", lambda: True)
104+
105+
assert sps.force_kernel_consistency() is True
106+
107+
settings = _read_settings(temp_project_root)
108+
109+
assert settings["search.exclude"]["**/.venv"] is True
110+
assert settings["search.exclude"]["**/.venv/**"] is True
111+
assert settings["files.watcherExclude"]["**/.venv/**"] is True
112+
assert settings["files.watcherExclude"]["other/**"] is True
113+
114+
assert settings["python.analysis.exclude"][: len(sps.DEFAULT_PYTHON_ANALYSIS_EXCLUDE)] == sps.DEFAULT_PYTHON_ANALYSIS_EXCLUDE
115+
assert "custom3/**" in settings["python.analysis.exclude"]

0 commit comments

Comments
 (0)