Python’s site module configures the interpreter’s module search path at startup, handling site-packages directories and .pth files. import site. getsitepackages: site.getsitepackages() → list of global site-packages directories; not available inside virtualenvs (raises AttributeError) — use sysconfig.get_path("purelib") as fallback. getusersitepackages: site.getusersitepackages() → ~/.local/lib/python3.12/site-packages (Unix) or %APPDATA%\Python\PythonXY\site-packages (Windows). getuserbase: site.getuserbase() → user base dir, e.g. ~/.local. addsitedir: site.addsitedir("/extra/pkgs") — adds the directory to sys.path and processes any .pth files in it. addpackage: site.addpackage(sitedir, pth_file, known_paths) — low-level .pth processor. ENABLE_USER_SITE: site.ENABLE_USER_SITE — True if user site-packages are active (False in venvs without --system-site-packages). PREFIXES: site.PREFIXES — list [sys.prefix, sys.exec_prefix]. main: site.main() — re-run the site initialization. getsitepackages / getusersitepackages raise AttributeError in venvs without those attributes — always check. -S flag: python -S starts the interpreter without running site.py. Claude Code generates sys.path auditors, site-packages finders, .pth file managers, and venv bootstrap helpers.
CLAUDE.md for site
## site Stack
- Stdlib: import site, sys
- Global: site.getsitepackages() # list of dirs (not in venvs)
- User: site.getusersitepackages() # ~/.local/lib/pythonX.Y/site-packages
- Base: site.getuserbase() # ~/.local
- Add: site.addsitedir("/extra") # add dir + process .pth files
- Active: site.ENABLE_USER_SITE # False if venv without system pkgs
- Guard: getattr(site, "getsitepackages", lambda: [])()
site sys.path Management Pipeline
# app/siteutil.py — site-packages, sys.path, pth files, venv probe, addsitedir
from __future__ import annotations
import os
import site
import sys
import sysconfig
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Site-packages discovery
# ─────────────────────────────────────────────────────────────────────────────
def global_site_packages() -> list[Path]:
"""
Return global (system/venv) site-packages directories.
Falls back to sysconfig if site.getsitepackages() is unavailable.
Example:
for d in global_site_packages():
print(d)
"""
try:
return [Path(p) for p in site.getsitepackages()]
except AttributeError:
# Inside a virtualenv that patched out getsitepackages
purelib = sysconfig.get_path("purelib")
platlib = sysconfig.get_path("platlib")
dirs = list({purelib, platlib} - {None})
return [Path(d) for d in dirs if d]
def user_site_packages() -> Path:
"""
Return the user site-packages directory.
Example:
user_pkgs = user_site_packages()
print(user_pkgs.exists())
"""
return Path(site.getusersitepackages())
def user_base() -> Path:
"""Return the user base directory (~/.local on Unix)."""
return Path(site.getuserbase())
def all_site_packages(include_user: bool = True) -> list[Path]:
"""
Return all site-packages directories: global + optionally user.
Example:
for d in all_site_packages():
print(d, d.exists())
"""
dirs = global_site_packages()
if include_user and getattr(site, "ENABLE_USER_SITE", False):
user = user_site_packages()
if user not in dirs:
dirs.append(user)
return dirs
# ─────────────────────────────────────────────────────────────────────────────
# 2. sys.path inspection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class PathEntry:
path: Path
kind: str # "site-packages", "stdlib", "user-site", "zip", "cwd", "other"
exists: bool
def __str__(self) -> str:
return f" [{self.kind:14s}] {'✓' if self.exists else '✗'} {self.path}"
def classify_sys_path() -> list[PathEntry]:
"""
Annotate each entry in sys.path with its kind and existence.
Example:
for e in classify_sys_path():
print(e)
"""
stdlib = sysconfig.get_path("stdlib") or ""
global_sp = {str(p) for p in global_site_packages()}
try:
user_sp = str(user_site_packages())
except Exception:
user_sp = ""
entries: list[PathEntry] = []
for raw in sys.path:
p = Path(raw) if raw else Path(".")
s = str(raw)
if s.endswith(".zip") or s.endswith(".egg"):
kind = "zip"
elif s == "":
kind = "cwd"
elif s in global_sp:
kind = "site-packages"
elif s == user_sp:
kind = "user-site"
elif stdlib and s.startswith(stdlib):
kind = "stdlib"
else:
kind = "other"
entries.append(PathEntry(path=p, kind=kind, exists=p.exists()))
return entries
def sys_path_has(directory: str | Path) -> bool:
"""Return True if directory (or its string form) is in sys.path."""
target = str(Path(directory))
return any(str(Path(p)) == target for p in sys.path if p)
def add_to_sys_path(directory: str | Path, position: int = 0) -> bool:
"""
Add directory to sys.path at position if not already present.
Uses site.addsitedir to also process any .pth files.
Returns True if the path was added.
Example:
add_to_sys_path("/opt/my-packages")
"""
d = Path(directory)
if sys_path_has(d):
return False
if position == -1:
site.addsitedir(str(d))
else:
sys.path.insert(position, str(d))
return True
# ─────────────────────────────────────────────────────────────────────────────
# 3. .pth file inspection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class PthFile:
path: Path
entries: list[str] # non-comment, non-empty lines
def __str__(self) -> str:
return f"{self.path.name} ({len(self.entries)} entries)"
def read_pth_file(pth_path: str | Path) -> PthFile:
"""
Parse a .pth file and return its path entries.
Example:
pth = read_pth_file("/usr/lib/python3/dist-packages/mylib.pth")
print(pth)
"""
p = Path(pth_path)
entries: list[str] = []
for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
line = line.strip()
if line and not line.startswith("#") and not line.startswith("import "):
entries.append(line)
return PthFile(path=p, entries=entries)
def find_pth_files(site_dir: str | Path | None = None) -> list[PthFile]:
"""
Find and parse all .pth files in a site-packages directory (or all of them).
Example:
for pth in find_pth_files():
print(pth)
for e in pth.entries:
print(f" {e}")
"""
if site_dir:
dirs = [Path(site_dir)]
else:
dirs = [p for p in global_site_packages() if p.exists()]
results: list[PthFile] = []
for d in dirs:
for pth_file in sorted(d.glob("*.pth")):
try:
results.append(read_pth_file(pth_file))
except OSError:
pass
return results
# ─────────────────────────────────────────────────────────────────────────────
# 4. Virtual environment detection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class VenvInfo:
in_venv: bool
venv_type: str # "venv", "virtualenv", "conda", "none"
prefix: str
base_prefix: str
user_site_active: bool
system_site_pkgs: bool # venv created with --system-site-packages?
def __str__(self) -> str:
if not self.in_venv:
return f"Not in a virtual environment (prefix: {self.prefix})"
return (
f"venv type: {self.venv_type}\n"
f"prefix: {self.prefix}\n"
f"base_prefix: {self.base_prefix}\n"
f"user-site: {'active' if self.user_site_active else 'disabled'}\n"
f"system-site-pkgs: {'yes' if self.system_site_pkgs else 'no'}"
)
def venv_info() -> VenvInfo:
"""
Detect whether the interpreter is inside a virtual environment and what kind.
Example:
info = venv_info()
print(info)
"""
base = getattr(sys, "base_prefix", sys.prefix)
real = getattr(sys, "real_prefix", None) # set by virtualenv
prefix = sys.prefix
if real is not None:
in_venv = True
venv_type = "virtualenv"
base_prefix = real
elif base != prefix:
in_venv = True
venv_type = "conda" if "conda" in prefix.lower() or "conda" in base.lower() else "venv"
base_prefix = base
else:
in_venv = False
venv_type = "none"
base_prefix = base
user_active = bool(getattr(site, "ENABLE_USER_SITE", False))
# system-site-packages: if both venv and base have the same site dir
system_sp = False
if in_venv:
try:
venv_sp = {str(p) for p in global_site_packages()}
sys_sp = str(Path(base_prefix) / "lib" /
f"python{sysconfig.get_python_version()}" / "site-packages")
system_sp = sys_sp in venv_sp
except Exception:
pass
return VenvInfo(
in_venv=in_venv,
venv_type=venv_type,
prefix=prefix,
base_prefix=base_prefix,
user_site_active=user_active,
system_site_pkgs=system_sp,
)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Installed package finder
# ─────────────────────────────────────────────────────────────────────────────
def find_dist_info(
site_dirs: list[Path] | None = None,
) -> list[tuple[str, str, Path]]:
"""
Scan site-packages for installed packages via .dist-info directories.
Returns [(name, version, dist_info_path), ...] sorted by name.
Example:
for name, ver, path in find_dist_info()[:10]:
print(f" {name}=={ver}")
"""
dirs = site_dirs or [p for p in all_site_packages() if p.exists()]
results: list[tuple[str, str, Path]] = []
seen: set[str] = set()
for d in dirs:
for di in sorted(d.glob("*.dist-info")):
meta_file = di / "METADATA"
if not meta_file.exists():
meta_file = di / "PKG-INFO"
if not meta_file.exists():
continue
name = ver = ""
try:
for line in meta_file.read_text(encoding="utf-8", errors="replace").splitlines():
if line.startswith("Name:"):
name = line.split(":", 1)[1].strip()
elif line.startswith("Version:"):
ver = line.split(":", 1)[1].strip()
if name and ver:
break
except OSError:
continue
key = name.lower()
if key and key not in seen:
seen.add(key)
results.append((name, ver, di))
results.sort(key=lambda t: t[0].lower())
return results
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== site demo ===")
# ── site-packages directories ──────────────────────────────────────────────
print("\n--- global_site_packages ---")
for d in global_site_packages():
print(f" {d} exists={d.exists()}")
print(f"\n--- user_site_packages: {user_site_packages()} ---")
print(f"--- user_base: {user_base()} ---")
print(f"--- ENABLE_USER_SITE: {getattr(site, 'ENABLE_USER_SITE', 'N/A')} ---")
# ── classify_sys_path ──────────────────────────────────────────────────────
print("\n--- classify_sys_path ---")
path_entries = classify_sys_path()
for e in path_entries[:8]:
print(e)
if len(path_entries) > 8:
print(f" ... ({len(path_entries)} total entries)")
# ── venv detection ─────────────────────────────────────────────────────────
print("\n--- venv_info ---")
print(venv_info())
# ── .pth files ─────────────────────────────────────────────────────────────
print("\n--- find_pth_files (first 5) ---")
pth_files = find_pth_files()
for pth in pth_files[:5]:
print(f" {pth}")
if pth_files:
print(f" ... ({len(pth_files)} total .pth files)")
# ── find_dist_info ─────────────────────────────────────────────────────────
print("\n--- find_dist_info (first 8) ---")
packages = find_dist_info()
for name, ver, _ in packages[:8]:
print(f" {name}=={ver}")
print(f" ... ({len(packages)} total installed packages)")
print("\n=== done ===")
For the sysconfig alternative — sysconfig.get_path("purelib") and sysconfig.get_paths() return the canonical install paths for the active scheme without relying on site.getsitepackages(), which is absent in some virtualenvs — use sysconfig when you need scheme-aware path resolution for packaging tools, build systems, or C extension compilation; use site when you need the actual live state of sys.path (including paths added by .pth files and addsitedir() calls), or when you want to inspect user-site enablement (ENABLE_USER_SITE) and process .pth files at runtime. For the importlib.metadata / pip alternative — importlib.metadata.packages_distributions() and importlib.metadata.distributions() are the modern way to enumerate installed packages and read their metadata without filesystem scanning — use importlib.metadata when you need version strings, entry points, or dependency metadata for a specific package by name; use site-based .dist-info scanning when you want a raw filesystem audit of what is installed (independent of the import system) or when you need to detect packages installed in unusual locations not on sys.path. The Claude Skills 360 bundle includes site skill sets covering global_site_packages()/user_site_packages()/user_base()/all_site_packages() directory finders, PathEntry with classify_sys_path()/sys_path_has()/add_to_sys_path() path managers, PthFile with read_pth_file()/find_pth_files() .pth parsers, VenvInfo with venv_info() virtual environment detector, and find_dist_info() installed package scanner. Start with the free tier to try sys.path management patterns and site pipeline code generation.