sqlspec 0.1.1__py3-none-any.whl → 0.4.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.

Files changed (42) hide show
  1. sqlspec/__metadata__.py +1 -1
  2. sqlspec/_serialization.py +1 -1
  3. sqlspec/_typing.py +138 -0
  4. sqlspec/adapters/adbc/config.py +52 -0
  5. sqlspec/adapters/aiosqlite/__init__.py +3 -0
  6. sqlspec/adapters/aiosqlite/config.py +95 -0
  7. sqlspec/adapters/asyncmy/__init__.py +3 -0
  8. sqlspec/adapters/asyncmy/config.py +176 -0
  9. sqlspec/adapters/asyncpg/__init__.py +0 -0
  10. sqlspec/adapters/asyncpg/config.py +145 -0
  11. sqlspec/adapters/duckdb/__init__.py +0 -0
  12. sqlspec/adapters/duckdb/config.py +201 -0
  13. sqlspec/adapters/oracledb/__init__.py +13 -0
  14. sqlspec/adapters/oracledb/config/__init__.py +9 -0
  15. sqlspec/adapters/oracledb/config/_asyncio.py +95 -0
  16. sqlspec/adapters/oracledb/config/_common.py +151 -0
  17. sqlspec/adapters/oracledb/config/_sync.py +95 -0
  18. sqlspec/adapters/psycopg/__init__.py +0 -0
  19. sqlspec/adapters/psycopg/config/__init__.py +9 -0
  20. sqlspec/adapters/psycopg/config/_async.py +75 -0
  21. sqlspec/adapters/psycopg/config/_common.py +73 -0
  22. sqlspec/adapters/psycopg/config/_sync.py +75 -0
  23. sqlspec/adapters/sqlite/__init__.py +0 -0
  24. sqlspec/adapters/sqlite/config.py +92 -0
  25. sqlspec/config.py +16 -0
  26. sqlspec/exceptions.py +29 -0
  27. sqlspec/extensions/__init__.py +0 -0
  28. sqlspec/extensions/litestar/__init__.py +0 -0
  29. sqlspec/extensions/litestar/plugin.py +34 -0
  30. sqlspec/filters.py +35 -28
  31. sqlspec/typing.py +415 -0
  32. sqlspec-0.4.0.dist-info/METADATA +84 -0
  33. sqlspec-0.4.0.dist-info/RECORD +39 -0
  34. {sqlspec-0.1.1.dist-info → sqlspec-0.4.0.dist-info}/WHEEL +1 -1
  35. sqlspec-0.4.0.dist-info/licenses/NOTICE +29 -0
  36. sqlspec/types/empty.py +0 -18
  37. sqlspec/types/protocols.py +0 -117
  38. sqlspec/utils/dataclass.py +0 -130
  39. sqlspec-0.1.1.dist-info/METADATA +0 -25
  40. sqlspec-0.1.1.dist-info/RECORD +0 -14
  41. /sqlspec/{types → adapters}/__init__.py +0 -0
  42. /sqlspec/{utils → adapters/adbc}/__init__.py +0 -0
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Generic, TypeVar
5
+
6
+ from sqlspec.config import GenericDatabaseConfig, GenericPoolConfig
7
+ from sqlspec.typing import Empty
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+ from typing import Any
12
+
13
+ from psycopg import AsyncConnection, Connection
14
+ from psycopg_pool import AsyncConnectionPool, ConnectionPool
15
+
16
+ from sqlspec.typing import EmptyType
17
+
18
+
19
+ __all__ = (
20
+ "PsycoPgGenericDatabaseConfig",
21
+ "PsycoPgGenericPoolConfig",
22
+ )
23
+
24
+
25
+ ConnectionT = TypeVar("ConnectionT", bound="Connection | AsyncConnection")
26
+ PoolT = TypeVar("PoolT", bound="ConnectionPool | AsyncConnectionPool")
27
+
28
+
29
+ @dataclass
30
+ class PsycoPgGenericPoolConfig(Generic[PoolT, ConnectionT], GenericPoolConfig):
31
+ """Configuration for Psycopg connection pools.
32
+
33
+ This class provides configuration options for both synchronous and asynchronous Psycopg
34
+ database connection pools. It supports all standard Psycopg connection parameters and pool-specific
35
+ settings.([1](https://www.psycopg.org/psycopg3/docs/api/pool.html))
36
+ """
37
+
38
+ conninfo: str | EmptyType = Empty
39
+ """Connection string in libpq format"""
40
+ kwargs: dict[str, Any] | EmptyType = Empty
41
+ """Additional connection parameters"""
42
+ min_size: int | EmptyType = Empty
43
+ """Minimum number of connections in the pool"""
44
+ max_size: int | EmptyType = Empty
45
+ """Maximum number of connections in the pool"""
46
+ name: str | EmptyType = Empty
47
+ """Name of the connection pool"""
48
+ timeout: float | EmptyType = Empty
49
+ """Timeout for acquiring connections"""
50
+ max_waiting: int | EmptyType = Empty
51
+ """Maximum number of waiting clients"""
52
+ max_lifetime: float | EmptyType = Empty
53
+ """Maximum connection lifetime"""
54
+ max_idle: float | EmptyType = Empty
55
+ """Maximum idle time for connections"""
56
+ reconnect_timeout: float | EmptyType = Empty
57
+ """Time between reconnection attempts"""
58
+ num_workers: int | EmptyType = Empty
59
+ """Number of background workers"""
60
+ configure: Callable[[ConnectionT], None] | EmptyType = Empty
61
+ """Callback to configure new connections"""
62
+
63
+
64
+ @dataclass
65
+ class PsycoPgGenericDatabaseConfig(Generic[PoolT, ConnectionT], GenericDatabaseConfig):
66
+ """Psycopg database Configuration.
67
+
68
+ This class provides the base configuration for Psycopg database connections, extending
69
+ the generic database configuration with Psycopg-specific settings.([1](https://www.psycopg.org/psycopg3/docs/api/connections.html))
70
+
71
+ The configuration supports all standard Psycopg connection parameters and can be used
72
+ with both synchronous and asynchronous connections.([2](https://www.psycopg.org/psycopg3/docs/api/connections.html))
73
+ """
@@ -0,0 +1,75 @@
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.typing import dataclass_to_dict
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 dataclass_to_dict(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
+ def provide_pool(self, *args: Any, **kwargs: Any) -> ConnectionPool:
67
+ """Create and return a connection pool."""
68
+ return self.create_pool()
69
+
70
+ @contextmanager
71
+ def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[Connection, None, None]:
72
+ """Create and provide a database connection."""
73
+ pool = self.provide_pool(*args, **kwargs)
74
+ with pool.connection() as connection:
75
+ yield connection
File without changes
@@ -0,0 +1,92 @@
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.typing import Empty, EmptyType, dataclass_to_dict
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Generator
13
+ from sqlite3 import Connection
14
+
15
+ __all__ = ("SqliteConfig",)
16
+
17
+
18
+ @dataclass
19
+ class SqliteConfig(GenericDatabaseConfig):
20
+ """Configuration for SQLite database connections.
21
+
22
+ This class provides configuration options for SQLite database connections, wrapping all parameters
23
+ available to sqlite3.connect().
24
+
25
+ For details see: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
26
+ """
27
+
28
+ database: str
29
+ """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."""
30
+
31
+ timeout: float | EmptyType = Empty
32
+ """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."""
33
+
34
+ detect_types: int | EmptyType = Empty
35
+ """Control whether and how data types are detected. It can be 0 (default) or a combination of PARSE_DECLTYPES and PARSE_COLNAMES."""
36
+
37
+ isolation_level: Literal["DEFERRED", "IMMEDIATE", "EXCLUSIVE"] | None | EmptyType = Empty
38
+ """The isolation_level of the connection. This can be None for autocommit mode or one of "DEFERRED", "IMMEDIATE" or "EXCLUSIVE"."""
39
+
40
+ check_same_thread: bool | EmptyType = Empty
41
+ """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."""
42
+
43
+ factory: type[Connection] | EmptyType = Empty
44
+ """A custom Connection class factory. If given, must be a callable that returns a Connection instance."""
45
+
46
+ cached_statements: int | EmptyType = Empty
47
+ """The number of statements that SQLite will cache for this connection. The default is 128."""
48
+
49
+ uri: bool | EmptyType = Empty
50
+ """If set to True, database is interpreted as a URI with supported options."""
51
+
52
+ @property
53
+ def connection_config_dict(self) -> dict[str, Any]:
54
+ """Return the connection configuration as a dict.
55
+
56
+ Returns:
57
+ A string keyed dict of config kwargs for the sqlite3.connect() function.
58
+ """
59
+ return dataclass_to_dict(self, exclude_empty=True, convert_nested=False)
60
+
61
+ def create_connection(self) -> Connection:
62
+ """Create and return a new database connection.
63
+
64
+ Returns:
65
+ A new SQLite connection instance.
66
+
67
+ Raises:
68
+ ImproperConfigurationError: If the connection could not be established.
69
+ """
70
+ import sqlite3
71
+
72
+ try:
73
+ return sqlite3.connect(**self.connection_config_dict) # type: ignore[no-any-return,unused-ignore]
74
+ except Exception as e:
75
+ msg = f"Could not configure the SQLite connection. Error: {e!s}"
76
+ raise ImproperConfigurationError(msg) from e
77
+
78
+ @contextmanager
79
+ def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[Connection, None, None]:
80
+ """Create and provide a database connection.
81
+
82
+ Yields:
83
+ A SQLite connection instance.
84
+
85
+ Raises:
86
+ ImproperConfigurationError: If the connection could not be established.
87
+ """
88
+ connection = self.create_connection()
89
+ try:
90
+ yield connection
91
+ finally:
92
+ 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,41 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections import abc # noqa: TCH003
5
+ from abc import ABC
6
+ from collections import abc # noqa: TC003
6
7
  from dataclasses import dataclass
7
- from datetime import datetime # noqa: TCH003
8
- from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar
8
+ from datetime import datetime # noqa: TC003
9
+ from typing import Generic, Literal, Protocol
9
10
 
10
- if TYPE_CHECKING:
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
- "FilterTypes",
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")
28
+
29
+
30
+ class StatementFilter(Protocol):
31
+ """Protocol for filters that can be appended to a statement."""
27
32
 
28
- FilterTypes: TypeAlias = "BeforeAfter | OnBeforeAfter | CollectionFilter[Any] | LimitOffset | OrderBy | SearchFilter | NotInCollectionFilter[Any] | NotInSearchFilter"
29
- """Aggregate type alias of the types supported for collection filtering."""
33
+ def append_to_statement(self, statement: str) -> str:
34
+ """Append the filter to the statement."""
35
+ return statement
30
36
 
31
37
 
32
38
  @dataclass
33
- class BeforeAfter:
39
+ class BeforeAfter(StatementFilter):
34
40
  """Data required to filter a query on a ``datetime`` column."""
35
41
 
36
42
  field_name: str
@@ -42,7 +48,7 @@ class BeforeAfter:
42
48
 
43
49
 
44
50
  @dataclass
45
- class OnBeforeAfter:
51
+ class OnBeforeAfter(StatementFilter):
46
52
  """Data required to filter a query on a ``datetime`` column."""
47
53
 
48
54
  field_name: str
@@ -53,8 +59,12 @@ class OnBeforeAfter:
53
59
  """Filter results where field on or later than this."""
54
60
 
55
61
 
62
+ class InAnyFilter(StatementFilter, ABC):
63
+ """Subclass for methods that have a `prefer_any` attribute."""
64
+
65
+
56
66
  @dataclass
57
- class CollectionFilter(Generic[T]):
67
+ class CollectionFilter(InAnyFilter, Generic[T]):
58
68
  """Data required to construct a ``WHERE ... IN (...)`` clause."""
59
69
 
60
70
  field_name: str
@@ -66,7 +76,7 @@ class CollectionFilter(Generic[T]):
66
76
 
67
77
 
68
78
  @dataclass
69
- class NotInCollectionFilter(Generic[T]):
79
+ class NotInCollectionFilter(InAnyFilter, Generic[T]):
70
80
  """Data required to construct a ``WHERE ... NOT IN (...)`` clause."""
71
81
 
72
82
  field_name: str
@@ -77,8 +87,12 @@ class NotInCollectionFilter(Generic[T]):
77
87
  An empty list or ``None`` will return all rows."""
78
88
 
79
89
 
90
+ class PaginationFilter(StatementFilter, ABC):
91
+ """Subclass for methods that function as a pagination type."""
92
+
93
+
80
94
  @dataclass
81
- class LimitOffset:
95
+ class LimitOffset(PaginationFilter):
82
96
  """Data required to add limit/offset filtering to a query."""
83
97
 
84
98
  limit: int
@@ -88,7 +102,7 @@ class LimitOffset:
88
102
 
89
103
 
90
104
  @dataclass
91
- class OrderBy:
105
+ class OrderBy(StatementFilter):
92
106
  """Data required to construct a ``ORDER BY ...`` clause."""
93
107
 
94
108
  field_name: str
@@ -98,24 +112,17 @@ class OrderBy:
98
112
 
99
113
 
100
114
  @dataclass
101
- class SearchFilter:
115
+ class SearchFilter(StatementFilter):
102
116
  """Data required to construct a ``WHERE field_name LIKE '%' || :value || '%'`` clause."""
103
117
 
104
- field_name: str
105
- """Name of the model attribute to sort on."""
118
+ field_name: str | set[str]
119
+ """Name of the model attribute to search on."""
106
120
  value: str
107
- """Values for ``LIKE`` clause."""
121
+ """Search value."""
108
122
  ignore_case: bool | None = False
109
123
  """Should the search be case insensitive."""
110
124
 
111
125
 
112
126
  @dataclass
113
- class NotInSearchFilter:
127
+ class NotInSearchFilter(SearchFilter):
114
128
  """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."""