asyncpg-typed 0.1.1__py3-none-any.whl → 0.1.2__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.
asyncpg_typed/__init__.py CHANGED
@@ -4,7 +4,7 @@ Type-safe queries for asyncpg.
4
4
  :see: https://github.com/hunyadi/asyncpg_typed
5
5
  """
6
6
 
7
- __version__ = "0.1.1"
7
+ __version__ = "0.1.2"
8
8
  __author__ = "Levente Hunyadi"
9
9
  __copyright__ = "Copyright 2025, Levente Hunyadi"
10
10
  __license__ = "MIT"
@@ -13,14 +13,15 @@ __status__ = "Production"
13
13
 
14
14
  import enum
15
15
  import sys
16
+ import typing
16
17
  from abc import abstractmethod
17
- from collections.abc import Iterable, Sequence
18
- from datetime import date, datetime, time
18
+ from collections.abc import Callable, Iterable, Sequence
19
+ from datetime import date, datetime, time, timedelta
19
20
  from decimal import Decimal
20
21
  from functools import reduce
21
22
  from io import StringIO
22
23
  from types import UnionType
23
- from typing import Any, Generic, TypeAlias, TypeVar, Union, get_args, get_origin, overload
24
+ from typing import Any, Protocol, TypeAlias, TypeVar, Union, get_args, get_origin, overload
24
25
  from uuid import UUID
25
26
 
26
27
  import asyncpg
@@ -31,12 +32,11 @@ if sys.version_info < (3, 11):
31
32
  else:
32
33
  from typing import LiteralString, TypeVarTuple, Unpack
33
34
 
34
- # list of supported data types
35
- DATA_TYPES: list[type[Any]] = [bool, int, float, Decimal, date, time, datetime, str, bytes, UUID]
35
+ JsonType = None | bool | int | float | str | dict[str, "JsonType"] | list["JsonType"]
36
36
 
37
- # maximum number of inbound query parameters
38
- NUM_ARGS = 8
37
+ RequiredJsonType = bool | int | float | str | dict[str, "JsonType"] | list["JsonType"]
39
38
 
39
+ TargetType: TypeAlias = type[Any] | UnionType
40
40
 
41
41
  if sys.version_info >= (3, 11):
42
42
 
@@ -83,6 +83,14 @@ def is_standard_type(tp: Any) -> bool:
83
83
  return tp.__module__ == "builtins" or tp.__module__ == UnionType.__module__
84
84
 
85
85
 
86
+ def is_json_type(tp: Any) -> bool:
87
+ """
88
+ `True` if the type represents an object de-serialized from a JSON string.
89
+ """
90
+
91
+ return tp in [JsonType, RequiredJsonType]
92
+
93
+
86
94
  def make_union_type(tpl: list[Any]) -> UnionType:
87
95
  """
88
96
  Creates a `UnionType` (a.k.a. `A | B | C`) dynamically at run time.
@@ -111,38 +119,77 @@ def get_required_type(tp: Any) -> Any:
111
119
  return type(None)
112
120
 
113
121
 
