sqlspec 0.8.0__tar.gz → 0.9.1__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.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (152) hide show
  1. {sqlspec-0.8.0 → sqlspec-0.9.1}/Makefile +2 -2
  2. {sqlspec-0.8.0 → sqlspec-0.9.1}/PKG-INFO +144 -2
  3. {sqlspec-0.8.0 → sqlspec-0.9.1}/README.md +141 -1
  4. {sqlspec-0.8.0 → sqlspec-0.9.1}/pyproject.toml +31 -4
  5. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/_typing.py +39 -6
  6. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/adbc/__init__.py +2 -2
  7. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/adbc/config.py +34 -11
  8. sqlspec-0.9.1/sqlspec/adapters/adbc/driver.py +524 -0
  9. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/aiosqlite/__init__.py +2 -2
  10. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/aiosqlite/config.py +2 -2
  11. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/aiosqlite/driver.py +164 -42
  12. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/asyncmy/__init__.py +3 -3
  13. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/asyncmy/config.py +11 -12
  14. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/asyncmy/driver.py +161 -37
  15. sqlspec-0.9.1/sqlspec/adapters/asyncpg/__init__.py +9 -0
  16. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/asyncpg/config.py +17 -19
  17. sqlspec-0.9.1/sqlspec/adapters/asyncpg/driver.py +578 -0
  18. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/duckdb/__init__.py +2 -2
  19. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/duckdb/config.py +2 -2
  20. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/duckdb/driver.py +190 -60
  21. sqlspec-0.9.1/sqlspec/adapters/oracledb/__init__.py +16 -0
  22. sqlspec-0.9.1/sqlspec/adapters/oracledb/config/__init__.py +9 -0
  23. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/oracledb/config/_asyncio.py +9 -10
  24. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/oracledb/config/_sync.py +8 -9
  25. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/oracledb/driver.py +384 -45
  26. sqlspec-0.9.1/sqlspec/adapters/psqlpy/config.py +250 -0
  27. sqlspec-0.9.1/sqlspec/adapters/psqlpy/driver.py +481 -0
  28. sqlspec-0.9.1/sqlspec/adapters/psycopg/__init__.py +16 -0
  29. sqlspec-0.9.1/sqlspec/adapters/psycopg/config/__init__.py +9 -0
  30. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/psycopg/config/_async.py +12 -12
  31. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/psycopg/config/_sync.py +13 -13
  32. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/psycopg/driver.py +432 -222
  33. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/sqlite/__init__.py +2 -2
  34. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/sqlite/config.py +2 -2
  35. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/sqlite/driver.py +176 -72
  36. sqlspec-0.9.1/sqlspec/base.py +1133 -0
  37. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/exceptions.py +30 -0
  38. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/extensions/litestar/config.py +6 -0
  39. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/extensions/litestar/handlers.py +25 -0
  40. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/extensions/litestar/plugin.py +8 -1
  41. sqlspec-0.9.1/sqlspec/statement.py +373 -0
  42. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/typing.py +10 -1
  43. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/fixtures/sql_utils.py +4 -2
  44. sqlspec-0.9.1/tests/integration/__init__.py +3 -0
  45. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_adbc/__init__.py +4 -0
  46. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_adbc/conftest.py +3 -3
  47. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_adbc/test_connection.py +4 -2
  48. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_adbc/test_driver_bigquery.py +54 -17
  49. sqlspec-0.9.1/tests/integration/test_adapters/test_adbc/test_driver_duckdb.py +444 -0
  50. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_adbc/test_driver_postgres.py +37 -3
  51. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_adbc/test_driver_sqlite.py +125 -17
  52. sqlspec-0.9.1/tests/integration/test_adapters/test_aiosqlite/__init__.py +5 -0
  53. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_aiosqlite/test_connection.py +3 -2
  54. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_aiosqlite/test_driver.py +11 -24
  55. sqlspec-0.9.1/tests/integration/test_adapters/test_asyncmy/__init__.py +3 -0
  56. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_asyncmy/test_connection.py +6 -5
  57. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_asyncmy/test_driver.py +15 -10
  58. sqlspec-0.9.1/tests/integration/test_adapters/test_asyncpg/test_connection.py +42 -0
  59. sqlspec-0.9.1/tests/integration/test_adapters/test_asyncpg/test_driver.py +275 -0
  60. sqlspec-0.9.1/tests/integration/test_adapters/test_duckdb/__init__.py +5 -0
  61. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_duckdb/test_connection.py +5 -2
  62. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_duckdb/test_driver.py +43 -9
  63. sqlspec-0.9.1/tests/integration/test_adapters/test_oracledb/__init__.py +5 -0
  64. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_oracledb/test_connection.py +11 -11
  65. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_oracledb/test_driver_async.py +59 -24
  66. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_oracledb/test_driver_sync.py +55 -21
  67. sqlspec-0.9.1/tests/integration/test_adapters/test_psqlpy/test_connection.py +66 -0
  68. sqlspec-0.9.1/tests/integration/test_adapters/test_psqlpy/test_driver.py +191 -0
  69. sqlspec-0.9.1/tests/integration/test_adapters/test_psycopg/__init__.py +5 -0
  70. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_psycopg/test_connection.py +36 -37
  71. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_psycopg/test_driver.py +48 -41
  72. sqlspec-0.9.1/tests/integration/test_adapters/test_sqlite/__init__.py +5 -0
  73. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_sqlite/test_connection.py +5 -2
  74. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/test_sqlite/test_driver.py +6 -2
  75. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_adbc/test_config.py +5 -7
  76. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_aiosqlite/test_config.py +7 -7
  77. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_asyncmy/test_config.py +8 -8
  78. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_asyncpg/test_config.py +9 -9
  79. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_duckdb/test_config.py +6 -6
  80. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_oracledb/test_async_config.py +8 -8
  81. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_oracledb/test_sync_config.py +8 -8
  82. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_psycopg/test_async_config.py +5 -5
  83. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_psycopg/test_sync_config.py +5 -5
  84. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_sqlite/test_config.py +7 -7
  85. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_base.py +24 -0
  86. {sqlspec-0.8.0 → sqlspec-0.9.1}/uv.lock +113 -20
  87. sqlspec-0.8.0/sqlspec/adapters/adbc/driver.py +0 -333
  88. sqlspec-0.8.0/sqlspec/adapters/asyncpg/__init__.py +0 -9
  89. sqlspec-0.8.0/sqlspec/adapters/asyncpg/driver.py +0 -288
  90. sqlspec-0.8.0/sqlspec/adapters/oracledb/__init__.py +0 -16
  91. sqlspec-0.8.0/sqlspec/adapters/oracledb/config/__init__.py +0 -9
  92. sqlspec-0.8.0/sqlspec/adapters/psycopg/__init__.py +0 -11
  93. sqlspec-0.8.0/sqlspec/adapters/psycopg/config/__init__.py +0 -9
  94. sqlspec-0.8.0/sqlspec/base.py +0 -607
  95. sqlspec-0.8.0/tests/integration/test_adapters/test_adbc/test_driver_duckdb.py +0 -228
  96. sqlspec-0.8.0/tests/integration/test_adapters/test_aiosqlite/__init__.py +0 -1
  97. sqlspec-0.8.0/tests/integration/test_adapters/test_aiosqlite/conftest.py +0 -16
  98. sqlspec-0.8.0/tests/integration/test_adapters/test_duckdb/__init__.py +0 -1
  99. sqlspec-0.8.0/tests/integration/test_adapters/test_oracledb/__init__.py +0 -1
  100. sqlspec-0.8.0/tests/integration/test_adapters/test_psycopg/__init__.py +0 -1
  101. sqlspec-0.8.0/tests/integration/test_adapters/test_sqlite/__init__.py +0 -1
  102. {sqlspec-0.8.0 → sqlspec-0.9.1}/.gitignore +0 -0
  103. {sqlspec-0.8.0 → sqlspec-0.9.1}/.pre-commit-config.yaml +0 -0
  104. {sqlspec-0.8.0 → sqlspec-0.9.1}/CONTRIBUTING.rst +0 -0
  105. {sqlspec-0.8.0 → sqlspec-0.9.1}/LICENSE +0 -0
  106. {sqlspec-0.8.0 → sqlspec-0.9.1}/NOTICE +0 -0
  107. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/__init__.py +0 -0
  108. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/__metadata__.py +0 -0
  109. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/_serialization.py +0 -0
  110. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/__init__.py +0 -0
  111. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/oracledb/config/_common.py +0 -0
  112. {sqlspec-0.8.0/sqlspec/extensions → sqlspec-0.9.1/sqlspec/adapters/psqlpy}/__init__.py +0 -0
  113. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/adapters/psycopg/config/_common.py +0 -0
  114. {sqlspec-0.8.0/tests → sqlspec-0.9.1/sqlspec/extensions}/__init__.py +0 -0
  115. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/extensions/litestar/__init__.py +0 -0
  116. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/extensions/litestar/_utils.py +0 -0
  117. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/filters.py +0 -0
  118. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/py.typed +0 -0
  119. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/utils/__init__.py +0 -0
  120. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/utils/deprecation.py +0 -0
  121. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/utils/fixtures.py +0 -0
  122. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/utils/module_loader.py +0 -0
  123. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/utils/sync_tools.py +0 -0
  124. {sqlspec-0.8.0 → sqlspec-0.9.1}/sqlspec/utils/text.py +0 -0
  125. {sqlspec-0.8.0/tests/integration → sqlspec-0.9.1/tests}/__init__.py +0 -0
  126. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/conftest.py +0 -0
  127. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/fixtures/__init__.py +0 -0
  128. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/fixtures/example_usage.py +0 -0
  129. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/integration/test_adapters/__init__.py +0 -0
  130. {sqlspec-0.8.0/tests/integration/test_adapters/test_asyncmy → sqlspec-0.9.1/tests/integration/test_adapters/test_asyncpg}/__init__.py +0 -0
  131. {sqlspec-0.8.0/tests/unit → sqlspec-0.9.1/tests/integration/test_adapters/test_psqlpy}/__init__.py +0 -0
  132. {sqlspec-0.8.0/tests/unit/test_adapters → sqlspec-0.9.1/tests/unit}/__init__.py +0 -0
  133. {sqlspec-0.8.0/tests/unit/test_adapters/test_duckdb → sqlspec-0.9.1/tests/unit/test_adapters}/__init__.py +0 -0
  134. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_adbc/__init__.py +0 -0
  135. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_aiosqlite/__init__.py +0 -0
  136. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_asyncmy/__init__.py +0 -0
  137. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_asyncpg/__init__.py +0 -0
  138. {sqlspec-0.8.0/tests/unit/test_utils → sqlspec-0.9.1/tests/unit/test_adapters/test_duckdb}/__init__.py +0 -0
  139. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_oracledb/__init__.py +0 -0
  140. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_psycopg/__init__.py +0 -0
  141. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_adapters/test_sqlite/__init__.py +0 -0
  142. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_typing.py +0 -0
  143. {sqlspec-0.8.0/tools → sqlspec-0.9.1/tests/unit/test_utils}/__init__.py +0 -0
  144. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_utils/test_module_loader.py +0 -0
  145. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_utils/test_sync_tools.py +0 -0
  146. {sqlspec-0.8.0 → sqlspec-0.9.1}/tests/unit/test_utils/test_text.py +0 -0
  147. /sqlspec-0.8.0/tests/integration/test_adapters/test_psycopg/conftest.py → /sqlspec-0.9.1/tools/__init__.py +0 -0
  148. {sqlspec-0.8.0 → sqlspec-0.9.1}/tools/build_docs.py +0 -0
  149. {sqlspec-0.8.0 → sqlspec-0.9.1}/tools/pypi_readme.py +0 -0
  150. {sqlspec-0.8.0 → sqlspec-0.9.1}/tools/sphinx_ext/__init__.py +0 -0
  151. {sqlspec-0.8.0 → sqlspec-0.9.1}/tools/sphinx_ext/changelog.py +0 -0
  152. {sqlspec-0.8.0 → sqlspec-0.9.1}/tools/sphinx_ext/missing_references.py +0 -0
