TypeDAL 3.9.4__py3-none-any.whl → 3.10.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/mixins.py +75 -9
- {typedal-3.9.4.dist-info → typedal-3.10.1.dist-info}/METADATA +2 -2
- {typedal-3.9.4.dist-info → typedal-3.10.1.dist-info}/RECORD +6 -6
- {typedal-3.9.4.dist-info → typedal-3.10.1.dist-info}/WHEEL +0 -0
- {typedal-3.9.4.dist-info → typedal-3.10.1.dist-info}/entry_points.txt +0 -0
typedal/__about__.py
CHANGED
typedal/mixins.py
CHANGED
|
@@ -11,6 +11,8 @@ import warnings
|
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
from typing import Any, Optional
|
|
13
13
|
|
|
14
|
+
from pydal import DAL
|
|
15
|
+
from pydal.validators import IS_NOT_IN_DB, ValidationError
|
|
14
16
|
from slugify import slugify
|
|
15
17
|
|
|
16
18
|
from .core import ( # noqa F401 - used by example in docstring
|
|
@@ -87,6 +89,56 @@ def slug_random_suffix(length: int = 8) -> str:
|
|
|
87
89
|
return base64.urlsafe_b64encode(os.urandom(length)).rstrip(b"=").decode().strip("=")
|
|
88
90
|
|
|
89
91
|
|
|
92
|
+
T = typing.TypeVar("T")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# noinspection PyPep8Naming
|
|
96
|
+
class HAS_UNIQUE_SLUG(IS_NOT_IN_DB):
|
|
97
|
+
"""
|
|
98
|
+
Checks if slugified field is already in the db.
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
table.name = HAS_UNIQUE_SLUG(db, "table.slug")
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
db: TypeDAL | DAL,
|
|
107
|
+
field: str, # table.slug
|
|
108
|
+
error_message: str = "This slug is not unique: %s.",
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Based on IS_NOT_IN_DB but with less options and a different default error message.
|
|
112
|
+
"""
|
|
113
|
+
super().__init__(db, field, error_message)
|
|
114
|
+
|
|
115
|
+
def validate(self, original: T, record_id: Optional[int] = None) -> T:
|
|
116
|
+
"""
|
|
117
|
+
Performs checks to see if the slug already exists for a different row.
|
|
118
|
+
"""
|
|
119
|
+
value = slugify(str(original))
|
|
120
|
+
if not value.strip():
|
|
121
|
+
raise ValidationError(self.translator(self.error_message))
|
|
122
|
+
|
|
123
|
+
(tablename, fieldname) = str(self.field).split(".")
|
|
124
|
+
table = self.dbset.db[tablename]
|
|
125
|
+
field = table[fieldname]
|
|
126
|
+
query = field == value
|
|
127
|
+
|
|
128
|
+
# make sure exclude the record_id
|
|
129
|
+
row_id = record_id or self.record_id
|
|
130
|
+
if isinstance(row_id, dict): # pragma: no cover
|
|
131
|
+
row_id = table(**row_id)
|
|
132
|
+
if row_id is not None:
|
|
133
|
+
query &= table._id != row_id
|
|
134
|
+
subset = self.dbset(query)
|
|
135
|
+
|
|
136
|
+
if subset.count():
|
|
137
|
+
raise ValidationError(self.error_message % value)
|
|
138
|
+
|
|
139
|
+
return original
|
|
140
|
+
|
|
141
|
+
|
|
90
142
|
class SlugMixin(Mixin):
|
|
91
143
|
"""
|
|
92
144
|
(Opinionated) example mixin to add a 'slug' field, which depends on a user-provided other field.
|
|
@@ -139,26 +191,40 @@ class SlugMixin(Mixin):
|
|
|
139
191
|
cls.__settings__["slug_field"] = slug_field
|
|
140
192
|
cls.__settings__["slug_suffix"] = slug_suffix
|
|
141
193
|
|
|
194
|
+
@classmethod
|
|
195
|
+
def __generate_slug_before_insert(cls, row: OpRow) -> None:
|
|
196
|
+
settings = cls.__settings__
|
|
197
|
+
|
|
198
|
+
text_input = row[settings["slug_field"]]
|
|
199
|
+
generated_slug = slugify(text_input)
|
|
200
|
+
|
|
201
|
+
if suffix_len := settings["slug_suffix"]:
|
|
202
|
+
generated_slug += f"-{slug_random_suffix(suffix_len)}"
|
|
203
|
+
|
|
204
|
+
row["slug"] = slugify(generated_slug)
|
|
205
|
+
return None
|
|
206
|
+
|
|
142
207
|
@classmethod
|
|
143
208
|
def __on_define__(cls, db: TypeDAL) -> None:
|
|
144
209
|
"""
|
|
145
210
|
When db is available, include a before_insert hook to generate and include a slug.
|
|
146
211
|
"""
|
|
147
212
|
super().__on_define__(db)
|
|
213
|
+
settings = cls.__settings__
|
|
148
214
|
|
|
149
215
|
# slugs should not be editable (for SEO reasons), so there is only a before insert hook:
|
|
150
|
-
|
|
151
|
-
settings = cls.__settings__
|
|
152
|
-
|
|
153
|
-
text_input = row[settings["slug_field"]]
|
|
154
|
-
generated_slug = slugify(text_input)
|
|
216
|
+
cls._before_insert.append(cls.__generate_slug_before_insert)
|
|
155
217
|
|
|
156
|
-
|
|
157
|
-
|
|
218
|
+
if settings["slug_suffix"] == 0:
|
|
219
|
+
# add a validator to the field that will be slugified:
|
|
220
|
+
slug_field = getattr(cls, settings["slug_field"])
|
|
221
|
+
current_requires = getattr(slug_field, "requires", None) or []
|
|
222
|
+
if not isinstance(current_requires, list):
|
|
223
|
+
current_requires = [current_requires]
|
|
158
224
|
|
|
159
|
-
|
|
225
|
+
current_requires.append(HAS_UNIQUE_SLUG(db, f"{cls}.slug"))
|
|
160
226
|
|
|
161
|
-
|
|
227
|
+
slug_field.requires = current_requires
|
|
162
228
|
|
|
163
229
|
@classmethod
|
|
164
230
|
def from_slug(cls: typing.Type[T_MetaInstance], slug: str, join: bool = True) -> Optional[T_MetaInstance]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: TypeDAL
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.10.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
|
|
@@ -19,7 +19,7 @@ Requires-Dist: configurable-json
|
|
|
19
19
|
Requires-Dist: configuraptor>=1.26.2
|
|
20
20
|
Requires-Dist: dill
|
|
21
21
|
Requires-Dist: legacy-cgi; python_version >= '3.13'
|
|
22
|
-
Requires-Dist: pydal
|
|
22
|
+
Requires-Dist: pydal<20250215.1
|
|
23
23
|
Requires-Dist: python-slugify
|
|
24
24
|
Provides-Extra: all
|
|
25
25
|
Requires-Dist: edwh-migrate[full]>=0.8.0; extra == 'all'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
typedal/__about__.py,sha256=
|
|
1
|
+
typedal/__about__.py,sha256=lfj40YAUn5B1Ndcb8s7gmDIHat_R1pkHfqFCP36BSKQ,207
|
|
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=wzyId6YwRyqfuJ2byxvl6YecDNaKpkLmo-R5HvRuTok,19265
|
|
@@ -8,12 +8,12 @@ typedal/fields.py,sha256=A4qt0aK4F_-UeOY-xJ0ObVY-tFEoLFy7TYRMHnp4g6o,6516
|
|
|
8
8
|
typedal/for_py4web.py,sha256=plb08K6HmxbM1_5GuBFFzovAJIT7Q3g9ocNjUJOBngY,1946
|
|
9
9
|
typedal/for_web2py.py,sha256=xn7zo6ImsmTkH6LacbjLQl2oqyBvP0zLqRxEJvMQk1w,1929
|
|
10
10
|
typedal/helpers.py,sha256=uej96exzJ9qVBd6LudP8uJ_7Cmq6vKraerdKvSRBVjc,8128
|
|
11
|
-
typedal/mixins.py,sha256=
|
|
11
|
+
typedal/mixins.py,sha256=ySF55URmSMyUCdMhiQJSKjkz4s5XGaO1pvmpJAOLUSU,7821
|
|
12
12
|
typedal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
typedal/types.py,sha256=1FIgv1s0be0E8r5Wd9E1nvDvK4ETV8u2NlfI7_P6UUY,6752
|
|
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.10.1.dist-info/METADATA,sha256=upJmwQSLqLlRE74JHQZ7RQxYVZsT9GrS56kaK5Fz7UM,10451
|
|
17
|
+
typedal-3.10.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
18
|
+
typedal-3.10.1.dist-info/entry_points.txt,sha256=m1wqcc_10rHWPdlQ71zEkmJDADUAnZtn7Jac_6mbyUc,44
|
|
19
|
+
typedal-3.10.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|