TypeDAL 3.3.0__py3-none-any.whl → 3.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of TypeDAL might be problematic. Click here for more details.
- typedal/__about__.py +1 -1
- typedal/core.py +90 -79
- typedal/helpers.py +9 -0
- typedal/types.py +9 -0
- {typedal-3.3.0.dist-info → typedal-3.4.0.dist-info}/METADATA +5 -2
- {typedal-3.3.0.dist-info → typedal-3.4.0.dist-info}/RECORD +8 -8
- {typedal-3.3.0.dist-info → typedal-3.4.0.dist-info}/WHEEL +0 -0
- {typedal-3.3.0.dist-info → typedal-3.4.0.dist-info}/entry_points.txt +0 -0
typedal/__about__.py
CHANGED
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
|
|
@@ -65,8 +65,8 @@ from .types import (
|
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
# use typing.cast(type, ...) to make mypy happy with unions
|
|
68
|
-
T_annotation =
|
|
69
|
-
T_Query = typing.Union["Table", Query, bool, None, "TypedTable",
|
|
68
|
+
T_annotation = Type[Any] | types.UnionType
|
|
69
|
+
T_Query = typing.Union["Table", Query, bool, None, "TypedTable", Type["TypedTable"]]
|
|
70
70
|
T_Value = typing.TypeVar("T_Value") # actual type of the Field (via Generic)
|
|
71
71
|
T_MetaInstance = typing.TypeVar("T_MetaInstance", bound="TypedTable") # bound="TypedTable"; bound="TableMeta"
|
|
72
72
|
T = typing.TypeVar("T")
|
|
@@ -102,7 +102,7 @@ JOIN_OPTIONS = typing.Literal["left", "inner", None]
|
|
|
102
102
|
DEFAULT_JOIN_OPTION: JOIN_OPTIONS = "left"
|
|
103
103
|
|
|
104
104
|
# table-ish paramter:
|
|
105
|
-
P_Table = typing.Union[
|
|
105
|
+
P_Table = typing.Union[Type["TypedTable"], pydal.objects.Table]
|
|
106
106
|
|
|
107
107
|
Condition: typing.TypeAlias = typing.Optional[
|
|
108
108
|
typing.Callable[
|
|
@@ -120,7 +120,7 @@ OnQuery: typing.TypeAlias = typing.Optional[
|
|
|
120
120
|
]
|
|
121
121
|
]
|
|
122
122
|
|
|
123
|
-
To_Type = typing.TypeVar("To_Type", type[Any],
|
|
123
|
+
To_Type = typing.TypeVar("To_Type", type[Any], Type[Any], str)
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
class Relationship(typing.Generic[To_Type]):
|
|
@@ -129,7 +129,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
129
129
|
"""
|
|
130
130
|
|
|
131
131
|
_type: To_Type
|
|
132
|
-
table:
|
|
132
|
+
table: Type["TypedTable"] | type | str
|
|
133
133
|
condition: Condition
|
|
134
134
|
on: OnQuery
|
|
135
135
|
multiple: bool
|
|
@@ -188,7 +188,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
188
188
|
join = f":{self.join}" if self.join else ""
|
|
189
189
|
return f"<Relationship{join} {src_code}>"
|
|
190
190
|
|
|
191
|
-
def get_table(self, db: "TypeDAL") ->
|
|
191
|
+
def get_table(self, db: "TypeDAL") -> Type["TypedTable"]:
|
|
192
192
|
"""
|
|
193
193
|
Get the table this relationship is bound to.
|
|
194
194
|
"""
|
|
@@ -199,7 +199,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
199
199
|
return mapped
|
|
200
200
|
|
|
201
201
|
# boo, fall back to untyped table but pretend it is typed:
|
|
202
|
-
return typing.cast(
|
|
202
|
+
return typing.cast(Type["TypedTable"], db[table]) # eh close enough!
|
|
203
203
|
|
|
204
204
|
return table
|
|
205
205
|
|
|
@@ -241,9 +241,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
241
241
|
return None
|
|
242
242
|
|
|
243
243
|
|
|
244
|
-
def relationship(
|
|
245
|
-
_type: To_Type, condition: Condition = None, join: JOIN_OPTIONS = None, on: OnQuery = None
|
|
246
|
-
) -> Relationship[To_Type]:
|
|
244
|
+
def relationship(_type: To_Type, condition: Condition = None, join: JOIN_OPTIONS = None, on: OnQuery = None) -> To_Type:
|
|
247
245
|
"""
|
|
248
246
|
Define a relationship to another table, when its id is not stored in the current table.
|
|
249
247
|
|
|
@@ -273,11 +271,17 @@ def relationship(
|
|
|
273
271
|
|
|
274
272
|
If you'd try to capture this in a single 'condition', pydal would create a cross join which is much less efficient.
|
|
275
273
|
"""
|
|
276
|
-
return
|
|
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
|
+
)
|
|
277
281
|
|
|
278
282
|
|
|
279
283
|
def _generate_relationship_condition(
|
|
280
|
-
_:
|
|
284
|
+
_: Type["TypedTable"], key: str, field: typing.Union["TypedField[Any]", "Table", Type["TypedTable"]]
|
|
281
285
|
) -> Condition:
|
|
282
286
|
origin = typing.get_origin(field)
|
|
283
287
|
# else: generic
|
|
@@ -294,9 +298,9 @@ def _generate_relationship_condition(
|
|
|
294
298
|
|
|
295
299
|
|
|
296
300
|
def to_relationship(
|
|
297
|
-
cls:
|
|
301
|
+
cls: Type["TypedTable"] | type[Any],
|
|
298
302
|
key: str,
|
|
299
|
-
field: typing.Union["TypedField[Any]", "Table",
|
|
303
|
+
field: typing.Union["TypedField[Any]", "Table", Type["TypedTable"]],
|
|
300
304
|
) -> typing.Optional[Relationship[Any]]:
|
|
301
305
|
"""
|
|
302
306
|
Used to automatically create relationship instance for reference fields.
|
|
@@ -427,7 +431,7 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
427
431
|
self.try_define(_TypedalCache)
|
|
428
432
|
self.try_define(_TypedalCacheDependency)
|
|
429
433
|
|
|
430
|
-
def try_define(self, model:
|
|
434
|
+
def try_define(self, model: Type[T], verbose: bool = False) -> Type[T]:
|
|
431
435
|
"""
|
|
432
436
|
Try to define a model with migrate or fall back to fake migrate.
|
|
433
437
|
"""
|
|
@@ -451,9 +455,9 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
451
455
|
}
|
|
452
456
|
|
|
453
457
|
# maps table name to typedal class, for resolving future references
|
|
454
|
-
_class_map: typing.ClassVar[dict[str,
|
|
458
|
+
_class_map: typing.ClassVar[dict[str, Type["TypedTable"]]] = {}
|
|
455
459
|
|
|
456
|
-
def _define(self, cls:
|
|
460
|
+
def _define(self, cls: Type[T], **kwargs: Any) -> Type[T]:
|
|
457
461
|
# todo: new relationship item added should also invalidate (previously unrelated) cache result
|
|
458
462
|
|
|
459
463
|
# todo: option to enable/disable cache dependency behavior:
|
|
@@ -555,7 +559,7 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
555
559
|
return cls
|
|
556
560
|
|
|
557
561
|
@typing.overload
|
|
558
|
-
def define(self, maybe_cls: None = None, **kwargs: Any) -> typing.Callable[[
|
|
562
|
+
def define(self, maybe_cls: None = None, **kwargs: Any) -> typing.Callable[[Type[T]], Type[T]]:
|
|
559
563
|
"""
|
|
560
564
|
Typing Overload for define without a class.
|
|
561
565
|
|
|
@@ -564,7 +568,7 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
564
568
|
"""
|
|
565
569
|
|
|
566
570
|
@typing.overload
|
|
567
|
-
def define(self, maybe_cls:
|
|
571
|
+
def define(self, maybe_cls: Type[T], **kwargs: Any) -> Type[T]:
|
|
568
572
|
"""
|
|
569
573
|
Typing Overload for define with a class.
|
|
570
574
|
|
|
@@ -572,9 +576,7 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
572
576
|
class MyTable(TypedTable): ...
|
|
573
577
|
"""
|
|
574
578
|
|
|
575
|
-
def define(
|
|
576
|
-
self, maybe_cls: typing.Type[T] | None = None, **kwargs: Any
|
|
577
|
-
) -> 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]]:
|
|
578
580
|
"""
|
|
579
581
|
Can be used as a decorator on a class that inherits `TypedTable`, \
|
|
580
582
|
or as a regular method if you need to define your classes before you have access to a 'db' instance.
|
|
@@ -597,7 +599,7 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
597
599
|
the result of pydal.define_table
|
|
598
600
|
"""
|
|
599
601
|
|
|
600
|
-
def wrapper(cls:
|
|
602
|
+
def wrapper(cls: Type[T]) -> Type[T]:
|
|
601
603
|
return self._define(cls, **kwargs)
|
|
602
604
|
|
|
603
605
|
if maybe_cls:
|
|
@@ -647,7 +649,7 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
647
649
|
|
|
648
650
|
if isinstance(cls, type) and issubclass(type(cls), type) and issubclass(cls, TypedTable):
|
|
649
651
|
# table defined without @db.define decorator!
|
|
650
|
-
_cls:
|
|
652
|
+
_cls: Type[TypedTable] = cls
|
|
651
653
|
args[0] = _cls.id != None
|
|
652
654
|
|
|
653
655
|
_set = super().__call__(*args, **kwargs)
|
|
@@ -671,11 +673,11 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
671
673
|
cls, _ftype: T_annotation, mut_kw: typing.MutableMapping[str, Any]
|
|
672
674
|
) -> Optional[str]:
|
|
673
675
|
# ftype can be a union or type. typing.cast is sometimes used to tell mypy when it's not a union.
|
|
674
|
-
ftype = typing.cast(type, _ftype) # cast from
|
|
676
|
+
ftype = typing.cast(type, _ftype) # cast from Type to type to make mypy happy)
|
|
675
677
|
|
|
676
678
|
if isinstance(ftype, str):
|
|
677
679
|
# extract type from string
|
|
678
|
-
ftype = typing.get_args(
|
|
680
|
+
ftype = typing.get_args(Type[ftype])[0]._evaluate(
|
|
679
681
|
localns=locals(), globalns=globals(), recursive_guard=frozenset()
|
|
680
682
|
)
|
|
681
683
|
|
|
@@ -835,13 +837,13 @@ class TableMeta(type):
|
|
|
835
837
|
else:
|
|
836
838
|
return f"<unbound table {self.__name__}>"
|
|
837
839
|
|
|
838
|
-
def from_row(self:
|
|
840
|
+
def from_row(self: Type[T_MetaInstance], row: pydal.objects.Row) -> T_MetaInstance:
|
|
839
841
|
"""
|
|
840
842
|
Create a model instance from a pydal row.
|
|
841
843
|
"""
|
|
842
844
|
return self(row)
|
|
843
845
|
|
|
844
|
-
def all(self:
|
|
846
|
+
def all(self: Type[T_MetaInstance]) -> "TypedRows[T_MetaInstance]":
|
|
845
847
|
"""
|
|
846
848
|
Return all rows for this model.
|
|
847
849
|
"""
|
|
@@ -857,7 +859,7 @@ class TableMeta(type):
|
|
|
857
859
|
# TypeDAL Modified Logic #
|
|
858
860
|
##########################
|
|
859
861
|
|
|
860
|
-
def insert(self:
|
|
862
|
+
def insert(self: Type[T_MetaInstance], **fields: Any) -> T_MetaInstance:
|
|
861
863
|
"""
|
|
862
864
|
This is only called when db.define is not used as a decorator.
|
|
863
865
|
|
|
@@ -880,7 +882,7 @@ class TableMeta(type):
|
|
|
880
882
|
|
|
881
883
|
return str(table._insert(**fields))
|
|
882
884
|
|
|
883
|
-
def bulk_insert(self:
|
|
885
|
+
def bulk_insert(self: Type[T_MetaInstance], items: list[AnyDict]) -> "TypedRows[T_MetaInstance]":
|
|
884
886
|
"""
|
|
885
887
|
Insert multiple rows, returns a TypedRows set of new instances.
|
|
886
888
|
"""
|
|
@@ -889,7 +891,7 @@ class TableMeta(type):
|
|
|
889
891
|
return self.where(lambda row: row.id.belongs(result)).collect()
|
|
890
892
|
|
|
891
893
|
def update_or_insert(
|
|
892
|
-
self:
|
|
894
|
+
self: Type[T_MetaInstance], query: T_Query | AnyDict = DEFAULT, **values: Any
|
|
893
895
|
) -> T_MetaInstance:
|
|
894
896
|
"""
|
|
895
897
|
Update a row if query matches, else insert a new one.
|
|
@@ -912,7 +914,7 @@ class TableMeta(type):
|
|
|
912
914
|
return self(record)
|
|
913
915
|
|
|
914
916
|
def validate_and_insert(
|
|
915
|
-
self:
|
|
917
|
+
self: Type[T_MetaInstance], **fields: Any
|
|
916
918
|
) -> tuple[Optional[T_MetaInstance], Optional[dict[str, str]]]:
|
|
917
919
|
"""
|
|
918
920
|
Validate input data and then insert a row.
|
|
@@ -927,7 +929,7 @@ class TableMeta(type):
|
|
|
927
929
|
return None, result.get("errors")
|
|
928
930
|
|
|
929
931
|
def validate_and_update(
|
|
930
|
-
self:
|
|
932
|
+
self: Type[T_MetaInstance], query: Query, **fields: Any
|
|
931
933
|
) -> tuple[Optional[T_MetaInstance], Optional[dict[str, str]]]:
|
|
932
934
|
"""
|
|
933
935
|
Validate input data and then update max 1 row.
|
|
@@ -947,7 +949,7 @@ class TableMeta(type):
|
|
|
947
949
|
return None, None
|
|
948
950
|
|
|
949
951
|
def validate_and_update_or_insert(
|
|
950
|
-
self:
|
|
952
|
+
self: Type[T_MetaInstance], query: Query, **fields: Any
|
|
951
953
|
) -> tuple[Optional[T_MetaInstance], Optional[dict[str, str]]]:
|
|
952
954
|
"""
|
|
953
955
|
Validate input data and then update_and_insert (on max 1 row).
|
|
@@ -965,59 +967,57 @@ class TableMeta(type):
|
|
|
965
967
|
# update on query without result (shouldnt happen)
|
|
966
968
|
return None, None
|
|
967
969
|
|
|
968
|
-
def select(self:
|
|
970
|
+
def select(self: Type[T_MetaInstance], *a: Any, **kw: Any) -> "QueryBuilder[T_MetaInstance]":
|
|
969
971
|
"""
|
|
970
972
|
See QueryBuilder.select!
|
|
971
973
|
"""
|
|
972
974
|
return QueryBuilder(self).select(*a, **kw)
|
|
973
975
|
|
|
974
|
-
def paginate(self:
|
|
976
|
+
def paginate(self: Type[T_MetaInstance], limit: int, page: int = 1) -> "PaginatedRows[T_MetaInstance]":
|
|
975
977
|
"""
|
|
976
978
|
See QueryBuilder.paginate!
|
|
977
979
|
"""
|
|
978
980
|
return QueryBuilder(self).paginate(limit=limit, page=page)
|
|
979
981
|
|
|
980
|
-
def chunk(
|
|
981
|
-
self: typing.Type[T_MetaInstance], chunk_size: int
|
|
982
|
-
) -> typing.Generator["TypedRows[T_MetaInstance]", Any, None]:
|
|
982
|
+
def chunk(self: Type[T_MetaInstance], chunk_size: int) -> typing.Generator["TypedRows[T_MetaInstance]", Any, None]:
|
|
983
983
|
"""
|
|
984
984
|
See QueryBuilder.chunk!
|
|
985
985
|
"""
|
|
986
986
|
return QueryBuilder(self).chunk(chunk_size)
|
|
987
987
|
|
|
988
|
-
def where(self:
|
|
988
|
+
def where(self: Type[T_MetaInstance], *a: Any, **kw: Any) -> "QueryBuilder[T_MetaInstance]":
|
|
989
989
|
"""
|
|
990
990
|
See QueryBuilder.where!
|
|
991
991
|
"""
|
|
992
992
|
return QueryBuilder(self).where(*a, **kw)
|
|
993
993
|
|
|
994
|
-
def cache(self:
|
|
994
|
+
def cache(self: Type[T_MetaInstance], *deps: Any, **kwargs: Any) -> "QueryBuilder[T_MetaInstance]":
|
|
995
995
|
"""
|
|
996
996
|
See QueryBuilder.cache!
|
|
997
997
|
"""
|
|
998
998
|
return QueryBuilder(self).cache(*deps, **kwargs)
|
|
999
999
|
|
|
1000
|
-
def count(self:
|
|
1000
|
+
def count(self: Type[T_MetaInstance]) -> int:
|
|
1001
1001
|
"""
|
|
1002
1002
|
See QueryBuilder.count!
|
|
1003
1003
|
"""
|
|
1004
1004
|
return QueryBuilder(self).count()
|
|
1005
1005
|
|
|
1006
|
-
def first(self:
|
|
1006
|
+
def first(self: Type[T_MetaInstance]) -> T_MetaInstance | None:
|
|
1007
1007
|
"""
|
|
1008
1008
|
See QueryBuilder.first!
|
|
1009
1009
|
"""
|
|
1010
1010
|
return QueryBuilder(self).first()
|
|
1011
1011
|
|
|
1012
|
-
def first_or_fail(self:
|
|
1012
|
+
def first_or_fail(self: Type[T_MetaInstance]) -> T_MetaInstance:
|
|
1013
1013
|
"""
|
|
1014
1014
|
See QueryBuilder.first_or_fail!
|
|
1015
1015
|
"""
|
|
1016
1016
|
return QueryBuilder(self).first_or_fail()
|
|
1017
1017
|
|
|
1018
1018
|
def join(
|
|
1019
|
-
self:
|
|
1020
|
-
*fields: str |
|
|
1019
|
+
self: Type[T_MetaInstance],
|
|
1020
|
+
*fields: str | Type["TypedTable"],
|
|
1021
1021
|
method: JOIN_OPTIONS = None,
|
|
1022
1022
|
on: OnQuery | list[Expression] | Expression = None,
|
|
1023
1023
|
condition: Condition = None,
|
|
@@ -1027,7 +1027,7 @@ class TableMeta(type):
|
|
|
1027
1027
|
"""
|
|
1028
1028
|
return QueryBuilder(self).join(*fields, on=on, condition=condition, method=method)
|
|
1029
1029
|
|
|
1030
|
-
def collect(self:
|
|
1030
|
+
def collect(self: Type[T_MetaInstance], verbose: bool = False) -> "TypedRows[T_MetaInstance]":
|
|
1031
1031
|
"""
|
|
1032
1032
|
See QueryBuilder.collect!
|
|
1033
1033
|
"""
|
|
@@ -1127,7 +1127,7 @@ class TableMeta(type):
|
|
|
1127
1127
|
table = self._ensure_table_defined()
|
|
1128
1128
|
return typing.cast(Expression, table.on(query))
|
|
1129
1129
|
|
|
1130
|
-
def with_alias(self, alias: str) ->
|
|
1130
|
+
def with_alias(self: Type[T_MetaInstance], alias: str) -> Type[T_MetaInstance]:
|
|
1131
1131
|
"""
|
|
1132
1132
|
Shadow Table.with_alias.
|
|
1133
1133
|
|
|
@@ -1137,7 +1137,7 @@ class TableMeta(type):
|
|
|
1137
1137
|
http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer?search=export_to_csv_file#One-to-many-relation
|
|
1138
1138
|
"""
|
|
1139
1139
|
table = self._ensure_table_defined()
|
|
1140
|
-
return table.with_alias(alias)
|
|
1140
|
+
return typing.cast(Type[T_MetaInstance], table.with_alias(alias))
|
|
1141
1141
|
|
|
1142
1142
|
# @typing.dataclass_transform()
|
|
1143
1143
|
|
|
@@ -1159,7 +1159,7 @@ class TypedField(Expression, typing.Generic[T_Value]): # pragma: no cover
|
|
|
1159
1159
|
|
|
1160
1160
|
requires: Validator | typing.Iterable[Validator]
|
|
1161
1161
|
|
|
1162
|
-
def __init__(self, _type:
|
|
1162
|
+
def __init__(self, _type: Type[T_Value] | types.UnionType = str, /, **settings: Any) -> None: # type: ignore
|
|
1163
1163
|
"""
|
|
1164
1164
|
A TypedFieldType should not be inited manually, but TypedField (from `fields.py`) should be used!
|
|
1165
1165
|
"""
|
|
@@ -1168,19 +1168,19 @@ class TypedField(Expression, typing.Generic[T_Value]): # pragma: no cover
|
|
|
1168
1168
|
# super().__init__()
|
|
1169
1169
|
|
|
1170
1170
|
@typing.overload
|
|
1171
|
-
def __get__(self, instance: T_MetaInstance, owner:
|
|
1171
|
+
def __get__(self, instance: T_MetaInstance, owner: Type[T_MetaInstance]) -> T_Value: # pragma: no cover
|
|
1172
1172
|
"""
|
|
1173
1173
|
row.field -> (actual data).
|
|
1174
1174
|
"""
|
|
1175
1175
|
|
|
1176
1176
|
@typing.overload
|
|
1177
|
-
def __get__(self, instance: None, owner: "
|
|
1177
|
+
def __get__(self, instance: None, owner: "Type[TypedTable]") -> "TypedField[T_Value]": # pragma: no cover
|
|
1178
1178
|
"""
|
|
1179
1179
|
Table.field -> Field.
|
|
1180
1180
|
"""
|
|
1181
1181
|
|
|
1182
1182
|
def __get__(
|
|
1183
|
-
self, instance: T_MetaInstance | None, owner:
|
|
1183
|
+
self, instance: T_MetaInstance | None, owner: Type[T_MetaInstance]
|
|
1184
1184
|
) -> typing.Union[T_Value, "TypedField[T_Value]"]:
|
|
1185
1185
|
"""
|
|
1186
1186
|
Since this class is a Descriptor field, \
|
|
@@ -1374,7 +1374,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
|
1374
1374
|
|
|
1375
1375
|
def __new__(
|
|
1376
1376
|
cls, row_or_id: typing.Union[Row, Query, pydal.objects.Set, int, str, None, "TypedTable"] = None, **filters: Any
|
|
1377
|
-
) ->
|
|
1377
|
+
) -> Self:
|
|
1378
1378
|
"""
|
|
1379
1379
|
Create a Typed Rows model instance from an existing row, ID or query.
|
|
1380
1380
|
|
|
@@ -1596,7 +1596,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
|
1596
1596
|
super().__setattr__(key, value)
|
|
1597
1597
|
|
|
1598
1598
|
@classmethod
|
|
1599
|
-
def update(cls:
|
|
1599
|
+
def update(cls: Type[T_MetaInstance], query: Query, **fields: Any) -> T_MetaInstance | None:
|
|
1600
1600
|
"""
|
|
1601
1601
|
Update one record.
|
|
1602
1602
|
|
|
@@ -1695,7 +1695,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1695
1695
|
|
|
1696
1696
|
records: dict[int, T_MetaInstance]
|
|
1697
1697
|
# _rows: Rows
|
|
1698
|
-
model:
|
|
1698
|
+
model: Type[T_MetaInstance]
|
|
1699
1699
|
metadata: Metadata
|
|
1700
1700
|
|
|
1701
1701
|
# pseudo-properties: actually stored in _rows
|
|
@@ -1708,7 +1708,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1708
1708
|
def __init__(
|
|
1709
1709
|
self,
|
|
1710
1710
|
rows: Rows,
|
|
1711
|
-
model:
|
|
1711
|
+
model: Type[T_MetaInstance],
|
|
1712
1712
|
records: dict[int, T_MetaInstance] = None,
|
|
1713
1713
|
metadata: Metadata = None,
|
|
1714
1714
|
) -> None:
|
|
@@ -1841,15 +1841,6 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1841
1841
|
result = super().group_by_value(*fields, **kwargs)
|
|
1842
1842
|
return typing.cast(dict[T, list[T_MetaInstance]], result)
|
|
1843
1843
|
|
|
1844
|
-
def column(self, column: str = None) -> list[Any]:
|
|
1845
|
-
"""
|
|
1846
|
-
Get a list of all values in a specific column.
|
|
1847
|
-
|
|
1848
|
-
Example:
|
|
1849
|
-
rows.column('name') -> ['Name 1', 'Name 2', ...]
|
|
1850
|
-
"""
|
|
1851
|
-
return typing.cast(list[Any], super().column(column))
|
|
1852
|
-
|
|
1853
1844
|
def as_csv(self) -> str:
|
|
1854
1845
|
"""
|
|
1855
1846
|
Dump the data to csv.
|
|
@@ -1938,7 +1929,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1938
1929
|
Update the current rows in the database with new_values.
|
|
1939
1930
|
"""
|
|
1940
1931
|
# cast to make mypy understand .id is a TypedField and not an int!
|
|
1941
|
-
table = typing.cast(
|
|
1932
|
+
table = typing.cast(Type[TypedTable], self.model._ensure_table_defined())
|
|
1942
1933
|
|
|
1943
1934
|
ids = set(self.column("id"))
|
|
1944
1935
|
query = table.id.belongs(ids)
|
|
@@ -1949,7 +1940,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1949
1940
|
Delete the currently selected rows from the database.
|
|
1950
1941
|
"""
|
|
1951
1942
|
# cast to make mypy understand .id is a TypedField and not an int!
|
|
1952
|
-
table = typing.cast(
|
|
1943
|
+
table = typing.cast(Type[TypedTable], self.model._ensure_table_defined())
|
|
1953
1944
|
|
|
1954
1945
|
ids = set(self.column("id"))
|
|
1955
1946
|
query = table.id.belongs(ids)
|
|
@@ -2004,7 +1995,7 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
2004
1995
|
|
|
2005
1996
|
@classmethod
|
|
2006
1997
|
def from_rows(
|
|
2007
|
-
cls, rows: Rows, model:
|
|
1998
|
+
cls, rows: Rows, model: Type[T_MetaInstance], metadata: Metadata = None
|
|
2008
1999
|
) -> "TypedRows[T_MetaInstance]":
|
|
2009
2000
|
"""
|
|
2010
2001
|
Internal method to convert a Rows object to a TypedRows.
|
|
@@ -2047,7 +2038,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2047
2038
|
Abstration on top of pydal's query system.
|
|
2048
2039
|
"""
|
|
2049
2040
|
|
|
2050
|
-
model:
|
|
2041
|
+
model: Type[T_MetaInstance]
|
|
2051
2042
|
query: Query
|
|
2052
2043
|
select_args: list[Any]
|
|
2053
2044
|
select_kwargs: SelectKwargs
|
|
@@ -2056,7 +2047,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2056
2047
|
|
|
2057
2048
|
def __init__(
|
|
2058
2049
|
self,
|
|
2059
|
-
model:
|
|
2050
|
+
model: Type[T_MetaInstance],
|
|
2060
2051
|
add_query: Optional[Query] = None,
|
|
2061
2052
|
select_args: Optional[list[Any]] = None,
|
|
2062
2053
|
select_kwargs: Optional[SelectKwargs] = None,
|
|
@@ -2151,7 +2142,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2151
2142
|
|
|
2152
2143
|
def where(
|
|
2153
2144
|
self,
|
|
2154
|
-
*queries_or_lambdas: Query | typing.Callable[[
|
|
2145
|
+
*queries_or_lambdas: Query | typing.Callable[[Type[T_MetaInstance]], Query],
|
|
2155
2146
|
**filters: Any,
|
|
2156
2147
|
) -> "QueryBuilder[T_MetaInstance]":
|
|
2157
2148
|
"""
|
|
@@ -2192,7 +2183,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2192
2183
|
|
|
2193
2184
|
def join(
|
|
2194
2185
|
self,
|
|
2195
|
-
*fields: str |
|
|
2186
|
+
*fields: str | Type[TypedTable],
|
|
2196
2187
|
method: JOIN_OPTIONS = None,
|
|
2197
2188
|
on: OnQuery | list[Expression] | Expression = None,
|
|
2198
2189
|
condition: Condition = None,
|
|
@@ -2219,7 +2210,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2219
2210
|
if isinstance(condition, pydal.objects.Query):
|
|
2220
2211
|
condition = as_lambda(condition)
|
|
2221
2212
|
|
|
2222
|
-
relationships = {str(fields[0]):
|
|
2213
|
+
relationships = {str(fields[0]): Relationship(fields[0], condition=condition, join=method)}
|
|
2223
2214
|
elif on:
|
|
2224
2215
|
if len(fields) != 1:
|
|
2225
2216
|
raise ValueError("join(field, on=...) can only be used with exactly one field!")
|
|
@@ -2229,7 +2220,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2229
2220
|
|
|
2230
2221
|
if isinstance(on, list):
|
|
2231
2222
|
on = as_lambda(on)
|
|
2232
|
-
relationships = {str(fields[0]):
|
|
2223
|
+
relationships = {str(fields[0]): Relationship(fields[0], on=on, join=method)}
|
|
2233
2224
|
|
|
2234
2225
|
else:
|
|
2235
2226
|
if fields:
|
|
@@ -2383,7 +2374,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2383
2374
|
return db(query).select(*select_args, **select_kwargs)
|
|
2384
2375
|
|
|
2385
2376
|
def collect(
|
|
2386
|
-
self, verbose: bool = False, _to:
|
|
2377
|
+
self, verbose: bool = False, _to: Type["TypedRows[Any]"] = None, add_id: bool = True
|
|
2387
2378
|
) -> "TypedRows[T_MetaInstance]":
|
|
2388
2379
|
"""
|
|
2389
2380
|
Execute the built query and turn it into model instances, while handling relationships.
|
|
@@ -2426,6 +2417,26 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2426
2417
|
# only saves if requested in metadata:
|
|
2427
2418
|
return save_to_cache(typed_rows, rows)
|
|
2428
2419
|
|
|
2420
|
+
@typing.overload
|
|
2421
|
+
def column(self, field: TypedField[T]) -> list[T]:
|
|
2422
|
+
"""
|
|
2423
|
+
If a typedfield is passed, the output type can be safely determined.
|
|
2424
|
+
"""
|
|
2425
|
+
|
|
2426
|
+
@typing.overload
|
|
2427
|
+
def column(self, field: T) -> list[T]:
|
|
2428
|
+
"""
|
|
2429
|
+
Otherwise, the output type is loosely determined (assumes `field: type` or Any).
|
|
2430
|
+
"""
|
|
2431
|
+
|
|
2432
|
+
def column(self, field: TypedField[T] | T) -> list[T]:
|
|
2433
|
+
"""
|
|
2434
|
+
Get all values in a specific column.
|
|
2435
|
+
|
|
2436
|
+
Shortcut for `.select(field).execute().column(field)`.
|
|
2437
|
+
"""
|
|
2438
|
+
return self.select(field).execute().column(field)
|
|
2439
|
+
|
|
2429
2440
|
def _handle_relationships_pre_select(
|
|
2430
2441
|
self,
|
|
2431
2442
|
query: Query,
|
|
@@ -2521,7 +2532,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2521
2532
|
return query, select_args
|
|
2522
2533
|
|
|
2523
2534
|
def _collect_with_relationships(
|
|
2524
|
-
self, rows: Rows, metadata: Metadata, _to:
|
|
2535
|
+
self, rows: Rows, metadata: Metadata, _to: Type["TypedRows[Any]"]
|
|
2525
2536
|
) -> "TypedRows[T_MetaInstance]":
|
|
2526
2537
|
"""
|
|
2527
2538
|
Transform the raw rows into Typed Table model instances.
|
|
@@ -2802,7 +2813,7 @@ class TypedSet(pydal.objects.Set): # type: ignore # pragma: no cover
|
|
|
2802
2813
|
result: TypedRows[MyTable] = db(MyTable.id > 0).select()
|
|
2803
2814
|
|
|
2804
2815
|
for row in result:
|
|
2805
|
-
|
|
2816
|
+
reveal_type(row) # MyTable
|
|
2806
2817
|
"""
|
|
2807
2818
|
rows = super().select(*fields, **attributes)
|
|
2808
2819
|
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
|
@@ -79,6 +79,15 @@ class Rows(_Rows): # type: ignore
|
|
|
79
79
|
Make mypy happy.
|
|
80
80
|
"""
|
|
81
81
|
|
|
82
|
+
def column(self, column: typing.Any = None) -> list[typing.Any]:
|
|
83
|
+
"""
|
|
84
|
+
Get a list of all values in a specific column.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
rows.column('name') -> ['Name 1', 'Name 2', ...]
|
|
88
|
+
"""
|
|
89
|
+
return [r[str(column) if column else self.colnames[0]] for r in self]
|
|
90
|
+
|
|
82
91
|
|
|
83
92
|
class Validator(_Validator): # type: ignore
|
|
84
93
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: TypeDAL
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
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:
|
|
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'
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
typedal/__about__.py,sha256=
|
|
1
|
+
typedal/__about__.py,sha256=4H4aYxAonMoSZ4TwJQKsDojr3VmwSW8DHN3jahxVVsA,206
|
|
2
2
|
typedal/__init__.py,sha256=QQpLiVl9w9hm2LBxey49Y_tCF_VB2bScVaS_mCjYy54,366
|
|
3
3
|
typedal/caching.py,sha256=SMcJsahLlZ79yykWCveERFx1ZJUNEKhA9SPmCTIuLp8,11798
|
|
4
4
|
typedal/cli.py,sha256=BXXu1w9WRRK9GA82xh_JLvvfpNDFWICXKCAWNg4a3-Y,18180
|
|
5
5
|
typedal/config.py,sha256=0qy1zrTUdtmXPM9jHzFnSR1DJsqGJqcdG6pvhzKQHe0,11625
|
|
6
|
-
typedal/core.py,sha256=
|
|
6
|
+
typedal/core.py,sha256=b8pFiaORXEC0PWaBSmPkQuPtJfk0mwqUiAqxSmX4ZtM,96318
|
|
7
7
|
typedal/fields.py,sha256=z2PD9vLWqBR_zXtiY0DthqTG4AeF3yxKoeuVfGXnSdg,5197
|
|
8
8
|
typedal/for_py4web.py,sha256=d07b8hL_PvNDUS26Z5fDH2OxWb-IETBuAFPSzrRwm04,1285
|
|
9
9
|
typedal/for_web2py.py,sha256=4RHgzGXgKIO_BYB-7adC5e35u52rX-p1t4tPEz-NK24,1867
|
|
10
|
-
typedal/helpers.py,sha256=
|
|
10
|
+
typedal/helpers.py,sha256=KbgP4ZRW7KCroyHwTwdErPqylOX4dsqVnKJeZ5TtTFY,7363
|
|
11
11
|
typedal/mixins.py,sha256=OHhVYLBGTzPSJKgp1uovBs1Y6NJa0d-DxHTOk9LyOXA,5414
|
|
12
12
|
typedal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
typedal/types.py,sha256=
|
|
13
|
+
typedal/types.py,sha256=fyTOAhORFufZsAKnf1dMcWJxLK-UMX6UFl-JbiqFmSk,5161
|
|
14
14
|
typedal/web2py_py4web_shared.py,sha256=VK9T8P5UwVLvfNBsY4q79ANcABv-jX76YKADt1Zz_co,1539
|
|
15
15
|
typedal/serializers/as_json.py,sha256=ffo152W-sARYXym4BzwX709rrO2-QwKk2KunWY8RNl4,2229
|
|
16
|
-
typedal-3.
|
|
17
|
-
typedal-3.
|
|
18
|
-
typedal-3.
|
|
19
|
-
typedal-3.
|
|
16
|
+
typedal-3.4.0.dist-info/METADATA,sha256=onWL5IwfJALWrdfIPUQNYrmFdIcMNZqRAlGixK88Tv0,9462
|
|
17
|
+
typedal-3.4.0.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
|
|
18
|
+
typedal-3.4.0.dist-info/entry_points.txt,sha256=m1wqcc_10rHWPdlQ71zEkmJDADUAnZtn7Jac_6mbyUc,44
|
|
19
|
+
typedal-3.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|