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.

Files changed (126) hide show
  1. wappa/__init__.py +4 -5
  2. wappa/api/controllers/webhook_controller.py +5 -2
  3. wappa/api/dependencies/__init__.py +0 -5
  4. wappa/api/middleware/error_handler.py +4 -4
  5. wappa/api/middleware/owner.py +11 -5
  6. wappa/api/routes/webhooks.py +2 -2
  7. wappa/cli/__init__.py +1 -1
  8. wappa/cli/examples/init/app/main.py +2 -1
  9. wappa/cli/examples/init/app/master_event.py +5 -3
  10. wappa/cli/examples/json_cache_example/app/__init__.py +1 -1
  11. wappa/cli/examples/json_cache_example/app/main.py +56 -44
  12. wappa/cli/examples/json_cache_example/app/master_event.py +181 -145
  13. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -1
  14. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +32 -51
  15. wappa/cli/examples/json_cache_example/app/scores/__init__.py +2 -2
  16. wappa/cli/examples/json_cache_example/app/scores/score_base.py +52 -46
  17. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +70 -62
  18. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +41 -44
  19. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +83 -71
  20. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +73 -57
  21. wappa/cli/examples/json_cache_example/app/utils/__init__.py +2 -2
  22. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +54 -56
  23. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +85 -80
  24. wappa/cli/examples/openai_transcript/app/main.py +2 -1
  25. wappa/cli/examples/openai_transcript/app/master_event.py +31 -22
  26. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +1 -1
  27. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +37 -24
  28. wappa/cli/examples/redis_cache_example/app/__init__.py +1 -1
  29. wappa/cli/examples/redis_cache_example/app/main.py +56 -44
  30. wappa/cli/examples/redis_cache_example/app/master_event.py +181 -145
  31. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +31 -50
  32. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +2 -2
  33. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +52 -46
  34. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +70 -62
  35. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +41 -44
  36. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +83 -71
  37. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +73 -57
  38. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +2 -2
  39. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +54 -56
  40. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +85 -80
  41. wappa/cli/examples/simple_echo_example/app/__init__.py +1 -1
  42. wappa/cli/examples/simple_echo_example/app/main.py +41 -33
  43. wappa/cli/examples/simple_echo_example/app/master_event.py +78 -57
  44. wappa/cli/examples/wappa_full_example/app/__init__.py +1 -1
  45. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +1 -1
  46. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +134 -126
  47. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +237 -229
  48. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +170 -148
  49. wappa/cli/examples/wappa_full_example/app/main.py +51 -39
  50. wappa/cli/examples/wappa_full_example/app/master_event.py +179 -120
  51. wappa/cli/examples/wappa_full_example/app/models/__init__.py +1 -1
  52. wappa/cli/examples/wappa_full_example/app/models/state_models.py +113 -104
  53. wappa/cli/examples/wappa_full_example/app/models/user_models.py +92 -76
  54. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +109 -83
  55. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +1 -1
  56. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +132 -113
  57. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +175 -132
  58. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +126 -87
  59. wappa/cli/main.py +9 -4
  60. wappa/core/__init__.py +18 -23
  61. wappa/core/config/settings.py +7 -5
  62. wappa/core/events/default_handlers.py +1 -1
  63. wappa/core/factory/wappa_builder.py +38 -25
  64. wappa/core/plugins/redis_plugin.py +1 -3
  65. wappa/core/plugins/wappa_core_plugin.py +7 -6
  66. wappa/core/types.py +12 -12
  67. wappa/core/wappa_app.py +10 -8
  68. wappa/database/__init__.py +3 -4
  69. wappa/domain/enums/messenger_platform.py +1 -2
  70. wappa/domain/factories/media_factory.py +5 -20
  71. wappa/domain/factories/message_factory.py +5 -20
  72. wappa/domain/factories/messenger_factory.py +2 -4
  73. wappa/domain/interfaces/cache_interface.py +7 -7
  74. wappa/domain/interfaces/media_interface.py +2 -5
  75. wappa/domain/models/media_result.py +1 -3
  76. wappa/domain/models/platforms/platform_config.py +1 -3
  77. wappa/messaging/__init__.py +9 -12
  78. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  79. wappa/models/__init__.py +27 -35
  80. wappa/persistence/__init__.py +12 -15
  81. wappa/persistence/cache_factory.py +0 -1
  82. wappa/persistence/json/__init__.py +1 -1
  83. wappa/persistence/json/cache_adapters.py +37 -25
  84. wappa/persistence/json/handlers/state_handler.py +60 -52
  85. wappa/persistence/json/handlers/table_handler.py +51 -49
  86. wappa/persistence/json/handlers/user_handler.py +71 -55
  87. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  88. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  89. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  90. wappa/persistence/json/json_cache_factory.py +4 -8
  91. wappa/persistence/json/storage_manager.py +66 -79
  92. wappa/persistence/memory/__init__.py +1 -1
  93. wappa/persistence/memory/cache_adapters.py +37 -25
  94. wappa/persistence/memory/handlers/state_handler.py +62 -52
  95. wappa/persistence/memory/handlers/table_handler.py +59 -53
  96. wappa/persistence/memory/handlers/user_handler.py +75 -55
  97. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  98. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  99. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  100. wappa/persistence/memory/memory_cache_factory.py +3 -7
  101. wappa/persistence/memory/storage_manager.py +52 -62
  102. wappa/persistence/redis/cache_adapters.py +27 -21
  103. wappa/persistence/redis/ops.py +11 -11
  104. wappa/persistence/redis/redis_client.py +4 -6
  105. wappa/persistence/redis/redis_manager.py +12 -4
  106. wappa/processors/factory.py +5 -5
  107. wappa/schemas/factory.py +2 -5
  108. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  109. wappa/schemas/whatsapp/validators.py +3 -3
  110. wappa/webhooks/__init__.py +17 -18
  111. wappa/webhooks/factory.py +3 -5
  112. wappa/webhooks/whatsapp/__init__.py +10 -13
  113. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  114. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  115. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  116. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  117. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  118. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  119. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  120. wappa/webhooks/whatsapp/status_models.py +2 -2
  121. wappa/webhooks/whatsapp/validators.py +3 -3
  122. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  123. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/RECORD +126 -126
  124. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  125. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  126. {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('phone_number', mode='before')
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('user_id', 'tenant_id', mode='before')
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
 
@@ -26,10 +26,10 @@ AVAILABLE_SCORES = [
26
26
 
27
27
  __all__ = [
28
28
  "ScoreBase",
29
- "ScoreDependencies",
29
+ "ScoreDependencies",
30
30
  "UserManagementScore",
31
31
  "MessageHistoryScore",
32
32
  "StateCommandsScore",
33
33
  "CacheStatisticsScore",
34
34
  "AVAILABLE_SCORES",
35
- ]
35
+ ]
@@ -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
- 'processed': self._processing_count,
68
- 'errors': self._error_count,
69
- 'success_rate': (
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 else 0.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([self.messenger, self.user_cache, self.table_cache,
109
- self.state_cache, self.logger]):
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) -> Optional[ScoreBase]:
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(f"Error checking if {score.score_name} can handle webhook: {e}")
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}