sqlspec 0.14.0__py3-none-any.whl → 0.15.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 (158) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +256 -120
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -248
  10. sqlspec/adapters/adbc/driver.py +462 -353
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +6 -64
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +828 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +651 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +168 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/config.py +0 -1
  90. sqlspec/extensions/litestar/handlers.py +15 -26
  91. sqlspec/extensions/litestar/plugin.py +16 -14
  92. sqlspec/extensions/litestar/providers.py +17 -52
  93. sqlspec/loader.py +424 -105
  94. sqlspec/migrations/__init__.py +12 -0
  95. sqlspec/migrations/base.py +92 -68
  96. sqlspec/migrations/commands.py +24 -106
  97. sqlspec/migrations/loaders.py +402 -0
  98. sqlspec/migrations/runner.py +49 -51
  99. sqlspec/migrations/tracker.py +31 -44
  100. sqlspec/migrations/utils.py +64 -24
  101. sqlspec/protocols.py +7 -183
  102. sqlspec/storage/__init__.py +1 -1
  103. sqlspec/storage/backends/base.py +37 -40
  104. sqlspec/storage/backends/fsspec.py +136 -112
  105. sqlspec/storage/backends/obstore.py +138 -160
  106. sqlspec/storage/capabilities.py +5 -4
  107. sqlspec/storage/registry.py +57 -106
  108. sqlspec/typing.py +136 -115
  109. sqlspec/utils/__init__.py +2 -3
  110. sqlspec/utils/correlation.py +0 -3
  111. sqlspec/utils/deprecation.py +6 -6
  112. sqlspec/utils/fixtures.py +6 -6
  113. sqlspec/utils/logging.py +0 -2
  114. sqlspec/utils/module_loader.py +7 -12
  115. sqlspec/utils/singleton.py +0 -1
  116. sqlspec/utils/sync_tools.py +16 -37
  117. sqlspec/utils/text.py +12 -51
  118. sqlspec/utils/type_guards.py +443 -232
  119. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
  120. sqlspec-0.15.0.dist-info/RECORD +134 -0
  121. sqlspec-0.15.0.dist-info/entry_points.txt +2 -0
  122. sqlspec/driver/connection.py +0 -207
  123. sqlspec/driver/mixins/_cache.py +0 -114
  124. sqlspec/driver/mixins/_csv_writer.py +0 -91
  125. sqlspec/driver/mixins/_pipeline.py +0 -508
  126. sqlspec/driver/mixins/_query_tools.py +0 -796
  127. sqlspec/driver/mixins/_result_utils.py +0 -138
  128. sqlspec/driver/mixins/_storage.py +0 -912
  129. sqlspec/driver/mixins/_type_coercion.py +0 -128
  130. sqlspec/driver/parameters.py +0 -138
  131. sqlspec/statement/__init__.py +0 -21
  132. sqlspec/statement/builder/_merge.py +0 -95
  133. sqlspec/statement/cache.py +0 -50
  134. sqlspec/statement/filters.py +0 -625
  135. sqlspec/statement/parameters.py +0 -996
  136. sqlspec/statement/pipelines/__init__.py +0 -210
  137. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  138. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  139. sqlspec/statement/pipelines/context.py +0 -115
  140. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  141. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  142. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  143. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  144. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  145. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  146. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  147. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  148. sqlspec/statement/pipelines/validators/_security.py +0 -967
  149. sqlspec/statement/result.py +0 -435
  150. sqlspec/statement/sql.py +0 -1774
  151. sqlspec/utils/cached_property.py +0 -25
  152. sqlspec/utils/statement_hashing.py +0 -203
  153. sqlspec-0.14.0.dist-info/RECORD +0 -143
  154. sqlspec-0.14.0.dist-info/entry_points.txt +0 -2
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
  158. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,23 +1,6 @@
1
1
  """Driver mixins for instrumentation, storage, and utilities."""
2
2
 
3
- from sqlspec.driver.mixins._cache import AsyncAdapterCacheMixin, SyncAdapterCacheMixin
4
- from sqlspec.driver.mixins._pipeline import AsyncPipelinedExecutionMixin, SyncPipelinedExecutionMixin
5
- from sqlspec.driver.mixins._query_tools import AsyncQueryMixin, SyncQueryMixin
6
- from sqlspec.driver.mixins._result_utils import ToSchemaMixin
3
+ from sqlspec.driver.mixins._result_tools import ToSchemaMixin
7
4
  from sqlspec.driver.mixins._sql_translator import SQLTranslatorMixin
