wappa 0.1.8__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 (147) 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/.env.example +33 -0
  9. wappa/cli/examples/init/app/__init__.py +0 -0
  10. wappa/cli/examples/init/app/main.py +9 -0
  11. wappa/cli/examples/init/app/master_event.py +10 -0
  12. wappa/cli/examples/json_cache_example/.env.example +33 -0
  13. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  14. wappa/cli/examples/json_cache_example/app/main.py +247 -0
  15. wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
  16. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  17. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
  18. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  19. wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
  20. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
  21. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
  22. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
  23. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
  24. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  25. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
  26. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
  27. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  28. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  29. wappa/cli/examples/openai_transcript/app/main.py +9 -0
  30. wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
  31. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  32. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
  33. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  34. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  35. wappa/cli/examples/redis_cache_example/app/main.py +246 -0
  36. wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
  37. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
  38. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  39. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
  40. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
  41. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
  42. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
  43. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
  44. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  45. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
  46. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
  47. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  48. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  49. wappa/cli/examples/simple_echo_example/app/main.py +191 -0
  50. wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
  51. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  52. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  53. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  54. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  55. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
  56. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
  57. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
  58. wappa/cli/examples/wappa_full_example/app/main.py +269 -0
  59. wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
  60. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  61. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  62. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  63. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  64. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  65. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  66. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  67. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  68. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  69. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  70. wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
  71. wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
  72. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
  73. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  74. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
  75. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
  76. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
  77. wappa/cli/main.py +14 -5
  78. wappa/core/__init__.py +18 -23
  79. wappa/core/config/settings.py +7 -5
  80. wappa/core/events/default_handlers.py +1 -1
  81. wappa/core/factory/wappa_builder.py +38 -25
  82. wappa/core/plugins/redis_plugin.py +1 -3
  83. wappa/core/plugins/wappa_core_plugin.py +7 -6
  84. wappa/core/types.py +12 -12
  85. wappa/core/wappa_app.py +10 -8
  86. wappa/database/__init__.py +3 -4
  87. wappa/domain/enums/messenger_platform.py +1 -2
  88. wappa/domain/factories/media_factory.py +5 -20
  89. wappa/domain/factories/message_factory.py +5 -20
  90. wappa/domain/factories/messenger_factory.py +2 -4
  91. wappa/domain/interfaces/cache_interface.py +7 -7
  92. wappa/domain/interfaces/media_interface.py +2 -5
  93. wappa/domain/models/media_result.py +1 -3
  94. wappa/domain/models/platforms/platform_config.py +1 -3
  95. wappa/messaging/__init__.py +9 -12
  96. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  97. wappa/models/__init__.py +27 -35
  98. wappa/persistence/__init__.py +12 -15
  99. wappa/persistence/cache_factory.py +0 -1
  100. wappa/persistence/json/__init__.py +1 -1
  101. wappa/persistence/json/cache_adapters.py +37 -25
  102. wappa/persistence/json/handlers/state_handler.py +60 -52
  103. wappa/persistence/json/handlers/table_handler.py +51 -49
  104. wappa/persistence/json/handlers/user_handler.py +71 -55
  105. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  106. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  107. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  108. wappa/persistence/json/json_cache_factory.py +4 -8
  109. wappa/persistence/json/storage_manager.py +66 -79
  110. wappa/persistence/memory/__init__.py +1 -1
  111. wappa/persistence/memory/cache_adapters.py +37 -25
  112. wappa/persistence/memory/handlers/state_handler.py +62 -52
  113. wappa/persistence/memory/handlers/table_handler.py +59 -53
  114. wappa/persistence/memory/handlers/user_handler.py +75 -55
  115. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  116. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  117. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  118. wappa/persistence/memory/memory_cache_factory.py +3 -7
  119. wappa/persistence/memory/storage_manager.py +52 -62
  120. wappa/persistence/redis/cache_adapters.py +27 -21
  121. wappa/persistence/redis/ops.py +11 -11
  122. wappa/persistence/redis/redis_client.py +4 -6
  123. wappa/persistence/redis/redis_manager.py +12 -4
  124. wappa/processors/factory.py +5 -5
  125. wappa/schemas/factory.py +2 -5
  126. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  127. wappa/schemas/whatsapp/validators.py +3 -3
  128. wappa/webhooks/__init__.py +17 -18
  129. wappa/webhooks/factory.py +3 -5
  130. wappa/webhooks/whatsapp/__init__.py +10 -13
  131. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  132. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  133. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  134. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  135. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  136. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  137. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  138. wappa/webhooks/whatsapp/status_models.py +2 -2
  139. wappa/webhooks/whatsapp/validators.py +3 -3
  140. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  141. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
  142. wappa/cli/examples/init/pyproject.toml +0 -7
  143. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  144. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  145. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  146. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  147. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ Provides state cache operations using JSON file storage.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Any, Dict, Optional
