sqlspec 0.10.0__py3-none-any.whl → 0.11.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 +24 -32
- sqlspec/adapters/adbc/config.py +1 -1
- sqlspec/adapters/adbc/driver.py +336 -165
- sqlspec/adapters/aiosqlite/driver.py +211 -126
- sqlspec/adapters/asyncmy/driver.py +164 -68
- sqlspec/adapters/asyncpg/config.py +3 -1
- sqlspec/adapters/asyncpg/driver.py +190 -231
- sqlspec/adapters/bigquery/driver.py +178 -169
- sqlspec/adapters/duckdb/driver.py +175 -84
- sqlspec/adapters/oracledb/driver.py +224 -90
- sqlspec/adapters/psqlpy/driver.py +267 -187
- sqlspec/adapters/psycopg/driver.py +138 -184
- sqlspec/adapters/sqlite/driver.py +153 -121
- sqlspec/base.py +57 -45
- sqlspec/extensions/litestar/__init__.py +3 -12
- sqlspec/extensions/litestar/config.py +22 -7
- sqlspec/extensions/litestar/handlers.py +142 -85
- sqlspec/extensions/litestar/plugin.py +9 -8
- sqlspec/extensions/litestar/providers.py +521 -0
- sqlspec/filters.py +214 -11
- sqlspec/mixins.py +152 -2
- sqlspec/statement.py +276 -271
- sqlspec/typing.py +18 -1
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/singleton.py +35 -0
- sqlspec/utils/sync_tools.py +90 -151
- sqlspec/utils/text.py +68 -5
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/METADATA +5 -1
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/RECORD +32 -30
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/typing.py
CHANGED
|
@@ -474,7 +474,7 @@ def dataclass_to_dict(
|
|
|
474
474
|
return cast("dict[str, Any]", ret)
|
|
475
475
|
|
|
476
476
|
|
|
477
|
-
def schema_dump(
|
|
477
|
+
def schema_dump(
|
|
478
478
|
data: "Union[dict[str, Any], DataclassProtocol, Struct, BaseModel]",
|
|
479
479
|
exclude_unset: bool = True,
|
|
480
480
|
) -> "dict[str, Any]":
|
|
@@ -503,6 +503,18 @@ def schema_dump( # noqa: PLR0911
|
|
|
503
503
|
return cast("dict[str, Any]", data)
|
|
504
504
|
|
|
505
505
|
|
|
506
|
+
def is_dto_data(v: Any) -> TypeGuard[DTOData[Any]]:
|
|
507
|
+
"""Check if a value is a Litestar DTOData object.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
v: Value to check.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
bool
|
|
514
|
+
"""
|
|
515
|
+
return LITESTAR_INSTALLED and isinstance(v, DTOData)
|
|
516
|
+
|
|
517
|
+
|
|
506
518
|
__all__ = (
|
|
507
519
|
"LITESTAR_INSTALLED",
|
|
508
520
|
"MSGSPEC_INSTALLED",
|
|
@@ -536,6 +548,7 @@ __all__ = (
|
|
|
536
548
|
"is_dict",
|
|
537
549
|
"is_dict_with_field",
|
|
538
550
|
"is_dict_without_field",
|
|
551
|
+
"is_dto_data",
|
|
539
552
|
"is_msgspec_struct",
|
|
540
553
|
"is_msgspec_struct_with_field",
|
|
541
554
|
"is_msgspec_struct_without_field",
|
|
@@ -566,3 +579,7 @@ if TYPE_CHECKING:
|
|
|
566
579
|
from sqlspec._typing import ArrowTable
|
|
567
580
|
else:
|
|
568
581
|
from pyarrow import Table as ArrowTable # noqa: TC004
|
|
582
|
+
if not LITESTAR_INSTALLED:
|
|
583
|
+
from sqlspec._typing import DTOData
|
|
584
|
+
else:
|
|
585
|
+
from litestar.dto import DTOData # noqa: TC004
|
sqlspec/utils/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from sqlspec.utils import deprecation, fixtures, module_loader, sync_tools, text
|
|
1
|
+
from sqlspec.utils import deprecation, fixtures, module_loader, singleton, sync_tools, text
|
|
2
2
|
|
|
3
|
-
__all__ = ("deprecation", "fixtures", "module_loader", "sync_tools", "text")
|
|
3
|
+
__all__ = ("deprecation", "fixtures", "module_loader", "singleton", "sync_tools", "text")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Any, TypeVar
|
|
2
|
+
|
|
3
|
+
__all__ = ("SingletonMeta",)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_T = TypeVar("_T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SingletonMeta(type):
|
|
10
|
+
"""Metaclass for singleton pattern."""
|
|
11
|
+
|
|
12
|
+
# We store instances keyed by the class type
|
|
13
|
+
_instances: dict[type, object] = {}
|
|
14
|
+
|
|
15
|
+
def __call__(cls: type[_T], *args: Any, **kwargs: Any) -> _T:
|
|
16
|
+
"""Call method for the singleton metaclass.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
cls: The class being instantiated.
|
|
20
|
+
*args: Positional arguments for the class constructor.
|
|
21
|
+
**kwargs: Keyword arguments for the class constructor.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
The singleton instance of the class.
|
|
25
|
+
"""
|
|
26
|
+
# Use SingletonMeta._instances to access the class attribute
|
|
27
|
+
if cls not in SingletonMeta._instances: # pyright: ignore[reportUnnecessaryContains]
|
|
28
|
+
# Create the instance using super().__call__ which calls the class's __new__ and __init__
|
|
29
|
+
instance = super().__call__(*args, **kwargs) # type: ignore[misc]
|
|
30
|
+
SingletonMeta._instances[cls] = instance
|
|
31
|
+
|
|
32
|
+
# Return the cached instance. We cast here because the dictionary stores `object`,
|
|
33
|
+
# but we know it's of type _T for the given cls key.
|
|
34
|
+
# Mypy might need an ignore here depending on configuration, but pyright should handle it.
|
|
35
|
+
return SingletonMeta._instances[cls] # type: ignore[return-value]
|
sqlspec/utils/sync_tools.py
CHANGED
|
@@ -30,139 +30,6 @@ ParamSpecT = ParamSpec("ParamSpecT")
|
|
|
30
30
|
T = TypeVar("T")
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class PendingType:
|
|
34
|
-
def __repr__(self) -> str:
|
|
35
|
-
return "AsyncPending"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Pending = PendingType()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class PendingValueError(Exception):
|
|
42
|
-
"""Exception raised when a value is accessed before it is ready."""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class SoonValue(Generic[T]):
|
|
46
|
-
"""Holds a value that will be available soon after an async operation."""
|
|
47
|
-
|
|
48
|
-
def __init__(self) -> None:
|
|
49
|
-
self._stored_value: Union[T, PendingType] = Pending
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def value(self) -> "T":
|
|
53
|
-
if isinstance(self._stored_value, PendingType):
|
|
54
|
-
msg = "The return value of this task is still pending."
|
|
55
|
-
raise PendingValueError(msg)
|
|
56
|
-
return self._stored_value
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def ready(self) -> bool:
|
|
60
|
-
return not isinstance(self._stored_value, PendingType)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class TaskGroup:
|
|
64
|
-
"""Manages a group of asyncio tasks, allowing them to be run concurrently."""
|
|
65
|
-
|
|
66
|
-
def __init__(self) -> None:
|
|
67
|
-
self._tasks: set[asyncio.Task[Any]] = set()
|
|
68
|
-
self._exceptions: list[BaseException] = []
|
|
69
|
-
self._closed = False
|
|
70
|
-
|
|
71
|
-
async def __aenter__(self) -> "TaskGroup":
|
|
72
|
-
if self._closed:
|
|
73
|
-
msg = "Cannot enter a task group that has already been closed."
|
|
74
|
-
raise RuntimeError(msg)
|
|
75
|
-
return self
|
|
76
|
-
|
|
77
|
-
async def __aexit__(
|
|
78
|
-
self,
|
|
79
|
-
exc_type: "Optional[type[BaseException]]", # noqa: PYI036
|
|
80
|
-
exc_val: "Optional[BaseException]", # noqa: PYI036
|
|
81
|
-
exc_tb: "Optional[TracebackType]", # noqa: PYI036
|
|
82
|
-
) -> None:
|
|
83
|
-
self._closed = True
|
|
84
|
-
if exc_val:
|
|
85
|
-
self._exceptions.append(exc_val)
|
|
86
|
-
|
|
87
|
-
if self._tasks:
|
|
88
|
-
await asyncio.wait(self._tasks)
|
|
89
|
-
|
|
90
|
-
if self._exceptions:
|
|
91
|
-
# Re-raise the first exception encountered.
|
|
92
|
-
raise self._exceptions[0]
|
|
93
|
-
|
|
94
|
-
def create_task(self, coro: "Coroutine[Any, Any, Any]") -> "asyncio.Task[Any]":
|
|
95
|
-
"""Create and add a coroutine as a task to the task group.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
coro (Coroutine): The coroutine to be added as a task.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
asyncio.Task: The created asyncio task.
|
|
102
|
-
|
|
103
|
-
Raises:
|
|
104
|
-
RuntimeError: If the task group has already been closed.
|
|
105
|
-
"""
|
|
106
|
-
if self._closed:
|
|
107
|
-
msg = "Cannot create a task in a task group that has already been closed."
|
|
108
|
-
raise RuntimeError(msg)
|
|
109
|
-
task = asyncio.create_task(coro)
|
|
110
|
-
self._tasks.add(task)
|
|
111
|
-
task.add_done_callback(self._tasks.discard)
|
|
112
|
-
task.add_done_callback(self._check_result)
|
|
113
|
-
return task
|
|
114
|
-
|
|
115
|
-
def _check_result(self, task: "asyncio.Task[Any]") -> None:
|
|
116
|
-
"""Check and store exceptions from a completed task.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
task (asyncio.Task): The task to check for exceptions.
|
|
120
|
-
"""
|
|
121
|
-
try:
|
|
122
|
-
task.result() # This will raise the exception if one occurred.
|
|
123
|
-
except Exception as e: # noqa: BLE001
|
|
124
|
-
self._exceptions.append(e)
|
|
125
|
-
|
|
126
|
-
def start_soon_(
|
|
127
|
-
self,
|
|
128
|
-
async_function: "Callable[ParamSpecT, Awaitable[T]]",
|
|
129
|
-
name: object = None,
|
|
130
|
-
) -> "Callable[ParamSpecT, SoonValue[T]]":
|
|
131
|
-
"""Create a function to start a new task in this task group.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
async_function (Callable): An async function to call soon.
|
|
135
|
-
name (object, optional): Name of the task for introspection and debugging.
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
Callable: A function that starts the task and returns a SoonValue object.
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
@functools.wraps(async_function)
|
|
142
|
-
def wrapper(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> "SoonValue[T]":
|
|
143
|
-
partial_f = functools.partial(async_function, *args, **kwargs)
|
|
144
|
-
soon_value: SoonValue[T] = SoonValue()
|
|
145
|
-
|
|
146
|
-
@functools.wraps(partial_f)
|
|
147
|
-
async def value_wrapper(*args: "Any") -> None:
|
|
148
|
-
value = await partial_f()
|
|
149
|
-
soon_value._stored_value = value # pyright: ignore[reportPrivateUsage] # noqa: SLF001
|
|
150
|
-
|
|
151
|
-
self.create_task(value_wrapper) # type: ignore[arg-type]
|
|
152
|
-
return soon_value
|
|
153
|
-
|
|
154
|
-
return wrapper
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def create_task_group() -> "TaskGroup":
|
|
158
|
-
"""Create a TaskGroup for managing multiple concurrent async tasks.
|
|
159
|
-
|
|
160
|
-
Returns:
|
|
161
|
-
TaskGroup: A new TaskGroup instance.
|
|
162
|
-
"""
|
|
163
|
-
return TaskGroup()
|
|
164
|
-
|
|
165
|
-
|
|
166
33
|
class CapacityLimiter:
|
|
167
34
|
"""Limits the number of concurrent operations using a semaphore."""
|
|
168
35
|
|
|
@@ -195,7 +62,7 @@ class CapacityLimiter:
|
|
|
195
62
|
self.release()
|
|
196
63
|
|
|
197
64
|
|
|
198
|
-
_default_limiter = CapacityLimiter(
|
|
65
|
+
_default_limiter = CapacityLimiter(15)
|
|
199
66
|
|
|
200
67
|
|
|
201
68
|
def run_(async_function: "Callable[ParamSpecT, Coroutine[Any, Any, ReturnT]]") -> "Callable[ParamSpecT, ReturnT]":
|
|
@@ -237,6 +104,7 @@ def await_(
|
|
|
237
104
|
Args:
|
|
238
105
|
async_function (Callable): The async function to convert.
|
|
239
106
|
raise_sync_error (bool, optional): If False, runs in a new event loop if no loop is present.
|
|
107
|
+
If True (default), raises RuntimeError if no loop is running.
|
|
240
108
|
|
|
241
109
|
Returns:
|
|
242
110
|
Callable: A blocking function that runs the async function.
|
|
@@ -248,12 +116,39 @@ def await_(
|
|
|
248
116
|
try:
|
|
249
117
|
loop = asyncio.get_running_loop()
|
|
250
118
|
except RuntimeError:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
119
|
+
# No running event loop
|
|
120
|
+
if raise_sync_error:
|
|
121
|
+
msg = "await_ called without a running event loop and raise_sync_error=True"
|
|
122
|
+
raise RuntimeError(msg) from None
|
|
123
|
+
return asyncio.run(partial_f())
|
|
124
|
+
else:
|
|
125
|
+
# Running in an existing event loop.
|
|
126
|
+
if loop.is_running():
|
|
127
|
+
try:
|
|
128
|
+
# Check if the current context is within a task managed by this loop
|
|
129
|
+
current_task = asyncio.current_task(loop=loop)
|
|
130
|
+
except RuntimeError:
|
|
131
|
+
# Not running inside a task managed by this loop
|
|
132
|
+
current_task = None
|
|
133
|
+
|
|
134
|
+
if current_task is not None:
|
|
135
|
+
# Called from within the event loop's execution context (a task).
|
|
136
|
+
# Blocking here would deadlock the loop.
|
|
137
|
+
msg = "await_ cannot be called from within an async task running on the same event loop. Use 'await' instead."
|
|
138
|
+
raise RuntimeError(msg)
|
|
139
|
+
# Called from a different thread than the loop's thread.
|
|
140
|
+
# It's safe to block this thread and wait for the loop.
|
|
141
|
+
future = asyncio.run_coroutine_threadsafe(partial_f(), loop)
|
|
142
|
+
# This blocks the *calling* thread, not the loop thread.
|
|
143
|
+
return future.result()
|
|
144
|
+
# This case should ideally not happen if get_running_loop() succeeded
|
|
145
|
+
# but the loop isn't running, but handle defensively.
|
|
146
|
+
# loop is not running
|
|
147
|
+
if raise_sync_error:
|
|
148
|
+
msg = "await_ found a non-running loop via get_running_loop()"
|
|
149
|
+
raise RuntimeError(msg)
|
|
150
|
+
# Fallback to running in a new loop
|
|
254
151
|
return asyncio.run(partial_f())
|
|
255
|
-
# Running in an existing event loop
|
|
256
|
-
return asyncio.run(partial_f())
|
|
257
152
|
|
|
258
153
|
return wrapper
|
|
259
154
|
|
|
@@ -286,9 +181,17 @@ def async_(
|
|
|
286
181
|
return wrapper
|
|
287
182
|
|
|
288
183
|
|
|
289
|
-
def
|
|
184
|
+
def ensure_async_(
|
|
290
185
|
function: "Callable[ParamSpecT, Union[Awaitable[ReturnT], ReturnT]]",
|
|
291
186
|
) -> "Callable[ParamSpecT, Awaitable[ReturnT]]":
|
|
187
|
+
"""Convert a function to an async one if it is not already.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
function (Callable): The function to convert.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Callable: An async function that runs the original function.
|
|
194
|
+
"""
|
|
292
195
|
if inspect.iscoroutinefunction(function):
|
|
293
196
|
return function
|
|
294
197
|
|
|
@@ -301,16 +204,6 @@ def maybe_async_(
|
|
|
301
204
|
return wrapper
|
|
302
205
|
|
|
303
206
|
|
|
304
|
-
def wrap_sync(fn: "Callable[ParamSpecT, ReturnT]") -> "Callable[ParamSpecT, Awaitable[ReturnT]]":
|
|
305
|
-
if inspect.iscoroutinefunction(fn):
|
|
306
|
-
return fn
|
|
307
|
-
|
|
308
|
-
async def wrapped(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> ReturnT:
|
|
309
|
-
return await async_(functools.partial(fn, *args, **kwargs))()
|
|
310
|
-
|
|
311
|
-
return wrapped
|
|
312
|
-
|
|
313
|
-
|
|
314
207
|
class _ContextManagerWrapper(Generic[T]):
|
|
315
208
|
def __init__(self, cm: AbstractContextManager[T]) -> None:
|
|
316
209
|
self._cm = cm
|
|
@@ -327,9 +220,55 @@ class _ContextManagerWrapper(Generic[T]):
|
|
|
327
220
|
return self._cm.__exit__(exc_type, exc_val, exc_tb)
|
|
328
221
|
|
|
329
222
|
|
|
330
|
-
def
|
|
223
|
+
def with_ensure_async_(
|
|
331
224
|
obj: "Union[AbstractContextManager[T], AbstractAsyncContextManager[T]]",
|
|
332
225
|
) -> "AbstractAsyncContextManager[T]":
|
|
226
|
+
"""Convert a context manager to an async one if it is not already.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
obj (AbstractContextManager[T] or AbstractAsyncContextManager[T]): The context manager to convert.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
AbstractAsyncContextManager[T]: An async context manager that runs the original context manager.
|
|
233
|
+
"""
|
|
234
|
+
|
|
333
235
|
if isinstance(obj, AbstractContextManager):
|
|
334
236
|
return cast("AbstractAsyncContextManager[T]", _ContextManagerWrapper(obj))
|
|
335
237
|
return obj
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class NoValue:
|
|
241
|
+
"""A fake "Empty class"""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def get_next(iterable: Any, default: Any = NoValue, *args: Any) -> Any: # pragma: no cover
|
|
245
|
+
"""Return the next item from an async iterator.
|
|
246
|
+
|
|
247
|
+
In Python <3.10, `anext` is not available. This function is a drop-in replacement.
|
|
248
|
+
|
|
249
|
+
This function will return the next value form an async iterable. If the
|
|
250
|
+
iterable is empty the StopAsyncIteration will be propagated. However, if
|
|
251
|
+
a default value is given as a second argument the exception is silenced and
|
|
252
|
+
the default value is returned instead.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
iterable: An async iterable.
|
|
256
|
+
default: An optional default value to return if the iterable is empty.
|
|
257
|
+
*args: The remaining args
|
|
258
|
+
Return:
|
|
259
|
+
The next value of the iterable.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
StopAsyncIteration: The iterable given is not async.
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
"""
|
|
266
|
+
has_default = bool(not isinstance(default, NoValue))
|
|
267
|
+
try:
|
|
268
|
+
return await iterable.__anext__()
|
|
269
|
+
|
|
270
|
+
except StopAsyncIteration as exc:
|
|
271
|
+
if has_default:
|
|
272
|
+
return default
|
|
273
|
+
|
|
274
|
+
raise StopAsyncIteration from exc
|
sqlspec/utils/text.py
CHANGED
|
@@ -2,23 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import unicodedata
|
|
5
|
+
from functools import lru_cache
|
|
5
6
|
from typing import Optional
|
|
6
7
|
|
|
8
|
+
# Compiled regex for slugify
|
|
9
|
+
_SLUGIFY_REMOVE_INVALID_CHARS_RE = re.compile(r"[^\w\s-]")
|
|
10
|
+
_SLUGIFY_COLLAPSE_SEPARATORS_RE = re.compile(r"[-\s]+")
|
|
11
|
+
|
|
12
|
+
# Compiled regex for snake_case
|
|
13
|
+
# Handles sequences like "HTTPRequest" -> "HTTP_Request" or "SSLError" -> "SSL_Error"
|
|
14
|
+
_SNAKE_CASE_RE_ACRONYM_SEQUENCE = re.compile(r"([A-Z\d]+)([A-Z][a-z])")
|
|
15
|
+
# Handles transitions like "camelCase" -> "camel_Case" or "PascalCase" -> "Pascal_Case" (partially)
|
|
16
|
+
_SNAKE_CASE_RE_LOWER_UPPER_TRANSITION = re.compile(r"([a-z\d])([A-Z])")
|
|
17
|
+
# Replaces hyphens, spaces, and dots with a single underscore
|
|
18
|
+
_SNAKE_CASE_RE_REPLACE_SEP = re.compile(r"[-\s.]+")
|
|
19
|
+
# Cleans up multiple consecutive underscores
|
|
20
|
+
_SNAKE_CASE_RE_CLEAN_MULTIPLE_UNDERSCORE = re.compile(r"__+")
|
|
21
|
+
|
|
7
22
|
__all__ = (
|
|
23
|
+
"camelize",
|
|
8
24
|
"check_email",
|
|
9
25
|
"slugify",
|
|
26
|
+
"snake_case",
|
|
10
27
|
)
|
|
11
28
|
|
|
12
29
|
|
|
13
30
|
def check_email(email: str) -> str:
|
|
14
|
-
"""Validate an email.
|
|
31
|
+
"""Validate an email.
|
|
32
|
+
|
|
33
|
+
Very simple email validation.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
email (str): The email to validate.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: If the email is invalid.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
str: The validated email.
|
|
43
|
+
"""
|
|
15
44
|
if "@" not in email:
|
|
16
45
|
msg = "Invalid email!"
|
|
17
46
|
raise ValueError(msg)
|
|
18
47
|
return email.lower()
|
|
19
48
|
|
|
20
49
|
|
|
21
|
-
def slugify(value: str, allow_unicode: bool = False, separator:
|
|
50
|
+
def slugify(value: str, allow_unicode: bool = False, separator: Optional[str] = None) -> str:
|
|
22
51
|
"""Slugify.
|
|
23
52
|
|
|
24
53
|
Convert to ASCII if ``allow_unicode`` is ``False``. Convert spaces or repeated
|
|
@@ -39,7 +68,41 @@ def slugify(value: str, allow_unicode: bool = False, separator: "Optional[str]"
|
|
|
39
68
|
value = unicodedata.normalize("NFKC", value)
|
|
40
69
|
else:
|
|
41
70
|
value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
|
|
42
|
-
value =
|
|
71
|
+
value = _SLUGIFY_REMOVE_INVALID_CHARS_RE.sub("", value.lower())
|
|
43
72
|
if separator is not None:
|
|
44
|
-
return
|
|
45
|
-
return
|
|
73
|
+
return _SLUGIFY_COLLAPSE_SEPARATORS_RE.sub("-", value).strip("-_").replace("-", separator)
|
|
74
|
+
return _SLUGIFY_COLLAPSE_SEPARATORS_RE.sub("-", value).strip("-_")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@lru_cache(maxsize=100)
|
|
78
|
+
def camelize(string: str) -> str:
|
|
79
|
+
"""Convert a string to camel case.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
string (str): The string to convert.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
str: The converted string.
|
|
86
|
+
"""
|
|
87
|
+
return "".join(word if index == 0 else word.capitalize() for index, word in enumerate(string.split("_")))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@lru_cache(maxsize=100)
|
|
91
|
+
def snake_case(string: str) -> str:
|
|
92
|
+
"""Convert a string to snake_case.
|
|
93
|
+
|
|
94
|
+
Handles CamelCase, PascalCase, strings with spaces, hyphens, or dots
|
|
95
|
+
as separators, and ensures single underscores. It also correctly
|
|
96
|
+
handles acronyms (e.g., "HTTPRequest" becomes "http_request").
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
string: The string to convert.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The snake_case version of the string.
|
|
103
|
+
"""
|
|
104
|
+
s = _SNAKE_CASE_RE_ACRONYM_SEQUENCE.sub(r"\1_\2", string)
|
|
105
|
+
s = _SNAKE_CASE_RE_LOWER_UPPER_TRANSITION.sub(r"\1_\2", s)
|
|
106
|
+
s = _SNAKE_CASE_RE_REPLACE_SEP.sub("_", s).lower()
|
|
107
|
+
s = _SNAKE_CASE_RE_CLEAN_MULTIPLE_UNDERSCORE.sub("_", s)
|
|
108
|
+
return s.strip("_")
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlspec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
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
|
|
5
8
|
Author-email: Cody Fincher <cody@litestar.dev>
|
|
6
9
|
Maintainer-email: Litestar Developers <hello@litestar.dev>
|
|
7
10
|
License-Expression: MIT
|
|
@@ -258,6 +261,7 @@ This list is not final. If you have a driver you'd like to see added, please ope
|
|
|
258
261
|
| [`spanner`](https://googleapis.dev/python/spanner/latest/index.html) | Spanner | Sync | 🗓️ |
|
|
259
262
|
| [`sqlserver`](https://docs.microsoft.com/en-us/sql/connect/python/pyodbc/python-sql-driver-for-pyodbc?view=sql-server-ver16) | SQL Server | Sync | 🗓️ |
|
|
260
263
|
| [`mysql`](https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysql-connector-python.html) | MySQL | Sync | 🗓️ |
|
|
264
|
+
| [`asyncmy`](https://github.com/long2ice/asyncmy) | MySQL | Async | ✅ |
|
|
261
265
|
| [`snowflake`](https://docs.snowflake.com) | Snowflake | Sync | 🗓️ |
|
|
262
266
|
|
|
263
267
|
## Proposed Project Structure
|
|
@@ -1,67 +1,69 @@
|
|
|
1
1
|
sqlspec/__init__.py,sha256=Dl1QOZAK21--ORQcRn15n4YPFklFMe2-Lb7TWWq_WQE,338
|
|
2
2
|
sqlspec/__metadata__.py,sha256=hNP3wXvtk8fQVPKGjRLpZ9mP-gaPJqzrmgm3UqpDIXQ,460
|
|
3
3
|
sqlspec/_serialization.py,sha256=tSwWwFImlYviC6ARdXRz0Bp4QXbCdc8cKGgZr33OglY,2657
|
|
4
|
-
sqlspec/_typing.py,sha256=
|
|
5
|
-
sqlspec/base.py,sha256=
|
|
4
|
+
sqlspec/_typing.py,sha256=a8QTy3oqdUfjrA6Iu4PxfQcUf9DuG9pZcalcevM_xIU,7037
|
|
5
|
+
sqlspec/base.py,sha256=Q6fIHtOqYXAcNt8fFe6zeqP34MLWm72do-l2Xc8pjLA,34140
|
|
6
6
|
sqlspec/exceptions.py,sha256=WnA56CdDSSdOjA5UE4jnfXXtrfClgwjCRxT-id0eVAo,4302
|
|
7
|
-
sqlspec/filters.py,sha256=
|
|
8
|
-
sqlspec/mixins.py,sha256=
|
|
7
|
+
sqlspec/filters.py,sha256=Up8lDFbAcObwhxHApxlqh84Zco57wPLIeMxdxiyUih4,11999
|
|
8
|
+
sqlspec/mixins.py,sha256=TkXGJg8eR54_LRGgzAKz0v6BT98XPNEwXUoMq02nckY,10416
|
|
9
9
|
sqlspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
sqlspec/statement.py,sha256=
|
|
11
|
-
sqlspec/typing.py,sha256=
|
|
10
|
+
sqlspec/statement.py,sha256=LNagGHKt-ixjom5WW5VLRzT9YIJC0lOfvwnn41Zz-fw,16433
|
|
11
|
+
sqlspec/typing.py,sha256=XAQpyOPAcHzlGScY3DCxIfiaQpfbFP4PntIgvYdcLwk,16313
|
|
12
12
|
sqlspec/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
sqlspec/adapters/adbc/__init__.py,sha256=Ih0mdciGyhmOlqmz4uiByj7mEaqvP4upRjYhlUNopAQ,193
|
|
14
|
-
sqlspec/adapters/adbc/config.py,sha256=
|
|
15
|
-
sqlspec/adapters/adbc/driver.py,sha256=
|
|
14
|
+
sqlspec/adapters/adbc/config.py,sha256=yayt62Mx789_B7Xo8DrlWCyeXclFchL4OxUxZIFnh9I,9646
|
|
15
|
+
sqlspec/adapters/adbc/driver.py,sha256=qOvZ1g-S02GRPX35AywciwB_S9d7ffXkaE_obd8EP-w,28871
|
|
16
16
|
sqlspec/adapters/aiosqlite/__init__.py,sha256=CndH49GREnHuIB-jY1XAxVlLwZTvKNuk8XJu9jiiM7A,233
|
|
17
17
|
sqlspec/adapters/aiosqlite/config.py,sha256=tE-DnjKbdy5FaqqRyA1q3s06rMSAglt0HkhO2oWQT2w,4603
|
|
18
|
-
sqlspec/adapters/aiosqlite/driver.py,sha256=
|
|
18
|
+
sqlspec/adapters/aiosqlite/driver.py,sha256=d9Gz4bQmmqPhkJ0GrGNRVKwzPrUOEeHjWrj-Rr-7GA4,16802
|
|
19
19
|
sqlspec/adapters/asyncmy/__init__.py,sha256=7jQFc0WOgc58XPeIiDgX9a95scMa6PuFgGXQaxG33nI,291
|
|
20
20
|
sqlspec/adapters/asyncmy/config.py,sha256=5ILV46FWjc9Cy46aMuhinpfc70b_708h9ubHj9HRCmE,9412
|
|
21
|
-
sqlspec/adapters/asyncmy/driver.py,sha256=
|
|
21
|
+
sqlspec/adapters/asyncmy/driver.py,sha256=jbaO3O3heNVF016FmlbHt0tCR2yczS2dlbttx9A2oUU,17207
|
|
22
22
|
sqlspec/adapters/asyncpg/__init__.py,sha256=iR-vJYxImcw5hLaDgMqPLLRxW_vOYylEpemSg5G4dP4,261
|
|
23
|
-
sqlspec/adapters/asyncpg/config.py,sha256=
|
|
24
|
-
sqlspec/adapters/asyncpg/driver.py,sha256=
|
|
23
|
+
sqlspec/adapters/asyncpg/config.py,sha256=cuQ_cCQ6xtqluaNO5Ax9vqZ-4JZoaUR31-QE6xAVbns,10011
|
|
24
|
+
sqlspec/adapters/asyncpg/driver.py,sha256=cmyxzieTcXSOnvNHdPORg5qHTGqYJyIi-ogNMBbL6hg,20495
|
|
25
25
|
sqlspec/adapters/bigquery/__init__.py,sha256=8kcl0_tnvnk8omVKnAljIY9n2XVnzkPRCfhfedGO0m4,264
|
|
26
|
-
sqlspec/adapters/bigquery/driver.py,sha256=
|
|
26
|
+
sqlspec/adapters/bigquery/driver.py,sha256=IcYWbP61-z2xLs7f5Df6mWidpWnDXbD9ONn7bFSOvo4,27601
|
|
27
27
|
sqlspec/adapters/bigquery/config/__init__.py,sha256=4ij4LAS-kIt-vOK8KetewVoYN90i1RV_BjtowKWDIs0,150
|
|
28
28
|
sqlspec/adapters/bigquery/config/_common.py,sha256=LSbBC302-Ewx8XHTRzYR-tInMYywVW9I6UcL8cO7-HQ,1901
|
|
29
29
|
sqlspec/adapters/bigquery/config/_sync.py,sha256=oglaEErXuce1TnE2wj5KrfFLkScRGFYAoOTCVV5BFcw,3323
|
|
30
30
|
sqlspec/adapters/duckdb/__init__.py,sha256=GMSHigyHgtaJKu751kE_Sxl5Ry4hnQOnFUSIeLSg1qs,209
|
|
31
31
|
sqlspec/adapters/duckdb/config.py,sha256=b-Ev7UnpguC12CJSxK9DDqcPw0R2dZUZXaVIePf_EEg,15700
|
|
32
|
-
sqlspec/adapters/duckdb/driver.py,sha256=
|
|
32
|
+
sqlspec/adapters/duckdb/driver.py,sha256=OZX9bx8qAK5N19KeM1OunZbHXfduSk_0CScQWH68SdA,16117
|
|
33
33
|
sqlspec/adapters/oracledb/__init__.py,sha256=vVe8cXZJLFvBA6LPx4NzGRLdOeRugzRjz92UYjb0lC0,521
|
|
34
|
-
sqlspec/adapters/oracledb/driver.py,sha256=
|
|
34
|
+
sqlspec/adapters/oracledb/driver.py,sha256=6KH-GNpHIjKKIV2uzD1SGo4QBUPBN2plyRHKnTst7vo,37603
|
|
35
35
|
sqlspec/adapters/oracledb/config/__init__.py,sha256=emx5jWXqw3ifoW-m_tNI7sTz_duq2vRkubc0J2QqEQ4,306
|
|
36
36
|
sqlspec/adapters/oracledb/config/_asyncio.py,sha256=k0wGr4FflFR03jUSgrw-4LC4mYtRlyH9gnbbBXNcMRM,7310
|
|
37
37
|
sqlspec/adapters/oracledb/config/_common.py,sha256=UJZL2DQQZM3uOn1E1A_gnsB8nX3-yCDXGd66PDI29_s,5691
|
|
38
38
|
sqlspec/adapters/oracledb/config/_sync.py,sha256=nm5FnrRG1ScrNviw3MR_40Vq8WJCXX5mJGtHhhRTPb0,7055
|
|
39
39
|
sqlspec/adapters/psqlpy/__init__.py,sha256=K8UlQrKfbCZmxGAaT4CHzQvdwGPxPLB9TDOHJgkESFc,251
|
|
40
40
|
sqlspec/adapters/psqlpy/config.py,sha256=qssXcu_Nd6o_X8QU1i61sAXwi9FiTApWxiTRU0gyBhk,10205
|
|
41
|
-
sqlspec/adapters/psqlpy/driver.py,sha256=
|
|
41
|
+
sqlspec/adapters/psqlpy/driver.py,sha256=AzoD-Gfm7VGoBXZYPORlzn_6ZE5A6blg2uV4COQF4aI,21156
|
|
42
42
|
sqlspec/adapters/psycopg/__init__.py,sha256=xmFWHSB6hwPNQS1wpBmAczJThABmhRv2PDNKqaMFX3E,535
|
|
43
|
-
sqlspec/adapters/psycopg/driver.py,sha256=
|
|
43
|
+
sqlspec/adapters/psycopg/driver.py,sha256=1YiIz_HX6tNj7LhU6gKxW4sr-5iza_eBPJ4VZPygy9w,27529
|
|
44
44
|
sqlspec/adapters/psycopg/config/__init__.py,sha256=hUmtNkSma4M-Y66B-l1gh601Lx7FVYVhU0BWo9ssdJo,570
|
|
45
45
|
sqlspec/adapters/psycopg/config/_async.py,sha256=oKitbfpsJVLhMnbMUmICQub3rBzVZ5n9lwsU_ZvB45s,6661
|
|
46
46
|
sqlspec/adapters/psycopg/config/_common.py,sha256=UqqvqPE9zlSO9G_Gh6fI190cHfCDG98S0GaznGAHpdU,2181
|
|
47
47
|
sqlspec/adapters/psycopg/config/_sync.py,sha256=GYPgE7jfb15gbVdTWRoGGS3B7hauN8wk61CW6Bdrjcs,6512
|
|
48
48
|
sqlspec/adapters/sqlite/__init__.py,sha256=N8VL4Y850OOt63qz-Yvu6rIaCXiziASmn_qDY5tsRuA,209
|
|
49
49
|
sqlspec/adapters/sqlite/config.py,sha256=lGz0G-JFd7dZLhbUrITn9V-ipbhlekwNUr0bXEzM-8k,4498
|
|
50
|
-
sqlspec/adapters/sqlite/driver.py,sha256=
|
|
50
|
+
sqlspec/adapters/sqlite/driver.py,sha256=QBy0Rfhw_9ozTM5yyWETdzqAzOXjtsoGWVjURbDGQec,14669
|
|
51
51
|
sqlspec/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
-
sqlspec/extensions/litestar/__init__.py,sha256=
|
|
52
|
+
sqlspec/extensions/litestar/__init__.py,sha256=ifiR2AXv3m4iDncA2XsVsgk8NnEjcEKxjK2jhFe6A-8,262
|
|
53
53
|
sqlspec/extensions/litestar/_utils.py,sha256=UgwFxqLnjDw9S8G0H24DP2GsbMGas81W1lfhfTY68m8,1969
|
|
54
|
-
sqlspec/extensions/litestar/config.py,sha256=
|
|
55
|
-
sqlspec/extensions/litestar/handlers.py,sha256=
|
|
56
|
-
sqlspec/extensions/litestar/plugin.py,sha256=
|
|
57
|
-
sqlspec/
|
|
54
|
+
sqlspec/extensions/litestar/config.py,sha256=Lz5_NqZ-XDXvaLh7fsPqxvuSDJ1JLJg1XUqbC-PdZPU,4666
|
|
55
|
+
sqlspec/extensions/litestar/handlers.py,sha256=3RJJ5rCEQCbmrhDhkz9Tp03UxqPYhyL8PYR7WKyvuP8,10372
|
|
56
|
+
sqlspec/extensions/litestar/plugin.py,sha256=0isCQ_VFC33_xyRf1Zcb2oWYbZpjFTg-OOZ2EGwTV5M,5175
|
|
57
|
+
sqlspec/extensions/litestar/providers.py,sha256=dwdw7o0jfhDGNbwgabx41dROQ3HCgA0cdXNhWsJPVuw,21805
|
|
58
|
+
sqlspec/utils/__init__.py,sha256=_Ya8IZuc2cZIstXr_xjgnSfxICXHXvu5mfWsi2USDrw,183
|
|
58
59
|
sqlspec/utils/deprecation.py,sha256=4pwGxoQYI3dAc3L1lh4tszZG6e2jp5m4e0ICk8SJx5M,3886
|
|
59
60
|
sqlspec/utils/fixtures.py,sha256=ni51rAuen6S1wuSi1kUwn6Qh25B-XrewPEsjV8G4gQ0,2029
|
|
60
61
|
sqlspec/utils/module_loader.py,sha256=tmMy9JcTTQETcwT8Wt8adCIuqr4zinQnPbCiBJ6JTSQ,2703
|
|
61
|
-
sqlspec/utils/
|
|
62
|
-
sqlspec/utils/
|
|
63
|
-
sqlspec
|
|
64
|
-
sqlspec-0.
|
|
65
|
-
sqlspec-0.
|
|
66
|
-
sqlspec-0.
|
|
67
|
-
sqlspec-0.
|
|
62
|
+
sqlspec/utils/singleton.py,sha256=YsHuo8_blPRQJRvXnjWXa8-Y-TO5V7jFGRwql-rNpO0,1363
|
|
63
|
+
sqlspec/utils/sync_tools.py,sha256=IRzgLMvdkYIwPGUG_EBYGwoOiBBmQSaphwaGachkV4M,9146
|
|
64
|
+
sqlspec/utils/text.py,sha256=-RlwmGjXtfD_ZkqIesZTHAPi46xg-uqlpVKDMcNwFrU,3539
|
|
65
|
+
sqlspec-0.11.0.dist-info/METADATA,sha256=a-INDP1gbHtG1KmqYdfxG4wcVN0zqzkYxca-m7hjaWU,14361
|
|
66
|
+
sqlspec-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
67
|
+
sqlspec-0.11.0.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
|
|
68
|
+
sqlspec-0.11.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
|
|
69
|
+
sqlspec-0.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|