114
- # maps PostgreSQL internal type names to Python types
115
- _name_to_type: dict[str, Any] = {
116
- "bool": bool,
117
- "int2": int,
118
- "int4": int,
119
- "int8": int,
120
- "float4": float,
121
- "float8": float,
122
- "numeric": Decimal,
123
- "date": date,
124
- "time": time,
125
- "timetz": time,
126
- "timestamp": datetime,
127
- "timestamptz": datetime,
128
- "bpchar": str,
129
- "varchar": str,
130
- "text": str,
131
- "bytea": bytes,
132
- "json": str,
133
- "jsonb": str,
134
- "uuid": UUID,
122
+ _json_converter: Callable[[str], JsonType]
123
+ if typing.TYPE_CHECKING:
124
+ import json
125
+
126
+ _json_decoder = json.JSONDecoder()
127
+ _json_converter = _json_decoder.decode
128
+ else:
129
+ try:
130
+ import orjson
131
+
132
+ _json_converter = orjson.loads
133
+ except ModuleNotFoundError:
134
+ import json
135
+
136
+ _json_decoder = json.JSONDecoder()
137
+ _json_converter = _json_decoder.decode
138
+
139
+
140
+ def get_converter_for(tp: Any) -> Callable[[Any], Any]:
141
+ """
142
+ Returns a callable that takes a wire type and returns a target type.
143
+
144
+ A wire type is one of the types returned by asyncpg.
145
+ A target type is one of the types supported by the library.
146
+ """
147
+
148
+ if is_json_type(tp):
149
+ # asyncpg returns fields of type `json` and `jsonb` as `str`, which must be de-serialized
150
+ return _json_converter
151
+ else:
152
+ # target data types that require conversion must have a single-argument `__init__` that takes an object of the source type
153
+ return tp
154
+
155
+
156
+ # maps PostgreSQL internal type names to compatible Python types
157
+ _name_to_type: dict[str, tuple[Any, ...]] = {
158
+ "bool": (bool,),
159
+ "int2": (int,),
160
+ "int4": (int,),
161
+ "int8": (int,),
162
+ "float4": (float,),
163
+ "float8": (float,),
164
+ "numeric": (Decimal,),
165
+ "date": (date,),
166
+ "time": (time,),
167
+ "timetz": (time,),
168
+ "timestamp": (datetime,),
169
+ "timestamptz": (datetime,),
170
+ "interval": (timedelta,),
171
+ "bpchar": (str,),
172
+ "varchar": (str,),
173
+ "text": (str,),
174
+ "bytea": (bytes,),
175
+ "json": (str, RequiredJsonType),
176
+ "jsonb": (str, RequiredJsonType),
177
+ "uuid": (UUID,),
178
+ "xml": (str,),
135
179
  }
136
180
 
137
181
 
138
- def check_data_type(schema: str, name: str, data_type: type[Any]) -> bool:
182
+ def check_data_type(schema: str, name: str, data_type: TargetType) -> bool:
139
183
  """
140
184
  Verifies if the Python target type can represent the PostgreSQL source type.
141
185
  """
142
186
 
143
187
  if schema == "pg_catalog":
144
- expected_type = _name_to_type.get(name)
145
- return expected_type == data_type
188
+ if is_enum_type(data_type):
189
+ return name in ["bpchar", "varchar", "text"]
190
+
191
+ expected_types = _name_to_type.get(name)
192
+ return expected_types is not None and data_type in expected_types
146
193
  else:
147
194
  if is_standard_type(data_type):
148
195
  return False
@@ -153,9 +200,9 @@ def check_data_type(schema: str, name: str, data_type: type[Any]) -> bool:
153
200
 
154
201
  class _SQLPlaceholder:
155
202
  ordinal: int
156
- data_type: type[Any]
203
+ data_type: TargetType
157
204
 
158
- def __init__(self, ordinal: int, data_type: type[Any]) -> None:
205
+ def __init__(self, ordinal: int, data_type: TargetType) -> None:
159
206
  self.ordinal = ordinal
160
207
  self.data_type = data_type
161
208
 
@@ -169,14 +216,15 @@ class _SQLObject:
169
216
  """
170
217
 
171
218
  parameter_data_types: tuple[_SQLPlaceholder, ...]
172
- resultset_data_types: tuple[type[Any], ...]
219
+ resultset_data_types: tuple[TargetType, ...]
173
220
  required: int
174
221
  cast: int
222
+ converters: tuple[Callable[[Any], Any], ...]
175
223
 
176
224
  def __init__(
177
225
  self,
178
- input_data_types: tuple[type[Any], ...],
179
- output_data_types: tuple[type[Any], ...],
226
+ input_data_types: tuple[TargetType, ...],
227
+ output_data_types: tuple[TargetType, ...],
180
228
  ) -> None:
181
229
  self.parameter_data_types = tuple(_SQLPlaceholder(ordinal, get_required_type(arg)) for ordinal, arg in enumerate(input_data_types, start=1))
182
230
  self.resultset_data_types = tuple(get_required_type(data_type) for data_type in output_data_types)
@@ -187,12 +235,14 @@ class _SQLObject:
187
235
  required |= (not is_optional_type(data_type)) << index
188
236
  self.required = required
189
237
 
190
- # create a bit-field of types that require cast/conversion (1: pass to __init__; 0: skip)
238
+ # create a bit-field of types that require cast or serialization (1: apply conversion; 0: forward value as-is)
191
239
  cast = 0
192
240
  for index, data_type in enumerate(self.resultset_data_types):
193
- cast |= is_enum_type(data_type) << index
241
+ cast |= (is_enum_type(data_type) or is_json_type(data_type)) << index
194
242
  self.cast = cast
195
243
 
244
+ self.converters = tuple(get_converter_for(data_type) for data_type in self.resultset_data_types)
245
+
196
246
  def _raise_required_is_none(self, row: tuple[Any, ...], row_index: int | None = None) -> None:
197
247
  """
198
248
  Raises an error with the index of the first column value that is of a required type but has been assigned a value of `None`.
@@ -345,8 +395,8 @@ if sys.version_info >= (3, 14):
345
395
  self,
346
396
  template: Template,
347
397
  *,
348
- args: tuple[type[Any], ...],
349
- resultset: tuple[type[Any], ...],
398
+ args: tuple[TargetType, ...],
399
+ resultset: tuple[TargetType, ...],
350
400
  ) -> None:
351
401
  super().__init__(args, resultset)
352
402
 
@@ -395,8 +445,8 @@ class _SQLString(_SQLObject):
395
445
  self,
396
446
  sql: str,
397
447
  *,
398
- args: tuple[type[Any], ...],
399
- resultset: tuple[type[Any], ...],
448
+ args: tuple[TargetType, ...],
449
+ resultset: tuple[TargetType, ...],
400
450
  ) -> None:
401
451
  super().__init__(args, resultset)
402
452
  self.sql = sql
@@ -405,7 +455,7 @@ class _SQLString(_SQLObject):
405
455
  return self.sql
406
456
 
407
457
 
408
- class _SQL:
458
+ class _SQL(Protocol):
409
459
  """
410
460
  Represents a SQL statement with associated type information.
411
461
  """
@@ -449,8 +499,8 @@ class _SQLImpl(_SQL):
449
499
  def _cast_fetch(self, rows: list[asyncpg.Record]) -> list[tuple[Any, ...]]:
450
500
  cast = self.sql.cast
451
501
  if cast:
452
- data_types = self.sql.resultset_data_types
453
- resultset = [tuple((data_types[i](value) if (value := row[i]) is not None and cast >> i & 1 else value) for i in range(len(row))) for row in rows]
502
+ converters = self.sql.converters
503
+ resultset = [tuple((converters[i](value) if (value := row[i]) is not None and cast >> i & 1 else value) for i in range(len(row))) for row in rows]
454
504
  else:
455
505
  resultset = [tuple(value for value in row) for row in rows]
456
506
  self.sql.check_rows(resultset)
@@ -473,8 +523,8 @@ class _SQLImpl(_SQL):
473
523
  return None
474
524
  cast = self.sql.cast
475
525
  if cast:
476
- data_types = self.sql.resultset_data_types
477
- resultset = tuple((data_types[i](value) if (value := row[i]) is not None and cast >> i & 1 else value) for i in range(len(row)))
526
+ converters = self.sql.converters
527
+ resultset = tuple((converters[i](value) if (value := row[i]) is not None and cast >> i & 1 else value) for i in range(len(row)))
478
528
  else:
479
529
  resultset = tuple(value for value in row)
480
530
  self.sql.check_row(resultset)
@@ -483,34 +533,29 @@ class _SQLImpl(_SQL):
483
533
  async def fetchval(self, connection: asyncpg.Connection, *args: Any) -> Any:
484
534
  stmt = await self._prepare(connection)
485
535
  value = await stmt.fetchval(*args)
486
- result = self.sql.resultset_data_types[0](value) if value is not None and self.sql.cast else value
536
+ result = self.sql.converters[0](value) if value is not None and self.sql.cast else value
487
537
  self.sql.check_value(result)
488
538
  return result
489
539
 
490
540
 
491
- ### START OF AUTO-GENERATED BLOCK ###
492
-
493
- PS = TypeVar("PS")
494
541
  P1 = TypeVar("P1")
495
- P2 = TypeVar("P2")
496
- P3 = TypeVar("P3")
497
- P4 = TypeVar("P4")
498
- P5 = TypeVar("P5")
499
- P6 = TypeVar("P6")
500
- P7 = TypeVar("P7")
501
- P8 = TypeVar("P8")
502
- RS = TypeVar("RS")
542
+ PX = TypeVarTuple("PX")
543
+
544
+ RT = TypeVar("RT")
503
545
  R1 = TypeVar("R1")
504
546
  R2 = TypeVar("R2")
505
547
  RX = TypeVarTuple("RX")
506
548
 
507
549
 
508
- class SQL_P0(_SQL):
550
+ ### START OF AUTO-GENERATED BLOCK ###
551
+
552
+
553
+ class SQL_P0(Protocol):
509
554
  @abstractmethod
510
555
  async def execute(self, connection: Connection) -> None: ...
511
556
 
512
557
 
513
- class SQL_P0_RS(Generic[R1], SQL_P0):
558
+ class SQL_R1_P0(SQL_P0, Protocol[R1]):
514
559
  @abstractmethod
515
560
  async def fetch(self, connection: Connection) -> list[tuple[R1]]: ...
516
561
  @abstractmethod
@@ -519,305 +564,64 @@ class SQL_P0_RS(Generic[R1], SQL_P0):
519
564
  async def fetchval(self, connection: Connection) -> R1: ...
520
565
 
521
566
 
