tina4-python 3.10.50__tar.gz → 3.10.54__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 (143) hide show
  1. {tina4_python-3.10.50 → tina4_python-3.10.54}/PKG-INFO +1 -1
  2. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/__init__.py +1 -1
  3. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/cli/__init__.py +5 -2
  4. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/server.py +48 -9
  5. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/firebird.py +1 -1
  6. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/dev_admin/__init__.py +4 -2
  7. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/orm/model.py +11 -1
  8. {tina4_python-3.10.50 → tina4_python-3.10.54}/.gitignore +0 -0
  9. {tina4_python-3.10.50 → tina4_python-3.10.54}/README.md +0 -0
  10. {tina4_python-3.10.50 → tina4_python-3.10.54}/pyproject.toml +0 -0
  11. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/CLAUDE.md +0 -0
  12. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/HtmlElement.py +0 -0
  13. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/Testing.py +0 -0
  14. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/ai/__init__.py +0 -0
  15. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/api/__init__.py +0 -0
  16. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/auth/__init__.py +0 -0
  17. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/cache/__init__.py +0 -0
  18. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/container/__init__.py +0 -0
  19. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/__init__.py +0 -0
  20. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/cache.py +0 -0
  21. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/constants.py +0 -0
  22. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/events.py +0 -0
  23. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/middleware.py +0 -0
  24. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/request.py +0 -0
  25. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/response.py +0 -0
  26. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/core/router.py +0 -0
  27. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/crud/__init__.py +0 -0
  28. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/__init__.py +0 -0
  29. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/adapter.py +0 -0
  30. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/connection.py +0 -0
  31. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/dev_admin/metrics.py +0 -0
  39. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/dev_reload.py +0 -0
  40. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/dotenv/__init__.py +0 -0
  41. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/frond/FROND.md +0 -0
  42. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/frond/__init__.py +0 -0
  43. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/frond/engine.py +0 -0
  44. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/auth/meta.json +0 -0
  45. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  46. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/database/meta.json +0 -0
  47. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  48. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/error-overlay/meta.json +0 -0
  49. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  50. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/orm/meta.json +0 -0
  51. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  52. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  53. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/queue/meta.json +0 -0
  54. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  55. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/rest-api/meta.json +0 -0
  56. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  57. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/templates/meta.json +0 -0
  58. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  59. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  60. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/graphql/__init__.py +0 -0
  61. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/i18n/__init__.py +0 -0
  62. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/mcp/__init__.py +0 -0
  63. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/mcp/protocol.py +0 -0
  64. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/mcp/tools.py +0 -0
  65. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/messenger/__init__.py +0 -0
  66. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/migration/__init__.py +0 -0
  67. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/migration/runner.py +0 -0
  68. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/orm/__init__.py +0 -0
  69. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/orm/fields.py +0 -0
  70. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/css/tina4.css +0 -0
  71. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/css/tina4.min.css +0 -0
  72. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/favicon.ico +0 -0
  73. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/images/logo.svg +0 -0
  74. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  75. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/js/frond.min.js +0 -0
  76. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  77. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/js/tina4.min.js +0 -0
  78. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/js/tina4js.min.js +0 -0
  79. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/swagger/index.html +0 -0
  80. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  81. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/query_builder/__init__.py +0 -0
  82. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/queue/__init__.py +0 -0
  83. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/queue_backends/__init__.py +0 -0
  84. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/queue_backends/kafka_backend.py +0 -0
  85. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/queue_backends/mongo_backend.py +0 -0
  86. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  87. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/__init__.py +0 -0
  88. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  89. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_badges.scss +0 -0
  90. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  91. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_cards.scss +0 -0
  92. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_forms.scss +0 -0
  93. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_grid.scss +0 -0
  94. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_modals.scss +0 -0
  95. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_nav.scss +0 -0
  96. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_reset.scss +0 -0
  97. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_tables.scss +0 -0
  98. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_typography.scss +0 -0
  99. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  100. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/_variables.scss +0 -0
  101. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/base.scss +0 -0
  102. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/colors.scss +0 -0
  103. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/scss/tina4css/tina4.scss +0 -0
  104. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/seeder/__init__.py +0 -0
  105. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/service/__init__.py +0 -0
  106. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/session/__init__.py +0 -0
  107. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/session_handlers/__init__.py +0 -0
  108. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  109. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/session_handlers/redis_handler.py +0 -0
  110. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/session_handlers/valkey_handler.py +0 -0
  111. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/swagger/__init__.py +0 -0
  112. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/components/crud.twig +0 -0
  113. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  114. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  115. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/docker/python/Dockerfile +0 -0
  116. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  117. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/302.twig +0 -0
  118. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/401.twig +0 -0
  119. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/403.twig +0 -0
  120. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/404.twig +0 -0
  121. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/500.twig +0 -0
  122. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/502.twig +0 -0
  123. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/503.twig +0 -0
  124. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/errors/base.twig +0 -0
  125. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/frontend/README.md +0 -0
  126. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/templates/readme.md +0 -0
  127. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/test_client/__init__.py +0 -0
  128. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  129. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  130. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  131. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  132. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  133. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  134. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  135. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  136. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  137. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  138. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  139. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  140. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/validator/__init__.py +0 -0
  141. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/websocket/__init__.py +0 -0
  142. {tina4_python-3.10.50 → tina4_python-3.10.54}/tina4_python/websocket/backplane.py +0 -0
  143. {tina4_python-3.10.50 → tina4_python-3.10.54}/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.50
