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.
Files changed (144) hide show
  1. {tina4_python-3.10.54 → tina4_python-3.10.58}/PKG-INFO +1 -1
  2. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/__init__.py +2 -2
  3. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/router.py +55 -6
  4. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/server.py +4 -4
  5. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/crud/__init__.py +27 -8
  6. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/adapter.py +11 -8
  7. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/connection.py +8 -0
  8. tina4_python-3.10.58/tina4_python/database/mongodb.py +751 -0
  9. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/orm/model.py +3 -3
  10. {tina4_python-3.10.54 → tina4_python-3.10.58}/.gitignore +0 -0
  11. {tina4_python-3.10.54 → tina4_python-3.10.58}/README.md +0 -0
  12. {tina4_python-3.10.54 → tina4_python-3.10.58}/pyproject.toml +0 -0
  13. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/CLAUDE.md +0 -0
  14. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/HtmlElement.py +0 -0
  15. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/Testing.py +0 -0
  16. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/ai/__init__.py +0 -0
  17. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/api/__init__.py +0 -0
  18. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/auth/__init__.py +0 -0
  19. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/cache/__init__.py +0 -0
  20. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/cli/__init__.py +0 -0
  21. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/container/__init__.py +0 -0
  22. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/__init__.py +0 -0
  23. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/cache.py +0 -0
  24. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/constants.py +0 -0
  25. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/events.py +0 -0
  26. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/middleware.py +0 -0
  27. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/request.py +0 -0
  28. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/core/response.py +0 -0
  29. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/__init__.py +0 -0
  30. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/firebird.py +0 -0
  31. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dev_admin/__init__.py +0 -0
  39. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dev_admin/metrics.py +0 -0
  40. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dev_reload.py +0 -0
  41. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/dotenv/__init__.py +0 -0
  42. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/frond/FROND.md +0 -0
  43. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/frond/__init__.py +0 -0
  44. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/frond/engine.py +0 -0
  45. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/auth/meta.json +0 -0
  46. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  47. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/database/meta.json +0 -0
  48. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  49. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/error-overlay/meta.json +0 -0
  50. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  51. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/orm/meta.json +0 -0
  52. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  53. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  54. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/queue/meta.json +0 -0
  55. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  56. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/rest-api/meta.json +0 -0
  57. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  58. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/templates/meta.json +0 -0
  59. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  60. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  61. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/graphql/__init__.py +0 -0
  62. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/i18n/__init__.py +0 -0
  63. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/mcp/__init__.py +0 -0
  64. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/mcp/protocol.py +0 -0
  65. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/mcp/tools.py +0 -0
  66. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/messenger/__init__.py +0 -0
  67. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/migration/__init__.py +0 -0
  68. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/migration/runner.py +0 -0
  69. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/orm/__init__.py +0 -0
  70. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/orm/fields.py +0 -0
  71. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/css/tina4.css +0 -0
  72. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/css/tina4.min.css +0 -0
  73. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/favicon.ico +0 -0
  74. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/images/logo.svg +0 -0
  75. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  76. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/frond.min.js +0 -0
  77. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  78. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/tina4.min.js +0 -0
  79. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/js/tina4js.min.js +0 -0
  80. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/swagger/index.html +0 -0
  81. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  82. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/query_builder/__init__.py +0 -0
  83. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue/__init__.py +0 -0
  84. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/__init__.py +0 -0
  85. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/kafka_backend.py +0 -0
  86. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/mongo_backend.py +0 -0
  87. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  88. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/__init__.py +0 -0
  89. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  90. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_badges.scss +0 -0
  91. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  92. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_cards.scss +0 -0
  93. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_forms.scss +0 -0
  94. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_grid.scss +0 -0
  95. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_modals.scss +0 -0
  96. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_nav.scss +0 -0
  97. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_reset.scss +0 -0
  98. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_tables.scss +0 -0
  99. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_typography.scss +0 -0
  100. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  101. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/_variables.scss +0 -0
  102. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/base.scss +0 -0
  103. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/colors.scss +0 -0
  104. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/scss/tina4css/tina4.scss +0 -0
  105. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/seeder/__init__.py +0 -0
  106. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/service/__init__.py +0 -0
  107. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session/__init__.py +0 -0
  108. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/__init__.py +0 -0
  109. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  110. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/redis_handler.py +0 -0
  111. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/session_handlers/valkey_handler.py +0 -0
  112. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/swagger/__init__.py +0 -0
  113. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/components/crud.twig +0 -0
  114. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  115. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  116. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/python/Dockerfile +0 -0
  117. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  118. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/302.twig +0 -0
  119. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/401.twig +0 -0
  120. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/403.twig +0 -0
  121. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/404.twig +0 -0
  122. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/500.twig +0 -0
  123. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/502.twig +0 -0
  124. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/503.twig +0 -0
  125. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/errors/base.twig +0 -0
  126. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/frontend/README.md +0 -0
  127. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/templates/readme.md +0 -0
  128. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/test_client/__init__.py +0 -0
  129. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  130. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  131. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  132. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  133. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  134. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  135. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  136. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  137. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  138. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  139. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  140. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  141. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/validator/__init__.py +0 -0
  142. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/websocket/__init__.py +0 -0
  143. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/websocket/backplane.py +0 -0
  144. {tina4_python-3.10.54 → tina4_python-3.10.58}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.10.54
3
+ Version: 3.10.58
4
4
  Summary: Tina4 for Python — 54 built-in features, zero dependencies
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -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.54"
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
- Saves/restores static prefix and middleware state around the
72
- callback so that nested groups concatenate correctly.
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
- Router.get("/users", handler),
78
- Router.post("/users", handler),
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
- callback()
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 AI Port: http://{display}:{ai_port} (no-reload)" if ai_port else ""
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 + 1) if (is_debug and not _no_ai_port) else None
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"AI dev port: http://{display}:{_ai_port} (no-reload)")
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
- # AI dev port (port + 1) — no-reload, no live-reload WebSocket
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, skip)
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
- limit = int(request.params.get("limit", 10))
98
- skip = int(request.params.get("skip", 0))
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
- skip = 0
111
+ offset = 0
112
+ page = 1
102
113
 
103
- records, total = _cls.all(limit=limit, skip=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
- "data": [r.to_dict() for r in records],
106
- "total": total,
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
- "skip": skip,
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
- start = (page - 1) * per_page
37
- end = start + per_page
36
+ offset = (page - 1) * per_page
37
+ data = self.records[offset:offset + per_page]
38
38
  return {
39
- "data": self.records[start:end],
40
- "total": self.count,
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
- "total_pages": total_pages,
44
- "has_next": page < total_pages,
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.