@@ -118,7 +118,7 @@ clean: ## Cleanup temporary build a
118
118
  .PHONY: test
119
119
  test: ## Run the tests
120
120
  @echo "${INFO} Running test cases... 🧪"
121
- @uv run pytest tests
121
+ @uv run pytest -n 2 --dist=loadgroup tests
122
122
  @echo "${OK} Tests complete ✨"
123
123
 
124
124
  .PHONY: test-all
@@ -128,7 +128,7 @@ test-all: tests ## Run all tests
128
128
  .PHONY: coverage
129
129
  coverage: ## Run tests with coverage report
130
130
  @echo "${INFO} Running tests with coverage... 📊"
131
- @uv run pytest --cov -n auto --quiet
131
+ @uv run pytest --cov -n 2 --dist=loadgroup --quiet
132
132
  @uv run coverage html >/dev/null 2>&1
133
133
  @uv run coverage xml >/dev/null 2>&1
134
134
  @echo "${OK} Coverage report generated ✨"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlspec
3
- Version: 0.8.0
3
+ Version: 0.9.1
4
4
  Summary: SQL Experiments in Python
5
5
  Author-email: Cody Fincher <cody@litestar.dev>
6
6
  Maintainer-email: Litestar Developers <hello@litestar.dev>
@@ -42,6 +42,8 @@ Provides-Extra: orjson
42
42
  Requires-Dist: orjson; extra == 'orjson'