3
+ Version: 3.10.54
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,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.10.50"
11
+ __version__ = "3.10.54"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -156,7 +156,7 @@ Usage: tina4python <command> [options]
156
156
 
157
157
  Commands:
158
158
  init [dir] Scaffold a new project
159
- serve [--port P] [--no-browser] Start dev server (default: 0.0.0.0:7146)
159
+ serve [--port P] [--no-browser] [--no-reload] Start dev server (default: 0.0.0.0:7146)
160
160
  migrate Run pending database migrations
161
161
  migrate:create <desc> Create a new migration file
162
162
  migrate:rollback Rollback last migration batch
@@ -327,12 +327,15 @@ def _serve(args):
327
327
  if os.environ.get("TINA4_OPEN_BROWSER", "").lower() in ("false", "0", "no"):
328
328
  no_browser = True
329
329
 
330
+ # --no-reload flag
331
+ no_reload = "no-reload" in flags
332
+
330
333
  # Kill existing process on port
331
334
  port = cli_port or int(os.environ.get("PORT", os.environ.get("TINA4_PORT", "7146")))
332
335
  _kill_process_on_port(port)
333
336
 
334
337
  from tina4_python.core import run
335
- run(host=cli_host, port=cli_port, no_browser=no_browser)
338
+ run(host=cli_host, port=cli_port, no_browser=no_browser, no_reload=no_reload)
336
339
 
337
340
 
338
341
  # ── Migrate ───────────────────────────────────────────────────────────
@@ -9,6 +9,7 @@ import os
9
9
  import sys
10
10
  import signal
11
11
  import asyncio
12
+ import contextvars
12
13
  import importlib
13
14
  import uuid
14
15
  from pathlib import Path
@@ -25,6 +26,9 @@ _cors = CorsMiddleware()
25
26
  _rate_limiter = RateLimiter()
26
27
 
27
28
 
29
+ # ContextVar to signal that the current request is being served on the AI dev port
30
+ _ai_port_ctx: contextvars.ContextVar[bool] = contextvars.ContextVar("_ai_port_ctx", default=False)
31
+
28
32
  # Track startup time
29
33
  _start_time: float = 0
30
34
 
@@ -1222,7 +1226,7 @@ def resolve_config(cli_host: str | None = None, cli_port: int | None = None) ->
1222
1226
  return host, port
1223
1227
 
1224
1228
 
1225
- def _print_banner(host: str, port: int, server_name: str = "asyncio"):
1229
+ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: int | None = None):
1226
1230
  """Print the Tina4 Slant ASCII banner to stdout (not through the logger)."""
1227
1231
  from tina4_python.dotenv import is_truthy
1228
1232
 
@@ -1234,6 +1238,8 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio"):
1234
1238
  color = "\033[34m" if sys.stdout.isatty() else ""
1235
1239
  reset = "\033[0m" if sys.stdout.isatty() else ""
1236
1240
 
