sqlspec 0.14.1__py3-none-any.whl → 0.16.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 (159) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +1 -1
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +480 -121
  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 -260
  10. sqlspec/adapters/adbc/driver.py +462 -367
  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 +18 -65
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
  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 +34 -18
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
  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 +25 -38
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
  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 +830 -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 +666 -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 +164 -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/cli.py +1 -1
  90. sqlspec/extensions/litestar/config.py +0 -1
  91. sqlspec/extensions/litestar/handlers.py +15 -26
  92. sqlspec/extensions/litestar/plugin.py +18 -16
  93. sqlspec/extensions/litestar/providers.py +17 -52
  94. sqlspec/loader.py +424 -105
  95. sqlspec/migrations/__init__.py +12 -0
  96. sqlspec/migrations/base.py +92 -68
  97. sqlspec/migrations/commands.py +24 -106
  98. sqlspec/migrations/loaders.py +402 -0
  99. sqlspec/migrations/runner.py +49 -51
  100. sqlspec/migrations/tracker.py +31 -44
  101. sqlspec/migrations/utils.py +64 -24
  102. sqlspec/protocols.py +7 -183
  103. sqlspec/storage/__init__.py +1 -1
  104. sqlspec/storage/backends/base.py +37 -40
  105. sqlspec/storage/backends/fsspec.py +136 -112
  106. sqlspec/storage/backends/obstore.py +138 -160
  107. sqlspec/storage/capabilities.py +5 -4
  108. sqlspec/storage/registry.py +57 -106
  109. sqlspec/typing.py +136 -115
  110. sqlspec/utils/__init__.py +2 -3
  111. sqlspec/utils/correlation.py +0 -3
  112. sqlspec/utils/deprecation.py +6 -6
  113. sqlspec/utils/fixtures.py +6 -6
  114. sqlspec/utils/logging.py +0 -2
  115. sqlspec/utils/module_loader.py +7 -12
  116. sqlspec/utils/singleton.py +0 -1
  117. sqlspec/utils/sync_tools.py +17 -38
  118. sqlspec/utils/text.py +12 -51
  119. sqlspec/utils/type_guards.py +443 -232
  120. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
  121. sqlspec-0.16.0.dist-info/RECORD +134 -0
  122. sqlspec/adapters/adbc/transformers.py +0 -108
  123. sqlspec/driver/connection.py +0 -207
  124. sqlspec/driver/mixins/_cache.py +0 -114
  125. sqlspec/driver/mixins/_csv_writer.py +0 -91
  126. sqlspec/driver/mixins/_pipeline.py +0 -508
  127. sqlspec/driver/mixins/_query_tools.py +0 -796
  128. sqlspec/driver/mixins/_result_utils.py +0 -138
  129. sqlspec/driver/mixins/_storage.py +0 -912
  130. sqlspec/driver/mixins/_type_coercion.py +0 -128
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/statement/__init__.py +0 -21
  133. sqlspec/statement/builder/_merge.py +0 -95
  134. sqlspec/statement/cache.py +0 -50
  135. sqlspec/statement/filters.py +0 -625
  136. sqlspec/statement/parameters.py +0 -956
  137. sqlspec/statement/pipelines/__init__.py +0 -210
  138. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  139. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  140. sqlspec/statement/pipelines/context.py +0 -109
  141. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  142. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  143. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  144. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  145. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  146. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  147. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  148. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  149. sqlspec/statement/pipelines/validators/_security.py +0 -967
  150. sqlspec/statement/result.py +0 -435
  151. sqlspec/statement/sql.py +0 -1774
  152. sqlspec/utils/cached_property.py +0 -25
  153. sqlspec/utils/statement_hashing.py +0 -203
  154. sqlspec-0.14.1.dist-info/RECORD +0 -145
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
  158. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
  159. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,128 +0,0 @@
