tina4-python 3.8.2__tar.gz → 3.8.7__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.
- {tina4_python-3.8.2 → tina4_python-3.8.7}/.gitignore +4 -1
- {tina4_python-3.8.2 → tina4_python-3.8.7}/PKG-INFO +9 -10
- {tina4_python-3.8.2 → tina4_python-3.8.7}/README.md +8 -9
- {tina4_python-3.8.2 → tina4_python-3.8.7}/pyproject.toml +1 -1
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/__init__.py +1 -1
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/server.py +52 -8
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/orm/model.py +13 -0
- tina4_python-3.8.7/tina4_python/query_builder/__init__.py +280 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/session/__init__.py +4 -1
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/Testing.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/events.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/request.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/response.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/core/router.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/dev_reload.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -54,7 +54,8 @@ __pycache__/
|
|
|
54
54
|
/benchmarks/secrets/
|
|
55
55
|
/benchmarks/results/
|
|
56
56
|
/benchmarks/__pycache__/
|
|
57
|
-
.claude
|
|
57
|
+
.claude/*
|
|
58
|
+
!.claude/skills/
|
|
58
59
|
*.c
|
|
59
60
|
*.so
|
|
60
61
|
build/
|
|
@@ -63,3 +64,5 @@ demo/.env
|
|
|
63
64
|
example/.env
|
|
64
65
|
demo/data/*.db
|
|
65
66
|
demo/logs/*.log
|
|
67
|
+
.claude/settings.local.json
|
|
68
|
+
.claude/worktrees/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 3.8.
|
|
3
|
+
Version: 3.8.7
|
|
4
4
|
Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
|
|
5
5
|
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -41,6 +41,14 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
Laravel joy. Python speed. 10x less code. Zero third-party dependencies.
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
|
+
<p align="center">
|
|
45
|
+
<a href="https://pypi.org/project/tina4-python/"><img src="https://img.shields.io/pypi/v/tina4-python?color=7b1fa2&label=PyPI" alt="PyPI"></a>
|
|
46
|
+
<img src="https://img.shields.io/badge/tests-1%2C791%20passing-brightgreen" alt="Tests">
|
|
47
|
+
<img src="https://img.shields.io/badge/features-38-blue" alt="Features">
|
|
48
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
49
|
+
<a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
44
52
|
<p align="center">
|
|
45
53
|
<a href="https://tina4.com">Documentation</a> •
|
|
46
54
|
<a href="#getting-started">Getting Started</a> •
|
|
@@ -49,15 +57,6 @@ Description-Content-Type: text/markdown
|
|
|
49
57
|
<a href="https://tina4.com">tina4.com</a>
|
|
50
58
|
</p>
|
|
51
59
|
|
|
52
|
-
<p align="center">
|
|
53
|
-
<img src="https://img.shields.io/badge/version-3.0.0-blue" alt="Version 3.0.0">
|
|
54
|
-
<img src="https://img.shields.io/badge/tests-1633%20passing-brightgreen" alt="Tests">
|
|
55
|
-
<img src="https://img.shields.io/badge/carbonah-A%2B%20rated-00cc44" alt="Carbonah A+">
|
|
56
|
-
<img src="https://img.shields.io/badge/zero--dep-core-blue" alt="Zero Dependencies">
|
|
57
|
-
<img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+">
|
|
58
|
-
<img src="https://img.shields.io/badge/license-MIT-lightgrey" alt="MIT License">
|
|
59
|
-
</p>
|
|
60
|
-
|
|
61
60
|
---
|
|
62
61
|
|
|
63
62
|
## Quick Start
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
Laravel joy. Python speed. 10x less code. Zero third-party dependencies.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://pypi.org/project/tina4-python/"><img src="https://img.shields.io/pypi/v/tina4-python?color=7b1fa2&label=PyPI" alt="PyPI"></a>
|
|
14
|
+
<img src="https://img.shields.io/badge/tests-1%2C791%20passing-brightgreen" alt="Tests">
|
|
15
|
+
<img src="https://img.shields.io/badge/features-38-blue" alt="Features">
|
|
16
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
17
|
+
<a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
12
20
|
<p align="center">
|
|
13
21
|
<a href="https://tina4.com">Documentation</a> •
|
|
14
22
|
<a href="#getting-started">Getting Started</a> •
|
|
@@ -17,15 +25,6 @@
|
|
|
17
25
|
<a href="https://tina4.com">tina4.com</a>
|
|
18
26
|
</p>
|
|
19
27
|
|
|
20
|
-
<p align="center">
|
|
21
|
-
<img src="https://img.shields.io/badge/version-3.0.0-blue" alt="Version 3.0.0">
|
|
22
|
-
<img src="https://img.shields.io/badge/tests-1633%20passing-brightgreen" alt="Tests">
|
|
23
|
-
<img src="https://img.shields.io/badge/carbonah-A%2B%20rated-00cc44" alt="Carbonah A+">
|
|
24
|
-
<img src="https://img.shields.io/badge/zero--dep-core-blue" alt="Zero Dependencies">
|
|
25
|
-
<img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+">
|
|
26
|
-
<img src="https://img.shields.io/badge/license-MIT-lightgrey" alt="MIT License">
|
|
27
|
-
</p>
|
|
28
|
-
|
|
29
28
|
---
|
|
30
29
|
|
|
31
30
|
## Quick Start
|
|
@@ -644,6 +644,22 @@ async def app(scope: dict, receive, send):
|
|
|
644
644
|
request_id = request.headers.get("x-request-id", str(uuid.uuid4())[:8])
|
|
645
645
|
set_request_id(request_id)
|
|
646
646
|
|
|
647
|
+
# Auto-start session — lazy, reads cookie, saves on response
|
|
648
|
+
try:
|
|
649
|
+
from tina4_python.session import Session
|
|
650
|
+
cookie_header = dict(scope.get("headers", [])).get(b"cookie", b"").decode()
|
|
651
|
+
sid_match = None
|
|
652
|
+
for part in cookie_header.split(";"):
|
|
653
|
+
part = part.strip()
|
|
654
|
+
if part.startswith("tina4_session="):
|
|
655
|
+
sid_match = part.split("=", 1)[1]
|
|
656
|
+
break
|
|
657
|
+
sess = Session()
|
|
658
|
+
sess.start(sid_match)
|
|
659
|
+
request.session = sess
|
|
660
|
+
except Exception:
|
|
661
|
+
pass # Session module not available — session stays None
|
|
662
|
+
|
|
647
663
|
response = Response()
|
|
648
664
|
response.header("x-request-id", request_id)
|
|
649
665
|
|
|
@@ -764,17 +780,34 @@ async def app(scope: dict, receive, send):
|
|
|
764
780
|
_sig = inspect.signature(route["handler"])
|
|
765
781
|
_params = list(_sig.parameters.values())
|
|
766
782
|
_pcount = len(_params)
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
783
|
+
|
|
784
|
+
# Build args: inject path params by name, then request/response
|
|
785
|
+
_args = []
|
|
786
|
+
_remaining = []
|
|
787
|
+
for p in _params:
|
|
788
|
+
name = p.name
|
|
789
|
+
if name in params:
|
|
790
|
+
_args.append(params[name])
|
|
791
|
+
else:
|
|
792
|
+
_remaining.append(p)
|
|
793
|
+
|
|
794
|
+
# Append request/response for remaining positional params
|
|
795
|
+
if len(_remaining) == 0:
|
|
796
|
+
pass # All params were path params
|
|
797
|
+
elif len(_remaining) == 1:
|
|
798
|
+
_ann = _remaining[0].annotation
|
|
772
799
|
if _ann is Request or (isinstance(_ann, str) and _ann in ("Request", "request")):
|
|
773
|
-
|
|
800
|
+
_args.append(request)
|
|
774
801
|
else:
|
|
775
|
-
|
|
802
|
+
_args.append(response)
|
|
803
|
+
elif len(_remaining) >= 2:
|
|
804
|
+
_args.append(request)
|
|
805
|
+
_args.append(response)
|
|
806
|
+
|
|
807
|
+
if _pcount == 0:
|
|
808
|
+
result = await route["handler"]()
|
|
776
809
|
else:
|
|
777
|
-
result = await route["handler"](
|
|
810
|
+
result = await route["handler"](*_args)
|
|
778
811
|
if isinstance(result, Response):
|
|
779
812
|
response = result
|
|
780
813
|
except Exception as e:
|
|
@@ -871,6 +904,17 @@ async def app(scope: dict, receive, send):
|
|
|
871
904
|
# ETag check — 304 Not Modified
|
|
872
905
|
if_none_match = request.headers.get("if-none-match", "")
|
|
873
906
|
|
|
907
|
+
# Save session and set cookie if session was used
|
|
908
|
+
if request.session is not None:
|
|
909
|
+
try:
|
|
910
|
+
request.session.save()
|
|
911
|
+
sid = request.session.session_id if hasattr(request.session, 'session_id') else getattr(request.session, 'id', None)
|
|
912
|
+
if sid:
|
|
913
|
+
ttl = int(os.environ.get("TINA4_SESSION_TTL", "3600"))
|
|
914
|
+
response.header("set-cookie", f"tina4_session={sid}; Path=/; HttpOnly; SameSite=Lax; Max-Age={ttl}")
|
|
915
|
+
except Exception:
|
|
916
|
+
pass
|
|
917
|
+
|
|
874
918
|
# Build and send response
|
|
875
919
|
accept_encoding = request.headers.get("accept-encoding", "")
|
|
876
920
|
headers = response.build_headers(accept_encoding)
|
|
@@ -138,6 +138,19 @@ class ORM(metaclass=ORMMeta):
|
|
|
138
138
|
data[db_col] = getattr(self, name)
|
|
139
139
|
return data
|
|
140
140
|
|
|
141
|
+
@classmethod
|
|
142
|
+
def query(cls) -> "QueryBuilder":
|
|
143
|
+
"""Create a fluent QueryBuilder pre-configured for this model's table and database.
|
|
144
|
+
|
|
145
|
+
Usage:
|
|
146
|
+
results = User.query().where("active = ?", [1]).order_by("name").get()
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
A QueryBuilder instance bound to this model's table and database.
|
|
150
|
+
"""
|
|
151
|
+
from tina4_python.query_builder import QueryBuilder
|
|
152
|
+
return QueryBuilder.from_table(cls._get_table(), cls._get_db())
|
|
153
|
+
|
|
141
154
|
@classmethod
|
|
142
155
|
def _get_table(cls) -> str:
|
|
143
156
|
"""Get table name — defaults to lowercase class name + 's'."""
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Tina4 QueryBuilder — Fluent SQL query builder.
|
|
2
|
+
"""
|
|
3
|
+
Fluent SQL query builder for Tina4 Python.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
# Standalone
|
|
7
|
+
result = QueryBuilder.from_table("users", db) \\
|
|
8
|
+
.select("id", "name") \\
|
|
9
|
+
.where("active = ?", [1]) \\
|
|
10
|
+
.order_by("name ASC") \\
|
|
11
|
+
.limit(10) \\
|
|
12
|
+
.get()
|
|
13
|
+
|
|
14
|
+
# From ORM model
|
|
15
|
+
result = User.query() \\
|
|
16
|
+
.where("age > ?", [18]) \\
|
|
17
|
+
.order_by("name") \\
|
|
18
|
+
.get()
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class QueryBuilder:
|
|
23
|
+
"""Fluent SQL query builder that produces and executes SQL statements."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, table: str, db=None):
|
|
26
|
+
"""Private-ish constructor. Use from_table() or ORM.query()."""
|
|
27
|
+
self._table = table
|
|
28
|
+
self._db = db
|
|
29
|
+
self._columns: list[str] = ["*"]
|
|
30
|
+
self._wheres: list[tuple[str, str]] = []
|
|
31
|
+
self._params: list = []
|
|
32
|
+
self._joins: list[str] = []
|
|
33
|
+
self._group_by_cols: list[str] = []
|
|
34
|
+
self._havings: list[str] = []
|
|
35
|
+
self._having_params: list = []
|
|
36
|
+
self._order_by_cols: list[str] = []
|
|
37
|
+
self._limit_val: int | None = None
|
|
38
|
+
self._offset_val: int | None = None
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def from_table(table_name: str, db=None) -> "QueryBuilder":
|
|
42
|
+
"""Create a QueryBuilder for a table.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
table_name: The database table name.
|
|
46
|
+
db: Optional database connection (Database or adapter).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A new QueryBuilder instance.
|
|
50
|
+
"""
|
|
51
|
+
return QueryBuilder(table_name, db)
|
|
52
|
+
|
|
53
|
+
def select(self, *columns: str) -> "QueryBuilder":
|
|
54
|
+
"""Set the columns to select.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
*columns: Column names (default is '*').
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
self for chaining.
|
|
61
|
+
"""
|
|
62
|
+
if columns:
|
|
63
|
+
self._columns = list(columns)
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def where(self, condition: str, params: list = None) -> "QueryBuilder":
|
|
67
|
+
"""Add a WHERE condition with AND.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
condition: SQL condition with ? placeholders.
|
|
71
|
+
params: Parameter values for placeholders.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
self for chaining.
|
|
75
|
+
"""
|
|
76
|
+
self._wheres.append(("AND", condition))
|
|
77
|
+
if params:
|
|
78
|
+
self._params.extend(params)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def or_where(self, condition: str, params: list = None) -> "QueryBuilder":
|
|
82
|
+
"""Add a WHERE condition with OR.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
condition: SQL condition with ? placeholders.
|
|
86
|
+
params: Parameter values for placeholders.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
self for chaining.
|
|
90
|
+
"""
|
|
91
|
+
self._wheres.append(("OR", condition))
|
|
92
|
+
if params:
|
|
93
|
+
self._params.extend(params)
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def join(self, table: str, on_clause: str) -> "QueryBuilder":
|
|
97
|
+
"""Add an INNER JOIN.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
table: Table to join.
|
|
101
|
+
on_clause: Join condition.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
self for chaining.
|
|
105
|
+
"""
|
|
106
|
+
self._joins.append(f"INNER JOIN {table} ON {on_clause}")
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
def left_join(self, table: str, on_clause: str) -> "QueryBuilder":
|
|
110
|
+
"""Add a LEFT JOIN.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
table: Table to join.
|
|
114
|
+
on_clause: Join condition.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
self for chaining.
|
|
118
|
+
"""
|
|
119
|
+
self._joins.append(f"LEFT JOIN {table} ON {on_clause}")
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
def group_by(self, column: str) -> "QueryBuilder":
|
|
123
|
+
"""Add a GROUP BY column.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
column: Column name to group by.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
self for chaining.
|
|
130
|
+
"""
|
|
131
|
+
self._group_by_cols.append(column)
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def having(self, expression: str, params: list = None) -> "QueryBuilder":
|
|
135
|
+
"""Add a HAVING clause.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
expression: HAVING expression with ? placeholders.
|
|
139
|
+
params: Parameter values.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
self for chaining.
|
|
143
|
+
"""
|
|
144
|
+
self._havings.append(expression)
|
|
145
|
+
if params:
|
|
146
|
+
self._having_params.extend(params)
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def order_by(self, expression: str) -> "QueryBuilder":
|
|
150
|
+
"""Add an ORDER BY clause.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
expression: Column and direction (e.g. "name ASC").
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
self for chaining.
|
|
157
|
+
"""
|
|
158
|
+
self._order_by_cols.append(expression)
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def limit(self, count: int, offset: int = None) -> "QueryBuilder":
|
|
162
|
+
"""Set LIMIT and optional OFFSET.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
count: Maximum rows to return.
|
|
166
|
+
offset: Number of rows to skip.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
self for chaining.
|
|
170
|
+
"""
|
|
171
|
+
self._limit_val = count
|
|
172
|
+
if offset is not None:
|
|
173
|
+
self._offset_val = offset
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
def to_sql(self) -> str:
|
|
177
|
+
"""Build and return the SQL string without executing.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
The constructed SQL query string.
|
|
181
|
+
"""
|
|
182
|
+
sql = f"SELECT {', '.join(self._columns)} FROM {self._table}"
|
|
183
|
+
|
|
184
|
+
if self._joins:
|
|
185
|
+
sql += " " + " ".join(self._joins)
|
|
186
|
+
|
|
187
|
+
if self._wheres:
|
|
188
|
+
sql += " WHERE " + self._build_where()
|
|
189
|
+
|
|
190
|
+
if self._group_by_cols:
|
|
191
|
+
sql += " GROUP BY " + ", ".join(self._group_by_cols)
|
|
192
|
+
|
|
193
|
+
if self._havings:
|
|
194
|
+
sql += " HAVING " + " AND ".join(self._havings)
|
|
195
|
+
|
|
196
|
+
if self._order_by_cols:
|
|
197
|
+
sql += " ORDER BY " + ", ".join(self._order_by_cols)
|
|
198
|
+
|
|
199
|
+
return sql
|
|
200
|
+
|
|
201
|
+
def get(self):
|
|
202
|
+
"""Execute the query and return the DatabaseResult.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
DatabaseResult from db.fetch().
|
|
206
|
+
"""
|
|
207
|
+
self._ensure_db()
|
|
208
|
+
sql = self.to_sql()
|
|
209
|
+
all_params = self._params + self._having_params
|
|
210
|
+
|
|
211
|
+
return self._db.fetch(
|
|
212
|
+
sql,
|
|
213
|
+
all_params or None,
|
|
214
|
+
self._limit_val if self._limit_val is not None else 100,
|
|
215
|
+
self._offset_val if self._offset_val is not None else 0,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def first(self) -> dict | None:
|
|
219
|
+
"""Execute the query and return a single row.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
A dict for the first matching row, or None.
|
|
223
|
+
"""
|
|
224
|
+
self._ensure_db()
|
|
225
|
+
sql = self.to_sql()
|
|
226
|
+
all_params = self._params + self._having_params
|
|
227
|
+
|
|
228
|
+
return self._db.fetch_one(sql, all_params or None)
|
|
229
|
+
|
|
230
|
+
def count(self) -> int:
|
|
231
|
+
"""Execute the query and return the row count.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Number of matching rows.
|
|
235
|
+
"""
|
|
236
|
+
self._ensure_db()
|
|
237
|
+
|
|
238
|
+
# Build a count query by replacing columns
|
|
239
|
+
original = self._columns
|
|
240
|
+
self._columns = ["COUNT(*) as cnt"]
|
|
241
|
+
sql = self.to_sql()
|
|
242
|
+
self._columns = original
|
|
243
|
+
|
|
244
|
+
all_params = self._params + self._having_params
|
|
245
|
+
|
|
246
|
+
row = self._db.fetch_one(sql, all_params or None)
|
|
247
|
+
if row is None:
|
|
248
|
+
return 0
|
|
249
|
+
# Handle case-insensitive column names
|
|
250
|
+
return int(row.get("cnt", row.get("CNT", 0)))
|
|
251
|
+
|
|
252
|
+
def exists(self) -> bool:
|
|
253
|
+
"""Check whether any matching rows exist.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
True if at least one row matches.
|
|
257
|
+
"""
|
|
258
|
+
return self.count() > 0
|
|
259
|
+
|
|
260
|
+
# -- Private helpers --
|
|
261
|
+
|
|
262
|
+
def _build_where(self) -> str:
|
|
263
|
+
"""Build the WHERE clause from accumulated conditions."""
|
|
264
|
+
parts = []
|
|
265
|
+
for i, (connector, condition) in enumerate(self._wheres):
|
|
266
|
+
if i == 0:
|
|
267
|
+
parts.append(condition)
|
|
268
|
+
else:
|
|
269
|
+
parts.append(f"{connector} {condition}")
|
|
270
|
+
return " ".join(parts)
|
|
271
|
+
|
|
272
|
+
def _ensure_db(self):
|
|
273
|
+
"""Ensure a database connection is available."""
|
|
274
|
+
if self._db is None:
|
|
275
|
+
# Try to use the global ORM database
|
|
276
|
+
from tina4_python.orm.model import _database
|
|
277
|
+
if _database is not None:
|
|
278
|
+
self._db = _database
|
|
279
|
+
else:
|
|
280
|
+
raise RuntimeError("QueryBuilder: No database connection provided.")
|
|
@@ -208,11 +208,14 @@ class Session:
|
|
|
208
208
|
self._data[key] = value
|
|
209
209
|
self._dirty = True
|
|
210
210
|
|
|
211
|
-
def
|
|
211
|
+
def delete(self, key: str):
|
|
212
212
|
"""Remove a session key."""
|
|
213
213
|
self._data.pop(key, None)
|
|
214
214
|
self._dirty = True
|
|
215
215
|
|
|
216
|
+
# Alias for backward compatibility
|
|
217
|
+
unset = delete
|
|
218
|
+
|
|
216
219
|
def has(self, key: str) -> bool:
|
|
217
220
|
return key in self._data
|
|
218
221
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/database/src/routes/api/gallery_db.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/gallery/templates/src/routes/gallery_page.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.8.2 → tina4_python-3.8.7}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|