1241
+ ai_port_line = f"\n AI Port: http://{display}:{ai_port} (no-reload)" if ai_port else ""
1242
+
1237
1243
  banner = f"""{color}
1238
1244
  ______ _ __ __
1239
1245
  /_ __/(_)___ ____ _/ // /
@@ -1246,12 +1252,12 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio"):
1246
1252
  Server: http://{display}:{port} ({server_name})
1247
1253
  Swagger: http://localhost:{port}/swagger
1248
1254
  Dashboard: http://localhost:{port}/__dev
1249
- Debug: {"ON" if is_debug else "OFF"} (Log level: {log_level})
1255
+ Debug: {"ON" if is_debug else "OFF"} (Log level: {log_level}){ai_port_line}
1250
1256
  """
1251
1257
  print(banner)
1252
1258
 
1253
1259
 
1254
- def run(host: str | None = None, port: int | None = None, no_browser: bool = False):
1260
+ def run(host: str | None = None, port: int | None = None, no_browser: bool = False, no_reload: bool = False):
1255
1261
  """Start the Tina4 dev server.
1256
1262
 
1257
1263
  Discovers routes from src/, starts ASGI server, handles shutdown.
@@ -1260,11 +1266,15 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1260
1266
  host: Bind address. Falls back to HOST env var, then 0.0.0.0.
1261
1267
  port: Bind port. Falls back to PORT env var, then 7146.
1262
1268
  no_browser: If True, do not open browser on startup.
1269
+ no_reload: If True, disable the file watcher / live-reload.
1263
1270
  """
1264
1271
  import time
1265
1272
  global _start_time
1266
1273
  _start_time = time.time()
1267
1274
 
1275
+ if no_reload:
1276
+ os.environ["TINA4_NO_RELOAD"] = "true"
1277
+
1268
1278
  # Ensure CWD is on sys.path so auto-discovered modules can be imported
1269
1279
  cwd = os.getcwd()
1270
1280
  if cwd not in sys.path:
@@ -1302,11 +1312,13 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1302
1312
 
1303
1313
  # Start DevReload file watcher in debug mode
1304
1314
  if is_debug:
1305
- try:
1306
- from tina4_python.dev_reload import start as _start_dev_reload
1307
- _start_dev_reload(["src", "public"])
1308
- except Exception as e:
1309
- Log.error(f"DevReload: failed to start: {e}")
1315
+ no_reload = os.environ.get("TINA4_NO_RELOAD", "").lower() in ("true", "1", "yes")
1316
+ if not no_reload:
1317
+ try:
1318
+ from tina4_python.dev_reload import start as _start_dev_reload
1319
+ _start_dev_reload(["src", "public"])
1320
+ except Exception as e:
1321
+ Log.error(f"DevReload: failed to start: {e}")
1310
1322
 
1311
1323
  prod = None
1312
1324
  if not is_debug:
@@ -1314,11 +1326,17 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1314
1326
 
1315
1327
  server_name = prod[0] if prod else "asyncio"
1316
1328
 
1329
+ # Determine AI dev port (port+1) when debug is on and not suppressed
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
1332
+
1317
1333
  # Banner — printed directly to stdout, not through the logger
1318
- _print_banner(host, port, server_name)
1334
+ _print_banner(host, port, server_name, ai_port=_ai_port)
1319
1335
 
1320
1336
  display = "localhost" if host in ("0.0.0.0", "::") else host
1321
1337
  Log.info(f"Server started http://{display}:{port} ({server_name})")
1338
+ if _ai_port:
1339
+ Log.info(f"AI dev port: http://{display}:{_ai_port} (no-reload)")
1322
1340
 
1323
1341
  # Open browser after a short delay (unless --no-browser)
1324
1342
  _skip_browser = no_browser or os.environ.get("TINA4_NO_BROWSER", "").lower() in ("true", "1", "yes")
@@ -1387,6 +1405,11 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1387
1405
  # Check for WebSocket upgrade before reading body
1388
1406
  _header_dict = {k.decode(): v.decode() for k, v in headers}
1389
1407
  if _header_dict.get("upgrade", "").lower() == "websocket":
