Skip to content

Commit eb90aba

Browse files
feat: add aiomysql adapter (#413)
## Summary - Add new async MySQL adapter using [aiomysql](https://github.com/aio-libs/aiomysql) (built on PyMySQL) - Mirrors the existing `asyncmy` adapter structure for consistency - Extensions (ADK, Litestar, Events) deferred to follow-up
1 parent d7dbfbb commit eb90aba

46 files changed

Lines changed: 7279 additions & 858 deletions

Some content is hidden

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

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ uv.toml
6969
.beads/
7070
tools/scripts/profiles/*.prof
7171
.agents/
72+
73+
# Beads / Dolt files (added by bd init)
74+
.dolt/
75+
.beads-credential-key

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ repos:
1717
- id: mixed-line-ending
1818
- id: trailing-whitespace
1919
- repo: https://github.com/charliermarsh/ruff-pre-commit
20-
rev: "v0.15.9"
20+
rev: "v0.15.11"
2121
hooks:
2222
- id: ruff
2323
args: ["--fix"]

docs/reference/adapters/index.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ exports a typed config class and a driver implementation.
5353
:link: mysql
5454
:link-type: doc
5555

56-
MySQL via mysql-connector, PyMySQL, and asyncmy.
56+
MySQL via mysql-connector, PyMySQL, asyncmy, and aiomysql.
5757

5858
.. grid-item-card:: BigQuery
5959
:link: bigquery
@@ -151,6 +151,12 @@ Feature Comparison
151151
- Yes
152152
-
153153
-
154+
* - aiomysql
155+
-
156+
- Yes
157+
- Yes
158+
-
159+
-
154160
* - bigquery
155161
- Yes
156162
-

docs/reference/adapters/mysql.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,43 @@ Driver
7474
.. autoclass:: sqlspec.adapters.asyncmy.AsyncmyDriver
7575
:members:
7676
:show-inheritance:
77+
78+
aiomysql
79+
========
80+
81+
Async MySQL driver (PyMySQL-compatible wire protocol, asyncio-native).
82+
83+
Configuration
84+
-------------
85+
86+
.. autoclass:: sqlspec.adapters.aiomysql.AiomysqlConfig
87+
:members:
88+
:show-inheritance:
89+
90+
Driver
91+
------
92+
93+
.. autoclass:: sqlspec.adapters.aiomysql.AiomysqlDriver
94+
:members:
95+
:show-inheritance:
96+
97+
Connection Parameters
98+
---------------------
99+
100+
.. autoclass:: sqlspec.adapters.aiomysql.AiomysqlConnectionParams
101+
:members:
102+
:show-inheritance:
103+
104+
Pool Parameters
105+
---------------
106+
107+
.. autoclass:: sqlspec.adapters.aiomysql.AiomysqlPoolParams
108+
:members:
109+
:show-inheritance:
110+
111+
Driver Features
112+
---------------
113+
114+
.. autoclass:: sqlspec.adapters.aiomysql.AiomysqlDriverFeatures
115+
:members:
116+
:show-inheritance:

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ maintainers = [{ name = "Litestar Developers", email = "hello@litestar.dev" }]
2424
name = "sqlspec"
2525
readme = "README.md"
2626
requires-python = ">=3.10, <4.0"
27-
version = "0.43.0"
27+
version = "0.44.0"
2828

2929
[project.urls]
3030
Discord = "https://discord.gg/litestar"
@@ -34,6 +34,7 @@ Source = "https://github.com/litestar-org/sqlspec"
3434
[project.optional-dependencies]
3535
adbc = ["adbc_driver_manager", "pyarrow"]
3636
adk = ["google-adk"]
37+
aiomysql = ["aiomysql"]
3738
aioodbc = ["aioodbc"]
3839
aiosqlite = ["aiosqlite"]
3940
alloydb = ["google-cloud-alloydb-connector"]
@@ -243,7 +244,7 @@ opt_level = "3" # Maximum optimization (0-3)
243244
allow_dirty = true
244245
commit = false
245246
commit_args = "--no-verify"
246-
current_version = "0.43.0"
247+
current_version = "0.44.0"
247248
ignore_missing_files = false
248249
ignore_missing_version = false
249250
message = "chore(release): bump to v{new_version}"
@@ -358,6 +359,7 @@ markers = [
358359
"mssql: marks tests specific to Microsoft SQL Server",
359360
# Driver markers
360361
"adbc: marks tests using ADBC drivers",
362+
"aiomysql: marks tests using aiomysql",
361363
"aioodbc: marks tests using aioodbc",
362364
"aiosqlite: marks tests using aiosqlite",
363365
"asyncmy: marks tests using asyncmy",
@@ -400,6 +402,8 @@ module = [
400402
"orjson",
401403
"uvicorn.*",
402404
"uvloop.*",
405+
"aiomysql",
406+
"aiomysql.*",
403407
"asyncmy",
404408
"asyncmy.*",
405409
"pyarrow",

sqlspec/adapters/adbc/_typing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __enter__(self) -> "AdbcRawCursor":
4545
def __exit__(self, *_: Any) -> None:
4646
if self.cursor is not None:
4747
with contextlib.suppress(Exception):
48-
self.cursor.close() # type: ignore[no-untyped-call]
48+
self.cursor.close()
4949

5050

5151
class AdbcSessionContext:

sqlspec/adapters/adbc/adk/store.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ def _create_tables(self) -> None:
445445
cursor.execute(events_idx)
446446
conn.commit()
447447
finally:
448-
cursor.close() # type: ignore[no-untyped-call]
448+
cursor.close()
449449

450450
async def create_tables(self) -> None:
451451
"""Create tables if they don't exist."""
@@ -506,7 +506,7 @@ def _create_session(
506506
cursor.execute(sql, params)
507507
conn.commit()
508508
finally:
509-
cursor.close() # type: ignore[no-untyped-call]
509+
cursor.close()
510510

511511
result = self._get_session(session_id)
512512
if result is None:
@@ -557,7 +557,7 @@ def _get_session(self, session_id: str) -> "SessionRecord | None":
557557
update_time=row[5],
558558
)
559559
finally:
560-
cursor.close() # type: ignore[no-untyped-call]
560+
cursor.close()
561561
except Exception as e:
562562
error_msg = str(e).lower()
563563
if any(pattern in error_msg for pattern in ADBC_TABLE_NOT_FOUND_PATTERNS):
@@ -592,7 +592,7 @@ def _update_session_state(self, session_id: str, state: "dict[str, Any]") -> Non
592592
cursor.execute(sql, (state_json, session_id))
593593
conn.commit()
594594
finally:
595-
cursor.close() # type: ignore[no-untyped-call]
595+
cursor.close()
596596

597597
async def update_session_state(self, session_id: str, state: "dict[str, Any]") -> None:
598598
"""Update session state."""
@@ -616,7 +616,7 @@ def _delete_session(self, session_id: str) -> None:
616616
cursor.execute(sql, (session_id,))
617617
conn.commit()
618618
finally:
619-
cursor.close() # type: ignore[no-untyped-call]
619+
cursor.close()
620620

621621
async def delete_session(self, session_id: str) -> None:
622622
"""Delete session and associated events."""
@@ -671,7 +671,7 @@ def _list_sessions(self, app_name: str, user_id: str | None = None) -> "list[Ses
671671
for row in rows
672672
]
673673
finally:
674-
cursor.close() # type: ignore[no-untyped-call]
674+
cursor.close()
675675
except Exception as e:
676676
error_msg = str(e).lower()
677677
if any(pattern in error_msg for pattern in ADBC_TABLE_NOT_FOUND_PATTERNS):
@@ -710,7 +710,7 @@ def _insert_event(self, event_record: "EventRecord") -> None:
710710
)
711711
conn.commit()
712712
finally:
713-
cursor.close() # type: ignore[no-untyped-call]
713+
cursor.close()
714714

715715
def _append_event_and_update_state(
716716
self, event_record: "EventRecord", session_id: str, state: "dict[str, Any]"
@@ -760,7 +760,7 @@ def _append_event_and_update_state(
760760
conn.rollback()
761761
raise
762762
finally:
763-
cursor.close() # type: ignore[no-untyped-call]
763+
cursor.close()
764764

765765
async def append_event_and_update_state(
766766
self, event_record: EventRecord, session_id: str, state: "dict[str, Any]"
@@ -820,7 +820,7 @@ def _get_events(
820820
for row in rows
821821
]
822822
finally:
823-
cursor.close() # type: ignore[no-untyped-call]
823+
cursor.close()
824824
except Exception as e:
825825
error_msg = str(e).lower()
826826
if any(pattern in error_msg for pattern in ADBC_TABLE_NOT_FOUND_PATTERNS):
@@ -1014,7 +1014,7 @@ def _create_tables(self) -> None:
10141014
cursor.execute(idx_session)
10151015
conn.commit()
10161016
finally:
1017-
cursor.close() # type: ignore[no-untyped-call]
1017+
cursor.close()
10181018

10191019
async def create_tables(self) -> None:
10201020
"""Create tables if they don't exist."""
@@ -1122,7 +1122,7 @@ def _insert_memory_entries(self, entries: "list[MemoryRecord]", owner_id: "objec
11221122
raise
11231123
conn.commit()
11241124
finally:
1125-
cursor.close() # type: ignore[no-untyped-call]
1125+
cursor.close()
11261126

11271127
return inserted_count
11281128

@@ -1161,7 +1161,7 @@ def _search_entries(
11611161
cursor.execute(sql, (app_name, user_id, pattern, effective_limit))
11621162
rows = cursor.fetchall()
11631163
finally:
1164-
cursor.close() # type: ignore[no-untyped-call]
1164+
cursor.close()
11651165
except Exception as exc:
11661166
error_msg = str(exc).lower()
11671167
if any(pattern in error_msg for pattern in ADBC_TABLE_NOT_FOUND_PATTERNS):
@@ -1193,7 +1193,7 @@ def _delete_entries_by_session(self, session_id: str) -> int:
11931193
conn.commit()
11941194
return cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
11951195
finally:
1196-
cursor.close() # type: ignore[no-untyped-call]
1196+
cursor.close()
11971197

11981198
async def delete_entries_by_session(self, session_id: str) -> int:
11991199
"""Delete all memory entries for a specific session."""
@@ -1217,7 +1217,7 @@ def _delete_entries_older_than(self, days: int) -> int:
12171217
conn.commit()
12181218
return cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
12191219
finally:
1220-
cursor.close() # type: ignore[no-untyped-call]
1220+
cursor.close()
12211221

12221222
async def delete_entries_older_than(self, days: int) -> int:
12231223
"""Delete memory entries older than specified days."""
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from sqlspec.adapters.aiomysql._typing import AiomysqlConnection, AiomysqlCursor
2+
from sqlspec.adapters.aiomysql.config import (
3+
AiomysqlConfig,
4+
AiomysqlConnectionParams,
5+
AiomysqlDriverFeatures,
6+
AiomysqlPoolParams,
7+
)
8+
from sqlspec.adapters.aiomysql.core import default_statement_config
9+
from sqlspec.adapters.aiomysql.driver import AiomysqlDriver, AiomysqlExceptionHandler
10+
11+
__all__ = (
12+
"AiomysqlConfig",
13+
"AiomysqlConnection",
14+
"AiomysqlConnectionParams",
15+
"AiomysqlCursor",
16+
"AiomysqlDriver",
17+
"AiomysqlDriverFeatures",
18+
"AiomysqlExceptionHandler",
19+
"AiomysqlPoolParams",
20+
"default_statement_config",
21+
)

0 commit comments

Comments
 (0)