kailash 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kailash/__init__.py +1 -1
- kailash/client/__init__.py +12 -0
- kailash/client/enhanced_client.py +306 -0
- kailash/core/actors/__init__.py +16 -0
- kailash/core/actors/connection_actor.py +566 -0
- kailash/core/actors/supervisor.py +364 -0
- kailash/edge/__init__.py +16 -0
- kailash/edge/compliance.py +834 -0
- kailash/edge/discovery.py +659 -0
- kailash/edge/location.py +582 -0
- kailash/gateway/__init__.py +33 -0
- kailash/gateway/api.py +289 -0
- kailash/gateway/enhanced_gateway.py +357 -0
- kailash/gateway/resource_resolver.py +217 -0
- kailash/gateway/security.py +227 -0
- kailash/middleware/auth/models.py +2 -2
- kailash/middleware/database/base_models.py +1 -7
- kailash/middleware/gateway/__init__.py +22 -0
- kailash/middleware/gateway/checkpoint_manager.py +398 -0
- kailash/middleware/gateway/deduplicator.py +382 -0
- kailash/middleware/gateway/durable_gateway.py +417 -0
- kailash/middleware/gateway/durable_request.py +498 -0
- kailash/middleware/gateway/event_store.py +459 -0
- kailash/nodes/admin/permission_check.py +817 -33
- kailash/nodes/admin/role_management.py +1242 -108
- kailash/nodes/admin/schema_manager.py +438 -0
- kailash/nodes/admin/user_management.py +1124 -1582
- kailash/nodes/code/__init__.py +8 -1
- kailash/nodes/code/async_python.py +1035 -0
- kailash/nodes/code/python.py +1 -0
- kailash/nodes/data/async_sql.py +9 -3
- kailash/nodes/data/sql.py +20 -11
- kailash/nodes/data/workflow_connection_pool.py +643 -0
- kailash/nodes/rag/__init__.py +1 -4
- kailash/resources/__init__.py +40 -0
- kailash/resources/factory.py +533 -0
- kailash/resources/health.py +319 -0
- kailash/resources/reference.py +288 -0
- kailash/resources/registry.py +392 -0
- kailash/runtime/async_local.py +711 -302
- kailash/testing/__init__.py +34 -0
- kailash/testing/async_test_case.py +353 -0
- kailash/testing/async_utils.py +345 -0
- kailash/testing/fixtures.py +458 -0
- kailash/testing/mock_registry.py +495 -0
- kailash/workflow/__init__.py +8 -0
- kailash/workflow/async_builder.py +621 -0
- kailash/workflow/async_patterns.py +766 -0
- kailash/workflow/cyclic_runner.py +107 -16
- kailash/workflow/graph.py +7 -2
- kailash/workflow/resilience.py +11 -1
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/METADATA +7 -4
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/RECORD +57 -22
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/WHEEL +0 -0
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,533 @@
|
|
1
|
+
"""
|
2
|
+
Resource Factories - Create and configure resources for the registry.
|
3
|
+
|
4
|
+
This module provides abstract factory interface and concrete implementations
|
5
|
+
for common resource types used in Kailash workflows.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import logging
|
10
|
+
from abc import ABC, abstractmethod
|
11
|
+
from typing import Any, Dict, Optional
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class ResourceFactory(ABC):
|
17
|
+
"""
|
18
|
+
Abstract factory for creating resources.
|
19
|
+
|
20
|
+
All resource factories must implement this interface to be used
|
21
|
+
with the ResourceRegistry.
|
22
|
+
"""
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
async def create(self) -> Any:
|
26
|
+
"""
|
27
|
+
Create and return a resource.
|
28
|
+
|
29
|
+
This method should:
|
30
|
+
- Create the resource (e.g., database connection pool)
|
31
|
+
- Perform any initialization
|
32
|
+
- Return the ready-to-use resource
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
The created resource
|
36
|
+
"""
|
37
|
+
pass
|
38
|
+
|
39
|
+
@abstractmethod
|
40
|
+
def get_config(self) -> Dict[str, Any]:
|
41
|
+
"""
|
42
|
+
Get factory configuration for serialization.
|
43
|
+
|
44
|
+
This is used for debugging and documentation purposes.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Dictionary of configuration parameters
|
48
|
+
"""
|
49
|
+
pass
|
50
|
+
|
51
|
+
|
52
|
+
class DatabasePoolFactory(ResourceFactory):
|
53
|
+
"""
|
54
|
+
Factory for creating database connection pools.
|
55
|
+
|
56
|
+
Supports multiple database backends:
|
57
|
+
- PostgreSQL (asyncpg)
|
58
|
+
- MySQL (aiomysql)
|
59
|
+
- SQLite (aiosqlite)
|
60
|
+
|
61
|
+
Example:
|
62
|
+
```python
|
63
|
+
factory = DatabasePoolFactory(
|
64
|
+
backend='postgresql',
|
65
|
+
host='localhost',
|
66
|
+
port=5432,
|
67
|
+
database='myapp',
|
68
|
+
user='dbuser',
|
69
|
+
password='secret',
|
70
|
+
min_size=5,
|
71
|
+
max_size=20
|
72
|
+
)
|
73
|
+
```
|
74
|
+
"""
|
75
|
+
|
76
|
+
def __init__(
|
77
|
+
self,
|
78
|
+
backend: str = "postgresql",
|
79
|
+
host: str = "localhost",
|
80
|
+
port: Optional[int] = None,
|
81
|
+
database: str = "test",
|
82
|
+
user: Optional[str] = None,
|
83
|
+
password: Optional[str] = None,
|
84
|
+
min_size: int = 5,
|
85
|
+
max_size: int = 20,
|
86
|
+
**kwargs,
|
87
|
+
):
|
88
|
+
"""Initialize database pool factory."""
|
89
|
+
self.backend = backend.lower()
|
90
|
+
self.host = host
|
91
|
+
self.port = port or self._default_port()
|
92
|
+
self.database = database
|
93
|
+
self.user = user
|
94
|
+
self.password = password
|
95
|
+
self.min_size = min_size
|
96
|
+
self.max_size = max_size
|
97
|
+
self.extra_config = kwargs
|
98
|
+
|
99
|
+
def _default_port(self) -> int:
|
100
|
+
"""Get default port for backend."""
|
101
|
+
return {
|
102
|
+
"postgresql": 5432,
|
103
|
+
"postgres": 5432,
|
104
|
+
"mysql": 3306,
|
105
|
+
"sqlite": None,
|
106
|
+
}.get(self.backend, 5432)
|
107
|
+
|
108
|
+
async def create(self) -> Any:
|
109
|
+
"""Create database connection pool."""
|
110
|
+
if self.backend in ("postgresql", "postgres"):
|
111
|
+
return await self._create_postgres_pool()
|
112
|
+
elif self.backend == "mysql":
|
113
|
+
return await self._create_mysql_pool()
|
114
|
+
elif self.backend == "sqlite":
|
115
|
+
return await self._create_sqlite_pool()
|
116
|
+
else:
|
117
|
+
raise ValueError(f"Unsupported database backend: {self.backend}")
|
118
|
+
|
119
|
+
async def _create_postgres_pool(self):
|
120
|
+
"""Create PostgreSQL connection pool."""
|
121
|
+
try:
|
122
|
+
import asyncpg
|
123
|
+
except ImportError:
|
124
|
+
raise ImportError(
|
125
|
+
"asyncpg is required for PostgreSQL. "
|
126
|
+
"Install with: pip install asyncpg"
|
127
|
+
)
|
128
|
+
|
129
|
+
# Use default user if not provided
|
130
|
+
user = self.user or "postgres"
|
131
|
+
password = self.password or ""
|
132
|
+
|
133
|
+
# Extract options if present
|
134
|
+
options = self.extra_config.pop("options", None)
|
135
|
+
|
136
|
+
# Build DSN
|
137
|
+
dsn = f"postgresql://{user}:{password}@{self.host}:{self.port}/{self.database}"
|
138
|
+
if options:
|
139
|
+
# Add options to DSN as query parameters
|
140
|
+
dsn += f"?options={options}"
|
141
|
+
|
142
|
+
logger.info(
|
143
|
+
f"Creating PostgreSQL pool: {self.host}:{self.port}/{self.database}"
|
144
|
+
)
|
145
|
+
|
146
|
+
return await asyncpg.create_pool(
|
147
|
+
dsn, min_size=self.min_size, max_size=self.max_size, **self.extra_config
|
148
|
+
)
|
149
|
+
|
150
|
+
async def _create_mysql_pool(self):
|
151
|
+
"""Create MySQL connection pool."""
|
152
|
+
try:
|
153
|
+
import aiomysql
|
154
|
+
except ImportError:
|
155
|
+
raise ImportError(
|
156
|
+
"aiomysql is required for MySQL. " "Install with: pip install aiomysql"
|
157
|
+
)
|
158
|
+
|
159
|
+
logger.info(f"Creating MySQL pool: {self.host}:{self.port}/{self.database}")
|
160
|
+
|
161
|
+
return await aiomysql.create_pool(
|
162
|
+
host=self.host,
|
163
|
+
port=self.port,
|
164
|
+
user=self.user,
|
165
|
+
password=self.password,
|
166
|
+
db=self.database,
|
167
|
+
minsize=self.min_size,
|
168
|
+
maxsize=self.max_size,
|
169
|
+
**self.extra_config,
|
170
|
+
)
|
171
|
+
|
172
|
+
async def _create_sqlite_pool(self):
|
173
|
+
"""Create SQLite connection."""
|
174
|
+
try:
|
175
|
+
import aiosqlite
|
176
|
+
except ImportError:
|
177
|
+
raise ImportError(
|
178
|
+
"aiosqlite is required for SQLite. "
|
179
|
+
"Install with: pip install aiosqlite"
|
180
|
+
)
|
181
|
+
|
182
|
+
logger.info(f"Creating SQLite connection: {self.database}")
|
183
|
+
|
184
|
+
# SQLite doesn't have pools, return a connection
|
185
|
+
return await aiosqlite.connect(self.database, **self.extra_config)
|
186
|
+
|
187
|
+
def get_config(self) -> Dict[str, Any]:
|
188
|
+
"""Get factory configuration."""
|
189
|
+
config = {
|
190
|
+
"backend": self.backend,
|
191
|
+
"host": self.host,
|
192
|
+
"port": self.port,
|
193
|
+
"database": self.database,
|
194
|
+
"user": self.user,
|
195
|
+
"min_size": self.min_size,
|
196
|
+
"max_size": self.max_size,
|
197
|
+
}
|
198
|
+
config.update(self.extra_config)
|
199
|
+
# Don't include password in config
|
200
|
+
return {k: v for k, v in config.items() if k != "password"}
|
201
|
+
|
202
|
+
|
203
|
+
class HttpClientFactory(ResourceFactory):
|
204
|
+
"""
|
205
|
+
Factory for creating HTTP clients.
|
206
|
+
|
207
|
+
Supports:
|
208
|
+
- aiohttp
|
209
|
+
- httpx
|
210
|
+
|
211
|
+
Example:
|
212
|
+
```python
|
213
|
+
factory = HttpClientFactory(
|
214
|
+
backend='aiohttp',
|
215
|
+
base_url='https://api.example.com',
|
216
|
+
timeout=30,
|
217
|
+
headers={'Authorization': 'Bearer token'}
|
218
|
+
)
|
219
|
+
```
|
220
|
+
"""
|
221
|
+
|
222
|
+
def __init__(
|
223
|
+
self,
|
224
|
+
backend: str = "aiohttp",
|
225
|
+
base_url: Optional[str] = None,
|
226
|
+
timeout: int = 30,
|
227
|
+
headers: Optional[Dict[str, str]] = None,
|
228
|
+
**kwargs,
|
229
|
+
):
|
230
|
+
"""Initialize HTTP client factory."""
|
231
|
+
self.backend = backend.lower()
|
232
|
+
self.base_url = base_url
|
233
|
+
self.timeout = timeout
|
234
|
+
self.headers = headers or {}
|
235
|
+
self.extra_config = kwargs
|
236
|
+
|
237
|
+
async def create(self) -> Any:
|
238
|
+
"""Create HTTP client."""
|
239
|
+
if self.backend == "aiohttp":
|
240
|
+
return await self._create_aiohttp_client()
|
241
|
+
elif self.backend == "httpx":
|
242
|
+
return self._create_httpx_client()
|
243
|
+
else:
|
244
|
+
raise ValueError(f"Unsupported HTTP backend: {self.backend}")
|
245
|
+
|
246
|
+
async def _create_aiohttp_client(self):
|
247
|
+
"""Create aiohttp client session."""
|
248
|
+
try:
|
249
|
+
import aiohttp
|
250
|
+
except ImportError:
|
251
|
+
raise ImportError("aiohttp is required. Install with: pip install aiohttp")
|
252
|
+
|
253
|
+
logger.info(f"Creating aiohttp client: {self.base_url}")
|
254
|
+
|
255
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
256
|
+
connector = aiohttp.TCPConnector(limit=100)
|
257
|
+
|
258
|
+
return aiohttp.ClientSession(
|
259
|
+
base_url=self.base_url,
|
260
|
+
timeout=timeout,
|
261
|
+
headers=self.headers,
|
262
|
+
connector=connector,
|
263
|
+
**self.extra_config,
|
264
|
+
)
|
265
|
+
|
266
|
+
def _create_httpx_client(self):
|
267
|
+
"""Create httpx async client."""
|
268
|
+
try:
|
269
|
+
import httpx
|
270
|
+
except ImportError:
|
271
|
+
raise ImportError("httpx is required. Install with: pip install httpx")
|
272
|
+
|
273
|
+
logger.info(f"Creating httpx client: {self.base_url}")
|
274
|
+
|
275
|
+
return httpx.AsyncClient(
|
276
|
+
base_url=self.base_url,
|
277
|
+
timeout=self.timeout,
|
278
|
+
headers=self.headers,
|
279
|
+
**self.extra_config,
|
280
|
+
)
|
281
|
+
|
282
|
+
def get_config(self) -> Dict[str, Any]:
|
283
|
+
"""Get factory configuration."""
|
284
|
+
config = {
|
285
|
+
"backend": self.backend,
|
286
|
+
"base_url": self.base_url,
|
287
|
+
"timeout": self.timeout,
|
288
|
+
"headers": {
|
289
|
+
k: "***" if "auth" in k.lower() else v for k, v in self.headers.items()
|
290
|
+
},
|
291
|
+
}
|
292
|
+
config.update(self.extra_config)
|
293
|
+
return config
|
294
|
+
|
295
|
+
|
296
|
+
class CacheFactory(ResourceFactory):
|
297
|
+
"""
|
298
|
+
Factory for creating cache clients.
|
299
|
+
|
300
|
+
Supports:
|
301
|
+
- Redis (aioredis)
|
302
|
+
- Memcached (aiomemcache)
|
303
|
+
- In-memory cache
|
304
|
+
|
305
|
+
Example:
|
306
|
+
```python
|
307
|
+
factory = CacheFactory(
|
308
|
+
backend='redis',
|
309
|
+
host='localhost',
|
310
|
+
port=6379,
|
311
|
+
db=0
|
312
|
+
)
|
313
|
+
```
|
314
|
+
"""
|
315
|
+
|
316
|
+
def __init__(
|
317
|
+
self,
|
318
|
+
backend: str = "redis",
|
319
|
+
host: str = "localhost",
|
320
|
+
port: Optional[int] = None,
|
321
|
+
**kwargs,
|
322
|
+
):
|
323
|
+
"""Initialize cache factory."""
|
324
|
+
self.backend = backend.lower()
|
325
|
+
self.host = host
|
326
|
+
self.port = port or self._default_port()
|
327
|
+
self.extra_config = kwargs
|
328
|
+
|
329
|
+
def _default_port(self) -> int:
|
330
|
+
"""Get default port for backend."""
|
331
|
+
return {"redis": 6379, "memcached": 11211, "memory": None}.get(
|
332
|
+
self.backend, 6379
|
333
|
+
)
|
334
|
+
|
335
|
+
async def create(self) -> Any:
|
336
|
+
"""Create cache client."""
|
337
|
+
if self.backend == "redis":
|
338
|
+
return await self._create_redis_client()
|
339
|
+
elif self.backend == "memcached":
|
340
|
+
return await self._create_memcached_client()
|
341
|
+
elif self.backend == "memory":
|
342
|
+
return self._create_memory_cache()
|
343
|
+
else:
|
344
|
+
raise ValueError(f"Unsupported cache backend: {self.backend}")
|
345
|
+
|
346
|
+
async def _create_redis_client(self):
|
347
|
+
"""Create Redis client."""
|
348
|
+
try:
|
349
|
+
import redis.asyncio as aioredis
|
350
|
+
except ImportError:
|
351
|
+
try:
|
352
|
+
import aioredis
|
353
|
+
except ImportError:
|
354
|
+
raise ImportError(
|
355
|
+
"redis or aioredis is required. "
|
356
|
+
"Install with: pip install redis[async] or pip install aioredis"
|
357
|
+
)
|
358
|
+
|
359
|
+
logger.info(f"Creating Redis client: {self.host}:{self.port}")
|
360
|
+
|
361
|
+
return await aioredis.from_url(
|
362
|
+
f"redis://{self.host}:{self.port}", **self.extra_config
|
363
|
+
)
|
364
|
+
|
365
|
+
async def _create_memcached_client(self):
|
366
|
+
"""Create Memcached client."""
|
367
|
+
try:
|
368
|
+
import aiomemcache
|
369
|
+
except ImportError:
|
370
|
+
raise ImportError(
|
371
|
+
"aiomemcache is required. " "Install with: pip install aiomemcache"
|
372
|
+
)
|
373
|
+
|
374
|
+
logger.info(f"Creating Memcached client: {self.host}:{self.port}")
|
375
|
+
|
376
|
+
client = aiomemcache.Client(self.host, self.port)
|
377
|
+
await client.connect()
|
378
|
+
return client
|
379
|
+
|
380
|
+
def _create_memory_cache(self):
|
381
|
+
"""Create in-memory cache."""
|
382
|
+
logger.info("Creating in-memory cache")
|
383
|
+
|
384
|
+
class MemoryCache:
|
385
|
+
"""Simple in-memory cache implementation."""
|
386
|
+
|
387
|
+
def __init__(self):
|
388
|
+
self._cache = {}
|
389
|
+
|
390
|
+
async def get(self, key: str) -> Any:
|
391
|
+
return self._cache.get(key)
|
392
|
+
|
393
|
+
async def set(self, key: str, value: Any, expire: int = None) -> None:
|
394
|
+
self._cache[key] = value
|
395
|
+
# TODO: Implement expiration
|
396
|
+
|
397
|
+
async def delete(self, key: str) -> None:
|
398
|
+
self._cache.pop(key, None)
|
399
|
+
|
400
|
+
async def clear(self) -> None:
|
401
|
+
self._cache.clear()
|
402
|
+
|
403
|
+
return MemoryCache()
|
404
|
+
|
405
|
+
def get_config(self) -> Dict[str, Any]:
|
406
|
+
"""Get factory configuration."""
|
407
|
+
config = {"backend": self.backend, "host": self.host, "port": self.port}
|
408
|
+
config.update(self.extra_config)
|
409
|
+
return config
|
410
|
+
|
411
|
+
|
412
|
+
class MessageQueueFactory(ResourceFactory):
|
413
|
+
"""
|
414
|
+
Factory for creating message queue clients.
|
415
|
+
|
416
|
+
Supports:
|
417
|
+
- RabbitMQ (aio-pika)
|
418
|
+
- Kafka (aiokafka)
|
419
|
+
- Redis Pub/Sub
|
420
|
+
|
421
|
+
Example:
|
422
|
+
```python
|
423
|
+
factory = MessageQueueFactory(
|
424
|
+
backend='rabbitmq',
|
425
|
+
host='localhost',
|
426
|
+
port=5672,
|
427
|
+
username='guest',
|
428
|
+
password='guest'
|
429
|
+
)
|
430
|
+
```
|
431
|
+
"""
|
432
|
+
|
433
|
+
def __init__(
|
434
|
+
self,
|
435
|
+
backend: str = "rabbitmq",
|
436
|
+
host: str = "localhost",
|
437
|
+
port: Optional[int] = None,
|
438
|
+
username: Optional[str] = None,
|
439
|
+
password: Optional[str] = None,
|
440
|
+
**kwargs,
|
441
|
+
):
|
442
|
+
"""Initialize message queue factory."""
|
443
|
+
self.backend = backend.lower()
|
444
|
+
self.host = host
|
445
|
+
self.port = port or self._default_port()
|
446
|
+
self.username = username
|
447
|
+
self.password = password
|
448
|
+
self.extra_config = kwargs
|
449
|
+
|
450
|
+
def _default_port(self) -> int:
|
451
|
+
"""Get default port for backend."""
|
452
|
+
return {"rabbitmq": 5672, "kafka": 9092, "redis": 6379}.get(self.backend, 5672)
|
453
|
+
|
454
|
+
async def create(self) -> Any:
|
455
|
+
"""Create message queue client."""
|
456
|
+
if self.backend == "rabbitmq":
|
457
|
+
return await self._create_rabbitmq_client()
|
458
|
+
elif self.backend == "kafka":
|
459
|
+
return await self._create_kafka_client()
|
460
|
+
elif self.backend == "redis":
|
461
|
+
return await self._create_redis_pubsub()
|
462
|
+
else:
|
463
|
+
raise ValueError(f"Unsupported message queue backend: {self.backend}")
|
464
|
+
|
465
|
+
async def _create_rabbitmq_client(self):
|
466
|
+
"""Create RabbitMQ client."""
|
467
|
+
try:
|
468
|
+
import aio_pika
|
469
|
+
except ImportError:
|
470
|
+
raise ImportError(
|
471
|
+
"aio-pika is required for RabbitMQ. "
|
472
|
+
"Install with: pip install aio-pika"
|
473
|
+
)
|
474
|
+
|
475
|
+
logger.info(f"Creating RabbitMQ connection: {self.host}:{self.port}")
|
476
|
+
|
477
|
+
url = f"amqp://{self.username}:{self.password}@{self.host}:{self.port}/"
|
478
|
+
|
479
|
+
return await aio_pika.connect_robust(url, **self.extra_config)
|
480
|
+
|
481
|
+
async def _create_kafka_client(self):
|
482
|
+
"""Create Kafka producer/consumer."""
|
483
|
+
try:
|
484
|
+
from aiokafka import AIOKafkaConsumer, AIOKafkaProducer
|
485
|
+
except ImportError:
|
486
|
+
raise ImportError(
|
487
|
+
"aiokafka is required for Kafka. " "Install with: pip install aiokafka"
|
488
|
+
)
|
489
|
+
|
490
|
+
logger.info(f"Creating Kafka client: {self.host}:{self.port}")
|
491
|
+
|
492
|
+
# Return both producer and consumer
|
493
|
+
producer = AIOKafkaProducer(
|
494
|
+
bootstrap_servers=f"{self.host}:{self.port}", **self.extra_config
|
495
|
+
)
|
496
|
+
|
497
|
+
consumer = AIOKafkaConsumer(
|
498
|
+
bootstrap_servers=f"{self.host}:{self.port}", **self.extra_config
|
499
|
+
)
|
500
|
+
|
501
|
+
await producer.start()
|
502
|
+
await consumer.start()
|
503
|
+
|
504
|
+
class KafkaClient:
|
505
|
+
def __init__(self, producer, consumer):
|
506
|
+
self.producer = producer
|
507
|
+
self.consumer = consumer
|
508
|
+
|
509
|
+
async def close(self):
|
510
|
+
await self.producer.stop()
|
511
|
+
await self.consumer.stop()
|
512
|
+
|
513
|
+
return KafkaClient(producer, consumer)
|
514
|
+
|
515
|
+
async def _create_redis_pubsub(self):
|
516
|
+
"""Create Redis Pub/Sub client."""
|
517
|
+
# Reuse cache factory for Redis
|
518
|
+
cache_factory = CacheFactory(
|
519
|
+
backend="redis", host=self.host, port=self.port, **self.extra_config
|
520
|
+
)
|
521
|
+
return await cache_factory.create()
|
522
|
+
|
523
|
+
def get_config(self) -> Dict[str, Any]:
|
524
|
+
"""Get factory configuration."""
|
525
|
+
config = {
|
526
|
+
"backend": self.backend,
|
527
|
+
"host": self.host,
|
528
|
+
"port": self.port,
|
529
|
+
"username": self.username,
|
530
|
+
}
|
531
|
+
config.update(self.extra_config)
|
532
|
+
# Don't include password
|
533
|
+
return {k: v for k, v in config.items() if k != "password"}
|