velocity-python 0.0.115__py3-none-any.whl → 0.0.117__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 velocity-python might be problematic. Click here for more details.

@@ -13,102 +13,57 @@ from typing import Any, Dict, Optional
13
13
 
14
14
  from velocity.aws import DEBUG
15
15
  from velocity.aws.handlers import context as VelocityContext
16
+ from velocity.aws.handlers.base_handler import BaseHandler
16
17
  from velocity.misc.format import to_json
17
18
 
18
19
 
19
- class SqsHandler:
20
+ class SqsHandler(BaseHandler):
20
21
  """
21
22
  Base class for handling SQS events in AWS Lambda functions.
22
-
23
+
23
24
  Provides structured processing of SQS records with automatic action routing,
24
25
  logging capabilities, and error handling hooks.
25
26
  """
26
27
 
27
- def __init__(self, aws_event: Dict[str, Any], aws_context: Any,
28
- context_class=VelocityContext.Context):
28
+ def __init__(
29
+ self,
30
+ aws_event: Dict[str, Any],
31
+ aws_context: Any,
32
+ context_class=VelocityContext.Context,
33
+ ):
29
34
  """
30
35
  Initialize the SQS handler.
31
-
36
+
32
37
  Args:
33
38
  aws_event: The AWS Lambda event containing SQS records
34
39
  aws_context: The AWS Lambda context object
35
40
  context_class: The context class to use for processing
36
41
  """
37
- self.aws_event = aws_event
38
- self.aws_context = aws_context
39
- self.serve_action_default = True
40
- self.skip_action = False
41
- self.ContextClass = context_class
42
-
43
- def log(self, tx, message: str, function: Optional[str] = None):
44
- """
45
- Log a message to the system log table.
46
-
47
- Args:
48
- tx: Database transaction object
49
- message: The message to log
50
- function: Optional function name, auto-detected if not provided
51
- """
52
- if not function:
53
- function = self._get_calling_function()
54
-
55
- data = {
56
- "app_name": os.environ.get("ProjectName", "Unknown"),
57
- "referer": "SQS",
58
- "user_agent": "QueueHandler",
59
- "device_type": "Lambda",
60
- "function": function,
61
- "message": message,
62
- "sys_modified_by": "Lambda:BackOfficeQueueHandler",
63
- }
64
- tx.table("sys_log").insert(data)
65
-
66
- def _get_calling_function(self) -> str:
67
- """
68
- Get the name of the calling function by inspecting the call stack.
69
-
70
- Returns:
71
- The name of the calling function or "<Unknown>" if not found
72
- """
73
- skip_functions = {"x", "log", "_transaction", "_get_calling_function"}
74
-
75
- for idx in range(10): # Limit search to prevent infinite loops
76
- try:
77
- frame = sys._getframe(idx)
78
- function_name = frame.f_code.co_name
79
-
80
- if function_name not in skip_functions:
81
- return function_name
82
-
83
- except ValueError:
84
- # No more frames in the stack
85
- break
86
-
87
- return "<Unknown>"
42
+ super().__init__(aws_event, aws_context, context_class)
88
43
 
89
44
  def serve(self, tx):
90
45
  """
91
46
  Process all SQS records in the event.
92
-
47
+
93
48
  Args:
94
49
  tx: Database transaction object
95
50
  """
96
51
  records = self.aws_event.get("Records", [])
97
-
52
+
98
53
  for record in records:
99
54
  self._process_record(tx, record)
100
-
55
+
101
56
  def _process_record(self, tx, record: Dict[str, Any]):
102
57
  """
103
58
  Process a single SQS record.
104
-
59
+
105
60
  Args:
106
61
  tx: Database transaction object
107
62
  record: Individual SQS record to process
108
63
  """
109
64
  attrs = record.get("attributes", {})
110
65
  postdata = {}
111
-
66
+
112
67
  # Parse message body if present
113
68
  body = record.get("body")
114
69
  if body:
@@ -127,93 +82,22 @@ class SqsHandler:
127
82
  response=None,
