datus-sqlalchemy 0.1.7__tar.gz → 0.1.8__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datus-sqlalchemy
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: SQLAlchemy base connector for Datus database adapters
5
5
  Project-URL: Homepage, https://github.com/Datus-ai/datus-db-adapters
6
6
  Project-URL: Repository, https://github.com/Datus-ai/datus-db-adapters
@@ -14,7 +14,7 @@ Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Requires-Python: >=3.12
17
- Requires-Dist: datus-db-core>=0.1.3
17
+ Requires-Dist: datus-db-core>=0.1.5
18
18
  Requires-Dist: pandas>=2.1.4
19
19
  Requires-Dist: pyarrow<19.0.0,>=14.0.0
20
20
  Requires-Dist: sqlalchemy>=2.0.23
@@ -40,6 +40,32 @@ from datus_db_core import (
40
40
 
41
41
  logger = get_logger(__name__)
42
42
 
43
+ _SQL_PREVIEW_CHARS = 50
44
+
45
+
46
+ def _log_sql_exception(event: str, sql: str, exc: Exception) -> None:
47
+ """Log a SQL failure with a bounded preview instead of the full statement."""
48
+ statement = sql or ""
49
+ normalized_sql = " ".join(statement.split())
50
+ sql_preview = normalized_sql[:_SQL_PREVIEW_CHARS]
51
+ if len(normalized_sql) > _SQL_PREVIEW_CHARS:
52
+ sql_preview += "..."
53
+ driver_error = str(getattr(exc, "orig", None) or exc) or type(exc).__name__
54
+ if statement:
55
+ driver_error = driver_error.replace(statement, "<sql>")
56
+ if normalized_sql and normalized_sql != statement:
57
+ driver_error = driver_error.replace(normalized_sql, "<sql>")
58
+ safe_exception = RuntimeError(driver_error)
59
+ logger.error(
60
+ "%s; sql_preview=%r; sql_chars=%d; error_type=%s; error=%s",
61
+ event,
62
+ sql_preview,
63
+ len(statement),
64
+ type(exc).__name__,
65
+ driver_error,
66
+ exc_info=(type(safe_exception), safe_exception, exc.__traceback__),
67
+ )
68
+
43
69
 
44
70
  class SQLAlchemyConnector(BaseSqlConnector, MigrationTargetMixin):
45
71
  """
@@ -170,6 +196,7 @@ class SQLAlchemyConnector(BaseSqlConnector, MigrationTargetMixin):
170
196
  """Map SQLAlchemy exceptions to Datus exceptions."""
171
197
  if isinstance(e, DatusDbException):
172
198
  return e
199
+ _log_sql_exception(f"{operation} failed", sql, e)
173
200
 
174
201
  # Extract error message
175
202
  if hasattr(e, "detail") and e.detail:
@@ -767,7 +794,7 @@ class SQLAlchemyConnector(BaseSqlConnector, MigrationTargetMixin):
767
794
  yield ()
768
795
  yield from []
769
796
  except Exception as e:
770
- raise self._handle_exception(e) from e
797
+ raise self._handle_exception(e, sql, "streaming query") from e
771
798
 
772
799
  # ==================== MigrationTargetMixin (generic fallback) ====================
773
800
  #
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datus-sqlalchemy"
3
- version = "0.1.7"
3
+ version = "0.1.8"
4
4
  description = "SQLAlchemy base connector for Datus database adapters"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -18,7 +18,7 @@ classifiers = [
18
18
  ]
19
19
 
20
20
  dependencies = [
21
- "datus-db-core>=0.1.3",
21
+ "datus-db-core>=0.1.5",
22
22
  "sqlalchemy>=2.0.23",
23
23
  "pyarrow>=14.0.0,<19.0.0",
24
24
  "pandas>=2.1.4",
@@ -255,13 +255,16 @@ def test_execute_ddl_with_context_params():
255
255
  ),
256
256
  ],
257
257
  )
258
- def test_handle_exception_classifies_common_failures(exception, expected_code):
258
+ def test_handle_exception_classifies_and_logs_common_failures(exception, expected_code, caplog):
259
259
  """SQLAlchemy failures should map to stable Datus error categories."""
260
260
  connector = DummySQLAlchemyConnector("sqlite://", dialect="sqlite")
261
261
 
262
262
  classified = connector._handle_exception(exception, "SELECT 1", "query")
263
263
 
264
264
  assert classified.code == expected_code
265
+ assert "query failed; sql_preview='SELECT 1'; sql_chars=8" in caplog.text
266
+ assert caplog.text.count("SELECT 1") == 1
267
+ assert caplog.records[-1].exc_info[2] is exception.__traceback__
265
268
 
266
269
 
267
270
  def test_ensure_engine_classifies_connection_creation_failure():