sqlspec 0.25.0__py3-none-any.whl → 0.27.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.

Files changed (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.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
+ store_instance._get_create_sessions_table_sql(), # pyright: ignore[reportPrivateUsage]
118
+ 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) -> "ListSessionsResponse":
123
+ """List all sessions for an app and user.
124
+
125
+ Args:
126
+ app_name: Name of the application.
127
+ user_id: ID of the user.
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