datus-sqlalchemy 0.1.0__tar.gz → 0.1.2__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.0
3
+ Version: 0.1.2
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
@@ -71,33 +71,33 @@ class SQLAlchemyConnector(BaseSqlConnector):
71
71
 
72
72
  @override
73
73
  def connect(self):
74
- """Establish connection to the database."""
75
- if self.engine and self.connection and self._owns_engine:
74
+ """Initialize the connection pool (engine only, no persistent connection)."""
75
+ if self.engine and self._owns_engine:
76
76
  return
77
77
 
78
78
  try:
79
79
  self._safe_close()
80
80
 
81
- # Create engine based on dialect
81
+ # Create engine with connection pool
82
82
  if self.dialect not in (DBType.DUCKDB, DBType.SQLITE):
83
83
  self.engine = create_engine(
84
84
  self.connection_string,
85
- pool_size=3,
86
- max_overflow=5,
85
+ pool_size=10, # Increased for parallel execution
86
+ max_overflow=20, # Allow more overflow connections
87
87
  pool_timeout=self.timeout_seconds,
88
88
  pool_recycle=3600,
89
+ pool_pre_ping=True, # Verify connections before use
89
90
  )
90
91
  else:
91
92
  self.engine = create_engine(self.connection_string)
92
93
 
93
- self.connection = self.engine.connect()
94
94
  self._owns_engine = True
95
95
 
96
96
  except Exception as e:
97
97
  self._force_reset()
98
98
  raise self._handle_exception(e, "", "connection") from e
99
99
 
100
- if not (self.engine and self.connection):
100
+ if not self.engine:
101
101
  self._force_reset()
102
102
  raise DatusException(
103
103
  ErrorCode.DB_CONNECTION_FAILED, message_args={"error_message": "Failed to establish connection"}
@@ -105,16 +105,14 @@ class SQLAlchemyConnector(BaseSqlConnector):
105
105
 
106
106
  @override
107
107
  def close(self):
108
- """Close the database connection."""
108
+ """Dispose the connection pool."""
109
109
  try:
110
- if self.connection:
111
- self.connection.close()
112
- self.connection = None
113
110
  if self.engine:
114
111
  self.engine.dispose()
115
112
  self.engine = None
113
+ self._owns_engine = False
116
114
  except Exception as e:
117
- logger.warning(f"Error closing connection: {str(e)}")
115
+ logger.warning(f"Error disposing engine: {str(e)}")
118
116
 
119
117
  def _safe_close(self):
120
118
  """Safely close connection, ignoring errors."""
@@ -124,15 +122,8 @@ class SQLAlchemyConnector(BaseSqlConnector):
124
122
  pass
125
123
 
126
124
  def _force_reset(self):
127
- """Force reset connection on error."""
125
+ """Force reset engine on error."""
128
126
  try:
129
- self._safe_rollback()
130
- if self.connection:
131
- try:
132
- self.connection.close()
133
- except Exception:
134
- pass
135
- self.connection = None
136
127
  if self.engine:
137
128
  try:
138
129
  self.engine.dispose()
@@ -141,18 +132,9 @@ class SQLAlchemyConnector(BaseSqlConnector):
141
132
  self.engine = None
142
133
  self._owns_engine = False
143
134
  except Exception:
144
- self.connection = None
145
135
  self.engine = None
146
136
  self._owns_engine = False
147
137
 
148
- def _safe_rollback(self):
149
- """Safely rollback transaction."""
150
- if self.connection:
151
- try:
152
- self.connection.rollback()
153
- except Exception:
154
- pass
155
-
156
138
  # ==================== Error Handling ====================
157
139
 
158
140
  def _handle_exception(self, e: Exception, sql: str = "", operation: str = "SQL execution") -> DatusException:
@@ -269,9 +251,11 @@ class SQLAlchemyConnector(BaseSqlConnector):
269
251
 
270
252
  self.connect()
271
253
  try:
272
- result = self.connection.execute(text(sql))
273
- rows = result.fetchall()
274
- return [row._asdict() for row in rows]
254
+ # Get connection from pool for this query
255
+ with self.engine.connect() as conn:
256
+ result = conn.execute(text(sql))
257
+ rows = result.fetchall()
258
+ return [row._asdict() for row in rows]
275
259
  except DatusException:
276
260
  raise
277
261
  except Exception as e:
@@ -282,23 +266,25 @@ class SQLAlchemyConnector(BaseSqlConnector):
282
266
  """Execute INSERT statement."""
283
267
  try:
284
268
  self.connect()
285
- res = self.connection.execute(text(sql))
286
- self.connection.commit()
269
+ with self.engine.connect() as conn:
270
+ res = conn.execute(text(sql))
271
+ conn.commit()
287
272
 
288
- # Get inserted primary key or row count
289
- inserted_pk = None
290
- try:
291
- if hasattr(res, "inserted_primary_key") and res.inserted_primary_key:
292
- inserted_pk = res.inserted_primary_key
293
- except Exception:
294
- pass
273
+ # Get inserted primary key or row count
274
+ inserted_pk = None
275
+ try:
276
+ if hasattr(res, "inserted_primary_key") and res.inserted_primary_key:
277
+ inserted_pk = res.inserted_primary_key
278
+ except Exception:
279
+ pass
295
280
 
296
- lastrowid = getattr(res, "lastrowid", None)
297
- return_value = inserted_pk if inserted_pk else (lastrowid if lastrowid else res.rowcount)
281
+ lastrowid = getattr(res, "lastrowid", None)
282
+ return_value = inserted_pk if inserted_pk else (lastrowid if lastrowid else res.rowcount)
298
283
 
299
- return ExecuteSQLResult(success=True, sql_query=sql, sql_return=str(return_value), row_count=res.rowcount)
284
+ return ExecuteSQLResult(
285
+ success=True, sql_query=sql, sql_return=str(return_value), row_count=res.rowcount
286
+ )
300
287
  except Exception as e:
301
- self._safe_rollback()
302
288
  ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
303
289
  return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql, sql_return="", row_count=0)
