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
@@ -1,27 +1,25 @@
1
1
  """
2
2
  Cache utility functions following Single Responsibility Principle.
3
3
 
4
- This module provides cache-related helper functions used across
4
+ This module provides cache-related helper functions used across
5
5
  different score modules for consistent cache management.
6
6
  """
7
7
 
8
8
  import re
9
- from datetime import datetime
10
- from typing import Optional
11
9
 
12
10
 
13
- def generate_cache_key(prefix: str, identifier: str, suffix: Optional[str] = None) -> str:
11
+ def generate_cache_key(prefix: str, identifier: str, suffix: str | None = None) -> str:
14
12
  """
15
13
  Generate a standardized cache key.
16
-
14
+
17
15
  Args:
18
16
  prefix: Cache type prefix (e.g., 'user', 'state', 'msg_history')
19
17
  identifier: Unique identifier (e.g., user_id, session_id)
20
18
  suffix: Optional suffix for additional specificity
21
-
19
+
22
20
  Returns:
23
21
  Properly formatted cache key
24
-
22
+
25
23
  Examples:
26
24
  >>> generate_cache_key('user', '1234567890')
27
25
  'user:1234567890'
@@ -30,139 +28,139 @@ def generate_cache_key(prefix: str, identifier: str, suffix: Optional[str] = Non
30
28
  """
31
29
  if not prefix or not identifier:
32
30
  raise ValueError("Both prefix and identifier are required")
33
-
31
+
34
32
  # Sanitize inputs
35
33
  prefix = sanitize_cache_component(prefix)
36
34
  identifier = sanitize_cache_component(identifier)
37
-
35
+
38
36
  key = f"{prefix}:{identifier}"
39
-
37
+
40
38
  if suffix:
41
39
  suffix = sanitize_cache_component(suffix)
42
40
  key += f":{suffix}"
43
-
41
+
44
42
  return key
45
43
 
46
44
 
47
45
  def sanitize_cache_component(component: str) -> str:
48
46
  """
49
47
  Sanitize a cache key component by removing invalid characters.
50
-
48
+
51
49
  Args:
52
50
  component: Component string to sanitize
53
-
51
+
54
52
  Returns:
55
53
  Sanitized component safe for cache keys
56
54
  """
57
55
  # Remove spaces and special characters, keep alphanumeric, underscore, hyphen
58
- sanitized = re.sub(r'[^\w\-]', '_', str(component).strip())
59
-
56
+ sanitized = re.sub(r"[^\w\-]", "_", str(component).strip())
57
+
60
58
  # Remove multiple consecutive underscores
61
- sanitized = re.sub(r'_+', '_', sanitized)
62
-
59
+ sanitized = re.sub(r"_+", "_", sanitized)
60
+
63
61
  # Remove leading/trailing underscores
64
- return sanitized.strip('_')
62
+ return sanitized.strip("_")
65
63
 
66
64
 
67
65
  def get_cache_ttl(cache_type: str) -> int:
68
66
  """
69
67
  Get standard TTL (time-to-live) values for different cache types.
70
-
68
+
71
69
  Args:
72
70
  cache_type: Type of cache ('user', 'state', 'message', 'statistics')
73
-
71
+
74
72
  Returns:
75
73
  TTL in seconds
76
-
74
+
77
75
  Raises:
78
76
  ValueError: If cache_type is not recognized
79
77
  """
80
78
  ttl_mapping = {
81
- 'user': 86400, # 24 hours - User profiles
82
- 'state': 3600, # 1 hour - Command states
83
- 'message': 604800, # 7 days - Message history
84
- 'statistics': 3600, # 1 hour - Cache statistics
85
- 'temporary': 600, # 10 minutes - Temporary data
79
+ "user": 86400, # 24 hours - User profiles
80
+ "state": 3600, # 1 hour - Command states
81
+ "message": 604800, # 7 days - Message history
82
+ "statistics": 3600, # 1 hour - Cache statistics
83
+ "temporary": 600, # 10 minutes - Temporary data
86
84
  }
