sqlspec 0.10.1__py3-none-any.whl → 0.11.1__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.

@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import re
2
3
  from contextlib import asynccontextmanager, contextmanager
3
4
  from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
4
5
 
@@ -6,12 +7,14 @@ from psycopg import AsyncConnection, Connection
6
7
  from psycopg.rows import dict_row
7
8
 
8
9
  from sqlspec.base import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol
9
- from sqlspec.exceptions import SQLParsingError
10
- from sqlspec.mixins import SQLTranslatorMixin
11
- from sqlspec.statement import PARAM_REGEX, SQLStatement
10
+ from sqlspec.exceptions import ParameterStyleMismatchError
11
+ from sqlspec.filters import StatementFilter
12
+ from sqlspec.mixins import ResultConverter, SQLTranslatorMixin
13
+ from sqlspec.statement import SQLStatement
14
+ from sqlspec.typing import is_dict
12
15
 
13
16
  if TYPE_CHECKING:
14
- from collections.abc import AsyncGenerator, Generator, Sequence
17
+ from collections.abc import AsyncGenerator, Generator, Mapping, Sequence
15
18
 
16
19
  from sqlspec.typing import ModelDTOT, StatementParameterType, T
17
20
 
@@ -19,130 +22,96 @@ logger = logging.getLogger("sqlspec")
19
22
 
20
23
  __all__ = ("PsycopgAsyncConnection", "PsycopgAsyncDriver", "PsycopgSyncConnection", "PsycopgSyncDriver")
21
24
 
25
+
26
+ NAMED_PARAMS_PATTERN = re.compile(r"(?<!:):([a-zA-Z0-9_]+)")
27
+ # Pattern matches %(name)s format while trying to avoid matches in string literals and comments
28
+ PSYCOPG_PARAMS_PATTERN = re.compile(r"(?<!'|\"|\w)%\(([a-zA-Z0-9_]+)\)s(?!'|\")")
29
+
22
30
  PsycopgSyncConnection = Connection
23
31
  PsycopgAsyncConnection = AsyncConnection
24
32
 
25
33
 
26
34
  class PsycopgDriverBase:
27
- dialect: str
35
+ dialect: str = "postgres"
28
36
 
