- Add docs/commenting-standard.md defining per-language comment standards (Go godoc, Python PEP-257, C Doxygen, Bash header blocks, SQL banners) - services/dashboard: 100% docstring coverage (100/100 functions) - All FastAPI route handlers, helpers, classes, and models documented - Language: French (project convention) - services/bot-detector: 100% docstring coverage (53/53 symbols) - bot_detector.py: 14 functions + module docstring - anubis/fetch_rules.py: 9 functions - shared/python/ja4_common: full docstrings on ClickHouseClient (7 methods) and ClickHouseSettings class - services/correlator: 24 godoc comments added across 6 Go files - correlation_service.go: 10 private helpers - unixsocket/source.go: 6 parsing/socket helpers - correlated_log.go: 4 field extraction helpers - orchestrator.go, logger.go, main.go: 4 comments - services/correlator/scripts/audit-architecture.sh: standardized header block Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
"""Client ClickHouse singleton partagé pour la suite de sécurité JA4."""
|
|
import clickhouse_connect
|
|
from typing import Optional
|
|
|
|
from .settings import settings
|
|
|
|
|
|
class ClickHouseClient:
|
|
"""Client ClickHouse singleton avec reconnexion automatique.
|
|
|
|
Attributs :
|
|
_client : instance du client clickhouse_connect sous-jacent,
|
|
ou None si la connexion n'est pas encore établie.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialise le client sans ouvrir de connexion immédiate."""
|
|
self._client: Optional[clickhouse_connect.driver.client.Client] = None
|
|
|
|
def connect(self) -> clickhouse_connect.driver.client.Client:
|
|
"""Retourne un client connecté, en créant ou rétablissant la connexion si nécessaire."""
|
|
if self._client is None or not self._ping():
|
|
self._client = clickhouse_connect.get_client(
|
|
host=settings.CLICKHOUSE_HOST,
|
|
port=settings.CLICKHOUSE_PORT,
|
|
database=settings.CLICKHOUSE_DB,
|
|
user=settings.CLICKHOUSE_USER,
|
|
password=settings.CLICKHOUSE_PASSWORD,
|
|
connect_timeout=10,
|
|
)
|
|
return self._client
|
|
|
|
def _ping(self) -> bool:
|
|
"""Vérifie que la connexion existante est active. Retourne False en cas d'erreur."""
|
|
try:
|
|
if self._client:
|
|
self._client.ping()
|
|
return True
|
|
except Exception:
|
|
pass
|
|
return False
|
|
|
|
def query(self, query: str, params: Optional[dict] = None):
|
|
"""Exécute une requête SELECT et retourne le résultat."""
|
|
return self.connect().query(query, params)
|
|
|
|
def command(self, query: str, params: Optional[dict] = None):
|
|
"""Exécute une commande DDL/DML (INSERT, CREATE, TRUNCATE, etc.)."""
|
|
return self.connect().command(query, parameters=params)
|
|
|
|
def insert(self, table: str, data, column_names=None):
|
|
"""Insère des données dans la table cible."""
|
|
return self.connect().insert(table, data, column_names=column_names)
|
|
|
|
def close(self):
|
|
"""Ferme la connexion et réinitialise le client interne."""
|
|
if self._client:
|
|
self._client.close()
|
|
self._client = None
|
|
|
|
|
|
_client: Optional[ClickHouseClient] = None
|
|
|
|
|
|
def get_client() -> ClickHouseClient:
|
|
"""Retourne l'instance singleton du ClickHouseClient, en la créant si nécessaire."""
|
|
global _client
|
|
if _client is None:
|
|
_client = ClickHouseClient()
|
|
return _client
|