1408
+ if hasattr(writer, "_tina4_ai_port") and path == "/__dev_reload":
1409
+ writer.write(b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")
1410
+ await writer.drain()
1411
+ writer.close()
1412
+ return
1390
1413
  await _handle_dev_websocket(reader, writer, _header_dict, path)
1391
1414
  return
1392
1415
 
@@ -1441,6 +1464,19 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1441
1464
 
1442
1465
  server = await start_server(_handle_connection, host, port)
1443
1466
 
1467
+ # AI dev port (port + 1) — no-reload, no live-reload WebSocket
1468
+ ai_server = None
1469
+ if _ai_port:
1470
+ try:
1471
+ async def _handle_ai_connection(reader, writer):
1472
+ _ai_port_ctx.set(True)
1473
+ writer._tina4_ai_port = True
1474
+ await _handle_connection(reader, writer)
1475
+
1476
+ ai_server = await start_server(_handle_ai_connection, host, _ai_port)
1477
+ except OSError:
1478
+ Log.warning(f"AI port {_ai_port} in use — skipping")
1479
+
1444
1480
  loop = asyncio.get_running_loop()
1445
1481
  for sig in (signal.SIGINT, signal.SIGTERM):
1446
1482
  try:
@@ -1449,6 +1485,9 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1449
1485
  pass # Windows
1450
1486
 
1451
1487
  await shutdown.wait()
1488
+ if ai_server:
1489
+ ai_server.close()
1490
+ await ai_server.wait_closed()
1452
1491
  server.close()
1453
1492
  await server.wait_closed()
1454
1493
  Log.info("Server stopped.")
@@ -49,7 +49,7 @@ class FirebirdAdapter(DatabaseAdapter):
49
49
  host = parsed.hostname or "localhost"
50
50
  port = parsed.port or 3050
51
51
  # Firebird database path — decode URL-encoded characters
52
- db_path = unquote(parsed.path.lstrip("/")) if parsed.path else ""
52
+ db_path = unquote(parsed.path[1:]) if parsed.path.startswith("/") else unquote(parsed.path)
53
53
  user = parsed.username or username or "SYSDBA"
54
54
  password = parsed.password or password or "masterkey"
55
55
  charset = kwargs.pop("charset", "UTF8")
@@ -2447,8 +2447,10 @@ def render_dev_toolbar(method: str, path: str, matched_pattern: str,
2447
2447
  and a close button.
2448
2448
  """
2449
2449
  import sys
2450
+ from tina4_python.core.server import _ai_port_ctx
2450
2451
  python_version = sys.version.split()[0]
2451
2452
  poll_interval_ms = int(os.environ.get("TINA4_DEV_POLL_INTERVAL", "3000"))
2453
+ no_reload = os.environ.get("TINA4_NO_RELOAD", "").lower() in ("true", "1", "yes") or _ai_port_ctx.get()
2452
2454
 
2453
2455
  return f"""<div id="tina4-dev-toolbar" style="position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px;">
2454
2456
  <span id="tina4-ver-btn" style="color:#3572A5;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v{__version__}</span>
@@ -2472,7 +2474,7 @@ def render_dev_toolbar(method: str, path: str, matched_pattern: str,
2472
2474
  <span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">&#10005;</span>
2473
2475
  </div>
2474
2476
  <script>
2475
- (function(){{
2477
+ {'(function(){})();' if no_reload else f"""(function(){{
2476
2478
  var _t4_mtime=0,_t4_css_exts=['.css','.scss'],_t4_debounce=null;
2477
2479
  var _t4_interval=parseInt('{poll_interval_ms}')||3000;
2478
2480
  function _t4_apply(d){{
@@ -2499,7 +2501,7 @@ def render_dev_toolbar(method: str, path: str, matched_pattern: str,
2499
2501
  }}).catch(function(){{}});
2500
2502
  }}
2501
2503
  setInterval(_t4_poll,_t4_interval);
2502
- }})();
2504
+ }})();"""}
2503
2505
  function tina4VersionModal(){{
2504
2506
  var m=document.getElementById('tina4-ver-modal');
2505
2507
  if(m.style.display==='block'){{m.style.display='none';return;}}
@@ -204,8 +204,18 @@ class ORM(metaclass=ORMMeta):
204
204
  return cls._db # Direct Database instance
205
205
 
206
206
  if _database is None:
207
+ # Try auto-discovery from DATABASE_URL
208
+ import os
209
+ url = os.environ.get("DATABASE_URL")
210
+ if url:
211
+ from tina4_python.database import Database
212
+ username = os.environ.get("DATABASE_USERNAME", "")
213
+ password = os.environ.get("DATABASE_PASSWORD", "")
214
+ global _database
215
+ _database = Database(url, username, password)
216
+ return _database
207
217
  raise RuntimeError(
208
- "No database bound. Call orm_bind(db) before using ORM models."
218
+ "No database bound. Call orm_bind(db) or set DATABASE_URL in .env"
209
219
  )
210
220
  return _database
211
221
 
File without changes