29
37
  def _process_sql_params(
30
38
  self,
31
39
  sql: str,
32
40
  parameters: "Optional[StatementParameterType]" = None,
33
- /,
41
+ *filters: "StatementFilter",
34
42
  **kwargs: Any,
35
43
  ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
36
- """Process SQL and parameters, converting :name -> %(name)s if needed."""
37
- stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
38
- processed_sql, processed_params = stmt.process()
44
+ """Process SQL and parameters using SQLStatement with dialect support.
45
+
46
+ Args:
47
+ sql: The SQL statement to process.
48
+ parameters: The parameters to bind to the statement.
49
+ *filters: Statement filters to apply.
50
+ **kwargs: Additional keyword arguments.
51
+
52
+ Raises:
53
+ ParameterStyleMismatchError: If the parameter style is mismatched.
54
+
55
+ Returns:
56
+ A tuple of (sql, parameters) ready for execution.
57
+ """
58
+ data_params_for_statement: Optional[Union[Mapping[str, Any], Sequence[Any]]] = None
59
+ combined_filters_list: list[StatementFilter] = list(filters)
60
+
61
+ if parameters is not None:
62
+ if isinstance(parameters, StatementFilter):
63
+ combined_filters_list.insert(0, parameters)
64
+ else:
65
+ data_params_for_statement = parameters
66
+ if data_params_for_statement is not None and not isinstance(data_params_for_statement, (list, tuple, dict)):
67
+ data_params_for_statement = (data_params_for_statement,)
68
+ statement = SQLStatement(sql, data_params_for_statement, kwargs=kwargs, dialect=self.dialect)
39
69
 
40
- if isinstance(processed_params, dict):
41
- parameter_dict = processed_params
42
- processed_sql_parts: list[str] = []
43
- last_end = 0
44
- found_params_regex: list[str] = []
70
+ # Apply all statement filters
71
+ for filter_obj in combined_filters_list:
72
+ statement = statement.apply_filter(filter_obj)
45
73
 
46
- for match in PARAM_REGEX.finditer(processed_sql):
47
- if match.group("dquote") or match.group("squote") or match.group("comment"):
48
- continue
74
+ processed_sql, processed_params, _ = statement.process()
49
75
 
50
- if match.group("var_name"):
51
- var_name = match.group("var_name")
52
- found_params_regex.append(var_name)
53
- start = match.start("var_name") - 1
54
- end = match.end("var_name")
76
+ if is_dict(processed_params):
77
+ named_params = NAMED_PARAMS_PATTERN.findall(processed_sql)
55
78
 
56
- if var_name not in parameter_dict:
57
- msg = (
58
- f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
59
- f"Processed SQL: {processed_sql}"
60
- )
61
- raise SQLParsingError(msg)
79
+ if not named_params:
80
+ if PSYCOPG_PARAMS_PATTERN.search(processed_sql):
81
+ return processed_sql, processed_params
62
82
 
63
- processed_sql_parts.extend((processed_sql[last_end:start], f"%({var_name})s"))
64
- last_end = end
83
+ if processed_params:
84
+ msg = "psycopg: Dictionary parameters provided, but no named placeholders found in SQL."
85
+ raise ParameterStyleMismatchError(msg)
86
+ return processed_sql, None
65
87
 
66
- processed_sql_parts.append(processed_sql[last_end:])
67
- final_sql = "".join(processed_sql_parts)
88
+ # Convert named parameters to psycopg's preferred format
89
+ return NAMED_PARAMS_PATTERN.sub("%s", processed_sql), tuple(processed_params[name] for name in named_params)
68
90
 
69
- if not found_params_regex and parameter_dict:
70
- logger.warning(
71
- "Dict params provided (%s), but no :name placeholders found. SQL: %s",
72
- list(parameter_dict.keys()),
73
- processed_sql,
74
- )
75
- return processed_sql, parameter_dict
91
+ # For sequence parameters, ensure they're a tuple
92
+ if isinstance(processed_params, (list, tuple)):
93
+ return processed_sql, tuple(processed_params)
76
94
 
77
- return final_sql, parameter_dict
95
+ # For scalar parameter or None
96
+ if processed_params is not None:
97
+ return processed_sql, (processed_params,)
78
98
 
79
- return processed_sql, processed_params
99
+ return processed_sql, None
80
100
 
81
101
 
82
102
  class PsycopgSyncDriver(
83
103
  PsycopgDriverBase,
84
104
  SQLTranslatorMixin["PsycopgSyncConnection"],
85
105
  SyncDriverAdapterProtocol["PsycopgSyncConnection"],
106
+ ResultConverter,
86
107
  ):
87
108
  """Psycopg Sync Driver Adapter."""
88
109
 
89
110
  connection: "PsycopgSyncConnection"
90
- dialect: str = "postgres"
91
111
 
92
112
  def __init__(self, connection: "PsycopgSyncConnection") -> None:
93
113
  self.connection = connection
94
114
 
95
- def _process_sql_params(
96
- self,
97
- sql: str,
98
- parameters: "Optional[StatementParameterType]" = None,
99
- /,
100
- **kwargs: Any,
101
- ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
102
- stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
103
- processed_sql, processed_params = stmt.process()
104
-
105
- if isinstance(processed_params, dict):
106
- parameter_dict = processed_params
107
- processed_sql_parts: list[str] = []
108
- last_end = 0
109
- found_params_regex: list[str] = []
110
-
111
- for match in PARAM_REGEX.finditer(processed_sql):
112
- if match.group("dquote") or match.group("squote") or match.group("comment"):
113
- continue
114
-
115
- if match.group("var_name"):
116
- var_name = match.group("var_name")
117
- found_params_regex.append(var_name)
118
- start = match.start("var_name") - 1
119
- end = match.end("var_name")
120
-
121
- if var_name not in parameter_dict:
122
- msg = (
123
- f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
124
- f"Processed SQL: {processed_sql}"
125
- )
126
- raise SQLParsingError(msg)
127
-
128
- processed_sql_parts.extend((processed_sql[last_end:start], f"%({var_name})s"))
129
- last_end = end
130
-
131
- processed_sql_parts.append(processed_sql[last_end:])
132
- final_sql = "".join(processed_sql_parts)
133
-
134
- if not found_params_regex and parameter_dict:
135
- logger.warning(
136
- "Dict params provided (%s), but no :name placeholders found. SQL: %s",
137
- list(parameter_dict.keys()),
138
- processed_sql,
139
- )
140
- return processed_sql, parameter_dict
141
-
142
- return final_sql, parameter_dict
143
-
144
- return processed_sql, processed_params
145
-
146
115
  @staticmethod
147
116
  @contextmanager
148
117
  def _with_cursor(connection: "PsycopgSyncConnection") -> "Generator[Any, None, None]":
@@ -158,8 +127,7 @@ class PsycopgSyncDriver(
158
127
  self,
159
128
  sql: str,
160
129
  parameters: "Optional[StatementParameterType]" = None,
161
- /,
162
- *,
130
+ *filters: "StatementFilter",
163
131
  connection: "Optional[PsycopgSyncConnection]" = None,
164
132
  schema_type: None = None,
165
133
  **kwargs: Any,
@@ -169,8 +137,7 @@ class PsycopgSyncDriver(
169
137
  self,
170
138
  sql: str,
171
139
  parameters: "Optional[StatementParameterType]" = None,
172
- /,
173
- *,
140
+ *filters: "StatementFilter",
174
141
  connection: "Optional[PsycopgSyncConnection]" = None,
175
142
  schema_type: "type[ModelDTOT]",
176
143
  **kwargs: Any,
@@ -179,8 +146,7 @@ class PsycopgSyncDriver(
179
146
  self,
180
147
  sql: str,
181
148
  parameters: "Optional[StatementParameterType]" = None,
182
- /,
183
- *,
149
+ *filters: "StatementFilter",
184
150
  schema_type: "Optional[type[ModelDTOT]]" = None,
185
151
  connection: "Optional[PsycopgSyncConnection]" = None,
186
152
  **kwargs: Any,
@@ -191,24 +157,21 @@ class PsycopgSyncDriver(
191
157
  List of row data as either model instances or dictionaries.
192
158
  """
193
159
  connection = self._connection(connection)
194
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
160
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
195
161
  with self._with_cursor(connection) as cursor:
196
162
  cursor.execute(sql, parameters)
197
163
  results = cursor.fetchall()
198
164
  if not results:
199
165
  return []
200
166
 
201
- if schema_type is not None:
202
- return [cast("ModelDTOT", schema_type(**row)) for row in results] # pyright: ignore[reportUnknownArgumentType]
203
- return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
167
+ return self.to_schema(cast("Sequence[dict[str, Any]]", results), schema_type=schema_type)
204
168
 
205
169
  @overload
206
170
  def select_one(
207
171
  self,
208
172
  sql: str,
209
173
  parameters: "Optional[StatementParameterType]" = None,
210
- /,
211
- *,
174
+ *filters: "StatementFilter",
212
175
  connection: "Optional[PsycopgSyncConnection]" = None,
213
176
  schema_type: None = None,
214
177
  **kwargs: Any,
@@ -218,8 +181,7 @@ class PsycopgSyncDriver(
218
181
  self,
219
182
  sql: str,
220
183
  parameters: "Optional[StatementParameterType]" = None,
221
- /,
222
- *,
184
+ *filters: "StatementFilter",
223
185
  connection: "Optional[PsycopgSyncConnection]" = None,
224
186
  schema_type: "type[ModelDTOT]",
225
187
  **kwargs: Any,
@@ -228,8 +190,7 @@ class PsycopgSyncDriver(
228
190
  self,
229
191
  sql: str,
230
192
  parameters: "Optional[StatementParameterType]" = None,
231
- /,
232
- *,
193
+ *filters: "StatementFilter",
233
194
  connection: "Optional[PsycopgSyncConnection]" = None,
234
195
  schema_type: "Optional[type[ModelDTOT]]" = None,
235
196
  **kwargs: Any,
@@ -240,22 +201,19 @@ class PsycopgSyncDriver(
240
201
  The first row of the query results.
241
202
  """
242
203
  connection = self._connection(connection)
243
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
204
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
244
205
  with self._with_cursor(connection) as cursor:
245
206
  cursor.execute(sql, parameters)
246
- row = cursor.fetchone()
247
- row = self.check_not_found(row)
248
- if schema_type is not None:
249
- return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
250
- return cast("dict[str,Any]", row)
207
+ result = cursor.fetchone()
208
+ result = self.check_not_found(result)
209
+ return self.to_schema(cast("dict[str, Any]", result), schema_type=schema_type)
251
210
 
252
211
  @overload
253
212
  def select_one_or_none(
254
213
  self,
255
214
  sql: str,
256
215
  parameters: "Optional[StatementParameterType]" = None,
257
- /,
258
- *,
216
+ *filters: "StatementFilter",
259
217
  connection: "Optional[PsycopgSyncConnection]" = None,
260
218
  schema_type: None = None,
261
219
  **kwargs: Any,
@@ -265,8 +223,7 @@ class PsycopgSyncDriver(
265
223
  self,
266
224
  sql: str,
267
225
  parameters: "Optional[StatementParameterType]" = None,
268
- /,
269
- *,
226
+ *filters: "StatementFilter",
270
227
  connection: "Optional[PsycopgSyncConnection]" = None,
271
228
  schema_type: "type[ModelDTOT]",
272
229
  **kwargs: Any,
@@ -275,8 +232,7 @@ class PsycopgSyncDriver(
275
232
  self,
276
233
  sql: str,
277
234
  parameters: "Optional[StatementParameterType]" = None,
278
- /,
279
- *,
235
+ *filters: "StatementFilter",
280
236
  connection: "Optional[PsycopgSyncConnection]" = None,
281
237
  schema_type: "Optional[type[ModelDTOT]]" = None,
282
238
  **kwargs: Any,
@@ -284,26 +240,23 @@ class PsycopgSyncDriver(
284
240
  """Fetch one row from the database.
285
241
 
286
242
  Returns:
287
- The first row of the query results.
243
+ The first row of the query results, or None if no results.
288
244
  """
289
245
  connection = self._connection(connection)
290
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
246
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
291
247
  with self._with_cursor(connection) as cursor:
292
248
  cursor.execute(sql, parameters)
293
- row = cursor.fetchone()
294
- if row is None:
249
+ result = cursor.fetchone()
250
+ if result is None:
295
251
  return None
296
- if schema_type is not None:
297
- return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
298
- return cast("dict[str,Any]", row)
252
+ return self.to_schema(cast("dict[str, Any]", result), schema_type=schema_type)
299
253
 
300
254
  @overload
301
255
  def select_value(
302
256
  self,
303
257
  sql: str,
304
258
  parameters: "Optional[StatementParameterType]" = None,
305
- /,
306
- *,
259
+ *filters: "StatementFilter",
307
260
  connection: "Optional[PsycopgSyncConnection]" = None,
308
261
  schema_type: None = None,
309
262
  **kwargs: Any,
@@ -313,8 +266,7 @@ class PsycopgSyncDriver(
313
266
  self,
314
267
  sql: str,
315
268
  parameters: "Optional[StatementParameterType]" = None,
316
- /,
317
- *,
269
+ *filters: "StatementFilter",
318
270
  connection: "Optional[PsycopgSyncConnection]" = None,
319
271
  schema_type: "type[T]",
320
272
  **kwargs: Any,
@@ -323,8 +275,7 @@ class PsycopgSyncDriver(
323
275
  self,
324
276
  sql: str,
325
277
  parameters: "Optional[StatementParameterType]" = None,
326
- /,
327
- *,
278
+ *filters: "StatementFilter",
328
279
  connection: "Optional[PsycopgSyncConnection]" = None,
329
280
  schema_type: "Optional[type[T]]" = None,
330
281
  **kwargs: Any,
@@ -332,27 +283,26 @@ class PsycopgSyncDriver(
332
283
  """Fetch a single value from the database.
333
284
 
334
285
  Returns:
335
- The first value from the first row of results, or None if no results.
286
+ The first value from the first row of results.
336
287
  """
337
288
  connection = self._connection(connection)
338
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
289
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
339
290
  with self._with_cursor(connection) as cursor:
340
291
  cursor.execute(sql, parameters)
341
- row = cursor.fetchone()
342
- row = self.check_not_found(row)
343
- val = next(iter(row.values())) if row else None
344
- val = self.check_not_found(val)
345
- if schema_type is not None:
346
- return schema_type(val) # type: ignore[call-arg]
347
- return val
292
+ result = cursor.fetchone()
293
+ result = self.check_not_found(result)
294
+
295
+ value = next(iter(result.values())) # Get the first value from the row
296
+ if schema_type is None:
297
+ return value
298
+ return schema_type(value) # type: ignore[call-arg]
348
299
 
349
300
  @overload
350
301
  def select_value_or_none(
351
302
  self,
352
303
  sql: str,
353
304
  parameters: "Optional[StatementParameterType]" = None,
354
- /,
355
- *,
305
+ *filters: "StatementFilter",
356
306
  connection: "Optional[PsycopgSyncConnection]" = None,
357
307
  schema_type: None = None,
358
308
  **kwargs: Any,
@@ -362,8 +312,7 @@ class PsycopgSyncDriver(
362
312
  self,
363
313
  sql: str,
364
314
  parameters: "Optional[StatementParameterType]" = None,
365
- /,
366
- *,
315
+ *filters: "StatementFilter",
367
316
  connection: "Optional[PsycopgSyncConnection]" = None,
368
317
  schema_type: "type[T]",
369
318
  **kwargs: Any,
@@ -372,8 +321,7 @@ class PsycopgSyncDriver(
372
321
  self,
373
322
  sql: str,
374
323
  parameters: "Optional[StatementParameterType]" = None,
375
- /,
376
- *,
324
+ *filters: "StatementFilter",
377
325
  connection: "Optional[PsycopgSyncConnection]" = None,
378
326
  schema_type: "Optional[type[T]]" = None,
379
327
  **kwargs: Any,
@@ -384,35 +332,33 @@ class PsycopgSyncDriver(
384
332
  The first value from the first row of results, or None if no results.
385
333
  """
386
334
  connection = self._connection(connection)
387
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
335
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
388
336
  with self._with_cursor(connection) as cursor:
389
337
  cursor.execute(sql, parameters)
390
- row = cursor.fetchone()
391
- if row is None:
392
- return None
393
- val = next(iter(row.values())) if row else None
394
- if val is None:
338
+ result = cursor.fetchone()
339
+ if result is None:
395
340
  return None
396
- if schema_type is not None:
397
- return schema_type(val) # type: ignore[call-arg]
398
- return val
341
+
342
+ value = next(iter(result.values())) # Get the first value from the row
343
+ if schema_type is None:
344
+ return value
345
+ return schema_type(value) # type: ignore[call-arg]
399
346
 
400
347
  def insert_update_delete(
401
348
  self,
402
349
  sql: str,
403
350
  parameters: "Optional[StatementParameterType]" = None,
404
- /,
405
- *,
351
+ *filters: "StatementFilter",
406
352
  connection: "Optional[PsycopgSyncConnection]" = None,
407
353
  **kwargs: Any,
408
354
  ) -> int:
409
- """Execute an INSERT, UPDATE, or DELETE query and return the number of affected rows.
355
+ """Insert, update, or delete data from the database.
410
356
 
411
357
  Returns:
412
- The number of rows affected by the operation.
358
+ Row count affected by the operation.
413
359
  """
414
360
  connection = self._connection(connection)
415
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
361
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
416
362
  with self._with_cursor(connection) as cursor:
417
363
  cursor.execute(sql, parameters)
418
364
  return getattr(cursor, "rowcount", -1) # pyright: ignore[reportUnknownMemberType]
@@ -422,8 +368,7 @@ class PsycopgSyncDriver(
422
368
  self,
423
369
  sql: str,
424
370
  parameters: "Optional[StatementParameterType]" = None,
425
- /,
426
- *,
371
+ *filters: "StatementFilter",
427
372
  connection: "Optional[PsycopgSyncConnection]" = None,
428
373
  schema_type: None = None,
429
374
  **kwargs: Any,
@@ -433,8 +378,7 @@ class PsycopgSyncDriver(
433
378
  self,
434
379
  sql: str,
435
380
  parameters: "Optional[StatementParameterType]" = None,
436
- /,
437
- *,
381
+ *filters: "StatementFilter",
438
382
  connection: "Optional[PsycopgSyncConnection]" = None,
439
383
  schema_type: "type[ModelDTOT]",
440
384
  **kwargs: Any,
@@ -443,36 +387,28 @@ class PsycopgSyncDriver(
443
387
  self,
444
388
  sql: str,
445
389
  parameters: "Optional[StatementParameterType]" = None,
446
- /,
447
- *,
390
+ *filters: "StatementFilter",
448
391
  connection: "Optional[PsycopgSyncConnection]" = None,
449
392
  schema_type: "Optional[type[ModelDTOT]]" = None,
450
393
  **kwargs: Any,
451
- ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
452
- """Insert, update, or delete data from the database and return result.
394
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
395
+ """Insert, update, or delete data with RETURNING clause.
453
396
 
454
397
  Returns:
455
- The first row of results.
398
+ The returned row data, as either a model instance or dictionary.
456
399
  """
457
400
  connection = self._connection(connection)
458
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
401
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
459
402
  with self._with_cursor(connection) as cursor:
460
403
  cursor.execute(sql, parameters)
461
404
  result = cursor.fetchone()
462
-
463
- if result is None:
464
- return None
465
-
466
- if schema_type is not None:
467
- return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
468
- return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
405
+ result = self.check_not_found(result)
406
+ return self.to_schema(cast("dict[str, Any]", result), schema_type=schema_type)
469
407
 
470
408
  def execute_script(
471
409
  self,
472
410
  sql: str,
473
411
  parameters: "Optional[StatementParameterType]" = None,
474
- /,
475
- *,
476
412
  connection: "Optional[PsycopgSyncConnection]" = None,
477
413
  **kwargs: Any,
478
414
  ) -> str:
@@ -492,11 +428,11 @@ class PsycopgAsyncDriver(
492
428
  PsycopgDriverBase,
493
429
  SQLTranslatorMixin["PsycopgAsyncConnection"],
494
430
  AsyncDriverAdapterProtocol["PsycopgAsyncConnection"],
431
+ ResultConverter,
495
432
  ):
496
433
  """Psycopg Async Driver Adapter."""
497
434
 
498
435
  connection: "PsycopgAsyncConnection"
499
- dialect: str = "postgres"
500
436
 
501
437
  def __init__(self, connection: "PsycopgAsyncConnection") -> None:
502
438
  self.connection = connection
@@ -516,8 +452,7 @@ class PsycopgAsyncDriver(
516
452
  self,
517
453
  sql: str,
518
454
  parameters: "Optional[StatementParameterType]" = None,
519
- /,
520
- *,
455
+ *filters: "StatementFilter",
521
456
  connection: "Optional[PsycopgAsyncConnection]" = None,
522
457
  schema_type: None = None,
523
458
  **kwargs: Any,
@@ -527,8 +462,7 @@ class PsycopgAsyncDriver(
527
462
  self,
528
463
  sql: str,
529
464
  parameters: "Optional[StatementParameterType]" = None,
530
- /,
531
- *,
465
+ *filters: "StatementFilter",
532
466
  connection: "Optional[PsycopgAsyncConnection]" = None,
533
467
  schema_type: "type[ModelDTOT]",
534
468
  **kwargs: Any,
@@ -537,10 +471,9 @@ class PsycopgAsyncDriver(
537
471
  self,
538
472
  sql: str,
539
473
  parameters: "Optional[StatementParameterType]" = None,
540
- /,
541
- *,
542
- connection: "Optional[PsycopgAsyncConnection]" = None,
474
+ *filters: "StatementFilter",
543
475
  schema_type: "Optional[type[ModelDTOT]]" = None,
476
+ connection: "Optional[PsycopgAsyncConnection]" = None,
544
477
  **kwargs: Any,
545
478
  ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
546
479
  """Fetch data from the database.
@@ -549,25 +482,21 @@ class PsycopgAsyncDriver(
549
482
  List of row data as either model instances or dictionaries.
550
483
  """
551
484
  connection = self._connection(connection)
552
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
553
- results: list[Union[ModelDTOT, dict[str, Any]]] = []
554
-
485
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
555
486
  async with self._with_cursor(connection) as cursor:
556
487
  await cursor.execute(sql, parameters)
557
488
  results = await cursor.fetchall()
558
489
  if not results:
559
490
  return []
560
- if schema_type is not None:
561
- return [cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row))) for row in results] # pyright: ignore[reportUnknownArgumentType]
562
- return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
491
+
492
+ return self.to_schema(cast("Sequence[dict[str, Any]]", results), schema_type=schema_type)
563
493
 
564
494
  @overload
565
495
  async def select_one(
566
496
  self,
567
497
  sql: str,
568
498
  parameters: "Optional[StatementParameterType]" = None,
569
- /,
570
- *,
499
+ *filters: "StatementFilter",
571
500
  connection: "Optional[PsycopgAsyncConnection]" = None,
572
501
  schema_type: None = None,
573
502
  **kwargs: Any,
@@ -577,8 +506,7 @@ class PsycopgAsyncDriver(
577
506
  self,
578
507
  sql: str,
579
508
  parameters: "Optional[StatementParameterType]" = None,
580
- /,
581
- *,
509
+ *filters: "StatementFilter",
582
510
  connection: "Optional[PsycopgAsyncConnection]" = None,
583
511
  schema_type: "type[ModelDTOT]",
584
512
  **kwargs: Any,
@@ -587,8 +515,7 @@ class PsycopgAsyncDriver(
587
515
  self,
588
516
  sql: str,
589
517
  parameters: "Optional[StatementParameterType]" = None,
590
- /,
591
- *,
518
+ *filters: "StatementFilter",
592
519
  connection: "Optional[PsycopgAsyncConnection]" = None,
593
520
  schema_type: "Optional[type[ModelDTOT]]" = None,
594
521
  **kwargs: Any,
@@ -599,23 +526,19 @@ class PsycopgAsyncDriver(
599
526
  The first row of the query results.
600
527
  """
601
528
  connection = self._connection(connection)
602
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
603
-
529
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
604
530
  async with self._with_cursor(connection) as cursor:
605
531
  await cursor.execute(sql, parameters)
606
- row = await cursor.fetchone()
607
- row = self.check_not_found(row)
608
- if schema_type is not None:
609
- return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
610
- return cast("dict[str,Any]", row)
532
+ result = await cursor.fetchone()
533
+ result = self.check_not_found(result)
534
+ return self.to_schema(cast("dict[str, Any]", result), schema_type=schema_type)
611
535
 
612
536
  @overload
613
537
  async def select_one_or_none(
614
538
  self,
615
539
  sql: str,
616
540
  parameters: "Optional[StatementParameterType]" = None,
617
- /,
618
- *,
541
+ *filters: "StatementFilter",
619
542
  connection: "Optional[PsycopgAsyncConnection]" = None,
620
543
  schema_type: None = None,
621
544
  **kwargs: Any,
@@ -625,8 +548,7 @@ class PsycopgAsyncDriver(
625
548
  self,
626
549
  sql: str,
627
550
  parameters: "Optional[StatementParameterType]" = None,
628
- /,
629
- *,
551
+ *filters: "StatementFilter",
630
552
  connection: "Optional[PsycopgAsyncConnection]" = None,
631
553
  schema_type: "type[ModelDTOT]",
632
554
  **kwargs: Any,
@@ -635,8 +557,7 @@ class PsycopgAsyncDriver(
635
557
  self,
636
558
  sql: str,
637
559
  parameters: "Optional[StatementParameterType]" = None,
638
- /,
639
- *,
560
+ *filters: "StatementFilter",
640
561
  schema_type: "Optional[type[ModelDTOT]]" = None,
641
562
  connection: "Optional[PsycopgAsyncConnection]" = None,
642
563
  **kwargs: Any,
@@ -644,27 +565,23 @@ class PsycopgAsyncDriver(
644
565
  """Fetch one row from the database.
645
566
 
646
567
  Returns:
647
- The first row of the query results.
568
+ The first row of the query results, or None if no results.
648
569
  """
649
570
  connection = self._connection(connection)
650
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
651
-
571
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
652
572
  async with self._with_cursor(connection) as cursor:
653
573
  await cursor.execute(sql, parameters)
654
- row = await cursor.fetchone()
655
- if row is None:
574
+ result = await cursor.fetchone()
575
+ if result is None:
656
576
  return None
657
- if schema_type is not None:
658
- return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
659
- return cast("dict[str,Any]", row)
577
+ return self.to_schema(cast("dict[str, Any]", result), schema_type=schema_type)
660
578
 
661
579
  @overload
662
580
  async def select_value(
663
581
  self,
664
582
  sql: str,
665
583
  parameters: "Optional[StatementParameterType]" = None,
666
- /,
667
- *,
584
+ *filters: "StatementFilter",
668
585
  connection: "Optional[PsycopgAsyncConnection]" = None,
669
586
  schema_type: None = None,
670
587
  **kwargs: Any,
@@ -674,8 +591,7 @@ class PsycopgAsyncDriver(
674
591
  self,
675
592
  sql: str,
676
593
  parameters: "Optional[StatementParameterType]" = None,
677
- /,
678
- *,
594
+ *filters: "StatementFilter",
679
595
  connection: "Optional[PsycopgAsyncConnection]" = None,
680
596
  schema_type: "type[T]",
681
597
  **kwargs: Any,
@@ -684,8 +600,7 @@ class PsycopgAsyncDriver(
684
600
  self,
685
601
  sql: str,
686
602
  parameters: "Optional[StatementParameterType]" = None,
687
- /,
688
- *,
603
+ *filters: "StatementFilter",
689
604
  connection: "Optional[PsycopgAsyncConnection]" = None,
690
605
  schema_type: "Optional[type[T]]" = None,
691
606
  **kwargs: Any,
@@ -693,27 +608,45 @@ class PsycopgAsyncDriver(
693
608
  """Fetch a single value from the database.
694
609
 
695
610
  Returns:
696
- The first value from the first row of results, or None if no results.
611
+ The first value from the first row of results.
697
612
  """
698
613
  connection = self._connection(connection)
699
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
700
-
614
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
701
615
  async with self._with_cursor(connection) as cursor:
702
616
  await cursor.execute(sql, parameters)
703
- row = await cursor.fetchone()
704
- row = self.check_not_found(row)
705
- val = next(iter(row.values())) if row else None
706
- val = self.check_not_found(val)
707
- if schema_type is not None:
708
- return schema_type(val) # type: ignore[call-arg]
709
- return val
617
+ result = await cursor.fetchone()
618
+ result = self.check_not_found(result)
619
+
620
+ value = next(iter(result.values())) # Get the first value from the row
621
+ if schema_type is None:
622
+ return value
623
+ return schema_type(value) # type: ignore[call-arg]
710
624
 
625
+ @overload
626
+ async def select_value_or_none(
627
+ self,
628
+ sql: str,
629
+ parameters: "Optional[StatementParameterType]" = None,
630
+ *filters: "StatementFilter",
631
+ connection: "Optional[PsycopgAsyncConnection]" = None,
632
+ schema_type: None = None,
633
+ **kwargs: Any,
634
+ ) -> "Optional[Any]": ...
635
+ @overload
711
636
  async def select_value_or_none(
712
637
  self,
713
638
  sql: str,
714
639
  parameters: "Optional[StatementParameterType]" = None,
715
- /,
716
- *,
640
+ *filters: "StatementFilter",
641
+ connection: "Optional[PsycopgAsyncConnection]" = None,
642
+ schema_type: "type[T]",
643
+ **kwargs: Any,
644
+ ) -> "Optional[T]": ...
645
+ async def select_value_or_none(
646
+ self,
647
+ sql: str,
648
+ parameters: "Optional[StatementParameterType]" = None,
649
+ *filters: "StatementFilter",
717
650
  connection: "Optional[PsycopgAsyncConnection]" = None,
718
651
  schema_type: "Optional[type[T]]" = None,
719
652
  **kwargs: Any,
@@ -724,52 +657,43 @@ class PsycopgAsyncDriver(
724
657
  The first value from the first row of results, or None if no results.
725
658
  """
726
659
  connection = self._connection(connection)
727
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
728
-
660
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
729
661
  async with self._with_cursor(connection) as cursor:
730
662
  await cursor.execute(sql, parameters)
731
- row = await cursor.fetchone()
732
- if row is None:
733
- return None
734
- val = next(iter(row.values())) if row else None
735
- if val is None:
663
+ result = await cursor.fetchone()
664
+ if result is None:
736
665
  return None
737
- if schema_type is not None:
738
- return schema_type(val) # type: ignore[call-arg]
739
- return val
666
+
667
+ value = next(iter(result.values())) # Get the first value from the row
668
+ if schema_type is None:
669
+ return value
670
+ return schema_type(value) # type: ignore[call-arg]
740
671
 
741
672
  async def insert_update_delete(
742
673
  self,
743
674
  sql: str,
744
675
  parameters: "Optional[StatementParameterType]" = None,
745
- /,
746
- *,
676
+ *filters: "StatementFilter",
747
677
  connection: "Optional[PsycopgAsyncConnection]" = None,
748
678
  **kwargs: Any,
749
679
  ) -> int:
750
- """Execute an INSERT, UPDATE, or DELETE query and return the number of affected rows.
680
+ """Insert, update, or delete data from the database.
751
681
 
752
682
  Returns:
753
- The number of rows affected by the operation.
683
+ Row count affected by the operation.
754
684
  """
755
685
  connection = self._connection(connection)
756
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
757
-
686
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
758
687
  async with self._with_cursor(connection) as cursor:
759
688
  await cursor.execute(sql, parameters)
760
- try:
761
- rowcount = int(cursor.rowcount)
762
- except (TypeError, ValueError):
763
- rowcount = -1
764
- return rowcount
689
+ return getattr(cursor, "rowcount", -1) # pyright: ignore[reportUnknownMemberType]
765
690
 
766
691
  @overload
767
692
  async def insert_update_delete_returning(
768
693
  self,
769
694
  sql: str,
770
695
  parameters: "Optional[StatementParameterType]" = None,
771
- /,
772
- *,
696
+ *filters: "StatementFilter",
773
697
  connection: "Optional[PsycopgAsyncConnection]" = None,
774
698
  schema_type: None = None,
775
699
  **kwargs: Any,
@@ -779,8 +703,7 @@ class PsycopgAsyncDriver(
779
703
  self,
780
704
  sql: str,
781
705
  parameters: "Optional[StatementParameterType]" = None,
782
- /,
783
- *,
706
+ *filters: "StatementFilter",
784
707
  connection: "Optional[PsycopgAsyncConnection]" = None,
785
708
  schema_type: "type[ModelDTOT]",
786
709
  **kwargs: Any,
@@ -789,37 +712,28 @@ class PsycopgAsyncDriver(
789
712
  self,
790
713
  sql: str,
791
714
  parameters: "Optional[StatementParameterType]" = None,
792
- /,
793
- *,
715
+ *filters: "StatementFilter",
794
716
  connection: "Optional[PsycopgAsyncConnection]" = None,
795
717
  schema_type: "Optional[type[ModelDTOT]]" = None,
796
718
  **kwargs: Any,
797
- ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
798
- """Insert, update, or delete data from the database and return result.
719
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
720
+ """Insert, update, or delete data with RETURNING clause.
799
721
 
800
722
  Returns:
801
- The first row of results.
723
+ The returned row data, as either a model instance or dictionary.
802
724
  """
803
725
  connection = self._connection(connection)
804
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
805
-
726
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
806
727
  async with self._with_cursor(connection) as cursor:
807
728
  await cursor.execute(sql, parameters)
808
729
  result = await cursor.fetchone()
809
-
810
- if result is None:
811
- return None
812
-
813
- if schema_type is not None:
814
- return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
815
- return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
730
+ result = self.check_not_found(result)
731
+ return self.to_schema(cast("dict[str, Any]", result), schema_type=schema_type)
816
732
 
817
733
  async def execute_script(
818
734
  self,
819
735
  sql: str,
820
736
  parameters: "Optional[StatementParameterType]" = None,
821
- /,
822
- *,
823
737
  connection: "Optional[PsycopgAsyncConnection]" = None,
824
738
  **kwargs: Any,
825
739
  ) -> str:
@@ -830,7 +744,6 @@ class PsycopgAsyncDriver(
830
744
  """
831
745
  connection = self._connection(connection)
832
746
  sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
833
-
834
747
  async with self._with_cursor(connection) as cursor:
835
748
  await cursor.execute(sql, parameters)
836
749
  return str(cursor.statusmessage) if cursor.statusmessage is not None else "DONE"