sqlspec 0.9.0__py3-none-any.whl → 0.9.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,6 +1,6 @@
1
1
  import logging
2
2
  from contextlib import asynccontextmanager, contextmanager
3
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
4
4
 
5
5
  from psycopg.rows import dict_row
6
6
 
@@ -9,7 +9,7 @@ from sqlspec.exceptions import SQLParsingError
9
9
  from sqlspec.statement import PARAM_REGEX, SQLStatement
10
10
 
11
11
  if TYPE_CHECKING:
12
- from collections.abc import AsyncGenerator, Generator
12
+ from collections.abc import AsyncGenerator, Generator, Sequence
13
13
 
14
14
  from psycopg import AsyncConnection, Connection
15
15
 
@@ -20,7 +20,63 @@ logger = logging.getLogger("sqlspec")
20
20
  __all__ = ("PsycopgAsyncDriver", "PsycopgSyncDriver")
21
21
 
22
22
 
23
- class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
23
+ class PsycopgParameterParser:
24
+ dialect: str
25
+
26
+ def _process_sql_params(
27
+ self,
28
+ sql: str,
29
+ parameters: "Optional[StatementParameterType]" = None,
30
+ /,
31
+ **kwargs: Any,
32
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
33
+ """Process SQL and parameters, converting :name -> %(name)s if needed."""
34
+ stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
35
+ processed_sql, processed_params = stmt.process()
36
+
37
+ if isinstance(processed_params, dict):
38
+ parameter_dict = processed_params
39
+ processed_sql_parts: list[str] = []
40
+ last_end = 0
41
+ found_params_regex: list[str] = []
42
+
43
+ for match in PARAM_REGEX.finditer(processed_sql):
44
+ if match.group("dquote") or match.group("squote") or match.group("comment"):
45
+ continue
46
+
47
+ if match.group("var_name"):
48
+ var_name = match.group("var_name")
49
+ found_params_regex.append(var_name)
50
+ start = match.start("var_name") - 1
51
+ end = match.end("var_name")
52
+
53
+ if var_name not in parameter_dict:
54
+ msg = (
55
+ f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
56
+ f"Processed SQL: {processed_sql}"
57
+ )
58
+ raise SQLParsingError(msg)
59
+
60
+ processed_sql_parts.extend((processed_sql[last_end:start], f"%({var_name})s"))
61
+ last_end = end
62
+
63
+ processed_sql_parts.append(processed_sql[last_end:])
64
+ final_sql = "".join(processed_sql_parts)
65
+
66
+ if not found_params_regex and parameter_dict:
67
+ logger.warning(
68
+ "Dict params provided (%s), but no :name placeholders found. SQL: %s",
69
+ list(parameter_dict.keys()),
70
+ processed_sql,
71
+ )
72
+ return processed_sql, parameter_dict
73
+
74
+ return final_sql, parameter_dict
75
+
76
+ return processed_sql, processed_params
77
+
78
+
79
+ class PsycopgSyncDriver(PsycopgParameterParser, SyncDriverAdapterProtocol["Connection"]):
24
80
  """Psycopg Sync Driver Adapter."""
25
81
 
26
82
  connection: "Connection"
@@ -90,6 +146,29 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
90
146
  finally:
91
147
  cursor.close()
92
148
 
149
+ # --- Public API Methods --- #
150
+ @overload
151
+ def select(
152
+ self,
153
+ sql: str,
154
+ parameters: "Optional[StatementParameterType]" = None,
155
+ /,
156
+ *,
157
+ connection: "Optional[Connection]" = None,
158
+ schema_type: None = None,
159
+ **kwargs: Any,
160
+ ) -> "Sequence[dict[str, Any]]": ...
161
+ @overload
162
+ def select(
163
+ self,
164
+ sql: str,
165
+ parameters: "Optional[StatementParameterType]" = None,
166
+ /,
167
+ *,
168
+ connection: "Optional[Connection]" = None,
169
+ schema_type: "type[ModelDTOT]",
170
+ **kwargs: Any,
171
+ ) -> "Sequence[ModelDTOT]": ...
93
172
  def select(
94
173
  self,
95
174
  sql: str,
@@ -99,7 +178,7 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
99
178
  schema_type: "Optional[type[ModelDTOT]]" = None,
100
179
  connection: "Optional[Connection]" = None,
101
180
  **kwargs: Any,
102
- ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
181
+ ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
103
182
  """Fetch data from the database.
104
183
 
105
184
  Returns:
@@ -117,6 +196,28 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
117
196
  return [cast("ModelDTOT", schema_type(**row)) for row in results] # pyright: ignore[reportUnknownArgumentType]
118
197
  return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
119
198
 
199
+ @overload
200
+ def select_one(
201
+ self,
202
+ sql: str,
203
+ parameters: "Optional[StatementParameterType]" = None,
204
+ /,
205
+ *,
206
+ connection: "Optional[Connection]" = None,
207
+ schema_type: None = None,
208
+ **kwargs: Any,
209
+ ) -> "dict[str, Any]": ...
210
+ @overload
211
+ def select_one(
212
+ self,
213
+ sql: str,
214
+ parameters: "Optional[StatementParameterType]" = None,
215
+ /,
216
+ *,
217
+ connection: "Optional[Connection]" = None,
218
+ schema_type: "type[ModelDTOT]",
219
+ **kwargs: Any,
220
+ ) -> "ModelDTOT": ...
120
221
  def select_one(
121
222
  self,
122
223
  sql: str,
@@ -142,6 +243,28 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
142
243
  return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
143
244
  return cast("dict[str,Any]", row)
144
245
 
246
+ @overload
247
+ def select_one_or_none(
248
+ self,
249
+ sql: str,
250
+ parameters: "Optional[StatementParameterType]" = None,
251
+ /,
252
+ *,
253
+ connection: "Optional[Connection]" = None,
254
+ schema_type: None = None,
255
+ **kwargs: Any,
256
+ ) -> "Optional[dict[str, Any]]": ...
257
+ @overload
258
+ def select_one_or_none(
259
+ self,
260
+ sql: str,
261
+ parameters: "Optional[StatementParameterType]" = None,
262
+ /,
263
+ *,
264
+ connection: "Optional[Connection]" = None,
265
+ schema_type: "type[ModelDTOT]",
266
+ **kwargs: Any,
267
+ ) -> "Optional[ModelDTOT]": ...
145
268
  def select_one_or_none(
146
269
  self,
147
270
  sql: str,
@@ -168,6 +291,28 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
168
291
  return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
169
292
  return cast("dict[str,Any]", row)
170
293
 
294
+ @overload
295
+ def select_value(
296
+ self,
297
+ sql: str,
298
+ parameters: "Optional[StatementParameterType]" = None,
299
+ /,
300
+ *,
301
+ connection: "Optional[Connection]" = None,
302
+ schema_type: None = None,
303
+ **kwargs: Any,
304
+ ) -> "Any": ...
305
+ @overload
306
+ def select_value(
307
+ self,
308
+ sql: str,
309
+ parameters: "Optional[StatementParameterType]" = None,
310
+ /,
311
+ *,
312
+ connection: "Optional[Connection]" = None,
313
+ schema_type: "type[T]",
314
+ **kwargs: Any,
315
+ ) -> "T": ...
171
316
  def select_value(
172
317
  self,
173
318
  sql: str,
@@ -195,6 +340,28 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
195
340
  return schema_type(val) # type: ignore[call-arg]
196
341
  return val
197
342
 
343
+ @overload
344
+ def select_value_or_none(
345
+ self,
346
+ sql: str,
347
+ parameters: "Optional[StatementParameterType]" = None,
348
+ /,
349
+ *,
350
+ connection: "Optional[Connection]" = None,
351
+ schema_type: None = None,
352
+ **kwargs: Any,
353
+ ) -> "Optional[Any]": ...
354
+ @overload
355
+ def select_value_or_none(
356
+ self,
357
+ sql: str,
358
+ parameters: "Optional[StatementParameterType]" = None,
359
+ /,
360
+ *,
361
+ connection: "Optional[Connection]" = None,
362
+ schema_type: "type[T]",
363
+ **kwargs: Any,
364
+ ) -> "Optional[T]": ...
198
365
  def select_value_or_none(
199
366
  self,
200
367
  sql: str,
@@ -244,6 +411,28 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
244
411
  cursor.execute(sql, parameters)
245
412
  return getattr(cursor, "rowcount", -1) # pyright: ignore[reportUnknownMemberType]
246
413
 
414
+ @overload
415
+ def insert_update_delete_returning(
416
+ self,
417
+ sql: str,
418
+ parameters: "Optional[StatementParameterType]" = None,
419
+ /,
420
+ *,
421
+ connection: "Optional[Connection]" = None,
422
+ schema_type: None = None,
423
+ **kwargs: Any,
424
+ ) -> "dict[str, Any]": ...
425
+ @overload
426
+ def insert_update_delete_returning(
427
+ self,
428
+ sql: str,
429
+ parameters: "Optional[StatementParameterType]" = None,
430
+ /,
431
+ *,
432
+ connection: "Optional[Connection]" = None,
433
+ schema_type: "type[ModelDTOT]",
434
+ **kwargs: Any,
435
+ ) -> "ModelDTOT": ...
247
436
  def insert_update_delete_returning(
248
437
  self,
249
438
  sql: str,
@@ -293,7 +482,7 @@ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
293
482
  return str(cursor.statusmessage) if cursor.statusmessage is not None else "DONE"
294
483
 
295
484
 
296
- class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
485
+ class PsycopgAsyncDriver(PsycopgParameterParser, AsyncDriverAdapterProtocol["AsyncConnection"]):
297
486
  """Psycopg Async Driver Adapter."""
298
487
 
299
488
  connection: "AsyncConnection"
@@ -302,58 +491,6 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
302
491
  def __init__(self, connection: "AsyncConnection") -> None:
303
492
  self.connection = connection
304
493
 
305
- def _process_sql_params(
306
- self,
307
- sql: str,
308
- parameters: "Optional[StatementParameterType]" = None,
309
- /,
310
- **kwargs: Any,
311
- ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
312
- """Process SQL and parameters, converting :name -> %(name)s if needed."""
313
- stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
314
- processed_sql, processed_params = stmt.process()
315
-
316
- if isinstance(processed_params, dict):
317
- parameter_dict = processed_params
318
- processed_sql_parts: list[str] = []
319
- last_end = 0
320
- found_params_regex: list[str] = []
321
-
322
- for match in PARAM_REGEX.finditer(processed_sql):
323
- if match.group("dquote") or match.group("squote") or match.group("comment"):
324
- continue
325
-
326
- if match.group("var_name"):
327
- var_name = match.group("var_name")
328
- found_params_regex.append(var_name)
329
- start = match.start("var_name") - 1
330
- end = match.end("var_name")
331
-
332
- if var_name not in parameter_dict:
333
- msg = (
334
- f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
335
- f"Processed SQL: {processed_sql}"
336
- )
337
- raise SQLParsingError(msg)
338
-
339
- processed_sql_parts.extend((processed_sql[last_end:start], f"%({var_name})s"))
340
- last_end = end
341
-
342
- processed_sql_parts.append(processed_sql[last_end:])
343
- final_sql = "".join(processed_sql_parts)
344
-
345
- if not found_params_regex and parameter_dict:
346
- logger.warning(
347
- "Dict params provided (%s), but no :name placeholders found. SQL: %s",
348
- list(parameter_dict.keys()),
349
- processed_sql,
350
- )
351
- return processed_sql, parameter_dict
352
-
353
- return final_sql, parameter_dict
354
-
355
- return processed_sql, processed_params
356
-
357
494
  @staticmethod
358
495
  @asynccontextmanager
359
496
  async def _with_cursor(connection: "AsyncConnection") -> "AsyncGenerator[Any, None]":
@@ -363,6 +500,29 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
363
500
  finally:
364
501
  await cursor.close()
365
502
 
503
+ # --- Public API Methods --- #
504
+ @overload
505
+ async def select(
506
+ self,
507
+ sql: str,
508
+ parameters: "Optional[StatementParameterType]" = None,
509
+ /,
510
+ *,
511
+ connection: "Optional[AsyncConnection]" = None,
512
+ schema_type: None = None,
513
+ **kwargs: Any,
514
+ ) -> "Sequence[dict[str, Any]]": ...
515
+ @overload
516
+ async def select(
517
+ self,
518
+ sql: str,
519
+ parameters: "Optional[StatementParameterType]" = None,
520
+ /,
521
+ *,
522
+ connection: "Optional[AsyncConnection]" = None,
523
+ schema_type: "type[ModelDTOT]",
524
+ **kwargs: Any,
525
+ ) -> "Sequence[ModelDTOT]": ...
366
526
  async def select(
367
527
  self,
368
528
  sql: str,
@@ -372,7 +532,7 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
372
532
  connection: "Optional[AsyncConnection]" = None,
373
533
  schema_type: "Optional[type[ModelDTOT]]" = None,
374
534
  **kwargs: Any,
375
- ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
535
+ ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
376
536
  """Fetch data from the database.
377
537
 
378
538
  Returns:
@@ -391,6 +551,28 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
391
551
  return [cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row))) for row in results] # pyright: ignore[reportUnknownArgumentType]
