Coverage for src/dataknobs_data/pooling/elasticsearch.py: 38%
58 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-17 19:59 -0500
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-17 19:59 -0500
1"""Elasticsearch-specific connection pooling implementation."""
3from dataclasses import dataclass
4from typing import Optional, Any, List
6from .base import BasePoolConfig
9@dataclass
10class ElasticsearchPoolConfig(BasePoolConfig):
11 """Configuration for Elasticsearch connection pools."""
12 hosts: List[str] = None
13 index: str = "records"
14 api_key: Optional[str] = None
15 basic_auth: Optional[tuple] = None
16 verify_certs: bool = True
17 ca_certs: Optional[str] = None
18 client_cert: Optional[str] = None
19 client_key: Optional[str] = None
20 ssl_show_warn: bool = True
22 def __post_init__(self):
23 """Set default hosts if not provided."""
24 if self.hosts is None:
25 self.hosts = ["http://localhost:9200"]
27 def to_connection_string(self) -> str:
28 """Convert to connection string (not used for ES, but required by base)."""
29 return ";".join(self.hosts)
31 def to_hash_key(self) -> tuple:
32 """Create a hashable key for this configuration."""
33 return (tuple(self.hosts), self.index)
35 @classmethod
36 def from_dict(cls, config: dict) -> "ElasticsearchPoolConfig":
37 """Create from configuration dictionary."""
38 # Handle both old-style (host, port) and new-style (hosts) configuration
39 if "hosts" in config:
40 hosts = config["hosts"]
41 elif "host" in config:
42 host = config["host"]
43 port = config.get("port", 9200)
44 # Check if it already has a scheme
45 if host.startswith("http://") or host.startswith("https://"):
46 hosts = [f"{host}:{port}" if ":" not in host.split("://")[1] else host]
47 else:
48 hosts = [f"http://{host}:{port}"]
49 else:
50 hosts = ["http://localhost:9200"]
52 return cls(
53 hosts=hosts,
54 index=config.get("index", "records"),
55 api_key=config.get("api_key"),
56 basic_auth=config.get("basic_auth"),
57 verify_certs=config.get("verify_certs", True),
58 ca_certs=config.get("ca_certs"),
59 client_cert=config.get("client_cert"),
60 client_key=config.get("client_key"),
61 ssl_show_warn=config.get("ssl_show_warn", True)
62 )
65async def create_async_elasticsearch_client(config: ElasticsearchPoolConfig):
66 """Create an async Elasticsearch client."""
67 from elasticsearch import AsyncElasticsearch
69 # Build client configuration
70 client_config = {
71 "hosts": config.hosts,
72 }
74 # Add authentication if provided
75 if config.api_key:
76 client_config["api_key"] = config.api_key
77 elif config.basic_auth:
78 client_config["basic_auth"] = config.basic_auth
80 # Add SSL configuration
81 if config.ca_certs:
82 client_config["ca_certs"] = config.ca_certs
83 if config.client_cert:
84 client_config["client_cert"] = config.client_cert
85 if config.client_key:
86 client_config["client_key"] = config.client_key
88 client_config["verify_certs"] = config.verify_certs
89 client_config["ssl_show_warn"] = config.ssl_show_warn
91 # Create and return the client
92 return AsyncElasticsearch(**client_config)
95async def validate_elasticsearch_client(client) -> None:
96 """Validate an Elasticsearch client by pinging it."""
97 if not await client.ping():
98 raise ConnectionError("Failed to ping Elasticsearch")
101async def close_elasticsearch_client(client) -> None:
102 """Properly close an Elasticsearch client and its underlying connections."""
103 if client:
104 try:
105 await client.close()
106 except Exception:
107 pass # Ignore errors during cleanup