TypeDAL 3.17.3__py3-none-any.whl → 4.0.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 +1 -1
- typedal/__init__.py +10 -9
- typedal/caching.py +36 -33
- typedal/config.py +15 -16
- typedal/constants.py +25 -0
- typedal/core.py +176 -3168
- typedal/define.py +188 -0
- typedal/fields.py +254 -29
- typedal/for_web2py.py +1 -1
- typedal/helpers.py +268 -61
- typedal/mixins.py +21 -25
- typedal/query_builder.py +1059 -0
- typedal/relationships.py +264 -0
- typedal/rows.py +524 -0
- typedal/serializers/as_json.py +9 -10
- typedal/tables.py +1122 -0
- typedal/types.py +183 -177
- typedal/web2py_py4web_shared.py +1 -1
- {typedal-3.17.3.dist-info → typedal-4.0.1.dist-info}/METADATA +8 -7
- typedal-4.0.1.dist-info/RECORD +25 -0
- typedal-3.17.3.dist-info/RECORD +0 -19
- {typedal-3.17.3.dist-info → typedal-4.0.1.dist-info}/WHEEL +0 -0
- {typedal-3.17.3.dist-info → typedal-4.0.1.dist-info}/entry_points.txt +0 -0
typedal/define.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Seperates the table definition code from core DAL code.
|
|
3
|
+
|
|
4
|
+
Since otherwise helper methods would clutter up the TypeDAl class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import types
|
|
11
|
+
import typing as t
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
import pydal
|
|
15
|
+
|
|
16
|
+
from .constants import BASIC_MAPPINGS
|
|
17
|
+
from .core import TypeDAL, evaluate_forward_reference, resolve_annotation
|
|
18
|
+
from .fields import TypedField, is_typed_field
|
|
19
|
+
from .helpers import (
|
|
20
|
+
all_annotations,
|
|
21
|
+
all_dict,
|
|
22
|
+
filter_out,
|
|
23
|
+
instanciate,
|
|
24
|
+
is_union,
|
|
25
|
+
origin_is_subclass,
|
|
26
|
+
to_snake,
|
|
27
|
+
)
|
|
28
|
+
from .relationships import Relationship, to_relationship
|
|
29
|
+
from .tables import TypedTable
|
|
30
|
+
from .types import (
|
|
31
|
+
Field,
|
|
32
|
+
T,
|
|
33
|
+
T_annotation,
|
|
34
|
+
Table,
|
|
35
|
+
_Types,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# python 3.14+
|
|
40
|
+
from annotationlib import ForwardRef
|
|
41
|
+
except ImportError: # pragma: no cover
|
|
42
|
+
# python 3.13-
|
|
43
|
+
from typing import ForwardRef
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TableDefinitionBuilder:
|
|
47
|
+
"""Handles the conversion of TypedTable classes to pydal tables."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, db: "TypeDAL"):
|
|
50
|
+
"""
|
|
51
|
+
Before, the `class_map` was a singleton on the pydal class; now it's per database.
|
|
52
|
+
"""
|
|
53
|
+
self.db = db
|
|
54
|
+
self.class_map: dict[str, t.Type["TypedTable"]] = {}
|
|
55
|
+
|
|
56
|
+
def define(self, cls: t.Type[T], **kwargs: t.Any) -> t.Type[T]:
|
|
57
|
+
"""Build and register a table from a TypedTable class."""
|
|
58
|
+
full_dict = all_dict(cls)
|
|
59
|
+
tablename = to_snake(cls.__name__)
|
|
60
|
+
annotations = all_annotations(cls)
|
|
61
|
+
annotations |= {k: t.cast(type, v) for k, v in full_dict.items() if is_typed_field(v)}
|
|
62
|
+
annotations = {k: v for k, v in annotations.items() if not k.startswith("_")}
|
|
63
|
+
|
|
64
|
+
typedfields: dict[str, TypedField[t.Any]] = {
|
|
65
|
+
k: instanciate(v, True) for k, v in annotations.items() if is_typed_field(v)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
relationships: dict[str, type[Relationship[t.Any]]] = filter_out(annotations, Relationship)
|
|
69
|
+
fields = {fname: self.to_field(fname, ftype) for fname, ftype in annotations.items()}
|
|
70
|
+
|
|
71
|
+
other_kwargs = kwargs | {
|
|
72
|
+
k: v for k, v in cls.__dict__.items() if k not in annotations and not k.startswith("_")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for key, field in typedfields.items():
|
|
76
|
+
clone = copy.copy(field)
|
|
77
|
+
setattr(cls, key, clone)
|
|
78
|
+
typedfields[key] = clone
|
|
79
|
+
|
|
80
|
+
relationships = filter_out(full_dict, Relationship) | relationships | filter_out(other_kwargs, Relationship)
|
|
81
|
+
|
|
82
|
+
reference_field_keys = [
|
|
83
|
+
k for k, v in fields.items() if str(v.type).split(" ")[0] in ("list:reference", "reference")
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
relationships |= {
|
|
87
|
+
k: new_relationship
|
|
88
|
+
for k in reference_field_keys
|
|
89
|
+
if k not in relationships and (new_relationship := to_relationship(cls, k, annotations[k]))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cache_dependency = self.db._config.caching and kwargs.pop("cache_dependency", True)
|
|
93
|
+
table: Table = self.db.define_table(tablename, *fields.values(), **kwargs)
|
|
94
|
+
|
|
95
|
+
for name, typed_field in typedfields.items():
|
|
96
|
+
field = fields[name]
|
|
97
|
+
typed_field.bind(field, table)
|
|
98
|
+
|
|
99
|
+
if issubclass(cls, TypedTable):
|
|
100
|
+
cls.__set_internals__(
|
|
101
|
+
db=self.db,
|
|
102
|
+
table=table,
|
|
103
|
+
relationships=t.cast(dict[str, Relationship[t.Any]], relationships),
|
|
104
|
+
)
|
|
105
|
+
self.class_map[str(table)] = cls
|
|
106
|
+
self.class_map[table._rname] = cls
|
|
107
|
+
cls.__on_define__(self.db)
|
|
108
|
+
else:
|
|
109
|
+
warnings.warn("db.define used without inheriting TypedTable. This could lead to strange problems!")
|
|
110
|
+
|
|
111
|
+
if not tablename.startswith("typedal_") and cache_dependency:
|
|
112
|
+
from .caching import _remove_cache
|
|
113
|
+
|
|
114
|
+
table._before_update.append(lambda s, _: _remove_cache(s, tablename))
|
|
115
|
+
table._before_delete.append(lambda s: _remove_cache(s, tablename))
|
|
116
|
+
|
|
117
|
+
return cls
|
|
118
|
+
|
|
119
|
+
def to_field(self, fname: str, ftype: type, **kw: t.Any) -> Field:
|
|
120
|
+
"""Convert annotation to pydal Field."""
|
|
121
|
+
fname = to_snake(fname)
|
|
122
|
+
if converted_type := self.annotation_to_pydal_fieldtype(ftype, kw):
|
|
123
|
+
return self.build_field(fname, converted_type, **kw)
|
|
124
|
+
else:
|
|
125
|
+
raise NotImplementedError(f"Unsupported type {ftype}/{type(ftype)}")
|
|
126
|
+
|
|
127
|
+
def annotation_to_pydal_fieldtype(
|
|
128
|
+
self,
|
|
129
|
+
ftype_annotation: T_annotation,
|
|
130
|
+
mut_kw: t.MutableMapping[str, t.Any],
|
|
131
|
+
) -> t.Optional[str]:
|
|
132
|
+
"""Convert Python type annotation to pydal field type string."""
|
|
133
|
+
ftype = t.cast(type, ftype_annotation) # cast from Type to type to make mypy happy)
|
|
134
|
+
|
|
135
|
+
if isinstance(ftype, str):
|
|
136
|
+
# extract type from string
|
|
137
|
+
ftype = resolve_annotation(ftype)
|
|
138
|
+
|
|
139
|
+
if isinstance(ftype, ForwardRef):
|
|
140
|
+
known_classes = {table.__name__: table for table in self.class_map.values()}
|
|
141
|
+
|
|
142
|
+
ftype = evaluate_forward_reference(ftype, namespace=known_classes)
|
|
143
|
+
|
|
144
|
+
if mapping := BASIC_MAPPINGS.get(ftype):
|
|
145
|
+
# basi types
|
|
146
|
+
return mapping
|
|
147
|
+
elif isinstance(ftype, pydal.objects.Table):
|
|
148
|
+
# db.table
|
|
149
|
+
return f"reference {ftype._tablename}"
|
|
150
|
+
elif issubclass(type(ftype), type) and issubclass(ftype, TypedTable):
|
|
151
|
+
# SomeTable
|
|
152
|
+
snakename = to_snake(ftype.__name__)
|
|
153
|
+
return f"reference {snakename}"
|
|
154
|
+
elif isinstance(ftype, TypedField):
|
|
155
|
+
# FieldType(type, ...)
|
|
156
|
+
return ftype._to_field(mut_kw, self)
|
|
157
|
+
elif origin_is_subclass(ftype, TypedField):
|
|
158
|
+
# TypedField[int]
|
|
159
|
+
return self.annotation_to_pydal_fieldtype(t.get_args(ftype)[0], mut_kw)
|
|
160
|
+
elif isinstance(ftype, types.GenericAlias) and t.get_origin(ftype) in (list, TypedField): # type: ignore
|
|
161
|
+
# list[str] -> str -> string -> list:string
|
|
162
|
+
_child_type = t.get_args(ftype)[0]
|
|
163
|
+
_child_type = self.annotation_to_pydal_fieldtype(_child_type, mut_kw)
|
|
164
|
+
return f"list:{_child_type}"
|
|
165
|
+
elif is_union(ftype):
|
|
166
|
+
# str | int -> UnionType
|
|
167
|
+
# typing.Union[str | int] -> typing._UnionGenericAlias
|
|
168
|
+
|
|
169
|
+
# Optional[type] == type | None
|
|
170
|
+
|
|
171
|
+
match t.get_args(ftype):
|
|
172
|
+
case (_child_type, _Types.NONETYPE) | (_Types.NONETYPE, _child_type):
|
|
173
|
+
# good union of Nullable
|
|
174
|
+
|
|
175
|
+
# if a field is optional, it is nullable:
|
|
176
|
+
mut_kw["notnull"] = False
|
|
177
|
+
return self.annotation_to_pydal_fieldtype(_child_type, mut_kw)
|
|
178
|
+
case _:
|
|
179
|
+
# two types is not supported by the db!
|
|
180
|
+
return None
|
|
181
|
+
else:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def build_field(cls, name: str, field_type: str, **kw: t.Any) -> Field:
|
|
186
|
+
"""Create a pydal Field with default kwargs."""
|
|
187
|
+
kw_combined = TypeDAL.default_kwargs | kw
|
|
188
|
+
return Field(name, field_type, **kw_combined)
|
typedal/fields.py
CHANGED
|
@@ -2,27 +2,248 @@
|
|
|
2
2
|
This file contains available Field types.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import ast
|
|
8
|
+
import contextlib
|
|
6
9
|
import datetime as dt
|
|
7
10
|
import decimal
|
|
8
|
-
import
|
|
11
|
+
import types
|
|
12
|
+
import typing as t
|
|
9
13
|
import uuid
|
|
10
14
|
|
|
15
|
+
import pydal
|
|
11
16
|
from pydal.helpers.classes import SQLCustomType
|
|
12
17
|
from pydal.objects import Table
|
|
13
|
-
from typing_extensions import Unpack
|
|
14
18
|
|
|
15
|
-
from .core import TypeDAL
|
|
16
|
-
from .types import
|
|
19
|
+
from .core import TypeDAL
|
|
20
|
+
from .types import (
|
|
21
|
+
Expression,
|
|
22
|
+
Field,
|
|
23
|
+
FieldSettings,
|
|
24
|
+
Query,
|
|
25
|
+
T_annotation,
|
|
26
|
+
T_MetaInstance,
|
|
27
|
+
T_subclass,
|
|
28
|
+
T_Value,
|
|
29
|
+
Validator,
|
|
30
|
+
)
|
|
17
31
|
|
|
18
|
-
|
|
32
|
+
if t.TYPE_CHECKING:
|
|
33
|
+
# will be imported for real later:
|
|
34
|
+
from .tables import TypedTable
|
|
19
35
|
|
|
20
36
|
|
|
21
37
|
## general
|
|
22
38
|
|
|
23
39
|
|
|
40
|
+
class TypedField(Expression, t.Generic[T_Value]): # pragma: no cover
|
|
41
|
+
"""
|
|
42
|
+
Typed version of pydal.Field, which will be converted to a normal Field in the background.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# will be set by .bind on db.define
|
|
46
|
+
name = ""
|
|
47
|
+
_db: t.Optional[pydal.DAL] = None
|
|
48
|
+
_rname: t.Optional[str] = None
|
|
49
|
+
_table: t.Optional[Table] = None
|
|
50
|
+
_field: t.Optional[Field] = None
|
|
51
|
+
|
|
52
|
+
_type: T_annotation
|
|
53
|
+
kwargs: t.Any
|
|
54
|
+
|
|
55
|
+
requires: Validator | t.Iterable[Validator]
|
|
56
|
+
|
|
57
|
+
# NOTE: for the logic of converting a TypedField into a pydal Field, see TypeDAL._to_field
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
_type: t.Type[T_Value] | types.UnionType = str, # type: ignore
|
|
62
|
+
/,
|
|
63
|
+
**settings: t.Unpack[FieldSettings],
|
|
64
|
+
) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Typed version of pydal.Field, which will be converted to a normal Field in the background.
|
|
67
|
+
|
|
68
|
+
Provide the Python type for this field as the first positional argument
|
|
69
|
+
and any other settings to Field() as keyword parameters.
|
|
70
|
+
"""
|
|
71
|
+
self._type = _type
|
|
72
|
+
self.kwargs = settings
|
|
73
|
+
# super().__init__()
|
|
74
|
+
|
|
75
|
+
@t.overload
|
|
76
|
+
def __get__(self, instance: T_MetaInstance, owner: t.Type[T_MetaInstance]) -> T_Value: # pragma: no cover
|
|
77
|
+
"""
|
|
78
|
+
row.field -> (actual data).
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
@t.overload
|
|
82
|
+
def __get__(self, instance: None, owner: "t.Type[TypedTable]") -> "TypedField[T_Value]": # pragma: no cover
|
|
83
|
+
"""
|
|
84
|
+
Table.field -> Field.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __get__(
|
|
88
|
+
self,
|
|
89
|
+
instance: T_MetaInstance | None,
|
|
90
|
+
owner: t.Type[T_MetaInstance],
|
|
91
|
+
) -> t.Union[T_Value, "TypedField[T_Value]"]:
|
|
92
|
+
"""
|
|
93
|
+
Since this class is a Descriptor field, \
|
|
94
|
+
it returns something else depending on if it's called on a class or instance.
|
|
95
|
+
|
|
96
|
+
(this is mostly for mypy/typing)
|
|
97
|
+
"""
|
|
98
|
+
if instance:
|
|
99
|
+
# this is only reached in a very specific case:
|
|
100
|
+
# an instance of the object was created with a specific set of fields selected (excluding the current one)
|
|
101
|
+
# in that case, no value was stored in the owner -> return None (since the field was not selected)
|
|
102
|
+
return t.cast(T_Value, None) # cast as T_Value so mypy understands it for selected fields
|
|
103
|
+
else:
|
|
104
|
+
# getting as class -> return actual field so pydal understands it when using in query etc.
|
|
105
|
+
return t.cast(TypedField[T_Value], self._field) # pretend it's still typed for IDE support
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
"""
|
|
109
|
+
String representation of a Typed Field.
|
|
110
|
+
|
|
111
|
+
If `type` is set explicitly (e.g. TypedField(str, type="text")), that type is used: `TypedField.text`,
|
|
112
|
+
otherwise the type annotation is used (e.g. TypedField(str) -> TypedField.str)
|
|
113
|
+
"""
|
|
114
|
+
return str(self._field) if self._field else ""
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str:
|
|
117
|
+
"""
|
|
118
|
+
More detailed string representation of a Typed Field.
|
|
119
|
+
|
|
120
|
+
Uses __str__ and adds the provided extra options (kwargs) in the representation.
|
|
121
|
+
"""
|
|
122
|
+
string_value = self.__str__()
|
|
123
|
+
|
|
124
|
+
if "type" in self.kwargs:
|
|
125
|
+
# manual type in kwargs supplied
|
|
126
|
+
typename = self.kwargs["type"]
|
|
127
|
+
elif issubclass(type, type(self._type)):
|
|
128
|
+
# normal type, str.__name__ = 'str'
|
|
129
|
+
typename = getattr(self._type, "__name__", str(self._type))
|
|
130
|
+
elif t_args := t.get_args(self._type):
|
|
131
|
+
# list[str] -> 'str'
|
|
132
|
+
typename = t_args[0].__name__
|
|
133
|
+
else: # pragma: no cover
|
|
134
|
+
# fallback - something else, may not even happen, I'm not sure
|
|
135
|
+
typename = self._type
|
|
136
|
+
|
|
137
|
+
string_value = f"TypedField[{typename}].{string_value}" if string_value else f"TypedField[{typename}]"
|
|
138
|
+
|
|
139
|
+
kw = self.kwargs.copy()
|
|
140
|
+
kw.pop("type", None)
|
|
141
|
+
return f"<{string_value} with options {kw}>"
|
|
142
|
+
|
|
143
|
+
def _to_field(self, extra_kwargs: t.MutableMapping[str, t.Any], builder: TableDefinitionBuilder) -> t.Optional[str]:
|
|
144
|
+
"""
|
|
145
|
+
Convert a Typed Field instance to a pydal.Field.
|
|
146
|
+
|
|
147
|
+
Actual logic in TypeDAL._to_field but this function creates the pydal type name and updates the kwarg settings.
|
|
148
|
+
"""
|
|
149
|
+
other_kwargs = self.kwargs.copy()
|
|
150
|
+
extra_kwargs.update(other_kwargs) # <- modifies and overwrites the default kwargs with user-specified ones
|
|
151
|
+
return extra_kwargs.pop("type", False) or builder.annotation_to_pydal_fieldtype(
|
|
152
|
+
self._type,
|
|
153
|
+
extra_kwargs,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def bind(self, field: pydal.objects.Field, table: pydal.objects.Table) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Bind the right db/table/field info to this class, so queries can be made using `Class.field == ...`.
|
|
159
|
+
"""
|
|
160
|
+
self._table = table
|
|
161
|
+
self._field = field
|
|
162
|
+
|
|
163
|
+
def __getattr__(self, key: str) -> t.Any:
|
|
164
|
+
"""
|
|
165
|
+
If the regular getattribute does not work, try to get info from the related Field.
|
|
166
|
+
"""
|
|
167
|
+
with contextlib.suppress(AttributeError):
|
|
168
|
+
return super().__getattribute__(key)
|
|
169
|
+
|
|
170
|
+
# try on actual field:
|
|
171
|
+
return getattr(self._field, key)
|
|
172
|
+
|
|
173
|
+
def __eq__(self, other: t.Any) -> Query:
|
|
174
|
+
"""
|
|
175
|
+
Performing == on a Field will result in a Query.
|
|
176
|
+
"""
|
|
177
|
+
return t.cast(Query, self._field == other)
|
|
178
|
+
|
|
179
|
+
def __ne__(self, other: t.Any) -> Query:
|
|
180
|
+
"""
|
|
181
|
+
Performing != on a Field will result in a Query.
|
|
182
|
+
"""
|
|
183
|
+
return t.cast(Query, self._field != other)
|
|
184
|
+
|
|
185
|
+
def __gt__(self, other: t.Any) -> Query:
|
|
186
|
+
"""
|
|
187
|
+
Performing > on a Field will result in a Query.
|
|
188
|
+
"""
|
|
189
|
+
return t.cast(Query, self._field > other)
|
|
190
|
+
|
|
191
|
+
def __lt__(self, other: t.Any) -> Query:
|
|
192
|
+
"""
|
|
193
|
+
Performing < on a Field will result in a Query.
|
|
194
|
+
"""
|
|
195
|
+
return t.cast(Query, self._field < other)
|
|
196
|
+
|
|
197
|
+
def __ge__(self, other: t.Any) -> Query:
|
|
198
|
+
"""
|
|
199
|
+
Performing >= on a Field will result in a Query.
|
|
200
|
+
"""
|
|
201
|
+
return t.cast(Query, self._field >= other)
|
|
202
|
+
|
|
203
|
+
def __le__(self, other: t.Any) -> Query:
|
|
204
|
+
"""
|
|
205
|
+
Performing <= on a Field will result in a Query.
|
|
206
|
+
"""
|
|
207
|
+
return t.cast(Query, self._field <= other)
|
|
208
|
+
|
|
209
|
+
def __hash__(self) -> int:
|
|
210
|
+
"""
|
|
211
|
+
Shadow Field.__hash__.
|
|
212
|
+
"""
|
|
213
|
+
return hash(self._field)
|
|
214
|
+
|
|
215
|
+
def __invert__(self) -> Expression:
|
|
216
|
+
"""
|
|
217
|
+
Performing ~ on a Field will result in an Expression.
|
|
218
|
+
"""
|
|
219
|
+
if not self._field: # pragma: no cover
|
|
220
|
+
raise ValueError("Unbound Field can not be inverted!")
|
|
221
|
+
|
|
222
|
+
return t.cast(Expression, ~self._field)
|
|
223
|
+
|
|
224
|
+
def lower(self) -> Expression:
|
|
225
|
+
"""
|
|
226
|
+
For string-fields: compare lowercased values.
|
|
227
|
+
"""
|
|
228
|
+
if not self._field: # pragma: no cover
|
|
229
|
+
raise ValueError("Unbound Field can not be lowered!")
|
|
230
|
+
|
|
231
|
+
return t.cast(Expression, self._field.lower())
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def is_typed_field(cls: t.Any) -> t.TypeGuard["TypedField[t.Any]"]:
|
|
235
|
+
"""
|
|
236
|
+
Is `cls` an instance or subclass of TypedField?
|
|
237
|
+
|
|
238
|
+
Deprecated
|
|
239
|
+
"""
|
|
240
|
+
return isinstance(cls, TypedField) or (
|
|
241
|
+
isinstance(t.get_origin(cls), type) and issubclass(t.get_origin(cls), TypedField)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
24
245
|
## specific
|
|
25
|
-
def StringField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
246
|
+
def StringField(**kw: t.Unpack[FieldSettings]) -> TypedField[str]:
|
|
26
247
|
"""
|
|
27
248
|
Pydal type is string, Python type is str.
|
|
28
249
|
"""
|
|
@@ -33,7 +254,7 @@ def StringField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
|
33
254
|
String = StringField
|
|
34
255
|
|
|
35
256
|
|
|
36
|
-
def TextField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
257
|
+
def TextField(**kw: t.Unpack[FieldSettings]) -> TypedField[str]:
|
|
37
258
|
"""
|
|
38
259
|
Pydal type is text, Python type is str.
|
|
39
260
|
"""
|
|
@@ -44,7 +265,7 @@ def TextField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
|
44
265
|
Text = TextField
|
|
45
266
|
|
|
46
267
|
|
|
47
|
-
def BlobField(**kw: Unpack[FieldSettings]) -> TypedField[bytes]:
|
|
268
|
+
def BlobField(**kw: t.Unpack[FieldSettings]) -> TypedField[bytes]:
|
|
48
269
|
"""
|
|
49
270
|
Pydal type is blob, Python type is bytes.
|
|
50
271
|
"""
|
|
@@ -55,7 +276,7 @@ def BlobField(**kw: Unpack[FieldSettings]) -> TypedField[bytes]:
|
|
|
55
276
|
Blob = BlobField
|
|
56
277
|
|
|
57
278
|
|
|
58
|
-
def BooleanField(**kw: Unpack[FieldSettings]) -> TypedField[bool]:
|
|
279
|
+
def BooleanField(**kw: t.Unpack[FieldSettings]) -> TypedField[bool]:
|
|
59
280
|
"""
|
|
60
281
|
Pydal type is boolean, Python type is bool.
|
|
61
282
|
"""
|
|
@@ -66,7 +287,7 @@ def BooleanField(**kw: Unpack[FieldSettings]) -> TypedField[bool]:
|
|
|
66
287
|
Boolean = BooleanField
|
|
67
288
|
|
|
68
289
|
|
|
69
|
-
def IntegerField(**kw: Unpack[FieldSettings]) -> TypedField[int]:
|
|
290
|
+
def IntegerField(**kw: t.Unpack[FieldSettings]) -> TypedField[int]:
|
|
70
291
|
"""
|
|
71
292
|
Pydal type is integer, Python type is int.
|
|
72
293
|
"""
|
|
@@ -77,7 +298,7 @@ def IntegerField(**kw: Unpack[FieldSettings]) -> TypedField[int]:
|
|
|
77
298
|
Integer = IntegerField
|
|
78
299
|
|
|
79
300
|
|
|
80
|
-
def DoubleField(**kw: Unpack[FieldSettings]) -> TypedField[float]:
|
|
301
|
+
def DoubleField(**kw: t.Unpack[FieldSettings]) -> TypedField[float]:
|
|
81
302
|
"""
|
|
82
303
|
Pydal type is double, Python type is float.
|
|
83
304
|
"""
|
|
@@ -88,7 +309,7 @@ def DoubleField(**kw: Unpack[FieldSettings]) -> TypedField[float]:
|
|
|
88
309
|
Double = DoubleField
|
|
89
310
|
|
|
90
311
|
|
|
91
|
-
def DecimalField(n: int, m: int, **kw: Unpack[FieldSettings]) -> TypedField[decimal.Decimal]:
|
|
312
|
+
def DecimalField(n: int, m: int, **kw: t.Unpack[FieldSettings]) -> TypedField[decimal.Decimal]:
|
|
92
313
|
"""
|
|
93
314
|
Pydal type is decimal, Python type is Decimal.
|
|
94
315
|
"""
|
|
@@ -99,7 +320,7 @@ def DecimalField(n: int, m: int, **kw: Unpack[FieldSettings]) -> TypedField[deci
|
|
|
99
320
|
Decimal = DecimalField
|
|
100
321
|
|
|
101
322
|
|
|
102
|
-
def DateField(**kw: Unpack[FieldSettings]) -> TypedField[dt.date]:
|
|
323
|
+
def DateField(**kw: t.Unpack[FieldSettings]) -> TypedField[dt.date]:
|
|
103
324
|
"""
|
|
104
325
|
Pydal type is date, Python type is datetime.date.
|
|
105
326
|
"""
|
|
@@ -110,7 +331,7 @@ def DateField(**kw: Unpack[FieldSettings]) -> TypedField[dt.date]:
|
|
|
110
331
|
Date = DateField
|
|
111
332
|
|
|
112
333
|
|
|
113
|
-
def TimeField(**kw: Unpack[FieldSettings]) -> TypedField[dt.time]:
|
|
334
|
+
def TimeField(**kw: t.Unpack[FieldSettings]) -> TypedField[dt.time]:
|
|
114
335
|
"""
|
|
115
336
|
Pydal type is time, Python type is datetime.time.
|
|
116
337
|
"""
|
|
@@ -121,7 +342,7 @@ def TimeField(**kw: Unpack[FieldSettings]) -> TypedField[dt.time]:
|
|
|
121
342
|
Time = TimeField
|
|
122
343
|
|
|
123
344
|
|
|
124
|
-
def DatetimeField(**kw: Unpack[FieldSettings]) -> TypedField[dt.datetime]:
|
|
345
|
+
def DatetimeField(**kw: t.Unpack[FieldSettings]) -> TypedField[dt.datetime]:
|
|
125
346
|
"""
|
|
126
347
|
Pydal type is datetime, Python type is datetime.datetime.
|
|
127
348
|
"""
|
|
@@ -132,7 +353,7 @@ def DatetimeField(**kw: Unpack[FieldSettings]) -> TypedField[dt.datetime]:
|
|
|
132
353
|
Datetime = DatetimeField
|
|
133
354
|
|
|
134
355
|
|
|
135
|
-
def PasswordField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
356
|
+
def PasswordField(**kw: t.Unpack[FieldSettings]) -> TypedField[str]:
|
|
136
357
|
"""
|
|
137
358
|
Pydal type is password, Python type is str.
|
|
138
359
|
"""
|
|
@@ -143,7 +364,7 @@ def PasswordField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
|
143
364
|
Password = PasswordField
|
|
144
365
|
|
|
145
366
|
|
|
146
|
-
def UploadField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
367
|
+
def UploadField(**kw: t.Unpack[FieldSettings]) -> TypedField[str]:
|
|
147
368
|
"""
|
|
148
369
|
Pydal type is upload, Python type is str.
|
|
149
370
|
"""
|
|
@@ -153,11 +374,10 @@ def UploadField(**kw: Unpack[FieldSettings]) -> TypedField[str]:
|
|
|
153
374
|
|
|
154
375
|
Upload = UploadField
|
|
155
376
|
|
|
156
|
-
T_subclass = typing.TypeVar("T_subclass", TypedTable, Table)
|
|
157
|
-
|
|
158
377
|
|
|
159
378
|
def ReferenceField(
|
|
160
|
-
other_table: str |
|
|
379
|
+
other_table: str | t.Type[TypedTable] | TypedTable | Table | T_subclass,
|
|
380
|
+
**kw: t.Unpack[FieldSettings],
|
|
161
381
|
) -> TypedField[int]:
|
|
162
382
|
"""
|
|
163
383
|
Pydal type is reference, Python type is int (id).
|
|
@@ -180,7 +400,7 @@ def ReferenceField(
|
|
|
180
400
|
Reference = ReferenceField
|
|
181
401
|
|
|
182
402
|
|
|
183
|
-
def ListStringField(**kw: Unpack[FieldSettings]) -> TypedField[list[str]]:
|
|
403
|
+
def ListStringField(**kw: t.Unpack[FieldSettings]) -> TypedField[list[str]]:
|
|
184
404
|
"""
|
|
185
405
|
Pydal type is list:string, Python type is list of str.
|
|
186
406
|
"""
|
|
@@ -191,7 +411,7 @@ def ListStringField(**kw: Unpack[FieldSettings]) -> TypedField[list[str]]:
|
|
|
191
411
|
ListString = ListStringField
|
|
192
412
|
|
|
193
413
|
|
|
194
|
-
def ListIntegerField(**kw: Unpack[FieldSettings]) -> TypedField[list[int]]:
|
|
414
|
+
def ListIntegerField(**kw: t.Unpack[FieldSettings]) -> TypedField[list[int]]:
|
|
195
415
|
"""
|
|
196
416
|
Pydal type is list:integer, Python type is list of int.
|
|
197
417
|
"""
|
|
@@ -202,7 +422,7 @@ def ListIntegerField(**kw: Unpack[FieldSettings]) -> TypedField[list[int]]:
|
|
|
202
422
|
ListInteger = ListIntegerField
|
|
203
423
|
|
|
204
424
|
|
|
205
|
-
def ListReferenceField(other_table: str, **kw: Unpack[FieldSettings]) -> TypedField[list[int]]:
|
|
425
|
+
def ListReferenceField(other_table: str, **kw: t.Unpack[FieldSettings]) -> TypedField[list[int]]:
|
|
206
426
|
"""
|
|
207
427
|
Pydal type is list:reference, Python type is list of int (id).
|
|
208
428
|
"""
|
|
@@ -213,7 +433,7 @@ def ListReferenceField(other_table: str, **kw: Unpack[FieldSettings]) -> TypedFi
|
|
|
213
433
|
ListReference = ListReferenceField
|
|
214
434
|
|
|
215
435
|
|
|
216
|
-
def JSONField(**kw: Unpack[FieldSettings]) -> TypedField[object]:
|
|
436
|
+
def JSONField(**kw: t.Unpack[FieldSettings]) -> TypedField[object]:
|
|
217
437
|
"""
|
|
218
438
|
Pydal type is json, Python type is object (can be anything JSON-encodable).
|
|
219
439
|
"""
|
|
@@ -221,7 +441,7 @@ def JSONField(**kw: Unpack[FieldSettings]) -> TypedField[object]:
|
|
|
221
441
|
return TypedField(object, **kw)
|
|
222
442
|
|
|
223
443
|
|
|
224
|
-
def BigintField(**kw: Unpack[FieldSettings]) -> TypedField[int]:
|
|
444
|
+
def BigintField(**kw: t.Unpack[FieldSettings]) -> TypedField[int]:
|
|
225
445
|
"""
|
|
226
446
|
Pydal type is bigint, Python type is int.
|
|
227
447
|
"""
|
|
@@ -241,7 +461,7 @@ NativeTimestampField = SQLCustomType(
|
|
|
241
461
|
)
|
|
242
462
|
|
|
243
463
|
|
|
244
|
-
def TimestampField(**kw: Unpack[FieldSettings]) -> TypedField[dt.datetime]:
|
|
464
|
+
def TimestampField(**kw: t.Unpack[FieldSettings]) -> TypedField[dt.datetime]:
|
|
245
465
|
"""
|
|
246
466
|
Database type is timestamp, Python type is datetime.
|
|
247
467
|
|
|
@@ -275,7 +495,7 @@ def safe_decode_native_point(value: str | None) -> tuple[float, ...]:
|
|
|
275
495
|
|
|
276
496
|
try:
|
|
277
497
|
parsed = ast.literal_eval(value)
|
|
278
|
-
return
|
|
498
|
+
return t.cast(tuple[float, ...], parsed)
|
|
279
499
|
except ValueError: # pragma: no cover
|
|
280
500
|
# should not happen when inserted with `safe_encode_native_point` but you never know
|
|
281
501
|
return ()
|
|
@@ -328,7 +548,7 @@ NativePointField = SQLCustomType(
|
|
|
328
548
|
)
|
|
329
549
|
|
|
330
550
|
|
|
331
|
-
def PointField(**kw: Unpack[FieldSettings]) -> TypedField[tuple[float, float]]:
|
|
551
|
+
def PointField(**kw: t.Unpack[FieldSettings]) -> TypedField[tuple[float, float]]:
|
|
332
552
|
"""
|
|
333
553
|
Database type is point, Python type is tuple[float, float].
|
|
334
554
|
"""
|
|
@@ -344,9 +564,14 @@ NativeUUIDField = SQLCustomType(
|
|
|
344
564
|
)
|
|
345
565
|
|
|
346
566
|
|
|
347
|
-
def UUIDField(**kw: Unpack[FieldSettings]) -> TypedField[uuid.UUID]:
|
|
567
|
+
def UUIDField(**kw: t.Unpack[FieldSettings]) -> TypedField[uuid.UUID]:
|
|
348
568
|
"""
|
|
349
569
|
Database type is uuid, Python type is UUID.
|
|
350
570
|
"""
|
|
351
571
|
kw["type"] = NativeUUIDField
|
|
352
572
|
return TypedField(uuid.UUID, **kw)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
# note: import at the end to prevent circular imports:
|
|
576
|
+
from .define import TableDefinitionBuilder # noqa: E402
|
|
577
|
+
from .tables import TypedTable # noqa: E402
|
typedal/for_web2py.py
CHANGED