sqlspec 0.26.0__py3-none-any.whl → 0.28.0__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 sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +55 -25
- sqlspec/_typing.py +155 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +880 -0
- sqlspec/adapters/adbc/config.py +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +74 -2
- sqlspec/adapters/adbc/driver.py +226 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +44 -50
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +536 -0
- sqlspec/adapters/aiosqlite/config.py +86 -16
- sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
- sqlspec/adapters/aiosqlite/driver.py +127 -38
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +503 -0
- sqlspec/adapters/asyncmy/config.py +59 -17
- sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
- sqlspec/adapters/asyncmy/driver.py +293 -62
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +460 -0
- sqlspec/adapters/asyncpg/config.py +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +48 -2
- sqlspec/adapters/asyncpg/driver.py +153 -23
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +585 -0
- sqlspec/adapters/bigquery/config.py +36 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +489 -144
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +55 -23
- sqlspec/adapters/duckdb/_types.py +2 -2
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +563 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +225 -44
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +5 -5
- sqlspec/adapters/duckdb/type_converter.py +51 -21
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1628 -0
- sqlspec/adapters/oracledb/config.py +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +475 -86
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +765 -0
- sqlspec/adapters/oracledb/migrations.py +316 -25
- sqlspec/adapters/oracledb/type_converter.py +91 -16
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +483 -0
- sqlspec/adapters/psqlpy/config.py +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
- sqlspec/adapters/psqlpy/driver.py +108 -41
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +40 -11
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +962 -0
- sqlspec/adapters/psycopg/config.py +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +91 -3
- sqlspec/adapters/psycopg/driver.py +200 -78
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +582 -0
- sqlspec/adapters/sqlite/config.py +85 -16
- sqlspec/adapters/sqlite/data_dictionary.py +34 -2
- sqlspec/adapters/sqlite/driver.py +120 -52
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +5 -5
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +91 -58
- sqlspec/builder/_column.py +5 -5
- sqlspec/builder/_ddl.py +98 -89
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +41 -44
- sqlspec/builder/_insert.py +5 -82
- sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +9 -11
- sqlspec/builder/_select.py +1313 -25
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +76 -69
- sqlspec/config.py +331 -62
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +55 -47
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +234 -47
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +32 -31
- sqlspec/core/type_conversion.py +3 -2
- sqlspec/driver/__init__.py +1 -3
- sqlspec/driver/_async.py +183 -160
- sqlspec/driver/_common.py +197 -109
- sqlspec/driver/_sync.py +189 -161
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +70 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +69 -61
- sqlspec/extensions/fastapi/__init__.py +21 -0
- sqlspec/extensions/fastapi/extension.py +331 -0
- sqlspec/extensions/fastapi/providers.py +543 -0
- sqlspec/extensions/flask/__init__.py +36 -0
- sqlspec/extensions/flask/_state.py +71 -0
- sqlspec/extensions/flask/_utils.py +40 -0
- sqlspec/extensions/flask/extension.py +389 -0
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +56 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +349 -224
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/extensions/starlette/__init__.py +10 -0
- sqlspec/extensions/starlette/_state.py +25 -0
- sqlspec/extensions/starlette/_utils.py +52 -0
- sqlspec/extensions/starlette/extension.py +254 -0
- sqlspec/extensions/starlette/middleware.py +154 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/base.py +200 -76
- sqlspec/migrations/commands.py +591 -62
- sqlspec/migrations/context.py +6 -9
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +47 -19
- sqlspec/migrations/runner.py +241 -75
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +106 -36
- sqlspec/storage/_utils.py +85 -0
- sqlspec/storage/backends/fsspec.py +133 -107
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +276 -168
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +30 -84
- sqlspec/utils/__init__.py +25 -4
- sqlspec/utils/arrow_helpers.py +81 -0
- sqlspec/utils/config_resolver.py +6 -6
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +205 -5
- sqlspec/utils/portal.py +311 -0
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +113 -4
- sqlspec/utils/sync_tools.py +36 -22
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +136 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
- sqlspec-0.28.0.dist-info/RECORD +221 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_merge_operations.py +0 -698
- sqlspec/builder/mixins/_order_limit_operations.py +0 -145
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -930
- sqlspec/builder/mixins/_update_operations.py +0 -199
- sqlspec/builder/mixins/_where_clause.py +0 -1298
- sqlspec-0.26.0.dist-info/RECORD +0 -157
- sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Conversion functions between ADK models and database records."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pickle
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from google.adk.events.event import Event
|
|
9
|
+
from google.adk.sessions import Session
|
|
10
|
+
from google.genai import types
|
|
11
|
+
|
|
12
|
+
from sqlspec.extensions.adk._types import EventRecord, SessionRecord
|
|
13
|
+
from sqlspec.utils.logging import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger("extensions.adk.converters")
|
|
16
|
+
|
|
17
|
+
__all__ = ("event_to_record", "record_to_event", "record_to_session", "session_to_record")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def session_to_record(session: "Session") -> SessionRecord:
|
|
21
|
+
"""Convert ADK Session to database record.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
session: ADK Session object.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
SessionRecord for database storage.
|
|
28
|
+
"""
|
|
29
|
+
return SessionRecord(
|
|
30
|
+
id=session.id,
|
|
31
|
+
app_name=session.app_name,
|
|
32
|
+
user_id=session.user_id,
|
|
33
|
+
state=session.state,
|
|
34
|
+
create_time=datetime.now(timezone.utc),
|
|
35
|
+
update_time=datetime.fromtimestamp(session.last_update_time, tz=timezone.utc),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def record_to_session(record: SessionRecord, events: "list[EventRecord]") -> "Session":
|
|
40
|
+
"""Convert database record to ADK Session.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
record: Session database record.
|
|
44
|
+
events: List of event records for this session.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
ADK Session object.
|
|
48
|
+
"""
|
|
49
|
+
event_objects = [record_to_event(event_record) for event_record in events]
|
|
50
|
+
|
|
51
|
+
return Session(
|
|
52
|
+
id=record["id"],
|
|
53
|
+
app_name=record["app_name"],
|
|
54
|
+
user_id=record["user_id"],
|
|
55
|
+
state=record["state"],
|
|
56
|
+
events=event_objects,
|
|
57
|
+
last_update_time=record["update_time"].timestamp(),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def event_to_record(event: "Event", session_id: str, app_name: str, user_id: str) -> EventRecord:
|
|
62
|
+
"""Convert ADK Event to database record.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
event: ADK Event object.
|
|
66
|
+
session_id: ID of the parent session.
|
|
67
|
+
app_name: Name of the application.
|
|
68
|
+
user_id: ID of the user.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
EventRecord for database storage.
|
|
72
|
+
"""
|
|
73
|
+
actions_bytes = pickle.dumps(event.actions)
|
|
74
|
+
|
|
75
|
+
long_running_tool_ids_json = None
|
|
76
|
+
if event.long_running_tool_ids:
|
|
77
|
+
long_running_tool_ids_json = json.dumps(list(event.long_running_tool_ids))
|
|
78
|
+
|
|
79
|
+
content_dict = None
|
|
80
|
+
if event.content:
|
|
81
|
+
content_dict = event.content.model_dump(exclude_none=True, mode="json")
|
|
82
|
+
|
|
83
|
+
grounding_metadata_dict = None
|
|
84
|
+
if event.grounding_metadata:
|
|
85
|
+
grounding_metadata_dict = event.grounding_metadata.model_dump(exclude_none=True, mode="json")
|
|
86
|
+
|
|
87
|
+
custom_metadata_dict = event.custom_metadata
|
|
88
|
+
|
|
89
|
+
return EventRecord(
|
|
90
|
+
id=event.id,
|
|
91
|
+
app_name=app_name,
|
|
92
|
+
user_id=user_id,
|
|
93
|
+
session_id=session_id,
|
|
94
|
+
invocation_id=event.invocation_id,
|
|
95
|
+
author=event.author,
|
|
96
|
+
branch=event.branch,
|
|
97
|
+
actions=actions_bytes,
|
|
98
|
+
long_running_tool_ids_json=long_running_tool_ids_json,
|
|
99
|
+
timestamp=datetime.fromtimestamp(event.timestamp, tz=timezone.utc),
|
|
100
|
+
content=content_dict,
|
|
101
|
+
grounding_metadata=grounding_metadata_dict,
|
|
102
|
+
custom_metadata=custom_metadata_dict,
|
|
103
|
+
partial=event.partial,
|
|
104
|
+
turn_complete=event.turn_complete,
|
|
105
|
+
interrupted=event.interrupted,
|
|
106
|
+
error_code=event.error_code,
|
|
107
|
+
error_message=event.error_message,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def record_to_event(record: "EventRecord") -> "Event":
|
|
112
|
+
"""Convert database record to ADK Event.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
record: Event database record.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
ADK Event object.
|
|
119
|
+
"""
|
|
120
|
+
actions = pickle.loads(record["actions"]) # noqa: S301
|
|
121
|
+
|
|
122
|
+
long_running_tool_ids = None
|
|
123
|
+
if record["long_running_tool_ids_json"]:
|
|
124
|
+
long_running_tool_ids = set(json.loads(record["long_running_tool_ids_json"]))
|
|
125
|
+
|
|
126
|
+
return Event(
|
|
127
|
+
id=record["id"],
|
|
128
|
+
invocation_id=record["invocation_id"],
|
|
129
|
+
author=record["author"],
|
|
130
|
+
branch=record["branch"],
|
|
131
|
+
actions=actions,
|
|
132
|
+
timestamp=record["timestamp"].timestamp(),
|
|
133
|
+
content=_decode_content(record["content"]),
|
|
134
|
+
long_running_tool_ids=long_running_tool_ids,
|
|
135
|
+
partial=record["partial"],
|
|
136
|
+
turn_complete=record["turn_complete"],
|
|
137
|
+
error_code=record["error_code"],
|
|
138
|
+
error_message=record["error_message"],
|
|
139
|
+
interrupted=record["interrupted"],
|
|
140
|
+
grounding_metadata=_decode_grounding_metadata(record["grounding_metadata"]),
|
|
141
|
+
custom_metadata=record["custom_metadata"],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _decode_content(content_dict: "dict[str, Any] | None") -> Any:
|
|
146
|
+
"""Decode content dictionary from database to ADK Content object.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
content_dict: Content dictionary from database.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
ADK Content object or None.
|
|
153
|
+
"""
|
|
154
|
+
if not content_dict:
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
return types.Content.model_validate(content_dict)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _decode_grounding_metadata(grounding_dict: "dict[str, Any] | None") -> Any:
|
|
161
|
+
"""Decode grounding metadata dictionary from database to ADK object.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
grounding_dict: Grounding metadata dictionary from database.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
ADK GroundingMetadata object or None.
|
|
168
|
+
"""
|
|
169
|
+
if not grounding_dict:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
return types.GroundingMetadata.model_validate(grounding_dict)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Create ADK session and events tables migration using store DDL definitions."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
4
|
+
|
|
5
|
+
from sqlspec.exceptions import SQLSpecError
|
|
6
|
+
from sqlspec.utils.logging import get_logger
|
|
7
|
+
from sqlspec.utils.module_loader import import_string
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from sqlspec.extensions.adk.store import BaseAsyncADKStore
|
|
11
|
+
from sqlspec.migrations.context import MigrationContext
|
|
12
|
+
|
|
13
|
+
logger = get_logger("migrations.adk.tables")
|
|
14
|
+
|
|
15
|
+
__all__ = ("down", "up")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_store_class(context: "MigrationContext | None") -> "type[BaseAsyncADKStore]":
|
|
19
|
+
"""Get the appropriate store class based on the config's module path.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
context: Migration context containing config.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Store class matching the config's adapter.
|
|
26
|
+
|
|
27
|
+
Notes:
|
|
28
|
+
Dynamically imports the store class from the config's module path.
|
|
29
|
+
For example, AsyncpgConfig at 'sqlspec.adapters.asyncpg.config'
|
|
30
|
+
maps to AsyncpgADKStore at 'sqlspec.adapters.asyncpg.adk.store.AsyncpgADKStore'.
|
|
31
|
+
"""
|
|
32
|
+
if not context or not context.config:
|
|
33
|
+
_raise_missing_config()
|
|
34
|
+
|
|
35
|
+
config_class = type(context.config)
|
|
36
|
+
config_module = config_class.__module__
|
|
37
|
+
config_name = config_class.__name__
|
|
38
|
+
|
|
39
|
+
if not config_module.startswith("sqlspec.adapters."):
|
|
40
|
+
_raise_unsupported_config(f"{config_module}.{config_name}")
|
|
41
|
+
|
|
42
|
+
adapter_name = config_module.split(".")[2]
|
|
43
|
+
store_class_name = config_name.replace("Config", "ADKStore")
|
|
44
|
+
|
|
45
|
+
store_path = f"sqlspec.adapters.{adapter_name}.adk.store.{store_class_name}"
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
store_class: type[BaseAsyncADKStore] = import_string(store_path)
|
|
49
|
+
except ImportError as e:
|
|
50
|
+
_raise_store_import_failed(store_path, e)
|
|
51
|
+
|
|
52
|
+
return store_class
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _raise_missing_config() -> NoReturn:
|
|
56
|
+
"""Raise error when migration context has no config.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
SQLSpecError: Always raised.
|
|
60
|
+
"""
|
|
61
|
+
msg = "Migration context must have a config to determine store class"
|
|
62
|
+
raise SQLSpecError(msg)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _raise_unsupported_config(config_type: str) -> NoReturn:
|
|
66
|
+
"""Raise error for unsupported config type.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
config_type: The unsupported config type name.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
SQLSpecError: Always raised with config type info.
|
|
73
|
+
"""
|
|
74
|
+
msg = f"Unsupported config type for ADK migration: {config_type}"
|
|
75
|
+
raise SQLSpecError(msg)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _raise_store_import_failed(store_path: str, error: ImportError) -> NoReturn:
|
|
79
|
+
"""Raise error when store class import fails.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
store_path: The import path that failed.
|
|
83
|
+
error: The original import error.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
SQLSpecError: Always raised with import details.
|
|
87
|
+
"""
|
|
88
|
+
msg = f"Failed to import ADK store class from {store_path}: {error}"
|
|
89
|
+
raise SQLSpecError(msg) from error
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def up(context: "MigrationContext | None" = None) -> "list[str]":
|
|
93
|
+
"""Create the ADK session and events tables using store DDL definitions.
|
|
94
|
+
|
|
95
|
+
This migration delegates to the appropriate store class to generate
|
|
96
|
+
dialect-specific DDL. The store classes contain the single source of
|
|
97
|
+
truth for table schemas.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
context: Migration context containing config.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of SQL statements to execute for upgrade.
|
|
104
|
+
|
|
105
|
+
Notes:
|
|
106
|
+
Configuration is read from context.config.extension_config["adk"].
|
|
107
|
+
Supports custom table names and optional owner_id_column for linking
|
|
108
|
+
sessions to owner tables (users, tenants, teams, etc.).
|
|
109
|
+
"""
|
|
110
|
+
if context is None or context.config is None:
|
|
111
|
+
_raise_missing_config()
|
|
112
|
+
|
|
113
|
+
store_class = _get_store_class(context)
|
|
114
|
+
store_instance = store_class(config=context.config)
|
|
115
|
+
|
|
116
|
+
return [
|
|
117
|
+
await store_instance._get_create_sessions_table_sql(), # pyright: ignore[reportPrivateUsage]
|
|
118
|
+
await store_instance._get_create_events_table_sql(), # pyright: ignore[reportPrivateUsage]
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def down(context: "MigrationContext | None" = None) -> "list[str]":
|
|
123
|
+
"""Drop the ADK session and events tables using store DDL definitions.
|
|
124
|
+
|
|
125
|
+
This migration delegates to the appropriate store class to generate
|
|
126
|
+
dialect-specific DROP statements. The store classes contain the single
|
|
127
|
+
source of truth for table schemas.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
context: Migration context containing config.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List of SQL statements to execute for downgrade.
|
|
134
|
+
|
|
135
|
+
Notes:
|
|
136
|
+
Configuration is read from context.config.extension_config["adk"].
|
|
137
|
+
"""
|
|
138
|
+
if context is None or context.config is None:
|
|
139
|
+
_raise_missing_config()
|
|
140
|
+
|
|
141
|
+
store_class = _get_store_class(context)
|
|
142
|
+
store_instance = store_class(config=context.config)
|
|
143
|
+
|
|
144
|
+
return store_instance._get_drop_tables_sql() # pyright: ignore[reportPrivateUsage]
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""SQLSpec-backed session service for Google ADK."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from google.adk.sessions.base_session_service import BaseSessionService, GetSessionConfig, ListSessionsResponse
|
|
8
|
+
|
|
9
|
+
from sqlspec.extensions.adk.converters import event_to_record, record_to_session
|
|
10
|
+
from sqlspec.utils.logging import get_logger
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from google.adk.events.event import Event
|
|
14
|
+
from google.adk.sessions import Session
|
|
15
|
+
|
|
16
|
+
from sqlspec.extensions.adk.store import BaseAsyncADKStore
|
|
17
|
+
|
|
18
|
+
logger = get_logger("extensions.adk.service")
|
|
19
|
+
|
|
20
|
+
__all__ = ("SQLSpecSessionService",)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SQLSpecSessionService(BaseSessionService):
|
|
24
|
+
"""SQLSpec-backed implementation of BaseSessionService.
|
|
25
|
+
|
|
26
|
+
Provides session and event storage using SQLSpec database adapters.
|
|
27
|
+
Delegates all database operations to a store implementation.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
store: Database store implementation (e.g., AsyncpgADKStore).
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
from sqlspec.adapters.asyncpg import AsyncpgConfig
|
|
34
|
+
from sqlspec.adapters.asyncpg.adk.store import AsyncpgADKStore
|
|
35
|
+
from sqlspec.extensions.adk.service import SQLSpecSessionService
|
|
36
|
+
|
|
37
|
+
config = AsyncpgConfig(pool_config={"dsn": "postgresql://..."})
|
|
38
|
+
store = AsyncpgADKStore(config)
|
|
39
|
+
await store.create_tables()
|
|
40
|
+
|
|
41
|
+
service = SQLSpecSessionService(store)
|
|
42
|
+
session = await service.create_session(
|
|
43
|
+
app_name="my_app",
|
|
44
|
+
user_id="user123",
|
|
45
|
+
state={"key": "value"}
|
|
46
|
+
)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, store: "BaseAsyncADKStore") -> None:
|
|
50
|
+
"""Initialize the session service.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
store: Database store implementation.
|
|
54
|
+
"""
|
|
55
|
+
self._store = store
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def store(self) -> "BaseAsyncADKStore":
|
|
59
|
+
"""Return the database store."""
|
|
60
|
+
return self._store
|
|
61
|
+
|
|
62
|
+
async def create_session(
|
|
63
|
+
self, *, app_name: str, user_id: str, state: "dict[str, Any] | None" = None, session_id: "str | None" = None
|
|
64
|
+
) -> "Session":
|
|
65
|
+
"""Create a new session.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
app_name: Name of the application.
|
|
69
|
+
user_id: ID of the user.
|
|
70
|
+
state: Initial state of the session.
|
|
71
|
+
session_id: Client-provided session ID. If None, generates a UUID.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The newly created session.
|
|
75
|
+
"""
|
|
76
|
+
if session_id is None:
|
|
77
|
+
session_id = str(uuid.uuid4())
|
|
78
|
+
|
|
79
|
+
if state is None:
|
|
80
|
+
state = {}
|
|
81
|
+
|
|
82
|
+
record = await self._store.create_session(
|
|
83
|
+
session_id=session_id, app_name=app_name, user_id=user_id, state=state
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return record_to_session(record, events=[])
|
|
87
|
+
|
|
88
|
+
async def get_session(
|
|
89
|
+
self, *, app_name: str, user_id: str, session_id: str, config: "GetSessionConfig | None" = None
|
|
90
|
+
) -> "Session | None":
|
|
91
|
+
"""Get a session by ID.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
app_name: Name of the application.
|
|
95
|
+
user_id: ID of the user.
|
|
96
|
+
session_id: Session identifier.
|
|
97
|
+
config: Configuration for retrieving events.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Session object if found, None otherwise.
|
|
101
|
+
"""
|
|
102
|
+
record = await self._store.get_session(session_id)
|
|
103
|
+
|
|
104
|
+
if not record:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
if record["app_name"] != app_name or record["user_id"] != user_id:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
after_timestamp = None
|
|
111
|
+
limit = None
|
|
112
|
+
|
|
113
|
+
if config:
|
|
114
|
+
if config.after_timestamp:
|
|
115
|
+
after_timestamp = datetime.fromtimestamp(config.after_timestamp, tz=timezone.utc)
|
|
116
|
+
limit = config.num_recent_events
|
|
117
|
+
|
|
118
|
+
events = await self._store.get_events(session_id=session_id, after_timestamp=after_timestamp, limit=limit)
|
|
119
|
+
|
|
120
|
+
return record_to_session(record, events)
|
|
121
|
+
|
|
122
|
+
async def list_sessions(self, *, app_name: str, user_id: str | None = None) -> "ListSessionsResponse":
|
|
123
|
+
"""List all sessions for an app, optionally filtered by user.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
app_name: Name of the application.
|
|
127
|
+
user_id: ID of the user. If None, all sessions for the app are listed.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Response containing list of sessions (without events).
|
|
131
|
+
"""
|
|
132
|
+
records = await self._store.list_sessions(app_name=app_name, user_id=user_id)
|
|
133
|
+
|
|
134
|
+
sessions = [record_to_session(record, events=[]) for record in records]
|
|
135
|
+
|
|
136
|
+
return ListSessionsResponse(sessions=sessions)
|
|
137
|
+
|
|
138
|
+
async def delete_session(self, *, app_name: str, user_id: str, session_id: str) -> None:
|
|
139
|
+
"""Delete a session and all its events.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
app_name: Name of the application.
|
|
143
|
+
user_id: ID of the user.
|
|
144
|
+
session_id: Session identifier.
|
|
145
|
+
"""
|
|
146
|
+
record = await self._store.get_session(session_id)
|
|
147
|
+
|
|
148
|
+
if not record:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
if record["app_name"] != app_name or record["user_id"] != user_id:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
await self._store.delete_session(session_id)
|
|
155
|
+
|
|
156
|
+
async def append_event(self, session: "Session", event: "Event") -> "Event":
|
|
157
|
+
"""Append an event to a session.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
session: Session to append to.
|
|
161
|
+
event: Event to append.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
The appended event.
|
|
165
|
+
"""
|
|
166
|
+
event = await super().append_event(session, event) # pyright: ignore
|
|
167
|
+
|
|
168
|
+
if event.partial:
|
|
169
|
+
return event
|
|
170
|
+
|
|
171
|
+
event_record = event_to_record(
|
|
172
|
+
event=event, session_id=session.id, app_name=session.app_name, user_id=session.user_id
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
await self._store.append_event(event_record)
|
|
176
|
+
|
|
177
|
+
session_record = await self._store.get_session(session.id)
|
|
178
|
+
if session_record:
|
|
179
|
+
session.last_update_time = session_record["update_time"].timestamp()
|
|
180
|
+
|
|
181
|
+
return event
|