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,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Repository factory interface.
|
|
3
|
+
|
|
4
|
+
Defines contract for creating context-aware repository instances.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
from .expiry_repository import IExpiryRepository
|
|
10
|
+
from .pubsub_repository import IPubSubRepository
|
|
11
|
+
from .shared_state_repository import ISharedStateRepository
|
|
12
|
+
from .state_repository import IStateRepository
|
|
13
|
+
from .tables_repository import ITablesRepository
|
|
14
|
+
from .user_repository import IUserRepository
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class IRepositoryFactory(ABC):
|
|
18
|
+
"""
|
|
19
|
+
Interface for creating context-aware repository instances.
|
|
20
|
+
|
|
21
|
+
Ensures all repositories are bound to the correct tenant and user context.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def create_user_repository(self, tenant_id: str, user_id: str) -> IUserRepository:
|
|
26
|
+
"""
|
|
27
|
+
Create user repository with context binding.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
tenant_id: Tenant identifier
|
|
31
|
+
user_id: User identifier
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Context-bound user repository instance
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def create_state_repository(self, tenant_id: str, user_id: str) -> IStateRepository:
|
|
40
|
+
"""
|
|
41
|
+
Create state repository with context binding.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
tenant_id: Tenant identifier
|
|
45
|
+
user_id: User identifier
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Context-bound state repository instance
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def create_shared_state_repository(
|
|
54
|
+
self, tenant_id: str, user_id: str
|
|
55
|
+
) -> ISharedStateRepository:
|
|
56
|
+
"""
|
|
57
|
+
Create shared state repository with context binding.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
tenant_id: Tenant identifier
|
|
61
|
+
user_id: User identifier
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Context-bound shared state repository instance
|
|
65
|
+
"""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def create_expiry_repository(self, tenant_id: str) -> IExpiryRepository:
|
|
70
|
+
"""
|
|
71
|
+
Create expiry repository with context binding.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
tenant_id: Tenant identifier
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Context-bound expiry repository instance
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def create_pubsub_repository(
|
|
83
|
+
self, tenant_id: str, user_id: str | None = None
|
|
84
|
+
) -> IPubSubRepository:
|
|
85
|
+
"""
|
|
86
|
+
Create pub/sub repository with context binding.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
tenant_id: Tenant identifier
|
|
90
|
+
user_id: Optional user identifier (for user-specific channels)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Context-bound pub/sub repository instance
|
|
94
|
+
"""
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def create_tables_repository(self, tenant_id: str) -> ITablesRepository:
|
|
99
|
+
"""
|
|
100
|
+
Create tables repository with context binding.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
tenant_id: Tenant identifier
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Context-bound tables repository instance
|
|
107
|
+
"""
|
|
108
|
+
pass
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared state repository interface.
|
|
3
|
+
|
|
4
|
+
Defines contract for shared state management in Redis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from .base_repository import IBaseRepository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ISharedStateRepository(IBaseRepository):
|
|
17
|
+
"""
|
|
18
|
+
Interface for shared state management.
|
|
19
|
+
|
|
20
|
+
Handles shared state and tool scratch-space with context binding.
|
|
21
|
+
Uses the 'symphony_shared_state' Redis pool (database 3).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def get_shared_state(
|
|
26
|
+
self,
|
|
27
|
+
state_name: str,
|
|
28
|
+
models: type[BaseModel] | None = None,
|
|
29
|
+
) -> dict[str, Any] | None:
|
|
30
|
+
"""
|
|
31
|
+
Get shared state for user with optional BaseModel deserialization.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
state_name: Name of the state (e.g., 'conversation', 'context')
|
|
35
|
+
models: Optional BaseModel class for full object reconstruction
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Shared state dictionary or None if not found
|
|
39
|
+
"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
async def set_shared_state(
|
|
44
|
+
self,
|
|
45
|
+
state_name: str,
|
|
46
|
+
state_data: dict[str, Any],
|
|
47
|
+
ttl: timedelta | None = None,
|
|
48
|
+
) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
Set shared state for user.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
state_name: Name of the state
|
|
54
|
+
state_data: State data dictionary
|
|
55
|
+
ttl: Optional time-to-live
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if successful
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
async def get_state_field(self, state_name: str, field: str) -> Any | None:
|
|
64
|
+
"""
|
|
65
|
+
Get specific field from shared state.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
state_name: Name of the state
|
|
69
|
+
field: Field name
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Field value or None if not found
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def set_state_field(self, state_name: str, field: str, value: Any) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Set specific field in shared state.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
state_name: Name of the state
|
|
83
|
+
field: Field name
|
|
84
|
+
value: Field value
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if successful
|
|
88
|
+
"""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def delete_shared_state(self, state_name: str) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
Delete complete shared state.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
state_name: Name of the state
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if state was deleted
|
|
101
|
+
"""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
async def get_user_states(self) -> list[str]:
|
|
106
|
+
"""
|
|
107
|
+
Get all active state names for user.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List of state names
|
|
111
|
+
"""
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
async def clear_user_states(self) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
Clear all shared states for user.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if states were cleared
|
|
121
|
+
"""
|
|
122
|
+
pass
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
State repository interface.
|
|
3
|
+
|
|
4
|
+
Defines contract for handler state management in Redis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from .base_repository import IBaseRepository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IStateRepository(IBaseRepository):
|
|
17
|
+
"""
|
|
18
|
+
Interface for handler state management.
|
|
19
|
+
|
|
20
|
+
Handles flow states and handler-specific data with context binding.
|
|
21
|
+
Uses the 'handlers' Redis pool (database 2).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def get_handler_state(
|
|
26
|
+
self,
|
|
27
|
+
handler_type: str,
|
|
28
|
+
models: type[BaseModel] | None = None,
|
|
29
|
+
) -> dict[str, Any] | None:
|
|
30
|
+
"""
|
|
31
|
+
Get handler state for user with optional BaseModel deserialization.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
handler_type: Type of handler (e.g., 'registration', 'reservation')
|
|
35
|
+
models: Optional BaseModel class for full object reconstruction
|
|
36
|
+
e.g., DemoHotelRegistrationState (will automatically handle nested models)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Handler state dictionary or None if not found
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def set_handler_state(
|
|
45
|
+
self,
|
|
46
|
+
handler_type: str,
|
|
47
|
+
state_data: dict[str, Any],
|
|
48
|
+
ttl: timedelta | None = None,
|
|
49
|
+
) -> bool:
|
|
50
|
+
"""
|
|
51
|
+
Set handler state for user.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
handler_type: Type of handler
|
|
55
|
+
state_data: State data dictionary
|
|
56
|
+
ttl: Optional time-to-live
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if successful
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
async def get_state_field(self, handler_type: str, field: str) -> Any | None:
|
|
65
|
+
"""
|
|
66
|
+
Get specific field from handler state.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
handler_type: Type of handler
|
|
70
|
+
field: Field name
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Field value or None if not found
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
async def set_state_field(self, handler_type: str, field: str, value: Any) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Set specific field in handler state.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
handler_type: Type of handler
|
|
84
|
+
field: Field name
|
|
85
|
+
value: Field value
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if successful
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
async def delete_handler_state(self, handler_type: str) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Delete complete handler state.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
handler_type: Type of handler
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if state was deleted
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
async def get_user_handlers(self) -> list[str]:
|
|
107
|
+
"""
|
|
108
|
+
Get all active handler types for user.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of handler type names
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
async def clear_user_states(self) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Clear all handler states for user.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if states were cleared
|
|
122
|
+
"""
|
|
123
|
+
pass
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tables repository interface.
|
|
3
|
+
|
|
4
|
+
Defines contract for table data operations (generic DataFrames/rows).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from .base_repository import IBaseRepository
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ITablesRepository(IBaseRepository, ABC):
|
|
16
|
+
"""
|
|
17
|
+
Interface for table data repository operations.
|
|
18
|
+
|
|
19
|
+
Provides contract for generic table/DataFrame data management,
|
|
20
|
+
following the existing RedisTable functionality patterns.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
async def get_table_data(
|
|
25
|
+
self,
|
|
26
|
+
table_name: str,
|
|
27
|
+
pkid: str,
|
|
28
|
+
models: type[BaseModel] | None = None,
|
|
29
|
+
) -> dict[str, Any] | None:
|
|
30
|
+
"""
|
|
31
|
+
Get full table row data.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
table_name: Name of the table
|
|
35
|
+
pkid: Primary key identifier
|
|
36
|
+
models: Optional BaseModel class for full object reconstruction
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Table row data dictionary or None if not found
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def set_table_data(
|
|
45
|
+
self,
|
|
46
|
+
table_name: str,
|
|
47
|
+
pkid: str,
|
|
48
|
+
data: dict[str, Any],
|
|
49
|
+
ttl: int | None = None,
|
|
50
|
+
) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Set table row data (upsert behavior).
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
table_name: Name of the table
|
|
56
|
+
pkid: Primary key identifier
|
|
57
|
+
data: Data to store
|
|
58
|
+
ttl: Optional TTL in seconds
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if stored successfully
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def get_table_field(
|
|
67
|
+
self, table_name: str, pkid: str, field: str
|
|
68
|
+
) -> Any | None:
|
|
69
|
+
"""
|
|
70
|
+
Get a specific field from table row data.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
table_name: Name of the table
|
|
74
|
+
pkid: Primary key identifier
|
|
75
|
+
field: Field name to retrieve
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Field value or None if not found
|
|
79
|
+
"""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
async def update_table_field(
|
|
84
|
+
self,
|
|
85
|
+
table_name: str,
|
|
86
|
+
pkid: str,
|
|
87
|
+
field: str,
|
|
88
|
+
value: Any,
|
|
89
|
+
ttl: int | None = None,
|
|
90
|
+
) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Update single field in table row.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
table_name: Name of the table
|
|
96
|
+
pkid: Primary key identifier
|
|
97
|
+
field: Field name to update
|
|
98
|
+
value: New field value
|
|
99
|
+
ttl: Optional TTL in seconds
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
True if updated successfully
|
|
103
|
+
"""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def increment_table_field(
|
|
108
|
+
self,
|
|
109
|
+
table_name: str,
|
|
110
|
+
pkid: str,
|
|
111
|
+
field: str,
|
|
112
|
+
increment: int = 1,
|
|
113
|
+
ttl: int | None = None,
|
|
114
|
+
) -> int | None:
|
|
115
|
+
"""
|
|
116
|
+
Atomically increment integer field.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
table_name: Name of the table
|
|
120
|
+
pkid: Primary key identifier
|
|
121
|
+
field: Field name to increment
|
|
122
|
+
increment: Amount to increment by (default: 1)
|
|
123
|
+
ttl: Optional TTL in seconds
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
New field value after increment, or None if failed
|
|
127
|
+
"""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
@abstractmethod
|
|
131
|
+
async def append_to_table_list_field(
|
|
132
|
+
self,
|
|
133
|
+
table_name: str,
|
|
134
|
+
pkid: str,
|
|
135
|
+
field: str,
|
|
136
|
+
value: Any,
|
|
137
|
+
ttl: int | None = None,
|
|
138
|
+
) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Append value to list field.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
table_name: Name of the table
|
|
144
|
+
pkid: Primary key identifier
|
|
145
|
+
field: Field name containing the list
|
|
146
|
+
value: Value to append
|
|
147
|
+
ttl: Optional TTL in seconds
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
True if appended successfully
|
|
151
|
+
"""
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
@abstractmethod
|
|
155
|
+
async def table_data_exists(self, table_name: str, pkid: str) -> bool:
|
|
156
|
+
"""
|
|
157
|
+
Check if table row exists.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
table_name: Name of the table
|
|
161
|
+
pkid: Primary key identifier
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if table row exists
|
|
165
|
+
"""
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
@abstractmethod
|
|
169
|
+
async def delete_table_data(self, table_name: str, pkid: str) -> int:
|
|
170
|
+
"""
|
|
171
|
+
Delete table row.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
table_name: Name of the table
|
|
175
|
+
pkid: Primary key identifier
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Number of keys deleted
|
|
179
|
+
"""
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
async def find_table_by_field(
|
|
184
|
+
self,
|
|
185
|
+
table_name: str,
|
|
186
|
+
field: str,
|
|
187
|
+
value: Any,
|
|
188
|
+
models: type[BaseModel] | None = None,
|
|
189
|
+
) -> dict[str, Any] | None:
|
|
190
|
+
"""
|
|
191
|
+
Find first row in table where field matches value.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
table_name: Name of the table
|
|
195
|
+
field: Field name to search
|
|
196
|
+
value: Value to match
|
|
197
|
+
models: Optional BaseModel class for full object reconstruction
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
First matching row data or None if not found
|
|
201
|
+
"""
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
@abstractmethod
|
|
205
|
+
async def delete_all_tables_by_pkid(self, pkid: str) -> int:
|
|
206
|
+
"""
|
|
207
|
+
Delete all table rows across all tables with same pkid.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
pkid: Primary key identifier
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Number of keys deleted
|
|
214
|
+
"""
|
|
215
|
+
pass
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User repository interface.
|
|
3
|
+
|
|
4
|
+
Defines contract for user state management in Redis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from .base_repository import IBaseRepository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IUserRepository(IBaseRepository):
|
|
17
|
+
"""
|
|
18
|
+
Interface for user state management.
|
|
19
|
+
|
|
20
|
+
Handles user-specific data with context binding (tenant_id + user_id).
|
|
21
|
+
Uses the 'user' Redis pool (database 1).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def get_user_data(
|
|
26
|
+
self, models: type[BaseModel] | None = None
|
|
27
|
+
) -> dict[str, Any] | None:
|
|
28
|
+
"""
|
|
29
|
+
Get complete user data hash with optional BaseModel deserialization.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
models: Optional BaseModel class for full object reconstruction
|
|
33
|
+
e.g., User (will automatically handle nested UserContact, UserLocation)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
User data dictionary or None if not found
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
async def set_user_data(
|
|
42
|
+
self, data: dict[str, Any], ttl: timedelta | None = None
|
|
43
|
+
) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Set complete user data hash.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
data: User data dictionary
|
|
49
|
+
ttl: Optional time-to-live
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
True if successful
|
|
53
|
+
"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
async def get_user_field(self, field: str) -> Any | None:
|
|
58
|
+
"""
|
|
59
|
+
Get specific field from user data.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
field: Field name
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Field value or None if not found
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
async def set_user_field(self, field: str, value: Any) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Set specific field in user data.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
field: Field name
|
|
76
|
+
value: Field value
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if successful
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
async def delete_user_field(self, field: str) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Delete specific field from user data.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
field: Field name
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if field was deleted
|
|
93
|
+
"""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
async def user_exists(self) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Check if user data exists.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
True if user data exists
|
|
103
|
+
"""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def delete_user(self) -> bool:
|
|
108
|
+
"""
|
|
109
|
+
Delete all user data.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if user was deleted
|
|
113
|
+
"""
|
|
114
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Webhook interface definitions."""
|