522
- class SQL_P0_RX(Generic[R1, R2, Unpack[RX]], SQL_P0):
523
- @abstractmethod
524
- async def fetch(self, connection: Connection) -> list[tuple[R1, R2, Unpack[RX]]]: ...
525
- @abstractmethod
526
- async def fetchrow(self, connection: Connection) -> tuple[R1, R2, Unpack[RX]] | None: ...
527
-
528
-
529
- class SQL_P1(Generic[P1], _SQL):
530
- @abstractmethod
531
- async def execute(self, connection: Connection, arg1: P1) -> None: ...
532
- @abstractmethod
533
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1]]) -> None: ...
534
-
535
-
536
- class SQL_P1_RS(Generic[P1, R1], SQL_P1[P1]):
537
- @abstractmethod
538
- async def fetch(self, connection: Connection, arg1: P1) -> list[tuple[R1]]: ...
539
- @abstractmethod
540
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1]]) -> list[tuple[R1]]: ...
541
- @abstractmethod
542
- async def fetchrow(self, connection: Connection, arg1: P1) -> tuple[R1] | None: ...
543
- @abstractmethod
544
- async def fetchval(self, connection: Connection, arg1: P1) -> R1: ...
545
-
546
-
547
- class SQL_P1_RX(Generic[P1, R1, R2, Unpack[RX]], SQL_P1[P1]):
548
- @abstractmethod
549
- async def fetch(self, connection: Connection, arg1: P1) -> list[tuple[R1, R2, Unpack[RX]]]: ...
550
- @abstractmethod
551
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
552
- @abstractmethod
553
- async def fetchrow(self, connection: Connection, arg1: P1) -> tuple[R1, R2, Unpack[RX]] | None: ...
554
-
555
-
556
- class SQL_P2(Generic[P1, P2], _SQL):
557
- @abstractmethod
558
- async def execute(self, connection: Connection, arg1: P1, arg2: P2) -> None: ...
559
- @abstractmethod
560
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2]]) -> None: ...
561
-
562
-
563
- class SQL_P2_RS(Generic[P1, P2, R1], SQL_P2[P1, P2]):
564
- @abstractmethod
565
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2) -> list[tuple[R1]]: ...
566
- @abstractmethod
567
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2]]) -> list[tuple[R1]]: ...
568
- @abstractmethod
569
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2) -> tuple[R1] | None: ...
570
- @abstractmethod
571
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2) -> R1: ...
572
-
573
-
574
- class SQL_P2_RX(Generic[P1, P2, R1, R2, Unpack[RX]], SQL_P2[P1, P2]):
575
- @abstractmethod
576
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2) -> list[tuple[R1, R2, Unpack[RX]]]: ...
577
- @abstractmethod
578
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
579
- @abstractmethod
580
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2) -> tuple[R1, R2, Unpack[RX]] | None: ...
581
-
582
-
583
- class SQL_P3(Generic[P1, P2, P3], _SQL):
584
- @abstractmethod
585
- async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> None: ...
586
- @abstractmethod
587
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3]]) -> None: ...
588
-
589
-
590
- class SQL_P3_RS(Generic[P1, P2, P3, R1], SQL_P3[P1, P2, P3]):
591
- @abstractmethod
592
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> list[tuple[R1]]: ...
593
- @abstractmethod
594
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3]]) -> list[tuple[R1]]: ...
595
- @abstractmethod
596
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> tuple[R1] | None: ...
597
- @abstractmethod
598
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> R1: ...
599
-
600
-
601
- class SQL_P3_RX(Generic[P1, P2, P3, R1, R2, Unpack[RX]], SQL_P3[P1, P2, P3]):
602
- @abstractmethod
603
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> list[tuple[R1, R2, Unpack[RX]]]: ...
604
- @abstractmethod
605
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
606
- @abstractmethod
607
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> tuple[R1, R2, Unpack[RX]] | None: ...
608
-
609
-
610
- class SQL_P4(Generic[P1, P2, P3, P4], _SQL):
611
- @abstractmethod
612
- async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> None: ...
613
- @abstractmethod
614
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4]]) -> None: ...
615
-
616
-
617
- class SQL_P4_RS(Generic[P1, P2, P3, P4, R1], SQL_P4[P1, P2, P3, P4]):
618
- @abstractmethod
619
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> list[tuple[R1]]: ...
620
- @abstractmethod
621
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4]]) -> list[tuple[R1]]: ...
622
- @abstractmethod
623
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> tuple[R1] | None: ...
624
- @abstractmethod
625
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> R1: ...
626
-
627
-
628
- class SQL_P4_RX(Generic[P1, P2, P3, P4, R1, R2, Unpack[RX]], SQL_P4[P1, P2, P3, P4]):
629
- @abstractmethod
630
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> list[tuple[R1, R2, Unpack[RX]]]: ...
631
- @abstractmethod
632
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
633
- @abstractmethod
634
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> tuple[R1, R2, Unpack[RX]] | None: ...
635
-
636
-
637
- class SQL_P5(Generic[P1, P2, P3, P4, P5], _SQL):
638
- @abstractmethod
639
- async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> None: ...
640
- @abstractmethod
641
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5]]) -> None: ...
642
-
643
-
644
- class SQL_P5_RS(Generic[P1, P2, P3, P4, P5, R1], SQL_P5[P1, P2, P3, P4, P5]):
645
- @abstractmethod
646
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> list[tuple[R1]]: ...
647
- @abstractmethod
648
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5]]) -> list[tuple[R1]]: ...
649
- @abstractmethod
650
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> tuple[R1] | None: ...
651
- @abstractmethod
652
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> R1: ...
653
-
654
-
655
- class SQL_P5_RX(Generic[P1, P2, P3, P4, P5, R1, R2, Unpack[RX]], SQL_P5[P1, P2, P3, P4, P5]):
656
- @abstractmethod
657
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> list[tuple[R1, R2, Unpack[RX]]]: ...
658
- @abstractmethod
659
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
660
- @abstractmethod
661
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> tuple[R1, R2, Unpack[RX]] | None: ...
662
-
663
-
664
- class SQL_P6(Generic[P1, P2, P3, P4, P5, P6], _SQL):
665
- @abstractmethod
666
- async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> None: ...
667
- @abstractmethod
668
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6]]) -> None: ...
669
-
670
-
671
- class SQL_P6_RS(Generic[P1, P2, P3, P4, P5, P6, R1], SQL_P6[P1, P2, P3, P4, P5, P6]):
672
- @abstractmethod
673
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> list[tuple[R1]]: ...
674
- @abstractmethod
675
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6]]) -> list[tuple[R1]]: ...
676
- @abstractmethod
677
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> tuple[R1] | None: ...
678
- @abstractmethod
679
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> R1: ...
680
-
681
-
682
- class SQL_P6_RX(Generic[P1, P2, P3, P4, P5, P6, R1, R2, Unpack[RX]], SQL_P6[P1, P2, P3, P4, P5, P6]):
683
- @abstractmethod
684
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> list[tuple[R1, R2, Unpack[RX]]]: ...
567
+ class SQL_RX_P0(SQL_P0, Protocol[RT]):
685
568
  @abstractmethod