8
- from sqlspec.driver.mixins._storage import AsyncStorageMixin, SyncStorageMixin
9
- from sqlspec.driver.mixins._type_coercion import TypeCoercionMixin
10
5
 
11
- __all__ = (
12
- "AsyncAdapterCacheMixin",
13
- "AsyncPipelinedExecutionMixin",
14
- "AsyncQueryMixin",
15
- "AsyncStorageMixin",
16
- "SQLTranslatorMixin",
17
- "SyncAdapterCacheMixin",
18
- "SyncPipelinedExecutionMixin",
19
- "SyncQueryMixin",
20
- "SyncStorageMixin",
21
- "ToSchemaMixin",
22
- "TypeCoercionMixin",
23
- )
6
+ __all__ = ("SQLTranslatorMixin", "ToSchemaMixin")
@@ -0,0 +1,168 @@
1
+ # pyright: reportCallIssue=false, reportAttributeAccessIssue=false, reportArgumentType=false
2
+ import datetime
3
+ import logging
4
+ from collections.abc import Sequence
5
+ from enum import Enum
6
+ from functools import partial
7
+ from pathlib import Path, PurePath
8
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union, overload
9
+ from uuid import UUID
10
+
11
+ from mypy_extensions import trait
12
+
13
+ from sqlspec.exceptions import SQLSpecError, wrap_exceptions
14
+ from sqlspec.typing import (
15
+ CATTRS_INSTALLED,
16
+ DataclassProtocol,
17
+ DictLike,
18
+ ModelDTOT,
19
+ ModelT,
20
+ attrs_asdict,
21
+ cattrs_structure,
22
+ cattrs_unstructure,
23
+ convert,
24
+ get_type_adapter,
25
+ )
26
+ from sqlspec.utils.type_guards import is_attrs_schema, is_dataclass, is_msgspec_struct, is_pydantic_model
27
+
28
+ if TYPE_CHECKING:
29
+ from sqlspec._typing import AttrsInstanceStub, BaseModelStub, StructStub
30
+
31
+ __all__ = ("_DEFAULT_TYPE_DECODERS", "_default_msgspec_deserializer")
32
+
33
+
34
+ logger = logging.getLogger(__name__)
35
+ _DEFAULT_TYPE_DECODERS: list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]] = [
36
+ (lambda x: x is UUID, lambda t, v: t(v.hex)),
37
+ (lambda x: x is datetime.datetime, lambda t, v: t(v.isoformat())),
38
+ (lambda x: x is datetime.date, lambda t, v: t(v.isoformat())),
39
+ (lambda x: x is datetime.time, lambda t, v: t(v.isoformat())),
40
+ (lambda x: x is Enum, lambda t, v: t(v.value)),
41
+ ]
42
+
43
+
44
+ def _default_msgspec_deserializer(
45
+ target_type: Any, value: Any, type_decoders: "Optional[Sequence[tuple[Any, Any]]]" = None
46
+ ) -> Any:
47
+ """Default msgspec deserializer with type conversion support.
48
+
49
+ Converts values to appropriate types for msgspec deserialization, including
50
+ UUID, datetime, date, time, Enum, Path, and PurePath types.
51
+ """
52
+ if type_decoders:
53
+ for predicate, decoder in type_decoders:
54
+ if predicate(target_type):
55
+ return decoder(target_type, value)
56
+ if target_type is UUID and isinstance(value, UUID):
57
+ return value.hex
58
+ if target_type in {datetime.datetime, datetime.date, datetime.time}:
59
+ with wrap_exceptions(suppress=AttributeError):
60
+ return value.isoformat()
61
+ if isinstance(target_type, type) and issubclass(target_type, Enum) and isinstance(value, Enum):
62
+ return value.value
63
+ if isinstance(value, target_type):
64
+ return value
65
+ if issubclass(target_type, (Path, PurePath, UUID)):
66
+ return target_type(value)
67
+ return value
68
+
69
+
70
+ @trait
71
+ class ToSchemaMixin:
72
+ __slots__ = ()
73
+
74
+ # Schema conversion overloads - handle common cases first
75
+ @overload
76
+ @staticmethod
77
+ def to_schema(data: "list[dict[str, Any]]", *, schema_type: "type[ModelDTOT]") -> "list[ModelDTOT]": ...
78
+ @overload
79
+ @staticmethod
80
+ def to_schema(data: "list[dict[str, Any]]", *, schema_type: None = None) -> "list[dict[str, Any]]": ...
81
+ @overload
82
+ @staticmethod
83
+ def to_schema(data: "dict[str, Any]", *, schema_type: "type[ModelDTOT]") -> "ModelDTOT": ...
84
+ @overload
85
+ @staticmethod
86
+ def to_schema(data: "dict[str, Any]", *, schema_type: None = None) -> "dict[str, Any]": ...
87
+ @overload
88
+ @staticmethod
89
+ def to_schema(data: "list[ModelT]", *, schema_type: "type[ModelDTOT]") -> "list[ModelDTOT]": ...
90
+ @overload
91
+ @staticmethod
92
+ def to_schema(data: "list[ModelT]", *, schema_type: None = None) -> "list[ModelT]": ...
93
+ @overload
94
+ @staticmethod
95
+ def to_schema(
96
+ data: "Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]",
97
+ *,
98
+ schema_type: "type[ModelDTOT]",
99
+ ) -> "ModelDTOT": ...
100
+ @overload
101
+ @staticmethod
102
+ def to_schema(
103
+ data: "Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]",
104
+ *,
105
+ schema_type: None = None,
106
+ ) -> "Union[DictLike, StructStub, BaseModelStub, DataclassProtocol, AttrsInstanceStub]": ...
107
+
108
+ @staticmethod
109
+ def to_schema(data: Any, *, schema_type: "Optional[type[ModelDTOT]]" = None) -> Any:
110
+ """Convert data to a specified schema type.
111
+
112
+ Supports conversion to dataclasses, msgspec structs, Pydantic models, and attrs classes.
113
+ Handles both single objects and sequences.
114
+
115
+ Raises:
116
+ SQLSpecError if `schema_type` is not a valid type.
117
+
118
+ Returns:
119
+ Converted data in the specified schema type.
120
+
121
+ """
122
+ if schema_type is None:
123
+ return data
124
+ if is_dataclass(schema_type):
125
+ if isinstance(data, list):
126
+ return [schema_type(**dict(item) if hasattr(item, "keys") else item) for item in data] # type: ignore[operator]
127
+ if hasattr(data, "keys"):
128
+ return schema_type(**dict(data)) # type: ignore[operator]
129
+ if isinstance(data, dict):
130
+ return schema_type(**data) # type: ignore[operator]
131
+ # Fallback for other types
132
+ return data
133
+ if is_msgspec_struct(schema_type):
134
+ if not isinstance(data, Sequence):
135
+ return convert(
136
+ obj=data,
137
+ type=schema_type,
138
+ from_attributes=True,
139
+ dec_hook=partial(_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS),
140
+ )
141
+ return convert(
142
+ obj=data,
143
+ type=list[schema_type], # type: ignore[valid-type] # pyright: ignore
144
+ from_attributes=True,
145
+ dec_hook=partial(_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS),
146
+ )
147
+ if is_pydantic_model(schema_type):
148
+ if not isinstance(data, Sequence):
149
+ return get_type_adapter(schema_type).validate_python(data, from_attributes=True) # pyright: ignore
150
+ return get_type_adapter(list[schema_type]).validate_python(data, from_attributes=True) # type: ignore[valid-type] # pyright: ignore
151
+ if is_attrs_schema(schema_type):
152
+ if CATTRS_INSTALLED:
153
+ if isinstance(data, Sequence):
154
+ return cattrs_structure(data, list[schema_type]) # type: ignore[valid-type] # pyright: ignore
155
+ # If data is already structured (attrs instance), unstructure it first
156
+ if hasattr(data, "__attrs_attrs__"):
157
+ data = cattrs_unstructure(data)
158
+ return cattrs_structure(data, schema_type) # pyright: ignore
159
+ if isinstance(data, list):
160
+ return [schema_type(**dict(item) if hasattr(item, "keys") else attrs_asdict(item)) for item in data]
161
+ if hasattr(data, "keys"):
162
+ return schema_type(**dict(data))
163
+ if isinstance(data, dict):
164
+ return schema_type(**data)
165
+ # Fallback for other types
166
+ return data
167
+ msg = "`schema_type` should be a valid Dataclass, Pydantic model, Msgspec struct, or Attrs class"
168
+ raise SQLSpecError(msg)
@@ -1,17 +1,20 @@
1
+ from mypy_extensions import trait
1
2
  from sqlglot import exp, parse_one
