sqlspec 0.32.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.
- sqlspec/__init__.py +104 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +312 -0
- sqlspec/_typing.py +784 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +880 -0
- sqlspec/adapters/adbc/config.py +436 -0
- sqlspec/adapters/adbc/data_dictionary.py +537 -0
- sqlspec/adapters/adbc/driver.py +841 -0
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/__init__.py +29 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +536 -0
- sqlspec/adapters/aiosqlite/config.py +310 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +260 -0
- sqlspec/adapters/aiosqlite/driver.py +463 -0
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +500 -0
- sqlspec/adapters/asyncmy/__init__.py +25 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +503 -0
- sqlspec/adapters/asyncmy/config.py +246 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +241 -0
- sqlspec/adapters/asyncmy/driver.py +632 -0
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +23 -0
- sqlspec/adapters/asyncpg/_type_handlers.py +76 -0
- sqlspec/adapters/asyncpg/_types.py +23 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +460 -0
- sqlspec/adapters/asyncpg/config.py +464 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +321 -0
- sqlspec/adapters/asyncpg/driver.py +720 -0
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +585 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/data_dictionary.py +256 -0
- sqlspec/adapters/bigquery/driver.py +1073 -0
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/__init__.py +24 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +563 -0
- sqlspec/adapters/duckdb/config.py +396 -0
- sqlspec/adapters/duckdb/data_dictionary.py +264 -0
- sqlspec/adapters/duckdb/driver.py +604 -0
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +273 -0
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +39 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +130 -0
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1632 -0
- sqlspec/adapters/oracledb/config.py +469 -0
- sqlspec/adapters/oracledb/data_dictionary.py +717 -0
- sqlspec/adapters/oracledb/driver.py +1493 -0
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +765 -0
- sqlspec/adapters/oracledb/migrations.py +532 -0
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +12 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +483 -0
- sqlspec/adapters/psqlpy/config.py +271 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +179 -0
- sqlspec/adapters/psqlpy/driver.py +892 -0
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_type_handlers.py +90 -0
- sqlspec/adapters/psycopg/_types.py +18 -0
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +962 -0
- sqlspec/adapters/psycopg/config.py +487 -0
- sqlspec/adapters/psycopg/data_dictionary.py +630 -0
- sqlspec/adapters/psycopg/driver.py +1336 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/spanner/__init__.py +38 -0
- sqlspec/adapters/spanner/_type_handlers.py +186 -0
- sqlspec/adapters/spanner/_types.py +12 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +435 -0
- sqlspec/adapters/spanner/config.py +241 -0
- sqlspec/adapters/spanner/data_dictionary.py +95 -0
- sqlspec/adapters/spanner/dialect/__init__.py +6 -0
- sqlspec/adapters/spanner/dialect/_spangres.py +52 -0
- sqlspec/adapters/spanner/dialect/_spanner.py +123 -0
- sqlspec/adapters/spanner/driver.py +366 -0
- sqlspec/adapters/spanner/litestar/__init__.py +5 -0
- sqlspec/adapters/spanner/litestar/store.py +266 -0
- sqlspec/adapters/spanner/type_converter.py +46 -0
- sqlspec/adapters/sqlite/__init__.py +18 -0
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +582 -0
- sqlspec/adapters/sqlite/config.py +221 -0
- sqlspec/adapters/sqlite/data_dictionary.py +256 -0
- sqlspec/adapters/sqlite/driver.py +527 -0
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +140 -0
- sqlspec/base.py +811 -0
- sqlspec/builder/__init__.py +146 -0
- sqlspec/builder/_base.py +900 -0
- sqlspec/builder/_column.py +517 -0
- sqlspec/builder/_ddl.py +1642 -0
- sqlspec/builder/_delete.py +84 -0
- sqlspec/builder/_dml.py +381 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.py +1537 -0
- sqlspec/builder/_insert.py +315 -0
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +848 -0
- sqlspec/builder/_parsing_utils.py +297 -0
- sqlspec/builder/_select.py +1615 -0
- sqlspec/builder/_update.py +161 -0
- sqlspec/builder/_vector_expressions.py +259 -0
- sqlspec/cli.py +764 -0
- sqlspec/config.py +1540 -0
- sqlspec/core/__init__.py +305 -0
- sqlspec/core/cache.py +785 -0
- sqlspec/core/compiler.py +603 -0
- sqlspec/core/filters.py +872 -0
- sqlspec/core/hashing.py +274 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +64 -0
- sqlspec/core/parameters/_alignment.py +266 -0
- sqlspec/core/parameters/_converter.py +413 -0
- sqlspec/core/parameters/_processor.py +341 -0
- sqlspec/core/parameters/_registry.py +201 -0
- sqlspec/core/parameters/_transformers.py +226 -0
- sqlspec/core/parameters/_types.py +430 -0
- sqlspec/core/parameters/_validator.py +123 -0
- sqlspec/core/pipeline.py +187 -0
- sqlspec/core/result.py +1124 -0
- sqlspec/core/splitter.py +940 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.py +835 -0
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +36 -0
- sqlspec/driver/_async.py +1027 -0
- sqlspec/driver/_common.py +1236 -0
- sqlspec/driver/_sync.py +1025 -0
- sqlspec/driver/mixins/__init__.py +7 -0
- sqlspec/driver/mixins/_result_tools.py +61 -0
- sqlspec/driver/mixins/_sql_translator.py +122 -0
- sqlspec/driver/mixins/_storage.py +311 -0
- sqlspec/exceptions.py +321 -0
- sqlspec/extensions/__init__.py +0 -0
- 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/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +471 -0
- sqlspec/extensions/fastapi/__init__.py +19 -0
- sqlspec/extensions/fastapi/extension.py +341 -0
- sqlspec/extensions/fastapi/providers.py +543 -0
- sqlspec/extensions/flask/__init__.py +36 -0
- sqlspec/extensions/flask/_state.py +72 -0
- sqlspec/extensions/flask/_utils.py +40 -0
- sqlspec/extensions/flask/extension.py +402 -0
- sqlspec/extensions/litestar/__init__.py +23 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +92 -0
- sqlspec/extensions/litestar/config.py +90 -0
- sqlspec/extensions/litestar/handlers.py +316 -0
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +638 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +107 -0
- sqlspec/extensions/starlette/__init__.py +10 -0
- sqlspec/extensions/starlette/_state.py +26 -0
- sqlspec/extensions/starlette/_utils.py +52 -0
- sqlspec/extensions/starlette/extension.py +257 -0
- sqlspec/extensions/starlette/middleware.py +154 -0
- sqlspec/loader.py +716 -0
- sqlspec/migrations/__init__.py +36 -0
- sqlspec/migrations/base.py +728 -0
- sqlspec/migrations/commands.py +1140 -0
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +203 -0
- sqlspec/migrations/loaders.py +450 -0
- sqlspec/migrations/runner.py +1024 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.py +403 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.py +203 -0
- sqlspec/observability/__init__.py +22 -0
- sqlspec/observability/_config.py +228 -0
- sqlspec/observability/_diagnostics.py +67 -0
- sqlspec/observability/_dispatcher.py +151 -0
- sqlspec/observability/_observer.py +180 -0
- sqlspec/observability/_runtime.py +381 -0
- sqlspec/observability/_spans.py +158 -0
- sqlspec/protocols.py +530 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +46 -0
- sqlspec/storage/_utils.py +104 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +398 -0
- sqlspec/storage/backends/local.py +377 -0
- sqlspec/storage/backends/obstore.py +580 -0
- sqlspec/storage/errors.py +104 -0
- sqlspec/storage/pipeline.py +604 -0
- sqlspec/storage/registry.py +289 -0
- sqlspec/typing.py +219 -0
- sqlspec/utils/__init__.py +31 -0
- sqlspec/utils/arrow_helpers.py +95 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +132 -0
- sqlspec/utils/data_transformation.py +114 -0
- sqlspec/utils/dependencies.py +79 -0
- sqlspec/utils/deprecation.py +113 -0
- sqlspec/utils/fixtures.py +250 -0
- sqlspec/utils/logging.py +172 -0
- sqlspec/utils/module_loader.py +273 -0
- sqlspec/utils/portal.py +325 -0
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +396 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.py +277 -0
- sqlspec/utils/text.py +108 -0
- sqlspec/utils/type_converters.py +99 -0
- sqlspec/utils/type_guards.py +1324 -0
- sqlspec/utils/version.py +444 -0
- sqlspec-0.32.0.dist-info/METADATA +202 -0
- sqlspec-0.32.0.dist-info/RECORD +262 -0
- sqlspec-0.32.0.dist-info/WHEEL +4 -0
- sqlspec-0.32.0.dist-info/entry_points.txt +2 -0
- sqlspec-0.32.0.dist-info/licenses/LICENSE +21 -0
sqlspec/utils/version.py
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""Migration version parsing and comparison utilities.
|
|
2
|
+
|
|
3
|
+
Provides structured parsing of migration versions supporting both legacy sequential
|
|
4
|
+
(0001) and timestamp-based (20251011120000) formats with type-safe comparison.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
"MigrationVersion",
|
|
16
|
+
"VersionType",
|
|
17
|
+
"convert_to_sequential_version",
|
|
18
|
+
"generate_conversion_map",
|
|
19
|
+
"generate_timestamp_version",
|
|
20
|
+
"get_next_sequential_number",
|
|
21
|
+
"is_sequential_version",
|
|
22
|
+
"is_timestamp_version",
|
|
23
|
+
"parse_version",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
SEQUENTIAL_PATTERN = re.compile(r"^(?!\d{14}$)\d+$")
|
|
29
|
+
TIMESTAMP_PATTERN = re.compile(r"^(\d{14})$")
|
|
30
|
+
EXTENSION_PATTERN = re.compile(r"^ext_(\w+)_(.+)$")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VersionType(Enum):
|
|
34
|
+
"""Migration version format type."""
|
|
35
|
+
|
|
36
|
+
SEQUENTIAL = "sequential"
|
|
37
|
+
TIMESTAMP = "timestamp"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class MigrationVersion:
|
|
42
|
+
"""Parsed migration version with structured comparison support.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
raw: Original version string (e.g., "0001", "20251011120000", "ext_litestar_0001").
|
|
46
|
+
type: Version format type (sequential or timestamp).
|
|
47
|
+
sequence: Numeric value for sequential versions (e.g., 1, 2, 42).
|
|
48
|
+
timestamp: Parsed datetime for timestamp versions (UTC).
|
|
49
|
+
extension: Extension name for extension-prefixed versions (e.g., "litestar").
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
raw: str
|
|
53
|
+
type: VersionType
|
|
54
|
+
sequence: "int | None"
|
|
55
|
+
timestamp: "datetime | None"
|
|
56
|
+
extension: "str | None"
|
|
57
|
+
|
|
58
|
+
def __lt__(self, other: "MigrationVersion") -> bool:
|
|
59
|
+
"""Compare versions supporting mixed formats.
|
|
60
|
+
|
|
61
|
+
Comparison Rules:
|
|
62
|
+
1. Extension migrations sort by extension name first, then version
|
|
63
|
+
2. Sequential < Timestamp (legacy migrations first)
|
|
64
|
+
3. Sequential vs Sequential: numeric comparison
|
|
65
|
+
4. Timestamp vs Timestamp: chronological comparison
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
other: Version to compare against.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if this version sorts before other.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
TypeError: If comparing against non-MigrationVersion.
|
|
75
|
+
"""
|
|
76
|
+
if not isinstance(other, MigrationVersion):
|
|
77
|
+
return NotImplemented
|
|
78
|
+
|
|
79
|
+
if self.extension != other.extension:
|
|
80
|
+
if self.extension is None:
|
|
81
|
+
return True
|
|
82
|
+
if other.extension is None:
|
|
83
|
+
return False
|
|
84
|
+
return self.extension < other.extension
|
|
85
|
+
|
|
86
|
+
if self.type == other.type:
|
|
87
|
+
if self.type == VersionType.SEQUENTIAL:
|
|
88
|
+
return (self.sequence or 0) < (other.sequence or 0)
|
|
89
|
+
return (self.timestamp or datetime.min.replace(tzinfo=timezone.utc)) < (
|
|
90
|
+
other.timestamp or datetime.min.replace(tzinfo=timezone.utc)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return self.type == VersionType.SEQUENTIAL
|
|
94
|
+
|
|
95
|
+
def __le__(self, other: "MigrationVersion") -> bool:
|
|
96
|
+
"""Check if version is less than or equal to another.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
other: Version to compare against.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
True if this version is less than or equal to other.
|
|
103
|
+
"""
|
|
104
|
+
return self == other or self < other
|
|
105
|
+
|
|
106
|
+
def __eq__(self, other: object) -> bool:
|
|
107
|
+
"""Check version equality.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
other: Version to compare against.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True if versions are equal.
|
|
114
|
+
"""
|
|
115
|
+
if not isinstance(other, MigrationVersion):
|
|
116
|
+
return NotImplemented
|
|
117
|
+
return self.raw == other.raw
|
|
118
|
+
|
|
119
|
+
def __hash__(self) -> int:
|
|
120
|
+
"""Hash version for use in sets and dicts.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Hash value based on raw version string.
|
|
124
|
+
"""
|
|
125
|
+
return hash(self.raw)
|
|
126
|
+
|
|
127
|
+
def __repr__(self) -> str:
|
|
128
|
+
"""Get string representation for debugging.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
String representation with type and value.
|
|
132
|
+
"""
|
|
133
|
+
if self.extension:
|
|
134
|
+
return f"MigrationVersion(ext={self.extension}, {self.type.value}={self.raw})"
|
|
135
|
+
return f"MigrationVersion({self.type.value}={self.raw})"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def is_sequential_version(version_str: "str | None") -> bool:
|
|
139
|
+
"""Check if version string is sequential format.
|
|
140
|
+
|
|
141
|
+
Sequential format: Any sequence of digits (0001, 42, 9999, 10000+).
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
version_str: Version string to check.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if sequential format, False if None or whitespace.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
>>> is_sequential_version("0001")
|
|
151
|
+
True
|
|
152
|
+
>>> is_sequential_version("42")
|
|
153
|
+
True
|
|
154
|
+
>>> is_sequential_version("10000")
|
|
155
|
+
True
|
|
156
|
+
>>> is_sequential_version("20251011120000")
|
|
157
|
+
False
|
|
158
|
+
>>> is_sequential_version(None)
|
|
159
|
+
False
|
|
160
|
+
"""
|
|
161
|
+
if version_str is None or not version_str.strip():
|
|
162
|
+
return False
|
|
163
|
+
return bool(SEQUENTIAL_PATTERN.match(version_str))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def is_timestamp_version(version_str: "str | None") -> bool:
|
|
167
|
+
"""Check if version string is timestamp format.
|
|
168
|
+
|
|
169
|
+
Timestamp format: 14-digit YYYYMMDDHHmmss (20251011120000).
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
version_str: Version string to check.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
True if timestamp format, False if None or whitespace.
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
>>> is_timestamp_version("20251011120000")
|
|
179
|
+
True
|
|
180
|
+
>>> is_timestamp_version("0001")
|
|
181
|
+
False
|
|
182
|
+
>>> is_timestamp_version(None)
|
|
183
|
+
False
|
|
184
|
+
"""
|
|
185
|
+
if version_str is None or not version_str.strip():
|
|
186
|
+
return False
|
|
187
|
+
if not TIMESTAMP_PATTERN.match(version_str):
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
datetime.strptime(version_str, "%Y%m%d%H%M%S").replace(tzinfo=timezone.utc)
|
|
192
|
+
except ValueError:
|
|
193
|
+
return False
|
|
194
|
+
else:
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def parse_version(version_str: "str | None") -> MigrationVersion:
|
|
199
|
+
"""Parse version string into structured format.
|
|
200
|
+
|
|
201
|
+
Supports:
|
|
202
|
+
- Sequential: "0001", "42", "9999"
|
|
203
|
+
- Timestamp: "20251011120000"
|
|
204
|
+
- Extension: "ext_litestar_0001", "ext_litestar_20251011120000"
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
version_str: Version string to parse.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Parsed migration version.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
ValueError: If version format is invalid, None, or whitespace-only.
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
>>> v = parse_version("0001")
|
|
217
|
+
>>> v.type == VersionType.SEQUENTIAL
|
|
218
|
+
True
|
|
219
|
+
>>> v.sequence
|
|
220
|
+
1
|
|
221
|
+
|
|
222
|
+
>>> v = parse_version("20251011120000")
|
|
223
|
+
>>> v.type == VersionType.TIMESTAMP
|
|
224
|
+
True
|
|
225
|
+
|
|
226
|
+
>>> v = parse_version("ext_litestar_0001")
|
|
227
|
+
>>> v.extension
|
|
228
|
+
'litestar'
|
|
229
|
+
"""
|
|
230
|
+
if version_str is None or not version_str.strip():
|
|
231
|
+
msg = "Invalid migration version: version string is None or empty"
|
|
232
|
+
raise ValueError(msg)
|
|
233
|
+
|
|
234
|
+
extension_match = EXTENSION_PATTERN.match(version_str)
|
|
235
|
+
if extension_match:
|
|
236
|
+
extension_name = extension_match.group(1)
|
|
237
|
+
base_version = extension_match.group(2)
|
|
238
|
+
parsed = parse_version(base_version)
|
|
239
|
+
|
|
240
|
+
return MigrationVersion(
|
|
241
|
+
raw=version_str,
|
|
242
|
+
type=parsed.type,
|
|
243
|
+
sequence=parsed.sequence,
|
|
244
|
+
timestamp=parsed.timestamp,
|
|
245
|
+
extension=extension_name,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if is_timestamp_version(version_str):
|
|
249
|
+
dt = datetime.strptime(version_str, "%Y%m%d%H%M%S").replace(tzinfo=timezone.utc)
|
|
250
|
+
return MigrationVersion(
|
|
251
|
+
raw=version_str, type=VersionType.TIMESTAMP, sequence=None, timestamp=dt, extension=None
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if is_sequential_version(version_str):
|
|
255
|
+
return MigrationVersion(
|
|
256
|
+
raw=version_str, type=VersionType.SEQUENTIAL, sequence=int(version_str), timestamp=None, extension=None
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
msg = f"Invalid migration version format: {version_str}. Expected sequential (0001) or timestamp (YYYYMMDDHHmmss)."
|
|
260
|
+
raise ValueError(msg)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def generate_timestamp_version() -> str:
|
|
264
|
+
"""Generate new timestamp version in UTC.
|
|
265
|
+
|
|
266
|
+
Format: YYYYMMDDHHmmss (14 digits).
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Timestamp version string.
|
|
270
|
+
|
|
271
|
+
Examples:
|
|
272
|
+
>>> version = generate_timestamp_version()
|
|
273
|
+
>>> len(version)
|
|
274
|
+
14
|
|
275
|
+
>>> is_timestamp_version(version)
|
|
276
|
+
True
|
|
277
|
+
"""
|
|
278
|
+
return datetime.now(tz=timezone.utc).strftime("%Y%m%d%H%M%S")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def get_next_sequential_number(migrations: "list[MigrationVersion]", extension: "str | None" = None) -> int:
|
|
282
|
+
"""Find highest sequential number and return next available.
|
|
283
|
+
|
|
284
|
+
Scans migrations for sequential versions and returns the next number in sequence.
|
|
285
|
+
When extension is specified, only that extension's migrations are considered.
|
|
286
|
+
When extension is None, only core (non-extension) migrations are considered.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
migrations: List of parsed migration versions.
|
|
290
|
+
extension: Optional extension name to filter by (e.g., "litestar", "adk").
|
|
291
|
+
None means core migrations only.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Next available sequential number (1 if no sequential migrations exist).
|
|
295
|
+
|
|
296
|
+
Examples:
|
|
297
|
+
>>> v1 = parse_version("0001")
|
|
298
|
+
>>> v2 = parse_version("0002")
|
|
299
|
+
>>> get_next_sequential_number([v1, v2])
|
|
300
|
+
3
|
|
301
|
+
|
|
302
|
+
>>> get_next_sequential_number([])
|
|
303
|
+
1
|
|
304
|
+
|
|
305
|
+
>>> ext = parse_version("ext_litestar_0001")
|
|
306
|
+
>>> core = parse_version("0001")
|
|
307
|
+
>>> get_next_sequential_number([ext, core])
|
|
308
|
+
2
|
|
309
|
+
|
|
310
|
+
>>> ext1 = parse_version("ext_litestar_0001")
|
|
311
|
+
>>> get_next_sequential_number([ext1], extension="litestar")
|
|
312
|
+
2
|
|
313
|
+
"""
|
|
314
|
+
sequential = [
|
|
315
|
+
m.sequence for m in migrations if m.type == VersionType.SEQUENTIAL and m.extension == extension and m.sequence
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
if not sequential:
|
|
319
|
+
return 1
|
|
320
|
+
|
|
321
|
+
return max(sequential) + 1
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def convert_to_sequential_version(timestamp_version: MigrationVersion, sequence_number: int) -> str:
|
|
325
|
+
"""Convert timestamp MigrationVersion to sequential string format.
|
|
326
|
+
|
|
327
|
+
Preserves extension prefixes during conversion. Format uses zero-padded
|
|
328
|
+
4-digit numbers (0001, 0002, etc.).
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
timestamp_version: Parsed timestamp version to convert.
|
|
332
|
+
sequence_number: Sequential number to assign.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Sequential version string with extension prefix if applicable.
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
ValueError: If input is not a timestamp version.
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
>>> v = parse_version("20251011120000")
|
|
342
|
+
>>> convert_to_sequential_version(v, 3)
|
|
343
|
+
'0003'
|
|
344
|
+
|
|
345
|
+
>>> v = parse_version("ext_litestar_20251011120000")
|
|
346
|
+
>>> convert_to_sequential_version(v, 1)
|
|
347
|
+
'ext_litestar_0001'
|
|
348
|
+
|
|
349
|
+
>>> v = parse_version("0001")
|
|
350
|
+
>>> convert_to_sequential_version(v, 2)
|
|
351
|
+
Traceback (most recent call last):
|
|
352
|
+
...
|
|
353
|
+
ValueError: Can only convert timestamp versions to sequential
|
|
354
|
+
"""
|
|
355
|
+
if timestamp_version.type != VersionType.TIMESTAMP:
|
|
356
|
+
msg = "Can only convert timestamp versions to sequential"
|
|
357
|
+
raise ValueError(msg)
|
|
358
|
+
|
|
359
|
+
seq_str = str(sequence_number).zfill(4)
|
|
360
|
+
|
|
361
|
+
if timestamp_version.extension:
|
|
362
|
+
return f"ext_{timestamp_version.extension}_{seq_str}"
|
|
363
|
+
|
|
364
|
+
return seq_str
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def generate_conversion_map(migrations: "list[tuple[str, Any]]") -> "dict[str, str]":
|
|
368
|
+
"""Generate mapping from timestamp versions to sequential versions.
|
|
369
|
+
|
|
370
|
+
Separates timestamp migrations from sequential, sorts timestamps chronologically,
|
|
371
|
+
and assigns sequential numbers starting after the highest existing sequential
|
|
372
|
+
number. Extension migrations maintain separate numbering within their namespace.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
migrations: List of tuples (version_string, migration_path).
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Dictionary mapping old timestamp versions to new sequential versions.
|
|
379
|
+
|
|
380
|
+
Examples:
|
|
381
|
+
>>> migrations = [
|
|
382
|
+
... ("0001", Path("0001_init.sql")),
|
|
383
|
+
... ("0002", Path("0002_users.sql")),
|
|
384
|
+
... ("20251011120000", Path("20251011120000_products.sql")),
|
|
385
|
+
... ("20251012130000", Path("20251012130000_orders.sql")),
|
|
386
|
+
... ]
|
|
387
|
+
>>> result = generate_conversion_map(migrations)
|
|
388
|
+
>>> result
|
|
389
|
+
{'20251011120000': '0003', '20251012130000': '0004'}
|
|
390
|
+
|
|
391
|
+
>>> migrations = [
|
|
392
|
+
... ("20251011120000", Path("20251011120000_first.sql")),
|
|
393
|
+
... ("20251010090000", Path("20251010090000_earlier.sql")),
|
|
394
|
+
... ]
|
|
395
|
+
>>> result = generate_conversion_map(migrations)
|
|
396
|
+
>>> result
|
|
397
|
+
{'20251010090000': '0001', '20251011120000': '0002'}
|
|
398
|
+
|
|
399
|
+
>>> migrations = []
|
|
400
|
+
>>> generate_conversion_map(migrations)
|
|
401
|
+
{}
|
|
402
|
+
"""
|
|
403
|
+
if not migrations:
|
|
404
|
+
return {}
|
|
405
|
+
|
|
406
|
+
def _try_parse_version(version_str: str) -> "MigrationVersion | None":
|
|
407
|
+
"""Parse version string, returning None for invalid versions."""
|
|
408
|
+
try:
|
|
409
|
+
return parse_version(version_str)
|
|
410
|
+
except ValueError:
|
|
411
|
+
logger.warning("Skipping invalid migration version: %s", version_str)
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
parsed_versions = [v for version_str, _path in migrations if (v := _try_parse_version(version_str)) is not None]
|
|
415
|
+
|
|
416
|
+
timestamp_migrations = sorted([v for v in parsed_versions if v.type == VersionType.TIMESTAMP])
|
|
417
|
+
|
|
418
|
+
if not timestamp_migrations:
|
|
419
|
+
return {}
|
|
420
|
+
|
|
421
|
+
core_timestamps = [m for m in timestamp_migrations if m.extension is None]
|
|
422
|
+
ext_timestamps_by_name: dict[str, list[MigrationVersion]] = {}
|
|
423
|
+
for m in timestamp_migrations:
|
|
424
|
+
if m.extension:
|
|
425
|
+
ext_timestamps_by_name.setdefault(m.extension, []).append(m)
|
|
426
|
+
|
|
427
|
+
conversion_map: dict[str, str] = {}
|
|
428
|
+
|
|
429
|
+
if core_timestamps:
|
|
430
|
+
next_seq = get_next_sequential_number(parsed_versions)
|
|
431
|
+
for timestamp_version in core_timestamps:
|
|
432
|
+
sequential_version = convert_to_sequential_version(timestamp_version, next_seq)
|
|
433
|
+
conversion_map[timestamp_version.raw] = sequential_version
|
|
434
|
+
next_seq += 1
|
|
435
|
+
|
|
436
|
+
for ext_name, ext_migrations in ext_timestamps_by_name.items():
|
|
437
|
+
ext_parsed = [v for v in parsed_versions if v.extension == ext_name]
|
|
438
|
+
next_seq = get_next_sequential_number(ext_parsed, extension=ext_name)
|
|
439
|
+
for timestamp_version in ext_migrations:
|
|
440
|
+
sequential_version = convert_to_sequential_version(timestamp_version, next_seq)
|
|
441
|
+
conversion_map[timestamp_version.raw] = sequential_version
|
|
442
|
+
next_seq += 1
|
|
443
|
+
|
|
444
|
+
return conversion_map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlspec
|
|
3
|
+
Version: 0.32.0
|
|
4
|
+
Summary: SQL Experiments in Python
|
|
5
|
+
Project-URL: Discord, https://discord.gg/litestar
|
|
6
|
+
Project-URL: Issue, https://github.com/litestar-org/sqlspec/issues/
|
|
7
|
+
Project-URL: Source, https://github.com/litestar-org/sqlspec
|
|
8
|
+
Author-email: Cody Fincher <cody@litestar.dev>
|
|
9
|
+
Maintainer-email: Litestar Developers <hello@litestar.dev>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Python: <4.0,>=3.10
|
|
13
|
+
Requires-Dist: mypy-extensions
|
|
14
|
+
Requires-Dist: rich-click
|
|
15
|
+
Requires-Dist: sqlglot>=19.9.0
|
|
16
|
+
Requires-Dist: typing-extensions
|
|
17
|
+
Provides-Extra: adbc
|
|
18
|
+
Requires-Dist: adbc-driver-manager; extra == 'adbc'
|
|
19
|
+
Requires-Dist: pyarrow; extra == 'adbc'
|
|
20
|
+
Provides-Extra: adk
|
|
21
|
+
Requires-Dist: google-adk; extra == 'adk'
|
|
22
|
+
Provides-Extra: aioodbc
|
|
23
|
+
Requires-Dist: aioodbc; extra == 'aioodbc'
|
|
24
|
+
Provides-Extra: aiosql
|
|
25
|
+
Requires-Dist: aiosql; extra == 'aiosql'
|
|
26
|
+
Provides-Extra: aiosqlite
|
|
27
|
+
Requires-Dist: aiosqlite; extra == 'aiosqlite'
|
|
28
|
+
Provides-Extra: alloydb
|
|
29
|
+
Requires-Dist: google-cloud-alloydb-connector; extra == 'alloydb'
|
|
30
|
+
Provides-Extra: asyncmy
|
|
31
|
+
Requires-Dist: asyncmy; extra == 'asyncmy'
|
|
32
|
+
Provides-Extra: asyncpg
|
|
33
|
+
Requires-Dist: asyncpg; extra == 'asyncpg'
|
|
34
|
+
Provides-Extra: attrs
|
|
35
|
+
Requires-Dist: attrs; extra == 'attrs'
|
|
36
|
+
Requires-Dist: cattrs; extra == 'attrs'
|
|
37
|
+
Provides-Extra: bigquery
|
|
38
|
+
Requires-Dist: google-cloud-bigquery; extra == 'bigquery'
|
|
39
|
+
Requires-Dist: google-cloud-storage; extra == 'bigquery'
|
|
40
|
+
Provides-Extra: cli
|
|
41
|
+
Requires-Dist: rich-click; extra == 'cli'
|
|
42
|
+
Provides-Extra: cloud-sql
|
|
43
|
+
Requires-Dist: cloud-sql-python-connector; extra == 'cloud-sql'
|
|
44
|
+
Provides-Extra: duckdb
|
|
45
|
+
Requires-Dist: duckdb; extra == 'duckdb'
|
|
46
|
+
Provides-Extra: fastapi
|
|
47
|
+
Requires-Dist: fastapi; extra == 'fastapi'
|
|
48
|
+
Provides-Extra: flask
|
|
49
|
+
Requires-Dist: flask; extra == 'flask'
|
|
50
|
+
Provides-Extra: fsspec
|
|
51
|
+
Requires-Dist: fsspec; extra == 'fsspec'
|
|
52
|
+
Provides-Extra: litestar
|
|
53
|
+
Requires-Dist: litestar; extra == 'litestar'
|
|
54
|
+
Provides-Extra: msgspec
|
|
55
|
+
Requires-Dist: msgspec; extra == 'msgspec'
|
|
56
|
+
Provides-Extra: mypyc
|
|
57
|
+
Provides-Extra: nanoid
|
|
58
|
+
Requires-Dist: fastnanoid>=0.4.1; extra == 'nanoid'
|
|
59
|
+
Provides-Extra: obstore
|
|
60
|
+
Requires-Dist: obstore; extra == 'obstore'
|
|
61
|
+
Provides-Extra: opentelemetry
|
|
62
|
+
Requires-Dist: opentelemetry-instrumentation; extra == 'opentelemetry'
|
|
63
|
+
Provides-Extra: oracledb
|
|
64
|
+
Requires-Dist: oracledb; extra == 'oracledb'
|
|
65
|
+
Provides-Extra: orjson
|
|
66
|
+
Requires-Dist: orjson; extra == 'orjson'
|
|
67
|
+
Provides-Extra: pandas
|
|
68
|
+
Requires-Dist: pandas; extra == 'pandas'
|
|
69
|
+
Requires-Dist: pyarrow; extra == 'pandas'
|
|
70
|
+
Provides-Extra: performance
|
|
71
|
+
Requires-Dist: msgspec; extra == 'performance'
|
|
72
|
+
Requires-Dist: sqlglot[rs]; extra == 'performance'
|
|
73
|
+
Provides-Extra: polars
|
|
74
|
+
Requires-Dist: polars; extra == 'polars'
|
|
75
|
+
Requires-Dist: pyarrow; extra == 'polars'
|
|
76
|
+
Provides-Extra: prometheus
|
|
77
|
+
Requires-Dist: prometheus-client; extra == 'prometheus'
|
|
78
|
+
Provides-Extra: psqlpy
|
|
79
|
+
Requires-Dist: psqlpy; extra == 'psqlpy'
|
|
80
|
+
Provides-Extra: psycopg
|
|
81
|
+
Requires-Dist: psycopg[binary,pool]; extra == 'psycopg'
|
|
82
|
+
Provides-Extra: pydantic
|
|
83
|
+
Requires-Dist: pydantic; extra == 'pydantic'
|
|
84
|
+
Requires-Dist: pydantic-extra-types; extra == 'pydantic'
|
|
85
|
+
Provides-Extra: pymssql
|
|
86
|
+
Requires-Dist: pymssql; extra == 'pymssql'
|
|
87
|
+
Provides-Extra: pymysql
|
|
88
|
+
Requires-Dist: pymysql; extra == 'pymysql'
|
|
89
|
+
Provides-Extra: spanner
|
|
90
|
+
Requires-Dist: google-cloud-spanner; extra == 'spanner'
|
|
91
|
+
Provides-Extra: uuid
|
|
92
|
+
Requires-Dist: uuid-utils; extra == 'uuid'
|
|
93
|
+
Description-Content-Type: text/markdown
|
|
94
|
+
|
|
95
|
+
# SQLSpec
|
|
96
|
+
|
|
97
|
+
**Type-safe SQL execution layer for Python.**
|
|
98
|
+
|
|
99
|
+
SQLSpec handles database connectivity and result mapping so you can focus on SQL. Write raw queries when you need precision, use the builder API when you need composability, or load SQL from files when you need organization. Every statement passes through a [sqlglot](https://github.com/tobymao/sqlglot)-powered AST pipeline for validation, dialect conversion, and optimization before execution. Export results as Python objects, Arrow tables, Polars or pandas DataFrames.
|
|
100
|
+
|
|
101
|
+
It's not an ORM. It's the connectivity and processing layer between your application and your database that provides the right abstraction for each situation without dictating how you write SQL.
|
|
102
|
+
|
|
103
|
+
## Status
|
|
104
|
+
|
|
105
|
+
SQLSpec is currently in active development. The public API may change. Follow the [docs](https://sqlspec.dev/) and changelog for updates.
|
|
106
|
+
|
|
107
|
+
## What You Get
|
|
108
|
+
|
|
109
|
+
**Connection Management**
|
|
110
|
+
|
|
111
|
+
- Connection pooling with configurable size, timeout, and lifecycle hooks
|
|
112
|
+
- Sync and async support with a unified API surface
|
|
113
|
+
- Adapters for PostgreSQL (psycopg, asyncpg, psqlpy), SQLite (sqlite3, aiosqlite), DuckDB, MySQL (asyncmy), Oracle, BigQuery, and ADBC-compatible databases
|
|
114
|
+
|
|
115
|
+
**Query Execution**
|
|
116
|
+
|
|
117
|
+
- Raw SQL strings with automatic parameter binding and dialect translation
|
|
118
|
+
- SQL AST parsing via sqlglot for validation, optimization, and dialect conversion
|
|
119
|
+
- Builder API for programmatic query construction without string concatenation
|
|
120
|
+
- SQL file loading to keep queries organized alongside your code (aiosql-style)
|
|
121
|
+
- Statement stacks for batching multiple operations with transaction control
|
|
122
|
+
|
|
123
|
+
**Result Handling**
|
|
124
|
+
|
|
125
|
+
- Type-safe result mapping to Pydantic, msgspec, attrs, or dataclasses
|
|
126
|
+
- Apache Arrow export for zero-copy integration with pandas, Polars, and analytical tools
|
|
127
|
+
- Result iteration, single-row fetch, or bulk retrieval based on your use case
|
|
128
|
+
|
|
129
|
+
**Framework Integration**
|
|
130
|
+
|
|
131
|
+
- Litestar plugin with dependency injection for connections, sessions, and pools
|
|
132
|
+
- Starlette/FastAPI middleware for automatic transaction management
|
|
133
|
+
- Flask extension with sync/async portal support
|
|
134
|
+
|
|
135
|
+
**Production Features**
|
|
136
|
+
|
|
137
|
+
- SQL validation and caching via sqlglot AST parsing
|
|
138
|
+
- OpenTelemetry and Prometheus instrumentation hooks
|
|
139
|
+
- Structured logging with correlation ID support
|
|
140
|
+
- Migration CLI for schema versioning
|
|
141
|
+
|
|
142
|
+
## Quick Start
|
|
143
|
+
|
|
144
|
+
### Install
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install "sqlspec"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Run your first query
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from pydantic import BaseModel
|
|
154
|
+
from sqlspec import SQLSpec
|
|
155
|
+
from sqlspec.adapters.sqlite import SqliteConfig
|
|
156
|
+
|
|
157
|
+
class Greeting(BaseModel):
|
|
158
|
+
message: str
|
|
159
|
+
|
|
160
|
+
spec = SQLSpec()
|
|
161
|
+
db = sql.add_config(SqliteConfig(pool_config={"database": ":memory:"}))
|
|
162
|
+
|
|
163
|
+
with spec.provide_session(db) as session:
|
|
164
|
+
greeting = session.select_one(
|
|
165
|
+
"SELECT 'Hello, SQLSpec!' AS message",
|
|
166
|
+
schema_type=Greeting,
|
|
167
|
+
)
|
|
168
|
+
print(greeting.message) # Output: Hello, SQLSpec!
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
That's it. Write SQL, define a schema, get typed objects back. Connection pooling, parameter binding, and result mapping are handled automatically.
|
|
172
|
+
|
|
173
|
+
See the [Getting Started guide](https://sqlspec.dev/getting_started/) for installation variants, adapter selection, and advanced result mapping options.
|
|
174
|
+
|
|
175
|
+
## Documentation
|
|
176
|
+
|
|
177
|
+
- [Getting Started](https://sqlspec.dev/getting_started/)
|
|
178
|
+
- [Usage Guides](https://sqlspec.dev/usage/)
|
|
179
|
+
- [Examples Gallery](https://sqlspec.dev/examples/)
|
|
180
|
+
- [API Reference](https://sqlspec.dev/reference/)
|
|
181
|
+
- [CLI Reference](https://sqlspec.dev/usage/cli.html)
|
|
182
|
+
|
|
183
|
+
## Reference Applications
|
|
184
|
+
|
|
185
|
+
- **[PostgreSQL + Vertex AI Demo](https://github.com/cofin/postgres-vertexai-demo)** - Vector search with pgvector and real-time chat using Litestar and Google ADK. Shows connection pooling, migrations, type-safe result mapping, vector embeddings, and response caching.
|
|
186
|
+
- **[Oracle + Vertex AI Demo](https://github.com/cofin/oracledb-vertexai-demo)** - Oracle 23ai vector search with semantic similarity using HNSW indexes. Demonstrates NumPy array conversion, large object (CLOB) handling, and real-time performance metrics.
|
|
187
|
+
|
|
188
|
+
See the [usage docs](https://sqlspec.dev/usage/) for detailed guides on adapters, configuration patterns, and features like the [SQL file loader](https://sqlspec.dev/usage/loader.html).
|
|
189
|
+
|
|
190
|
+
## Built With
|
|
191
|
+
|
|
192
|
+
- **[sqlglot](https://github.com/tobymao/sqlglot)** - SQL parser, transpiler, and optimizer powering SQLSpec's AST pipeline
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Contributions, issue reports, and adapter ideas are welcome. Review the
|
|
197
|
+
[contributor guide](https://sqlspec.dev/contributing/) and follow the project
|
|
198
|
+
coding standards before opening a pull request.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
SQLSpec is distributed under the MIT License.
|