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.

Files changed (211) hide show
  1. wappa/__init__.py +85 -0
  2. wappa/api/__init__.py +1 -0
  3. wappa/api/controllers/__init__.py +10 -0
  4. wappa/api/controllers/webhook_controller.py +441 -0
  5. wappa/api/dependencies/__init__.py +15 -0
  6. wappa/api/dependencies/whatsapp_dependencies.py +220 -0
  7. wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
  8. wappa/api/middleware/__init__.py +7 -0
  9. wappa/api/middleware/error_handler.py +158 -0
  10. wappa/api/middleware/owner.py +99 -0
  11. wappa/api/middleware/request_logging.py +184 -0
  12. wappa/api/routes/__init__.py +6 -0
  13. wappa/api/routes/health.py +102 -0
  14. wappa/api/routes/webhooks.py +211 -0
  15. wappa/api/routes/whatsapp/__init__.py +15 -0
  16. wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
  17. wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
  18. wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
  19. wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
  20. wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
  21. wappa/api/routes/whatsapp_combined.py +35 -0
  22. wappa/cli/__init__.py +9 -0
  23. wappa/cli/main.py +199 -0
  24. wappa/core/__init__.py +6 -0
  25. wappa/core/config/__init__.py +5 -0
  26. wappa/core/config/settings.py +161 -0
  27. wappa/core/events/__init__.py +41 -0
  28. wappa/core/events/default_handlers.py +642 -0
  29. wappa/core/events/event_dispatcher.py +244 -0
  30. wappa/core/events/event_handler.py +247 -0
  31. wappa/core/events/webhook_factory.py +219 -0
  32. wappa/core/factory/__init__.py +15 -0
  33. wappa/core/factory/plugin.py +68 -0
  34. wappa/core/factory/wappa_builder.py +326 -0
  35. wappa/core/logging/__init__.py +5 -0
  36. wappa/core/logging/context.py +100 -0
  37. wappa/core/logging/logger.py +343 -0
  38. wappa/core/plugins/__init__.py +34 -0
  39. wappa/core/plugins/auth_plugin.py +169 -0
  40. wappa/core/plugins/cors_plugin.py +128 -0
  41. wappa/core/plugins/custom_middleware_plugin.py +182 -0
  42. wappa/core/plugins/database_plugin.py +235 -0
  43. wappa/core/plugins/rate_limit_plugin.py +183 -0
  44. wappa/core/plugins/redis_plugin.py +224 -0
  45. wappa/core/plugins/wappa_core_plugin.py +261 -0
  46. wappa/core/plugins/webhook_plugin.py +253 -0
  47. wappa/core/types.py +108 -0
  48. wappa/core/wappa_app.py +546 -0
  49. wappa/database/__init__.py +18 -0
  50. wappa/database/adapter.py +107 -0
  51. wappa/database/adapters/__init__.py +17 -0
  52. wappa/database/adapters/mysql_adapter.py +187 -0
  53. wappa/database/adapters/postgresql_adapter.py +169 -0
  54. wappa/database/adapters/sqlite_adapter.py +174 -0
  55. wappa/domain/__init__.py +28 -0
  56. wappa/domain/builders/__init__.py +5 -0
  57. wappa/domain/builders/message_builder.py +189 -0
  58. wappa/domain/entities/__init__.py +5 -0
  59. wappa/domain/enums/messenger_platform.py +123 -0
  60. wappa/domain/factories/__init__.py +6 -0
  61. wappa/domain/factories/media_factory.py +450 -0
  62. wappa/domain/factories/message_factory.py +497 -0
  63. wappa/domain/factories/messenger_factory.py +244 -0
  64. wappa/domain/interfaces/__init__.py +32 -0
  65. wappa/domain/interfaces/base_repository.py +94 -0
  66. wappa/domain/interfaces/cache_factory.py +85 -0
  67. wappa/domain/interfaces/cache_interface.py +199 -0
  68. wappa/domain/interfaces/expiry_repository.py +68 -0
  69. wappa/domain/interfaces/media_interface.py +311 -0
  70. wappa/domain/interfaces/messaging_interface.py +523 -0
  71. wappa/domain/interfaces/pubsub_repository.py +151 -0
  72. wappa/domain/interfaces/repository_factory.py +108 -0
  73. wappa/domain/interfaces/shared_state_repository.py +122 -0
  74. wappa/domain/interfaces/state_repository.py +123 -0
  75. wappa/domain/interfaces/tables_repository.py +215 -0
  76. wappa/domain/interfaces/user_repository.py +114 -0
  77. wappa/domain/interfaces/webhooks/__init__.py +1 -0
  78. wappa/domain/models/media_result.py +110 -0
  79. wappa/domain/models/platforms/__init__.py +15 -0
  80. wappa/domain/models/platforms/platform_config.py +104 -0
  81. wappa/domain/services/__init__.py +11 -0
  82. wappa/domain/services/tenant_credentials_service.py +56 -0
  83. wappa/messaging/__init__.py +7 -0
  84. wappa/messaging/whatsapp/__init__.py +1 -0
  85. wappa/messaging/whatsapp/client/__init__.py +5 -0
  86. wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
  87. wappa/messaging/whatsapp/handlers/__init__.py +13 -0
  88. wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
  89. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
  90. wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
  91. wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
  92. wappa/messaging/whatsapp/messenger/__init__.py +5 -0
  93. wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
  94. wappa/messaging/whatsapp/models/__init__.py +61 -0
  95. wappa/messaging/whatsapp/models/basic_models.py +65 -0
  96. wappa/messaging/whatsapp/models/interactive_models.py +287 -0
  97. wappa/messaging/whatsapp/models/media_models.py +215 -0
  98. wappa/messaging/whatsapp/models/specialized_models.py +304 -0
  99. wappa/messaging/whatsapp/models/template_models.py +261 -0
  100. wappa/persistence/cache_factory.py +93 -0
  101. wappa/persistence/json/__init__.py +14 -0
  102. wappa/persistence/json/cache_adapters.py +271 -0
  103. wappa/persistence/json/handlers/__init__.py +1 -0
  104. wappa/persistence/json/handlers/state_handler.py +250 -0
  105. wappa/persistence/json/handlers/table_handler.py +263 -0
  106. wappa/persistence/json/handlers/user_handler.py +213 -0
  107. wappa/persistence/json/handlers/utils/__init__.py +1 -0
  108. wappa/persistence/json/handlers/utils/file_manager.py +153 -0
  109. wappa/persistence/json/handlers/utils/key_factory.py +11 -0
  110. wappa/persistence/json/handlers/utils/serialization.py +121 -0
  111. wappa/persistence/json/json_cache_factory.py +76 -0
  112. wappa/persistence/json/storage_manager.py +285 -0
  113. wappa/persistence/memory/__init__.py +14 -0
  114. wappa/persistence/memory/cache_adapters.py +271 -0
  115. wappa/persistence/memory/handlers/__init__.py +1 -0
  116. wappa/persistence/memory/handlers/state_handler.py +250 -0
  117. wappa/persistence/memory/handlers/table_handler.py +280 -0
  118. wappa/persistence/memory/handlers/user_handler.py +213 -0
  119. wappa/persistence/memory/handlers/utils/__init__.py +1 -0
  120. wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
  121. wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
  122. wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
  123. wappa/persistence/memory/memory_cache_factory.py +76 -0
  124. wappa/persistence/memory/storage_manager.py +235 -0
  125. wappa/persistence/redis/README.md +699 -0
  126. wappa/persistence/redis/__init__.py +11 -0
  127. wappa/persistence/redis/cache_adapters.py +285 -0
  128. wappa/persistence/redis/ops.py +880 -0
  129. wappa/persistence/redis/redis_cache_factory.py +71 -0
  130. wappa/persistence/redis/redis_client.py +231 -0
  131. wappa/persistence/redis/redis_handler/__init__.py +26 -0
  132. wappa/persistence/redis/redis_handler/state_handler.py +176 -0
  133. wappa/persistence/redis/redis_handler/table.py +158 -0
  134. wappa/persistence/redis/redis_handler/user.py +138 -0
  135. wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
  136. wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
  137. wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
  138. wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
  139. wappa/persistence/redis/redis_manager.py +189 -0
  140. wappa/processors/__init__.py +6 -0
  141. wappa/processors/base_processor.py +262 -0
  142. wappa/processors/factory.py +550 -0
  143. wappa/processors/whatsapp_processor.py +810 -0
  144. wappa/schemas/__init__.py +6 -0
  145. wappa/schemas/core/__init__.py +71 -0
  146. wappa/schemas/core/base_message.py +499 -0
  147. wappa/schemas/core/base_status.py +322 -0
  148. wappa/schemas/core/base_webhook.py +312 -0
  149. wappa/schemas/core/types.py +253 -0
  150. wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
  151. wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
  152. wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
  153. wappa/schemas/factory.py +754 -0
  154. wappa/schemas/webhooks/__init__.py +3 -0
  155. wappa/schemas/whatsapp/__init__.py +6 -0
  156. wappa/schemas/whatsapp/base_models.py +285 -0
  157. wappa/schemas/whatsapp/message_types/__init__.py +93 -0
  158. wappa/schemas/whatsapp/message_types/audio.py +350 -0
  159. wappa/schemas/whatsapp/message_types/button.py +267 -0
  160. wappa/schemas/whatsapp/message_types/contact.py +464 -0
  161. wappa/schemas/whatsapp/message_types/document.py +421 -0
  162. wappa/schemas/whatsapp/message_types/errors.py +195 -0
  163. wappa/schemas/whatsapp/message_types/image.py +424 -0
  164. wappa/schemas/whatsapp/message_types/interactive.py +430 -0
  165. wappa/schemas/whatsapp/message_types/location.py +416 -0
  166. wappa/schemas/whatsapp/message_types/order.py +372 -0
  167. wappa/schemas/whatsapp/message_types/reaction.py +271 -0
  168. wappa/schemas/whatsapp/message_types/sticker.py +328 -0
  169. wappa/schemas/whatsapp/message_types/system.py +317 -0
  170. wappa/schemas/whatsapp/message_types/text.py +411 -0
  171. wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
  172. wappa/schemas/whatsapp/message_types/video.py +344 -0
  173. wappa/schemas/whatsapp/status_models.py +479 -0
  174. wappa/schemas/whatsapp/validators.py +454 -0
  175. wappa/schemas/whatsapp/webhook_container.py +438 -0
  176. wappa/webhooks/__init__.py +17 -0
  177. wappa/webhooks/core/__init__.py +71 -0
  178. wappa/webhooks/core/base_message.py +499 -0
  179. wappa/webhooks/core/base_status.py +322 -0
  180. wappa/webhooks/core/base_webhook.py +312 -0
  181. wappa/webhooks/core/types.py +253 -0
  182. wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
  183. wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
  184. wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
  185. wappa/webhooks/factory.py +754 -0
  186. wappa/webhooks/whatsapp/__init__.py +6 -0
  187. wappa/webhooks/whatsapp/base_models.py +285 -0
  188. wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
  189. wappa/webhooks/whatsapp/message_types/audio.py +350 -0
  190. wappa/webhooks/whatsapp/message_types/button.py +267 -0
  191. wappa/webhooks/whatsapp/message_types/contact.py +464 -0
  192. wappa/webhooks/whatsapp/message_types/document.py +421 -0
  193. wappa/webhooks/whatsapp/message_types/errors.py +195 -0
  194. wappa/webhooks/whatsapp/message_types/image.py +424 -0
  195. wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
  196. wappa/webhooks/whatsapp/message_types/location.py +416 -0
  197. wappa/webhooks/whatsapp/message_types/order.py +372 -0
  198. wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
  199. wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
  200. wappa/webhooks/whatsapp/message_types/system.py +317 -0
  201. wappa/webhooks/whatsapp/message_types/text.py +411 -0
  202. wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
  203. wappa/webhooks/whatsapp/message_types/video.py +344 -0
  204. wappa/webhooks/whatsapp/status_models.py +479 -0
  205. wappa/webhooks/whatsapp/validators.py +454 -0
  206. wappa/webhooks/whatsapp/webhook_container.py +438 -0
  207. wappa-0.1.0.dist-info/METADATA +269 -0
  208. wappa-0.1.0.dist-info/RECORD +211 -0
  209. wappa-0.1.0.dist-info/WHEEL +4 -0
  210. wappa-0.1.0.dist-info/entry_points.txt +2 -0
  211. 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
+ }
@@ -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
+ ]
@@ -0,0 +1,5 @@
1
+ """Domain builders package."""
2
+
3
+ from .message_builder import MessageBuilder
4
+
5
+ __all__ = ["MessageBuilder"]