87
-
85
+
88
86
  if cache_type not in ttl_mapping:
89
- raise ValueError(f"Unknown cache_type: {cache_type}. Valid types: {list(ttl_mapping.keys())}")
90
-
87
+ raise ValueError(
88
+ f"Unknown cache_type: {cache_type}. Valid types: {list(ttl_mapping.keys())}"
89
+ )
90
+
91
91
  return ttl_mapping[cache_type]
92
92
 
93
93
 
94
94
  def validate_cache_key(key: str) -> bool:
95
95
  """
96
96
  Validate if a cache key follows the expected format.
97
-
97
+
98
98
  Args:
99
99
  key: Cache key to validate
100
-
100
+
101
101
  Returns:
102
102
  True if valid, False otherwise
103
103
  """
104
104
  if not key or not isinstance(key, str):
105
105
  return False
106
-
106
+
107
107
  # Basic format check: should contain at least one colon
108
- if ':' not in key:
108
+ if ":" not in key:
109
109
  return False
110
-
110
+
111
111
  # Check for invalid characters (spaces, special chars except : _ -)
112
- if re.search(r'[^\w:\-]', key):
112
+ if re.search(r"[^\w:\-]", key):
113
113
  return False
114
-
114
+
115
115
  # Should not start or end with colon
116
- if key.startswith(':') or key.endswith(':'):
116
+ if key.startswith(":") or key.endswith(":"):
117
117
  return False
118
-
118
+
119
119
  # Should not have consecutive colons
120
- if '::' in key:
121
- return False
122
-
123
- return True
120
+ return "::" not in key
124
121
 
125
122
 
126
123
  def create_user_profile_key(user_id: str) -> str:
127
124
  """Create standardized user profile cache key."""
128
- return generate_cache_key('user', user_id, 'profile')
125
+ return generate_cache_key("user", user_id, "profile")
129
126
 
130
127
 
131
128
  def create_message_history_key(user_id: str) -> str:
132
- """Create standardized message history cache key."""
133
- return generate_cache_key('msg_history', user_id)
129
+ """Create standardized message history cache key."""
130
+ return generate_cache_key("msg_history", user_id)
134
131
 
135
132
 
136
- def create_state_key(user_id: str, state_type: str = 'wappa') -> str:
133
+ def create_state_key(user_id: str, state_type: str = "wappa") -> str:
137
134
  """Create standardized state cache key."""
138
- return generate_cache_key('state', user_id, state_type)
135
+ return generate_cache_key("state", user_id, state_type)
139
136
 
140
137
 
141
- def create_statistics_key(scope: str = 'global') -> str:
138
+ def create_statistics_key(scope: str = "global") -> str:
142
139
  """Create standardized cache statistics key."""
143
- return generate_cache_key('stats', scope)
140
+ return generate_cache_key("stats", scope)
144
141
 
145
142
 
146
143
  def format_cache_error(operation: str, key: str, error: Exception) -> str:
147
144
  """
148
145
  Format cache operation error messages consistently.
149
-
146
+
150
147
  Args:
151
148
  operation: Cache operation ('get', 'set', 'delete')
152
149
  key: Cache key that failed
153
150
  error: Exception that occurred
154
-
151
+
155
152
  Returns:
156
153
  Formatted error message
157
154
  """
158
155
  return f"Cache {operation} failed for key '{key}': {str(error)}"
159
156
 
160
157
 
161
- def log_cache_operation(logger, operation: str, key: str, success: bool,
162
- duration_ms: Optional[float] = None) -> None:
158
+ def log_cache_operation(
159
+ logger, operation: str, key: str, success: bool, duration_ms: float | None = None
160
+ ) -> None:
163
161
  """
164
162
  Log cache operations consistently across score modules.
165
-
163
+
166
164
  Args:
167
165
  logger: Logger instance
168
166
  operation: Cache operation performed
@@ -172,5 +170,5 @@ def log_cache_operation(logger, operation: str, key: str, success: bool,
172
170
  """
173
171
  status = "✅" if success else "❌"
