velocity-python 0.0.98__py3-none-any.whl → 0.0.101__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.

@@ -325,81 +325,192 @@ class Engine:
325
325
  result = tx.execute(sql, vals)
326
326
  return [f"{x[0]}.{x[1]}" for x in result.as_tuple()]
327
327
 
328
- def process_error(self, sql_stmt=None, sql_params=None):
329
- """
330
- Central method to parse driver exceptions and re-raise them as our custom exceptions.
331
- """
332
- e = sys.exc_info()[1]
333
- msg = str(e).strip().lower()
334
-
335
- if isinstance(e, exceptions.DbException):
336
- raise
337
-
338
- error_code, error_mesg = self.sql.get_error(e)
339
-
328
+ def process_error(self, exception, sql=None, parameters=None):
329
+ """
330
+ Process database errors and raise appropriate velocity exceptions.
331
+ Enhanced for robustness with exception chaining and comprehensive error handling.
332
+
333
+ Args:
334
+ exception: The original exception from the database driver
335
+ sql: The SQL statement that caused the error (optional)
336
+ parameters: The parameters passed to the SQL statement (optional)
337
+
338
+ Returns:
339
+ The appropriate velocity exception to raise
340
+ """
341
+ logger = logging.getLogger(__name__)
342
+
343
+ # Enhanced logging with context
344
+ extra_data = {
345
+ 'exception_type': type(exception).__name__,
346
+ 'sql': sql,
347
+ 'parameters': parameters
348
+ }
349
+
350
+ logger.error(
351
+ f"Database error caught. Attempting to transform: "
352
+ f"type={type(exception).__name__}, sql={sql[:100] if sql else 'None'}...",
353
+ extra=extra_data
354
+ )
355
+
356
+ # Safely get error code and message with fallbacks
357
+ try:
358
+ error_code = getattr(exception, 'pgcode', None) or self.get_error(exception)
359
+ except Exception as e:
360
+ logger.warning(f"Failed to extract error code: {e}")
361
+ error_code = None
362
+
363
+ try:
364
+ error_message = str(exception)
365
+ except Exception as e:
366
+ logger.warning(f"Failed to convert exception to string: {e}")
367
+ error_message = f"<Error converting exception: {type(exception).__name__}>"
368
+
369
+ # Primary error classification by error code
370
+ if error_code and hasattr(self, 'error_codes'):
371
+ for error_class, codes in self.error_codes.items():
372
+ if error_code in codes:
373
+ logger.info(f"Classified error by code: {error_code} -> {error_class}")
374
+ try:
375
+ return self._create_exception_with_chaining(
376
+ error_class, error_message, exception, sql, parameters
377
+ )
378
+ except Exception as creation_error:
379
+ logger.error(f"Failed to create {error_class} exception: {creation_error}")
380
+ # Fall through to regex classification
381
+ break
382
+
383
+ # Secondary error classification by message patterns (regex fallback)
384
+ error_message_lower = error_message.lower()
385
+
386
+ # Enhanced connection error patterns
387
+ connection_patterns = [
388
+ r'connection.*refused|could not connect',
389
+ r'network.*unreachable|network.*down',
390
+ r'broken pipe|connection.*broken',
391
+ r'timeout.*connection|connection.*timeout',
392
+ r'server.*closed.*connection|connection.*lost',
393
+ r'no route to host|host.*unreachable',
394
+ r'connection.*reset|reset.*connection'
395
+ ]
396
+
397
+ # Enhanced duplicate key patterns
398
+ duplicate_patterns = [
399
+ r'duplicate.*key.*value|unique.*constraint.*violated',
400
+ r'duplicate.*entry|key.*already.*exists',
401
+ r'violates.*unique.*constraint',
402
+ r'unique.*violation|constraint.*unique'
403
+ ]
404
+
405
+ # Enhanced permission/authorization patterns
406
+ permission_patterns = [
407
+ r'permission.*denied|access.*denied|authorization.*failed',
408
+ r'insufficient.*privileges|privilege.*denied',
409
+ r'not.*authorized|unauthorized.*access',
410
+ r'authentication.*failed|login.*failed'
411
+ ]
412
+
413
+ # Enhanced database/table not found patterns
414
+ not_found_patterns = [
415
+ r'database.*does.*not.*exist|unknown.*database',
416
+ r'table.*does.*not.*exist|relation.*does.*not.*exist',
417
+ r'no.*such.*database|database.*not.*found',
418
+ r'schema.*does.*not.*exist|unknown.*table'
419
+ ]
420
+
421
+ # Enhanced syntax error patterns
422
+ syntax_patterns = [
423
+ r'syntax.*error|invalid.*syntax',
424
+ r'malformed.*query|bad.*sql.*grammar',
425
+ r'unexpected.*token|parse.*error'
426
+ ]
427
+
428
+ # Enhanced deadlock/timeout patterns
429
+ deadlock_patterns = [
430
+ r'deadlock.*detected|lock.*timeout',
431
+ r'timeout.*waiting.*for.*lock|query.*timeout',
432
+ r'lock.*wait.*timeout|deadlock.*found'
433
+ ]
434
+
435
+ # Comprehensive pattern matching with error class mapping
436
+ pattern_mappings = [
437
+ (connection_patterns, 'ConnectionError'),
438
+ (duplicate_patterns, 'DuplicateError'),
439
+ (permission_patterns, 'PermissionError'),
440
+ (not_found_patterns, 'NotFoundError'),
441
+ (syntax_patterns, 'SyntaxError'),
442
+ (deadlock_patterns, 'DeadlockError')
443
+ ]
444
+
445
+ # Apply pattern matching
446
+ for patterns, error_class in pattern_mappings:
447
+ for pattern in patterns:
448
+ try:
449
+ if re.search(pattern, error_message_lower):
450
+ logger.info(f"Classified error by pattern: '{pattern}' -> {error_class}")
451
+ return self._create_exception_with_chaining(
452
+ error_class, error_message, exception, sql, parameters
453
+ )
454
+ except re.error as regex_error:
455
+ logger.warning(f"Regex pattern error '{pattern}': {regex_error}")
456
+ continue
457
+ except Exception as pattern_error:
458
+ logger.error(f"Error applying pattern '{pattern}': {pattern_error}")
459
+ continue
460
+
461
+ # Fallback: return generic database error with full context
340
462
  logger.warning(
341
- "Database error caught. Attempting to transform: code=%s message=%s",
342
- error_code,
343
- error_mesg,
463
+ f"Could not classify error. Returning generic DatabaseError. "
464
+ f"Error code: {error_code}, Available error codes: {list(getattr(self, 'error_codes', {}).keys()) if hasattr(self, 'error_codes') else 'None'}"
344
465
  )