686
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
569
+ async def fetch(self, connection: Connection) -> list[RT]: ...
687
570
  @abstractmethod
688
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> tuple[R1, R2, Unpack[RX]] | None: ...
571
+ async def fetchrow(self, connection: Connection) -> RT | None: ...
689
572
 
690
573
 
691
- class SQL_P7(Generic[P1, P2, P3, P4, P5, P6, P7], _SQL):
574
+ class SQL_PX(Protocol[Unpack[PX]]):
692
575
  @abstractmethod
693
- async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> None: ...
576
+ async def execute(self, connection: Connection, *args: Unpack[PX]) -> None: ...
694
577
  @abstractmethod
695
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> None: ...
578
+ async def executemany(self, connection: Connection, args: Iterable[tuple[Unpack[PX]]]) -> None: ...
696
579
 
697
580
 
698
- class SQL_P7_RS(Generic[P1, P2, P3, P4, P5, P6, P7, R1], SQL_P7[P1, P2, P3, P4, P5, P6, P7]):
581
+ class SQL_R1_PX(SQL_PX[Unpack[PX]], Protocol[R1, Unpack[PX]]):
699
582
  @abstractmethod
700
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> list[tuple[R1]]: ...
583
+ async def fetch(self, connection: Connection, *args: Unpack[PX]) -> list[tuple[R1]]: ...
701
584
  @abstractmethod
702
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> list[tuple[R1]]: ...
585
+ async def fetchmany(self, connection: Connection, args: Iterable[tuple[Unpack[PX]]]) -> list[tuple[R1]]: ...
703
586
  @abstractmethod
704
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> tuple[R1] | None: ...
587
+ async def fetchrow(self, connection: Connection, *args: Unpack[PX]) -> tuple[R1] | None: ...
705
588
  @abstractmethod
706
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> R1: ...
589
+ async def fetchval(self, connection: Connection, *args: Unpack[PX]) -> R1: ...
707
590
 
708
591
 
709
- class SQL_P7_RX(Generic[P1, P2, P3, P4, P5, P6, P7, R1, R2, Unpack[RX]], SQL_P7[P1, P2, P3, P4, P5, P6, P7]):
592
+ class SQL_RX_PX(SQL_PX[Unpack[PX]], Protocol[RT, Unpack[PX]]):
710
593
  @abstractmethod
711
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> list[tuple[R1, R2, Unpack[RX]]]: ...
594
+ async def fetch(self, connection: Connection, *args: Unpack[PX]) -> list[RT]: ...
712
595
  @abstractmethod
