wappa 0.1.9__py3-none-any.whl → 0.1.10__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 +4 -5
- wappa/api/controllers/webhook_controller.py +5 -2
- wappa/api/dependencies/__init__.py +0 -5
- wappa/api/middleware/error_handler.py +4 -4
- wappa/api/middleware/owner.py +11 -5
- wappa/api/routes/webhooks.py +2 -2
- wappa/cli/__init__.py +1 -1
- wappa/cli/examples/init/app/main.py +2 -1
- wappa/cli/examples/init/app/master_event.py +5 -3
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -1
- wappa/cli/examples/json_cache_example/app/main.py +56 -44
- wappa/cli/examples/json_cache_example/app/master_event.py +181 -145
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -1
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +32 -51
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +2 -2
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +52 -46
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +70 -62
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +41 -44
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +83 -71
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +73 -57
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +2 -2
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +54 -56
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +85 -80
- wappa/cli/examples/openai_transcript/app/main.py +2 -1
- wappa/cli/examples/openai_transcript/app/master_event.py +31 -22
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +1 -1
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +37 -24
- wappa/cli/examples/redis_cache_example/app/__init__.py +1 -1
- wappa/cli/examples/redis_cache_example/app/main.py +56 -44
- wappa/cli/examples/redis_cache_example/app/master_event.py +181 -145
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +31 -50
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +2 -2
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +52 -46
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +70 -62
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +41 -44
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +83 -71
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +73 -57
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +2 -2
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +54 -56
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +85 -80
- wappa/cli/examples/simple_echo_example/app/__init__.py +1 -1
- wappa/cli/examples/simple_echo_example/app/main.py +41 -33
- wappa/cli/examples/simple_echo_example/app/master_event.py +78 -57
- wappa/cli/examples/wappa_full_example/app/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +134 -126
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +237 -229
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +170 -148
- wappa/cli/examples/wappa_full_example/app/main.py +51 -39
- wappa/cli/examples/wappa_full_example/app/master_event.py +179 -120
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +113 -104
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +92 -76
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +109 -83
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +132 -113
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +175 -132
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +126 -87
- wappa/cli/main.py +9 -4
- wappa/core/__init__.py +18 -23
- wappa/core/config/settings.py +7 -5
- wappa/core/events/default_handlers.py +1 -1
- wappa/core/factory/wappa_builder.py +38 -25
- wappa/core/plugins/redis_plugin.py +1 -3
- wappa/core/plugins/wappa_core_plugin.py +7 -6
- wappa/core/types.py +12 -12
- wappa/core/wappa_app.py +10 -8
- wappa/database/__init__.py +3 -4
- wappa/domain/enums/messenger_platform.py +1 -2
- wappa/domain/factories/media_factory.py +5 -20
- wappa/domain/factories/message_factory.py +5 -20
- wappa/domain/factories/messenger_factory.py +2 -4
- wappa/domain/interfaces/cache_interface.py +7 -7
- wappa/domain/interfaces/media_interface.py +2 -5
- wappa/domain/models/media_result.py +1 -3
- wappa/domain/models/platforms/platform_config.py +1 -3
- wappa/messaging/__init__.py +9 -12
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
- wappa/models/__init__.py +27 -35
- wappa/persistence/__init__.py +12 -15
- wappa/persistence/cache_factory.py +0 -1
- wappa/persistence/json/__init__.py +1 -1
- wappa/persistence/json/cache_adapters.py +37 -25
- wappa/persistence/json/handlers/state_handler.py +60 -52
- wappa/persistence/json/handlers/table_handler.py +51 -49
- wappa/persistence/json/handlers/user_handler.py +71 -55
- wappa/persistence/json/handlers/utils/file_manager.py +42 -39
- wappa/persistence/json/handlers/utils/key_factory.py +1 -1
- wappa/persistence/json/handlers/utils/serialization.py +13 -11
- wappa/persistence/json/json_cache_factory.py +4 -8
- wappa/persistence/json/storage_manager.py +66 -79
- wappa/persistence/memory/__init__.py +1 -1
- wappa/persistence/memory/cache_adapters.py +37 -25
- wappa/persistence/memory/handlers/state_handler.py +62 -52
- wappa/persistence/memory/handlers/table_handler.py +59 -53
- wappa/persistence/memory/handlers/user_handler.py +75 -55
- wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
- wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
- wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
- wappa/persistence/memory/memory_cache_factory.py +3 -7
- wappa/persistence/memory/storage_manager.py +52 -62
- wappa/persistence/redis/cache_adapters.py +27 -21
- wappa/persistence/redis/ops.py +11 -11
- wappa/persistence/redis/redis_client.py +4 -6
- wappa/persistence/redis/redis_manager.py +12 -4
- wappa/processors/factory.py +5 -5
- wappa/schemas/factory.py +2 -5
- wappa/schemas/whatsapp/message_types/errors.py +3 -12
- wappa/schemas/whatsapp/validators.py +3 -3
- wappa/webhooks/__init__.py +17 -18
- wappa/webhooks/factory.py +3 -5
- wappa/webhooks/whatsapp/__init__.py +10 -13
- wappa/webhooks/whatsapp/message_types/audio.py +0 -4
- wappa/webhooks/whatsapp/message_types/document.py +1 -9
- wappa/webhooks/whatsapp/message_types/errors.py +3 -12
- wappa/webhooks/whatsapp/message_types/location.py +1 -21
- wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
- wappa/webhooks/whatsapp/message_types/text.py +0 -6
- wappa/webhooks/whatsapp/message_types/video.py +1 -20
- wappa/webhooks/whatsapp/status_models.py +2 -2
- wappa/webhooks/whatsapp/validators.py +3 -3
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/RECORD +126 -126
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,32 +13,29 @@ from pydantic import BaseModel, Field, field_validator
|
|
|
13
13
|
class MessageHistory(BaseModel):
|
|
14
14
|
"""
|
|
15
15
|
Individual message entry for message history storage.
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
Stores a single message with its timestamp.
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
message: str = Field(
|
|
21
|
-
...,
|
|
22
|
-
description="The message content or type description",
|
|
23
|
-
max_length=500
|
|
21
|
+
..., description="The message content or type description", max_length=500
|
|
24
22
|
)
|
|
25
|
-
|
|
23
|
+
|
|
26
24
|
timestamp: datetime = Field(
|
|
27
|
-
default_factory=datetime.utcnow,
|
|
28
|
-
description="When the message was sent"
|
|
25
|
+
default_factory=datetime.utcnow, description="When the message was sent"
|
|
29
26
|
)
|
|
30
|
-
|
|
27
|
+
|
|
31
28
|
message_type: str = Field(
|
|
32
29
|
default="text",
|
|
33
30
|
description="Type of message (text, image, audio, etc.)",
|
|
34
|
-
max_length=20
|
|
31
|
+
max_length=20,
|
|
35
32
|
)
|
|
36
33
|
|
|
37
34
|
|
|
38
35
|
class User(BaseModel):
|
|
39
36
|
"""
|
|
40
37
|
User profile model for user_cache demonstration.
|
|
41
|
-
|
|
38
|
+
|
|
42
39
|
Stores user information extracted from WhatsApp webhook data.
|
|
43
40
|
"""
|
|
44
41
|
|
|
@@ -46,37 +43,30 @@ class User(BaseModel):
|
|
|
46
43
|
...,
|
|
47
44
|
description="User's phone number (WhatsApp ID)",
|
|
48
45
|
min_length=10,
|
|
49
|
-
max_length=20
|
|
46
|
+
max_length=20,
|
|
50
47
|
)
|
|
51
48
|
|
|
52
49
|
user_name: str | None = Field(
|
|
53
|
-
None,
|
|
54
|
-
description="User's display name from WhatsApp profile",
|
|
55
|
-
max_length=100
|
|
50
|
+
None, description="User's display name from WhatsApp profile", max_length=100
|
|
56
51
|
)
|
|
57
52
|
|
|
58
53
|
first_seen: datetime = Field(
|
|
59
|
-
default_factory=datetime.utcnow,
|
|
60
|
-
description="When the user was first seen"
|
|
54
|
+
default_factory=datetime.utcnow, description="When the user was first seen"
|
|
61
55
|
)
|
|
62
56
|
|
|
63
57
|
last_seen: datetime = Field(
|
|
64
|
-
default_factory=datetime.utcnow,
|
|
65
|
-
description="When the user was last seen"
|
|
58
|
+
default_factory=datetime.utcnow, description="When the user was last seen"
|
|
66
59
|
)
|
|
67
60
|
|
|
68
61
|
message_count: int = Field(
|
|
69
|
-
default=0,
|
|
70
|
-
description="Total number of messages received from this user",
|
|
71
|
-
ge=0
|
|
62
|
+
default=0, description="Total number of messages received from this user", ge=0
|
|
72
63
|
)
|
|
73
64
|
|
|
74
65
|
is_active: bool = Field(
|
|
75
|
-
default=True,
|
|
76
|
-
description="Whether the user is currently active"
|
|
66
|
+
default=True, description="Whether the user is currently active"
|
|
77
67
|
)
|
|
78
68
|
|
|
79
|
-
@field_validator(
|
|
69
|
+
@field_validator("phone_number", mode="before")
|
|
80
70
|
@classmethod
|
|
81
71
|
def validate_phone_number(cls, v) -> str:
|
|
82
72
|
"""Convert phone number to string if it's an integer."""
|
|
@@ -93,7 +83,7 @@ class User(BaseModel):
|
|
|
93
83
|
class MessageLog(BaseModel):
|
|
94
84
|
"""
|
|
95
85
|
Message log model for table_cache demonstration.
|
|
96
|
-
|
|
86
|
+
|
|
97
87
|
Stores message history for a user with waid as primary key.
|
|
98
88
|
Contains a list of all messages sent by the user.
|
|
99
89
|
"""
|
|
@@ -102,38 +92,35 @@ class MessageLog(BaseModel):
|
|
|
102
92
|
...,
|
|
103
93
|
description="User's phone number/ID (primary key)",
|
|
104
94
|
min_length=10,
|
|
105
|
-
max_length=20
|
|
95
|
+
max_length=20,
|
|
106
96
|
)
|
|
107
97
|
|
|
108
98
|
text_message: list[MessageHistory] = Field(
|
|
109
|
-
default_factory=list,
|
|
110
|
-
description="List of all messages sent by this user"
|
|
99
|
+
default_factory=list, description="List of all messages sent by this user"
|
|
111
100
|
)
|
|
112
101
|
|
|
113
102
|
tenant_id: str | None = Field(
|
|
114
103
|
None,
|
|
115
104
|
description="Tenant/business ID that received the messages",
|
|
116
|
-
max_length=100
|
|
105
|
+
max_length=100,
|
|
117
106
|
)
|
|
118
107
|
|
|
119
108
|
def get_log_key(self) -> str:
|
|
120
109
|
"""Generate the primary key for this user's message log."""
|
|
121
110
|
return f"msg_history:{self.user_id}"
|
|
122
|
-
|
|
111
|
+
|
|
123
112
|
def add_message(self, message: str, message_type: str = "text") -> None:
|
|
124
113
|
"""Add a new message to the user's history."""
|
|
125
114
|
new_message = MessageHistory(
|
|
126
|
-
message=message,
|
|
127
|
-
message_type=message_type,
|
|
128
|
-
timestamp=datetime.utcnow()
|
|
115
|
+
message=message, message_type=message_type, timestamp=datetime.utcnow()
|
|
129
116
|
)
|
|
130
117
|
self.text_message.append(new_message)
|
|
131
|
-
|
|
118
|
+
|
|
132
119
|
def get_recent_messages(self, count: int = 10) -> list[MessageHistory]:
|
|
133
120
|
"""Get the most recent messages from the user's history."""
|
|
134
121
|
return self.text_message[-count:] if self.text_message else []
|
|
135
|
-
|
|
136
|
-
@field_validator(
|
|
122
|
+
|
|
123
|
+
@field_validator("user_id", "tenant_id", mode="before")
|
|
137
124
|
@classmethod
|
|
138
125
|
def validate_string_ids(cls, v) -> str:
|
|
139
126
|
"""Convert ID fields to string if they're integers."""
|
|
@@ -149,30 +136,24 @@ class MessageLog(BaseModel):
|
|
|
149
136
|
class StateHandler(BaseModel):
|
|
150
137
|
"""
|
|
151
138
|
State handler model for state_cache demonstration.
|
|
152
|
-
|
|
139
|
+
|
|
153
140
|
Manages user state for the /WAPPA command flow.
|
|
154
141
|
"""
|
|
155
142
|
|
|
156
143
|
is_wappa: bool = Field(
|
|
157
|
-
default=False,
|
|
158
|
-
description="Whether the user is in 'WAPPA' state"
|
|
144
|
+
default=False, description="Whether the user is in 'WAPPA' state"
|
|
159
145
|
)
|
|
160
146
|
|
|
161
147
|
activated_at: datetime | None = Field(
|
|
162
|
-
None,
|
|
163
|
-
description="When the WAPPA state was activated"
|
|
148
|
+
None, description="When the WAPPA state was activated"
|
|
164
149
|
)
|
|
165
150
|
|
|
166
151
|
command_count: int = Field(
|
|
167
|
-
default=0,
|
|
168
|
-
description="Number of commands processed while in WAPPA state",
|
|
169
|
-
ge=0
|
|
152
|
+
default=0, description="Number of commands processed while in WAPPA state", ge=0
|
|
170
153
|
)
|
|
171
154
|
|
|
172
155
|
last_command: str | None = Field(
|
|
173
|
-
None,
|
|
174
|
-
description="Last command processed in WAPPA state",
|
|
175
|
-
max_length=100
|
|
156
|
+
None, description="Last command processed in WAPPA state", max_length=100
|
|
176
157
|
)
|
|
177
158
|
|
|
178
159
|
def activate_wappa(self) -> None:
|
|
@@ -202,7 +183,7 @@ class StateHandler(BaseModel):
|
|
|
202
183
|
class CacheStats(BaseModel):
|
|
203
184
|
"""
|
|
204
185
|
Cache statistics model for monitoring cache usage.
|
|
205
|
-
|
|
186
|
+
|
|
206
187
|
Used to track cache performance and usage statistics.
|
|
207
188
|
"""
|
|
208
189
|
|
|
@@ -220,7 +201,7 @@ class CacheStats(BaseModel):
|
|
|
220
201
|
cache_type: str = Field(default="Unknown")
|
|
221
202
|
connection_status: str = Field(default="Unknown")
|
|
222
203
|
is_healthy: bool = Field(default=True)
|
|
223
|
-
|
|
204
|
+
|
|
224
205
|
# Timing
|
|
225
206
|
last_updated: datetime = Field(default_factory=datetime.utcnow)
|
|
226
207
|
|
|
@@ -8,7 +8,6 @@ ensuring consistent behavior across different business logic handlers.
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from logging import Logger
|
|
11
|
-
from typing import Optional
|
|
12
11
|
|
|
13
12
|
from wappa.domain.interfaces.cache_interface import ICache
|
|
14
13
|
from wappa.messaging.whatsapp.messenger.whatsapp_messenger import WhatsAppMessenger
|
|
@@ -19,13 +18,14 @@ from wappa.webhooks import IncomingMessageWebhook
|
|
|
19
18
|
class ScoreDependencies:
|
|
20
19
|
"""
|
|
21
20
|
Dependencies required by score modules.
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
This follows Dependency Inversion Principle by providing
|
|
24
23
|
abstractions that score modules depend on.
|
|
25
24
|
"""
|
|
25
|
+
|
|
26
26
|
messenger: WhatsAppMessenger
|
|
27
27
|
user_cache: ICache
|
|
28
|
-
table_cache: ICache
|
|
28
|
+
table_cache: ICache
|
|
29
29
|
state_cache: ICache
|
|
30
30
|
logger: Logger
|
|
31
31
|
|
|
@@ -33,15 +33,15 @@ class ScoreDependencies:
|
|
|
33
33
|
class ScoreBase(ABC):
|
|
34
34
|
"""
|
|
35
35
|
Base class for all score modules.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
Implements Interface Segregation Principle by providing
|
|
38
38
|
only the methods that score modules actually need.
|
|
39
39
|
"""
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def __init__(self, dependencies: ScoreDependencies):
|
|
42
42
|
"""
|
|
43
43
|
Initialize score with injected dependencies.
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
Args:
|
|
46
46
|
dependencies: Required dependencies for the score
|
|
47
47
|
"""
|
|
@@ -50,87 +50,94 @@ class ScoreBase(ABC):
|
|
|
50
50
|
self.table_cache = dependencies.table_cache
|
|
51
51
|
self.state_cache = dependencies.state_cache
|
|
52
52
|
self.logger = dependencies.logger
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
# Track processing statistics
|
|
55
55
|
self._processing_count = 0
|
|
56
56
|
self._error_count = 0
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
@property
|
|
59
59
|
def score_name(self) -> str:
|
|
60
60
|
"""Return the name of this score module."""
|
|
61
61
|
return self.__class__.__name__
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
@property
|
|
64
64
|
def processing_stats(self) -> dict:
|
|
65
65
|
"""Return processing statistics for this score."""
|
|
66
66
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
"processed": self._processing_count,
|
|
68
|
+
"errors": self._error_count,
|
|
69
|
+
"success_rate": (
|
|
70
70
|
(self._processing_count - self._error_count) / self._processing_count
|
|
71
|
-
if self._processing_count > 0
|
|
72
|
-
|
|
71
|
+
if self._processing_count > 0
|
|
72
|
+
else 0.0
|
|
73
|
+
),
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
|
|
75
76
|
@abstractmethod
|
|
76
77
|
async def can_handle(self, webhook: IncomingMessageWebhook) -> bool:
|
|
77
78
|
"""
|
|
78
79
|
Determine if this score can handle the given webhook.
|
|
79
|
-
|
|
80
|
+
|
|
80
81
|
Args:
|
|
81
82
|
webhook: Incoming message webhook to evaluate
|
|
82
|
-
|
|
83
|
+
|
|
83
84
|
Returns:
|
|
84
85
|
True if this score should process the webhook
|
|
85
86
|
"""
|
|
86
87
|
pass
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
@abstractmethod
|
|
89
90
|
async def process(self, webhook: IncomingMessageWebhook) -> bool:
|
|
90
91
|
"""
|
|
91
92
|
Process the webhook with this score's business logic.
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
Args:
|
|
94
95
|
webhook: Incoming message webhook to process
|
|
95
|
-
|
|
96
|
+
|
|
96
97
|
Returns:
|
|
97
98
|
True if processing was successful and complete
|
|
98
99
|
"""
|
|
99
100
|
pass
|
|
100
|
-
|
|
101
|
+
|
|
101
102
|
async def validate_dependencies(self) -> bool:
|
|
102
103
|
"""
|
|
103
104
|
Validate that all required dependencies are available.
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
Returns:
|
|
106
107
|
True if all dependencies are valid
|
|
107
108
|
"""
|
|
108
|
-
if not all(
|
|
109
|
-
|
|
109
|
+
if not all(
|
|
110
|
+
[
|
|
111
|
+
self.messenger,
|
|
112
|
+
self.user_cache,
|
|
113
|
+
self.table_cache,
|
|
114
|
+
self.state_cache,
|
|
115
|
+
self.logger,
|
|
116
|
+
]
|
|
117
|
+
):
|
|
110
118
|
self.logger.error(f"{self.score_name}: Missing required dependencies")
|
|
111
119
|
return False
|
|
112
120
|
return True
|
|
113
|
-
|
|
121
|
+
|
|
114
122
|
def _record_processing(self, success: bool = True) -> None:
|
|
115
123
|
"""Record processing statistics."""
|
|
116
124
|
self._processing_count += 1
|
|
117
125
|
if not success:
|
|
118
126
|
self._error_count += 1
|
|
119
|
-
|
|
127
|
+
|
|
120
128
|
async def _handle_error(self, error: Exception, context: str) -> None:
|
|
121
129
|
"""
|
|
122
130
|
Handle errors consistently across score modules.
|
|
123
|
-
|
|
131
|
+
|
|
124
132
|
Args:
|
|
125
133
|
error: Exception that occurred
|
|
126
134
|
context: Context where error occurred
|
|
127
135
|
"""
|
|
128
136
|
self._record_processing(success=False)
|
|
129
137
|
self.logger.error(
|
|
130
|
-
f"{self.score_name} error in {context}: {str(error)}",
|
|
131
|
-
exc_info=True
|
|
138
|
+
f"{self.score_name} error in {context}: {str(error)}", exc_info=True
|
|
132
139
|
)
|
|
133
|
-
|
|
140
|
+
|
|
134
141
|
def __str__(self) -> str:
|
|
135
142
|
"""String representation of the score."""
|
|
136
143
|
return f"{self.score_name}(processed={self._processing_count}, errors={self._error_count})"
|
|
@@ -139,32 +146,32 @@ class ScoreBase(ABC):
|
|
|
139
146
|
class ScoreRegistry:
|
|
140
147
|
"""
|
|
141
148
|
Registry for managing score modules.
|
|
142
|
-
|
|
149
|
+
|
|
143
150
|
Implements Open/Closed Principle by allowing new scores
|
|
144
151
|
to be registered without modifying existing code.
|
|
145
152
|
"""
|
|
146
|
-
|
|
153
|
+
|
|
147
154
|
def __init__(self):
|
|
148
155
|
self._scores: list[ScoreBase] = []
|
|
149
|
-
|
|
156
|
+
|
|
150
157
|
def register_score(self, score: ScoreBase) -> None:
|
|
151
158
|
"""Register a score module."""
|
|
152
159
|
if not isinstance(score, ScoreBase):
|
|
153
160
|
raise ValueError("Score must inherit from ScoreBase")
|
|
154
|
-
|
|
161
|
+
|
|
155
162
|
self._scores.append(score)
|
|
156
|
-
|
|
163
|
+
|
|
157
164
|
def get_scores(self) -> list[ScoreBase]:
|
|
158
165
|
"""Get all registered scores."""
|
|
159
166
|
return self._scores.copy()
|
|
160
|
-
|
|
161
|
-
async def find_handler(self, webhook: IncomingMessageWebhook) ->
|
|
167
|
+
|
|
168
|
+
async def find_handler(self, webhook: IncomingMessageWebhook) -> ScoreBase | None:
|
|
162
169
|
"""
|
|
163
170
|
Find the first score that can handle the webhook.
|
|
164
|
-
|
|
171
|
+
|
|
165
172
|
Args:
|
|
166
173
|
webhook: Webhook to find handler for
|
|
167
|
-
|
|
174
|
+
|
|
168
175
|
Returns:
|
|
169
176
|
Score that can handle the webhook, or None
|
|
170
177
|
"""
|
|
@@ -174,13 +181,12 @@ class ScoreRegistry:
|
|
|
174
181
|
return score
|
|
175
182
|
except Exception as e:
|
|
176
183
|
# Log error but continue to next score
|
|
177
|
-
score.logger.error(
|
|
178
|
-
|
|
184
|
+
score.logger.error(
|
|
185
|
+
f"Error checking if {score.score_name} can handle webhook: {e}"
|
|
186
|
+
)
|
|
187
|
+
|
|
179
188
|
return None
|
|
180
|
-
|
|
189
|
+
|
|
181
190
|
def get_score_stats(self) -> dict:
|
|
182
191
|
"""Get statistics for all registered scores."""
|
|
183
|
-
return {
|
|
184
|
-
score.score_name: score.processing_stats
|
|
185
|
-
for score in self._scores
|
|
186
|
-
}
|
|
192
|
+
return {score.score_name: score.processing_stats for score in self._scores}
|