- Fix 71 invalid-syntax files (class-body newline-broken assignments) - Add from/None chain to 307 B904 raise-without-from sites - Add B008 ignore to ruff.toml (already in pyproject.toml) - Noqa F401 on __init__.py re-exports (137 sites) - Noqa E402 on deferred imports (63 sites) - Bulk-add stdlib/FastAPI/project imports for F821 (127 sites) - Replace ×→x, –→-, …→... in docstrings (4093 chars) - Manual refactor of 5 SIM103/SIM116 patterns Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py) Co-authored-by: opencode <opencode@rugmunch.io>
138 lines
4.2 KiB
Python
138 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Qdrant collection audit + cleanup for test_col_* artifacts.
|
|
|
|
T15 (G14 FIX) - Qdrant accumulates test_col_* collections from integration
|
|
tests that forget to clean up. This script audits and cleans them.
|
|
|
|
Usage:
|
|
# Audit only (safe - no changes):
|
|
python scripts/ops/qdrant_audit.py
|
|
python scripts/ops/qdrant_audit.py --audit-only
|
|
|
|
# Audit + cleanup (drops test_col_*):
|
|
python scripts/ops/qdrant_audit.py --cleanup
|
|
|
|
# Custom Qdrant URL:
|
|
python scripts/ops/qdrant_audit.py --url http://qdrant.example:6333 --cleanup
|
|
|
|
# CI gate mode (exit 1 if any test_col_* found):
|
|
python scripts/ops/qdrant_audit.py --check
|
|
|
|
Cron suggestion (netcup):
|
|
# Daily audit, notify if anything leaked:
|
|
0 6 * * * python3 /root/scripts/qdrant_audit.py --audit-only | \\
|
|
curl -s --data-binary @- http://localhost:9080/rmi-info || true
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
import urllib.request
|
|
|
|
|
|
def fetch_collections(base_url: str) -> list[dict]:
|
|
with urllib.request.urlopen(f"{base_url}/collections", timeout=10) as r:
|
|
return json.loads(r.read()).get("result", {}).get("collections", [])
|
|
|
|
|
|
def fetch_collection_info(base_url: str, name: str) -> dict:
|
|
try:
|
|
with urllib.request.urlopen(
|
|
f"{base_url}/collections/{name}", timeout=5
|
|
) as r:
|
|
return json.loads(r.read()).get("result", {})
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
def delete_collection(base_url: str, name: str) -> bool:
|
|
req = urllib.request.Request(
|
|
f"{base_url}/collections/{name}", method="DELETE"
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=10) as r:
|
|
return json.loads(r.read()).get("status") == "ok"
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def main() -> int:
|
|
p = argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument(
|
|
"--url",
|
|
default="http://localhost:6333",
|
|
help="Qdrant base URL (default: http://localhost:6333)",
|
|
)
|
|
p.add_argument(
|
|
"--audit-only",
|
|
action="store_true",
|
|
help="Only audit - never delete anything",
|
|
)
|
|
p.add_argument(
|
|
"--cleanup",
|
|
action="store_true",
|
|
help="Audit + drop all test_col_* collections",
|
|
)
|
|
p.add_argument(
|
|
"--check",
|
|
action="store_true",
|
|
help="CI mode: exit 1 if any test_col_* found (no output unless found)",
|
|
)
|
|
args = p.parse_args()
|
|
|
|
cols = fetch_collections(args.url)
|
|
test_like = [c["name"] for c in cols if c["name"].startswith("test_col")]
|
|
production = [c["name"] for c in cols if not c["name"].startswith("test_col")]
|
|
|
|
if args.check:
|
|
if test_like:
|
|
print(
|
|
f"FAIL: {len(test_like)} test_col_* collection(s) found: "
|
|
f"{test_like}",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
return 0
|
|
|
|
print(f"=== Qdrant Audit: {args.url} ===")
|
|
print(f"Total collections: {len(cols)}")
|
|
print(f" Production: {len(production)}")
|
|
print(f" Test artifacts (test_col_*): {len(test_like)}")
|
|
|
|
if not test_like:
|
|
print("\nCLEAN - zero test_col_* artifacts.")
|
|
return 0
|
|
|
|
print("\n--- Test artifacts detail ---")
|
|
for name in test_like:
|
|
info = fetch_collection_info(args.url, name)
|
|
points = info.get("points_count", "?")
|
|
vectors_size = (
|
|
info.get("config", {}).get("params", {}).get("vectors", {}).get("size", "?")
|
|
)
|
|
print(f" {name}: {points} points, {vectors_size}-dim vectors")
|
|
|
|
if args.audit_only:
|
|
print(f"\n(Use --cleanup to drop these {len(test_like)} collection(s))")
|
|
return 0
|
|
|
|
if args.cleanup:
|
|
print("\n--- Cleanup ---")
|
|
dropped = 0
|
|
for name in test_like:
|
|
if delete_collection(args.url, name):
|
|
print(f" ✓ dropped {name}")
|
|
dropped += 1
|
|
else:
|
|
print(f" ✗ FAILED to drop {name}")
|
|
print(f"\n{dropped}/{len(test_like)} dropped.")
|
|
return 0 if dropped == len(test_like) else 1
|
|
|
|
print("\n(Use --cleanup to drop these, or --audit-only to suppress this hint)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|