713
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
596
+ async def fetchmany(self, connection: Connection, args: Iterable[tuple[Unpack[PX]]]) -> list[RT]: ...
714
597
  @abstractmethod
715
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> tuple[R1, R2, Unpack[RX]] | None: ...
716
-
717
-
718
- class SQL_P8(Generic[P1, P2, P3, P4, P5, P6, P7, P8], _SQL):
719
- @abstractmethod
720
- async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> None: ...
721
- @abstractmethod
722
- async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> None: ...
723
-
724
-
725
- class SQL_P8_RS(Generic[P1, P2, P3, P4, P5, P6, P7, P8, R1], SQL_P8[P1, P2, P3, P4, P5, P6, P7, P8]):
726
- @abstractmethod
727
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> list[tuple[R1]]: ...
728
- @abstractmethod
729
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> list[tuple[R1]]: ...
730
- @abstractmethod
731
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> tuple[R1] | None: ...
732
- @abstractmethod
733
- async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> R1: ...
734
-
735
-
736
- class SQL_P8_RX(Generic[P1, P2, P3, P4, P5, P6, P7, P8, R1, R2, Unpack[RX]], SQL_P8[P1, P2, P3, P4, P5, P6, P7, P8]):
737
- @abstractmethod
738
- async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> list[tuple[R1, R2, Unpack[RX]]]: ...
739
- @abstractmethod
740
- async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
741
- @abstractmethod
742
- async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> tuple[R1, R2, Unpack[RX]] | None: ...
598
+ async def fetchrow(self, connection: Connection, *args: Unpack[PX]) -> RT | None: ...
743
599
 
744
600
 
745
601
  @overload
746
602
  def sql(stmt: SQLExpression) -> SQL_P0: ...
747
603
  @overload
748
- def sql(stmt: SQLExpression, *, result: type[RS]) -> SQL_P0_RS[RS]: ...
749
- @overload
750
- def sql(stmt: SQLExpression, *, resultset: type[tuple[R1]]) -> SQL_P0_RS[R1]: ...
751
- @overload
752
- def sql(stmt: SQLExpression, *, resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P0_RX[R1, R2, Unpack[RX]]: ...
753
- @overload
754
- def sql(stmt: SQLExpression, *, arg: type[PS]) -> SQL_P1[PS]: ...
755
- @overload
756
- def sql(stmt: SQLExpression, *, args: type[tuple[P1]]) -> SQL_P1[P1]: ...
757
- @overload
758
- def sql(stmt: SQLExpression, *, arg: type[PS], result: type[RS]) -> SQL_P1_RS[PS, RS]: ...
759
- @overload
760
- def sql(stmt: SQLExpression, *, args: type[tuple[P1]], resultset: type[tuple[R1]]) -> SQL_P1_RS[P1, R1]: ...
761
- @overload
762
- def sql(stmt: SQLExpression, *, arg: type[PS], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P1_RX[PS, R1, R2, Unpack[RX]]: ...
763
- @overload
764
- def sql(stmt: SQLExpression, *, args: type[tuple[P1]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P1_RX[P1, R1, R2, Unpack[RX]]: ...
765
- @overload
766
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]]) -> SQL_P2[P1, P2]: ...
767
- @overload
768
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]], result: type[RS]) -> SQL_P2_RS[P1, P2, RS]: ...
769
- @overload
770
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]], resultset: type[tuple[R1]]) -> SQL_P2_RS[P1, P2, R1]: ...
771
- @overload
772
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P2_RX[P1, P2, R1, R2, Unpack[RX]]: ...
773
- @overload
774
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]]) -> SQL_P3[P1, P2, P3]: ...
775
- @overload
776
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]], result: type[RS]) -> SQL_P3_RS[P1, P2, P3, RS]: ...
777
- @overload
778
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]], resultset: type[tuple[R1]]) -> SQL_P3_RS[P1, P2, P3, R1]: ...
779
- @overload
780
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P3_RX[P1, P2, P3, R1, R2, Unpack[RX]]: ...
781
- @overload
782
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]]) -> SQL_P4[P1, P2, P3, P4]: ...
783
- @overload
784
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]], result: type[RS]) -> SQL_P4_RS[P1, P2, P3, P4, RS]: ...
785
- @overload
786
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]], resultset: type[tuple[R1]]) -> SQL_P4_RS[P1, P2, P3, P4, R1]: ...
787
- @overload
788
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P4_RX[P1, P2, P3, P4, R1, R2, Unpack[RX]]: ...
789
- @overload
790
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]]) -> SQL_P5[P1, P2, P3, P4, P5]: ...
791
- @overload
792
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]], result: type[RS]) -> SQL_P5_RS[P1, P2, P3, P4, P5, RS]: ...
793
- @overload
794
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]], resultset: type[tuple[R1]]) -> SQL_P5_RS[P1, P2, P3, P4, P5, R1]: ...
795
- @overload
796
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P5_RX[P1, P2, P3, P4, P5, R1, R2, Unpack[RX]]: ...
797
- @overload
798
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]]) -> SQL_P6[P1, P2, P3, P4, P5, P6]: ...
799
- @overload
800
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]], result: type[RS]) -> SQL_P6_RS[P1, P2, P3, P4, P5, P6, RS]: ...
604
+ def sql(stmt: SQLExpression, *, result: type[R1]) -> SQL_R1_P0[R1]: ...
801
605
  @overload