392
552
  return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
393
553
 
554
+ @overload
555
+ async def select_one(
556
+ self,
557
+ sql: str,
558
+ parameters: "Optional[StatementParameterType]" = None,
559
+ /,
560
+ *,
561
+ connection: "Optional[AsyncConnection]" = None,
562
+ schema_type: None = None,
563
+ **kwargs: Any,
564
+ ) -> "dict[str, Any]": ...
565
+ @overload
566
+ async def select_one(
567
+ self,
568
+ sql: str,
569
+ parameters: "Optional[StatementParameterType]" = None,
570
+ /,
571
+ *,
572
+ connection: "Optional[AsyncConnection]" = None,
573
+ schema_type: "type[ModelDTOT]",
574
+ **kwargs: Any,
575
+ ) -> "ModelDTOT": ...
394
576
  async def select_one(
395
577
  self,
396
578
  sql: str,
@@ -417,6 +599,28 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
417
599
  return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
418
600
  return cast("dict[str,Any]", row)
419
601
 
602
+ @overload
603
+ async def select_one_or_none(
604
+ self,
605
+ sql: str,
606
+ parameters: "Optional[StatementParameterType]" = None,
607
+ /,
608
+ *,
609
+ connection: "Optional[AsyncConnection]" = None,
610
+ schema_type: None = None,
611
+ **kwargs: Any,
612
+ ) -> "Optional[dict[str, Any]]": ...
613
+ @overload
614
+ async def select_one_or_none(
615
+ self,
616
+ sql: str,
617
+ parameters: "Optional[StatementParameterType]" = None,
618
+ /,
619
+ *,
620
+ connection: "Optional[AsyncConnection]" = None,
621
+ schema_type: "type[ModelDTOT]",
622
+ **kwargs: Any,
623
+ ) -> "Optional[ModelDTOT]": ...
420
624
  async def select_one_or_none(
421
625
  self,
422
626
  sql: str,
@@ -444,6 +648,28 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
444
648
  return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
445
649
  return cast("dict[str,Any]", row)
446
650
 
651
+ @overload
652
+ async def select_value(
653
+ self,
654
+ sql: str,
655
+ parameters: "Optional[StatementParameterType]" = None,
656
+ /,
657
+ *,
658
+ connection: "Optional[AsyncConnection]" = None,
659
+ schema_type: None = None,
660
+ **kwargs: Any,
661
+ ) -> "Any": ...
662
+ @overload
663
+ async def select_value(
664
+ self,
665
+ sql: str,
666
+ parameters: "Optional[StatementParameterType]" = None,
667
+ /,
668
+ *,
669
+ connection: "Optional[AsyncConnection]" = None,
670
+ schema_type: "type[T]",
671
+ **kwargs: Any,
672
+ ) -> "T": ...
447
673
  async def select_value(
448
674
  self,
449
675
  sql: str,
@@ -527,6 +753,28 @@ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
527
753
  rowcount = -1
528
754
  return rowcount
529
755
 
756
+ @overload
757
+ async def insert_update_delete_returning(
758
+ self,
759
+ sql: str,
760
+ parameters: "Optional[StatementParameterType]" = None,
761
+ /,
762
+ *,
763
+ connection: "Optional[AsyncConnection]" = None,
764
+ schema_type: None = None,
765
+ **kwargs: Any,
766
+ ) -> "dict[str, Any]": ...
767
+ @overload
768
+ async def insert_update_delete_returning(
769
+ self,
770
+ sql: str,
771
+ parameters: "Optional[StatementParameterType]" = None,
772
+ /,
773
+ *,
774
+ connection: "Optional[AsyncConnection]" = None,
775
+ schema_type: "type[ModelDTOT]",
776
+ **kwargs: Any,
777
+ ) -> "ModelDTOT": ...
530
778
  async def insert_update_delete_returning(
531
779
  self,
532
780
  sql: str,