304
290
 
@@ -307,11 +293,13 @@ class SQLAlchemyConnector(BaseSqlConnector):
307
293
  """Execute UPDATE statement."""
308
294
  try:
309
295
  self.connect()
310
- res = self.connection.execute(text(sql))
311
- self.connection.commit()
312
- return ExecuteSQLResult(success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount)
296
+ with self.engine.connect() as conn:
297
+ res = conn.execute(text(sql))
298
+ conn.commit()
299
+ return ExecuteSQLResult(
300
+ success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount
301
+ )
313
302
  except Exception as e:
314
- self._safe_rollback()
315
303
  ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
316
304
  return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql, sql_return="", row_count=0)
317
305
 
@@ -320,11 +308,13 @@ class SQLAlchemyConnector(BaseSqlConnector):
320
308
  """Execute DELETE statement."""
321
309
  try:
322
310
  self.connect()
323
- res = self.connection.execute(text(sql))
324
- self.connection.commit()
325
- return ExecuteSQLResult(success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount)
311
+ with self.engine.connect() as conn:
312
+ res = conn.execute(text(sql))
313
+ conn.commit()
314
+ return ExecuteSQLResult(
315
+ success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount
316
+ )
326
317
  except Exception as e:
327
- self._safe_rollback()
328
318
  ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
329
319
  return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql, sql_return="", row_count=0)
330
320
 
@@ -333,11 +323,13 @@ class SQLAlchemyConnector(BaseSqlConnector):
333
323
  """Execute DDL statement (CREATE, ALTER, DROP, etc.)."""
334
324
  try:
335
325
  self.connect()
336
- res = self.connection.execute(text(sql))
337
- self.connection.commit()
338
- return ExecuteSQLResult(success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount)
326
+ with self.engine.connect() as conn:
327
+ res = conn.execute(text(sql))
328
+ conn.commit()
329
+ return ExecuteSQLResult(
330
+ success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount
331
+ )
339
332
  except Exception as e:
340
- self._safe_rollback()
341
333
  ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
342
334
  return ExecuteSQLResult(success=False, sql_query=sql, error=str(ex))
343
335
 
@@ -395,8 +387,9 @@ class SQLAlchemyConnector(BaseSqlConnector):
395
387
  """Execute USE/SET commands."""
396
388
  self.connect()
397
389
  try:
398
- self.connection.execute(text(sql))
399
- self.connection.commit()
390
+ with self.engine.connect() as conn:
391
+ conn.execute(text(sql))
392
+ conn.commit()
400
393
 
401
394
  # Update context if applicable
402
395
  if self.dialect != DBType.SQLITE.value:
@@ -411,7 +404,6 @@ class SQLAlchemyConnector(BaseSqlConnector):
411
404
 
