chore: import upstream snapshot with attribution
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
Executable
+217
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pre-commit hook to enforce using pathlib.Path instead of os.path.
|
||||
|
||||
This hook checks for os.path usage in Python files and suggests
|
||||
using pathlib.Path instead for better cross-platform compatibility
|
||||
and more modern Python code.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class OsPathChecker(ast.NodeVisitor):
|
||||
"""AST visitor to find os.path usage."""
|
||||
|
||||
def __init__(self, filename: str):
|
||||
self.filename = filename
|
||||
self.violations: list[tuple[int, str]] = []
|
||||
# Local names bound to the ``os`` module. Includes aliases from
|
||||
# ``import os as <alias>`` so they cannot bypass the check.
|
||||
self.os_module_names: set[str] = set()
|
||||
self.has_os_path_import = False
|
||||
|
||||
def visit_Import(self, node: ast.Import) -> None:
|
||||
"""Check for 'import os' statements (incl. ``import os as <alias>``)."""
|
||||
for alias in node.names:
|
||||
if alias.name == "os":
|
||||
self.os_module_names.add(alias.asname or "os")
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||||
"""Check for 'from os import path' or 'from os.path import ...' statements."""
|
||||
if node.module == "os" and any(
|
||||
alias.name == "path" for alias in node.names
|
||||
):
|
||||
self.has_os_path_import = True
|
||||
self.violations.append(
|
||||
(
|
||||
node.lineno,
|
||||
"Found 'from os import path' - use 'from pathlib import Path' instead",
|
||||
)
|
||||
)
|
||||
elif node.module == "os.path":
|
||||
self.has_os_path_import = True
|
||||
imported_names = [alias.name for alias in node.names]
|
||||
self.violations.append(
|
||||
(
|
||||
node.lineno,
|
||||
f"Found 'from os.path import {', '.join(imported_names)}' - use pathlib.Path methods instead",
|
||||
)
|
||||
)
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Attribute(self, node: ast.Attribute) -> None:
|
||||
"""Check for os.path.* usage."""
|
||||
if (
|
||||
isinstance(node.value, ast.Name)
|
||||
and node.value.id in self.os_module_names
|
||||
and node.attr == "path"
|
||||
):
|
||||
# This is os.path usage
|
||||
# Try to get the specific method being called
|
||||
parent = getattr(node, "parent", None)
|
||||
if parent and isinstance(parent, ast.Attribute):
|
||||
method = parent.attr
|
||||
# Skip os.path.expandvars as it has no pathlib equivalent
|
||||
if method == "expandvars":
|
||||
return
|
||||
suggestion = get_pathlib_equivalent(f"os.path.{method}")
|
||||
else:
|
||||
suggestion = "Use pathlib.Path instead"
|
||||
|
||||
self.violations.append(
|
||||
(node.lineno, f"Found os.path usage - {suggestion}")
|
||||
)
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Call(self, node: ast.Call) -> None:
|
||||
"""Check for direct calls to os.path functions."""
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
# Store parent reference for better context
|
||||
node.func.parent = node
|
||||
|
||||
# Check for os.path.* calls
|
||||
if (
|
||||
isinstance(node.func.value, ast.Attribute)
|
||||
and isinstance(node.func.value.value, ast.Name)
|
||||
and node.func.value.value.id in self.os_module_names
|
||||
and node.func.value.attr == "path"
|
||||
):
|
||||
method = node.func.attr
|
||||
# Skip os.path.expandvars as it has no pathlib equivalent
|
||||
if method == "expandvars":
|
||||
return
|
||||
suggestion = get_pathlib_equivalent(f"os.path.{method}")
|
||||
self.violations.append(
|
||||
(node.lineno, f"Found os.path.{method}() - {suggestion}")
|
||||
)
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def get_pathlib_equivalent(os_path_call: str) -> str:
|
||||
"""Get the pathlib equivalent for common os.path operations."""
|
||||
equivalents = {
|
||||
"os.path.join": "Use Path() / 'subpath' or Path().joinpath()",
|
||||
"os.path.exists": "Use Path().exists()",
|
||||
"os.path.isfile": "Use Path().is_file()",
|
||||
"os.path.isdir": "Use Path().is_dir()",
|
||||
"os.path.dirname": "Use Path().parent",
|
||||
"os.path.basename": "Use Path().name",
|
||||
"os.path.abspath": "Use Path().resolve()",
|
||||
"os.path.realpath": "Use Path().resolve()",
|
||||
"os.path.expanduser": "Use Path().expanduser()",
|
||||
"os.path.split": "Use Path().parent and Path().name",
|
||||
"os.path.splitext": "Use Path().stem and Path().suffix",
|
||||
"os.path.getsize": "Use Path().stat().st_size",
|
||||
"os.path.getmtime": "Use Path().stat().st_mtime",
|
||||
"os.path.normpath": "Use Path() - it normalizes automatically",
|
||||
# Note: os.path.expandvars has no pathlib equivalent and is allowed
|
||||
"os.path.expandvars": "(No pathlib equivalent - allowed)",
|
||||
}
|
||||
return equivalents.get(os_path_call, "Use pathlib.Path equivalent method")
|
||||
|
||||
|
||||
def check_file(
|
||||
filepath: Path, allow_legacy: bool = False
|
||||
) -> list[tuple[str, int, str]]:
|
||||
"""
|
||||
Check a Python file for os.path usage.
|
||||
|
||||
Args:
|
||||
filepath: Path to the Python file to check
|
||||
allow_legacy: If True, only check modified lines (not implemented yet)
|
||||
|
||||
Returns:
|
||||
List of (filename, line_number, violation_message) tuples
|
||||
"""
|
||||
try:
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
tree = ast.parse(content, filename=str(filepath))
|
||||
except SyntaxError as e:
|
||||
print(f"Syntax error in {filepath}: {e}", file=sys.stderr)
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"Error reading {filepath}: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
checker = OsPathChecker(str(filepath))
|
||||
checker.visit(tree)
|
||||
|
||||
return [(str(filepath), line, msg) for line, msg in checker.violations]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Main entry point for the pre-commit hook."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check for os.path usage and suggest pathlib alternatives"
|
||||
)
|
||||
parser.add_argument(
|
||||
"filenames",
|
||||
nargs="*",
|
||||
help="Python files to check",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-legacy",
|
||||
action="store_true",
|
||||
help="Allow os.path in existing code (only check new/modified lines)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# List of files that are allowed to use os.path (legacy or special cases)
|
||||
ALLOWED_FILES = {
|
||||
"src/local_deep_research/utilities/log_utils.py", # May need os.path for low-level operations
|
||||
"src/local_deep_research/config/paths.py", # Already migrated but may have legacy code
|
||||
".pre-commit-hooks/check-pathlib-usage.py", # This file itself
|
||||
}
|
||||
|
||||
violations = []
|
||||
for filename in args.filenames:
|
||||
filepath = Path(filename)
|
||||
|
||||
# Skip non-Python files
|
||||
if not filename.endswith(".py"):
|
||||
continue
|
||||
|
||||
# Skip allowed files
|
||||
if any(filename.endswith(allowed) for allowed in ALLOWED_FILES):
|
||||
continue
|
||||
|
||||
file_violations = check_file(filepath, args.allow_legacy)
|
||||
violations.extend(file_violations)
|
||||
|
||||
if violations:
|
||||
print("\n❌ Found os.path usage - please use pathlib.Path instead:\n")
|
||||
for filename, line, message in violations:
|
||||
print(f" {filename}:{line}: {message}")
|
||||
|
||||
print(
|
||||
"\n💡 Tip: pathlib.Path provides a more modern and cross-platform API."
|
||||
)
|
||||
print(
|
||||
" Example: Path('dir') / 'file.txt' instead of os.path.join('dir', 'file.txt')"
|
||||
)
|
||||
print(
|
||||
"\n📚 See https://docs.python.org/3/library/pathlib.html for more information.\n"
|
||||
)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user