TypeDAL 3.2.0__py3-none-any.whl → 3.3.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 TypeDAL might be problematic. Click here for more details.

typedal/__about__.py CHANGED
@@ -5,4 +5,4 @@ This file contains the Version info for this package.
5
5
  # SPDX-FileCopyrightText: 2023-present Robin van der Noord <robinvandernoord@gmail.com>
6
6
  #
7
7
  # SPDX-License-Identifier: MIT
8
- __version__ = "3.2.0"
8
+ __version__ = "3.3.1"
typedal/caching.py CHANGED
@@ -15,7 +15,7 @@ from pydal.objects import Field, Rows, Set
15
15
  from .core import TypedField, TypedRows, TypedTable
16
16
  from .types import Query
17
17
 
18
- if typing.TYPE_CHECKING: # pragma: no cover
18
+ if typing.TYPE_CHECKING:
19
19
  from .core import TypeDAL
20
20
 
21
21
 
typedal/cli.py CHANGED
@@ -24,7 +24,7 @@ try:
24
24
  import tomlkit
25
25
  import typer
26
26
  from tabulate import tabulate
27
- except ImportError as e: # pragma: no cover
27
+ except ImportError as e:
28
28
  # ImportWarning is hidden by default
29
29
  warnings.warn(
30
30
  "`migrations` extra not installed. Please run `pip install typedal[migrations]` to fix this.",
typedal/config.py CHANGED
@@ -17,7 +17,7 @@ from dotenv import dotenv_values, find_dotenv
17
17
 
18
18
  from .types import AnyDict
19
19
 
20
- if typing.TYPE_CHECKING: # pragma: no cover
20
+ if typing.TYPE_CHECKING:
21
21
  from edwh_migrate import Config as MigrateConfig
22
22
  from pydal2sql.typer_support import Config as P2SConfig
23
23
 
typedal/core.py CHANGED
@@ -15,7 +15,7 @@ from collections import defaultdict
15
15
  from copy import copy
16
16
  from decimal import Decimal
17
17
  from pathlib import Path
18
- from typing import Any, Optional
18
+ from typing import Any, Optional, Type
19
19
 
20
20
  import pydal
21
21
  from pydal._globals import DEFAULT
@@ -23,7 +23,7 @@ from pydal.objects import Field as _Field
23
23
  from pydal.objects import Query as _Query
24
24
  from pydal.objects import Row
25
25
  from pydal.objects import Table as _Table
26
- from typing_extensions import Self
26
+ from typing_extensions import Self, Unpack
27
27
 
28
28
  from .config import TypeDALConfig, load_config
29
29
  from .helpers import (
@@ -58,13 +58,15 @@ from .types import (
58
58
  Pagination,
59
59
  Query,
60
60
  Rows,
61
+ SelectKwargs,
62
+ Table,
61
63
  Validator,
62
64
  _Types,
63
65
  )
64
66
 
65
67
  # use typing.cast(type, ...) to make mypy happy with unions
66
- T_annotation = typing.Type[Any] | types.UnionType
67
- T_Query = typing.Union["Table", Query, bool, None, "TypedTable", typing.Type["TypedTable"]]
68
+ T_annotation = Type[Any] | types.UnionType
69
+ T_Query = typing.Union["Table", Query, bool, None, "TypedTable", Type["TypedTable"]]
68
70
  T_Value = typing.TypeVar("T_Value") # actual type of the Field (via Generic)
69
71
  T_MetaInstance = typing.TypeVar("T_MetaInstance", bound="TypedTable") # bound="TypedTable"; bound="TableMeta"
70
72
  T = typing.TypeVar("T")
@@ -100,7 +102,7 @@ JOIN_OPTIONS = typing.Literal["left", "inner", None]
100
102
  DEFAULT_JOIN_OPTION: JOIN_OPTIONS = "left"
101
103
 
102
104
  # table-ish paramter:
103
- P_Table = typing.Union[typing.Type["TypedTable"], pydal.objects.Table]
105
+ P_Table = typing.Union[Type["TypedTable"], pydal.objects.Table]
104
106
 
105
107
  Condition: typing.TypeAlias = typing.Optional[
106
108
  typing.Callable[
@@ -118,7 +120,7 @@ OnQuery: typing.TypeAlias = typing.Optional[
118
120
  ]
119
121
  ]
120
122
 
121
- To_Type = typing.TypeVar("To_Type", type[Any], typing.Type[Any], str)
123
+ To_Type = typing.TypeVar("To_Type", type[Any], Type[Any], str)
122
124
 
123
125
 
124
126
  class Relationship(typing.Generic[To_Type]):
@@ -127,7 +129,7 @@ class Relationship(typing.Generic[To_Type]):
127
129
  """
128
130
 
129
131
  _type: To_Type
130
- table: typing.Type["TypedTable"] | type | str
132
+ table: Type["TypedTable"] | type | str
131
133
  condition: Condition
132
134
  on: OnQuery
133
135
  multiple: bool
@@ -186,7 +188,7 @@ class Relationship(typing.Generic[To_Type]):
186
188
  join = f":{self.join}" if self.join else ""
187
189
  return f"<Relationship{join} {src_code}>"
188
190
 
189
- def get_table(self, db: "TypeDAL") -> typing.Type["TypedTable"]:
191
+ def get_table(self, db: "TypeDAL") -> Type["TypedTable"]:
190
192
  """
191
193
  Get the table this relationship is bound to.
192
194
  """
@@ -197,7 +199,7 @@ class Relationship(typing.Generic[To_Type]):
197
199
  return mapped
198
200
 
199
201
  # boo, fall back to untyped table but pretend it is typed:
200
- return typing.cast(typing.Type["TypedTable"], db[table]) # eh close enough!
202
+ return typing.cast(Type["TypedTable"], db[table]) # eh close enough!
201
203
 
202
204
  return table
203
205
 
@@ -239,9 +241,7 @@ class Relationship(typing.Generic[To_Type]):
239
241
  return None
240
242
 
241
243
 
242
- def relationship(
243
- _type: To_Type, condition: Condition = None, join: JOIN_OPTIONS = None, on: OnQuery = None
244
- ) -> Relationship[To_Type]:
244
+ def relationship(_type: To_Type, condition: Condition = None, join: JOIN_OPTIONS = None, on: OnQuery = None) -> To_Type:
245
245
  """
246
246
  Define a relationship to another table, when its id is not stored in the current table.
247
247
 
@@ -271,11 +271,17 @@ def relationship(
271
271
 
272
272
  If you'd try to capture this in a single 'condition', pydal would create a cross join which is much less efficient.
273
273
  """
274
- return Relationship(_type, condition, join, on)
274
+ return typing.cast(
275
+ # note: The descriptor `Relationship[To_Type]` is more correct, but pycharm doesn't really get that.
276
+ # so for ease of use, just cast to the refered type for now!
277
+ # e.g. x = relationship(Author) -> x: Author
278
+ To_Type,
279
+ Relationship(_type, condition, join, on),
280
+ )
275
281
 
276
282
 
277
283
  def _generate_relationship_condition(
278
- _: typing.Type["TypedTable"], key: str, field: typing.Union["TypedField[Any]", "Table", typing.Type["TypedTable"]]
284
+ _: Type["TypedTable"], key: str, field: typing.Union["TypedField[Any]", "Table", Type["TypedTable"]]
279
285
  ) -> Condition:
280
286
  origin = typing.get_origin(field)
281
287
  # else: generic
@@ -292,9 +298,9 @@ def _generate_relationship_condition(
292
298
 
293
299
 
294
300
  def to_relationship(
295
- cls: typing.Type["TypedTable"] | type[Any],
301
+ cls: Type["TypedTable"] | type[Any],
296
302
  key: str,
297
- field: typing.Union["TypedField[Any]", "Table", typing.Type["TypedTable"]],
303
+ field: typing.Union["TypedField[Any]", "Table", Type["TypedTable"]],
298
304
  ) -> typing.Optional[Relationship[Any]]:
299
305
  """
300
306
  Used to automatically create relationship instance for reference fields.
@@ -425,7 +431,7 @@ class TypeDAL(pydal.DAL): # type: ignore
425
431
  self.try_define(_TypedalCache)
426
432
  self.try_define(_TypedalCacheDependency)
427
433
 
428
- def try_define(self, model: typing.Type[T], verbose: bool = False) -> typing.Type[T]:
434
+ def try_define(self, model: Type[T], verbose: bool = False) -> Type[T]:
429
435
  """
430
436
  Try to define a model with migrate or fall back to fake migrate.
431
437
  """
@@ -449,9 +455,9 @@ class TypeDAL(pydal.DAL): # type: ignore
449
455
  }
450
456
 
451
457
  # maps table name to typedal class, for resolving future references
452
- _class_map: typing.ClassVar[dict[str, typing.Type["TypedTable"]]] = {}
458
+ _class_map: typing.ClassVar[dict[str, Type["TypedTable"]]] = {}
453
459
 
454
- def _define(self, cls: typing.Type[T], **kwargs: Any) -> typing.Type[T]:
460
+ def _define(self, cls: Type[T], **kwargs: Any) -> Type[T]:
455
461
  # todo: new relationship item added should also invalidate (previously unrelated) cache result
456
462
 
457
463
  # todo: option to enable/disable cache dependency behavior:
@@ -553,7 +559,7 @@ class TypeDAL(pydal.DAL): # type: ignore
553
559
  return cls
554
560
 
555
561
  @typing.overload
556
- def define(self, maybe_cls: None = None, **kwargs: Any) -> typing.Callable[[typing.Type[T]], typing.Type[T]]:
562
+ def define(self, maybe_cls: None = None, **kwargs: Any) -> typing.Callable[[Type[T]], Type[T]]:
557
563
  """
558
564
  Typing Overload for define without a class.
559
565
 
@@ -562,7 +568,7 @@ class TypeDAL(pydal.DAL): # type: ignore
562
568
  """
563
569
 
564
570
  @typing.overload
565
- def define(self, maybe_cls: typing.Type[T], **kwargs: Any) -> typing.Type[T]:
571
+ def define(self, maybe_cls: Type[T], **kwargs: Any) -> Type[T]:
566
572
  """
567
573
  Typing Overload for define with a class.
568
574
 
@@ -570,9 +576,7 @@ class TypeDAL(pydal.DAL): # type: ignore
570
576
  class MyTable(TypedTable): ...
571
577
  """
572
578
 
573
- def define(
574
- self, maybe_cls: typing.Type[T] | None = None, **kwargs: Any
575
- ) -> typing.Type[T] | typing.Callable[[typing.Type[T]], typing.Type[T]]:
579
+ def define(self, maybe_cls: Type[T] | None = None, **kwargs: Any) -> Type[T] | typing.Callable[[Type[T]], Type[T]]:
576
580
  """
577
581
  Can be used as a decorator on a class that inherits `TypedTable`, \
578
582
  or as a regular method if you need to define your classes before you have access to a 'db' instance.
@@ -595,7 +599,7 @@ class TypeDAL(pydal.DAL): # type: ignore
595
599
  the result of pydal.define_table
596
600
  """
597
601
 
598
- def wrapper(cls: typing.Type[T]) -> typing.Type[T]:
602
+ def wrapper(cls: Type[T]) -> Type[T]:
599
603
  return self._define(cls, **kwargs)
600
604
 
601
605
  if maybe_cls:
@@ -645,7 +649,7 @@ class TypeDAL(pydal.DAL): # type: ignore
645
649
 
646
650
  if isinstance(cls, type) and issubclass(type(cls), type) and issubclass(cls, TypedTable):
647
651
  # table defined without @db.define decorator!
648
- _cls: typing.Type[TypedTable] = cls
652
+ _cls: Type[TypedTable] = cls
649
653
  args[0] = _cls.id != None
650
654
 
651
655
  _set = super().__call__(*args, **kwargs)
@@ -669,11 +673,11 @@ class TypeDAL(pydal.DAL): # type: ignore
669
673
  cls, _ftype: T_annotation, mut_kw: typing.MutableMapping[str, Any]
670
674
  ) -> Optional[str]:
671
675
  # ftype can be a union or type. typing.cast is sometimes used to tell mypy when it's not a union.
672
- ftype = typing.cast(type, _ftype) # cast from typing.Type to type to make mypy happy)
676
+ ftype = typing.cast(type, _ftype) # cast from Type to type to make mypy happy)
673
677
 
674
678
  if isinstance(ftype, str):
675
679
  # extract type from string
676
- ftype = typing.get_args(typing.Type[ftype])[0]._evaluate(
680
+ ftype = typing.get_args(Type[ftype])[0]._evaluate(
677
681
  localns=locals(), globalns=globals(), recursive_guard=frozenset()
678
682
  )
679
683
 
@@ -753,25 +757,6 @@ class TypeDAL(pydal.DAL): # type: ignore
753
757
  return to_snake(camel)
754
758
 
755
759
 
756
- class TableProtocol(typing.Protocol): # pragma: no cover
757
- """
758
- Make mypy happy.
759
- """
760
-
761
- id: "TypedField[int]"
762
-
763
- def __getitem__(self, item: str) -> Field:
764
- """
765
- Tell mypy a Table supports dictionary notation for columns.
766
- """
767
-
768
-
769
- class Table(_Table, TableProtocol): # type: ignore
770
- """
771
- Make mypy happy.
772
- """
773
-
774
-
775
760
  class TableMeta(type):
776
761
  """
777
762
  This metaclass contains functionality on table classes, that doesn't exist on its instances.
@@ -852,13 +837,13 @@ class TableMeta(type):
852
837
  else:
853
838
  return f"<unbound table {self.__name__}>"
854
839
 
855
- def from_row(self: typing.Type[T_MetaInstance], row: pydal.objects.Row) -> T_MetaInstance:
840
+ def from_row(self: Type[T_MetaInstance], row: pydal.objects.Row) -> T_MetaInstance:
856
841
  """
857
842
  Create a model instance from a pydal row.
858
843
  """
859
844
  return self(row)
860
845
 
861
- def all(self: typing.Type[T_MetaInstance]) -> "TypedRows[T_MetaInstance]":
846
+ def all(self: Type[T_MetaInstance]) -> "TypedRows[T_MetaInstance]":
862
847
  """
863
848
  Return all rows for this model.
864
849
  """
@@ -874,7 +859,7 @@ class TableMeta(type):
874
859
  # TypeDAL Modified Logic #
875
860
  ##########################
876
861
 
877
- def insert(self: typing.Type[T_MetaInstance], **fields: Any) -> T_MetaInstance:
862
+ def insert(self: Type[T_MetaInstance], **fields: Any) -> T_MetaInstance:
878
863
  """
879
864
  This is only called when db.define is not used as a decorator.
880
865
 
@@ -897,7 +882,7 @@ class TableMeta(type):
897
882
 
898
883
  return str(table._insert(**fields))
899
884
 
900
- def bulk_insert(self: typing.Type[T_MetaInstance], items: list[AnyDict]) -> "TypedRows[T_MetaInstance]":
885
+ def bulk_insert(self: Type[T_MetaInstance], items: list[AnyDict]) -> "TypedRows[T_MetaInstance]":
901
886
  """
902
887
  Insert multiple rows, returns a TypedRows set of new instances.
903
888
  """
@@ -906,7 +891,7 @@ class TableMeta(type):
906
891
  return self.where(lambda row: row.id.belongs(result)).collect()
907
892
 
908
893
  def update_or_insert(
909
- self: typing.Type[T_MetaInstance], query: T_Query | AnyDict = DEFAULT, **values: Any
894
+ self: Type[T_MetaInstance], query: T_Query | AnyDict = DEFAULT, **values: Any
910
895
  ) -> T_MetaInstance:
911
896
  """
912
897
  Update a row if query matches, else insert a new one.
@@ -929,7 +914,7 @@ class TableMeta(type):
929
914
  return self(record)
930
915
 
931
916
  def validate_and_insert(
932
- self: typing.Type[T_MetaInstance], **fields: Any
917
+ self: Type[T_MetaInstance], **fields: Any
933
918
  ) -> tuple[Optional[T_MetaInstance], Optional[dict[str, str]]]:
934
919
  """
935
920
  Validate input data and then insert a row.
@@ -944,7 +929,7 @@ class TableMeta(type):
944
929
  return None, result.get("errors")
945
930
 
946
931
  def validate_and_update(
947
- self: typing.Type[T_MetaInstance], query: Query, **fields: Any
932
+ self: Type[T_MetaInstance], query: Query, **fields: Any
948
933
  ) -> tuple[Optional[T_MetaInstance], Optional[dict[str, str]]]:
949
934
  """
950
935
  Validate input data and then update max 1 row.
@@ -953,10 +938,7 @@ class TableMeta(type):
953
938
  """
954
939
  table = self._ensure_table_defined()
955
940
 
956
- try:
957
- result = table.validate_and_update(query, **fields)
958
- except Exception as e:
959
- result = {"errors": {"exception": str(e)}}
941
+ result = table.validate_and_update(query, **fields)
960
942
 
961
943
  if errors := result.get("errors"):
962
944
  return None, errors
@@ -967,7 +949,7 @@ class TableMeta(type):
967
949
  return None, None
968
950
 
969
951
  def validate_and_update_or_insert(
970
- self: typing.Type[T_MetaInstance], query: Query, **fields: Any
952
+ self: Type[T_MetaInstance], query: Query, **fields: Any
971
953
  ) -> tuple[Optional[T_MetaInstance], Optional[dict[str, str]]]:
972
954
  """
973
955
  Validate input data and then update_and_insert (on max 1 row).
@@ -985,53 +967,57 @@ class TableMeta(type):
985
967
  # update on query without result (shouldnt happen)
986
968
  return None, None
987
969
 
988
- def select(self: typing.Type[T_MetaInstance], *a: Any, **kw: Any) -> "QueryBuilder[T_MetaInstance]":
970
+ def select(self: Type[T_MetaInstance], *a: Any, **kw: Any) -> "QueryBuilder[T_MetaInstance]":
989
971
  """
990
972
  See QueryBuilder.select!
991
973
  """
992
974
  return QueryBuilder(self).select(*a, **kw)
993
975
 
994
- def paginate(self: typing.Type[T_MetaInstance], limit: int, page: int = 1) -> "PaginatedRows[T_MetaInstance]":
976
+ def paginate(self: Type[T_MetaInstance], limit: int, page: int = 1) -> "PaginatedRows[T_MetaInstance]":
995
977
  """
996
978
  See QueryBuilder.paginate!
997
979
  """
998
980
  return QueryBuilder(self).paginate(limit=limit, page=page)
999
981
 
1000
- def chunk(
1001
- self: typing.Type[T_MetaInstance], chunk_size: int
1002
- ) -> typing.Generator["TypedRows[T_MetaInstance]", Any, None]:
982
+ def chunk(self: Type[T_MetaInstance], chunk_size: int) -> typing.Generator["TypedRows[T_MetaInstance]", Any, None]:
1003
983
  """
1004
984
  See QueryBuilder.chunk!
1005
985
  """
1006
986
  return QueryBuilder(self).chunk(chunk_size)
1007
987
 
1008
- def where(self: typing.Type[T_MetaInstance], *a: Any, **kw: Any) -> "QueryBuilder[T_MetaInstance]":
988
+ def where(self: Type[T_MetaInstance], *a: Any, **kw: Any) -> "QueryBuilder[T_MetaInstance]":
1009
989
  """
1010
990
  See QueryBuilder.where!
1011
991
  """
1012
992
  return QueryBuilder(self).where(*a, **kw)
1013
993
 
1014
- def cache(self: typing.Type[T_MetaInstance], *deps: Any, **kwargs: Any) -> "QueryBuilder[T_MetaInstance]":
994
+ def cache(self: Type[T_MetaInstance], *deps: Any, **kwargs: Any) -> "QueryBuilder[T_MetaInstance]":
1015
995
  """
1016
996
  See QueryBuilder.cache!
1017
997
  """
1018
998
  return QueryBuilder(self).cache(*deps, **kwargs)
1019
999
 
1020
- def count(self: typing.Type[T_MetaInstance]) -> int:
1000
+ def count(self: Type[T_MetaInstance]) -> int:
1021
1001
  """
1022
1002
  See QueryBuilder.count!
1023
1003
  """
1024
1004
  return QueryBuilder(self).count()
1025
1005
 
1026
- def first(self: typing.Type[T_MetaInstance]) -> T_MetaInstance | None:
1006
+ def first(self: Type[T_MetaInstance]) -> T_MetaInstance | None:
1027
1007
  """
1028
1008
  See QueryBuilder.first!
1029
1009
  """
1030
1010
  return QueryBuilder(self).first()
1031
1011
 
1012
+ def first_or_fail(self: Type[T_MetaInstance]) -> T_MetaInstance:
1013
+ """
1014
+ See QueryBuilder.first_or_fail!
1015
+ """
1016
+ return QueryBuilder(self).first_or_fail()
1017
+
1032
1018
  def join(
1033
- self: typing.Type[T_MetaInstance],
1034
- *fields: str | typing.Type["TypedTable"],
1019
+ self: Type[T_MetaInstance],
1020
+ *fields: str | Type["TypedTable"],
1035
1021
  method: JOIN_OPTIONS = None,
1036
1022
  on: OnQuery | list[Expression] | Expression = None,
1037
1023
  condition: Condition = None,
@@ -1041,7 +1027,7 @@ class TableMeta(type):
1041
1027
  """
1042
1028
  return QueryBuilder(self).join(*fields, on=on, condition=condition, method=method)
1043
1029
 
1044
- def collect(self: typing.Type[T_MetaInstance], verbose: bool = False) -> "TypedRows[T_MetaInstance]":
1030
+ def collect(self: Type[T_MetaInstance], verbose: bool = False) -> "TypedRows[T_MetaInstance]":
1045
1031
  """
1046
1032
  See QueryBuilder.collect!
1047
1033
  """
@@ -1141,7 +1127,7 @@ class TableMeta(type):
1141
1127
  table = self._ensure_table_defined()
1142
1128
  return typing.cast(Expression, table.on(query))
1143
1129
 
1144
- def with_alias(self, alias: str) -> _Table:
1130
+ def with_alias(self: Type[T_MetaInstance], alias: str) -> Type[T_MetaInstance]:
1145
1131
  """
1146
1132
  Shadow Table.with_alias.
1147
1133
 
@@ -1151,12 +1137,12 @@ class TableMeta(type):
1151
1137
  http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer?search=export_to_csv_file#One-to-many-relation
1152
1138
  """
1153
1139
  table = self._ensure_table_defined()
1154
- return table.with_alias(alias)
1140
+ return typing.cast(Type[T_MetaInstance], table.with_alias(alias))
1155
1141
 
1156
1142
  # @typing.dataclass_transform()
1157
1143
 
1158
1144
 
1159
- class TypedField(typing.Generic[T_Value]): # pragma: no cover
1145
+ class TypedField(Expression, typing.Generic[T_Value]): # pragma: no cover
1160
1146
  """
1161
1147
  Typed version of pydal.Field, which will be converted to a normal Field in the background.
1162
1148
  """
@@ -1173,28 +1159,28 @@ class TypedField(typing.Generic[T_Value]): # pragma: no cover
1173
1159
 
1174
1160
  requires: Validator | typing.Iterable[Validator]
1175
1161
 
1176
- def __init__(self, _type: typing.Type[T_Value] | types.UnionType = str, /, **settings: Any) -> None: # type: ignore
1162
+ def __init__(self, _type: Type[T_Value] | types.UnionType = str, /, **settings: Any) -> None: # type: ignore
1177
1163
  """
1178
1164
  A TypedFieldType should not be inited manually, but TypedField (from `fields.py`) should be used!
1179
1165
  """
1180
1166
  self._type = _type
1181
1167
  self.kwargs = settings
1182
- super().__init__()
1168
+ # super().__init__()
1183
1169
 
1184
1170
  @typing.overload
1185
- def __get__(self, instance: T_MetaInstance, owner: typing.Type[T_MetaInstance]) -> T_Value: # pragma: no cover
1171
+ def __get__(self, instance: T_MetaInstance, owner: Type[T_MetaInstance]) -> T_Value: # pragma: no cover
1186
1172
  """
1187
1173
  row.field -> (actual data).
1188
1174
  """
1189
1175
 
1190
1176
  @typing.overload
1191
- def __get__(self, instance: None, owner: "typing.Type[TypedTable]") -> "TypedField[T_Value]": # pragma: no cover
1177
+ def __get__(self, instance: None, owner: "Type[TypedTable]") -> "TypedField[T_Value]": # pragma: no cover
1192
1178
  """
1193
1179
  Table.field -> Field.
1194
1180
  """
1195
1181
 
1196
1182
  def __get__(
1197
- self, instance: T_MetaInstance | None, owner: typing.Type[T_MetaInstance]
1183
+ self, instance: T_MetaInstance | None, owner: Type[T_MetaInstance]
1198
1184
  ) -> typing.Union[T_Value, "TypedField[T_Value]"]:
1199
1185
  """
1200
1186
  Since this class is a Descriptor field, \
@@ -1323,6 +1309,17 @@ class TypedField(typing.Generic[T_Value]): # pragma: no cover
1323
1309
 
1324
1310
  return typing.cast(Expression, ~self._field)
1325
1311
 
1312
+ def lower(self) -> Expression:
1313
+ """
1314
+ For string-fields: compare lowercased values.
1315
+ """
1316
+ if not self._field: # pragma: no cover
1317
+ raise ValueError("Unbound Field can not be lowered!")
1318
+
1319
+ return typing.cast(Expression, self._field.lower())
1320
+
1321
+ # ... etc
1322
+
1326
1323
 
1327
1324
  class _TypedTable:
1328
1325
  """
@@ -1377,7 +1374,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1377
1374
 
1378
1375
  def __new__(
1379
1376
  cls, row_or_id: typing.Union[Row, Query, pydal.objects.Set, int, str, None, "TypedTable"] = None, **filters: Any
1380
- ) -> "TypedTable":
1377
+ ) -> Self:
1381
1378
  """
1382
1379
  Create a Typed Rows model instance from an existing row, ID or query.
1383
1380
 
@@ -1391,7 +1388,8 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1391
1388
 
1392
1389
  if isinstance(row_or_id, TypedTable):
1393
1390
  # existing typed table instance!
1394
- return row_or_id
1391
+ return typing.cast(Self, row_or_id)
1392
+
1395
1393
  elif isinstance(row_or_id, pydal.objects.Row):
1396
1394
  row = row_or_id
1397
1395
  elif row_or_id is not None:
@@ -1598,7 +1596,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1598
1596
  super().__setattr__(key, value)
1599
1597
 
1600
1598
  @classmethod
1601
- def update(cls: typing.Type[T_MetaInstance], query: Query, **fields: Any) -> T_MetaInstance | None:
1599
+ def update(cls: Type[T_MetaInstance], query: Query, **fields: Any) -> T_MetaInstance | None:
1602
1600
  """
1603
1601
  Update one record.
1604
1602
 
@@ -1697,7 +1695,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
1697
1695
 
1698
1696
  records: dict[int, T_MetaInstance]
1699
1697
  # _rows: Rows
1700
- model: typing.Type[T_MetaInstance]
1698
+ model: Type[T_MetaInstance]
1701
1699
  metadata: Metadata
1702
1700
 
1703
1701
  # pseudo-properties: actually stored in _rows
@@ -1710,7 +1708,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
1710
1708
  def __init__(
1711
1709
  self,
1712
1710
  rows: Rows,
1713
- model: typing.Type[T_MetaInstance],
1711
+ model: Type[T_MetaInstance],
1714
1712
  records: dict[int, T_MetaInstance] = None,
1715
1713
  metadata: Metadata = None,
1716
1714
  ) -> None:
@@ -1940,7 +1938,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
1940
1938
  Update the current rows in the database with new_values.
1941
1939
  """
1942
1940
  # cast to make mypy understand .id is a TypedField and not an int!
1943
- table = typing.cast(typing.Type[TypedTable], self.model._ensure_table_defined())
1941
+ table = typing.cast(Type[TypedTable], self.model._ensure_table_defined())
1944
1942
 
1945
1943
  ids = set(self.column("id"))
1946
1944
  query = table.id.belongs(ids)
@@ -1951,7 +1949,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
1951
1949
  Delete the currently selected rows from the database.
1952
1950
  """
1953
1951
  # cast to make mypy understand .id is a TypedField and not an int!
1954
- table = typing.cast(typing.Type[TypedTable], self.model._ensure_table_defined())
1952
+ table = typing.cast(Type[TypedTable], self.model._ensure_table_defined())
1955
1953
 
1956
1954
  ids = set(self.column("id"))
1957
1955
  query = table.id.belongs(ids)
@@ -2006,7 +2004,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
2006
2004
 
2007
2005
  @classmethod
2008
2006
  def from_rows(
2009
- cls, rows: Rows, model: typing.Type[T_MetaInstance], metadata: Metadata = None
2007
+ cls, rows: Rows, model: Type[T_MetaInstance], metadata: Metadata = None
2010
2008
  ) -> "TypedRows[T_MetaInstance]":
2011
2009
  """
2012
2010
  Internal method to convert a Rows object to a TypedRows.
@@ -2049,19 +2047,19 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2049
2047
  Abstration on top of pydal's query system.
2050
2048
  """
2051
2049
 
2052
- model: typing.Type[T_MetaInstance]
2050
+ model: Type[T_MetaInstance]
2053
2051
  query: Query
2054
2052
  select_args: list[Any]
2055
- select_kwargs: AnyDict
2053
+ select_kwargs: SelectKwargs
2056
2054
  relationships: dict[str, Relationship[Any]]
2057
2055
  metadata: Metadata
2058
2056
 
2059
2057
  def __init__(
2060
2058
  self,
2061
- model: typing.Type[T_MetaInstance],
2059
+ model: Type[T_MetaInstance],
2062
2060
  add_query: Optional[Query] = None,
2063
2061
  select_args: Optional[list[Any]] = None,
2064
- select_kwargs: Optional[AnyDict] = None,
2062
+ select_kwargs: Optional[SelectKwargs] = None,
2065
2063
  relationships: dict[str, Relationship[Any]] = None,
2066
2064
  metadata: Metadata = None,
2067
2065
  ):
@@ -2111,7 +2109,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2111
2109
  add_query: Optional[Query] = None,
2112
2110
  overwrite_query: Optional[Query] = None,
2113
2111
  select_args: Optional[list[Any]] = None,
2114
- select_kwargs: Optional[AnyDict] = None,
2112
+ select_kwargs: Optional[SelectKwargs] = None,
2115
2113
  relationships: dict[str, Relationship[Any]] = None,
2116
2114
  metadata: Metadata = None,
2117
2115
  ) -> "QueryBuilder[T_MetaInstance]":
@@ -2124,7 +2122,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2124
2122
  (self.metadata | (metadata or {})) if metadata else self.metadata,
2125
2123
  )