128
83
  session=None,
129
84
  )
130
-
85
+
86
+ # Use BaseHandler's execute_actions method
131
87
  try:
132
- self._execute_actions(local_context)
88
+ self.execute_actions(tx, local_context, postdata, attrs)
133
89
  except Exception as e:
134
- if hasattr(self, "onError"):
135
- self.onError(
136
- local_context,
137
- exc=e.__class__.__name__,
138
- tb=traceback.format_exc(),
139
- )
140
- else:
141
- # Re-raise if no error handler is defined
142
- raise
143
-
144
- def _execute_actions(self, local_context):
145
- """
146
- Execute the appropriate actions for the given context.
147
-
148
- Args:
149
- local_context: The context object for this record
150
- """
151
- # Execute beforeAction hook if available
152
- if hasattr(self, "beforeAction"):
153
- self.beforeAction(local_context)
154
-
155
- # Determine which actions to execute
156
- actions = self._get_actions_to_execute(local_context)
157
-
158
- # Execute the first matching action
159
- for action in actions:
160
- if self.skip_action:
161
- return
162
-
163
- if hasattr(self, action):
164
- getattr(self, action)(local_context)
165
- break
166
-
167
- # Execute afterAction hook if available
168
- if hasattr(self, "afterAction"):
169
- self.afterAction(local_context)
170
-
171
- def _get_actions_to_execute(self, local_context) -> list:
172
- """
173
- Get the list of actions to execute for the given context.
174
-
175
- Args:
176
- local_context: The context object for this record
177
-
178
- Returns:
179
- List of action method names to try executing
180
- """
181
- actions = []
182
-
183
- # Add specific action if available
184
- action = local_context.action()
185
- if action:
186
- action_method = self._format_action_name(action)
187
- actions.append(action_method)
188
-
189
- # Add default action if enabled
190
- if self.serve_action_default:
191
- actions.append("OnActionDefault")
192
-
193
- return actions
194
-
195
- def _format_action_name(self, action: str) -> str:
196
- """
197
- Format an action string into a method name.
198
-
199
- Args:
200
- action: The raw action string
201
-
202
- Returns:
203
- Formatted method name
204
- """
205
- formatted = action.replace('-', ' ').replace('_', ' ')
206
- return f"on action {formatted}".title().replace(" ", "")
90
+ self.handle_error(local_context, e)
207
91
 
208
92
  def OnActionDefault(self, tx, context):
209
93
  """
210
94
  Default action handler when no specific action is found.
211
-
95
+
212
96
  Args:
213
97
  tx: Database transaction object
214
98
  context: The context object for this record
215
99
  """