43
43
  Provides-Extra: performance
44
44
  Requires-Dist: sqlglot[rs]; extra == 'performance'
45
+ Provides-Extra: psqlpy
46
+ Requires-Dist: psqlpy; extra == 'psqlpy'
45
47
  Provides-Extra: psycopg
46
48
  Requires-Dist: psycopg[binary,pool]; extra == 'psycopg'
47
49
  Provides-Extra: pydantic
@@ -81,6 +83,145 @@ SQLSpec is an experimental Python library designed to streamline and modernize y
81
83
 
82
84
  SQLSpec is a work in progress. While it offers a solid foundation for modern SQL interactions, it does not yet include every feature you might find in a mature ORM or database toolkit. The focus is on building a robust, flexible core that can be extended over time.
83
85
 
86
+ ## Examples
87
+
88
+ We've talked about what SQLSpec is not, so let's look at what it can do.
89
+
90
+ These are just a few of the examples that demonstrate SQLSpec's flexibility and each of the bundled adapters offer the same config and driver interfaces.
91
+
92
+ ### DuckDB LLM
93
+
94
+ This is a quick implementation using some of the built in Secret and Extension management features of SQLSpec's DuckDB integration.
95
+
96
+ It allows you to communicate with any compatible OpenAPI conversations endpoint (such as Ollama). This examples:
97
+
98
+ - auto installs the `open_prompt` DuckDB extensions
99
+ - automatically creates the correct `open_prompt` comptaible secret required to use the extension
100
+
101
+ ```py
102
+ # /// script
103
+ # dependencies = [
104
+ # "sqlspec[duckdb,performance]",
105
+ # ]
106
+ # ///
107
+ import os
108
+
109
+ from sqlspec import SQLSpec
110
+ from sqlspec.adapters.duckdb import DuckDBConfig
111
+ from pydantic import BaseModel
112
+
113
+ class ChatMessage(BaseModel):
114
+ message: str
115
+
116
+ sql = SQLSpec()
117
+ etl_config = sql.add_config(
118
+ DuckDBConfig(
119
+ extensions=[{"name": "open_prompt"}],
120
+ secrets=[
121
+ {
122
+ "secret_type": "open_prompt",
123
+ "name": "open_prompt",
124
+ "value": {
125
+ "api_url": "http://127.0.0.1:11434/v1/chat/completions",
126
+ "model_name": "gemma3:1b",
127
+ "api_timeout": "120",
128
+ },
129
+ }
130
+ ],
131
+ )
132
+ )
133
+ with sql.provide_session(etl_config) as session:
134
+ result = session.select_one("SELECT open_prompt(?)", data.message, schema_type=ChatMessage)
135
+ print(result) # result is a ChatMessage pydantic model
136
+ ```
137
+
138
+ ### DuckDB Gemini Embeddings
139
+
140
+ In this example, we are again using DuckDB. However, we are going to use the built in to call the Google Gemini embeddings service directly from the database.
141
+
142
+ This example will
143
+
144
+ - auto installs the `http_client` and `vss` (vector similarity search) DuckDB extensions
145
+ - when a connection is created, it ensures that the `generate_embeddings` macro exists in the DuckDB database.
146
+ - Execute a simple query to call the Google API
147
+
148
+ ```py
149
+ # /// script
150
+ # dependencies = [
151
+ # "sqlspec[duckdb,performance]",
152
+ # ]
153
+ # ///
154
+ import os
155
+
156
+ from sqlspec import SQLSpec
157
+ from sqlspec.adapters.duckdb import DuckDBConfig
158
+
159
+ EMBEDDING_MODEL = "gemini-embedding-exp-03-07"
160
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
161
+ API_URL = (
162
+ f"https://generativelanguage.googleapis.com/v1beta/models/{EMBEDDING_MODEL}:embedContent?key=${GOOGLE_API_KEY}"
163
+ )
164
+
165
+ sql = SQLSpec()
166
+ etl_config = sql.add_config(
167
+ DuckDBConfig(
168
+ extensions=[{"name": "vss"}, {"name": "http_client"}],
169
+ on_connection_create=lambda connection: connection.execute(f"""
170
+ CREATE IF NOT EXISTS MACRO generate_embedding(q) AS (
171
+ WITH __request AS (
172
+ SELECT http_post(
173
+ '{API_URL}',
174
+ headers => MAP {{
175
+ 'accept': 'application/json',
176
+ }},
177
+ params => MAP {{
178
+ 'model': 'models/{EMBEDDING_MODEL}',
179
+ 'parts': [{{ 'text': q }}],
180
+ 'taskType': 'SEMANTIC_SIMILARITY'
181
+ }}
182
+ ) AS response
183
+ )
184
+ SELECT *
185
+ FROM __request,
186
+ );
187
+ """),
188
+ )
189
+ )
190
+ with sql.provide_session(etl_config) as session:
191
+ result = session.select_one("SELECT generate_embedding('example text')")
192
+ print(result) # result is a dictionary when `schema_type` is omitted.
193
+ ```
194
+
195
+ ### Basic Litestar Integration
196
+
197
+ In this example we are going to demonstrate how to create a basic configuration that integrates into Litestar.
198
+
199
+ ```py
200
+ # /// script
201
+ # dependencies = [
202
+ # "sqlspec[aiosqlite]",
203
+ # "litestar[standard]",
204
+ # ]
205
+ # ///
206
+
207
+ from aiosqlite import Connection
208
+ from litestar import Litestar, get
209
+
210
+ from sqlspec.adapters.aiosqlite import AiosqliteConfig, AiosqliteDriver
211
+ from sqlspec.extensions.litestar import SQLSpec
212
+
213
+
214
+ @get("/")
215
+ async def simple_sqlite(db_session: AiosqliteDriver) -> dict[str, str]:
216
+ return await db_session.select_one("SELECT 'Hello, world!' AS greeting")
217
+
218
+
219
+ sqlspec = SQLSpec(config=DatabaseConfig(
220
+ config=[AiosqliteConfig(), commit_mode="autocommit")],
221
+ )
222
+ app = Litestar(route_handlers=[simple_sqlite], plugins=[sqlspec])
223
+ ```
224
+
84
225
  ## Inspiration and Future Direction