2126
2124
 
2127
- def select(self, *fields: Any, **options: Any) -> "QueryBuilder[T_MetaInstance]":
2125
+ def select(self, *fields: Any, **options: Unpack[SelectKwargs]) -> "QueryBuilder[T_MetaInstance]":
2128
2126
  """
2129
2127
  Fields: database columns by name ('id'), by field reference (table.id) or other (e.g. table.ALL).
2130
2128
 
@@ -2153,7 +2151,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2153
2151
 
2154
2152
  def where(
2155
2153
  self,
2156
- *queries_or_lambdas: Query | typing.Callable[[typing.Type[T_MetaInstance]], Query],
2154
+ *queries_or_lambdas: Query | typing.Callable[[Type[T_MetaInstance]], Query],
2157
2155
  **filters: Any,
2158
2156
  ) -> "QueryBuilder[T_MetaInstance]":
2159
2157
  """
@@ -2194,7 +2192,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2194
2192
 
2195
2193
  def join(
2196
2194
  self,
2197
- *fields: str | typing.Type[TypedTable],
2195
+ *fields: str | Type[TypedTable],
2198
2196
  method: JOIN_OPTIONS = None,
2199
2197
  on: OnQuery | list[Expression] | Expression = None,
2200
2198
  condition: Condition = None,
@@ -2221,7 +2219,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2221
2219
  if isinstance(condition, pydal.objects.Query):
2222
2220
  condition = as_lambda(condition)
2223
2221
 
2224
- relationships = {str(fields[0]): relationship(fields[0], condition=condition, join=method)}
2222
+ relationships = {str(fields[0]): Relationship(fields[0], condition=condition, join=method)}
2225
2223
  elif on:
2226
2224
  if len(fields) != 1:
2227
2225
  raise ValueError("join(field, on=...) can only be used with exactly one field!")
@@ -2231,7 +2229,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2231
2229
 
2232
2230
  if isinstance(on, list):
2233
2231
  on = as_lambda(on)
2234
- relationships = {str(fields[0]): relationship(fields[0], on=on, join=method)}
2232
+ relationships = {str(fields[0]): Relationship(fields[0], on=on, join=method)}
2235
2233
 
2236
2234
  else:
2237
2235
  if fields:
@@ -2313,7 +2311,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2313
2311
  db = self._get_db()
2314
2312
  return str(db(self.query)._update(**fields))
2315
2313
 
2316
- def _before_query(self, mut_metadata: Metadata, add_id: bool = True) -> tuple[Query, list[Any], AnyDict]:
2314
+ def _before_query(self, mut_metadata: Metadata, add_id: bool = True) -> tuple[Query, list[Any], SelectKwargs]:
2317
2315
  select_args = [self._select_arg_convert(_) for _ in self.select_args] or [self.model.ALL]
2318
2316
  select_kwargs = self.select_kwargs.copy()
2319
2317
  query = self.query
@@ -2385,7 +2383,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2385
2383
  return db(query).select(*select_args, **select_kwargs)
2386
2384
 
2387
2385
  def collect(
2388
- self, verbose: bool = False, _to: typing.Type["TypedRows[Any]"] = None, add_id: bool = True
2386
+ self, verbose: bool = False, _to: Type["TypedRows[Any]"] = None, add_id: bool = True
2389
2387
  ) -> "TypedRows[T_MetaInstance]":
2390
2388
  """
2391
2389
  Execute the built query and turn it into model instances, while handling relationships.
@@ -2432,7 +2430,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2432
2430
  self,
2433
2431
  query: Query,
2434
2432
  select_args: list[Any],
2435
- select_kwargs: AnyDict,
2433
+ select_kwargs: SelectKwargs,
2436
2434
  metadata: Metadata,
2437
2435
  ) -> tuple[Query, list[Any]]:
2438
2436
  db = self._get_db()
@@ -2450,13 +2448,16 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2450
2448
  other = other.with_alias(f"{key}_{hash(relation)}")
2451
2449
  join.append(other.on(relation.condition(model, other)))
2452
2450
 
2453
- if limitby := select_kwargs.pop("limitby", None):
2451
+ if limitby := select_kwargs.pop("limitby", ()):
2452
+
2454
2453
  # if limitby + relationships:
2455
2454
  # 1. get IDs of main table entries that match 'query'
2456
2455
  # 2. change query to .belongs(id)
2457
2456
  # 3. add joins etc
2458
2457
 
2459
- kwargs = {"limitby": limitby}
2458
+ kwargs: SelectKwargs = select_kwargs | {"limitby": limitby}
2459
+ # if orderby := select_kwargs.get("orderby"):
2460
+ # kwargs["orderby"] = orderby
2460
2461
 
2461
2462
  if join:
2462
2463
  kwargs["join"] = join
@@ -2520,7 +2521,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2520
2521
  return query, select_args
2521
2522
 
2522
2523
  def _collect_with_relationships(
2523
- self, rows: Rows, metadata: Metadata, _to: typing.Type["TypedRows[Any]"]
2524
+ self, rows: Rows, metadata: Metadata, _to: Type["TypedRows[Any]"]
2524
2525
  ) -> "TypedRows[T_MetaInstance]":
2525
2526
  """
2526
2527
  Transform the raw rows into Typed Table model instances.
@@ -2801,7 +2802,7 @@ class TypedSet(pydal.objects.Set): # type: ignore # pragma: no cover
2801
2802
  result: TypedRows[MyTable] = db(MyTable.id > 0).select()
2802
2803
 
2803
2804
  for row in result:
2804
- typing.reveal_type(row) # MyTable
2805
+ reveal_type(row) # MyTable
2805
2806
  """
2806
2807
  rows = super().select(*fields, **attributes)
2807
2808
  return typing.cast(TypedRows[T_MetaInstance], rows)
typedal/helpers.py CHANGED
@@ -2,6 +2,7 @@
2
2
  Helpers that work independently of core.
3
3
  """
4
4
 
5
+ import datetime as dt
5
6
  import fnmatch
6
7
  import io
7
8
  import types
@@ -265,3 +266,11 @@ def match_strings(patterns: list[str] | str, string_list: list[str]) -> list[str
265
266
  matches.extend([s for s in string_list if fnmatch.fnmatch(s, pattern)])
266
267
 
267
268
  return matches
269
+
270
+
271
+ def utcnow() -> dt.datetime:
272
+ """
273
+ Replacement of datetime.utcnow.
274
+ """
275
+ # return dt.datetime.now(dt.UTC)
276
+ return dt.datetime.now(dt.timezone.utc)
typedal/types.py CHANGED
@@ -6,6 +6,7 @@ import typing
6
6
  from datetime import datetime
7
7
  from typing import Any, Optional, TypedDict
8
8
 
9
+ from pydal.adapters.base import BaseAdapter
9
10
  from pydal.helpers.classes import OpRow as _OpRow
10
11
  from pydal.helpers.classes import Reference as _Reference
11
12
  from pydal.objects import Expression as _Expression
@@ -13,9 +14,13 @@ from pydal.objects import Field as _Field
13
14
  from pydal.objects import Query as _Query
14
15
  from pydal.objects import Rows as _Rows
15
16
  from pydal.objects import Set as _Set
17
+ from pydal.objects import Table as _Table
16
18
  from pydal.validators import Validator as _Validator
17
19
  from typing_extensions import NotRequired
18
20
 
21
+ if typing.TYPE_CHECKING:
22
+ from .core import TypedField
23
+
19
24
  AnyDict: typing.TypeAlias = dict[str, Any]
20
25
 
21
26
 
@@ -148,6 +153,62 @@ class PaginationMetadata(TypedDict):
148
153
  min_max: tuple[int, int]
149
154
 
150
155
 
156
+ class TableProtocol(typing.Protocol): # pragma: no cover
157
+ """
158
+ Make mypy happy.
159
+ """
160
+
161
+ id: "TypedField[int]"
162
+
163
+ def __getitem__(self, item: str) -> Field:
164
+ """
165
+ Tell mypy a Table supports dictionary notation for columns.
166
+ """
167
+
168
+
169
+ class Table(_Table, TableProtocol): # type: ignore
170
+ """
171
+ Make mypy happy.
172
+ """
173
+
174
+
175
+ class CacheFn(typing.Protocol):
176
+ """
177
+ The cache model (e.g. cache.ram) accepts these parameters (all filled by dfeault).
178
+ """
179
+
180
+ def __call__(
181
+ self: BaseAdapter,
182
+ sql: str = "",
183
+ fields: typing.Iterable[str] = (),
184
+ attributes: typing.Iterable[str] = (),
185
+ colnames: typing.Iterable[str] = (),
186
+ ) -> Rows:
187
+ """
188
+ Only used for type-hinting.
189
+ """
190
+
191
+
192
+ # CacheFn = typing.Callable[[], Rows]
193
+ CacheModel = typing.Callable[[str, CacheFn, int], Rows]
194
+ CacheTuple = tuple[CacheModel, int]
195
+
196
+
197
+ class SelectKwargs(typing.TypedDict, total=False):
198
+ """
199
+ Possible keyword arguments for .select().
200
+ """
201
+
202
+ join: Optional[list[Expression]]
203
+ left: Optional[list[Expression]]
204
+ orderby: Optional[Expression | str | Table]
205
+ limitby: Optional[tuple[int, int]]
206
+ distinct: bool | Field | Expression
207
+ orderby_on_limitby: bool
208
+ cacheable: bool
209
+ cache: CacheTuple
210
+
211
+
151
212
  class Metadata(TypedDict):
152
213
  """
153
214
  Loosely structured metadata used by Query Builder.
@@ -161,7 +222,7 @@ class Metadata(TypedDict):
161
222
 
162
223
  final_query: NotRequired[Query | str | None]
163
224
  final_args: NotRequired[list[Any]]
164
- final_kwargs: NotRequired[AnyDict]
225
+ final_kwargs: NotRequired[SelectKwargs]
165
226
  relationships: NotRequired[set[str]]
166
227
 
167
228
  sql: NotRequired[str]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.1
2
2
  Name: TypeDAL
3
- Version: 3.2.0
3
+ Version: 3.3.1
4
4
  Summary: Typing support for PyDAL
5
5
  Project-URL: Documentation, https://typedal.readthedocs.io/
6
6
  Project-URL: Issues, https://github.com/trialandsuccess/TypeDAL/issues
@@ -29,14 +29,17 @@ Requires-Dist: tabulate; extra == 'all'
29
29
  Requires-Dist: tomlkit; extra == 'all'
30
30
  Requires-Dist: typer; extra == 'all'
31
31
  Provides-Extra: dev
32
+ Requires-Dist: contextlib-chdir; extra == 'dev'
32
33
  Requires-Dist: hatch; extra == 'dev'
33
34
  Requires-Dist: mkdocs; extra == 'dev'
34
35
  Requires-Dist: mkdocs-dracula-theme; extra == 'dev'
35
36
  Requires-Dist: pytest-mypy-testing; extra == 'dev'
36
37
  Requires-Dist: python-semantic-release<8; extra == 'dev'
37
- Requires-Dist: su6[all]; extra == 'dev'
38
+ Requires-Dist: requests<2.32; extra == 'dev'
39
+ Requires-Dist: su6[all]>=1.9.0; extra == 'dev'
38
40
  Requires-Dist: testcontainers; extra == 'dev'
39
41
  Requires-Dist: types-pyyaml; extra == 'dev'
42
+ Requires-Dist: types-requests; extra == 'dev'
40
43
  Requires-Dist: types-tabulate; extra == 'dev'
41
44
  Provides-Extra: migrations
42
45
  Requires-Dist: edwh-migrate>=0.8.0; extra == 'migrations'
@@ -0,0 +1,19 @@
1
+ typedal/__about__.py,sha256=Wu5Lv4Jglqs8h2_AjyrHv-m67Gz9cmuGT7s5MPNnAFs,206
2
+ typedal/__init__.py,sha256=QQpLiVl9w9hm2LBxey49Y_tCF_VB2bScVaS_mCjYy54,366
3
+ typedal/caching.py,sha256=SMcJsahLlZ79yykWCveERFx1ZJUNEKhA9SPmCTIuLp8,11798
4
+ typedal/cli.py,sha256=BXXu1w9WRRK9GA82xh_JLvvfpNDFWICXKCAWNg4a3-Y,18180
5
+ typedal/config.py,sha256=0qy1zrTUdtmXPM9jHzFnSR1DJsqGJqcdG6pvhzKQHe0,11625
6
+ typedal/core.py,sha256=ScUZkDzHGULNTcrW9Jq4Anf4axUfha-CXWmm7SWrn90,95991
7
+ typedal/fields.py,sha256=z2PD9vLWqBR_zXtiY0DthqTG4AeF3yxKoeuVfGXnSdg,5197
8
+ typedal/for_py4web.py,sha256=d07b8hL_PvNDUS26Z5fDH2OxWb-IETBuAFPSzrRwm04,1285
9
+ typedal/for_web2py.py,sha256=4RHgzGXgKIO_BYB-7adC5e35u52rX-p1t4tPEz-NK24,1867
10
+ typedal/helpers.py,sha256=KbgP4ZRW7KCroyHwTwdErPqylOX4dsqVnKJeZ5TtTFY,7363
11
+ typedal/mixins.py,sha256=OHhVYLBGTzPSJKgp1uovBs1Y6NJa0d-DxHTOk9LyOXA,5414
12
+ typedal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ typedal/types.py,sha256=AsUHrAXk058zmdqDbZlpf6sE85ID1Q2vofsGgCXni1s,4851
14
+ typedal/web2py_py4web_shared.py,sha256=VK9T8P5UwVLvfNBsY4q79ANcABv-jX76YKADt1Zz_co,1539
15
+ typedal/serializers/as_json.py,sha256=ffo152W-sARYXym4BzwX709rrO2-QwKk2KunWY8RNl4,2229
16
+ typedal-3.3.1.dist-info/METADATA,sha256=8sBC0FTFEy7qE17mgnL5sj06hkl3LQc_tRP8_j2-kbU,9462
17
+ typedal-3.3.1.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
18
+ typedal-3.3.1.dist-info/entry_points.txt,sha256=m1wqcc_10rHWPdlQ71zEkmJDADUAnZtn7Jac_6mbyUc,44
19
+ typedal-3.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.22.4
2
+ Generator: hatchling 1.17.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,19 +0,0 @@
1
- typedal/__about__.py,sha256=JM7UvclHJLbyevmmJGGt8HQwzgu3SMpy7q2WeNEm9Io,206
2
- typedal/__init__.py,sha256=QQpLiVl9w9hm2LBxey49Y_tCF_VB2bScVaS_mCjYy54,366
3
- typedal/caching.py,sha256=8UABVAhOlBpL96ykmqhxLaFYOe-XeAh7JoGh57OkxP8,11818
4
- typedal/cli.py,sha256=3tge8B-YjgjMC6425-RMczmWvpOTfWV5QYPXRY23IWA,18200
5
- typedal/config.py,sha256=jS1K0_1F5rwJtvwTZ-qR29ZCX7WlyORGEIFvfSnusko,11645
6
- typedal/core.py,sha256=tocfC9KDnuOoPgUTpBhFo2WGTupS5Y0esyFSTcANwLc,95866
7
- typedal/fields.py,sha256=z2PD9vLWqBR_zXtiY0DthqTG4AeF3yxKoeuVfGXnSdg,5197
8
- typedal/for_py4web.py,sha256=d07b8hL_PvNDUS26Z5fDH2OxWb-IETBuAFPSzrRwm04,1285
9
- typedal/for_web2py.py,sha256=4RHgzGXgKIO_BYB-7adC5e35u52rX-p1t4tPEz-NK24,1867
10
- typedal/helpers.py,sha256=mtRYPFlS0dx2wK8kYSJ4vm1wsTXRkdPumFRlOAjF_xU,7177
11
- typedal/mixins.py,sha256=OHhVYLBGTzPSJKgp1uovBs1Y6NJa0d-DxHTOk9LyOXA,5414
12
- typedal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- typedal/types.py,sha256=1kGkNX6vfGg6ln84AG558C4Zx5ACRz-emrUTnuy-rRY,3410
14
- typedal/web2py_py4web_shared.py,sha256=VK9T8P5UwVLvfNBsY4q79ANcABv-jX76YKADt1Zz_co,1539
15
- typedal/serializers/as_json.py,sha256=ffo152W-sARYXym4BzwX709rrO2-QwKk2KunWY8RNl4,2229
16
- typedal-3.2.0.dist-info/METADATA,sha256=foiNGED0K4DE5WJOKqWZt693_ms7zMp8JO8GXbblkok,9316
17
- typedal-3.2.0.dist-info/WHEEL,sha256=uNdcs2TADwSd5pVaP0Z_kcjcvvTUklh2S7bxZMF8Uj0,87
18
- typedal-3.2.0.dist-info/entry_points.txt,sha256=m1wqcc_10rHWPdlQ71zEkmJDADUAnZtn7Jac_6mbyUc,44
19
- typedal-3.2.0.dist-info/RECORD,,