1
- """Type coercion mixin for database drivers.
2
-
3
- This module provides a mixin that all database drivers use to handle
4
- TypedParameter objects and perform appropriate type conversions.
5
- """
6
-
7
- from decimal import Decimal
8
- from typing import TYPE_CHECKING, Any, Optional, Union
9
-
10
- from sqlspec.utils.type_guards import has_parameter_value
11
-
12
- if TYPE_CHECKING:
13
- from sqlspec.typing import SQLParameterType
14
-
15
- __all__ = ("TypeCoercionMixin",)
16
-
17
-
18
- class TypeCoercionMixin:
19
- """Mixin providing type coercion for database drivers.
20
-
21
- This mixin is used by all database drivers to handle TypedParameter objects
22
- and convert values to database-specific types.
23
- """
24
-
25
- def _process_parameters(self, parameters: "SQLParameterType") -> "SQLParameterType":
26
- """Process parameters, extracting values from TypedParameter objects.
27
-
28
- This method is called by drivers before executing SQL to handle
29
- TypedParameter objects and perform necessary type conversions.
30
-
31
- Args:
32
- parameters: Raw parameters that may contain TypedParameter objects
33
-
34
- Returns:
35
- Processed parameters with TypedParameter values extracted and converted
36
- """
37
- if parameters is None:
38
- return None
39
-
40
- if isinstance(parameters, dict):
41
- return self._process_dict_parameters(parameters)
42
- if isinstance(parameters, (list, tuple)):
43
- return self._process_sequence_parameters(parameters)
44
- # Single scalar parameter
45
- return self._coerce_parameter_type(parameters)
46
-
47
- def _process_dict_parameters(self, params: dict[str, Any]) -> dict[str, Any]:
48
- """Process dictionary parameters."""
49
- result = {}
50
- for key, value in params.items():
51
- result[key] = self._coerce_parameter_type(value)
52
- return result
53
-
54
- def _process_sequence_parameters(self, params: Union[list, tuple]) -> Union[list, tuple]:
55
- """Process list/tuple parameters."""
56
- result = [self._coerce_parameter_type(p) for p in params]
57
- return tuple(result) if isinstance(params, tuple) else result
58
-
59
- def _coerce_parameter_type(self, param: Any) -> Any:
60
- """Coerce a single parameter to the appropriate database type.
61
-
62
- This method checks if the parameter is a TypedParameter and extracts
63
- its value, then applies driver-specific type conversions.
64
-
65
- Args:
66
- param: Parameter value or TypedParameter object
67
-
68
- Returns:
69
- Coerced parameter value suitable for the database
70
- """
71
- if has_parameter_value(param):
72
- value = param.value
73
- type_hint = param.type_hint
74
-
75
- return self._apply_type_coercion(value, type_hint)
76
- # Regular parameter - apply default coercion
77
- return self._apply_type_coercion(param, None)
78
-
79
- def _apply_type_coercion(self, value: Any, type_hint: Optional[str]) -> Any:
80
- """Apply driver-specific type coercion.
81
-
82
- This method should be overridden by each driver to implement
83
- database-specific type conversions.
84
-
85
- Args:
86
- value: The value to coerce
87
- type_hint: Optional type hint from TypedParameter
88
-
89
- Returns:
90
- Coerced value
91
- """
92
- # Default implementation - override in specific drivers
93
- # This base implementation handles common cases
94
-
95
- if value is None:
96
- return None
97
-
98
- # Use type hint if available
99
- if type_hint:
100
- if type_hint == "boolean":
101
- return self._coerce_boolean(value)
102
- if type_hint == "decimal":
103
- return self._coerce_decimal(value)
104
- if type_hint == "json":
105
- return self._coerce_json(value)
106
- if type_hint.startswith("array"):
107
- return self._coerce_array(value)
108
-
109
- # Default: return value as-is
110
- return value
111
-
112
- def _coerce_boolean(self, value: Any) -> Any:
113
- """Coerce boolean values. Override in drivers without native boolean support."""
114
- return value
115
-
116
- def _coerce_decimal(self, value: Any) -> Any:
117
- """Coerce decimal values. Override for specific decimal handling."""
118
- if isinstance(value, str):
119
- return Decimal(value)
120
- return value
121
-
122
- def _coerce_json(self, value: Any) -> Any:
123
- """Coerce JSON values. Override for databases needing JSON strings."""
124
- return value
125
-
126
- def _coerce_array(self, value: Any) -> Any:
127
- """Coerce array values. Override for databases without native array support."""
128
- return value
@@ -1,138 +0,0 @@
1
- """Consolidated parameter processing utilities for database drivers.
2
-
3
- This module provides centralized parameter handling logic to avoid duplication
4
- across sync and async driver implementations.
5
- """
6
-
7
- from typing import TYPE_CHECKING, Any, Optional, Union
8
-
9
- from sqlspec.statement.filters import StatementFilter
10
- from sqlspec.utils.type_guards import is_sync_transaction_capable
11
-
12
- if TYPE_CHECKING:
13
- from sqlspec.typing import StatementParameters
14
-
15
- __all__ = (
16
- "convert_parameter_sequence",
17
- "convert_parameters_to_positional",
18
- "process_execute_many_parameters",
19
- "separate_filters_and_parameters",
20
- "should_use_transaction",
21
- )
22
-
23
-
24
- def separate_filters_and_parameters(
25
- parameters: "tuple[Union[StatementParameters, StatementFilter], ...]",
26
- ) -> "tuple[list[StatementFilter], list[Any]]":
27
- """Separate filters from parameters in a mixed parameter tuple.
28
-
29
- Args:
30
- parameters: Mixed tuple of parameters and filters
31
-
32
- Returns:
33
- Tuple of (filters, parameters) lists
34
- """
35
-
36
- filters: list[StatementFilter] = []
37
- param_values: list[Any] = []
38
-
39
- for param in parameters:
40
- if isinstance(param, StatementFilter):
41
- filters.append(param)
42
- else:
43
- param_values.append(param)
44
-
45
- return filters, param_values
46
-
47
-
48
- def process_execute_many_parameters(
49
- parameters: "tuple[Union[StatementParameters, StatementFilter], ...]",
50
- ) -> "tuple[list[StatementFilter], Optional[list[Any]]]":
51
- """Process parameters for execute_many operations.
52
-
53
- Args:
54
- parameters: Mixed tuple of parameters and filters
55
-
56
- Returns:
57
- Tuple of (filters, parameter_sequence)
58
- """
59
- filters, param_values = separate_filters_and_parameters(parameters)
60
-
61
- # Use first parameter as the sequence for execute_many
62
- param_sequence = param_values[0] if param_values else None
63
-
64
- # Normalize the parameter sequence
65
- param_sequence = convert_parameter_sequence(param_sequence)
66
-
67
- return filters, param_sequence
68
-
69
-
70
- def convert_parameter_sequence(params: Any) -> Optional[list[Any]]:
71
- """Normalize a parameter sequence to a list format.
72
-
73
- Args:
74
- params: Parameter sequence in various formats
75
-
76
- Returns:
77
- converted list of parameters or None
78
- """
79
- if params is None:
80
- return None
81
-
82
- if isinstance(params, list):
83
- return params
84
-
85
- if isinstance(params, tuple):
86
- return list(params)
87
-
88
- # Check if it's iterable (but not string or dict)
89
- # Use duck typing to check for iterable protocol
90
- try:
91
- iter(params)
92
- if not isinstance(params, (str, dict)):
93
- return list(params)
94
- except TypeError:
95
- pass
96
-
97
- # Single parameter, wrap in list
98
- return [params]
99
-
100
-
101
- def convert_parameters_to_positional(params: "dict[str, Any]", parameter_info: "list[Any]") -> list[Any]:
102
- """Convert named parameters to positional based on SQL order.
103
-
104
- Args:
105
- params: Dictionary of named parameters
106
- parameter_info: List of parameter info from SQL parsing
107
-
108
- Returns:
109
- List of positional parameters
110
- """
111
- if not params:
112
- return []
113
-
114
- # Handle param_0, param_1, etc. pattern
115
- if all(key.startswith("param_") for key in params):
116
- return [params[f"param_{i}"] for i in range(len(params))]
117
-
118
- # Convert based on parameter info order
119
- # Check for name attribute using getattr with default
120
- result = []
121
- for info in parameter_info:
122
- param_name = getattr(info, "name", None)
123
- if param_name is not None:
124
- result.append(params.get(param_name, None))
125
- return result
126
-
127
-
128
- def should_use_transaction(connection: Any, auto_commit: bool = True) -> bool:
129
- """Determine if a transaction should be used.
130
-
131
- Args:
132
- connection: Database connection object
133
- auto_commit: Whether auto-commit is enabled
134
-
135
- Returns:
136
- True if transaction capabilities are available and should be used
137
- """
138
- return False if auto_commit else is_sync_transaction_capable(connection)
@@ -1,21 +0,0 @@
1
- """SQL utilities, validation, and parameter handling."""
2
-
3
- from sqlspec.statement import builder, filters, parameters, result, sql
4
- from sqlspec.statement.filters import StatementFilter
5
- from sqlspec.statement.result import ArrowResult, SQLResult, StatementResult
6
- from sqlspec.statement.sql import SQL, SQLConfig, Statement
7
-
8
- __all__ = (
9
- "SQL",
10
- "ArrowResult",
11
- "SQLConfig",
12
- "SQLResult",
13
- "Statement",
14
- "StatementFilter",
15
- "StatementResult",
16
- "builder",
17
- "filters",
18
- "parameters",
19
- "result",
20
- "sql",
21
- )
@@ -1,95 +0,0 @@
1
- """Safe SQL query builder with validation and parameter binding.
2
-
3
- This module provides a fluent interface for building SQL queries safely,
4
- with automatic parameter binding and validation.
5
- """
6
-
7
- from dataclasses import dataclass
8
-
9
- from sqlglot import exp
10
-
11
- from sqlspec.statement.builder._base import QueryBuilder
12
- from sqlspec.statement.builder.mixins import (
13
- MergeIntoClauseMixin,
14
- MergeMatchedClauseMixin,
15
- MergeNotMatchedBySourceClauseMixin,
16
- MergeNotMatchedClauseMixin,
17
- MergeOnClauseMixin,
18
- MergeUsingClauseMixin,
19
- )
20
- from sqlspec.statement.result import SQLResult
21
- from sqlspec.typing import RowT
22
-
23
- __all__ = ("Merge",)
24
-
25
-
26
- @dataclass(unsafe_hash=True)
27
- class Merge(
28
- QueryBuilder[RowT],
29
- MergeUsingClauseMixin,
30
- MergeOnClauseMixin,
31
- MergeMatchedClauseMixin,
32
- MergeNotMatchedClauseMixin,
33
- MergeIntoClauseMixin,
34
- MergeNotMatchedBySourceClauseMixin,
35
- ):
36
- """Builder for MERGE statements.
37
-
38
- This builder provides a fluent interface for constructing SQL MERGE statements
39
- (also known as UPSERT in some databases) with automatic parameter binding and validation.
40
-
41
- Example:
42
- ```python
43
- # Basic MERGE statement
44
- merge_query = (
45
- Merge()
46
- .into("target_table")
47
- .using("source_table", "src")
48
- .on("target_table.id = src.id")
49
- .when_matched_then_update(
50
- {"name": "src.name", "updated_at": "NOW()"}
51
- )
52
- .when_not_matched_then_insert(
53
- columns=["id", "name", "created_at"],
54
- values=["src.id", "src.name", "NOW()"],
55
- )
56
- )
57
-
58
- # MERGE with subquery source
59
- source_query = (
60
- SelectBuilder()
61
- .select("id", "name", "email")
62
- .from_("temp_users")
63
- .where("status = 'pending'")
64
- )
65
-
66
- merge_query = (
67
- Merge()
68
- .into("users")
69
- .using(source_query, "src")
70
- .on("users.email = src.email")
71
- .when_matched_then_update({"name": "src.name"})
72
- .when_not_matched_then_insert(
73
- columns=["id", "name", "email"],
74
- values=["src.id", "src.name", "src.email"],
75
- )
76
- )
77
- ```
78
- """
79
-
80
- @property
81
- def _expected_result_type(self) -> "type[SQLResult[RowT]]":
82
- """Return the expected result type for this builder.
83
-
84
- Returns:
85
- The SQLResult type for MERGE statements.
86
- """
87
- return SQLResult[RowT]
88
-
89
- def _create_base_expression(self) -> "exp.Merge":
90
- """Create a base MERGE expression.
91
-
92
- Returns:
93
- A new sqlglot Merge expression with empty clauses.
94
- """
95
- return exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
@@ -1,50 +0,0 @@
1
- """Cache implementation for SQL statement processing."""
2
-
3
- import threading
4
- from collections import OrderedDict
5
- from typing import Any, Optional
6
-
7
- __all__ = ("SQLCache",)
8
-
9
-
10
- DEFAULT_CACHE_MAX_SIZE = 1000
11
-
12
-
13
- class SQLCache:
14
- """A thread-safe LRU cache for processed SQL states."""
15
-
16
- def __init__(self, max_size: int = DEFAULT_CACHE_MAX_SIZE) -> None:
17
- self.cache: OrderedDict[str, Any] = OrderedDict()
18
- self.max_size = max_size
19
- self.lock = threading.Lock()
20
-
21
- @property
22
- def size(self) -> int:
23
- """Get current cache size."""
24
- return len(self.cache)
25
-
26
- def get(self, key: str) -> Optional[Any]:
27
- """Get an item from the cache, marking it as recently used."""
28
- with self.lock:
29
- if key in self.cache:
30
- self.cache.move_to_end(key)
31
- return self.cache[key]
32
- return None
33
-
34
- def set(self, key: str, value: Any) -> None:
35
- """Set an item in the cache with LRU eviction."""
36
- with self.lock:
37
- if key in self.cache:
38
- self.cache.move_to_end(key)
39
- # Add new entry
40
- elif len(self.cache) >= self.max_size:
41
- self.cache.popitem(last=False)
42
- self.cache[key] = value
43
-
44
- def clear(self) -> None:
45
- """Clear the cache."""
46
- with self.lock:
47
- self.cache.clear()
48
-
49
-
50
- sql_cache = SQLCache()