174
172
  duration_str = f" ({duration_ms:.1f}ms)" if duration_ms else ""
175
-
176
- logger.debug(f"{status} Cache {operation}: {key}{duration_str}")
173
+
174
+ logger.debug(f"{status} Cache {operation}: {key}{duration_str}")
@@ -6,88 +6,87 @@ different score modules for consistent message handling.
6
6
  """
7
7
 
8
8
  from datetime import datetime
9
- from typing import Dict, Optional, Tuple
10
9
 
11
10
  from wappa.webhooks import IncomingMessageWebhook
12
11
 
13
12
 
14
- def extract_user_data(webhook: IncomingMessageWebhook) -> Dict[str, str]:
13
+ def extract_user_data(webhook: IncomingMessageWebhook) -> dict[str, str]:
15
14
  """
16
15
  Extract user data from webhook in a standardized format.
17
-
16
+
18
17
  Args:
19
18
  webhook: Incoming message webhook
20
-
19
+
21
20
  Returns:
22
21
  Dictionary with standardized user data
23
22
  """
24
23
  return {
25
- 'user_id': webhook.user.user_id,
26
- 'user_name': webhook.user.profile_name or "Unknown User",
27
- 'tenant_id': webhook.tenant.get_tenant_key(),
28
- 'message_id': webhook.message.message_id,
24
+ "user_id": webhook.user.user_id,
25
+ "user_name": webhook.user.profile_name or "Unknown User",
26
+ "tenant_id": webhook.tenant.get_tenant_key(),
27
+ "message_id": webhook.message.message_id,
29
28
  }
30
29
 
31
30
 
32
31
  def sanitize_message_text(text: str, max_length: int = 500) -> str:
33
32
  """
34
33
  Sanitize message text for safe storage and processing.
35
-
34
+
36
35
  Args:
37
36
  text: Raw message text
38
37
  max_length: Maximum allowed length
39
-
38
+
40
39
  Returns:
41
40
  Sanitized message text
42
41
  """
43
42
  if not text:
44
43
  return ""
45
-
44
+
46
45
  # Convert to string and strip whitespace
47
46
  sanitized = str(text).strip()
48
-
47
+
49
48
  # Truncate if too long
50
49
  if len(sanitized) > max_length:
51
- sanitized = sanitized[:max_length-3] + "..."
52
-
50
+ sanitized = sanitized[: max_length - 3] + "..."
51
+
53
52
  # Replace problematic characters
54
- sanitized = sanitized.replace('\x00', '').replace('\r\n', '\n')
55
-
53
+ sanitized = sanitized.replace("\x00", "").replace("\r\n", "\n")
54
+
56
55
  return sanitized
57
56
 
58
57
 
59
- def format_timestamp(dt: datetime, format_type: str = 'display') -> str:
58
+ def format_timestamp(dt: datetime, format_type: str = "display") -> str:
60
59
  """
61
60
  Format timestamps consistently across the application.
62
-
61
+
63
62
  Args:
64
63
  dt: Datetime to format
65
64
  format_type: Format type ('display', 'compact', 'iso')
66
-
65
+
67
66
  Returns:
68
67
  Formatted timestamp string
69
68
  """
70
- if format_type == 'display':
69
+ if format_type == "display":
71
70
  return dt.strftime("%Y-%m-%d %H:%M:%S")
72
- elif format_type == 'compact':
71
+ elif format_type == "compact":
73
72
  return dt.strftime("%m/%d %H:%M")
74
- elif format_type == 'iso':
73
+ elif format_type == "iso":
75
74
  return dt.isoformat()
76
75
  else:
77
76
  raise ValueError(f"Unknown format_type: {format_type}")
78
77
 
79
78
 
80
- def extract_command_from_message(text: str) -> Tuple[Optional[str], str]:
79
+ def extract_command_from_message(text: str) -> tuple[str | None, str]:
81
80
  """
82
81
  Extract command and remaining text from message.
83
-
82
+
84
83
  Args:
85
84
  text: Message text
86
-
85
+
87
86
  Returns:
88
87
  Tuple of (command, remaining_text)
89
88
  Command is None if no command found
90
-
89
+
91
90
  Examples:
92
91
  >>> extract_command_from_message("/WAPPA hello")
93
92
  ("/WAPPA", "hello")
@@ -96,80 +95,80 @@ def extract_command_from_message(text: str) -> Tuple[Optional[str], str]:
96
95
  """
97
96
  if not text:
98
97
  return None, ""
99
-
98
+
100
99
  text = text.strip()
101
-
100
+
102
101
  # Check if it starts with a command
103
- if text.startswith('/'):
104
- parts = text.split(' ', 1)
102
+ if text.startswith("/"):
103
+ parts = text.split(" ", 1)
105
104
  command = parts[0].upper()
106
105
  remaining = parts[1] if len(parts) > 1 else ""
107
106
  return command, remaining
108
-
107
+
109
108
  return None, text
110
109
 
111
110
 
112
111
  def is_special_command(text: str) -> bool:
113
112
  """
114
113
  Check if message text contains a special command.
115
-
114
+
116
115
  Args:
117
116
  text: Message text to check
118
-
117
+
119
118
  Returns:
120
119
  True if text contains a recognized command
121
120
  """
122
121
  command, _ = extract_command_from_message(text)
123
-
122
+
124
123
  if not command:
125
124
  return False
126
-
125
+
127
126
  # List of recognized commands
128
- special_commands = ['/WAPPA', '/EXIT', '/HISTORY', '/HELP', '/STATUS']
129
-
127
+ special_commands = ["/WAPPA", "/EXIT", "/HISTORY", "/HELP", "/STATUS"]
128
+
130
129
  return command in special_commands
131
130
 
132
131
 
133
132
  def get_message_type_display_name(message_type: str) -> str:
134
133
  """
135
134
  Get human-readable display name for message types.
136
-
135
+
137
136
  Args:
138
137
  message_type: Technical message type
139
-
138
+
140
139
  Returns:
141
140
  Human-readable display name
142
141
  """
143
142
  type_mapping = {
144
- 'text': 'Text',
145
- 'image': 'Image',
146
- 'audio': 'Audio',
147
- 'video': 'Video',
148
- 'document': 'Document',
149
- 'location': 'Location',
150
- 'contacts': 'Contact',
151
- 'interactive': 'Interactive',
152
- 'button': 'Button Response',
153
- 'list': 'List Response',
154
- 'sticker': 'Sticker',
143
+ "text": "Text",
144
+ "image": "Image",
145
+ "audio": "Audio",
146
+ "video": "Video",
147
+ "document": "Document",
148
+ "location": "Location",
149
+ "contacts": "Contact",
150
+ "interactive": "Interactive",
151
+ "button": "Button Response",
152
+ "list": "List Response",
153
+ "sticker": "Sticker",
155
154
  }
156
-
155
+
157
156
  return type_mapping.get(message_type.lower(), message_type.title())
158
157
 
159
158
 
160
- def create_user_greeting(user_name: Optional[str], message_count: int) -> str:
159
+ def create_user_greeting(user_name: str | None, message_count: int) -> str:
161
160
  """
162
161
  Create personalized user greeting message.
163
-
162
+
164
163
  Args:
165
164
  user_name: User's display name (can be None)
166
165
  message_count: Number of messages from user
167
-
166
+
168
167
  Returns:
169
168
  Personalized greeting text
170
169
  """
171
170
  name = user_name or "there"
172
-
171
+
173
172
  if message_count == 1:
174
173
  return f"👋 Hello {name}! Welcome to the Redis Cache Demo!"
175
174
  elif message_count < 5:
@@ -178,69 +177,75 @@ def create_user_greeting(user_name: Optional[str], message_count: int) -> str:
178
177
  return f"👋 Hello {name}! You're becoming a regular here! ({message_count} messages)"
179
178
 
180
179
 
181
- def format_message_history_display(messages, total_count: int, display_count: int = 20) -> str:
180
+ def format_message_history_display(
181
+ messages, total_count: int, display_count: int = 20
182
+ ) -> str:
182
183
  """