345
-
346
- if error_code in self.sql.ApplicationErrorCodes:
347
- raise exceptions.DbApplicationError from None
348
- if error_code in self.sql.ColumnMissingErrorCodes:
349
- raise exceptions.DbColumnMissingError from None
350
- if error_code in self.sql.TableMissingErrorCodes:
351
- raise exceptions.DbTableMissingError from None
352
- if error_code in self.sql.DatabaseMissingErrorCodes:
353
- raise exceptions.DbDatabaseMissingError from None
354
- if error_code in self.sql.ForeignKeyMissingErrorCodes:
355
- raise exceptions.DbForeignKeyMissingError from None
356
- if error_code in self.sql.TruncationErrorCodes:
357
- raise exceptions.DbTruncationError from None
358
- if error_code in self.sql.DataIntegrityErrorCodes:
359
- raise exceptions.DbDataIntegrityError from None
360
- if error_code in self.sql.ConnectionErrorCodes:
361
- raise exceptions.DbConnectionError from None
362
- if error_code in self.sql.DuplicateKeyErrorCodes:
363
- raise exceptions.DbDuplicateKeyError from None
364
- if re.search(r"key \(sys_id\)=\(\d+\) already exists.", msg, re.M):
365
- raise exceptions.DbDuplicateKeyError from None
366
- if error_code in self.sql.DatabaseObjectExistsErrorCodes:
367
- raise exceptions.DbObjectExistsError from None
368
- if error_code in self.sql.LockTimeoutErrorCodes:
369
- raise exceptions.DbLockTimeoutError from None
370
- if error_code in self.sql.RetryTransactionCodes:
371
- raise exceptions.DbRetryTransaction from None
372
- if re.findall(r"database.*does not exist", msg, re.M):
373
- raise exceptions.DbDatabaseMissingError from None
374
- if re.findall(r"no such database", msg, re.M):
375
- raise exceptions.DbDatabaseMissingError from None
376
- if re.findall(r"already exists", msg, re.M):
377
- raise exceptions.DbObjectExistsError from None
378
- if re.findall(r"server closed the connection unexpectedly", msg, re.M):
379
- raise exceptions.DbConnectionError from None
380
- if re.findall(r"no connection to the server", msg, re.M):
381
- raise exceptions.DbConnectionError from None
382
- if re.findall(r"connection timed out", msg, re.M):
383
- raise exceptions.DbConnectionError from None
384
- if re.findall(r"could not connect to server", msg, re.M):
385
- raise exceptions.DbConnectionError from None
386
- if re.findall(r"cannot connect to server", msg, re.M):
387
- raise exceptions.DbConnectionError from None
388
- if re.findall(r"connection already closed", msg, re.M):
389
- raise exceptions.DbConnectionError from None
390
- if re.findall(r"cursor already closed", msg, re.M):
391
- raise exceptions.DbConnectionError from None
392
- if "no such table:" in msg:
393
- raise exceptions.DbTableMissingError from None
394
-
395
- logger.error(
396
- "Unhandled/Unknown Error in engine.process_error",
397
- exc_info=True,
398
- extra={
399
- "error_code": error_code,
400
- "error_msg": error_mesg,
401
- "sql_stmt": sql_stmt,
402
- "sql_params": sql_params,
403
- },
466
+
467
+ return self._create_exception_with_chaining(
468
+ 'DatabaseError', error_message, exception, sql, parameters
404
469
  )
405
- raise
470
+
471
+ def _create_exception_with_chaining(self, error_class, message, original_exception, sql=None, parameters=None):
472
+ """
473
+ Create a velocity exception with proper exception chaining.
474
+
475
+ Args:
476
+ error_class: The name of the exception class to create
477
+ message: The error message
478
+ original_exception: The original exception to chain
479
+ sql: The SQL statement (optional)
480
+ parameters: The SQL parameters (optional)
481
+
482
+ Returns:
483
+ The created exception with proper chaining
484
+ """
485
+ logger = logging.getLogger(__name__)
486
+
487
+ try:
488
+ # Import the exception class dynamically
489
+ exception_module = __import__('velocity.db.exceptions', fromlist=[error_class])
490
+ ExceptionClass = getattr(exception_module, error_class)
491
+
492
+ # Create enhanced message with context
493
+ if sql:
494
+ enhanced_message = f"{message} (SQL: {sql[:200]}{'...' if len(sql) > 200 else ''})"
495
+ else:
496
+ enhanced_message = message
497
+
498
+ # Create the exception with chaining
499
+ new_exception = ExceptionClass(enhanced_message)
500
+ new_exception.__cause__ = original_exception # Preserve exception chain
501
+
502
+ return new_exception
503
+
504
+ except (ImportError, AttributeError) as e:
505
+ logger.error(f"Could not import exception class {error_class}: {e}")
506
+ # Fallback to generic database error
507
+ try:
508
+ exception_module = __import__('velocity.db.exceptions', fromlist=['DatabaseError'])
509
+ DatabaseError = getattr(exception_module, 'DatabaseError')
510
+ fallback_exception = DatabaseError(f"Database error: {message}")
511
+ fallback_exception.__cause__ = original_exception
512
+ return fallback_exception
513
+ except Exception as fallback_error:
514
+ logger.critical(f"Failed to create fallback exception: {fallback_error}")
515
+ # Last resort: return the original exception
516
+ return original_exception
@@ -68,20 +68,20 @@ class SQL:
68
68
 
69
69
  default_schema = "public"
70
70
 
71
- ApplicationErrorCodes = ["22P02", "42883"]
71
+ ApplicationErrorCodes = ["22P02", "42883", "42501", "42601", "25P01", "25P02"]
72
72
 
73
- DatabaseMissingErrorCodes = []
73
+ DatabaseMissingErrorCodes = ["3D000"]
74
74
  TableMissingErrorCodes = ["42P01"]
75
75
  ColumnMissingErrorCodes = ["42703"]
76
76
  ForeignKeyMissingErrorCodes = ["42704"]
77
77
 
78
- ConnectionErrorCodes = ["08001", "08S01", "57P03", "08006", "53300"]
79
- DuplicateKeyErrorCodes = [] # Handled in regex check.
80
- RetryTransactionCodes = []
78
+ ConnectionErrorCodes = ["08001", "08S01", "57P03", "08006", "53300", "08003", "08004", "08P01"]
79
+ DuplicateKeyErrorCodes = ["23505"] # unique_violation - no longer relying only on regex
80
+ RetryTransactionCodes = ["40001", "40P01", "40002"]
81
81
  TruncationErrorCodes = ["22001"]
82
82
  LockTimeoutErrorCodes = ["55P03"]
83
83
  DatabaseObjectExistsErrorCodes = ["42710", "42P07", "42P04"]
84
- DataIntegrityErrorCodes = ["23503"]
84
+ DataIntegrityErrorCodes = ["23503", "23502", "23514", "23P01", "22003"]
85
85
 
86
86
  @classmethod
87
87
  def get_error(self, e):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.98
3
+ Version: 0.0.101
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <contact@example.com>
6
6
  License: MIT
@@ -16,7 +16,7 @@ velocity/db/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
16
16
  velocity/db/core/column.py,sha256=tAr8tL3a2nyaYpNHhGl508FrY_pGZTzyYgjAV5CEBv4,4092
17
17
  velocity/db/core/database.py,sha256=3zNGItklu9tZCKsbx2T2vCcU1so8AL9PPL0DLjvaz6s,3554
18
18
  velocity/db/core/decorators.py,sha256=76Jkr9XptXt8cvcgp1zbHfuL8uHzWy8lwfR29u-DVu4,4574
19
- velocity/db/core/engine.py,sha256=3POukyvvtGVTwEGx4vsAInWw_WGGsqMdBFozoPUnIi0,14665
19
+ velocity/db/core/engine.py,sha256=UmaYwVCDBtvDHdHdKNsZ4kZ0GpZ7uMp1MaqpfOHblNI,19311
20
20
  velocity/db/core/exceptions.py,sha256=MOWyA1mlMe8eWbFkEHK0Lp9czdplpRyqbAn2JfGmMrM,707
21
21
  velocity/db/core/result.py,sha256=OVqoMwlx3CHNNwr-JGWRx5I8u_YX6hlUpecx99UT5nE,6164
22
22
  velocity/db/core/row.py,sha256=aliLYTTFirgJsOvmUsANwJMyxaATuhpGpFJhcu_twwY,6709
@@ -34,7 +34,7 @@ velocity/db/servers/tablehelper.py,sha256=qOHHKgQgUC0t_AUcY5oaPfjkRJS9wnMI4YJCDI
34
34
  velocity/db/servers/postgres/__init__.py,sha256=FUvXO3R5CtKCTGRim1geisIxXbiG_aQ_VFSQX9HGsjw,529
35
35
  velocity/db/servers/postgres/operators.py,sha256=A2T1qFwhzPl0fdXVhLZJhh5Qfx-qF8oZsDnxnq2n_V8,389
36
36
  velocity/db/servers/postgres/reserved.py,sha256=5tKLaqFV-HrWRj-nsrxl5KGbmeM3ukn_bPZK36XEu8M,3648
37
- velocity/db/servers/postgres/sql.py,sha256=dDUcdErrAgWulrr6582p4Zf1co6-PH8MfVU2q9KRisI,41416
37
+ velocity/db/servers/postgres/sql.py,sha256=hQugqzYuLWPCDpN1Tdt6CSYRTZHpG9aBc0GPagkmGYw,41581
38
38
  velocity/db/servers/postgres/types.py,sha256=Wa45ppVf_pdWul-jYWFRGMl6IdSq8dAp10SKnhL7osQ,3757
39
39
  velocity/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  velocity/misc/db.py,sha256=MPgt-kkukKR_Wh_S_5W-MyDgaeoZ4YLoDJ54wU2ppm4,2830
@@ -47,8 +47,8 @@ velocity/misc/tools.py,sha256=_bGneHHA_BV-kUonzw5H3hdJ5AOJRCKfzhgpkFbGqIo,1502
47
47
  velocity/misc/conv/__init__.py,sha256=MLYF58QHjzfDSxb1rdnmLnuEQCa3gnhzzZ30CwZVvQo,40
48
48
  velocity/misc/conv/iconv.py,sha256=d4_BucW8HTIkGNurJ7GWrtuptqUf-9t79ObzjJ5N76U,10603
49
49
  velocity/misc/conv/oconv.py,sha256=h5Lo05DqOQnxoD3y6Px_MQP_V-pBbWf8Hkgkb9Xp1jk,6032
50
- velocity_python-0.0.98.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
51
- velocity_python-0.0.98.dist-info/METADATA,sha256=Rku_t3HhYXhu2YvIwrFe7ew6m82pAe5edjwXmtdZY5M,33022
52
- velocity_python-0.0.98.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- velocity_python-0.0.98.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
54
- velocity_python-0.0.98.dist-info/RECORD,,
50
+ velocity_python-0.0.101.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
51
+ velocity_python-0.0.101.dist-info/METADATA,sha256=rlIAaLsXf3k1JAQbQZuGuXWzHo6hIFG1Yxe5Zk9jwas,33023
52
+ velocity_python-0.0.101.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ velocity_python-0.0.101.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
54
+ velocity_python-0.0.101.dist-info/RECORD,,