8
+ from typing import Any
9
9
 
10
10
  from pydantic import BaseModel
11
11
 
@@ -18,47 +18,51 @@ logger = logging.getLogger("JSONStateHandler")
18
18
  class JSONStateHandler:
19
19
  """
20
20
  JSON-based state cache handler.
21
-
21
+
22
22
  Mirrors RedisStateHandler functionality using file-based JSON storage.
23
23
  Maintains the same API for seamless cache backend switching.
24
24
  """
25
-
25
+
26
26
  def __init__(self, tenant: str, user_id: str):
27
27
  """
28
28
  Initialize JSON state handler.
29
-
29
+
30
30
  Args:
31
31
  tenant: Tenant identifier
32
32
  user_id: User identifier
33
33
  """
34
34
  if not tenant or not user_id:
35
- raise ValueError(f"Missing required parameters: tenant={tenant}, user_id={user_id}")
36
-
35
+ raise ValueError(
36
+ f"Missing required parameters: tenant={tenant}, user_id={user_id}"
37
+ )
38
+
37
39
  self.tenant = tenant
38
40
  self.user_id = user_id
39
41
  self.keys = default_key_factory
40
-
42
+
41
43
  def _key(self, handler_name: str) -> str:
42
44
  """Build handler key using KeyFactory (same as Redis)."""
43
45
  return self.keys.handler(self.tenant, handler_name, self.user_id)
44
-
46
+
45
47
  # ---- Public API matching RedisStateHandler ----
46
48
  async def get(
47
49
  self, handler_name: str, models: type[BaseModel] | None = None
48
50
  ) -> dict[str, Any] | None:
49
51
  """
50
52
  Get handler state data.
51
-
53
+
52
54
  Args:
53
55
  handler_name: Handler name
54
56
  models: Optional BaseModel class for deserialization
55
-
57
+
56
58
  Returns:
57
59
  Handler state data or None if not found
58
60
  """
59
61
  key = self._key(handler_name)
60
- return await storage_manager.get("states", self.tenant, self.user_id, key, models)
61
-
62
+ return await storage_manager.get(
63
+ "states", self.tenant, self.user_id, key, models
64
+ )
65
+
62
66
  async def upsert(
63
67
  self,
64
68
  handler_name: str,
@@ -67,66 +71,68 @@ class JSONStateHandler:
67
71
  ) -> bool:
68
72
  """
69
73
  Create or update handler state data.
70
-
74
+
71
75
  Args:
72
76
  handler_name: Handler name
73
77
  data: State data to store
74
78
  ttl: Time to live in seconds
75
-
79
+
76
80
  Returns:
77
81
  True if successful, False otherwise
78
82
  """
79
83
  key = self._key(handler_name)
80
- return await storage_manager.set("states", self.tenant, self.user_id, key, data, ttl)
81
-
84
+ return await storage_manager.set(
85
+ "states", self.tenant, self.user_id, key, data, ttl
86
+ )
87
+
82
88
  async def delete(self, handler_name: str) -> int:
83
89
  """
84
90
  Delete handler state data.
85
-
91
+
86
92
  Args:
87
93
  handler_name: Handler name
88
-
94
+
89
95
  Returns:
90
96
  1 if deleted, 0 if didn't exist
91
97
  """
92
98
  key = self._key(handler_name)
93
99
  success = await storage_manager.delete("states", self.tenant, self.user_id, key)
94
100
  return 1 if success else 0
95
-
101
+
96
102
  async def exists(self, handler_name: str) -> bool:
97
103
  """
98
104
  Check if handler state exists.
99
-
105
+
100
106
  Args:
101
107
  handler_name: Handler name
102
-
108
+
103
109
  Returns:
104
110
  True if exists, False otherwise
105
111
  """
106
112
  key = self._key(handler_name)
107
113
  return await storage_manager.exists("states", self.tenant, self.user_id, key)
108
-
114
+
109
115
  async def get_field(self, handler_name: str, field: str) -> Any | None:
110
116
  """
111
117
  Get a specific field from handler state.
112
-
118
+
113
119
  Args:
114
120
  handler_name: Handler name
115
121
  field: Field name
116
-
122
+
117
123
  Returns:
118
124
  Field value or None if not found
119
125
  """
