viur-core 3.8.0.dev7__tar.gz → 3.8.0.dev9__tar.gz
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.
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/PKG-INFO +5 -5
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/pyproject.toml +4 -4
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/file.py +2 -1
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/record.py +1 -1
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/config.py +5 -3
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/cache.py +67 -61
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/query.py +48 -21
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/types.py +6 -2
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/file.py +110 -49
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/scripts/viur_migrate.py +0 -1
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/relskel.py +18 -3
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/skeleton.py +14 -14
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/tasks.py +136 -125
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/version.py +1 -1
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur_core.egg-info/PKG-INFO +5 -5
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur_core.egg-info/requires.txt +2 -1
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/tests/test_config.py +0 -1
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/LICENSE +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/README.md +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/setup.cfg +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/base.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/boolean.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/captcha.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/color.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/credential.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/date.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/email.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/image.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/json.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/key.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/numeric.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/password.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/phone.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/randomslice.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/raw.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/relational.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/select.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/selectcountry.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/sortindex.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/spam.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/spatial.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/string.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/text.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/treeleaf.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/treenode.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/uid.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/uri.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/bones/user.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/cache.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/current.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/config.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/overrides.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/transport.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/db/utils.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/decorators.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/email.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/errors.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/i18n.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/languages/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/languages/de.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/languages/en.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/logging.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/module.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/formmailer.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/history.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/moduleconf.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/page.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/script.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/site.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/translation.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/modules/user.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/pagination.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/prototypes/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/prototypes/instanced_module.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/prototypes/list.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/prototypes/singleton.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/prototypes/skelmodule.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/prototypes/tree.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/ratelimit.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/abstract.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/default.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/date.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/debug.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/regex.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/session.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/strings.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/tests.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/env/viur.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/user.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/html/utils.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/json/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/json/default.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/json/user.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/vi/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/render/vi/user.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/request.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/secret.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/securityheaders.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/securitykey.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/session.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/adapter.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/instance.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/meta.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/skeleton/utils.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/tasks.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/template/error.html +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/template/vi_user_google_login.html +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/utils/__init__.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/utils/json.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/utils/parse.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur/core/utils/string.py +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur_core.egg-info/SOURCES.txt +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur_core.egg-info/dependency_links.txt +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur_core.egg-info/entry_points.txt +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/src/viur_core.egg-info/top_level.txt +0 -0
- {viur_core-3.8.0.dev7 → viur_core-3.8.0.dev9}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: viur-core
|
|
3
|
-
Version: 3.8.0.
|
|
3
|
+
Version: 3.8.0.dev9
|
|
4
4
|
Summary: The core component of ViUR, a development framework for Google App Engine
|
|
5
5
|
Author-email: Mausbrand Informationssysteme GmbH <devs@viur.dev>
|
|
6
6
|
Maintainer-email: Jan Max Meyer <jm@mausbrand.de>
|
|
@@ -34,11 +34,10 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
34
34
|
Classifier: Intended Audience :: Developers
|
|
35
35
|
Classifier: License :: OSI Approved :: MIT License
|
|
36
36
|
Classifier: Operating System :: OS Independent
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
39
37
|
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
39
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
41
|
-
Requires-Python: >=3.
|
|
40
|
+
Requires-Python: >=3.12
|
|
42
41
|
Description-Content-Type: text/markdown
|
|
43
42
|
License-File: LICENSE
|
|
44
43
|
Requires-Dist: appengine-python-standard~=1.0
|
|
@@ -57,6 +56,7 @@ Requires-Dist: googleapis-common-protos[grpc]~=1.0
|
|
|
57
56
|
Requires-Dist: gunicorn~=23.0
|
|
58
57
|
Requires-Dist: jinja2~=3.0
|
|
59
58
|
Requires-Dist: jsonschema~=4.0
|
|
59
|
+
Requires-Dist: logics-py==0.0.12
|
|
60
60
|
Requires-Dist: pillow~=10.0
|
|
61
61
|
Requires-Dist: pyotp~=2.0
|
|
62
62
|
Requires-Dist: pytz~=2023.0
|
|
@@ -64,7 +64,7 @@ Requires-Dist: pyyaml~=6.0
|
|
|
64
64
|
Requires-Dist: qrcode~=7.0
|
|
65
65
|
Requires-Dist: requests~=2.0
|
|
66
66
|
Requires-Dist: tzlocal~=5.0
|
|
67
|
-
Requires-Dist: urllib3==1.26.
|
|
67
|
+
Requires-Dist: urllib3==1.26.20
|
|
68
68
|
Requires-Dist: user-agents~=2.0
|
|
69
69
|
Requires-Dist: webob~=1.0
|
|
70
70
|
Provides-Extra: mailjet
|
|
@@ -22,6 +22,7 @@ dependencies = [
|
|
|
22
22
|
"gunicorn~=23.0",
|
|
23
23
|
"jinja2~=3.0",
|
|
24
24
|
"jsonschema~=4.0",
|
|
25
|
+
"logics-py==0.0.12",
|
|
25
26
|
"pillow~=10.0",
|
|
26
27
|
"pyotp~=2.0",
|
|
27
28
|
"pytz~=2023.0",
|
|
@@ -29,11 +30,11 @@ dependencies = [
|
|
|
29
30
|
"qrcode~=7.0",
|
|
30
31
|
"requests~=2.0",
|
|
31
32
|
"tzlocal~=5.0",
|
|
32
|
-
"urllib3==1.26.
|
|
33
|
+
"urllib3==1.26.20", # for appengine-python-standard (https://github.com/GoogleCloudPlatform/appengine-python-standard/blob/main/setup.py#L28)
|
|
33
34
|
"user-agents~=2.0",
|
|
34
35
|
"webob~=1.0",
|
|
35
36
|
]
|
|
36
|
-
requires-python = ">=3.
|
|
37
|
+
requires-python = ">=3.12"
|
|
37
38
|
authors = [
|
|
38
39
|
{ name = "Mausbrand Informationssysteme GmbH", email = "devs@viur.dev" },
|
|
39
40
|
]
|
|
@@ -49,9 +50,8 @@ classifiers = [
|
|
|
49
50
|
"Intended Audience :: Developers",
|
|
50
51
|
"License :: OSI Approved :: MIT License",
|
|
51
52
|
"Operating System :: OS Independent",
|
|
52
|
-
"Programming Language :: Python :: 3.10",
|
|
53
|
-
"Programming Language :: Python :: 3.11",
|
|
54
53
|
"Programming Language :: Python :: 3.12",
|
|
54
|
+
"Programming Language :: Python :: 3.13",
|
|
55
55
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
56
56
|
]
|
|
57
57
|
|
|
@@ -229,9 +229,10 @@ class FileBone(TreeLeafBone):
|
|
|
229
229
|
super().postSavedHandler(skel, boneName, key)
|
|
230
230
|
if (
|
|
231
231
|
current.request.get().is_deferred
|
|
232
|
-
and "derived" in current.request_data.get().get("__update_relations_bones")
|
|
232
|
+
and "derived" in (current.request_data.get().get("__update_relations_bones") or ())
|
|
233
233
|
):
|
|
234
234
|
return
|
|
235
|
+
|
|
235
236
|
from viur.core.skeleton import RelSkel, Skeleton
|
|
236
237
|
|
|
237
238
|
if issubclass(skel.skeletonCls, Skeleton):
|
|
@@ -31,7 +31,7 @@ class RecordBone(BaseBone):
|
|
|
31
31
|
using: 'viur.core.skeleton.RelSkel' = None,
|
|
32
32
|
**kwargs
|
|
33
33
|
):
|
|
34
|
-
from viur.core.skeleton import RelSkel
|
|
34
|
+
from viur.core.skeleton.relskel import RelSkel
|
|
35
35
|
if not issubclass(using, RelSkel):
|
|
36
36
|
raise ValueError("RecordBone requires for valid using-parameter (subclass of viur.core.skeleton.RelSkel)")
|
|
37
37
|
|
|
@@ -849,8 +849,11 @@ class Conf(ConfigType):
|
|
|
849
849
|
]
|
|
850
850
|
"""Backward compatibility flags; Remove to enforce new style."""
|
|
851
851
|
|
|
852
|
-
|
|
853
|
-
"""
|
|
852
|
+
db_query_external_limit: int = 100
|
|
853
|
+
"""Sets the maximum query limit allowed by external filters."""
|
|
854
|
+
|
|
855
|
+
db_query_default_limit: int = 30
|
|
856
|
+
"""Sets the default query limit for all queries."""
|
|
854
857
|
|
|
855
858
|
db_memcache_client: Client | None = None
|
|
856
859
|
"""If set, ViUR cache data for the db.get in the Memcache for faster access."""
|
|
@@ -1031,7 +1034,6 @@ class Conf(ConfigType):
|
|
|
1031
1034
|
"viur.cacheEnvironmentKey": "cache_environment_key",
|
|
1032
1035
|
"viur.contentSecurityPolicy": "content_security_policy",
|
|
1033
1036
|
"viur.bone.boolean.str2true": "bone_boolean_str2true",
|
|
1034
|
-
"viur.db.engine": "db_engine",
|
|
1035
1037
|
"viur.errorHandler": "error_handler",
|
|
1036
1038
|
"viur.static.embedSvg.path": "static_embed_svg_path",
|
|
1037
1039
|
"viur.file.hmacKey": "file_hmac_key",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from google.appengine.ext.testbed import Testbed
|
|
4
|
+
|
|
1
5
|
import logging
|
|
2
6
|
import sys
|
|
3
|
-
import time
|
|
4
7
|
import typing as t
|
|
5
8
|
|
|
6
9
|
from viur.core.config import conf
|
|
@@ -8,9 +11,9 @@ from .types import Entity, Key
|
|
|
8
11
|
|
|
9
12
|
MEMCACHE_MAX_BATCH_SIZE = 30
|
|
10
13
|
MEMCACHE_NAMESPACE = "viur-datastore"
|
|
11
|
-
MEMCACHE_TIMEOUT
|
|
12
|
-
MEMCACHE_MAX_SIZE = 1_000_000
|
|
13
|
-
|
|
14
|
+
MEMCACHE_TIMEOUT: int | datetime.timedelta = datetime.timedelta(days=1)
|
|
15
|
+
MEMCACHE_MAX_SIZE: t.Final[int] = 1_000_000
|
|
16
|
+
TESTBED = None
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
19
|
This Module controls the Interaction with the Memcache from Google
|
|
@@ -18,12 +21,8 @@ MEMCACHE_MAX_SIZE = 1_000_000
|
|
|
18
21
|
.. code-block:: python
|
|
19
22
|
# Example
|
|
20
23
|
from viur.core import conf
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
conf.db_memcache_client = Client()
|
|
24
|
-
else:
|
|
25
|
-
conf.db_memcache_client = db.cache.LocalMemcache()
|
|
26
|
-
|
|
24
|
+
from google.appengine.api.memcache import Client
|
|
25
|
+
conf.db_memcache_client = Client()
|
|
27
26
|
"""
|
|
28
27
|
|
|
29
28
|
__all__ = [
|
|
@@ -34,40 +33,63 @@ __all__ = [
|
|
|
34
33
|
"get",
|
|
35
34
|
"put",
|
|
36
35
|
"delete",
|
|
37
|
-
"
|
|
36
|
+
"flush",
|
|
38
37
|
]
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
def get(keys: t.Union[
|
|
40
|
+
def get(keys: t.Union[Key, list[Key]], namespace: t.Optional[str] = None) -> t.Union[Entity, list[Entity], None]:
|
|
42
41
|
"""
|
|
43
42
|
Reads data form the memcache.
|
|
44
43
|
:param keys: Unique identifier(s) for one or more entry(s).
|
|
45
|
-
:
|
|
44
|
+
:param namespace: Optional namespace to use.
|
|
45
|
+
:return: The entity (or None if it has not been found), or a list of entities.
|
|
46
46
|
"""
|
|
47
47
|
if not check_for_memcache():
|
|
48
|
-
return
|
|
49
|
-
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
namespace = namespace or MEMCACHE_NAMESPACE
|
|
51
|
+
single_request = not isinstance(keys, (list, tuple, set))
|
|
52
|
+
if single_request:
|
|
50
53
|
keys = [keys]
|
|
51
54
|
keys = [str(key) for key in keys] # Enforce that all keys are strings
|
|
52
|
-
|
|
55
|
+
cached_data = {}
|
|
56
|
+
result = []
|
|
53
57
|
try:
|
|
54
58
|
while keys:
|
|
55
|
-
|
|
59
|
+
cached_data |= conf.db_memcache_client.get_multi(keys[:MEMCACHE_MAX_BATCH_SIZE], namespace=namespace)
|
|
56
60
|
keys = keys[MEMCACHE_MAX_BATCH_SIZE:]
|
|
57
61
|
except Exception as e:
|
|
58
62
|
logging.error(f"""Failed to get keys form the memcache with {e=}""")
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
for key, value in cached_data.items():
|
|
64
|
+
entity = Entity(Key(key))
|
|
65
|
+
entity |= value
|
|
66
|
+
result.append(entity)
|
|
67
|
+
if single_request:
|
|
68
|
+
return result[0] if result else None
|
|
69
|
+
return result if result else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def put(
|
|
73
|
+
data: t.Union[Entity, t.Dict[Key, Entity], t.Iterable[Entity]],
|
|
74
|
+
namespace: t.Optional[str] = None,
|
|
75
|
+
timeout: t.Optional[t.Union[int, datetime.timedelta]] = None
|
|
76
|
+
) -> bool:
|
|
63
77
|
"""
|
|
64
78
|
Writes Data to the memcache.
|
|
65
|
-
|
|
66
79
|
:param data: Data to write
|
|
80
|
+
:param namespace: Optional namespace to use.
|
|
81
|
+
:param timeout: Optional timeout in seconds or a timedelta object.
|
|
82
|
+
:return: A boolean indicating success.
|
|
67
83
|
"""
|
|
68
84
|
if not check_for_memcache():
|
|
69
|
-
return
|
|
70
|
-
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
namespace = namespace or MEMCACHE_NAMESPACE
|
|
88
|
+
timeout = timeout or MEMCACHE_TIMEOUT
|
|
89
|
+
if isinstance(timeout, datetime.timedelta):
|
|
90
|
+
timeout = timeout.total_seconds()
|
|
91
|
+
|
|
92
|
+
if isinstance(data, (list, tuple, set)):
|
|
71
93
|
data = {item.key: item for item in data}
|
|
72
94
|
elif isinstance(data, Entity):
|
|
73
95
|
data = {data.key: data}
|
|
@@ -80,41 +102,48 @@ def put(data: t.Union[Entity, t.Dict[Key, Entity], t.List[Entity]]):
|
|
|
80
102
|
try:
|
|
81
103
|
while keys:
|
|
82
104
|
data_batch = {key: data[key] for key in keys[:MEMCACHE_MAX_BATCH_SIZE]}
|
|
83
|
-
conf.db_memcache_client.set_multi(data_batch, namespace=
|
|
105
|
+
conf.db_memcache_client.set_multi(data_batch, namespace=namespace, time=timeout)
|
|
84
106
|
keys = keys[MEMCACHE_MAX_BATCH_SIZE:]
|
|
107
|
+
return True
|
|
85
108
|
except Exception as e:
|
|
86
109
|
logging.error(f"""Failed to put data to the memcache with {e=}""")
|
|
110
|
+
return False
|
|
87
111
|
|
|
88
112
|
|
|
89
|
-
def delete(keys: t.Union[
|
|
113
|
+
def delete(keys: t.Union[Key, list[Key]], namespace: t.Optional[str] = None) -> None:
|
|
90
114
|
"""
|
|
91
115
|
Deletes an Entry form memcache.
|
|
92
|
-
|
|
93
116
|
:param keys: Unique identifier(s) for one or more entry(s).
|
|
117
|
+
:param namespace: Optional namespace to use.
|
|
94
118
|
"""
|
|
95
119
|
if not check_for_memcache():
|
|
96
|
-
return
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
namespace = namespace or MEMCACHE_NAMESPACE
|
|
97
123
|
if not isinstance(keys, list):
|
|
98
124
|
keys = [keys]
|
|
99
125
|
keys = [str(key) for key in keys] # Enforce that all keys are strings
|
|
100
126
|
try:
|
|
101
127
|
while keys:
|
|
102
|
-
conf.db_memcache_client.delete_multi(keys[:MEMCACHE_MAX_BATCH_SIZE], namespace=
|
|
128
|
+
conf.db_memcache_client.delete_multi(keys[:MEMCACHE_MAX_BATCH_SIZE], namespace=namespace)
|
|
103
129
|
keys = keys[MEMCACHE_MAX_BATCH_SIZE:]
|
|
104
130
|
except Exception as e:
|
|
105
131
|
logging.error(f"""Failed to delete keys form the memcache with {e=}""")
|
|
106
132
|
|
|
107
133
|
|
|
108
|
-
def flush():
|
|
134
|
+
def flush() -> bool:
|
|
109
135
|
"""
|
|
110
136
|
Deletes everything in memcache.
|
|
137
|
+
:return: A boolean indicating success.
|
|
111
138
|
"""
|
|
112
139
|
if not check_for_memcache():
|
|
113
|
-
return
|
|
140
|
+
return False
|
|
114
141
|
try:
|
|
115
142
|
conf.db_memcache_client.flush_all()
|
|
116
143
|
except Exception as e:
|
|
117
144
|
logging.error(f"""Failed to flush the memcache with {e=}""")
|
|
145
|
+
return False
|
|
146
|
+
return True
|
|
118
147
|
|
|
119
148
|
|
|
120
149
|
def get_size(obj: t.Any) -> int:
|
|
@@ -133,36 +162,13 @@ def check_for_memcache() -> bool:
|
|
|
133
162
|
if conf.db_memcache_client is None:
|
|
134
163
|
logging.warning(f"""conf.db_memcache_client is 'None'. It can not be used.""")
|
|
135
164
|
return False
|
|
165
|
+
init_testbed()
|
|
136
166
|
return True
|
|
137
167
|
|
|
138
168
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
res = {}
|
|
146
|
-
for key in keys:
|
|
147
|
-
if (data := self._data[namespace].get(key)) is not None:
|
|
148
|
-
if data["__lifetime__"]["last_seen"] + data["__lifetime__"]["timeout"] > time.time():
|
|
149
|
-
res[key] = data["__data__"]
|
|
150
|
-
else:
|
|
151
|
-
self._data[namespace].pop(key)
|
|
152
|
-
return res
|
|
153
|
-
|
|
154
|
-
def set_multi(self, data: t.Dict[str, t.Any], namespace: str = MEMCACHE_NAMESPACE, time: int = MEMCACHE_TIMEOUT):
|
|
155
|
-
self._data.setdefault(namespace, {})
|
|
156
|
-
for key, value in data.items():
|
|
157
|
-
self._data[namespace][key] = {}
|
|
158
|
-
self._data[namespace][key]["__data__"] = value
|
|
159
|
-
self._data[namespace][key]["__lifetime__"] = {"timeout": time, "last_seen": time.time()}
|
|
160
|
-
|
|
161
|
-
def delete_multi(self, keys: t.List[str] = [], namespace: str = MEMCACHE_NAMESPACE):
|
|
162
|
-
self._data.setdefault(namespace, {})
|
|
163
|
-
for key in keys:
|
|
164
|
-
if (data := self._data[namespace].get(key)) is not None:
|
|
165
|
-
self._data[namespace].pop(key)
|
|
166
|
-
|
|
167
|
-
def flush_all(self):
|
|
168
|
-
self._data.clear()
|
|
169
|
+
def init_testbed() -> None:
|
|
170
|
+
global TESTBED
|
|
171
|
+
if TESTBED is None and conf.instance.is_dev_server and conf.db_memcache_client:
|
|
172
|
+
TESTBED = Testbed()
|
|
173
|
+
TESTBED.activate()
|
|
174
|
+
TESTBED.init_memcache_stub()
|
|
@@ -151,9 +151,12 @@ class Query(object):
|
|
|
151
151
|
"""
|
|
152
152
|
if self.srcSkel is None:
|
|
153
153
|
raise NotImplementedError("This query has not been created using skel.all()")
|
|
154
|
+
|
|
154
155
|
if self.queries is None: # This query is already unsatisfiable and adding more constraints won't change this
|
|
155
156
|
return self
|
|
157
|
+
|
|
156
158
|
skel = self.srcSkel
|
|
159
|
+
|
|
157
160
|
if "search" in filters:
|
|
158
161
|
if self.srcSkel.customDatabaseAdapter and self.srcSkel.customDatabaseAdapter.providesFulltextSearch:
|
|
159
162
|
self._fulltextQueryString = str(filters["search"])
|
|
@@ -163,31 +166,50 @@ class Query(object):
|
|
|
163
166
|
% self.srcSkel.kindName
|
|
164
167
|
)
|
|
165
168
|
self.queries = None
|
|
169
|
+
|
|
166
170
|
bones = [(y, x) for x, y in skel.items()]
|
|
171
|
+
|
|
167
172
|
try:
|
|
168
173
|
# Process filters first
|
|
169
174
|
for bone, key in bones:
|
|
170
175
|
bone.buildDBFilter(key, skel, self, filters)
|
|
176
|
+
|
|
171
177
|
# Parse orders
|
|
172
178
|
for bone, key in bones:
|
|
173
179
|
bone.buildDBSort(key, skel, self, filters)
|
|
180
|
+
|
|
174
181
|
except RuntimeError as e:
|
|
175
182
|
logging.exception(e)
|
|
176
183
|
self.queries = None
|
|
177
184
|
return self
|
|
185
|
+
|
|
178
186
|
startCursor = endCursor = None
|
|
187
|
+
|
|
179
188
|
if "cursor" in filters and filters["cursor"] and filters["cursor"].lower() != "none":
|
|
180
189
|
startCursor = filters["cursor"]
|
|
190
|
+
|
|
181
191
|
if "endcursor" in filters and filters["endcursor"] and filters["endcursor"].lower() != "none":
|
|
182
192
|
endCursor = filters["endcursor"]
|
|
193
|
+
|
|
183
194
|
if startCursor or endCursor:
|
|
184
195
|
self.setCursor(startCursor, endCursor)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
|
|
197
|
+
if limit := filters.get("limit"):
|
|
198
|
+
try:
|
|
199
|
+
limit = int(limit)
|
|
200
|
+
|
|
201
|
+
# disallow limit beyond conf.db_query_external_limit
|
|
202
|
+
if limit > conf.db_query_external_limit:
|
|
203
|
+
limit = conf.db_query_external_limit
|
|
204
|
+
|
|
205
|
+
# forbid any limit < 0, which might bypass defaults
|
|
206
|
+
if limit < 0:
|
|
207
|
+
limit = 0
|
|
208
|
+
|
|
209
|
+
self.limit(limit)
|
|
210
|
+
except ValueError:
|
|
211
|
+
pass # ignore this
|
|
212
|
+
|
|
191
213
|
return self
|
|
192
214
|
|
|
193
215
|
def filter(self, prop: str, value: DATASTORE_BASE_TYPES | list[DATASTORE_BASE_TYPES]) -> t.Self:
|
|
@@ -373,19 +395,17 @@ class Query(object):
|
|
|
373
395
|
|
|
374
396
|
def limit(self, limit: int) -> t.Self:
|
|
375
397
|
"""
|
|
376
|
-
Sets the query limit to *
|
|
398
|
+
Sets the query limit to *limit* entities in the result.
|
|
377
399
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
:param limit: The maximum number of entities.
|
|
400
|
+
:param limit: The maximum number of entities per batch.
|
|
381
401
|
:returns: Returns the query itself for chaining.
|
|
382
402
|
"""
|
|
383
|
-
# TODO Add a check for the limit (<=100) ?
|
|
384
403
|
if isinstance(self.queries, QueryDefinition):
|
|
385
404
|
self.queries.limit = limit
|
|
386
405
|
elif isinstance(self.queries, list):
|
|
387
406
|
for query in self.queries:
|
|
388
407
|
query.limit = limit
|
|
408
|
+
|
|
389
409
|
return self
|
|
390
410
|
|
|
391
411
|
def distinctOn(self, keyList: t.List[str]) -> t.Self:
|
|
@@ -586,36 +606,46 @@ class Query(object):
|
|
|
586
606
|
if self._fulltextQueryString:
|
|
587
607
|
if utils.is_in_transaction():
|
|
588
608
|
raise ValueError("Can't run fulltextSearch inside transactions!") # InvalidStateError FIXME!
|
|
609
|
+
|
|
589
610
|
qryStr = self._fulltextQueryString
|
|
590
611
|
self._fulltextQueryString = None # Reset, so the adapter can still work with this query
|
|
591
612
|
res = self.srcSkel.customDatabaseAdapter.fulltextSearch(qryStr, self)
|
|
613
|
+
|
|
592
614
|
if not self.srcSkel.customDatabaseAdapter.fulltextSearchGuaranteesQueryConstrains:
|
|
593
615
|
# Search might yield results that are not included in the listfilter
|
|
594
616
|
if isinstance(self.queries, QueryDefinition): # Just one
|
|
595
617
|
res = [x for x in res if _entryMatchesQuery(x, self.queries.filters)]
|
|
596
618
|
else: # Multi-Query, must match at least one
|
|
597
619
|
res = [x for x in res if any([_entryMatchesQuery(x, y.filters) for y in self.queries])]
|
|
620
|
+
|
|
598
621
|
elif isinstance(self.queries, list):
|
|
622
|
+
limit = limit if limit >= 0 else self.queries[0].limit
|
|
623
|
+
|
|
599
624
|
# We have more than one query to run
|
|
600
625
|
if self._calculateInternalMultiQueryLimit:
|
|
601
|
-
limit = self._calculateInternalMultiQueryLimit(self, limit
|
|
626
|
+
limit = self._calculateInternalMultiQueryLimit(self, limit)
|
|
627
|
+
|
|
602
628
|
res = []
|
|
603
629
|
# We run all queries first (preventing multiple round-trips to the server)
|
|
604
630
|
for singleQuery in self.queries:
|
|
605
|
-
res.append(self._run_single_filter_query(singleQuery, limit
|
|
631
|
+
res.append(self._run_single_filter_query(singleQuery, limit))
|
|
632
|
+
|
|
606
633
|
# Wait for the actual results to arrive and convert the protobuffs to Entries
|
|
607
634
|
res = [self._fixKind(x) for x in res]
|
|
608
635
|
if self._customMultiQueryMerge:
|
|
609
636
|
# We have a custom merge function, use that
|
|
610
|
-
res = self._customMultiQueryMerge(self, res, limit
|
|
637
|
+
res = self._customMultiQueryMerge(self, res, limit)
|
|
611
638
|
else:
|
|
612
639
|
# We must merge (and sort) the results ourself
|
|
613
640
|
res = self._merge_multi_query_results(res)
|
|
641
|
+
|
|
614
642
|
else: # We have just one single query
|
|
615
643
|
res = self._fixKind(self._run_single_filter_query(
|
|
616
|
-
self.queries, limit if limit
|
|
644
|
+
self.queries, limit if limit >= 0 else self.queries.limit))
|
|
645
|
+
|
|
617
646
|
if res:
|
|
618
647
|
self._lastEntry = res[-1]
|
|
648
|
+
|
|
619
649
|
return res
|
|
620
650
|
|
|
621
651
|
def count(self, up_to: int = 2 ** 63 - 1) -> int:
|
|
@@ -648,7 +678,6 @@ class Query(object):
|
|
|
648
678
|
should be used.
|
|
649
679
|
|
|
650
680
|
:param limit: Limits the query to the defined maximum entities.
|
|
651
|
-
A maximum value of 100 entries can be fetched at once.
|
|
652
681
|
|
|
653
682
|
:raises: :exc:`BadFilterError` if a filter string is invalid
|
|
654
683
|
:raises: :exc:`BadValueError` if a filter value is invalid.
|
|
@@ -659,19 +688,17 @@ class Query(object):
|
|
|
659
688
|
|
|
660
689
|
if self.srcSkel is None:
|
|
661
690
|
raise NotImplementedError("This query has not been created using skel.all()")
|
|
662
|
-
# limit = limit if limit != -1 else self._limit
|
|
663
|
-
if limit != -1 and not (0 < limit <= 100):
|
|
664
|
-
logging.error(("Limit", limit))
|
|
665
|
-
raise NotImplementedError(
|
|
666
|
-
"This query is not limited! You must specify an upper bound using limit() between 1 and 100")
|
|
667
691
|
|
|
668
692
|
res = SkelList(self.srcSkel)
|
|
693
|
+
|
|
669
694
|
for entity in self.run(limit):
|
|
670
695
|
skel_instance = SkeletonInstance(self.srcSkel.skeletonCls, bone_map=self.srcSkel.boneMap)
|
|
671
696
|
skel_instance.dbEntity = entity
|
|
672
697
|
res.append(skel_instance)
|
|
698
|
+
|
|
673
699
|
res.getCursor = lambda: self.getCursor()
|
|
674
700
|
res.get_orders = lambda: self.get_orders()
|
|
701
|
+
|
|
675
702
|
return res
|
|
676
703
|
|
|
677
704
|
def iter(self) -> t.Iterator[Entity]:
|
|
@@ -7,7 +7,8 @@ import datetime
|
|
|
7
7
|
import enum
|
|
8
8
|
import typing as t
|
|
9
9
|
from contextvars import ContextVar
|
|
10
|
-
from dataclasses import dataclass
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from ..config import conf
|
|
11
12
|
|
|
12
13
|
from google.cloud.datastore import Entity as Datastore_entity, Key as Datastore_key
|
|
13
14
|
|
|
@@ -167,7 +168,7 @@ class QueryDefinition:
|
|
|
167
168
|
distinct: t.Optional[list[str]] = None
|
|
168
169
|
"""If set, a list of fields that we should return distinct values of"""
|
|
169
170
|
|
|
170
|
-
limit: int =
|
|
171
|
+
limit: int = field(init=False)
|
|
171
172
|
"""The maximum amount of entities that should be returned"""
|
|
172
173
|
|
|
173
174
|
startCursor: t.Optional[str] = None
|
|
@@ -178,3 +179,6 @@ class QueryDefinition:
|
|
|
178
179
|
|
|
179
180
|
currentCursor: t.Optional[str] = None
|
|
180
181
|
"""Will be set after this query has been run, pointing after the last entity returned"""
|
|
182
|
+
|
|
183
|
+
def __post_init__(self):
|
|
184
|
+
self.limit = conf.db_query_default_limit
|