tina4-python 3.10.54__tar.gz → 3.10.58__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.10.54 → tina4_python-3.10.58}/PKG-INFO +1 -1
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/__init__.py +2 -2
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/router.py +55 -6
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/server.py +4 -4
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/crud/__init__.py +27 -8
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/adapter.py +11 -8
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/connection.py +8 -0
- tina4_python-3.10.58/tina4_python/database/mongodb.py +751 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/orm/model.py +3 -3
- {tina4_python-3.10.54 → tina4_python-3.10.58}/.gitignore +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/README.md +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/pyproject.toml +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/Testing.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/events.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/request.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/response.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dev_reload.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -8,13 +8,13 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
|
|
|
8
8
|
|
|
9
9
|
One import, everything works.
|
|
10
10
|
"""
|
|
11
|
-
__version__ = "3.10.
|
|
11
|
+
__version__ = "3.10.58"
|
|
12
12
|
|
|
13
13
|
# ── Route decorators ──
|
|
14
14
|
from tina4_python.core.router import ( # noqa: E402, F401
|
|
15
15
|
get, post, put, patch, delete, any_method,
|
|
16
16
|
noauth, secured, cached, middleware, template,
|
|
17
|
-
Router,
|
|
17
|
+
Router, RouteGroup,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
# ── HTTP Constants ──
|
|
@@ -57,6 +57,51 @@ class RouteRef:
|
|
|
57
57
|
return self
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
class RouteGroup:
|
|
61
|
+
"""A group of routes sharing a common prefix and middleware.
|
|
62
|
+
|
|
63
|
+
Passed to the callback in Router.group(). Supports nesting.
|
|
64
|
+
|
|
65
|
+
Usage::
|
|
66
|
+
|
|
67
|
+
Router.group("/api", lambda group: [
|
|
68
|
+
group.get("/users", list_handler),
|
|
69
|
+
group.post("/users", create_handler),
|
|
70
|
+
group.group("/admin", lambda admin: [
|
|
71
|
+
admin.get("/stats", stats_handler),
|
|
72
|
+
], middleware=[admin_check]),
|
|
73
|
+
], middleware=[auth_check])
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, router_cls, prefix: str, middleware: list = None):
|
|
77
|
+
self._router = router_cls
|
|
78
|
+
self._prefix = prefix
|
|
79
|
+
self._middleware = middleware or []
|
|
80
|
+
|
|
81
|
+
def get(self, path: str, handler, **options) -> RouteRef:
|
|
82
|
+
return self._router.add("GET", self._prefix + path, handler, middleware=self._middleware, **options)
|
|
83
|
+
|
|
84
|
+
def post(self, path: str, handler, **options) -> RouteRef:
|
|
85
|
+
return self._router.add("POST", self._prefix + path, handler, middleware=self._middleware, **options)
|
|
86
|
+
|
|
87
|
+
def put(self, path: str, handler, **options) -> RouteRef:
|
|
88
|
+
return self._router.add("PUT", self._prefix + path, handler, middleware=self._middleware, **options)
|
|
89
|
+
|
|
90
|
+
def patch(self, path: str, handler, **options) -> RouteRef:
|
|
91
|
+
return self._router.add("PATCH", self._prefix + path, handler, middleware=self._middleware, **options)
|
|
92
|
+
|
|
93
|
+
def delete(self, path: str, handler, **options) -> RouteRef:
|
|
94
|
+
return self._router.add("DELETE", self._prefix + path, handler, middleware=self._middleware, **options)
|
|
95
|
+
|
|
96
|
+
def any(self, path: str, handler, **options) -> RouteRef:
|
|
97
|
+
return self._router.add("ANY", self._prefix + path, handler, middleware=self._middleware, **options)
|
|
98
|
+
|
|
99
|
+
def group(self, prefix: str, callback, middleware=None):
|
|
100
|
+
merged = list(self._middleware) + (middleware or [])
|
|
101
|
+
nested = RouteGroup(self._router, self._prefix + prefix.rstrip("/"), merged)
|
|
102
|
+
callback(nested)
|
|
103
|
+
|
|
104
|
+
|
|
60
105
|
class Router:
|
|
61
106
|
"""Route registry and matcher."""
|
|
62
107
|
|
|
@@ -68,14 +113,17 @@ class Router:
|
|
|
68
113
|
def group(cls, prefix: str, callback, middleware=None):
|
|
69
114
|
"""Register routes with a shared prefix and optional middleware.
|
|
70
115
|
|
|
71
|
-
|
|
72
|
-
|
|
116
|
+
The callback receives a RouteGroup object with get/post/put/patch/
|
|
117
|
+
delete/any/group methods for registering routes under the prefix.
|
|
73
118
|
|
|
74
119
|
Usage::
|
|
75
120
|
|
|
76
|
-
Router.group("/api", lambda: [
|
|
77
|
-
|
|
78
|
-
|
|
121
|
+
Router.group("/api", lambda group: [
|
|
122
|
+
group.get("/users", list_handler),
|
|
123
|
+
group.post("/users", create_handler),
|
|
124
|
+
group.group("/admin", lambda admin: [
|
|
125
|
+
admin.get("/stats", stats_handler),
|
|
126
|
+
], middleware=[admin_check]),
|
|
79
127
|
], middleware=[auth_check])
|
|
80
128
|
"""
|
|
81
129
|
prev_prefix = cls._group_prefix
|
|
@@ -85,7 +133,8 @@ class Router:
|
|
|
85
133
|
cls._group_middleware = prev_middleware + (middleware or [])
|
|
86
134
|
|
|
87
135
|
try:
|
|
88
|
-
|
|
136
|
+
group = RouteGroup(cls, cls._group_prefix, list(cls._group_middleware))
|
|
137
|
+
callback(group)
|
|
89
138
|
finally:
|
|
90
139
|
cls._group_prefix = prev_prefix
|
|
91
140
|
cls._group_middleware = prev_middleware
|
|
@@ -1238,7 +1238,7 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: i
|
|
|
1238
1238
|
color = "\033[34m" if sys.stdout.isatty() else ""
|
|
1239
1239
|
reset = "\033[0m" if sys.stdout.isatty() else ""
|
|
1240
1240
|
|
|
1241
|
-
ai_port_line = f"\n
|
|
1241
|
+
ai_port_line = f"\n Test Port: http://{display}:{ai_port} (stable — no hot-reload)" if ai_port else ""
|
|
1242
1242
|
|
|
1243
1243
|
banner = f"""{color}
|
|
1244
1244
|
______ _ __ __
|
|
@@ -1328,7 +1328,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
|
|
|
1328
1328
|
|
|
1329
1329
|
# Determine AI dev port (port+1) when debug is on and not suppressed
|
|
1330
1330
|
_no_ai_port = os.environ.get("TINA4_NO_AI_PORT", "").lower() in ("true", "1", "yes")
|
|
1331
|
-
_ai_port = (port +
|
|
1331
|
+
_ai_port = (port + 1000) if (is_debug and not _no_ai_port) else None
|
|
1332
1332
|
|
|
1333
1333
|
# Banner — printed directly to stdout, not through the logger
|
|
1334
1334
|
_print_banner(host, port, server_name, ai_port=_ai_port)
|
|
@@ -1336,7 +1336,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
|
|
|
1336
1336
|
display = "localhost" if host in ("0.0.0.0", "::") else host
|
|
1337
1337
|
Log.info(f"Server started http://{display}:{port} ({server_name})")
|
|
1338
1338
|
if _ai_port:
|
|
1339
|
-
Log.info(f"
|
|
1339
|
+
Log.info(f"Test port: http://{display}:{_ai_port} (stable — no hot-reload)")
|
|
1340
1340
|
|
|
1341
1341
|
# Open browser after a short delay (unless --no-browser)
|
|
1342
1342
|
_skip_browser = no_browser or os.environ.get("TINA4_NO_BROWSER", "").lower() in ("true", "1", "yes")
|
|
@@ -1464,7 +1464,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
|
|
|
1464
1464
|
|
|
1465
1465
|
server = await start_server(_handle_connection, host, port)
|
|
1466
1466
|
|
|
1467
|
-
#
|
|
1467
|
+
# Test port (port + 1000) — stable, no live-reload WebSocket
|
|
1468
1468
|
ai_server = None
|
|
1469
1469
|
if _ai_port:
|
|
1470
1470
|
try:
|
|
@@ -15,7 +15,7 @@ Discovers ORM models and registers CRUD routes automatically.
|
|
|
15
15
|
|
|
16
16
|
Generated endpoints per model:
|
|
17
17
|
|
|
18
|
-
GET /api/{table_name} — list with pagination (limit,
|
|
18
|
+
GET /api/{table_name} — list with pagination (limit, offset; also accepts page, per_page)
|
|
19
19
|
GET /api/{table_name}/{id} — get single record by primary key
|
|
20
20
|
POST /api/{table_name} — create new record
|
|
21
21
|
PUT /api/{table_name}/{id} — update record by primary key
|
|
@@ -94,18 +94,37 @@ class AutoCrud:
|
|
|
94
94
|
# ── GET /api/{table} — list with pagination ──────────────
|
|
95
95
|
async def list_handler(request, response, _cls=model_class):
|
|
96
96
|
try:
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
# Primary names: limit / offset
|
|
98
|
+
# Compat names: per_page / page (PHP/Ruby/Node style)
|
|
99
|
+
limit = int(request.params.get("limit", request.params.get("per_page", 10)))
|
|
100
|
+
offset = int(request.params.get("offset", 0))
|
|
101
|
+
# page/per_page compat: if page is provided, derive offset from it
|
|
102
|
+
if "page" in request.params and "offset" not in request.params:
|
|
103
|
+
page = int(request.params.get("page", 1))
|
|
104
|
+
per_page = int(request.params.get("per_page", limit))
|
|
105
|
+
offset = (page - 1) * per_page
|
|
106
|
+
limit = per_page
|
|
107
|
+
else:
|
|
108
|
+
page = (offset // limit) + 1 if limit else 1
|
|
99
109
|
except (ValueError, TypeError):
|
|
100
110
|
limit = 10
|
|
101
|
-
|
|
111
|
+
offset = 0
|
|
112
|
+
page = 1
|
|
102
113
|
|
|
103
|
-
records, total = _cls.all(limit=limit, skip=
|
|
114
|
+
records, total = _cls.all(limit=limit, skip=offset)
|
|
115
|
+
total_pages = max(1, -(-total // limit)) if limit else 1
|
|
116
|
+
data = [r.to_dict() for r in records]
|
|
104
117
|
return response({
|
|
105
|
-
"
|
|
106
|
-
"
|
|
118
|
+
"records": data, # standard name
|
|
119
|
+
"data": data, # backwards compat
|
|
120
|
+
"count": total, # standard name
|
|
121
|
+
"total": total, # backwards compat
|
|
107
122
|
"limit": limit,
|
|
108
|
-
"
|
|
123
|
+
"offset": offset,
|
|
124
|
+
"page": page,
|
|
125
|
+
"per_page": limit, # backwards compat
|
|
126
|
+
"totalPages": total_pages, # camelCase standard
|
|
127
|
+
"total_pages": total_pages, # backwards compat
|
|
109
128
|
})
|
|
110
129
|
|
|
111
130
|
list_handler.__name__ = f"autocrud_list_{table}"
|
|
@@ -33,16 +33,19 @@ class DatabaseResult:
|
|
|
33
33
|
|
|
34
34
|
def to_paginate(self, page: int = 1, per_page: int = 20) -> dict:
|
|
35
35
|
total_pages = max(1, -(-self.count // per_page)) # ceil division
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
offset = (page - 1) * per_page
|
|
37
|
+
data = self.records[offset:offset + per_page]
|
|
38
38
|
return {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
39
|
+
"records": data, # standard name
|
|
40
|
+
"data": data, # backwards compat (PHP/Ruby/Node)
|
|
41
|
+
"count": self.count, # standard name
|
|
42
|
+
"total": self.count, # backwards compat
|
|
43
|
+
"limit": per_page, # standard name
|
|
44
|
+
"offset": offset, # standard name
|
|
41
45
|
"page": page,
|
|
42
|
-
"per_page": per_page,
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"has_prev": page > 1,
|
|
46
|
+
"per_page": per_page, # backwards compat
|
|
47
|
+
"totalPages": total_pages, # camelCase standard
|
|
48
|
+
"total_pages": total_pages, # backwards compat
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
def column_info(self) -> list[dict]:
|
|
@@ -123,6 +123,14 @@ register_driver("sqlserver", MSSQLAdapter)
|
|
|
123
123
|
from tina4_python.database.firebird import FirebirdAdapter
|
|
124
124
|
register_driver("firebird", FirebirdAdapter)
|
|
125
125
|
|
|
126
|
+
# Register MongoDB (pymongo — optional)
|
|
127
|
+
try:
|
|
128
|
+
from tina4_python.database.mongodb import MongoDBAdapter
|
|
129
|
+
register_driver("mongodb", MongoDBAdapter)
|
|
130
|
+
register_driver("pymongo", MongoDBAdapter)
|
|
131
|
+
except ImportError:
|
|
132
|
+
pass
|
|
133
|
+
|
|
126
134
|
|
|
127
135
|
class Database:
|
|
128
136
|
"""Database connection manager.
|