sqlspec 0.1.0__py3-none-any.whl → 0.3.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/__metadata__.py +1 -1
- sqlspec/_serialization.py +1 -1
- sqlspec/_typing.py +112 -0
- sqlspec/adapters/adbc/__init__.py +0 -0
- sqlspec/adapters/adbc/config.py +54 -0
- sqlspec/adapters/aiosqlite/__init__.py +3 -0
- sqlspec/adapters/aiosqlite/config.py +112 -0
- sqlspec/adapters/asyncmy/__init__.py +3 -0
- sqlspec/adapters/asyncmy/config.py +194 -0
- sqlspec/adapters/asyncpg/__init__.py +0 -0
- sqlspec/adapters/asyncpg/config.py +155 -0
- sqlspec/adapters/duckdb/__init__.py +0 -0
- sqlspec/adapters/duckdb/config.py +101 -0
- sqlspec/adapters/oracledb/__init__.py +13 -0
- sqlspec/adapters/oracledb/config/__init__.py +9 -0
- sqlspec/adapters/oracledb/config/_asyncio.py +98 -0
- sqlspec/adapters/oracledb/config/_common.py +151 -0
- sqlspec/adapters/oracledb/config/_sync.py +102 -0
- sqlspec/adapters/psycopg/__init__.py +0 -0
- sqlspec/adapters/psycopg/config/__init__.py +9 -0
- sqlspec/adapters/psycopg/config/_async.py +84 -0
- sqlspec/adapters/psycopg/config/_common.py +72 -0
- sqlspec/adapters/psycopg/config/_sync.py +84 -0
- sqlspec/adapters/sqlite/__init__.py +0 -0
- sqlspec/adapters/sqlite/config.py +109 -0
- sqlspec/config.py +16 -0
- sqlspec/exceptions.py +29 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/litestar/__init__.py +0 -0
- sqlspec/extensions/litestar/plugin.py +34 -0
- sqlspec/filters.py +33 -28
- sqlspec/typing.py +287 -0
- sqlspec/utils/dataclass.py +11 -3
- sqlspec/{types → utils}/empty.py +1 -1
- sqlspec-0.3.0.dist-info/METADATA +84 -0
- sqlspec-0.3.0.dist-info/RECORD +42 -0
- {sqlspec-0.1.0.dist-info → sqlspec-0.3.0.dist-info}/WHEEL +1 -1
- sqlspec-0.3.0.dist-info/licenses/NOTICE +29 -0
- sqlspec/types/protocols.py +0 -117
- sqlspec-0.1.0.dist-info/METADATA +0 -25
- sqlspec-0.1.0.dist-info/RECORD +0 -14
- /sqlspec/{types → adapters}/__init__.py +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from psycopg import Connection
|
|
8
|
+
from psycopg_pool import ConnectionPool
|
|
9
|
+
|
|
10
|
+
from sqlspec.adapters.psycopg.config._common import (
|
|
11
|
+
PsycoPgGenericDatabaseConfig,
|
|
12
|
+
PsycoPgGenericPoolConfig,
|
|
13
|
+
)
|
|
14
|
+
from sqlspec.exceptions import ImproperConfigurationError
|
|
15
|
+
from sqlspec.utils.dataclass import simple_asdict
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Generator
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = (
|
|
23
|
+
"PsycoPgSyncDatabaseConfig",
|
|
24
|
+
"PsycoPgSyncPoolConfig",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class PsycoPgSyncPoolConfig(PsycoPgGenericPoolConfig[ConnectionPool, Connection]):
|
|
30
|
+
"""Sync Psycopg Pool Config"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class PsycoPgSyncDatabaseConfig(PsycoPgGenericDatabaseConfig[ConnectionPool, Connection]):
|
|
35
|
+
"""Sync Psycopg database Configuration."""
|
|
36
|
+
|
|
37
|
+
pool_config: PsycoPgSyncPoolConfig | None = None
|
|
38
|
+
"""Psycopg Pool configuration"""
|
|
39
|
+
pool_instance: ConnectionPool | None = None
|
|
40
|
+
"""Optional pool to use"""
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def pool_config_dict(self) -> dict[str, Any]:
|
|
44
|
+
"""Return the pool configuration as a dict."""
|
|
45
|
+
if self.pool_config:
|
|
46
|
+
return simple_asdict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
47
|
+
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
48
|
+
raise ImproperConfigurationError(msg)
|
|
49
|
+
|
|
50
|
+
def create_pool(self) -> ConnectionPool:
|
|
51
|
+
"""Create and return a connection pool."""
|
|
52
|
+
if self.pool_instance is not None:
|
|
53
|
+
return self.pool_instance
|
|
54
|
+
|
|
55
|
+
if self.pool_config is None:
|
|
56
|
+
msg = "One of 'pool_config' or 'pool_instance' must be provided."
|
|
57
|
+
raise ImproperConfigurationError(msg)
|
|
58
|
+
|
|
59
|
+
pool_config = self.pool_config_dict
|
|
60
|
+
self.pool_instance = ConnectionPool(**pool_config)
|
|
61
|
+
if self.pool_instance is None:
|
|
62
|
+
msg = "Could not configure the 'pool_instance'. Please check your configuration." # type: ignore[unreachable]
|
|
63
|
+
raise ImproperConfigurationError(msg)
|
|
64
|
+
return self.pool_instance
|
|
65
|
+
|
|
66
|
+
@contextmanager
|
|
67
|
+
def lifespan(self, *args: Any, **kwargs: Any) -> Generator[None, None, None]:
|
|
68
|
+
"""Manage the lifecycle of the connection pool."""
|
|
69
|
+
pool = self.create_pool()
|
|
70
|
+
try:
|
|
71
|
+
yield
|
|
72
|
+
finally:
|
|
73
|
+
pool.close()
|
|
74
|
+
|
|
75
|
+
def provide_pool(self, *args: Any, **kwargs: Any) -> ConnectionPool:
|
|
76
|
+
"""Create and return a connection pool."""
|
|
77
|
+
return self.create_pool()
|
|
78
|
+
|
|
79
|
+
@contextmanager
|
|
80
|
+
def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[Connection, None, None]:
|
|
81
|
+
"""Create and provide a database connection."""
|
|
82
|
+
pool = self.provide_pool(*args, **kwargs)
|
|
83
|
+
with pool.connection() as connection:
|
|
84
|
+
yield connection
|
|
File without changes
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
6
|
+
|
|
7
|
+
from sqlspec.config import GenericDatabaseConfig
|
|
8
|
+
from sqlspec.exceptions import ImproperConfigurationError
|
|
9
|
+
from sqlspec.utils.dataclass import simple_asdict
|
|
10
|
+
from sqlspec.utils.empty import Empty, EmptyType
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Generator
|
|
14
|
+
from sqlite3 import Connection
|
|
15
|
+
|
|
16
|
+
__all__ = ("SqliteConfig",)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class SqliteConfig(GenericDatabaseConfig):
|
|
21
|
+
"""Configuration for SQLite database connections.
|
|
22
|
+
|
|
23
|
+
This class provides configuration options for SQLite database connections, wrapping all parameters
|
|
24
|
+
available to sqlite3.connect().
|
|
25
|
+
|
|
26
|
+
For details see: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
database: str
|
|
30
|
+
"""The path to the database file to be opened. Pass ":memory:" to open a connection to a database that resides in RAM instead of on disk."""
|
|
31
|
+
|
|
32
|
+
timeout: float | EmptyType = Empty
|
|
33
|
+
"""How many seconds the connection should wait before raising an OperationalError when a table is locked. If another thread or process has acquired a shared lock, a wait for the specified timeout occurs."""
|
|
34
|
+
|
|
35
|
+
detect_types: int | EmptyType = Empty
|
|
36
|
+
"""Control whether and how data types are detected. It can be 0 (default) or a combination of PARSE_DECLTYPES and PARSE_COLNAMES."""
|
|
37
|
+
|
|
38
|
+
isolation_level: Literal["DEFERRED", "IMMEDIATE", "EXCLUSIVE"] | None | EmptyType = Empty
|
|
39
|
+
"""The isolation_level of the connection. This can be None for autocommit mode or one of "DEFERRED", "IMMEDIATE" or "EXCLUSIVE"."""
|
|
40
|
+
|
|
41
|
+
check_same_thread: bool | EmptyType = Empty
|
|
42
|
+
"""If True (default), ProgrammingError is raised if the database connection is used by a thread other than the one that created it. If False, the connection may be shared across multiple threads."""
|
|
43
|
+
|
|
44
|
+
factory: type[Connection] | EmptyType = Empty
|
|
45
|
+
"""A custom Connection class factory. If given, must be a callable that returns a Connection instance."""
|
|
46
|
+
|
|
47
|
+
cached_statements: int | EmptyType = Empty
|
|
48
|
+
"""The number of statements that SQLite will cache for this connection. The default is 128."""
|
|
49
|
+
|
|
50
|
+
uri: bool | EmptyType = Empty
|
|
51
|
+
"""If set to True, database is interpreted as a URI with supported options."""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def connection_config_dict(self) -> dict[str, Any]:
|
|
55
|
+
"""Return the connection configuration as a dict.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A string keyed dict of config kwargs for the sqlite3.connect() function.
|
|
59
|
+
"""
|
|
60
|
+
return simple_asdict(self, exclude_empty=True, convert_nested=False)
|
|
61
|
+
|
|
62
|
+
def create_connection(self) -> Connection:
|
|
63
|
+
"""Create and return a new database connection.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
A new SQLite connection instance.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ImproperConfigurationError: If the connection could not be established.
|
|
70
|
+
"""
|
|
71
|
+
import sqlite3
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
return sqlite3.connect(**self.connection_config_dict)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
msg = f"Could not configure the SQLite connection. Error: {e!s}"
|
|
77
|
+
raise ImproperConfigurationError(msg) from e
|
|
78
|
+
|
|
79
|
+
@contextmanager
|
|
80
|
+
def lifespan(self, *args: Any, **kwargs: Any) -> Generator[None, None, None]:
|
|
81
|
+
"""Manage the lifecycle of a database connection.
|
|
82
|
+
|
|
83
|
+
Yields:
|
|
84
|
+
None
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ImproperConfigurationError: If the connection could not be established.
|
|
88
|
+
"""
|
|
89
|
+
connection = self.create_connection()
|
|
90
|
+
try:
|
|
91
|
+
yield
|
|
92
|
+
finally:
|
|
93
|
+
connection.close()
|
|
94
|
+
|
|
95
|
+
@contextmanager
|
|
96
|
+
def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[Connection, None, None]:
|
|
97
|
+
"""Create and provide a database connection.
|
|
98
|
+
|
|
99
|
+
Yields:
|
|
100
|
+
A SQLite connection instance.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ImproperConfigurationError: If the connection could not be established.
|
|
104
|
+
"""
|
|
105
|
+
connection = self.create_connection()
|
|
106
|
+
try:
|
|
107
|
+
yield connection
|
|
108
|
+
finally:
|
|
109
|
+
connection.close()
|
sqlspec/config.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
__all__ = (
|
|
4
|
+
"GenericDatabaseConfig",
|
|
5
|
+
"GenericPoolConfig",
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class GenericDatabaseConfig:
|
|
11
|
+
"""Generic Database Configuration."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class GenericPoolConfig:
|
|
16
|
+
"""Generic Database Pool Configuration."""
|
sqlspec/exceptions.py
CHANGED
|
@@ -2,6 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
__all__ = (
|
|
6
|
+
"ImproperConfigurationError",
|
|
7
|
+
"IntegrityError",
|
|
8
|
+
"MissingDependencyError",
|
|
9
|
+
"MultipleResultsFoundError",
|
|
10
|
+
"NotFoundError",
|
|
11
|
+
"RepositoryError",
|
|
12
|
+
"SQLSpecError",
|
|
13
|
+
"SerializationError",
|
|
14
|
+
)
|
|
15
|
+
|
|
5
16
|
|
|
6
17
|
class SQLSpecError(Exception):
|
|
7
18
|
"""Base exception class from which all Advanced Alchemy exceptions inherit."""
|
|
@@ -47,6 +58,24 @@ class MissingDependencyError(SQLSpecError, ImportError):
|
|
|
47
58
|
)
|
|
48
59
|
|
|
49
60
|
|
|
61
|
+
class SQLLoadingError(SQLSpecError):
|
|
62
|
+
"""Issues loading referenced SQL file."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, message: str | None = None) -> None:
|
|
65
|
+
if message is None:
|
|
66
|
+
message = "Issues loading referenced SQL file."
|
|
67
|
+
super().__init__(message)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SQLParsingError(SQLSpecError):
|
|
71
|
+
"""Issues parsing SQL statements."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, message: str | None = None) -> None:
|
|
74
|
+
if message is None:
|
|
75
|
+
message = "Issues parsing SQL statement."
|
|
76
|
+
super().__init__(message)
|
|
77
|
+
|
|
78
|
+
|
|
50
79
|
class ImproperConfigurationError(SQLSpecError):
|
|
51
80
|
"""Improper Configuration error.
|
|
52
81
|
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
4
|
+
|
|
5
|
+
from litestar.plugins import InitPluginProtocol
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from litestar.config.app import AppConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SQLSpecPlugin(InitPluginProtocol):
|
|
15
|
+
"""Aiosql plugin."""
|
|
16
|
+
|
|
17
|
+
__slots__ = ("_config",)
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: Any) -> None:
|
|
20
|
+
"""Initialize ``AiosqlPlugin``.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
config: configure and start Aiosql.
|
|
24
|
+
"""
|
|
25
|
+
self._config = config
|
|
26
|
+
|
|
27
|
+
def on_app_init(self, app_config: AppConfig) -> AppConfig:
|
|
28
|
+
"""Configure application for use with Aiosql.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
app_config: The :class:`AppConfig <.config.app.AppConfig>` instance.
|
|
32
|
+
"""
|
|
33
|
+
app_config.signature_namespace.update(self._config.signature_namespace)
|
|
34
|
+
return app_config
|
sqlspec/filters.py
CHANGED
|
@@ -2,35 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections import abc # noqa: TC003
|
|
6
7
|
from dataclasses import dataclass
|
|
7
|
-
from datetime import datetime # noqa:
|
|
8
|
-
from typing import
|
|
8
|
+
from datetime import datetime # noqa: TC003
|
|
9
|
+
from typing import Generic, Literal
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
from typing_extensions import TypeAlias
|
|
12
|
-
|
|
13
|
-
T = TypeVar("T")
|
|
11
|
+
from typing_extensions import TypeVar
|
|
14
12
|
|
|
15
13
|
__all__ = (
|
|
16
14
|
"BeforeAfter",
|
|
17
15
|
"CollectionFilter",
|
|
18
|
-
"
|
|
16
|
+
"InAnyFilter",
|
|
19
17
|
"LimitOffset",
|
|
20
18
|
"NotInCollectionFilter",
|
|
21
19
|
"NotInSearchFilter",
|
|
22
20
|
"OnBeforeAfter",
|
|
23
21
|
"OrderBy",
|
|
22
|
+
"PaginationFilter",
|
|
24
23
|
"SearchFilter",
|
|
24
|
+
"StatementFilter",
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
+
T = TypeVar("T")
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
class StatementFilter(ABC):
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def append_to_statement(self, statement: str) -> str:
|
|
33
|
+
return statement
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
@dataclass
|
|
33
|
-
class BeforeAfter:
|
|
37
|
+
class BeforeAfter(StatementFilter):
|
|
34
38
|
"""Data required to filter a query on a ``datetime`` column."""
|
|
35
39
|
|
|
36
40
|
field_name: str
|
|
@@ -42,7 +46,7 @@ class BeforeAfter:
|
|
|
42
46
|
|
|
43
47
|
|
|
44
48
|
@dataclass
|
|
45
|
-
class OnBeforeAfter:
|
|
49
|
+
class OnBeforeAfter(StatementFilter):
|
|
46
50
|
"""Data required to filter a query on a ``datetime`` column."""
|
|
47
51
|
|
|
48
52
|
field_name: str
|
|
@@ -53,8 +57,12 @@ class OnBeforeAfter:
|
|
|
53
57
|
"""Filter results where field on or later than this."""
|
|
54
58
|
|
|
55
59
|
|
|
60
|
+
class InAnyFilter(StatementFilter, ABC):
|
|
61
|
+
"""Subclass for methods that have a `prefer_any` attribute."""
|
|
62
|
+
|
|
63
|
+
|
|
56
64
|
@dataclass
|
|
57
|
-
class CollectionFilter(Generic[T]):
|
|
65
|
+
class CollectionFilter(InAnyFilter, Generic[T]):
|
|
58
66
|
"""Data required to construct a ``WHERE ... IN (...)`` clause."""
|
|
59
67
|
|
|
60
68
|
field_name: str
|
|
@@ -66,7 +74,7 @@ class CollectionFilter(Generic[T]):
|
|
|
66
74
|
|
|
67
75
|
|
|
68
76
|
@dataclass
|
|
69
|
-
class NotInCollectionFilter(Generic[T]):
|
|
77
|
+
class NotInCollectionFilter(InAnyFilter, Generic[T]):
|
|
70
78
|
"""Data required to construct a ``WHERE ... NOT IN (...)`` clause."""
|
|
71
79
|
|
|
72
80
|
field_name: str
|
|
@@ -77,8 +85,12 @@ class NotInCollectionFilter(Generic[T]):
|
|
|
77
85
|
An empty list or ``None`` will return all rows."""
|
|
78
86
|
|
|
79
87
|
|
|
88
|
+
class PaginationFilter(StatementFilter, ABC):
|
|
89
|
+
"""Subclass for methods that function as a pagination type."""
|
|
90
|
+
|
|
91
|
+
|
|
80
92
|
@dataclass
|
|
81
|
-
class LimitOffset:
|
|
93
|
+
class LimitOffset(PaginationFilter):
|
|
82
94
|
"""Data required to add limit/offset filtering to a query."""
|
|
83
95
|
|
|
84
96
|
limit: int
|
|
@@ -88,7 +100,7 @@ class LimitOffset:
|
|
|
88
100
|
|
|
89
101
|
|
|
90
102
|
@dataclass
|
|
91
|
-
class OrderBy:
|
|
103
|
+
class OrderBy(StatementFilter):
|
|
92
104
|
"""Data required to construct a ``ORDER BY ...`` clause."""
|
|
93
105
|
|
|
94
106
|
field_name: str
|
|
@@ -98,24 +110,17 @@ class OrderBy:
|
|
|
98
110
|
|
|
99
111
|
|
|
100
112
|
@dataclass
|
|
101
|
-
class SearchFilter:
|
|
113
|
+
class SearchFilter(StatementFilter):
|
|
102
114
|
"""Data required to construct a ``WHERE field_name LIKE '%' || :value || '%'`` clause."""
|
|
103
115
|
|
|
104
|
-
field_name: str
|
|
105
|
-
"""Name of the model attribute to
|
|
116
|
+
field_name: str | set[str]
|
|
117
|
+
"""Name of the model attribute to search on."""
|
|
106
118
|
value: str
|
|
107
|
-
"""
|
|
119
|
+
"""Search value."""
|
|
108
120
|
ignore_case: bool | None = False
|
|
109
121
|
"""Should the search be case insensitive."""
|
|
110
122
|
|
|
111
123
|
|
|
112
124
|
@dataclass
|
|
113
|
-
class NotInSearchFilter:
|
|
125
|
+
class NotInSearchFilter(SearchFilter):
|
|
114
126
|
"""Data required to construct a ``WHERE field_name NOT LIKE '%' || :value || '%'`` clause."""
|
|
115
|
-
|
|
116
|
-
field_name: str
|
|
117
|
-
"""Name of the model attribute to search on."""
|
|
118
|
-
value: str
|
|
119
|
-
"""Values for ``NOT LIKE`` clause."""
|
|
120
|
-
ignore_case: bool | None = False
|
|
121
|
-
"""Should the search be case insensitive."""
|