412
405
  return ExecuteSQLResult(success=True, sql_query=sql, sql_return="Successful", row_count=0)
413
406
  except Exception as e:
414
- self._safe_rollback()
415
407
  ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
416
408
  return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql)
417
409
 
@@ -420,29 +412,31 @@ class SQLAlchemyConnector(BaseSqlConnector):
420
412
  results = []
421
413
  self.connect()
422
414
  try:
423
- for query in queries:
424
- result = self.connection.execute(text(query))
425
- if result.returns_rows:
426
- df = DataFrame(result.fetchall(), columns=list(result.keys()))
427
- results.append(df.to_dict(orient="records"))
428
- else:
429
- query_lower = query.strip().lower()
430
- if query_lower.startswith("insert"):
431
- inserted_pk = None
432
- try:
433
- if hasattr(result, "inserted_primary_key") and result.inserted_primary_key:
434
- inserted_pk = result.inserted_primary_key
435
- except Exception:
436
- pass
437
- lastrowid = getattr(result, "lastrowid", None)
438
- results.append(inserted_pk if inserted_pk else (lastrowid if lastrowid else result.rowcount))
439
- elif query_lower.startswith(("update", "delete")):
440
- results.append(result.rowcount)
415
+ with self.engine.connect() as conn:
416
+ for query in queries:
417
+ result = conn.execute(text(query))
418
+ if result.returns_rows:
419
+ df = DataFrame(result.fetchall(), columns=list(result.keys()))
420
+ results.append(df.to_dict(orient="records"))
441
421
  else:
442
- results.append(None)
443
- self.connection.commit()
422
+ query_lower = query.strip().lower()
423
+ if query_lower.startswith("insert"):
424
+ inserted_pk = None
425
+ try:
426
+ if hasattr(result, "inserted_primary_key") and result.inserted_primary_key:
427
+ inserted_pk = result.inserted_primary_key
428
+ except Exception:
429
+ pass
430
+ lastrowid = getattr(result, "lastrowid", None)
431
+ results.append(
432
+ inserted_pk if inserted_pk else (lastrowid if lastrowid else result.rowcount)
433
+ )
434
+ elif query_lower.startswith(("update", "delete")):
435
+ results.append(result.rowcount)
436
+ else:
437
+ results.append(None)
438
+ conn.commit()
444
439
  except SQLAlchemyError as e:
445
- self._safe_rollback()
446
440
  raise self._handle_exception(e, "\n".join(queries), "batch query") from e
447
441
  return results
448
442
 
@@ -453,14 +447,11 @@ class SQLAlchemyConnector(BaseSqlConnector):
453
447
  self._execute_query("SELECT 1")
454
448
  return True
455
449
  except Exception as e:
456
- self._safe_close()
457
450
  if isinstance(e, DatusException):
458
451
  raise
459
452
  raise DatusException(
460
453
  ErrorCode.DB_CONNECTION_FAILED, message_args={"error_message": "Connection test failed"}
461
454
  ) from e
462
- finally:
463
- self._safe_close()
464
455
 
465
456
  # ==================== Metadata Methods ====================
466
457
 
@@ -609,19 +600,20 @@ class SQLAlchemyConnector(BaseSqlConnector):
609
600
  """Execute query and return CSV rows in batches."""
610
601
  self.connect()
611
602
  try:
612
- result = self.connection.execute(text(sql).execution_options(stream_results=True, max_row_buffer=max_rows))
613
- if result.returns_rows:
614
- if with_header:
615
- yield result.keys()
616
- while True:
617
- batch_rows = result.fetchmany(max_rows)
618
- if not batch_rows:
619
- break
620
- for row in batch_rows:
621
- yield row
622
- else:
623
- if with_header:
624
- yield ()
625
- yield from []
603
+ with self.engine.connect() as conn:
604
+ result = conn.execute(text(sql).execution_options(stream_results=True, max_row_buffer=max_rows))
605
+ if result.returns_rows:
606
+ if with_header:
607
+ yield result.keys()
608
+ while True:
609
+ batch_rows = result.fetchmany(max_rows)
610
+ if not batch_rows:
611
+ break
612
+ for row in batch_rows:
613
+ yield row
614
+ else:
615
+ if with_header:
616
+ yield ()
617
+ yield from []
626
618
  except Exception as e:
627
619
  raise self._handle_exception(e) from e
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datus-sqlalchemy"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "SQLAlchemy base connector for Datus database adapters"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"