120
126
  state_data = await self.get(handler_name)
121
127
  if state_data is None:
122
128
  return None
123
-
129
+
124
130
  if isinstance(state_data, dict):
125
131
  return state_data.get(field)
126
132
  else:
127
133
  # BaseModel instance
128
134
  return getattr(state_data, field, None)
129
-
135
+
130
136
  async def update_field(
131
137
  self,
132
138
  handler_name: str,
@@ -136,26 +142,26 @@ class JSONStateHandler:
136
142
  ) -> bool:
137
143
  """
138
144
  Update a specific field in handler state.
139
-
145
+
140
146
  Args:
141
147
  handler_name: Handler name
142
148
  field: Field name
143
149
  value: New value
144
150
  ttl: Time to live in seconds
145
-
151
+
146
152
  Returns:
147
153
  True if successful, False otherwise
148
154
  """
149
155
  state_data = await self.get(handler_name)
150
156
  if state_data is None:
151
157
  state_data = {}
152
-
158
+
153
159
  if isinstance(state_data, BaseModel):
154
160
  state_data = state_data.model_dump()
155
-
161
+
156
162
  state_data[field] = value
157
163
  return await self.upsert(handler_name, state_data, ttl)
158
-
164
+
159
165
  async def increment_field(
160
166
  self,
161
167
  handler_name: str,
@@ -165,34 +171,36 @@ class JSONStateHandler:
165
171
  ) -> int | None:
166
172
  """
167
173
  Atomically increment an integer field in handler state.
168
-
174
+
169
175
  Args:
170
176
  handler_name: Handler name
171
177
  field: Field name
172
178
  increment: Amount to increment by
173
179
  ttl: Time to live in seconds
174
-
180
+
175
181
  Returns:
176
182
  New value after increment or None on error
177
183
  """
178
184
  state_data = await self.get(handler_name)
179
185
  if state_data is None:
180
186
  state_data = {}
181
-
187
+
182
188
  if isinstance(state_data, BaseModel):
183
189
  state_data = state_data.model_dump()
184
-
190
+
185
191
  current_value = state_data.get(field, 0)
186
- if not isinstance(current_value, (int, float)):
187
- logger.warning(f"Cannot increment non-numeric field '{field}': {current_value}")
192
+ if not isinstance(current_value, int | float):
193
+ logger.warning(
194
+ f"Cannot increment non-numeric field '{field}': {current_value}"
195
+ )
188
196
  return None
189
-
197
+
190
198
  new_value = int(current_value) + increment
191
199
  state_data[field] = new_value
192
-
200
+
193
201
  success = await self.upsert(handler_name, state_data, ttl)
194
202
  return new_value if success else None
195
-
203
+
196
204
  async def append_to_list(
197
205
  self,
198
206
  handler_name: str,
@@ -202,49 +210,49 @@ class JSONStateHandler:
202
210
  ) -> bool:
203
211
  """
204
212
  Append value to a list field in handler state.
205
-
213
+
206
214
  Args:
207
215
  handler_name: Handler name
208
216
  field: Field name containing list
209
217
  value: Value to append
210
218
  ttl: Time to live in seconds
211
-
219
+
212
220
  Returns:
213
221
  True if successful, False otherwise
214
222
  """
215
223
  state_data = await self.get(handler_name)
216
224
  if state_data is None:
217
225
  state_data = {}
218
-
226
+
219
227
  if isinstance(state_data, BaseModel):
220
228
  state_data = state_data.model_dump()
221
-
229
+
222
230
  current_list = state_data.get(field, [])
223
231
  if not isinstance(current_list, list):
224
232
  current_list = []
225
-
233
+
226
234
  current_list.append(value)
227
235
  state_data[field] = current_list
228
-
236
+
229
237
  return await self.upsert(handler_name, state_data, ttl)
230
-
238
+
231
239
  async def get_ttl(self, key: str) -> int:
232
240
  """
233
241
  Get remaining time to live for state cache.
234
-
242
+
235
243
  Returns:
236
244
  Remaining TTL in seconds, -1 if no expiry, -2 if doesn't exist
237
245
  """
238
246
  return await storage_manager.get_ttl("states", self.tenant, self.user_id)
239
-
247
+
240
248
  async def renew_ttl(self, key: str, ttl: int) -> bool:
241
249
  """
242
250
  Renew time to live for state cache.
243
-
251
+
244
252
  Args:
245
253
  ttl: New time to live in seconds
246
-
254
+
247
255
  Returns:
248
256
  True if successful, False otherwise
249
257
  """