85
226
 
86
227
  SQLSpec originally drew inspiration from features found in the `aiosql` library. This is a great library for working with and executed SQL stored in files. It's unclear how much of an overlap there will be between the two libraries, but it's possible that some features will be contributed back to `aiosql` where appropriate.
@@ -102,6 +243,7 @@ This list is not final. If you have a driver you'd like to see added, please ope
102
243
  | [`asyncpg`](https://magicstack.github.io/asyncpg/current/) | PostgreSQL | Async | ✅ |
103
244
  | [`psycopg`](https://www.psycopg.org/) | PostgreSQL | Sync | ✅ |
104
245
  | [`psycopg`](https://www.psycopg.org/) | PostgreSQL | Async | ✅ |
246
+ | [`psqlpy`](https://psqlpy-python.github.io/) | PostgreSQL | Async | ✅ |
105
247
  | [`aiosqlite`](https://github.com/omnilib/aiosqlite) | SQLite | Async | ✅ |
106
248
  | `sqlite3` | SQLite | Sync | ✅ |
107
249
  | [`oracledb`](https://oracle.github.io/python-oracledb/) | Oracle | Async | ✅ |
@@ -118,7 +260,7 @@ This list is not final. If you have a driver you'd like to see added, please ope
118
260
  - `sqlspec/`:
119
261
  - `adapters/`: Contains all database drivers and associated configuration.
120
262
  - `extensions/`:
121
- - `litestar/`: Future home of `litestar` integration.
263
+ - `litestar/`: Litestar framework integration
122
264
  - `fastapi/`: Future home of `fastapi` integration.
123
265
  - `flask/`: Future home of `flask` integration.
124
266
  - `*/`: Future home of your favorite framework integration 🔌 ✨
@@ -22,6 +22,145 @@ SQLSpec is an experimental Python library designed to streamline and modernize y
22
22
 
23
23
  SQLSpec is a work in progress. While it offers a solid foundation for modern SQL interactions, it does not yet include every feature you might find in a mature ORM or database toolkit. The focus is on building a robust, flexible core that can be extended over time.
24
24
 
25
+ ## Examples
26
+
27
+ We've talked about what SQLSpec is not, so let's look at what it can do.
28
+
29
+ These are just a few of the examples that demonstrate SQLSpec's flexibility and each of the bundled adapters offer the same config and driver interfaces.
30
+
31
+ ### DuckDB LLM
32
+
33
+ This is a quick implementation using some of the built in Secret and Extension management features of SQLSpec's DuckDB integration.
34
+
35
+ It allows you to communicate with any compatible OpenAPI conversations endpoint (such as Ollama). This examples:
36
+
37
+ - auto installs the `open_prompt` DuckDB extensions
38
+ - automatically creates the correct `open_prompt` comptaible secret required to use the extension
39
+
40
+ ```py
41
+ # /// script
42
+ # dependencies = [
43
+ # "sqlspec[duckdb,performance]",
44
+ # ]
45
+ # ///
46
+ import os
47
+
48
+ from sqlspec import SQLSpec
49
+ from sqlspec.adapters.duckdb import DuckDBConfig
50
+ from pydantic import BaseModel
51
+
52
+ class ChatMessage(BaseModel):
53
+ message: str
54
+
55
+ sql = SQLSpec()
56
+ etl_config = sql.add_config(
57
+ DuckDBConfig(
58
+ extensions=[{"name": "open_prompt"}],
59
+ secrets=[
60
+ {
61
+ "secret_type": "open_prompt",
62
+ "name": "open_prompt",
63
+ "value": {
64
+ "api_url": "http://127.0.0.1:11434/v1/chat/completions",
65
+ "model_name": "gemma3:1b",
66
+ "api_timeout": "120",
67
+ },
68
+ }
69
+ ],
70
+ )
71
+ )
72
+ with sql.provide_session(etl_config) as session:
73
+ result = session.select_one("SELECT open_prompt(?)", data.message, schema_type=ChatMessage)
74
+ print(result) # result is a ChatMessage pydantic model
75
+ ```
76
+
77
+ ### DuckDB Gemini Embeddings
78
+
79
+ In this example, we are again using DuckDB. However, we are going to use the built in to call the Google Gemini embeddings service directly from the database.
80
+
81
+ This example will
82
+
83
+ - auto installs the `http_client` and `vss` (vector similarity search) DuckDB extensions
84
+ - when a connection is created, it ensures that the `generate_embeddings` macro exists in the DuckDB database.
85
+ - Execute a simple query to call the Google API
86
+
87
+ ```py
88
+ # /// script
89
+ # dependencies = [
90
+ # "sqlspec[duckdb,performance]",
91
+ # ]
92
+ # ///
93
+ import os
94
+
95
+ from sqlspec import SQLSpec
96
+ from sqlspec.adapters.duckdb import DuckDBConfig
97
+
98
+ EMBEDDING_MODEL = "gemini-embedding-exp-03-07"
99
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
100
+ API_URL = (
101
+ f"https://generativelanguage.googleapis.com/v1beta/models/{EMBEDDING_MODEL}:embedContent?key=${GOOGLE_API_KEY}"
102
+ )
103
+
104
+ sql = SQLSpec()
105
+ etl_config = sql.add_config(
106
+ DuckDBConfig(
107
+ extensions=[{"name": "vss"}, {"name": "http_client"}],
108
+ on_connection_create=lambda connection: connection.execute(f"""
109
+ CREATE IF NOT EXISTS MACRO generate_embedding(q) AS (
110
+ WITH __request AS (
111
+ SELECT http_post(
112
+ '{API_URL}',
113
+ headers => MAP {{
114
+ 'accept': 'application/json',
115
+ }},
116
+ params => MAP {{
117
+ 'model': 'models/{EMBEDDING_MODEL}',
118
+ 'parts': [{{ 'text': q }}],
119
+ 'taskType': 'SEMANTIC_SIMILARITY'
120
+ }}
121
+ ) AS response
122
+ )
123
+ SELECT *
124
+ FROM __request,
125
+ );
126
+ """),
127
+ )
128
+ )
129
+ with sql.provide_session(etl_config) as session:
130
+ result = session.select_one("SELECT generate_embedding('example text')")
131
+ print(result) # result is a dictionary when `schema_type` is omitted.
132
+ ```
133
+
134
+ ### Basic Litestar Integration
135
+
136
+ In this example we are going to demonstrate how to create a basic configuration that integrates into Litestar.
137
+
138
+ ```py
139
+ # /// script
140
+ # dependencies = [
141
+ # "sqlspec[aiosqlite]",
142
+ # "litestar[standard]",
143
+ # ]
144
+ # ///
145
+
146
+ from aiosqlite import Connection
147
+ from litestar import Litestar, get
148
+
149
+ from sqlspec.adapters.aiosqlite import AiosqliteConfig, AiosqliteDriver
150
+ from sqlspec.extensions.litestar import SQLSpec
151
+
152
+
153
+ @get("/")
154
+ async def simple_sqlite(db_session: AiosqliteDriver) -> dict[str, str]:
155
+ return await db_session.select_one("SELECT 'Hello, world!' AS greeting")
156
+
157
+
158
+ sqlspec = SQLSpec(config=DatabaseConfig(
159
+ config=[AiosqliteConfig(), commit_mode="autocommit")],
160
+ )
161
+ app = Litestar(route_handlers=[simple_sqlite], plugins=[sqlspec])
162
+ ```
163
+
25
164
  ## Inspiration and Future Direction
26
165
 
27
166
  SQLSpec originally drew inspiration from features found in the `aiosql` library. This is a great library for working with and executed SQL stored in files. It's unclear how much of an overlap there will be between the two libraries, but it's possible that some features will be contributed back to `aiosql` where appropriate.
@@ -43,6 +182,7 @@ This list is not final. If you have a driver you'd like to see added, please ope
43
182
  | [`asyncpg`](https://magicstack.github.io/asyncpg/current/) | PostgreSQL | Async | ✅ |
44
183
  | [`psycopg`](https://www.psycopg.org/) | PostgreSQL | Sync | ✅ |
45
184
  | [`psycopg`](https://www.psycopg.org/) | PostgreSQL | Async | ✅ |
185
+ | [`psqlpy`](https://psqlpy-python.github.io/) | PostgreSQL | Async | ✅ |
46
186
  | [`aiosqlite`](https://github.com/omnilib/aiosqlite) | SQLite | Async | ✅ |
47
187
  | `sqlite3` | SQLite | Sync | ✅ |
48
188
  | [`oracledb`](https://oracle.github.io/python-oracledb/) | Oracle | Async | ✅ |
@@ -59,7 +199,7 @@ This list is not final. If you have a driver you'd like to see added, please ope
59
199
  - `sqlspec/`:
60
200
  - `adapters/`: Contains all database drivers and associated configuration.
61
201
  - `extensions/`:
62
- - `litestar/`: Future home of `litestar` integration.
202
+ - `litestar/`: Litestar framework integration
63
203
  - `fastapi/`: Future home of `fastapi` integration.
64
204
  - `flask/`: Future home of `flask` integration.
65
205
  - `*/`: Future home of your favorite framework integration 🔌 ✨
@@ -7,7 +7,7 @@ maintainers = [{ name = "Litestar Developers", email = "hello@litestar.dev" }]
7
7
  name = "sqlspec"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.9, <4.0"
10
- version = "0.8.0"
10
+ version = "0.9.1"
11
11
 
12
12
  [project.optional-dependencies]
13
13
  adbc = ["adbc_driver_manager", "pyarrow"]
@@ -25,6 +25,7 @@ nanoid = ["fastnanoid>=0.4.1"]
25
25
  oracledb = ["oracledb"]
26
26
  orjson = ["orjson"]
27
27
  performance = ["sqlglot[rs]"]
28
+ psqlpy = ["psqlpy"]
28
29
  psycopg = ["psycopg[binary,pool]"]
29
30
  pydantic = ["pydantic", "pydantic-extra-types"]
30
31
  pymssql = ["pymssql"]
@@ -108,7 +109,7 @@ packages = ["sqlspec"]
108
109
  allow_dirty = true
109
110
  commit = false
110
111
  commit_args = "--no-verify"
111
- current_version = "0.8.0"
112
+ current_version = "0.9.1"
112
113
  ignore_missing_files = false
113
114
  ignore_missing_version = false
114
115
  message = "chore(release): bump to v{new_version}"
@@ -175,7 +176,7 @@ exclude_lines = [
175
176
  ]
176
177
 
177
178
  [tool.pytest.ini_options]
178
- addopts = "-ra -q --doctest-glob='*.md' --strict-markers --strict-config"
179
+ addopts = ["-q", "-ra"]
179
180
  asyncio_default_fixture_loop_scope = "function"
180
181
  asyncio_mode = "auto"
181
182
  filterwarnings = [
@@ -189,8 +190,32 @@ filterwarnings = [
189
190
  "ignore::DeprecationWarning:websockets.connection",
190
191
  "ignore::DeprecationWarning:websockets.legacy",
191
192
  ]
193
+ markers = [
194
+ "integration: marks tests that require an external database",
195
+ "postgres: marks tests specific to PostgreSQL",
196
+ "duckdb: marks tests specific to DuckDB",
197
+ "sqlite: marks tests specific to SQLite",
198
+ "bigquery: marks tests specific to Google BigQuery",
199
+ "mysql: marks tests specific to MySQL",
200
+ "oracle: marks tests specific to Oracle",
201
+ "spanner: marks tests specific to Google Cloud Spanner",
202
+ "mssql: marks tests specific to Microsoft SQL Server",
203
+ # Driver markers
204
+ "adbc: marks tests using ADBC drivers",
205
+ "aioodbc: marks tests using aioodbc",
206
+ "aiosqlite: marks tests using aiosqlite",
207
+ "asyncmy: marks tests using asyncmy",
208
+ "asyncpg: marks tests using asyncpg",
209
+ "duckdb_driver: marks tests using the duckdb driver",
210
+ "google_bigquery: marks tests using google-cloud-bigquery",
211
+ "google_spanner: marks tests using google-cloud-spanner",
212
+ "oracledb: marks tests using oracledb",
213
+ "psycopg: marks tests using psycopg",
214
+ "pymssql: marks tests using pymssql",
215
+ "pymysql: marks tests using pymysql",
216
+ "psqlpy: marks tests using psqlpy",
217
+ ]
192
218
  testpaths = ["tests"]
193
- xfail_strict = true
194
219
 
195
220
  [tool.mypy]
196
221
  packages = ["sqlspec", "tests"]
@@ -220,6 +245,8 @@ module = [
220
245
  "uvloop.*",
221
246
  "asyncmy",
222
247
  "asyncmy.*",
248
+ "pyarrow",
249
+ "pyarrow.*",
223
250
  ]
224
251
 
225
252
  [tool.pyright]
@@ -1,8 +1,10 @@
1
+ # ruff: noqa: RUF100, PLR0913, A002, DOC201, PLR6301
1
2
  """This is a simple wrapper around a few important classes in each library.
2
3
 
3
4
  This is used to ensure compatibility when one or more of the libraries are installed.
4
5
  """
5
6
 
7
+ from collections.abc import Iterable, Mapping
6
8
  from enum import Enum
7
9
  from typing import (
8
10
  Any,
@@ -96,7 +98,7 @@ except ImportError:
96
98
 
97
99
  def validate_python(
98
100
  self,
99
- object: Any, # noqa: A002
101
+ object: Any,
100
102
  /,
101
103
  *,
102
104
  strict: "Optional[bool]" = None,
@@ -127,10 +129,7 @@ try:
127
129
  except ImportError:
128
130
  import enum
129
131
  from collections.abc import Iterable
130
- from typing import TYPE_CHECKING, Callable, Optional, Union
131
-
132
- if TYPE_CHECKING:
133
- from collections.abc import Iterable
132
+ from typing import Callable, Optional, Union
134
133
 
135
134
  @dataclass_transform()
136
135
  @runtime_checkable
@@ -174,7 +173,6 @@ except ImportError:
174
173
  """Placeholder init"""
175
174
 
176
175
  def create_instance(self, **kwargs: Any) -> "T":
177
- """Placeholder implementation"""
178
176
  return cast("T", kwargs)
179
177
 
180
178
  def update_instance(self, instance: "T", **kwargs: Any) -> "T":
@@ -198,11 +196,46 @@ EmptyType = Union[Literal[EmptyEnum.EMPTY], UnsetType]
198
196
  Empty: Final = EmptyEnum.EMPTY
199
197
 
200
198
 
199
+ try:
200
+ from pyarrow import Table as ArrowTable
201
+
202
+ PYARROW_INSTALLED = True
203
+ except ImportError:
204
+
205
+ @runtime_checkable
206
+ class ArrowTable(Protocol): # type: ignore[no-redef]
207
+ """Placeholder Implementation"""
208
+
209
+ def to_batches(self, batch_size: int) -> Any: ...
210
+ def num_rows(self) -> int: ...
211
+ def num_columns(self) -> int: ...
212
+ def to_pydict(self) -> dict[str, Any]: ...
213
+ def to_string(self) -> str: ...
214
+ def from_arrays(
215
+ self,
216
+ arrays: list[Any],
217
+ names: "Optional[list[str]]" = None,
218
+ schema: "Optional[Any]" = None,
219
+ metadata: "Optional[Mapping[str, Any]]" = None,
220
+ ) -> Any: ...
221
+ def from_pydict(
222
+ self,
223
+ mapping: dict[str, Any],
224
+ schema: "Optional[Any]" = None,
225
+ metadata: "Optional[Mapping[str, Any]]" = None,
226
+ ) -> Any: ...
227
+ def from_batches(self, batches: Iterable[Any], schema: Optional[Any] = None) -> Any: ...
228
+
229
+ PYARROW_INSTALLED = False # pyright: ignore[reportConstantRedefinition]
230
+
231
+
201
232
  __all__ = (
202
233
  "LITESTAR_INSTALLED",
203
234
  "MSGSPEC_INSTALLED",
235
+ "PYARROW_INSTALLED",
204
236
  "PYDANTIC_INSTALLED",
205
237
  "UNSET",
238
+ "ArrowTable",
206
239
  "BaseModel",
207
240
  "DTOData",
208
241
  "DataclassProtocol",
@@ -1,7 +1,7 @@
1
- from sqlspec.adapters.adbc.config import Adbc
1
+ from sqlspec.adapters.adbc.config import AdbcConfig
2
2
  from sqlspec.adapters.adbc.driver import AdbcDriver
3
3
 
4
4
  __all__ = (
5
- "Adbc",
5
+ "AdbcConfig",
6
6
  "AdbcDriver",
7
7
  )
@@ -14,11 +14,11 @@ if TYPE_CHECKING:
14
14
  from collections.abc import Generator
15
15
 
16
16
 
17
- __all__ = ("Adbc",)
17
+ __all__ = ("AdbcConfig",)
18
18
 
19
19
 
20
20
  @dataclass
21
- class Adbc(NoPoolSyncConfig["Connection", "AdbcDriver"]):
21
+ class AdbcConfig(NoPoolSyncConfig["Connection", "AdbcDriver"]):
22
22
  """Configuration for ADBC connections.
23
23
 
24
24
  This class provides configuration options for ADBC database connections using the
@@ -55,17 +55,41 @@ class Adbc(NoPoolSyncConfig["Connection", "AdbcDriver"]):
55
55
  """
56
56
 
57
57
  if isinstance(self.driver_name, str):
58
- if self.driver_name != "adbc_driver_sqlite.dbapi.connect" and "sqlite" in self.driver_name:
58
+ if self.driver_name != "adbc_driver_sqlite.dbapi.connect" and self.driver_name in {
59
+ "sqlite",
60
+ "sqlite3",
61
+ "adbc_driver_sqlite",
62
+ }:
59
63
  self.driver_name = "adbc_driver_sqlite.dbapi.connect"
60
- elif self.driver_name != "adbc_driver_duckdb.dbapi.connect" and "duckdb" in self.driver_name:
64
+ elif self.driver_name != "adbc_driver_duckdb.dbapi.connect" and self.driver_name in {
65
+ "duckdb",
66
+ "adbc_driver_duckdb",
67
+ }:
61
68
  self.driver_name = "adbc_driver_duckdb.dbapi.connect"
62
- elif self.driver_name != "adbc_driver_postgresql.dbapi.connect" and "postgres" in self.driver_name:
69
+ elif self.driver_name != "adbc_driver_postgresql.dbapi.connect" and self.driver_name in {
70
+ "postgres",
71
+ "adbc_driver_postgresql",
72
+ "postgresql",
73
+ "pg",
74
+ }:
63
75
  self.driver_name = "adbc_driver_postgresql.dbapi.connect"
64
- elif self.driver_name != "adbc_driver_snowflake.dbapi.connect" and "snowflake" in self.driver_name:
76
+ elif self.driver_name != "adbc_driver_snowflake.dbapi.connect" and self.driver_name in {
77
+ "snowflake",
78
+ "adbc_driver_snowflake",
79
+ "sf",
80
+ }:
65
81
  self.driver_name = "adbc_driver_snowflake.dbapi.connect"
66
- elif self.driver_name != "adbc_driver_bigquery.dbapi.connect" and "bigquery" in self.driver_name:
82
+ elif self.driver_name != "adbc_driver_bigquery.dbapi.connect" and self.driver_name in {
83
+ "bigquery",
84
+ "adbc_driver_bigquery",
85
+ "bq",
86
+ }:
67
87
  self.driver_name = "adbc_driver_bigquery.dbapi.connect"
68
- elif self.driver_name != "adbc_driver_flightsql.dbapi.connect" and "flightsql" in self.driver_name:
88
+ elif self.driver_name != "adbc_driver_flightsql.dbapi.connect" and self.driver_name in {
89
+ "flightsql",
90
+ "adbc_driver_flightsql",
91
+ "grpc",
92
+ }:
69
93
  self.driver_name = "adbc_driver_flightsql.dbapi.connect"
70
94
  return self.driver_name
71
95
 
@@ -153,11 +177,10 @@ class Adbc(NoPoolSyncConfig["Connection", "AdbcDriver"]):
153
177
  """
154
178
  try:
155
179
  connect_func = self._get_connect_func()
156
- _config = self.connection_config_dict
157
- return connect_func(**_config)
180
+ return connect_func(**self.connection_config_dict)
158
181
  except Exception as e:
159
182
  # Include driver name in error message for better context
160
- driver_name = self.driver_name if isinstance(self.driver_name, str) else "Unknown/Derived"
183
+ driver_name = self.driver_name if isinstance(self.driver_name, str) else "Unknown/Missing"
161
184
  # Use the potentially modified driver_path from _get_connect_func if available,
162
185
  # otherwise fallback to self.driver_name for the error message.
163
186
  # This requires _get_connect_func to potentially return the used path or store it.