802
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]], resultset: type[tuple[R1]]) -> SQL_P6_RS[P1, P2, P3, P4, P5, P6, R1]: ...
606
+ def sql(stmt: SQLExpression, *, resultset: type[tuple[R1]]) -> SQL_R1_P0[R1]: ...
803
607
  @overload
804
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P6_RX[P1, P2, P3, P4, P5, P6, R1, R2, Unpack[RX]]: ...
608
+ def sql(stmt: SQLExpression, *, resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_RX_P0[tuple[R1, R2, Unpack[RX]]]: ...
805
609
  @overload
806
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> SQL_P7[P1, P2, P3, P4, P5, P6, P7]: ...
610
+ def sql(stmt: SQLExpression, *, arg: type[P1]) -> SQL_PX[P1]: ...
807
611
  @overload
808
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]], result: type[RS]) -> SQL_P7_RS[P1, P2, P3, P4, P5, P6, P7, RS]: ...
612
+ def sql(stmt: SQLExpression, *, arg: type[P1], result: type[R1]) -> SQL_R1_PX[R1, P1]: ...
809
613
  @overload
810
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]], resultset: type[tuple[R1]]) -> SQL_P7_RS[P1, P2, P3, P4, P5, P6, P7, R1]: ...
614
+ def sql(stmt: SQLExpression, *, arg: type[P1], resultset: type[tuple[R1]]) -> SQL_R1_PX[R1, P1]: ...
811
615
  @overload
812
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P7_RX[P1, P2, P3, P4, P5, P6, P7, R1, R2, Unpack[RX]]: ...
616
+ def sql(stmt: SQLExpression, *, arg: type[P1], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_RX_PX[tuple[R1, R2, Unpack[RX]], P1]: ...
813
617
  @overload
814
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> SQL_P8[P1, P2, P3, P4, P5, P6, P7, P8]: ...
618
+ def sql(stmt: SQLExpression, *, args: type[tuple[P1, Unpack[PX]]]) -> SQL_PX[P1, Unpack[PX]]: ...
815
619
  @overload
816
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]], result: type[RS]) -> SQL_P8_RS[P1, P2, P3, P4, P5, P6, P7, P8, RS]: ...
620
+ def sql(stmt: SQLExpression, *, args: type[tuple[P1, Unpack[PX]]], result: type[R1]) -> SQL_R1_PX[R1, P1, Unpack[PX]]: ...
817
621
  @overload
818
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]], resultset: type[tuple[R1]]) -> SQL_P8_RS[P1, P2, P3, P4, P5, P6, P7, P8, R1]: ...
622
+ def sql(stmt: SQLExpression, *, args: type[tuple[P1, Unpack[PX]]], resultset: type[tuple[R1]]) -> SQL_R1_PX[R1, P1, Unpack[PX]]: ...
819
623
  @overload