250
- return await storage_manager.set_ttl("states", self.tenant, self.user_id, ttl)
258
+ return await storage_manager.set_ttl("states", self.tenant, self.user_id, ttl)
@@ -5,7 +5,7 @@ Provides table cache operations using JSON file storage.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Any, Dict, Optional
8
+ from typing import Any
9
9
 
10
10
  from pydantic import BaseModel
11
11
 
@@ -18,28 +18,28 @@ logger = logging.getLogger("JSONTable")
18
18
  class JSONTable:
19
19
  """
20
20
  JSON-based table cache handler.
21
-
21
+
22
22
  Mirrors RedisTable functionality using file-based JSON storage.
23
23
  Maintains the same API for seamless cache backend switching.
24
24
  """
25
-
25
+
26
26
  def __init__(self, tenant: str):
27
27
  """
28
28
  Initialize JSON table handler.
29
-
29
+
30
30
  Args:
31
31
  tenant: Tenant identifier
32
32
  """
33
33
  if not tenant:
34
34
  raise ValueError(f"Missing required parameter: tenant={tenant}")
35
-
35
+
36
36
  self.tenant = tenant
37
37
  self.keys = default_key_factory
38
-
38
+
39
39
  def _key(self, table_name: str, pkid: str) -> str:
40
40
  """Build table key using KeyFactory (same as Redis)."""
41
41
  return self.keys.table(self.tenant, table_name, pkid)
42
-
42
+
43
43
  # ---- Public API matching RedisTable ----
44
44
  async def get(
45
45
  self,
@@ -49,18 +49,18 @@ class JSONTable:
49
49
  ) -> dict[str, Any] | None:
50
50
  """
51
51
  Get table row data.
52
-
52
+
53
53
  Args:
54
54
  table_name: Table name
55
55
  pkid: Primary key ID
56
56
  models: Optional BaseModel class for deserialization
57
-
57
+
58
58
  Returns:
59
59
  Table row data or None if not found
60
60
  """
61
61
  key = self._key(table_name, pkid)
62
62
  return await storage_manager.get("tables", self.tenant, None, key, models)
63
-
63
+
64
64
  async def upsert(
65
65
  self,
66
66
  table_name: str,
@@ -70,70 +70,70 @@ class JSONTable:
70
70
  ) -> bool:
71
71
  """
72
72
  Create or update table row data.
73
-
73
+
74
74
  Args:
75
75
  table_name: Table name
76
76
  pkid: Primary key ID
77
77
  data: Data to store
78
78
  ttl: Time to live in seconds
79
-
79
+
80
80
  Returns:
81
81
  True if successful, False otherwise
82
82
  """
83
83
  key = self._key(table_name, pkid)
84
84
  return await storage_manager.set("tables", self.tenant, None, key, data, ttl)
85
-
85
+
86
86
  async def delete(self, table_name: str, pkid: str) -> int:
87
87
  """
88
88
  Delete table row data.
89
-
89
+
90
90
  Args:
91
91
  table_name: Table name
92
92
  pkid: Primary key ID
93
-
93
+
94
94
  Returns:
95
95
  1 if deleted, 0 if didn't exist
96
96
  """
97
97
  key = self._key(table_name, pkid)
98
98
  success = await storage_manager.delete("tables", self.tenant, None, key)
99
99
  return 1 if success else 0
100
-
100
+
101
101
  async def exists(self, table_name: str, pkid: str) -> bool:
102
102
  """
103
103
  Check if table row exists.
104
-
104
+
105
105
  Args:
106
106
  table_name: Table name
107
107
  pkid: Primary key ID
108
-
108
+
109
109
  Returns:
110
110
  True if exists, False otherwise
111
111
  """
112
112
  key = self._key(table_name, pkid)
113
113
  return await storage_manager.exists("tables", self.tenant, None, key)
114
-
114
+
115
115
  async def get_field(self, table_name: str, pkid: str, field: str) -> Any | None:
116
116
  """
117
117
  Get a specific field from table row.
118
-
118
+
119
119
  Args:
120
120
  table_name: Table name
121
121
  pkid: Primary key ID
122
122
  field: Field name
123
-
123
+
124
124
  Returns:
125
125
  Field value or None if not found
126
126
  """
127
127
  row_data = await self.get(table_name, pkid)
128
128
  if row_data is None:
129
129
  return None
130
-
130
+
131
131
  if isinstance(row_data, dict):
132
132
  return row_data.get(field)
133
133
  else:
134
134
  # BaseModel instance