183
184
  Format message history for display to user.
184
-
185
+
185
186
  Args:
186
187
  messages: List of MessageHistory objects
187
188
  total_count: Total number of messages in history
188
189
  display_count: Number of messages being displayed
189
-
190
+
190
191
  Returns:
191
192
  Formatted history text
192
193
  """
193
194
  if not messages:
194
195
  return "📚 Your message history is empty. Start chatting to build your history!"
195
-
196
+
196
197
  history_text = f"📚 Your Message History ({total_count} total messages):\n\n"
197
-
198
+
198
199
  for i, msg_history in enumerate(messages, 1):
199
- timestamp_str = format_timestamp(msg_history.timestamp, 'compact')
200
- msg_type = f"[{get_message_type_display_name(msg_history.message_type)}]" if msg_history.message_type != "text" else ""
201
-
200
+ timestamp_str = format_timestamp(msg_history.timestamp, "compact")
201
+ msg_type = (
202
+ f"[{get_message_type_display_name(msg_history.message_type)}]"
203
+ if msg_history.message_type != "text"
204
+ else ""
205
+ )
206
+
202
207
  # Truncate long messages for display
203
208
  display_message = sanitize_message_text(msg_history.message, 50)
204
-
209
+
205
210
  history_text += f"{i:2d}. {timestamp_str} {msg_type} {display_message}\n"
206
-
211
+
207
212
  if total_count > display_count:
208
213
  history_text += f"\n... showing last {display_count} of {total_count} messages"
209
-
214
+
210
215
  return history_text
211
216
 
212
217
 
213
218
  def create_cache_info_message(user_profile, cache_stats) -> str:
214
219
  """
215
220
  Create informational message about cache status.
216
-
221
+
217
222
  Args:
218
223
  user_profile: User profile data
219
224
  cache_stats: Cache statistics data
220
-
225
+
221
226
  Returns:
222
227
  Formatted cache information message
