sqlspec 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/_typing.py +39 -6
- sqlspec/adapters/adbc/__init__.py +2 -2
- sqlspec/adapters/adbc/config.py +34 -11
- sqlspec/adapters/adbc/driver.py +167 -108
- sqlspec/adapters/aiosqlite/__init__.py +2 -2
- sqlspec/adapters/aiosqlite/config.py +2 -2
- sqlspec/adapters/aiosqlite/driver.py +28 -39
- sqlspec/adapters/asyncmy/__init__.py +3 -3
- sqlspec/adapters/asyncmy/config.py +11 -12
- sqlspec/adapters/asyncmy/driver.py +25 -34
- sqlspec/adapters/asyncpg/__init__.py +5 -5
- sqlspec/adapters/asyncpg/config.py +17 -19
- sqlspec/adapters/asyncpg/driver.py +249 -93
- sqlspec/adapters/duckdb/__init__.py +2 -2
- sqlspec/adapters/duckdb/config.py +2 -2
- sqlspec/adapters/duckdb/driver.py +49 -49
- sqlspec/adapters/oracledb/__init__.py +8 -8
- sqlspec/adapters/oracledb/config/__init__.py +6 -6
- sqlspec/adapters/oracledb/config/_asyncio.py +9 -10
- sqlspec/adapters/oracledb/config/_sync.py +8 -9
- sqlspec/adapters/oracledb/driver.py +114 -41
- sqlspec/adapters/psqlpy/__init__.py +0 -0
- sqlspec/adapters/psqlpy/config.py +258 -0
- sqlspec/adapters/psqlpy/driver.py +335 -0
- sqlspec/adapters/psycopg/__init__.py +10 -5
- sqlspec/adapters/psycopg/config/__init__.py +6 -6
- sqlspec/adapters/psycopg/config/_async.py +12 -12
- sqlspec/adapters/psycopg/config/_sync.py +13 -13
- sqlspec/adapters/psycopg/driver.py +180 -218
- sqlspec/adapters/sqlite/__init__.py +2 -2
- sqlspec/adapters/sqlite/config.py +2 -2
- sqlspec/adapters/sqlite/driver.py +43 -41
- sqlspec/base.py +275 -153
- sqlspec/exceptions.py +30 -0
- sqlspec/extensions/litestar/config.py +6 -0
- sqlspec/extensions/litestar/handlers.py +25 -0
- sqlspec/extensions/litestar/plugin.py +6 -1
- sqlspec/statement.py +373 -0
- sqlspec/typing.py +10 -1
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
- sqlspec-0.9.0.dist-info/RECORD +61 -0
- sqlspec-0.8.0.dist-info/RECORD +0 -57
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/exceptions.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from contextlib import contextmanager
|
|
1
3
|
from typing import Any, Optional
|
|
2
4
|
|
|
3
5
|
__all__ = (
|
|
@@ -6,7 +8,9 @@ __all__ = (
|
|
|
6
8
|
"MissingDependencyError",
|
|
7
9
|
"MultipleResultsFoundError",
|
|
8
10
|
"NotFoundError",
|
|
11
|
+
"ParameterStyleMismatchError",
|
|
9
12
|
"RepositoryError",
|
|
13
|
+
"SQLParsingError",
|
|
10
14
|
"SQLSpecError",
|
|
11
15
|
"SerializationError",
|
|
12
16
|
)
|
|
@@ -74,6 +78,20 @@ class SQLParsingError(SQLSpecError):
|
|
|
74
78
|
super().__init__(message)
|
|
75
79
|
|
|
76
80
|
|
|
81
|
+
class ParameterStyleMismatchError(SQLSpecError):
|
|
82
|
+
"""Error when parameter style doesn't match SQL placeholder style.
|
|
83
|
+
|
|
84
|
+
This exception is raised when there's a mismatch between the parameter type
|
|
85
|
+
(dictionary, tuple, etc.) and the placeholder style in the SQL query
|
|
86
|
+
(named, positional, etc.).
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, message: Optional[str] = None) -> None:
|
|
90
|
+
if message is None:
|
|
91
|
+
message = "Parameter style mismatch: dictionary parameters provided but no named placeholders found in SQL."
|
|
92
|
+
super().__init__(message)
|
|
93
|
+
|
|
94
|
+
|
|
77
95
|
class ImproperConfigurationError(SQLSpecError):
|
|
78
96
|
"""Improper Configuration error.
|
|
79
97
|
|
|
@@ -99,3 +117,15 @@ class NotFoundError(RepositoryError):
|
|
|
99
117
|
|
|
100
118
|
class MultipleResultsFoundError(RepositoryError):
|
|
101
119
|
"""A single database result was required but more than one were found."""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@contextmanager
|
|
123
|
+
def wrap_exceptions(wrap_exceptions: bool = True) -> Generator[None, None, None]:
|
|
124
|
+
try:
|
|
125
|
+
yield
|
|
126
|
+
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
if wrap_exceptions is False:
|
|
129
|
+
raise
|
|
130
|
+
msg = "An error occurred during the operation."
|
|
131
|
+
raise RepositoryError(detail=msg) from exc
|
|
@@ -12,6 +12,7 @@ from sqlspec.extensions.litestar.handlers import (
|
|
|
12
12
|
lifespan_handler_maker,
|
|
13
13
|
manual_handler_maker,
|
|
14
14
|
pool_provider_maker,
|
|
15
|
+
session_provider_maker,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
@@ -25,6 +26,7 @@ if TYPE_CHECKING:
|
|
|
25
26
|
from sqlspec.base import (
|
|
26
27
|
AsyncConfigT,
|
|
27
28
|
ConnectionT,
|
|
29
|
+
DriverT,
|
|
28
30
|
PoolT,
|
|
29
31
|
SyncConfigT,
|
|
30
32
|
)
|
|
@@ -33,6 +35,7 @@ CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
|
|
|
33
35
|
DEFAULT_COMMIT_MODE: CommitMode = "manual"
|
|
34
36
|
DEFAULT_CONNECTION_KEY = "db_connection"
|
|
35
37
|
DEFAULT_POOL_KEY = "db_pool"
|
|
38
|
+
DEFAULT_SESSION_KEY = "db_session"
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
@dataclass
|
|
@@ -40,11 +43,13 @@ class DatabaseConfig:
|
|
|
40
43
|
config: "Union[SyncConfigT, AsyncConfigT]" = field() # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
|
|
41
44
|
connection_key: str = field(default=DEFAULT_CONNECTION_KEY)
|
|
42
45
|
pool_key: str = field(default=DEFAULT_POOL_KEY)
|
|
46
|
+
session_key: str = field(default=DEFAULT_SESSION_KEY)
|
|
43
47
|
commit_mode: "CommitMode" = field(default=DEFAULT_COMMIT_MODE)
|
|
44
48
|
extra_commit_statuses: "Optional[set[int]]" = field(default=None)
|
|
45
49
|
extra_rollback_statuses: "Optional[set[int]]" = field(default=None)
|
|
46
50
|
connection_provider: "Callable[[State,Scope], Awaitable[ConnectionT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
|
|
47
51
|
pool_provider: "Callable[[State,Scope], Awaitable[PoolT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
|
|
52
|
+
session_provider: "Callable[[State,Scope], Awaitable[DriverT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
|
|
48
53
|
before_send_handler: "BeforeMessageSendHookHandler" = field(init=False, repr=False, hash=False)
|
|
49
54
|
lifespan_handler: "Callable[[Litestar], AbstractAsyncContextManager[None]]" = field(
|
|
50
55
|
init=False,
|
|
@@ -79,3 +84,4 @@ class DatabaseConfig:
|
|
|
79
84
|
self.lifespan_handler = lifespan_handler_maker(config=self.config, pool_key=self.pool_key)
|
|
80
85
|
self.connection_provider = connection_provider_maker(connection_key=self.connection_key, config=self.config)
|
|
81
86
|
self.pool_provider = pool_provider_maker(pool_key=self.pool_key, config=self.config)
|
|
87
|
+
self.session_provider = session_provider_maker(session_key=self.session_key, config=self.config)
|
|
@@ -186,3 +186,28 @@ def pool_provider_maker(
|
|
|
186
186
|
return cast("PoolT", pool)
|
|
187
187
|
|
|
188
188
|
return provide_pool
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def session_provider_maker(
|
|
192
|
+
session_key: str,
|
|
193
|
+
config: "DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]",
|
|
194
|
+
) -> "Callable[[State,Scope], Awaitable[DriverT]]":
|
|
195
|
+
"""Build the session provider for the database configuration.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
session_key: The dependency key to use for the session within Litestar.
|
|
199
|
+
config: The database configuration.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
The generated session provider for the database.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
async def provide_session(state: "State", scope: "Scope") -> "DriverT":
|
|
206
|
+
session = get_sqlspec_scope_state(scope, session_key)
|
|
207
|
+
if session is None:
|
|
208
|
+
connection = await maybe_async_(config.create_connection)()
|
|
209
|
+
session = config.driver_type(connection=connection) # pyright: ignore[reportCallIssue]
|
|
210
|
+
set_sqlspec_scope_state(scope, session_key, session)
|
|
211
|
+
return cast("DriverT", session)
|
|
212
|
+
|
|
213
|
+
return provide_session
|
|
@@ -23,6 +23,7 @@ CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
|
|
|
23
23
|
DEFAULT_COMMIT_MODE: CommitMode = "manual"
|
|
24
24
|
DEFAULT_CONNECTION_KEY = "db_connection"
|
|
25
25
|
DEFAULT_POOL_KEY = "db_pool"
|
|
26
|
+
DEFAULT_SESSION_KEY = "db_session"
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
@@ -85,7 +86,11 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
|
85
86
|
app_config.before_send.append(c.before_send_handler)
|
|
86
87
|
app_config.lifespan.append(c.lifespan_handler) # pyright: ignore[reportUnknownMemberType]
|
|
87
88
|
app_config.dependencies.update(
|
|
88
|
-
{
|
|
89
|
+
{
|
|
90
|
+
c.connection_key: Provide(c.connection_provider),
|
|
91
|
+
c.pool_key: Provide(c.pool_provider),
|
|
92
|
+
c.session_key: Provide(c.session_provider),
|
|
93
|
+
},
|
|
89
94
|
)
|
|
90
95
|
|
|
91
96
|
return app_config
|
sqlspec/statement.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# ruff: noqa: RUF100, PLR6301, PLR0912, PLR0915, C901, PLR0911, PLR0914, N806
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Optional,
|
|
9
|
+
Union,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
import sqlglot
|
|
13
|
+
from sqlglot import exp
|
|
14
|
+
|
|
15
|
+
from sqlspec.exceptions import ParameterStyleMismatchError, SQLParsingError
|
|
16
|
+
from sqlspec.typing import StatementParameterType
|
|
17
|
+
|
|
18
|
+
__all__ = ("SQLStatement",)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("sqlspec")
|
|
21
|
+
|
|
22
|
+
# Regex to find :param style placeholders, skipping those inside quotes or SQL comments
|
|
23
|
+
# Adapted from previous version in psycopg adapter
|
|
24
|
+
PARAM_REGEX = re.compile(
|
|
25
|
+
r"""(?<![:\w]) # Negative lookbehind to avoid matching things like ::type or \:escaped
|
|
26
|
+
(?:
|
|
27
|
+
(?P<dquote>"(?:[^"]|"")*") | # Double-quoted strings (support SQL standard escaping "")
|
|
28
|
+
(?P<squote>'(?:[^']|'')*') | # Single-quoted strings (support SQL standard escaping '')
|
|
29
|
+
(?P<comment>--.*?\n|\/\*.*?\*\/) | # SQL comments (single line or multi-line)
|
|
30
|
+
: (?P<var_name>[a-zA-Z_][a-zA-Z0-9_]*) # :var_name identifier
|
|
31
|
+
)
|
|
32
|
+
""",
|
|
33
|
+
re.VERBOSE | re.DOTALL,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass()
|
|
38
|
+
class SQLStatement:
|
|
39
|
+
"""An immutable representation of a SQL statement with its parameters.
|
|
40
|
+
|
|
41
|
+
This class encapsulates the SQL statement and its parameters, providing
|
|
42
|
+
a clean interface for parameter binding and SQL statement formatting.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
dialect: str
|
|
46
|
+
"""The SQL dialect to use for parsing (e.g., 'postgres', 'mysql'). Defaults to 'postgres' if None."""
|
|
47
|
+
sql: str
|
|
48
|
+
"""The raw SQL statement."""
|
|
49
|
+
parameters: Optional[StatementParameterType] = None
|
|
50
|
+
"""The parameters for the SQL statement."""
|
|
51
|
+
kwargs: Optional[dict[str, Any]] = None
|
|
52
|
+
"""Keyword arguments passed for parameter binding."""
|
|
53
|
+
|
|
54
|
+
_merged_parameters: Optional[Union[StatementParameterType, dict[str, Any]]] = None
|
|
55
|
+
|
|
56
|
+
def __post_init__(self) -> None:
|
|
57
|
+
"""Merge parameters and kwargs after initialization."""
|
|
58
|
+
merged_params = self.parameters
|
|
59
|
+
|
|
60
|
+
if self.kwargs:
|
|
61
|
+
if merged_params is None:
|
|
62
|
+
merged_params = self.kwargs
|
|
63
|
+
elif isinstance(merged_params, dict):
|
|
64
|
+
# Merge kwargs into parameters dict, kwargs take precedence
|
|
65
|
+
merged_params = {**merged_params, **self.kwargs}
|
|
66
|
+
else:
|
|
67
|
+
# If parameters is sequence or scalar, kwargs replace it
|
|
68
|
+
# Consider adding a warning here if this behavior is surprising
|
|
69
|
+
merged_params = self.kwargs
|
|
70
|
+
|
|
71
|
+
self._merged_parameters = merged_params
|
|
72
|
+
|
|
73
|
+
def process(self) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
74
|
+
"""Process the SQL statement and merged parameters for execution.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A tuple containing the processed SQL string and the processed parameters
|
|
78
|
+
ready for database driver execution.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
SQLParsingError: If the SQL statement contains parameter placeholders, but no parameters were provided.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
A tuple containing the processed SQL string and the processed parameters
|
|
85
|
+
ready for database driver execution.
|
|
86
|
+
"""
|
|
87
|
+
if self._merged_parameters is None:
|
|
88
|
+
# Validate that the SQL doesn't expect parameters if none were provided
|
|
89
|
+
# Parse ONLY if we need to validate
|
|
90
|
+
try: # Add try/except in case parsing fails even here
|
|
91
|
+
expression = self._parse_sql()
|
|
92
|
+
except SQLParsingError:
|
|
93
|
+
# If parsing fails, we can't validate, but maybe that's okay if no params were passed?
|
|
94
|
+
# Log a warning? For now, let the original error propagate if needed.
|
|
95
|
+
# Or, maybe assume it's okay if _merged_parameters is None?
|
|
96
|
+
# Let's re-raise for now, as unparsable SQL is usually bad.
|
|
97
|
+
logger.warning("SQL statement is unparsable: %s", self.sql)
|
|
98
|
+
return self.sql, None
|
|
99
|
+
if list(expression.find_all(exp.Parameter)):
|
|
100
|
+
msg = "SQL statement contains parameter placeholders, but no parameters were provided."
|
|
101
|
+
raise SQLParsingError(msg)
|
|
102
|
+
return self.sql, None
|
|
103
|
+
|
|
104
|
+
if isinstance(self._merged_parameters, dict):
|
|
105
|
+
# Pass only the dict, parsing happens inside
|
|
106
|
+
return self._process_dict_params(self._merged_parameters)
|
|
107
|
+
|
|
108
|
+
if isinstance(self._merged_parameters, (tuple, list)):
|
|
109
|
+
# Pass only the sequence, parsing happens inside if needed for validation
|
|
110
|
+
return self._process_sequence_params(self._merged_parameters)
|
|
111
|
+
|
|
112
|
+
# Assume it's a single scalar value otherwise
|
|
113
|
+
# Pass only the value, parsing happens inside for validation
|
|
114
|
+
return self._process_scalar_param(self._merged_parameters)
|
|
115
|
+
|
|
116
|
+
def _parse_sql(self) -> exp.Expression:
|
|
117
|
+
"""Parse the SQL using sqlglot.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
SQLParsingError: If the SQL statement cannot be parsed.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
The parsed SQL expression.
|
|
124
|
+
"""
|
|
125
|
+
parse_dialect = self.dialect or "postgres"
|
|
126
|
+
try:
|
|
127
|
+
read_dialect = parse_dialect or None
|
|
128
|
+
return sqlglot.parse_one(self.sql, read=read_dialect)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
# Ensure the original sqlglot error message is included
|
|
131
|
+
error_detail = str(e)
|
|
132
|
+
msg = f"Failed to parse SQL with dialect '{parse_dialect or 'auto-detected'}': {error_detail}\nSQL: {self.sql}"
|
|
133
|
+
raise SQLParsingError(msg) from e
|
|
134
|
+
|
|
135
|
+
def _process_dict_params(
|
|
136
|
+
self,
|
|
137
|
+
parameter_dict: dict[str, Any],
|
|
138
|
+
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
139
|
+
"""Processes dictionary parameters based on dialect capabilities.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ParameterStyleMismatchError: If the SQL statement contains unnamed placeholders (e.g., '?') in the SQL query.
|
|
143
|
+
SQLParsingError: If the SQL statement contains named parameters, but no parameters were provided.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
A tuple containing the processed SQL string and the processed parameters
|
|
147
|
+
ready for database driver execution.
|
|
148
|
+
"""
|
|
149
|
+
# Attempt to parse with sqlglot first (for other dialects like postgres, mysql)
|
|
150
|
+
named_sql_params: Optional[list[exp.Parameter]] = None
|
|
151
|
+
unnamed_sql_params: Optional[list[exp.Parameter]] = None
|
|
152
|
+
sqlglot_parsed_ok = False
|
|
153
|
+
# --- Dialect-Specific Bypasses for Native Handling ---
|
|
154
|
+
if self.dialect == "sqlite": # Handles :name natively
|
|
155
|
+
return self.sql, parameter_dict
|
|
156
|
+
|
|
157
|
+
# Add bypass for postgres handled by specific adapters (e.g., asyncpg)
|
|
158
|
+
if self.dialect == "postgres":
|
|
159
|
+
# The adapter (e.g., asyncpg) will handle :name -> $n conversion.
|
|
160
|
+
# SQLStatement just validates parameters against the original SQL here.
|
|
161
|
+
# Perform validation using regex if sqlglot parsing fails, otherwise use sqlglot.
|
|
162
|
+
try:
|
|
163
|
+
expression = self._parse_sql()
|
|
164
|
+
sql_params = list(expression.find_all(exp.Parameter))
|
|
165
|
+
named_sql_params = [p for p in sql_params if p.name]
|
|
166
|
+
unnamed_sql_params = [p for p in sql_params if not p.name]
|
|
167
|
+
|
|
168
|
+
if unnamed_sql_params:
|
|
169
|
+
msg = "Cannot use dictionary parameters with unnamed placeholders (e.g., '?') found by sqlglot for postgres."
|
|
170
|
+
raise ParameterStyleMismatchError(msg)
|
|
171
|
+
|
|
172
|
+
# Validate keys using sqlglot results
|
|
173
|
+
required_keys = {p.name for p in named_sql_params}
|
|
174
|
+
provided_keys = set(parameter_dict.keys())
|
|
175
|
+
missing_keys = required_keys - provided_keys
|
|
176
|
+
if missing_keys:
|
|
177
|
+
msg = (
|
|
178
|
+
f"Named parameters found in SQL (via sqlglot) but not provided: {missing_keys}. SQL: {self.sql}"
|
|
179
|
+
)
|
|
180
|
+
raise SQLParsingError(msg) # noqa: TRY301
|
|
181
|
+
# Allow extra keys
|
|
182
|
+
|
|
183
|
+
except SQLParsingError as e:
|
|
184
|
+
logger.debug("SQLglot parsing failed for postgres dict params, attempting regex validation: %s", e)
|
|
185
|
+
# Regex validation fallback (without conversion)
|
|
186
|
+
postgres_found_params_regex: list[str] = []
|
|
187
|
+
for match in PARAM_REGEX.finditer(self.sql):
|
|
188
|
+
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
189
|
+
continue
|
|
190
|
+
if match.group("var_name"):
|
|
191
|
+
var_name = match.group("var_name")
|
|
192
|
+
postgres_found_params_regex.append(var_name)
|
|
193
|
+
if var_name not in parameter_dict:
|
|
194
|
+
msg = f"Named parameter ':{var_name}' found in SQL (via regex) but not provided. SQL: {self.sql}"
|
|
195
|
+
raise SQLParsingError(msg) # noqa: B904
|
|
196
|
+
|
|
197
|
+
if not postgres_found_params_regex and parameter_dict:
|
|
198
|
+
msg = f"Dictionary parameters provided, but no named placeholders (:name) found via regex. SQL: {self.sql}"
|
|
199
|
+
raise ParameterStyleMismatchError(msg) # noqa: B904
|
|
200
|
+
# Allow extra keys with regex check too
|
|
201
|
+
|
|
202
|
+
# Return the *original* SQL and the processed dict for the adapter to handle
|
|
203
|
+
return self.sql, parameter_dict
|
|
204
|
+
|
|
205
|
+
if self.dialect == "duckdb": # Handles $name natively (and :name via driver? Check driver docs)
|
|
206
|
+
# Bypass sqlglot/regex checks. Trust user SQL ($name or ?) + dict for DuckDB driver.
|
|
207
|
+
# We lose :name -> $name conversion *if* sqlglot parsing fails, but avoid errors on valid $name SQL.
|
|
208
|
+
return self.sql, parameter_dict
|
|
209
|
+
# --- End Bypasses ---
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
expression = self._parse_sql()
|
|
213
|
+
sql_params = list(expression.find_all(exp.Parameter))
|
|
214
|
+
named_sql_params = [p for p in sql_params if p.name]
|
|
215
|
+
unnamed_sql_params = [p for p in sql_params if not p.name]
|
|
216
|
+
sqlglot_parsed_ok = True
|
|
217
|
+
logger.debug("SQLglot parsed dict params successfully for: %s", self.sql)
|
|
218
|
+
except SQLParsingError as e:
|
|
219
|
+
logger.debug("SQLglot parsing failed for dict params, attempting regex fallback: %s", e)
|
|
220
|
+
# Proceed using regex fallback below
|
|
221
|
+
|
|
222
|
+
# Check for unnamed placeholders if parsing worked
|
|
223
|
+
if sqlglot_parsed_ok and unnamed_sql_params:
|
|
224
|
+
msg = "Cannot use dictionary parameters with unnamed placeholders (e.g., '?') found by sqlglot."
|
|
225
|
+
raise ParameterStyleMismatchError(msg)
|
|
226
|
+
|
|
227
|
+
# Determine if we need to use regex fallback
|
|
228
|
+
# Use fallback if: parsing failed OR (parsing worked BUT found no named params when a dict was provided)
|
|
229
|
+
use_regex_fallback = not sqlglot_parsed_ok or (not named_sql_params and parameter_dict)
|
|
230
|
+
|
|
231
|
+
if use_regex_fallback:
|
|
232
|
+
# Regex fallback logic for :name -> self.param_style conversion
|
|
233
|
+
# ... (regex fallback code as implemented previously) ...
|
|
234
|
+
logger.debug("Using regex fallback for dict param processing: %s", self.sql)
|
|
235
|
+
# --- Regex Fallback Logic ---
|
|
236
|
+
regex_processed_sql_parts: list[str] = []
|
|
237
|
+
ordered_params = []
|
|
238
|
+
last_end = 0
|
|
239
|
+
regex_found_params: list[str] = []
|
|
240
|
+
|
|
241
|
+
for match in PARAM_REGEX.finditer(self.sql):
|
|
242
|
+
# Skip matches that are comments or quoted strings
|
|
243
|
+
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
if match.group("var_name"):
|
|
247
|
+
var_name = match.group("var_name")
|
|
248
|
+
regex_found_params.append(var_name)
|
|
249
|
+
# Get start and end from the match object for the :var_name part
|
|
250
|
+
# The var_name group itself doesn't include the leading :, so adjust start.
|
|
251
|
+
start = match.start("var_name") - 1
|
|
252
|
+
end = match.end("var_name")
|
|
253
|
+
|
|
254
|
+
if var_name not in parameter_dict:
|
|
255
|
+
msg = (
|
|
256
|
+
f"Named parameter ':{var_name}' found in SQL (via regex) but not provided. SQL: {self.sql}"
|
|
257
|
+
)
|
|
258
|
+
raise SQLParsingError(msg)
|
|
259
|
+
|
|
260
|
+
regex_processed_sql_parts.extend((self.sql[last_end:start], self.param_style)) # Use target style
|
|
261
|
+
ordered_params.append(parameter_dict[var_name])
|
|
262
|
+
last_end = end
|
|
263
|
+
|
|
264
|
+
regex_processed_sql_parts.append(self.sql[last_end:])
|
|
265
|
+
|
|
266
|
+
# Validation with regex results
|
|
267
|
+
if not regex_found_params and parameter_dict:
|
|
268
|
+
msg = f"Dictionary parameters provided, but no named placeholders (e.g., :name) found via regex in the SQL query for dialect '{self.dialect}'. SQL: {self.sql}"
|
|
269
|
+
raise ParameterStyleMismatchError(msg)
|
|
270
|
+
|
|
271
|
+
provided_keys = set(parameter_dict.keys())
|
|
272
|
+
missing_keys = set(regex_found_params) - provided_keys # Should be caught above, but double check
|
|
273
|
+
if missing_keys:
|
|
274
|
+
msg = f"Named parameters found in SQL (via regex) but not provided: {missing_keys}. SQL: {self.sql}"
|
|
275
|
+
raise SQLParsingError(msg)
|
|
276
|
+
|
|
277
|
+
extra_keys = provided_keys - set(regex_found_params)
|
|
278
|
+
if extra_keys:
|
|
279
|
+
# Allow extra keys
|
|
280
|
+
pass
|
|
281
|
+
|
|
282
|
+
return "".join(regex_processed_sql_parts), tuple(ordered_params)
|
|
283
|
+
|
|
284
|
+
# Sqlglot Logic (if parsing worked and found params)
|
|
285
|
+
# ... (sqlglot logic as implemented previously, including :name -> %s conversion) ...
|
|
286
|
+
logger.debug("Using sqlglot results for dict param processing: %s", self.sql)
|
|
287
|
+
|
|
288
|
+
# Ensure named_sql_params is iterable, default to empty list if None (shouldn't happen ideally)
|
|
289
|
+
active_named_params = named_sql_params or []
|
|
290
|
+
|
|
291
|
+
if not active_named_params and not parameter_dict:
|
|
292
|
+
# No SQL params found by sqlglot, no provided params dict -> OK
|
|
293
|
+
return self.sql, ()
|
|
294
|
+
|
|
295
|
+
# Validation with sqlglot results
|
|
296
|
+
required_keys = {p.name for p in active_named_params} # Use active_named_params
|
|
297
|
+
provided_keys = set(parameter_dict.keys())
|
|
298
|
+
|
|
299
|
+
missing_keys = required_keys - provided_keys
|
|
300
|
+
if missing_keys:
|
|
301
|
+
msg = f"Named parameters found in SQL (via sqlglot) but not provided: {missing_keys}. SQL: {self.sql}"
|
|
302
|
+
raise SQLParsingError(msg)
|
|
303
|
+
|
|
304
|
+
extra_keys = provided_keys - required_keys
|
|
305
|
+
if extra_keys:
|
|
306
|
+
pass # Allow extra keys
|
|
307
|
+
|
|
308
|
+
# Note: DuckDB handled by bypass above if sqlglot fails.
|
|
309
|
+
# This block handles successful sqlglot parse for other dialects.
|
|
310
|
+
# We don't need the specific DuckDB $name conversion here anymore,
|
|
311
|
+
# as the bypass handles the native $name case.
|
|
312
|
+
# The general logic converts :name -> self.param_style for dialects like postgres.
|
|
313
|
+
# if self.dialect == "duckdb": ... (Removed specific block here)
|
|
314
|
+
|
|
315
|
+
# For other dialects requiring positional conversion (using sqlglot param info):
|
|
316
|
+
sqlglot_processed_parts: list[str] = []
|
|
317
|
+
ordered_params = []
|
|
318
|
+
last_end = 0
|
|
319
|
+
for param in active_named_params: # Use active_named_params
|
|
320
|
+
start = param.this.this.start
|
|
321
|
+
end = param.this.this.end
|
|
322
|
+
sqlglot_processed_parts.extend((self.sql[last_end:start], self.param_style))
|
|
323
|
+
ordered_params.append(parameter_dict[param.name])
|
|
324
|
+
last_end = end
|
|
325
|
+
sqlglot_processed_parts.append(self.sql[last_end:])
|
|
326
|
+
return "".join(sqlglot_processed_parts), tuple(ordered_params)
|
|
327
|
+
|
|
328
|
+
def _process_sequence_params(
|
|
329
|
+
self, params: Union[tuple[Any, ...], list[Any]]
|
|
330
|
+
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
331
|
+
"""Processes a sequence of parameters.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
A tuple containing the processed SQL string and the processed parameters
|
|
335
|
+
ready for database driver execution.
|
|
336
|
+
"""
|
|
337
|
+
return self.sql, params
|
|
338
|
+
|
|
339
|
+
def _process_scalar_param(
|
|
340
|
+
self, param_value: Any
|
|
341
|
+
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
342
|
+
"""Processes a single scalar parameter value.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
A tuple containing the processed SQL string and the processed parameters
|
|
346
|
+
ready for database driver execution.
|
|
347
|
+
"""
|
|
348
|
+
return self.sql, (param_value,)
|
|
349
|
+
|
|
350
|
+
@cached_property
|
|
351
|
+
def param_style(self) -> str:
|
|
352
|
+
"""Get the parameter style based on the dialect.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
The parameter style placeholder for the dialect.
|
|
356
|
+
"""
|
|
357
|
+
dialect = self.dialect
|
|
358
|
+
|
|
359
|
+
# Map dialects to parameter styles for placeholder replacement
|
|
360
|
+
# Note: Used when converting named params (:name) for dialects needing positional.
|
|
361
|
+
# Dialects supporting named params natively (SQLite, DuckDB) are handled via bypasses.
|
|
362
|
+
dialect_to_param_style = {
|
|
363
|
+
"postgres": "%s",
|
|
364
|
+
"mysql": "%s",
|
|
365
|
+
"oracle": ":1",
|
|
366
|
+
"mssql": "?",
|
|
367
|
+
"bigquery": "?",
|
|
368
|
+
"snowflake": "?",
|
|
369
|
+
"cockroach": "%s",
|
|
370
|
+
"db2": "?",
|
|
371
|
+
}
|
|
372
|
+
# Default to '?' for unknown/unhandled dialects or when dialect=None is forced
|
|
373
|
+
return dialect_to_param_style.get(dialect, "?")
|
sqlspec/typing.py
CHANGED
|
@@ -7,8 +7,10 @@ from typing_extensions import TypeAlias, TypeGuard
|
|
|
7
7
|
from sqlspec._typing import (
|
|
8
8
|
LITESTAR_INSTALLED,
|
|
9
9
|
MSGSPEC_INSTALLED,
|
|
10
|
+
PYARROW_INSTALLED,
|
|
10
11
|
PYDANTIC_INSTALLED,
|
|
11
12
|
UNSET,
|
|
13
|
+
ArrowTable,
|
|
12
14
|
BaseModel,
|
|
13
15
|
DataclassProtocol,
|
|
14
16
|
DTOData,
|
|
@@ -77,7 +79,7 @@ Represents:
|
|
|
77
79
|
- :class:`DTOData`[:type:`list[ModelT]`]
|
|
78
80
|
"""
|
|
79
81
|
|
|
80
|
-
StatementParameterType: TypeAlias = "Union[dict[str, Any], list[Any], tuple[Any, ...], None]"
|
|
82
|
+
StatementParameterType: TypeAlias = "Union[Any, dict[str, Any], list[Any], tuple[Any, ...], None]"
|
|
81
83
|
"""Type alias for parameter types.
|
|
82
84
|
|
|
83
85
|
Represents:
|
|
@@ -486,9 +488,11 @@ def schema_dump( # noqa: PLR0911
|
|
|
486
488
|
__all__ = (
|
|
487
489
|
"LITESTAR_INSTALLED",
|
|
488
490
|
"MSGSPEC_INSTALLED",
|
|
491
|
+
"PYARROW_INSTALLED",
|
|
489
492
|
"PYDANTIC_INSTALLED",
|
|
490
493
|
"PYDANTIC_USE_FAILFAST",
|
|
491
494
|
"UNSET",
|
|
495
|
+
"ArrowTable",
|
|
492
496
|
"BaseModel",
|
|
493
497
|
"DataclassProtocol",
|
|
494
498
|
"Empty",
|
|
@@ -539,3 +543,8 @@ if TYPE_CHECKING:
|
|
|
539
543
|
from sqlspec._typing import UNSET, Struct, UnsetType, convert
|
|
540
544
|
else:
|
|
541
545
|
from msgspec import UNSET, Struct, UnsetType, convert # noqa: TC004
|
|
546
|
+
|
|
547
|
+
if not PYARROW_INSTALLED:
|
|
548
|
+
from sqlspec._typing import ArrowTable
|
|
549
|
+
else:
|
|
550
|
+
from pyarrow import Table as ArrowTable # noqa: TC004
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlspec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -102,6 +104,7 @@ This list is not final. If you have a driver you'd like to see added, please ope
|
|
|
102
104
|
| [`asyncpg`](https://magicstack.github.io/asyncpg/current/) | PostgreSQL | Async | ✅ |
|
|
103
105
|
| [`psycopg`](https://www.psycopg.org/) | PostgreSQL | Sync | ✅ |
|
|
104
106
|
| [`psycopg`](https://www.psycopg.org/) | PostgreSQL | Async | ✅ |
|
|
107
|
+
| [`psqlpy`](https://psqlpy-python.github.io/) | PostgreSQL | Async | ✅ |
|
|
105
108
|
| [`aiosqlite`](https://github.com/omnilib/aiosqlite) | SQLite | Async | ✅ |
|
|
106
109
|
| `sqlite3` | SQLite | Sync | ✅ |
|
|
107
110
|
| [`oracledb`](https://oracle.github.io/python-oracledb/) | Oracle | Async | ✅ |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
sqlspec/__init__.py,sha256=dsOivB7SmZhOcWkZQT12L4LHC6fQhWBk2s_3j85KkgQ,316
|
|
2
|
+
sqlspec/__metadata__.py,sha256=hNP3wXvtk8fQVPKGjRLpZ9mP-gaPJqzrmgm3UqpDIXQ,460
|
|
3
|
+
sqlspec/_serialization.py,sha256=tSwWwFImlYviC6ARdXRz0Bp4QXbCdc8cKGgZr33OglY,2657
|
|
4
|
+
sqlspec/_typing.py,sha256=6ukndnNu_XWKf2OW-HP1x1L7YmqohMwjg2HF4Hq3xdU,7111
|
|
5
|
+
sqlspec/base.py,sha256=vYXMVrdxEhaRfeAD4MVz9izY_mbCg5G3rbNchJ7vp0Q,24539
|
|
6
|
+
sqlspec/exceptions.py,sha256=EyS7B9qyyTo78CdkC5iQDYFW9eXILDA6xiSS4ABr0Ws,4033
|
|
7
|
+
sqlspec/filters.py,sha256=Gqwt4VQ7e8ffphkuR-jBfqW4m0OWGGUPiYWyEQ0Xn7M,3620
|
|
8
|
+
sqlspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
sqlspec/statement.py,sha256=lbnF_flv4sWPPiZtHX-53d72eCFcceBYB4BgQQYjnCU,17451
|
|
10
|
+
sqlspec/typing.py,sha256=AI-72LFQcwwPoW5oEdEFkNHzxnWQv6I7lxcZRoZd88U,15428
|
|
11
|
+
sqlspec/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
sqlspec/adapters/adbc/__init__.py,sha256=_mIoHMCXYWLCqGtnN8YEylhkNt3goTAvZgsT1dfEL6g,155
|
|
13
|
+
sqlspec/adapters/adbc/config.py,sha256=e2lrNsr8VN6R1ZBY16qusNj_W7npy7jp730NXCnhbBI,9866
|
|
14
|
+
sqlspec/adapters/adbc/driver.py,sha256=lP9YEf5sUmeWRBULVZurfiRvE1sMuYdGzvCibzQPALw,16255
|
|
15
|
+
sqlspec/adapters/aiosqlite/__init__.py,sha256=TQME6tGSPswTkZBjW2oH39lbxpzSwwWG5SQfgFpSe00,185
|
|
16
|
+
sqlspec/adapters/aiosqlite/config.py,sha256=EjDJq9-Jn6MKnqlpL4ZwXYxaCKD0wE5GxrSYa2Ck1bA,4788
|
|
17
|
+
sqlspec/adapters/aiosqlite/driver.py,sha256=D6kTpRxcfD_NSH7ThcVq2VbYZz0iriCh_CMjPQBl_Z8,11466
|
|
18
|
+
sqlspec/adapters/asyncmy/__init__.py,sha256=w0hjokFrsBgYS0EnIv8Dorb7jz8M3ZBZYKMUXCUYEQg,247
|
|
19
|
+
sqlspec/adapters/asyncmy/config.py,sha256=5ILV46FWjc9Cy46aMuhinpfc70b_708h9ubHj9HRCmE,9412
|
|
20
|
+
sqlspec/adapters/asyncmy/driver.py,sha256=RICHrYxv2jOUk0m4bex0k2YVc5GMnSnOdrMIp4p4fNo,8507
|
|
21
|
+
sqlspec/adapters/asyncpg/__init__.py,sha256=iR-vJYxImcw5hLaDgMqPLLRxW_vOYylEpemSg5G4dP4,261
|
|
22
|
+
sqlspec/adapters/asyncpg/config.py,sha256=DH6ZLkL4iHmRbW80VFQK1kzWIFRyGkFAZdtF3_f13AQ,9987
|
|
23
|
+
sqlspec/adapters/asyncpg/driver.py,sha256=G6CSo3k1EzmPTZr2cCnTchR7J_O-1_uNC5eKylFMysY,18557
|
|
24
|
+
sqlspec/adapters/duckdb/__init__.py,sha256=b_4QWZC0emTsbuBjcDZrxoEc83hGuBMx7Wwha3fFTAE,167
|
|
25
|
+
sqlspec/adapters/duckdb/config.py,sha256=GJTzKp1cEFhKSe94n5wOXvrT4Sz3Md8mKRxmCBnfvsM,15731
|
|
26
|
+
sqlspec/adapters/duckdb/driver.py,sha256=e91Iu84FbZ7NefqxkhmWZcwICav0lxJpAxxD08sFqUk,10160
|
|
27
|
+
sqlspec/adapters/oracledb/__init__.py,sha256=g54_WLVzZhvD1sLC2uV0nfUs6WnYUJv2vqvvG34L7v0,398
|
|
28
|
+
sqlspec/adapters/oracledb/driver.py,sha256=1YCj0pyaIMB-WXi_2w0ywRR0DlAlz5Q6k4gjGkY2yDE,23081
|
|
29
|
+
sqlspec/adapters/oracledb/config/__init__.py,sha256=emx5jWXqw3ifoW-m_tNI7sTz_duq2vRkubc0J2QqEQ4,306
|
|
30
|
+
sqlspec/adapters/oracledb/config/_asyncio.py,sha256=hu4o5q_d1O7svR_XrylWcmMrvmBzOp0T1m_Ybrbbr_o,7293
|
|
31
|
+
sqlspec/adapters/oracledb/config/_common.py,sha256=UJZL2DQQZM3uOn1E1A_gnsB8nX3-yCDXGd66PDI29_s,5691
|
|
32
|
+
sqlspec/adapters/oracledb/config/_sync.py,sha256=wvmC8bX2FhSD3W-k4RUBXnMP6cvEThsO6-vA-q5flUY,7016
|
|
33
|
+
sqlspec/adapters/psqlpy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
sqlspec/adapters/psqlpy/config.py,sha256=SFa40Fp0HyrAAT49nNoH9gYovzk0iEhm6qMmqS9OgDI,10590
|
|
35
|
+
sqlspec/adapters/psqlpy/driver.py,sha256=8DnLXFwIUkGtlu-xpj2O4lAaERIIRVVbNgkL6GQ7Qgk,12844
|
|
36
|
+
sqlspec/adapters/psycopg/__init__.py,sha256=_aCZRfDiC4lXM5UdkYEQEHZq-bqY1nyS_sioTM91Ul0,408
|
|
37
|
+
sqlspec/adapters/psycopg/driver.py,sha256=4hQfw_hHuzZJYz1gKVECRAAdchop-ZIVWg9fZjY-NUM,21296
|
|
38
|
+
sqlspec/adapters/psycopg/config/__init__.py,sha256=TIFGht0TdHKC7NTJwaJPpG-6-BRRGzWfc2Bu756R-A4,310
|
|
39
|
+
sqlspec/adapters/psycopg/config/_async.py,sha256=6YpxNLxHwvI7r7ZBKKjWnd0xbEG1i4HLOOimiiDUr6w,6631
|
|
40
|
+
sqlspec/adapters/psycopg/config/_common.py,sha256=UqqvqPE9zlSO9G_Gh6fI190cHfCDG98S0GaznGAHpdU,2181
|
|
41
|
+
sqlspec/adapters/psycopg/config/_sync.py,sha256=QIybrGVkuu45GlAcYBzpcsRq7VW6EnHff4fcNATAgsw,6443
|
|
42
|
+
sqlspec/adapters/sqlite/__init__.py,sha256=cGsMyN4_ZWApbhtwmYG18PYEBRiO-NdOU6eecBxTwRQ,167
|
|
43
|
+
sqlspec/adapters/sqlite/config.py,sha256=m2TV-3jcjxIeMSCTsvvrTm50L6uE3007WVPluvqIX3Q,4447
|
|
44
|
+
sqlspec/adapters/sqlite/driver.py,sha256=quh0Bazho05QVRWxy_Jww02I6HKLFg8Q0jUWMtfBjEU,11293
|
|
45
|
+
sqlspec/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
sqlspec/extensions/litestar/__init__.py,sha256=vJf0co-AUM75c7ZLil_TUCYilVNDtmVsdeVLbm9_xn8,512
|
|
47
|
+
sqlspec/extensions/litestar/_utils.py,sha256=UgwFxqLnjDw9S8G0H24DP2GsbMGas81W1lfhfTY68m8,1969
|
|
48
|
+
sqlspec/extensions/litestar/config.py,sha256=zq_mlwNubDhRAtAivi2d5S5ippJ1A5xhw19XC6yPC60,4443
|
|
49
|
+
sqlspec/extensions/litestar/handlers.py,sha256=6FNqIEWHUltnNuhcp8QNktK0j6-UB4isMpzoP7qCc3g,7932
|
|
50
|
+
sqlspec/extensions/litestar/plugin.py,sha256=TOXz5Np17DbNaPhPp09-88reDHE29kmMjmRe4W5_-zE,4904
|
|
51
|
+
sqlspec/utils/__init__.py,sha256=nVIUuMaHhhC1vXydoSBvnc-xTArqwWJaAtpjHhiM84Q,159
|
|
52
|
+
sqlspec/utils/deprecation.py,sha256=4pwGxoQYI3dAc3L1lh4tszZG6e2jp5m4e0ICk8SJx5M,3886
|
|
53
|
+
sqlspec/utils/fixtures.py,sha256=ni51rAuen6S1wuSi1kUwn6Qh25B-XrewPEsjV8G4gQ0,2029
|
|
54
|
+
sqlspec/utils/module_loader.py,sha256=tmMy9JcTTQETcwT8Wt8adCIuqr4zinQnPbCiBJ6JTSQ,2703
|
|
55
|
+
sqlspec/utils/sync_tools.py,sha256=xxHZ-5rnTrPEUNr7gxQUVvJd0NGJeO_MgisR8X7sI3c,10425
|
|
56
|
+
sqlspec/utils/text.py,sha256=Ya-fWBcfkQRhguNs7MNFIYtAUiArBo62w8sRPHavMWM,1476
|
|
57
|
+
sqlspec-0.9.0.dist-info/METADATA,sha256=zKfhk4Zk5WwoRzWlGqFTERFpN5kDOaFW77k7gU52tBo,9616
|
|
58
|
+
sqlspec-0.9.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
59
|
+
sqlspec-0.9.0.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
|
|
60
|
+
sqlspec-0.9.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
|
|
61
|
+
sqlspec-0.9.0.dist-info/RECORD,,
|