Skip to content

Commit aea1876

Browse files
committed
chore: remove hasattr usages
1 parent 383df5e commit aea1876

File tree

110 files changed

+1086
-1134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+1086
-1134
lines changed

AGENTS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ class MyAdapterDriver(SyncDriverBase):
155155
- Add integration tests under `tests/integration/test_adapters/<adapter>/test_driver.py::test_*statement_stack*` that cover native path, sequential fallback, and continue-on-error.
156156
- Guard base behavior (empty stacks, large stacks, transaction boundaries) via `tests/integration/test_stack_edge_cases.py`.
157157

158+
### ADK Memory Store Pattern
159+
160+
- `SQLSpecMemoryService` delegates storage to adapter-backed memory stores (`BaseAsyncADKMemoryStore` / `BaseSyncADKMemoryStore`).
161+
- All ADK settings live in `extension_config["adk"]`; memory flags are `enable_memory`, `include_memory_migration`, `memory_table`, `memory_use_fts`, and `memory_max_results`.
162+
- Search strategy is driver-determined: `memory_use_fts=True` enables adapter FTS when available, otherwise fall back to `LIKE`/`ILIKE` with warning on failure.
163+
- Deduplication is keyed by `event_id` with idempotent inserts (ignore duplicates, return inserted count).
164+
- Multi-tenancy uses the shared `owner_id_column` DDL; stores parse the column name to bind filter parameters.
165+
- TTL cleanup is explicit via store helpers or CLI (`delete_entries_older_than`, `sqlspec adk memory cleanup`).
166+
158167
### Driver Parameter Profile Registry
159168

160169
- All adapter parameter defaults live in `DriverParameterProfile` entries inside `sqlspec/core/parameters.py`.

docs/guides/adapters/oracledb.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,8 @@ For comprehensive examples and migration guides, see:
905905
- Set `extension_config={"events": {"backend": "advanced_queue"}}` to enable native
906906
Advanced Queuing support. Event publishing uses `connection.queue()` and
907907
inherits the AQ options from `extension_config["events"]`
908-
(`aq_queue`, `aq_wait_seconds`, `aq_visibility`).
908+
(`aq_queue`, `aq_wait_seconds`, `aq_visibility`). Use `AQMSG_VISIBLE` or
909+
`AQMSG_INVISIBLE` (string or int constant) for visibility control.
909910
- AQ requires DBA-provisioned queues plus enqueue/dequeue privileges. When the
910911
driver detects missing privileges it logs a warning and falls back to the
911912
durable queue backend automatically.

docs/guides/events/database-event-channels.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Optional ``extension_config["events"]`` keys:
7272

7373
- ``aq_queue``: AQ queue name (default ``SQLSPEC_EVENTS_QUEUE``)
7474
- ``aq_wait_seconds``: dequeue wait timeout (default 5 seconds)
75-
- ``aq_visibility``: visibility constant (e.g., ``AQMSG_VISIBLE``)
75+
- ``aq_visibility``: visibility constant (``AQMSG_VISIBLE`` or ``AQMSG_INVISIBLE``) or the integer value.
7676

7777
If AQ is not configured or the Python driver lacks the feature, SQLSpec logs a warning
7878
and transparently falls back to the table-backed queue backend.

