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.

Files changed (42) hide show
  1. sqlspec/__metadata__.py +1 -1
  2. sqlspec/_serialization.py +1 -1
  3. sqlspec/_typing.py +112 -0
  4. sqlspec/adapters/adbc/__init__.py +0 -0
  5. sqlspec/adapters/adbc/config.py +54 -0
  6. sqlspec/adapters/aiosqlite/__init__.py +3 -0
  7. sqlspec/adapters/aiosqlite/config.py +112 -0
  8. sqlspec/adapters/asyncmy/__init__.py +3 -0
  9. sqlspec/adapters/asyncmy/config.py +194 -0
  10. sqlspec/adapters/asyncpg/__init__.py +0 -0
  11. sqlspec/adapters/asyncpg/config.py +155 -0
  12. sqlspec/adapters/duckdb/__init__.py +0 -0
  13. sqlspec/adapters/duckdb/config.py +101 -0
  14. sqlspec/adapters/oracledb/__init__.py +13 -0
  15. sqlspec/adapters/oracledb/config/__init__.py +9 -0
  16. sqlspec/adapters/oracledb/config/_asyncio.py +98 -0
  17. sqlspec/adapters/oracledb/config/_common.py +151 -0
  18. sqlspec/adapters/oracledb/config/_sync.py +102 -0
  19. sqlspec/adapters/psycopg/__init__.py +0 -0
  20. sqlspec/adapters/psycopg/config/__init__.py +9 -0
  21. sqlspec/adapters/psycopg/config/_async.py +84 -0
  22. sqlspec/adapters/psycopg/config/_common.py +72 -0
  23. sqlspec/adapters/psycopg/config/_sync.py +84 -0
  24. sqlspec/adapters/sqlite/__init__.py +0 -0
  25. sqlspec/adapters/sqlite/config.py +109 -0
  26. sqlspec/config.py +16 -0
  27. sqlspec/exceptions.py +29 -0
  28. sqlspec/extensions/__init__.py +0 -0
  29. sqlspec/extensions/litestar/__init__.py +0 -0
  30. sqlspec/extensions/litestar/plugin.py +34 -0
  31. sqlspec/filters.py +33 -28
  32. sqlspec/typing.py +287 -0
  33. sqlspec/utils/dataclass.py +11 -3
  34. sqlspec/{types → utils}/empty.py +1 -1
  35. sqlspec-0.3.0.dist-info/METADATA +84 -0
  36. sqlspec-0.3.0.dist-info/RECORD +42 -0
  37. {sqlspec-0.1.0.dist-info → sqlspec-0.3.0.dist-info}/WHEEL +1 -1
  38. sqlspec-0.3.0.dist-info/licenses/NOTICE +29 -0
  39. sqlspec/types/protocols.py +0 -117
  40. sqlspec-0.1.0.dist-info/METADATA +0 -25
  41. sqlspec-0.1.0.dist-info/RECORD +0 -14
  42. /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 collections import abc # noqa: TCH003
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: TCH003
8
- from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar
8
+ from datetime import datetime # noqa: TC003
9
+ from typing import Generic, Literal
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")
27
28
 
28
- FilterTypes: TypeAlias = "BeforeAfter | OnBeforeAfter | CollectionFilter[Any] | LimitOffset | OrderBy | SearchFilter | NotInCollectionFilter[Any] | NotInSearchFilter"
29
- """Aggregate type alias of the types supported for collection filtering."""
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 sort on."""
116
+ field_name: str | set[str]
117
+ """Name of the model attribute to search on."""
106
118
  value: str
107
- """Values for ``LIKE`` clause."""
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."""