Coverage for src/dataknobs_data/factory.py: 21%
58 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-17 16:10 -0500
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-17 16:10 -0500
1"""Backend factory for dynamic database creation."""
3import logging
4from typing import Any, Dict, Optional
6from dataknobs_config import FactoryBase
7from dataknobs_data.database import SyncDatabase
9logger = logging.getLogger(__name__)
12class DatabaseFactory(FactoryBase):
13 """Factory for creating database backends dynamically.
15 This factory allows creating different database implementations
16 based on configuration, supporting all available backends.
18 Configuration Options:
19 backend (str): Backend type (memory, file, postgres, elasticsearch, s3)
20 **kwargs: Backend-specific configuration options
22 Example Configuration:
23 databases:
24 - name: main
25 factory: database
26 backend: postgres
27 host: localhost
28 database: myapp
30 - name: cache
31 factory: database
32 backend: memory
34 - name: archive
35 factory: database
36 backend: s3
37 bucket: my-archive-bucket
38 prefix: archives/
39 """
41 def create(self, **config) -> SyncDatabase:
42 """Create a database instance based on configuration.
44 Args:
45 **config: Configuration including 'backend' field and backend-specific options
47 Returns:
48 Instance of appropriate database backend
50 Raises:
51 ValueError: If backend type is not recognized or not available
52 """
53 backend_type = config.pop("backend", "memory").lower()
55 logger.info(f"Creating database with backend: {backend_type}")
57 if backend_type in ("memory", "mem"):
58 from dataknobs_data.backends.memory import SyncMemoryDatabase
59 return SyncMemoryDatabase.from_config(config)
61 elif backend_type == "file":
62 from dataknobs_data.backends.file import SyncFileDatabase
63 return SyncFileDatabase.from_config(config)
65 elif backend_type in ("postgres", "postgresql", "pg"):
66 try:
67 from dataknobs_data.backends.postgres import SyncPostgresDatabase
68 return SyncPostgresDatabase.from_config(config)
69 except ImportError as e:
70 raise ValueError(
71 f"PostgreSQL backend requires psycopg2. "
72 f"Install with: pip install dataknobs-data[postgres]"
73 ) from e
75 elif backend_type in ("elasticsearch", "es"):
76 try:
77 from dataknobs_data.backends.elasticsearch import SyncElasticsearchDatabase
78 return SyncElasticsearchDatabase.from_config(config)
79 except ImportError as e:
80 raise ValueError(
81 f"Elasticsearch backend requires elasticsearch package. "
82 f"Install with: pip install dataknobs-data[elasticsearch]"
83 ) from e
85 elif backend_type == "s3":
86 try:
87 from dataknobs_data.backends.s3 import SyncS3Database
88 return SyncS3Database.from_config(config)
89 except ImportError as e:
90 raise ValueError(
91 f"S3 backend requires boto3. "
92 f"Install with: pip install dataknobs-data[s3]"
93 ) from e
95 else:
96 raise ValueError(
97 f"Unknown backend type: {backend_type}. "
98 f"Available backends: memory, file, postgres, elasticsearch, s3"
99 )
101 def get_backend_info(self, backend_type: str) -> Dict[str, Any]:
102 """Get information about a specific backend.
104 Args:
105 backend_type: Name of the backend
107 Returns:
108 Dictionary with backend information
109 """
110 info = {
111 "memory": {
112 "description": "In-memory storage for testing and caching",
113 "persistent": False,
114 "requires_install": False,
115 "config_options": {
116 "initial_data": "Optional initial data dictionary"
117 }
118 },
119 "file": {
120 "description": "File-based storage (JSON, CSV, Parquet)",
121 "persistent": True,
122 "requires_install": False,
123 "config_options": {
124 "path": "Path to the file (required)",
125 "format": "File format: json, csv, parquet (default: json)",
126 "compression": "Optional compression: gzip, bz2, xz"
127 }
128 },
129 "postgres": {
130 "description": "PostgreSQL database backend",
131 "persistent": True,
132 "requires_install": "pip install dataknobs-data[postgres]",
133 "config_options": {
134 "host": "Database host (required)",
135 "port": "Database port (default: 5432)",
136 "database": "Database name (required)",
137 "user": "Username (required)",
138 "password": "Password (required)",
139 "table": "Table name (default: records)"
140 }
141 },
142 "elasticsearch": {
143 "description": "Elasticsearch search engine backend",
144 "persistent": True,
145 "requires_install": "pip install dataknobs-data[elasticsearch]",
146 "config_options": {
147 "hosts": "List of host URLs (required)",
148 "index": "Index name (required)",
149 "doc_type": "Document type (default: _doc)",
150 "username": "Optional username",
151 "password": "Optional password"
152 }
153 },
154 "s3": {
155 "description": "AWS S3 object storage backend",
156 "persistent": True,
157 "requires_install": "pip install dataknobs-data[s3]",
158 "config_options": {
159 "bucket": "S3 bucket name (required)",
160 "prefix": "Object key prefix (default: records/)",
161 "region": "AWS region (default: us-east-1)",
162 "endpoint_url": "Custom endpoint for S3-compatible services",
163 "access_key_id": "AWS access key (or use IAM role)",
164 "secret_access_key": "AWS secret key (or use IAM role)"
165 }
166 }
167 }
169 return info.get(backend_type.lower(), {
170 "description": "Unknown backend",
171 "error": f"Backend '{backend_type}' not recognized"
172 })
175class AsyncDatabaseFactory(FactoryBase):
176 """Factory for creating async database backends.
178 Note: Currently only some backends support async operations.
179 """
181 def create(self, **config) -> Any:
182 """Create an async database instance.
184 Args:
185 **config: Configuration including 'backend' field
187 Returns:
188 Instance of appropriate async database backend
190 Raises:
191 ValueError: If backend doesn't support async operations
192 """
193 backend_type = config.pop("backend", "memory").lower()
195 if backend_type in ("memory", "mem"):
196 from dataknobs_data.backends.memory import AsyncMemoryDatabase
197 return AsyncMemoryDatabase.from_config(config)
199 elif backend_type == "file":
200 from dataknobs_data.backends.file import AsyncFileDatabase
201 return AsyncFileDatabase.from_config(config)
203 elif backend_type in ("postgres", "postgresql", "pg"):
204 from dataknobs_data.backends.postgres import AsyncPostgresDatabase
205 return AsyncPostgresDatabase.from_config(config)
207 elif backend_type in ("elasticsearch", "es"):
208 from dataknobs_data.backends.elasticsearch import AsyncElasticsearchDatabase
209 return AsyncElasticsearchDatabase.from_config(config)
211 elif backend_type == "s3":
212 from dataknobs_data.backends.s3 import AsyncS3Database
213 return AsyncS3Database.from_config(config)
215 else:
216 raise ValueError(
217 f"Backend '{backend_type}' does not support async operations yet. "
218 f"Available async backends: memory, file, postgres, elasticsearch, s3"
219 )
222# Create singleton instances for registration
223database_factory = DatabaseFactory()
224async_database_factory = AsyncDatabaseFactory()