docs/guides/extensions/adk.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ config = AsyncpgConfig(connection_config={"dsn": "postgresql://..."},
225225
- Schedule periodic cleanup with adapter-provided pruning helpers or ad-hoc SQL that removes stale rows.
226226
- Back up tables like any other transactional data; events can grow quickly, so consider partitioning or TTL policies in PostgreSQL (`CREATE POLICY ... USING (create_time > now() - interval '90 days')`).
227227

228+
## Future Enhancements
229+
230+
- Vector/embedding search hooks for adapter-specific similarity queries.
231+
- Vertex AI Memory Bank and RAG bridge helpers for hybrid deployments.
232+
- Background TTL pruning workflows for high-volume memory tables.
233+
228234
## Additional Resources
229235

230236
- API reference: `docs/extensions/adk/`

sqlspec/adapters/adbc/adk/store.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""ADBC ADK store for Google Agent Development Kit session/event storage."""
22

3+
from datetime import datetime, timezone
34
from typing import TYPE_CHECKING, Any, Final
45

56
from sqlspec.extensions.adk import BaseSyncADKStore, EventRecord, SessionRecord
@@ -747,8 +748,6 @@ def create_event(
747748

748749
timestamp = kwargs.get("timestamp")
749750
if timestamp is None:
750-
from datetime import datetime, timezone
751-
752751
timestamp = datetime.now(timezone.utc)
753752

754753
with self._config.provide_connection() as conn:

sqlspec/adapters/aiosqlite/driver.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import asyncio
44
import contextlib
5+
import random
56
from datetime import date, datetime
67
from decimal import Decimal
78
from typing import TYPE_CHECKING, Any, cast
89

910
import aiosqlite
1011

12+
from sqlspec.adapters.aiosqlite.data_dictionary import AiosqliteAsyncDataDictionary
1113
from sqlspec.core import (
1214
ArrowResult,
1315
DriverParameterProfile,
@@ -99,7 +101,7 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
99101
if issubclass(exc_type, aiosqlite.Error):
100102
self._map_sqlite_exception(exc_val)
101103

102-
def _map_sqlite_exception(self, e: Any) -> None:
104+
def _map_sqlite_exception(self, e: BaseException) -> None:
103105
"""Map SQLite exception to SQLSpec exception.
104106
105107
Args:
@@ -108,17 +110,18 @@ def _map_sqlite_exception(self, e: Any) -> None:
108110
Raises:
109111
Specific SQLSpec exception based on error code
110112
"""
113+
exc: BaseException = e
111114
if has_sqlite_error(e):
112115
error_code = e.sqlite_errorcode
113116
error_name = e.sqlite_errorname
114117
else:
115118
error_code = None
116119
error_name = None
117-
error_msg = str(e).lower()
120+
error_msg = str(exc).lower()
118121

119122
if "locked" in error_msg:
120-
msg = f"AIOSQLite database locked: {e}. Consider enabling WAL mode or reducing concurrency."
121-
raise SQLSpecError(msg) from e
123+
msg = f"AIOSQLite database locked: {exc}. Consider enabling WAL mode or reducing concurrency."
124+
raise SQLSpecError(msg) from exc
122125

123126
if not error_code:
124127
if "unique constraint" in error_msg:
@@ -358,8 +361,6 @@ async def begin(self) -> None:
358361
if not self.connection.in_transaction:
359362
await self.connection.execute("BEGIN IMMEDIATE")
360363
except aiosqlite.Error as e:
361-
import random
362-
363364
max_retries = 3
364365
for attempt in range(max_retries):
365366
delay = 0.01 * (2**attempt) + random.uniform(0, 0.01) # noqa: S311
@@ -411,8 +412,6 @@ def data_dictionary(self) -> "AsyncDataDictionaryBase":
411412
Data dictionary instance for metadata queries
412413
"""
413414
if self._data_dictionary is None:
414-
from sqlspec.adapters.aiosqlite.data_dictionary import AiosqliteAsyncDataDictionary
415-
416415
self._data_dictionary = AiosqliteAsyncDataDictionary()
417416
return self._data_dictionary
418417

sqlspec/adapters/asyncmy/adk/memory_store.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""AsyncMy ADK memory store for Google Agent Development Kit memory storage."""
22

33
import json
4+
import re
45
from typing import TYPE_CHECKING, Any, Final
56

67
import asyncmy
@@ -28,8 +29,6 @@ def _parse_owner_id_column_for_mysql(column_ddl: str) -> "tuple[str, str]":
2829
Returns:
2930
Tuple of (column_definition, foreign_key_constraint).
3031
"""
31-
import re
32-
3332
references_match = re.search(r"\s+REFERENCES\s+(.+)", column_ddl, re.IGNORECASE)
3433
if not references_match:
3534
return (column_ddl.strip(), "")

sqlspec/adapters/asyncmy/adk/store.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""AsyncMy ADK store for Google Agent Development Kit session/event storage."""
22

33
import json
4+
import re
45
from typing import TYPE_CHECKING, Any, Final
56

67
import asyncmy
@@ -92,8 +93,6 @@ def _parse_owner_id_column_for_mysql(self, column_ddl: str) -> "tuple[str, str]"
9293
Input: "tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE"
9394
Output: ("tenant_id BIGINT NOT NULL", "FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE")
9495
"""
95-
import re
96-
9796
references_match = re.search(r"\s+REFERENCES\s+(.+)", column_ddl, re.IGNORECASE)
9897

9998
if not references_match:

sqlspec/adapters/asyncmy/driver.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from asyncmy.constants import FIELD_TYPE as ASYNC_MY_FIELD_TYPE # pyright: ignore
1111
from asyncmy.cursors import Cursor, DictCursor # pyright: ignore
1212

13+
from sqlspec.adapters.asyncmy.data_dictionary import MySQLAsyncDataDictionary
1314
from sqlspec.core import (
1415
ArrowResult,
1516
DriverParameterProfile,
@@ -33,7 +34,14 @@
3334
)
3435
from sqlspec.utils.logging import get_logger
3536
from sqlspec.utils.serializers import from_json, to_json
36-
from sqlspec.utils.type_guards import has_cursor_metadata, has_rowcount, has_sqlstate, has_type_code, supports_json_type
37+
from sqlspec.utils.type_guards import (
38+
has_cursor_metadata,
39+
has_lastrowid,
40+
has_rowcount,
41+
has_sqlstate,
42+
has_type_code,
43+
supports_json_type,
44+
)
3745

3846
if TYPE_CHECKING:
3947
from collections.abc import Callable
@@ -444,11 +452,8 @@ async def _execute_statement(self, cursor: Any, statement: "SQL") -> "ExecutionR
444452

445453
affected_rows = cursor.rowcount if cursor.rowcount is not None else -1
446454
last_id = None
447-
if has_rowcount(cursor) and cursor.rowcount and cursor.rowcount > 0:
448-
try:
449-
last_id = cursor.lastrowid
450-
except AttributeError:
451-
last_id = None
455+
if has_rowcount(cursor) and cursor.rowcount and cursor.rowcount > 0 and has_lastrowid(cursor):
456+
last_id = cursor.lastrowid
452457
return self.create_execution_result(cursor, rowcount_override=affected_rows, last_inserted_id=last_id)
453458

454459
async def select_to_storage(
@@ -579,8 +584,6 @@ def data_dictionary(self) -> "AsyncDataDictionaryBase":
579584
Data dictionary instance for metadata queries
580585
"""
581586
if self._data_dictionary is None:
582-
from sqlspec.adapters.asyncmy.data_dictionary import MySQLAsyncDataDictionary
583-
584587
self._data_dictionary = MySQLAsyncDataDictionary()
585588
return self._data_dictionary
586589

sqlspec/adapters/asyncpg/driver.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import datetime
44
import re
55
from collections import OrderedDict
6+
from io import BytesIO
67
from typing import TYPE_CHECKING, Any, Final, NamedTuple, cast
78

89
import asyncpg
910

11+
from sqlspec.adapters.asyncpg.data_dictionary import PostgresAsyncDataDictionary
1012
from sqlspec.core import (
1113
DriverParameterProfile,
1214
ParameterStyle,
@@ -262,11 +264,8 @@ async def _handle_copy_operation(self, cursor: "AsyncpgConnection", statement: "
262264
statement: SQL statement with COPY operation
263265
"""
264266

265-
try:
266-
metadata_obj = statement.metadata
267-
except AttributeError:
268-
metadata_obj = {}
269-
metadata: dict[str, Any] = dict(metadata_obj) if metadata_obj else {}
267+
execution_args = statement.statement_config.execution_args
268+
metadata: dict[str, Any] = dict(execution_args) if execution_args else {}
270269
sql_text = statement.sql
271270
sql_upper = sql_text.upper()
272271
copy_data = metadata.get("postgres_copy_data")
@@ -283,8 +282,6 @@ async def _handle_copy_operation(self, cursor: "AsyncpgConnection", statement: "
283282
else:
284283
data_str = str(copy_data)
285284

286-
from io import BytesIO
287-
288285
data_io = BytesIO(data_str.encode("utf-8"))
289286
await cursor.copy_from_query(sql_text, output=data_io)
290287
return
@@ -626,8 +623,6 @@ def data_dictionary(self) -> "AsyncDataDictionaryBase":
626623
Data dictionary instance for metadata queries
627624
"""
628625
if self._data_dictionary is None:
629-
from sqlspec.adapters.asyncpg.data_dictionary import PostgresAsyncDataDictionary
630-
631626
self._data_dictionary = PostgresAsyncDataDictionary()
632627
return self._data_dictionary
633628

0 commit comments

Comments
 (0)