wappa 0.1.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.
Potentially problematic release.
This version of wappa might be problematic. Click here for more details.
- wappa/__init__.py +85 -0
- wappa/api/__init__.py +1 -0
- wappa/api/controllers/__init__.py +10 -0
- wappa/api/controllers/webhook_controller.py +441 -0
- wappa/api/dependencies/__init__.py +15 -0
- wappa/api/dependencies/whatsapp_dependencies.py +220 -0
- wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
- wappa/api/middleware/__init__.py +7 -0
- wappa/api/middleware/error_handler.py +158 -0
- wappa/api/middleware/owner.py +99 -0
- wappa/api/middleware/request_logging.py +184 -0
- wappa/api/routes/__init__.py +6 -0
- wappa/api/routes/health.py +102 -0
- wappa/api/routes/webhooks.py +211 -0
- wappa/api/routes/whatsapp/__init__.py +15 -0
- wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
- wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
- wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
- wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
- wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
- wappa/api/routes/whatsapp_combined.py +35 -0
- wappa/cli/__init__.py +9 -0
- wappa/cli/main.py +199 -0
- wappa/core/__init__.py +6 -0
- wappa/core/config/__init__.py +5 -0
- wappa/core/config/settings.py +161 -0
- wappa/core/events/__init__.py +41 -0
- wappa/core/events/default_handlers.py +642 -0
- wappa/core/events/event_dispatcher.py +244 -0
- wappa/core/events/event_handler.py +247 -0
- wappa/core/events/webhook_factory.py +219 -0
- wappa/core/factory/__init__.py +15 -0
- wappa/core/factory/plugin.py +68 -0
- wappa/core/factory/wappa_builder.py +326 -0
- wappa/core/logging/__init__.py +5 -0
- wappa/core/logging/context.py +100 -0
- wappa/core/logging/logger.py +343 -0
- wappa/core/plugins/__init__.py +34 -0
- wappa/core/plugins/auth_plugin.py +169 -0
- wappa/core/plugins/cors_plugin.py +128 -0
- wappa/core/plugins/custom_middleware_plugin.py +182 -0
- wappa/core/plugins/database_plugin.py +235 -0
- wappa/core/plugins/rate_limit_plugin.py +183 -0
- wappa/core/plugins/redis_plugin.py +224 -0
- wappa/core/plugins/wappa_core_plugin.py +261 -0
- wappa/core/plugins/webhook_plugin.py +253 -0
- wappa/core/types.py +108 -0
- wappa/core/wappa_app.py +546 -0
- wappa/database/__init__.py +18 -0
- wappa/database/adapter.py +107 -0
- wappa/database/adapters/__init__.py +17 -0
- wappa/database/adapters/mysql_adapter.py +187 -0
- wappa/database/adapters/postgresql_adapter.py +169 -0
- wappa/database/adapters/sqlite_adapter.py +174 -0
- wappa/domain/__init__.py +28 -0
- wappa/domain/builders/__init__.py +5 -0
- wappa/domain/builders/message_builder.py +189 -0
- wappa/domain/entities/__init__.py +5 -0
- wappa/domain/enums/messenger_platform.py +123 -0
- wappa/domain/factories/__init__.py +6 -0
- wappa/domain/factories/media_factory.py +450 -0
- wappa/domain/factories/message_factory.py +497 -0
- wappa/domain/factories/messenger_factory.py +244 -0
- wappa/domain/interfaces/__init__.py +32 -0
- wappa/domain/interfaces/base_repository.py +94 -0
- wappa/domain/interfaces/cache_factory.py +85 -0
- wappa/domain/interfaces/cache_interface.py +199 -0
- wappa/domain/interfaces/expiry_repository.py +68 -0
- wappa/domain/interfaces/media_interface.py +311 -0
- wappa/domain/interfaces/messaging_interface.py +523 -0
- wappa/domain/interfaces/pubsub_repository.py +151 -0
- wappa/domain/interfaces/repository_factory.py +108 -0
- wappa/domain/interfaces/shared_state_repository.py +122 -0
- wappa/domain/interfaces/state_repository.py +123 -0
- wappa/domain/interfaces/tables_repository.py +215 -0
- wappa/domain/interfaces/user_repository.py +114 -0
- wappa/domain/interfaces/webhooks/__init__.py +1 -0
- wappa/domain/models/media_result.py +110 -0
- wappa/domain/models/platforms/__init__.py +15 -0
- wappa/domain/models/platforms/platform_config.py +104 -0
- wappa/domain/services/__init__.py +11 -0
- wappa/domain/services/tenant_credentials_service.py +56 -0
- wappa/messaging/__init__.py +7 -0
- wappa/messaging/whatsapp/__init__.py +1 -0
- wappa/messaging/whatsapp/client/__init__.py +5 -0
- wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
- wappa/messaging/whatsapp/handlers/__init__.py +13 -0
- wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
- wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
- wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
- wappa/messaging/whatsapp/messenger/__init__.py +5 -0
- wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
- wappa/messaging/whatsapp/models/__init__.py +61 -0
- wappa/messaging/whatsapp/models/basic_models.py +65 -0
- wappa/messaging/whatsapp/models/interactive_models.py +287 -0
- wappa/messaging/whatsapp/models/media_models.py +215 -0
- wappa/messaging/whatsapp/models/specialized_models.py +304 -0
- wappa/messaging/whatsapp/models/template_models.py +261 -0
- wappa/persistence/cache_factory.py +93 -0
- wappa/persistence/json/__init__.py +14 -0
- wappa/persistence/json/cache_adapters.py +271 -0
- wappa/persistence/json/handlers/__init__.py +1 -0
- wappa/persistence/json/handlers/state_handler.py +250 -0
- wappa/persistence/json/handlers/table_handler.py +263 -0
- wappa/persistence/json/handlers/user_handler.py +213 -0
- wappa/persistence/json/handlers/utils/__init__.py +1 -0
- wappa/persistence/json/handlers/utils/file_manager.py +153 -0
- wappa/persistence/json/handlers/utils/key_factory.py +11 -0
- wappa/persistence/json/handlers/utils/serialization.py +121 -0
- wappa/persistence/json/json_cache_factory.py +76 -0
- wappa/persistence/json/storage_manager.py +285 -0
- wappa/persistence/memory/__init__.py +14 -0
- wappa/persistence/memory/cache_adapters.py +271 -0
- wappa/persistence/memory/handlers/__init__.py +1 -0
- wappa/persistence/memory/handlers/state_handler.py +250 -0
- wappa/persistence/memory/handlers/table_handler.py +280 -0
- wappa/persistence/memory/handlers/user_handler.py +213 -0
- wappa/persistence/memory/handlers/utils/__init__.py +1 -0
- wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
- wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
- wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
- wappa/persistence/memory/memory_cache_factory.py +76 -0
- wappa/persistence/memory/storage_manager.py +235 -0
- wappa/persistence/redis/README.md +699 -0
- wappa/persistence/redis/__init__.py +11 -0
- wappa/persistence/redis/cache_adapters.py +285 -0
- wappa/persistence/redis/ops.py +880 -0
- wappa/persistence/redis/redis_cache_factory.py +71 -0
- wappa/persistence/redis/redis_client.py +231 -0
- wappa/persistence/redis/redis_handler/__init__.py +26 -0
- wappa/persistence/redis/redis_handler/state_handler.py +176 -0
- wappa/persistence/redis/redis_handler/table.py +158 -0
- wappa/persistence/redis/redis_handler/user.py +138 -0
- wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
- wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
- wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
- wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
- wappa/persistence/redis/redis_manager.py +189 -0
- wappa/processors/__init__.py +6 -0
- wappa/processors/base_processor.py +262 -0
- wappa/processors/factory.py +550 -0
- wappa/processors/whatsapp_processor.py +810 -0
- wappa/schemas/__init__.py +6 -0
- wappa/schemas/core/__init__.py +71 -0
- wappa/schemas/core/base_message.py +499 -0
- wappa/schemas/core/base_status.py +322 -0
- wappa/schemas/core/base_webhook.py +312 -0
- wappa/schemas/core/types.py +253 -0
- wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
- wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
- wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
- wappa/schemas/factory.py +754 -0
- wappa/schemas/webhooks/__init__.py +3 -0
- wappa/schemas/whatsapp/__init__.py +6 -0
- wappa/schemas/whatsapp/base_models.py +285 -0
- wappa/schemas/whatsapp/message_types/__init__.py +93 -0
- wappa/schemas/whatsapp/message_types/audio.py +350 -0
- wappa/schemas/whatsapp/message_types/button.py +267 -0
- wappa/schemas/whatsapp/message_types/contact.py +464 -0
- wappa/schemas/whatsapp/message_types/document.py +421 -0
- wappa/schemas/whatsapp/message_types/errors.py +195 -0
- wappa/schemas/whatsapp/message_types/image.py +424 -0
- wappa/schemas/whatsapp/message_types/interactive.py +430 -0
- wappa/schemas/whatsapp/message_types/location.py +416 -0
- wappa/schemas/whatsapp/message_types/order.py +372 -0
- wappa/schemas/whatsapp/message_types/reaction.py +271 -0
- wappa/schemas/whatsapp/message_types/sticker.py +328 -0
- wappa/schemas/whatsapp/message_types/system.py +317 -0
- wappa/schemas/whatsapp/message_types/text.py +411 -0
- wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
- wappa/schemas/whatsapp/message_types/video.py +344 -0
- wappa/schemas/whatsapp/status_models.py +479 -0
- wappa/schemas/whatsapp/validators.py +454 -0
- wappa/schemas/whatsapp/webhook_container.py +438 -0
- wappa/webhooks/__init__.py +17 -0
- wappa/webhooks/core/__init__.py +71 -0
- wappa/webhooks/core/base_message.py +499 -0
- wappa/webhooks/core/base_status.py +322 -0
- wappa/webhooks/core/base_webhook.py +312 -0
- wappa/webhooks/core/types.py +253 -0
- wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
- wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
- wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
- wappa/webhooks/factory.py +754 -0
- wappa/webhooks/whatsapp/__init__.py +6 -0
- wappa/webhooks/whatsapp/base_models.py +285 -0
- wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
- wappa/webhooks/whatsapp/message_types/audio.py +350 -0
- wappa/webhooks/whatsapp/message_types/button.py +267 -0
- wappa/webhooks/whatsapp/message_types/contact.py +464 -0
- wappa/webhooks/whatsapp/message_types/document.py +421 -0
- wappa/webhooks/whatsapp/message_types/errors.py +195 -0
- wappa/webhooks/whatsapp/message_types/image.py +424 -0
- wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
- wappa/webhooks/whatsapp/message_types/location.py +416 -0
- wappa/webhooks/whatsapp/message_types/order.py +372 -0
- wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
- wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
- wappa/webhooks/whatsapp/message_types/system.py +317 -0
- wappa/webhooks/whatsapp/message_types/text.py +411 -0
- wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
- wappa/webhooks/whatsapp/message_types/video.py +344 -0
- wappa/webhooks/whatsapp/status_models.py +479 -0
- wappa/webhooks/whatsapp/validators.py +454 -0
- wappa/webhooks/whatsapp/webhook_container.py +438 -0
- wappa-0.1.0.dist-info/METADATA +269 -0
- wappa-0.1.0.dist-info/RECORD +211 -0
- wappa-0.1.0.dist-info/WHEEL +4 -0
- wappa-0.1.0.dist-info/entry_points.txt +2 -0
- wappa-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MySQL Database Adapter
|
|
3
|
+
|
|
4
|
+
Provides MySQL-specific implementation for SQLModel/SQLAlchemy async connections
|
|
5
|
+
using aiomysql as the async driver.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
from typing import Any, AsyncContextManager
|
|
11
|
+
|
|
12
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
|
13
|
+
from sqlalchemy.orm import sessionmaker
|
|
14
|
+
from sqlmodel import SQLModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MySQLAdapter:
|
|
18
|
+
"""
|
|
19
|
+
MySQL adapter for SQLModel/SQLAlchemy async connections.
|
|
20
|
+
|
|
21
|
+
Uses aiomysql driver for async MySQL operations.
|
|
22
|
+
Provides connection pooling, health checks, and schema management
|
|
23
|
+
with MySQL-specific optimizations.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
async def create_engine(self, connection_string: str, **kwargs: Any) -> AsyncEngine:
|
|
27
|
+
"""
|
|
28
|
+
Create MySQL async engine with aiomysql driver.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
connection_string: MySQL connection URL (mysql+aiomysql://...)
|
|
32
|
+
**kwargs: Engine configuration options
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Configured AsyncEngine for MySQL
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ValueError: If connection string is invalid
|
|
39
|
+
ConnectionError: If unable to create engine
|
|
40
|
+
"""
|
|
41
|
+
# Ensure aiomysql driver is specified
|
|
42
|
+
if not connection_string.startswith("mysql+aiomysql://"):
|
|
43
|
+
# Convert standard mysql:// to aiomysql version
|
|
44
|
+
if connection_string.startswith("mysql://"):
|
|
45
|
+
connection_string = connection_string.replace(
|
|
46
|
+
"mysql://", "mysql+aiomysql://", 1
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
"MySQL connection string must use mysql+aiomysql:// scheme"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Default MySQL engine configuration
|
|
54
|
+
default_config = {
|
|
55
|
+
"pool_size": 20,
|
|
56
|
+
"max_overflow": 30,
|
|
57
|
+
"pool_timeout": 30,
|
|
58
|
+
"pool_recycle": 3600,
|
|
59
|
+
"pool_pre_ping": True,
|
|
60
|
+
"echo": False,
|
|
61
|
+
"connect_args": {
|
|
62
|
+
"charset": "utf8mb4",
|
|
63
|
+
"autocommit": False,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
default_config.update(kwargs)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
engine = create_async_engine(connection_string, **default_config)
|
|
70
|
+
return engine
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise ConnectionError(f"Failed to create MySQL engine: {e}") from e
|
|
73
|
+
|
|
74
|
+
async def create_session_factory(
|
|
75
|
+
self, engine: AsyncEngine
|
|
76
|
+
) -> Callable[[], AsyncContextManager[AsyncSession]]:
|
|
77
|
+
"""
|
|
78
|
+
Create session factory for MySQL async sessions.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
engine: MySQL AsyncEngine instance
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Session factory function that returns context manager
|
|
85
|
+
"""
|
|
86
|
+
# Create async session maker
|
|
87
|
+
async_session_maker = sessionmaker(
|
|
88
|
+
engine,
|
|
89
|
+
class_=AsyncSession,
|
|
90
|
+
expire_on_commit=False,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@asynccontextmanager
|
|
94
|
+
async def session_factory():
|
|
95
|
+
async with async_session_maker() as session:
|
|
96
|
+
try:
|
|
97
|
+
yield session
|
|
98
|
+
await session.commit()
|
|
99
|
+
except Exception:
|
|
100
|
+
await session.rollback()
|
|
101
|
+
raise
|
|
102
|
+
|
|
103
|
+
return session_factory
|
|
104
|
+
|
|
105
|
+
async def initialize_schema(
|
|
106
|
+
self, engine: AsyncEngine, models: list[type[SQLModel]] = None
|
|
107
|
+
) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Initialize MySQL schema from SQLModel definitions.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
engine: MySQL AsyncEngine instance
|
|
113
|
+
models: List of SQLModel classes to create tables for
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
DatabaseError: If schema creation fails
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
async with engine.begin() as conn:
|
|
120
|
+
# Set MySQL specific settings for better compatibility
|
|
121
|
+
await conn.execute(
|
|
122
|
+
"SET sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO'"
|
|
123
|
+
)
|
|
124
|
+
# Create all tables from SQLModel metadata
|
|
125
|
+
await conn.run_sync(SQLModel.metadata.create_all)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise RuntimeError(f"Failed to initialize MySQL schema: {e}") from e
|
|
128
|
+
|
|
129
|
+
async def health_check(self, engine: AsyncEngine) -> bool:
|
|
130
|
+
"""
|
|
131
|
+
Perform MySQL health check.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
engine: MySQL AsyncEngine instance
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
True if database is healthy and responsive
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
async with engine.begin() as conn:
|
|
141
|
+
result = await conn.execute("SELECT 1")
|
|
142
|
+
return result.scalar() == 1
|
|
143
|
+
except Exception:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
async def get_connection_info(self, engine: AsyncEngine) -> dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Get MySQL connection information.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
engine: MySQL AsyncEngine instance
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dictionary with MySQL connection details
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
async with engine.begin() as conn:
|
|
158
|
+
version_result = await conn.execute("SELECT VERSION()")
|
|
159
|
+
version = version_result.scalar()
|
|
160
|
+
|
|
161
|
+
# Get character set info
|
|
162
|
+
charset_result = await conn.execute("SELECT @@character_set_database")
|
|
163
|
+
charset = charset_result.scalar()
|
|
164
|
+
|
|
165
|
+
# Get collation info
|
|
166
|
+
collation_result = await conn.execute("SELECT @@collation_database")
|
|
167
|
+
collation = collation_result.scalar()
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"driver": "aiomysql",
|
|
171
|
+
"database": "mysql",
|
|
172
|
+
"version": version,
|
|
173
|
+
"charset": charset,
|
|
174
|
+
"collation": collation,
|
|
175
|
+
"pool_size": engine.pool.size(),
|
|
176
|
+
"pool_checked_in": engine.pool.checkedin(),
|
|
177
|
+
"pool_checked_out": engine.pool.checkedout(),
|
|
178
|
+
"pool_invalid": engine.pool.invalidated(),
|
|
179
|
+
"healthy": True,
|
|
180
|
+
}
|
|
181
|
+
except Exception as e:
|
|
182
|
+
return {
|
|
183
|
+
"driver": "aiomysql",
|
|
184
|
+
"database": "mysql",
|
|
185
|
+
"error": str(e),
|
|
186
|
+
"healthy": False,
|
|
187
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PostgreSQL Database Adapter
|
|
3
|
+
|
|
4
|
+
Provides PostgreSQL-specific implementation for SQLModel/SQLAlchemy async connections
|
|
5
|
+
using asyncpg as the async driver.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
from typing import Any, AsyncContextManager
|
|
11
|
+
|
|
12
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
|
13
|
+
from sqlalchemy.orm import sessionmaker
|
|
14
|
+
from sqlmodel import SQLModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PostgreSQLAdapter:
|
|
18
|
+
"""
|
|
19
|
+
PostgreSQL adapter for SQLModel/SQLAlchemy async connections.
|
|
20
|
+
|
|
21
|
+
Uses asyncpg driver for optimal PostgreSQL async performance.
|
|
22
|
+
Provides connection pooling, health checks, and schema management.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
async def create_engine(self, connection_string: str, **kwargs: Any) -> AsyncEngine:
|
|
26
|
+
"""
|
|
27
|
+
Create PostgreSQL async engine with asyncpg driver.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
connection_string: PostgreSQL connection URL (postgresql+asyncpg://...)
|
|
31
|
+
**kwargs: Engine configuration options
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Configured AsyncEngine for PostgreSQL
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If connection string is invalid
|
|
38
|
+
ConnectionError: If unable to create engine
|
|
39
|
+
"""
|
|
40
|
+
# Ensure asyncpg driver is specified
|
|
41
|
+
if not connection_string.startswith(
|
|
42
|
+
("postgresql+asyncpg://", "postgres+asyncpg://")
|
|
43
|
+
):
|
|
44
|
+
# Convert standard postgresql:// to asyncpg version
|
|
45
|
+
if connection_string.startswith(("postgresql://", "postgres://")):
|
|
46
|
+
connection_string = connection_string.replace(
|
|
47
|
+
"postgresql://", "postgresql+asyncpg://", 1
|
|
48
|
+
).replace("postgres://", "postgresql+asyncpg://", 1)
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"PostgreSQL connection string must use postgresql+asyncpg:// scheme"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Default PostgreSQL engine configuration
|
|
55
|
+
default_config = {
|
|
56
|
+
"pool_size": 20,
|
|
57
|
+
"max_overflow": 40,
|
|
58
|
+
"pool_timeout": 30,
|
|
59
|
+
"pool_recycle": 3600,
|
|
60
|
+
"pool_pre_ping": True,
|
|
61
|
+
"echo": False,
|
|
62
|
+
}
|
|
63
|
+
default_config.update(kwargs)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
engine = create_async_engine(connection_string, **default_config)
|
|
67
|
+
return engine
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise ConnectionError(f"Failed to create PostgreSQL engine: {e}") from e
|
|
70
|
+
|
|
71
|
+
async def create_session_factory(
|
|
72
|
+
self, engine: AsyncEngine
|
|
73
|
+
) -> Callable[[], AsyncContextManager[AsyncSession]]:
|
|
74
|
+
"""
|
|
75
|
+
Create session factory for PostgreSQL async sessions.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
engine: PostgreSQL AsyncEngine instance
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Session factory function that returns context manager
|
|
82
|
+
"""
|
|
83
|
+
# Create async session maker
|
|
84
|
+
async_session_maker = sessionmaker(
|
|
85
|
+
engine,
|
|
86
|
+
class_=AsyncSession,
|
|
87
|
+
expire_on_commit=False,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@asynccontextmanager
|
|
91
|
+
async def session_factory():
|
|
92
|
+
async with async_session_maker() as session:
|
|
93
|
+
try:
|
|
94
|
+
yield session
|
|
95
|
+
await session.commit()
|
|
96
|
+
except Exception:
|
|
97
|
+
await session.rollback()
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
return session_factory
|
|
101
|
+
|
|
102
|
+
async def initialize_schema(
|
|
103
|
+
self, engine: AsyncEngine, models: list[type[SQLModel]] = None
|
|
104
|
+
) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Initialize PostgreSQL schema from SQLModel definitions.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
engine: PostgreSQL AsyncEngine instance
|
|
110
|
+
models: List of SQLModel classes to create tables for
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
DatabaseError: If schema creation fails
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
async with engine.begin() as conn:
|
|
117
|
+
# Create all tables from SQLModel metadata
|
|
118
|
+
await conn.run_sync(SQLModel.metadata.create_all)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise RuntimeError(f"Failed to initialize PostgreSQL schema: {e}") from e
|
|
121
|
+
|
|
122
|
+
async def health_check(self, engine: AsyncEngine) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Perform PostgreSQL health check.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
engine: PostgreSQL AsyncEngine instance
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if database is healthy and responsive
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
async with engine.begin() as conn:
|
|
134
|
+
result = await conn.execute("SELECT 1")
|
|
135
|
+
return result.scalar() == 1
|
|
136
|
+
except Exception:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
async def get_connection_info(self, engine: AsyncEngine) -> dict[str, Any]:
|
|
140
|
+
"""
|
|
141
|
+
Get PostgreSQL connection information.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
engine: PostgreSQL AsyncEngine instance
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dictionary with PostgreSQL connection details
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
async with engine.begin() as conn:
|
|
151
|
+
version_result = await conn.execute("SELECT version()")
|
|
152
|
+
version = version_result.scalar()
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"driver": "asyncpg",
|
|
156
|
+
"database": "postgresql",
|
|
157
|
+
"version": version,
|
|
158
|
+
"pool_size": engine.pool.size(),
|
|
159
|
+
"pool_checked_in": engine.pool.checkedin(),
|
|
160
|
+
"pool_checked_out": engine.pool.checkedout(),
|
|
161
|
+
"pool_invalid": engine.pool.invalidated(),
|
|
162
|
+
}
|
|
163
|
+
except Exception as e:
|
|
164
|
+
return {
|
|
165
|
+
"driver": "asyncpg",
|
|
166
|
+
"database": "postgresql",
|
|
167
|
+
"error": str(e),
|
|
168
|
+
"healthy": False,
|
|
169
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQLite Database Adapter
|
|
3
|
+
|
|
4
|
+
Provides SQLite-specific implementation for SQLModel/SQLAlchemy async connections
|
|
5
|
+
using aiosqlite as the async driver.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
from typing import Any, AsyncContextManager
|
|
11
|
+
|
|
12
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
|
13
|
+
from sqlalchemy.orm import sessionmaker
|
|
14
|
+
from sqlmodel import SQLModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SQLiteAdapter:
|
|
18
|
+
"""
|
|
19
|
+
SQLite adapter for SQLModel/SQLAlchemy async connections.
|
|
20
|
+
|
|
21
|
+
Uses aiosqlite driver for async SQLite operations.
|
|
22
|
+
Provides connection management, health checks, and schema management
|
|
23
|
+
with SQLite-specific optimizations.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
async def create_engine(self, connection_string: str, **kwargs: Any) -> AsyncEngine:
|
|
27
|
+
"""
|
|
28
|
+
Create SQLite async engine with aiosqlite driver.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
connection_string: SQLite connection URL (sqlite+aiosqlite://...)
|
|
32
|
+
**kwargs: Engine configuration options
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Configured AsyncEngine for SQLite
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ValueError: If connection string is invalid
|
|
39
|
+
ConnectionError: If unable to create engine
|
|
40
|
+
"""
|
|
41
|
+
# Ensure aiosqlite driver is specified
|
|
42
|
+
if not connection_string.startswith("sqlite+aiosqlite://"):
|
|
43
|
+
# Convert standard sqlite:// to aiosqlite version
|
|
44
|
+
if connection_string.startswith("sqlite://"):
|
|
45
|
+
connection_string = connection_string.replace(
|
|
46
|
+
"sqlite://", "sqlite+aiosqlite://", 1
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
"SQLite connection string must use sqlite+aiosqlite:// scheme"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Default SQLite engine configuration
|
|
54
|
+
default_config = {
|
|
55
|
+
"echo": False,
|
|
56
|
+
"connect_args": {
|
|
57
|
+
"check_same_thread": False, # Required for async
|
|
58
|
+
"timeout": 30,
|
|
59
|
+
},
|
|
60
|
+
# SQLite doesn't use connection pooling in the traditional sense
|
|
61
|
+
"poolclass": None,
|
|
62
|
+
}
|
|
63
|
+
default_config.update(kwargs)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
engine = create_async_engine(connection_string, **default_config)
|
|
67
|
+
return engine
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise ConnectionError(f"Failed to create SQLite engine: {e}") from e
|
|
70
|
+
|
|
71
|
+
async def create_session_factory(
|
|
72
|
+
self, engine: AsyncEngine
|
|
73
|
+
) -> Callable[[], AsyncContextManager[AsyncSession]]:
|
|
74
|
+
"""
|
|
75
|
+
Create session factory for SQLite async sessions.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
engine: SQLite AsyncEngine instance
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Session factory function that returns context manager
|
|
82
|
+
"""
|
|
83
|
+
# Create async session maker
|
|
84
|
+
async_session_maker = sessionmaker(
|
|
85
|
+
engine,
|
|
86
|
+
class_=AsyncSession,
|
|
87
|
+
expire_on_commit=False,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@asynccontextmanager
|
|
91
|
+
async def session_factory():
|
|
92
|
+
async with async_session_maker() as session:
|
|
93
|
+
try:
|
|
94
|
+
yield session
|
|
95
|
+
await session.commit()
|
|
96
|
+
except Exception:
|
|
97
|
+
await session.rollback()
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
return session_factory
|
|
101
|
+
|
|
102
|
+
async def initialize_schema(
|
|
103
|
+
self, engine: AsyncEngine, models: list[type[SQLModel]] = None
|
|
104
|
+
) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Initialize SQLite schema from SQLModel definitions.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
engine: SQLite AsyncEngine instance
|
|
110
|
+
models: List of SQLModel classes to create tables for
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
DatabaseError: If schema creation fails
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
async with engine.begin() as conn:
|
|
117
|
+
# Enable foreign key support for SQLite
|
|
118
|
+
await conn.execute("PRAGMA foreign_keys=ON")
|
|
119
|
+
# Create all tables from SQLModel metadata
|
|
120
|
+
await conn.run_sync(SQLModel.metadata.create_all)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
raise RuntimeError(f"Failed to initialize SQLite schema: {e}") from e
|
|
123
|
+
|
|
124
|
+
async def health_check(self, engine: AsyncEngine) -> bool:
|
|
125
|
+
"""
|
|
126
|
+
Perform SQLite health check.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
engine: SQLite AsyncEngine instance
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if database is healthy and responsive
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
async with engine.begin() as conn:
|
|
136
|
+
result = await conn.execute("SELECT 1")
|
|
137
|
+
return result.scalar() == 1
|
|
138
|
+
except Exception:
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
async def get_connection_info(self, engine: AsyncEngine) -> dict[str, Any]:
|
|
142
|
+
"""
|
|
143
|
+
Get SQLite connection information.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
engine: SQLite AsyncEngine instance
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dictionary with SQLite connection details
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
async with engine.begin() as conn:
|
|
153
|
+
version_result = await conn.execute("SELECT sqlite_version()")
|
|
154
|
+
version = version_result.scalar()
|
|
155
|
+
|
|
156
|
+
# Get database file info
|
|
157
|
+
pragma_result = await conn.execute("PRAGMA database_list")
|
|
158
|
+
database_info = pragma_result.fetchall()
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"driver": "aiosqlite",
|
|
162
|
+
"database": "sqlite",
|
|
163
|
+
"version": version,
|
|
164
|
+
"database_file": database_info[0][2] if database_info else "memory",
|
|
165
|
+
"foreign_keys_enabled": True, # We enable this by default
|
|
166
|
+
"healthy": True,
|
|
167
|
+
}
|
|
168
|
+
except Exception as e:
|
|
169
|
+
return {
|
|
170
|
+
"driver": "aiosqlite",
|
|
171
|
+
"database": "sqlite",
|
|
172
|
+
"error": str(e),
|
|
173
|
+
"healthy": False,
|
|
174
|
+
}
|
wappa/domain/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Domain layer for the Mimeia AI Agent Platform.
|
|
3
|
+
|
|
4
|
+
This layer contains the core business logic, entities, and interfaces.
|
|
5
|
+
It's independent of external concerns and defines the contract for
|
|
6
|
+
infrastructure implementations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Export domain interfaces
|
|
10
|
+
from .interfaces import (
|
|
11
|
+
IBaseRepository,
|
|
12
|
+
IExpiryRepository,
|
|
13
|
+
IPubSubRepository,
|
|
14
|
+
IRepositoryFactory,
|
|
15
|
+
ISharedStateRepository,
|
|
16
|
+
IStateRepository,
|
|
17
|
+
IUserRepository,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"IBaseRepository",
|
|
22
|
+
"IUserRepository",
|
|
23
|
+
"IStateRepository",
|
|
24
|
+
"ISharedStateRepository",
|
|
25
|
+
"IExpiryRepository",
|
|
26
|
+
"IPubSubRepository",
|
|
27
|
+
"IRepositoryFactory",
|
|
28
|
+
]
|