2
3
  from sqlglot.dialects.dialect import DialectType
3
4
 
5
+ from sqlspec.core.statement import SQL, Statement
4
6
  from sqlspec.exceptions import SQLConversionError
5
- from sqlspec.statement.sql import SQL, Statement
6
7
 
7
8
  __all__ = ("SQLTranslatorMixin",)
8
9
 
9
10
 
11
+ @trait
10
12
  class SQLTranslatorMixin:
11
13
  """Mixin for drivers supporting SQL translation."""
12
14
 
15
+ __slots__ = ()
16
+
13
17
  def convert_to_dialect(self, statement: "Statement", to_dialect: DialectType = None, pretty: bool = True) -> str:
14
- parsed_expression: exp.Expression
15
18
  if statement is not None and isinstance(statement, SQL):
16
19
  if statement.expression is None:
17
20
  msg = "Statement could not be parsed"
@@ -25,7 +28,7 @@ class SQLTranslatorMixin:
25
28
  except Exception as e:
26
29
  error_msg = f"Failed to parse SQL statement: {e!s}"
27
30
  raise SQLConversionError(error_msg) from e
28
- target_dialect = to_dialect if to_dialect is not None else self.dialect # type: ignore[attr-defined]
31
+ target_dialect = to_dialect or self.dialect # type: ignore[attr-defined]
29
32
  try:
30
33
  return parsed_expression.sql(dialect=target_dialect, pretty=pretty)
31
34
  except Exception as e:
sqlspec/exceptions.py CHANGED
@@ -1,48 +1,33 @@
1
1
  from collections.abc import Generator
2
2
  from contextlib import contextmanager
3
- from enum import Enum
4
- from typing import Any, Optional, Union, cast
3
+ from typing import Any, Optional, Union
5
4
 
6
5
  __all__ = (
7
- "ExtraParameterError",
8
6
  "FileNotFoundInStorageError",
9
7
  "ImproperConfigurationError",
10
8
  "IntegrityError",
11
9
  "MissingDependencyError",
12
- "MissingParameterError",
13
10
  "MultipleResultsFoundError",
14
11
  "NotFoundError",
15
- "ParameterError",
16
- "ParameterStyleMismatchError",
17
- "PipelineExecutionError",
18
- "QueryError",
19
12
  "RepositoryError",
20
- "RiskLevel",
21
13
  "SQLBuilderError",
22
- "SQLCompilationError",
23
14
  "SQLConversionError",
24
15
  "SQLFileNotFoundError",
25
16
  "SQLFileParseError",
26
- "SQLFileParsingError",
27
- "SQLInjectionError",
28
17
  "SQLParsingError",
29
18
  "SQLSpecError",
30
- "SQLTransformationError",
31
- "SQLValidationError",
32
19
  "SerializationError",
33
20
  "StorageOperationFailedError",
34
- "UnknownParameterError",
35
- "UnsafeSQLError",
36
21
  )
37
22
 
38
23
 
39
24
  class SQLSpecError(Exception):
40
- """Base exception class from which all Advanced Alchemy exceptions inherit."""
25
+ """Base exception class for SQLSpec exceptions."""
41
26
 
42
27
  detail: str
43
28
 
44
29
  def __init__(self, *args: Any, detail: str = "") -> None:
45
- """Initialize ``AdvancedAlchemyException``.
30
+ """Initialize SQLSpecError.
46
31
 
47
32
  Args:
48
33
  *args: args are converted to :class:`str` before passing to :class:`Exception`
@@ -67,10 +52,7 @@ class SQLSpecError(Exception):
67
52
 
68
53
 
69
54
  class MissingDependencyError(SQLSpecError, ImportError):
70
- """Missing optional dependency.
71
-
72
- This exception is raised only when a module depends on a dependency that has not been installed.
73
- """
55
+ """Raised when a required dependency is not installed."""
74
56
 
75
57
  def __init__(self, package: str, install_package: Optional[str] = None) -> None:
76
58
  super().__init__(
@@ -87,15 +69,6 @@ class BackendNotRegisteredError(SQLSpecError):
87
69
  super().__init__(f"Storage backend '{backend_key}' is not registered. Please register it before use.")
88
70
 
89
71
 
90
- class SQLLoadingError(SQLSpecError):
91
- """Issues loading referenced SQL file."""
92
-
93
- def __init__(self, message: Optional[str] = None) -> None:
94
- if message is None:
95
- message = "Issues loading referenced SQL file."
96
- super().__init__(message)
97
-
98
-
99
72
  class SQLParsingError(SQLSpecError):
100
73
  """Issues parsing SQL statements."""
101
74
 
@@ -105,15 +78,6 @@ class SQLParsingError(SQLSpecError):
105
78
  super().__init__(message)
106
79
 
107
80
 
108
- class SQLFileParsingError(SQLSpecError):
109
- """Issues parsing SQL files."""
110
-
111
- def __init__(self, message: Optional[str] = None) -> None:
112
- if message is None:
113
- message = "Issues parsing SQL files."
114
- super().__init__(message)
115
-
116
-
117
81
  class SQLBuilderError(SQLSpecError):
118
82
  """Issues Building or Generating SQL statements."""
119
83
 
@@ -123,15 +87,6 @@ class SQLBuilderError(SQLSpecError):
123
87
  super().__init__(message)
124
88
 
125
89
 
126
- class SQLCompilationError(SQLSpecError):
127
- """Issues Compiling SQL statements."""
128
-
129
- def __init__(self, message: Optional[str] = None) -> None:
130
- if message is None:
131
- message = "Issues compiling SQL statement."
132
- super().__init__(message)
133
-
134
-
135
90
  class SQLConversionError(SQLSpecError):
136
91
  """Issues converting SQL statements."""
137
92
 
@@ -141,170 +96,8 @@ class SQLConversionError(SQLSpecError):
141
96
  super().__init__(message)
142
97
 
143
98
 
144
- # -- SQL Validation Errors --
145
- class RiskLevel(Enum):
146
- """SQL risk assessment levels."""
147
-
148
- SKIP = 1
149
- SAFE = 2
150
- LOW = 3
151
- MEDIUM = 4
152
- HIGH = 5
153
- CRITICAL = 6
154
-
155
- def __str__(self) -> str:
156
- """String representation.
157
-
158
- Returns:
159
- Lowercase name of the style.
160
- """
161
- return self.name.lower()
162
-
163
- def __lt__(self, other: "RiskLevel") -> bool: # pragma: no cover
164
- """Less than comparison for ordering."""
165
- if not isinstance(other, RiskLevel):
166
- return NotImplemented
167
- return self.value < other.value
168
-
169
- def __le__(self, other: "RiskLevel") -> bool: # pragma: no cover
170
- """Less than or equal comparison for ordering."""
171
- if not isinstance(other, RiskLevel):
172
- return NotImplemented
173
- return self.value <= other.value
174
-
175
- def __gt__(self, other: "RiskLevel") -> bool: # pragma: no cover
176
- """Greater than comparison for ordering."""
177
- if not isinstance(other, RiskLevel):
178
- return NotImplemented
179
- return self.value > other.value
180
-
181
- def __ge__(self, other: "RiskLevel") -> bool: # pragma: no cover
182
- """Greater than or equal comparison for ordering."""
183
- if not isinstance(other, RiskLevel):
184
- return NotImplemented
185
- return self.value >= other.value
186
-
187
-
188
- class SQLValidationError(SQLSpecError):
189
- """Base class for SQL validation errors."""
190
-
191
- sql: Optional[str]
192
- risk_level: RiskLevel
193
-
194
- def __init__(self, message: str, sql: Optional[str] = None, risk_level: RiskLevel = RiskLevel.MEDIUM) -> None:
195
- """Initialize with SQL context and risk level."""
196
- detail_message = message
197
- if sql is not None:
198
- detail_message = f"{message}\nSQL: {sql}"
199
- super().__init__(detail=detail_message)
200
- self.sql = sql
201
- self.risk_level = risk_level
202
-
203
-
204
- class SQLTransformationError(SQLSpecError):
205
- """Base class for SQL transformation errors."""
206
-
207
- sql: Optional[str]
208
-
209
- def __init__(self, message: str, sql: Optional[str] = None) -> None:
210
- """Initialize with SQL context and risk level."""
211
- detail_message = message
212
- if sql is not None:
213
- detail_message = f"{message}\nSQL: {sql}"
214
- super().__init__(detail=detail_message)
215
- self.sql = sql
216
-
217
-
218
- class SQLInjectionError(SQLValidationError):
219
- """Raised when potential SQL injection is detected."""
220
-
221
- pattern: Optional[str]
222
-
223
- def __init__(self, message: str, sql: Optional[str] = None, pattern: Optional[str] = None) -> None:
224
- """Initialize with injection pattern context."""
225
- detail_message = message
226
- if pattern:
227
- detail_message = f"{message} (Pattern: {pattern})"
228
- super().__init__(detail_message, sql, RiskLevel.CRITICAL)
229
- self.pattern = pattern
230
-
231
-
232
- class UnsafeSQLError(SQLValidationError):
233
- """Raised when unsafe SQL constructs are detected."""
234
-
235
- construct: Optional[str]
236
-
237
- def __init__(self, message: str, sql: Optional[str] = None, construct: Optional[str] = None) -> None:
238
- """Initialize with unsafe construct context."""
239
- detail_message = message
240
- if construct:
241
- detail_message = f"{message} (Construct: {construct})"
242
- super().__init__(detail_message, sql, RiskLevel.HIGH)
243
- self.construct = construct
244
-
245
-
246
- # -- SQL Query Errors --
247
- class QueryError(SQLSpecError):
248
- """Base class for Query errors."""
249
-
250
-
251
- # -- SQL Parameter Errors --
252
- class ParameterError(SQLSpecError):
253
- """Base class for parameter-related errors."""
254
-
255
- sql: Optional[str]
256
-
257
- def __init__(self, message: str, sql: Optional[str] = None) -> None:
258
- """Initialize with optional SQL context."""
259
- detail_message = message
260
- if sql is not None:
261
- detail_message = f"{message}\nSQL: {sql}"
262
- super().__init__(detail=detail_message)
263
- self.sql = sql
264
-
265
-
266
- class UnknownParameterError(ParameterError):
267
- """Raised when encountering unknown parameter syntax."""
268
-
269
-
270
- class MissingParameterError(ParameterError):
271
- """Raised when required parameters are missing."""
272
-
273
-
274
- class ExtraParameterError(ParameterError):
275
- """Raised when extra parameters are provided."""
276
-
277
-
278
- class ParameterStyleMismatchError(SQLSpecError):
279
- """Error when parameter style doesn't match SQL placeholder style.
280
-
281
- This exception is raised when there's a mismatch between the parameter type
282
- (dictionary, tuple, etc.) and the placeholder style in the SQL query
283
- (named, positional, etc.).
284
- """
285
-
286
- sql: Optional[str]
287
-
288
- def __init__(self, message: Optional[str] = None, sql: Optional[str] = None) -> None:
289
- final_message = message
290
- if final_message is None:
291
- final_message = (
292
- "Parameter style mismatch: dictionary parameters provided but no named placeholders found in SQL."
293
- )
294
-
295
- detail_message = final_message
296
- if sql:
297
- detail_message = f"{final_message}\nSQL: {sql}"
298
-
299
- super().__init__(detail=detail_message)
300
- self.sql = sql
301
-
302
-
303
99
  class ImproperConfigurationError(SQLSpecError):
304
- """Improper Configuration error.
305
-
306
- This exception is raised only when a module depends on a dependency that has not been installed.
307
- """
100
+ """Raised when configuration is invalid or incomplete."""
308
101
 
309
102
 
310
103
  class SerializationError(SQLSpecError):
@@ -398,43 +191,3 @@ def wrap_exceptions(
398
191
  raise
399
192
  msg = "An error occurred during the operation."
400
193
  raise RepositoryError(detail=msg) from exc
401
-
402
-
403
- class PipelineExecutionError(SQLSpecError):
404
- """Rich error information for pipeline execution failures."""
405
-
406
- def __init__(
407
- self,
408
- message: str,
409
- *,
410
- operation_index: "Optional[int]" = None,
411
- failed_operation: "Optional[Any]" = None,
412
- partial_results: "Optional[list[Any]]" = None,
413
- driver_error: "Optional[Exception]" = None,
414
- ) -> None:
415
- """Initialize the pipeline execution error.
416
-
417
- Args:
418
- message: Error message describing the failure
419
- operation_index: Index of the operation that failed
420
- failed_operation: The PipelineOperation that failed
421
- partial_results: Results from operations that succeeded before the failure
422
- driver_error: Original exception from the database driver
423
- """
424
- super().__init__(message)
425
- self.operation_index = operation_index
426
- self.failed_operation = failed_operation
427
- self.partial_results = partial_results or []
428
- self.driver_error = driver_error
429
-
430
- def get_failed_sql(self) -> "Optional[str]":
431
- """Get the SQL that failed for debugging."""
432
- if self.failed_operation and hasattr(self.failed_operation, "sql"):
433
- return cast("str", self.failed_operation.sql.to_sql())
434
- return None
435
-
436
- def get_failed_parameters(self) -> "Optional[Any]":
437
- """Get the parameters that failed."""
438
- if self.failed_operation and hasattr(self.failed_operation, "original_params"):
439
- return self.failed_operation.original_params
440
- return None