diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4b18263 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +QDRANT_API_KEY= +QDRANT_URL= \ No newline at end of file diff --git a/README.md b/README.md index 77c4b0f..b938fb5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A lean, open-source Qdrant collection management tool focused on inspection, deduplication, vector repair, sparse-content detection, and targeted cleanup tasks. +Supports both **local Qdrant** (Docker/self-hosted) and **Qdrant Cloud**. + ## Installation ```bash @@ -24,17 +26,27 @@ The public package surface is intentionally small: ## Configuration -The tool reads `QDRANT_URL` from the environment and defaults to `http://localhost:6333`. +The tool reads `QDRANT_URL` and `QDRANT_API_KEY` from the environment. +**Local Qdrant (Docker/self-hosted):** ```bash export QDRANT_URL=http://localhost:6333 -python -m qdrant_manager +python __main__.py +``` + +**Qdrant Cloud:** +```bash +export QDRANT_URL=https://..cloud.qdrant.io +export QDRANT_API_KEY= +python __main__.py ``` +Note: Qdrant Cloud URLs should **not** include `:6333`. Use the full HTTPS URL from your Qdrant Cloud dashboard. + ## Launching the Cleaner CLI ```bash -python -m qdrant_manager +python __main__.py ``` On startup you will be prompted for a Qdrant URL (pre-filled from `QDRANT_URL` or `http://localhost:6333`). The CLI checks connectivity immediately and shows a green **Connected** or red **Unreachable** status. If the instance is not reachable you can enter a different URL or continue anyway — the main menu stays up and reports errors per-action rather than crashing. @@ -44,7 +56,12 @@ On startup you will be prompted for a Qdrant URL (pre-filled from `QDRANT_URL` o ```python from qdrant_manager import QdrantCleaner +# Local Qdrant cleaner = QdrantCleaner(host="localhost", port=6333) + +# Qdrant Cloud (with API key) +cleaner = QdrantCleaner(host="https://..cloud.qdrant.io", api_key="") + cleaner.set_collection("my_collection") summary = cleaner.analyze_collection(progress_callback=print) diff --git a/__init__.py b/__init__.py index fdc1051..858e3f7 100755 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,5 @@ """Public package surface for the standalone Qdrant cleaner.""" -from .cleaner import QdrantCleaner -from .cleaner_cli import QdrantCleanerCLI +from cleaner import QdrantCleaner +from cleaner_cli import QdrantCleanerCLI __all__ = ["QdrantCleaner", "QdrantCleanerCLI"] diff --git a/__main__.py b/__main__.py index 9ff6c75..c316fc9 100644 --- a/__main__.py +++ b/__main__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .cleaner_cli import QdrantCleanerCLI +from cleaner_cli import QdrantCleanerCLI def main() -> None: diff --git a/cleaner.py b/cleaner.py index 8d998fc..adcc453 100755 --- a/cleaner.py +++ b/cleaner.py @@ -10,8 +10,8 @@ from typing import Any, Dict, List, Optional, Tuple from urllib.parse import urlparse -from .config import CANONICAL_TEXT_FIELDS -from .dependencies import ( +from config import CANONICAL_TEXT_FIELDS, QDRANT_API_KEY +from dependencies import ( CuMLDBSCAN, GPU_CLUSTERING_AVAILABLE, IsolationForest, @@ -31,16 +31,25 @@ class QdrantCleaner: """Collection cleaning utilities""" - def __init__(self, host="localhost", port=6333): + def __init__(self, host="localhost", port=6333, api_key=None): if QdrantClient is None: raise ImportError("qdrant-client required") - - if host.startswith("http://") or host.startswith("https://"): - parsed = urlparse(host) - host = parsed.hostname - port = parsed.port or port - - self.client = QdrantClient(host=host, port=int(port)) + + api_key = api_key or QDRANT_API_KEY + + # Use 'url' parameter for HTTPS/cloud, 'host'+'port' for local HTTP + if host.startswith("https://"): + client_kwargs = {"url": host} + if api_key: + client_kwargs["api_key"] = api_key + elif host.startswith("http://"): + client_kwargs = {"url": host} + else: + client_kwargs = {"host": host, "port": int(port)} + if api_key: + client_kwargs["api_key"] = api_key + + self.client = QdrantClient(**client_kwargs) self.collection_name = None self.has_optimize_method = hasattr(self.client, 'optimize_collection') diff --git a/cleaner_cli.py b/cleaner_cli.py index f34cebd..32088e4 100644 --- a/cleaner_cli.py +++ b/cleaner_cli.py @@ -5,8 +5,8 @@ from typing import Any, Dict, List, Optional from urllib.parse import urlparse -from .cleaner import QdrantCleaner -from .config import QDRANT_DIRECT +from cleaner import QdrantCleaner +from config import QDRANT_DIRECT, QDRANT_API_KEY _COLOUR = sys.stdout.isatty() @@ -77,13 +77,14 @@ def __init__(self, *, qdrant_url: str = QDRANT_DIRECT) -> None: self.collections: List[Dict[str, Any]] = [] self._set_qdrant_url(qdrant_url) - def _set_qdrant_url(self, qdrant_url: str) -> None: + def _set_qdrant_url(self, qdrant_url: str, api_key: str = None) -> None: qdrant_url = (qdrant_url or "").strip() or QDRANT_DIRECT parsed = urlparse(qdrant_url) host = parsed.hostname or "localhost" port = parsed.port or 6333 + api_key = api_key or QDRANT_API_KEY self.qdrant_url = qdrant_url - self.cleaner = QdrantCleaner(host=host, port=port) + self.cleaner = QdrantCleaner(host=host, port=port, api_key=api_key) self.collections = [] def _progress(self, message: str) -> None: diff --git a/config.py b/config.py index 9920577..0439b8d 100755 --- a/config.py +++ b/config.py @@ -4,6 +4,7 @@ import os QDRANT_URL = os.environ.get("QDRANT_URL", "http://localhost:6333") +QDRANT_API_KEY = os.environ.get("QDRANT_API_KEY") QDRANT_DIRECT = QDRANT_URL CANONICAL_TEXT_FIELDS = ["text", "content", "information"] CANONICAL_ROOT_FIELDS: list[str] = []