820
- def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P8_RX[P1, P2, P3, P4, P5, P6, P7, P8, R1, R2, Unpack[RX]]: ...
624
+ def sql(stmt: SQLExpression, *, args: type[tuple[P1, Unpack[PX]]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_RX_PX[tuple[R1, R2, Unpack[RX]], P1, Unpack[PX]]: ...
821
625
 
822
626
 
823
627
  ### END OF AUTO-GENERATED BLOCK ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asyncpg_typed
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Type-safe queries for asyncpg
5
5
  Author-email: Levente Hunyadi <hunyadi@gmail.com>
6
6
  Maintainer-email: Levente Hunyadi <hunyadi@gmail.com>
@@ -74,6 +74,25 @@ try:
74
74
 
75
75
  finally:
76
76
  await conn.close()
77
+
78
+ # create a list of data-class instances from a list of typed tuples
79
+ @dataclass
80
+ class DataObject:
81
+ boolean_value: bool
82
+ integer_value: int
83
+ string_value: str | None
84
+
85
+ # ✅ Valid initializer call
86
+ items = [DataObject(*row) for row in rows]
87
+
88
+ @dataclass
89
+ class MismatchedObject:
90
+ boolean_value: bool
91
+ integer_value: int
92
+ string_value: str
93
+
94
+ # ⚠️ Argument of type "int | None" cannot be assigned to parameter "integer_value" of type "int" in function "__init__"; "None" is not assignable to "int"
95
+ items = [MismatchedObject(*row) for row in rows]
77
96
  ```
78
97
 
79
98
 
@@ -137,7 +156,7 @@ The parameters `arg` and `result` take a single type `P` or `R`. Passing a simpl
137
156
 
138
157
  The number of types in `args` must correspond to the number of query parameters. (This is validated on calling `sql(...)` for the *t-string* syntax.) The number of types in `resultset` must correspond to the number of columns returned by the query.
139
158
 
140
- Both `args` and `resultset` types must be compatible with their corresponding PostgreSQL query parameter types and resultset column types, respectively. The following table shows the mapping between PostgreSQL and Python types.
159
+ Both `args` and `resultset` types must be compatible with their corresponding PostgreSQL query parameter types and resultset column types, respectively. The following table shows the mapping between PostgreSQL and Python types. When there are multiple options separated by a slash, either of the types can be specified as a source or target type.
141
160
 
142
161
  | PostgreSQL type | Python type |
143
162
  | ----------------- | ------------------ |
@@ -154,15 +173,19 @@ Both `args` and `resultset` types must be compatible with their corresponding Po
154
173
  | `timetz` | `time` (tz) |
155
174
  | `timestamp` | `datetime` (naive) |
156
175
  | `timestamptz` | `datetime` (tz) |
176
+ | `interval` | `timedelta` |
157
177
  | `char(N)` | `str` |
158
178
  | `varchar(N)` | `str` |
159
179
  | `text` | `str` |
160
180
  | `bytea` | `bytes` |
161
- | `json` | `str` |
162
- | `jsonb` | `str` |
181
+ | `json` | `str`/`JsonType` |
182
+ | `jsonb` | `str`/`JsonType` |
183
+ | `xml` | `str` |
163
184
  | `uuid` | `UUID` |
164
185
  | enumeration | `E: StrEnum` |
165
186
 
187
+ PostgreSQL types `json` and `jsonb` are [returned by asyncpg](https://magicstack.github.io/asyncpg/current/usage.html#type-conversion) as Python type `str`. However, if we specify the union type `JsonType` in `args` or `resultset`, the JSON string is parsed as if by calling `json.loads()`. (`JsonType` is defined in the module `asyncpg_typed`.) If the library `orjson` is present, its faster routines are invoked instead of the slower standard library implementation in the module `json`.
188
+
166
189
  ### Using a SQL object
167
190
 
168
191
  The function `sql` returns an object that derives from the base class `_SQL` and is specific to the number and types of parameters passed in `args` and `resultset`.
@@ -0,0 +1,8 @@
1
+ asyncpg_typed/__init__.py,sha256=Z9UqmIr2QcSpGe7qC-ddMDDkwnJSGg5mm1dqiWPKYQM,24915
2
+ asyncpg_typed/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ asyncpg_typed-0.1.2.dist-info/licenses/LICENSE,sha256=rx4jD36wX8TyLZaR2HEOJ6TphFPjKUqoCSSYWzwWNRk,1093
4
+ asyncpg_typed-0.1.2.dist-info/METADATA,sha256=9wNzfDUQWAOhedM3g3cx_TYYlaaDjlqTrNq1qEqcK0k,9932
5
+ asyncpg_typed-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ asyncpg_typed-0.1.2.dist-info/top_level.txt,sha256=T0X1nWnXRTi5a5oTErGy572ORDbM9UV9wfhRXWLsaoY,14
7
+ asyncpg_typed-0.1.2.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
8
+ asyncpg_typed-0.1.2.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- asyncpg_typed/__init__.py,sha256=2c4xyDjjR-yVrIlcgL-rqLJlWB7_JJR76eRxPDTqAPY,38154
2
- asyncpg_typed/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- asyncpg_typed-0.1.1.dist-info/licenses/LICENSE,sha256=rx4jD36wX8TyLZaR2HEOJ6TphFPjKUqoCSSYWzwWNRk,1093
4
- asyncpg_typed-0.1.1.dist-info/METADATA,sha256=6RqDzYtI9FnIbKAjHHQdhnOQhBfM3pK1IHUOYXNf9yU,8652
5
- asyncpg_typed-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- asyncpg_typed-0.1.1.dist-info/top_level.txt,sha256=T0X1nWnXRTi5a5oTErGy572ORDbM9UV9wfhRXWLsaoY,14
7
- asyncpg_typed-0.1.1.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
8
- asyncpg_typed-0.1.1.dist-info/RECORD,,