135
135
  return getattr(row_data, field, None)
136
-
136
+
137
137
  async def update_field(
138
138
  self,
139
139
  table_name: str,
@@ -144,27 +144,27 @@ class JSONTable:
144
144
  ) -> bool:
145
145
  """
146
146
  Update a specific field in table row.
147
-
147
+
148
148
  Args:
149
149
  table_name: Table name
150
150
  pkid: Primary key ID
151
151
  field: Field name
152
152
  value: New value
153
153
  ttl: Time to live in seconds
154
-
154
+
155
155
  Returns:
156
156
  True if successful, False otherwise
157
157
  """
158
158
  row_data = await self.get(table_name, pkid)
159
159
  if row_data is None:
160
160
  row_data = {}
161
-
161
+
162
162
  if isinstance(row_data, BaseModel):
163
163
  row_data = row_data.model_dump()
164
-
164
+
165
165
  row_data[field] = value
166
166
  return await self.upsert(table_name, pkid, row_data, ttl)
167
-
167
+
168
168
  async def increment_field(
169
169
  self,
170
170
  table_name: str,
@@ -175,35 +175,37 @@ class JSONTable:
175
175
  ) -> int | None:
176
176
  """
177
177
  Atomically increment an integer field in table row.
178
-
178
+
179
179
  Args:
180
180
  table_name: Table name
181
181
  pkid: Primary key ID
182
182
  field: Field name
183
183
  increment: Amount to increment by
184
184
  ttl: Time to live in seconds
185
-
185
+
186
186
  Returns:
187
187
  New value after increment or None on error
188
188
  """
189
189
  row_data = await self.get(table_name, pkid)
190
190
  if row_data is None:
191
191
  row_data = {}
192
-
192
+
193
193
  if isinstance(row_data, BaseModel):
194
194
  row_data = row_data.model_dump()
195
-
195
+
196
196
  current_value = row_data.get(field, 0)
197
- if not isinstance(current_value, (int, float)):
198
- logger.warning(f"Cannot increment non-numeric field '{field}': {current_value}")
197
+ if not isinstance(current_value, int | float):
198
+ logger.warning(
199
+ f"Cannot increment non-numeric field '{field}': {current_value}"
200
+ )
199
201
  return None
200
-
202
+
201
203
  new_value = int(current_value) + increment
202
204
  row_data[field] = new_value
203
-
205
+
204
206
  success = await self.upsert(table_name, pkid, row_data, ttl)
205
207
  return new_value if success else None
206
-
208
+
207
209
  async def append_to_list(
208
210
  self,
209
211
  table_name: str,
@@ -214,50 +216,50 @@ class JSONTable:
214
216
  ) -> bool:
215
217
  """
216
218
  Append value to a list field in table row.
217
-
219
+
218
220
  Args:
219
221
  table_name: Table name
220
222
  pkid: Primary key ID
221
223
  field: Field name containing list
222
224
  value: Value to append
223
225
  ttl: Time to live in seconds
224
-
226
+
225
227
  Returns:
226
228
  True if successful, False otherwise
227
229
  """
228
230
  row_data = await self.get(table_name, pkid)
229
231
  if row_data is None:
230
232
  row_data = {}
231
-
233
+
232
234
  if isinstance(row_data, BaseModel):
233
235
  row_data = row_data.model_dump()
234
-
236
+
235
237
  current_list = row_data.get(field, [])
236
238
  if not isinstance(current_list, list):
237
239
  current_list = []
238
-
240
+
239
241
  current_list.append(value)
240
242
  row_data[field] = current_list
241
-
243
+
242
244
  return await self.upsert(table_name, pkid, row_data, ttl)
243
-
245
+
244
246
  async def get_ttl(self, key: str) -> int:
245
247
  """
246
248
  Get remaining time to live for table cache.
247
-
249
+
248
250
  Returns:
249
251
  Remaining TTL in seconds, -1 if no expiry, -2 if doesn't exist
250
252
  """
251
253
  return await storage_manager.get_ttl("tables", self.tenant, None)
252
-
254
+
253
255
  async def renew_ttl(self, key: str, ttl: int) -> bool:
254
256
  """
255
257
  Renew time to live for table cache.
256
-
258
+
257
259
  Args:
258
260
  ttl: New time to live in seconds
259
-
261
+
260
262
  Returns:
261
263
  True if successful, False otherwise
262
264
  """
263
- return await storage_manager.set_ttl("tables", self.tenant, None, ttl)
265
+ return await storage_manager.set_ttl("tables", self.tenant, None, ttl)