216
- action = context.action() if hasattr(context, 'action') else 'unknown'
100
+ action = context.action() if hasattr(context, "action") else "unknown"
217
101
  warning_message = (
218
102
  f"[Warn] Action handler not found. Calling default action "
219
103
  f"`SqsHandler.OnActionDefault` with the following parameters:\n"
velocity/db/__init__.py CHANGED
@@ -15,5 +15,5 @@ from velocity.db.utils import (
15
15
  safe_sort_key_none_first,
16
16
  safe_sort_key_with_default,
17
17
  group_by_fields,
18
- safe_sort_grouped_rows
18
+ safe_sort_grouped_rows,
19
19
  )
@@ -330,30 +330,32 @@ class Engine:
330
330
  Central method to parse driver exceptions and re-raise them as our custom exceptions.
331
331
  """
332
332
  logger = logging.getLogger(__name__)
333
-
333
+
334
334
  # If it's already a velocity exception, just re-raise it
335
335
  if isinstance(exception, exceptions.DbException):
336
336
  raise exception
337
-
337
+
338
338
  # Get error code and message from the SQL driver
339
339
  try:
340
340
  error_code, error_message = self.sql.get_error(exception)
341
341
  except Exception:
342
342
  error_code, error_message = None, str(exception)
343
-
343
+
344
344
  msg = str(exception).strip().lower()
345
-
345
+
346
346
  # Create enhanced error message with SQL query
347
347
  enhanced_message = str(exception)
348
348
  if sql:
349
- enhanced_message += f"\n\nSQL Query:\n{self._format_sql_with_params(sql, parameters)}"
350
-
349
+ enhanced_message += (
350
+ f"\n\nSQL Query:\n{self._format_sql_with_params(sql, parameters)}"
351
+ )
352
+
351
353
  logger.warning(
352
354
  "Database error caught. Attempting to transform: code=%s message=%s",
353
355
  error_code,
354
356
  error_message,
355
357
  )
356
-
358
+
357
359
  # Direct error code mapping
358
360
  if error_code in self.sql.ApplicationErrorCodes:
359
361
  raise exceptions.DbApplicationError(enhanced_message) from exception
@@ -379,7 +381,7 @@ class Engine:
379
381
  raise exceptions.DbLockTimeoutError(enhanced_message) from exception
380
382
  if error_code in self.sql.RetryTransactionCodes:
381
383
  raise exceptions.DbRetryTransaction(enhanced_message) from exception
382
-
384
+
383
385
  # Regex-based fallback patterns
384
386
  if re.search(r"key \(sys_id\)=\(\d+\) already exists.", msg, re.M):
385
387
  raise exceptions.DbDuplicateKeyError(enhanced_message) from exception
@@ -405,7 +407,7 @@ class Engine:
405
407
  raise exceptions.DbConnectionError(enhanced_message) from exception
406
408
  if "no such table:" in msg:
407
409
  raise exceptions.DbTableMissingError(enhanced_message) from exception
408
-
410
+
409
411
  logger.error(
410
412
  "Unhandled/Unknown Error in engine.process_error",
411
413
  exc_info=True,
@@ -416,7 +418,7 @@ class Engine:
416
418
  "sql_params": parameters,
417
419
  },
418
420
  )
419
-
421
+
420
422
  # If we can't classify it, re-raise with enhanced message
421
423
  raise type(exception)(enhanced_message) from exception
422
424
 
@@ -426,10 +428,10 @@ class Engine:
426
428
  """
427
429
  if not sql:
428
430
  return "No SQL provided"
429
-
431
+
430
432
  if not parameters:
431
433
  return sql
432
-
434
+
433
435
  try:
434
436
  # Handle different parameter formats
435
437
  if isinstance(parameters, (list, tuple)):
@@ -437,45 +439,47 @@ class Engine:
437
439
  formatted_params = []
438
440
  for param in parameters:
439
441
  if param is None:
440
- formatted_params.append('NULL')
442
+ formatted_params.append("NULL")
441
443
  elif isinstance(param, str):
442
444
  # Escape single quotes and wrap in quotes
443
445
  escaped = param.replace("'", "''")
444
446
  formatted_params.append(f"'{escaped}'")
445
447
  elif isinstance(param, bool):
446
- formatted_params.append('TRUE' if param else 'FALSE')
448
+ formatted_params.append("TRUE" if param else "FALSE")
447
449
  else:
448
450
  formatted_params.append(str(param))
449
-
451
+
450
452
  # Replace %s placeholders with actual values
451
453
  formatted_sql = sql
452
454
  for param in formatted_params:
453
- formatted_sql = formatted_sql.replace('%s', param, 1)
454
-
455
+ formatted_sql = formatted_sql.replace("%s", param, 1)
456
+
455
457
  return formatted_sql
456
-
458
+
457
459
  elif isinstance(parameters, dict):
458
460
  # Handle named parameters
459
461
  formatted_sql = sql
460
462
  for key, value in parameters.items():
461
463
  if value is None:
462
- replacement = 'NULL'
464
+ replacement = "NULL"
463
465
  elif isinstance(value, str):
464
466
  escaped = value.replace("'", "''")
465
467
  replacement = f"'{escaped}'"
466
468
  elif isinstance(value, bool):
467
- replacement = 'TRUE' if value else 'FALSE'
469
+ replacement = "TRUE" if value else "FALSE"
468
470
  else:
469
471
  replacement = str(value)
470
-
472
+
471
473
  # Replace %(key)s or :key patterns
472
- formatted_sql = formatted_sql.replace(f'%({key})s', replacement)
473
- formatted_sql = formatted_sql.replace(f':{key}', replacement)
474
-
474
+ formatted_sql = formatted_sql.replace(f"%({key})s", replacement)
475
+ formatted_sql = formatted_sql.replace(f":{key}", replacement)
476
+
475
477
  return formatted_sql
476
478
  else:
477
479
  return f"{sql}\n-- Parameters: {parameters}"
478
-
480
+
479
481
  except Exception as e:
480
482
  # If formatting fails, return original SQL with parameters shown separately
481
- return f"{sql}\n-- Parameters (formatting failed): {parameters}\n-- Error: {e}"
483
+ return (
484
+ f"{sql}\n-- Parameters (formatting failed): {parameters}\n-- Error: {e}"
485
+ )
@@ -56,15 +56,18 @@ class DuplicateRowsFoundError(Exception):
56
56
 
57
57
  class DbQueryError(DbException):
58
58
  """Database query error"""
59
+
59
60
  pass
60
61
 
61
62
 
62
63
  class DbTransactionError(DbException):
63
64
  """Database transaction error"""
65
+
64
66
  pass
65
67
 
66
68
 
67
69
  # Add aliases for backward compatibility with engine.py
68
70
  class DatabaseError(DbException):
69
71
  """Generic database error - alias for DbException"""
72
+
70
73
  pass
@@ -7,14 +7,14 @@ class Result:
7
7
  """
8
8
  Wraps a database cursor to provide various convenience transformations
9
9
  (dict, list, tuple, etc.) and helps iterate over query results.
10
-
10
+
11
11
  Features:
12
12
  - Pre-fetches first row for immediate boolean evaluation
13
13
  - Boolean state changes as rows are consumed: bool(result) tells you if MORE rows are available
14
14
  - Supports __bool__, is_empty(), has_results() for checking remaining results
15
15
  - Efficient iteration without unnecessary fetchall() calls
16
16
  - Caches next row to maintain accurate state without redundant database calls
17
-
17
+
18
18
  Boolean Behavior:
19
19
  - Initially: bool(result) = True if query returned any rows
20
20
  - After each row: bool(result) = True if more rows are available to fetch
@@ -29,15 +29,15 @@ class Result:
29
29
  description = getattr(cursor, "description", []) or []
30
30
  self._headers = []
31
31
  for col in description:
32
- if hasattr(col, '__getitem__'): # Tuple-like (col[0])
32
+ if hasattr(col, "__getitem__"): # Tuple-like (col[0])
33
33
  self._headers.append(col[0].lower())
34
- elif hasattr(col, 'name'): # Object with name attribute
34
+ elif hasattr(col, "name"): # Object with name attribute
35
35
  self._headers.append(col.name.lower())
36
36
  else:
37
- self._headers.append(f'column_{len(self._headers)}')
37
+ self._headers.append(f"column_{len(self._headers)}")
38
38
  except (AttributeError, TypeError, IndexError):
39
39
  self._headers = []
40
-
40
+
41
41
  self.__as_strings = False
42
42
  self.__enumerate = False
43
43
  self.__count = -1
@@ -49,7 +49,7 @@ class Result:
49
49
  self._cached_first_row = None
50
50
  self._first_row_fetched = False
51
51
  self._exhausted = False
52
-
52
+
53
53
  # Pre-fetch the first row to enable immediate boolean evaluation
54
54
  self._fetch_first_row()
55
55
 
@@ -60,14 +60,16 @@ class Result:
60
60
  """
61
61
  if self._first_row_fetched or not self._cursor:
62
62
  return
63
-
63
+
64
64
  # Don't try to fetch from INSERT/UPDATE/DELETE operations
65
65
  # These operations don't return rows, only rowcount
66
- if self.__sql and self.__sql.strip().upper().startswith(('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE')):
66
+ if self.__sql and self.__sql.strip().upper().startswith(
67
+ ("INSERT", "UPDATE", "DELETE", "TRUNCATE")
68
+ ):
67
69
  self._exhausted = True
68
70
  self._first_row_fetched = True
69
71
  return
70
-
72
+
71
73
  try:
72
74
  raw_row = self._cursor.fetchone()
73
75
  if raw_row:
@@ -109,7 +111,9 @@ class Result:
109
111
  Return True if there are more rows available to fetch.
110
112
  This is based on whether we have a cached row or the cursor isn't exhausted.
111
113
  """
112
- return self._cached_first_row is not None or (not self._exhausted and self._cursor)
114
+ return self._cached_first_row is not None or (
115
+ not self._exhausted and self._cursor
116
+ )
113
117
 
114
118
  def __next__(self):
115
119
  """
@@ -127,7 +131,7 @@ class Result:
127
131
  if not row:
128
132
  self._exhausted = True
129
133
  raise StopIteration
130
- # Try to pre-fetch the next row to update our state
134
+ # Try to pre-fetch the next row to update our state
131
135
  self._try_cache_next_row()
132
136
  except Exception as e:
133
137
  # Handle cursor errors (e.g., closed cursor)
@@ -154,7 +158,7 @@ class Result:
154
158
  """
155
159
  if not self._cursor or self._cached_first_row is not None:
156
160
  return
157
-
161
+
158
162
  try:
159
163
  next_row = self._cursor.fetchone()
160
164
  if next_row:
@@ -214,18 +218,22 @@ class Result:
214
218
  """
215
219
  if not self.__columns and self._cursor and hasattr(self._cursor, "description"):
216
220
  for column in self._cursor.description:
217
- data = {
218
- "type_name": "unknown" # Default value
219
- }
220
-
221
+ data = {"type_name": "unknown"} # Default value
222
+
221
223
  # Try to get type information (PostgreSQL specific)
222
224
  try:
223
- if hasattr(column, 'type_code') and self.__tx and hasattr(self.__tx, 'pg_types'):
224
- data["type_name"] = self.__tx.pg_types.get(column.type_code, "unknown")
225
+ if (
226
+ hasattr(column, "type_code")
227
+ and self.__tx
228
+ and hasattr(self.__tx, "pg_types")
229
+ ):
230
+ data["type_name"] = self.__tx.pg_types.get(
231
+ column.type_code, "unknown"
232
+ )
225
233
  except (AttributeError, KeyError):
226
234
  # Keep default value
227
235
  pass
228
-
236
+
229
237
  # Get all other column attributes safely
230
238
  for key in dir(column):
231
239
  if not key.startswith("__"):
@@ -234,8 +242,8 @@ class Result:
234
242
  except (AttributeError, TypeError):
235
243
  # Skip attributes that can't be accessed
236
244
  continue
237
-
238
- column_name = getattr(column, 'name', f'column_{len(self.__columns)}')
245
+
246
+ column_name = getattr(column, "name", f"column_{len(self.__columns)}")
239
247
  self.__columns[column_name] = data
240
248
  return self.__columns
241
249
 
velocity/db/core/row.py CHANGED
@@ -97,7 +97,9 @@ class Row:
97
97
  except Exception as e:
98
98
  # Check if the error message indicates a missing column
99
99
  error_msg = str(e).lower()
100
- if 'column' in error_msg and ('does not exist' in error_msg or 'not found' in error_msg):
100
+ if "column" in error_msg and (
101
+ "does not exist" in error_msg or "not found" in error_msg
102
+ ):
101
103
  return failobj
102
104
  # Re-raise other exceptions
103
105
  raise
velocity/db/core/table.py CHANGED
@@ -287,7 +287,8 @@ class Table:
287
287
  if use_where:
288
288
  return Row(self, where, lock=lock)
289
289
  return Row(self, result[0]["sys_id"], lock=lock)
290
- one=find
290
+
291
+ one = find
291
292
 
292
293
  @return_default(None)
293
294
  def first(