223
228
  """
224
229
  info_lines = [
225
- f"👤 Your Profile:",
230
+ "👤 Your Profile:",
226
231
  f"• Messages sent: {user_profile.message_count}",
227
232
  f"• First seen: {format_timestamp(user_profile.first_seen, 'compact')}",
228
233
  f"• Last seen: {format_timestamp(user_profile.last_seen, 'compact')}",
229
234
  "",
230
- f"🎯 Special Commands:",
231
- f"• Send '/WAPPA' to enter special state",
232
- f"• Send '/EXIT' to leave special state",
233
- f"• Send '/HISTORY' to see your message history",
235
+ "🎯 Special Commands:",
236
+ "• Send '/WAPPA' to enter special state",
237
+ "• Send '/EXIT' to leave special state",
238
+ "• Send '/HISTORY' to see your message history",
234
239
  "",
235
- f"📊 Cache Statistics:",
240
+ "📊 Cache Statistics:",
236
241
  f"• Total operations: {cache_stats.total_operations}",
237
242
  f"• User cache hit rate: {cache_stats.get_user_hit_rate():.1%}",
238
243
  f"• Active states: {cache_stats.state_cache_active}",
239
244
  "",
240
- f"💾 This demo showcases Redis caching:",
241
- f"• User data cached in user_cache",
242
- f"• Message history stored in table_cache per user",
243
- f"• Commands tracked in state_cache"
245
+ "💾 This demo showcases Redis caching:",
246
+ "• User data cached in user_cache",
247
+ "• Message history stored in table_cache per user",
248
+ "• Commands tracked in state_cache",
244
249
  ]
245
-
246
- return "\n".join(info_lines)
250
+
251
+ return "\n".join(info_lines)
@@ -1,8 +1,9 @@
1
1
  from wappa import Wappa
2
+
2
3
  from .master_event import TranscriptEventHandler
3
4
 
4
5
  app = Wappa()
5
6
  app.set_event_handler(TranscriptEventHandler())
6
7
 
7
8
  if __name__ == "__main__":
8
- app.run()
9
+ app.run()
@@ -1,44 +1,50 @@
1
- import tempfile
2
- import os
1
+ from openai import AsyncOpenAI
3
2
 
4
3
  from wappa import WappaEventHandler
5
- from wappa.webhooks import IncomingMessageWebhook
6
- from wappa.core.logging import get_logger
7
4
  from wappa.core.config import settings
8
-
9
- from openai import AsyncOpenAI
5
+ from wappa.core.logging import get_logger
6
+ from wappa.webhooks import IncomingMessageWebhook
10
7
 
11
8
  from .openai_utils import AudioProcessingService
12
9
 
13
-
14
-
15
10
  logger = get_logger("TranscriptEventHandler")
16
11
 
12
+
17
13
  class TranscriptEventHandler(WappaEventHandler):
18
-
19
14
  async def process_message(self, webhook: IncomingMessageWebhook):
20
-
21
-
22
15
  message_type = webhook.get_message_type_name()
23
16
 
24
- await self.messenger.mark_as_read(webhook.message.message_id, webhook.user.user_id)
25
-
17
+ await self.messenger.mark_as_read(
18
+ webhook.message.message_id, webhook.user.user_id
19
+ )
20
+
26
21
  if message_type == "audio":
27
22
  audio_id = webhook.message.audio.id
28
-
23
+
29
24
  openai_client = AsyncOpenAI(api_key=settings.openai_api_key)
30
25
  audio_service = AudioProcessingService(openai_client)
31
26
 
32
27
  # Option 1: Using tempfile context manager (automatic cleanup)
33
- async with self.messenger.media_handler.download_media_tempfile(audio_id) as audio_download:
28
+ async with self.messenger.media_handler.download_media_tempfile(
29
+ audio_id
30
+ ) as audio_download:
34
31
  if audio_download.success:
35
- transcription = await audio_service.transcribe_audio(audio_download.file_path)
36
- await self.messenger.send_text(f"*Transcript:*\n\n{transcription}", webhook.user.user_id)
37
- logger.info(f"Transcribed audio from temp file: {audio_download.file_path}")
32
+ transcription = await audio_service.transcribe_audio(
33
+ audio_download.file_path
34
+ )
35
+ await self.messenger.send_text(
36
+ f"*Transcript:*\n\n{transcription}", webhook.user.user_id
37
+ )
38
+ logger.info(
39
+ f"Transcribed audio from temp file: {audio_download.file_path}"
40
+ )
38
41
  else:
39
42
  logger.error(f"Failed to download audio: {audio_download.error}")
40
- await self.messenger.send_text("Sorry, I couldn't download the audio file.", webhook.user.user_id)
41
-
43
+ await self.messenger.send_text(
44
+ "Sorry, I couldn't download the audio file.",
45
+ webhook.user.user_id,
46
+ )
47
+
42
48
  # Option 2: Memory-only processing (no files created)
43
49
  # Uncomment to use bytes-based processing instead:
44
50
  # audio_bytes_result = await self.messenger.media_handler.get_media_as_bytes(audio_id)
@@ -49,5 +55,8 @@ class TranscriptEventHandler(WappaEventHandler):
49
55
  # else:
50
56
  # logger.error(f"Failed to download audio: {audio_bytes_result.error}")
51
57
  # await self.messenger.send_text("Sorry, I couldn't download the audio file.", webhook.user.user_id)
52
- else:
53
- await self.messenger.send_text("*Hey Wapp@!*\n\nThis app only receives Audio, send a Voice Note for Transcript", webhook.user.user_id)
58
+ else:
59
+ await self.messenger.send_text(
60
+ "*Hey Wapp@!*\n\nThis app only receives Audio, send a Voice Note for Transcript",
61
+ webhook.user.user_id,
62
+ )
@@ -1,3 +1,3 @@
1
1
  from .audio_processing import AudioProcessingService
2
2
 
3
- all = ["AudioProcessingService"]
3
+ all = ["AudioProcessingService"]