tina4-python 3.10.55__tar.gz → 3.10.59__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.55 → tina4_python-3.10.59}/PKG-INFO +1 -1
  2. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/__init__.py +1 -1
  3. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/server.py +81 -19
  4. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/orm/model.py +3 -3
  5. {tina4_python-3.10.55 → tina4_python-3.10.59}/.gitignore +0 -0
  6. {tina4_python-3.10.55 → tina4_python-3.10.59}/README.md +0 -0
  7. {tina4_python-3.10.55 → tina4_python-3.10.59}/pyproject.toml +0 -0
  8. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/CLAUDE.md +0 -0
  9. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/HtmlElement.py +0 -0
  10. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/Testing.py +0 -0
  11. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/ai/__init__.py +0 -0
  12. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/api/__init__.py +0 -0
  13. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/auth/__init__.py +0 -0
  14. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/cache/__init__.py +0 -0
  15. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/cli/__init__.py +0 -0
  16. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/container/__init__.py +0 -0
  17. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/__init__.py +0 -0
  18. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/cache.py +0 -0
  19. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/constants.py +0 -0
  20. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/events.py +0 -0
  21. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/middleware.py +0 -0
  22. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/mongodb.py +0 -0
  31. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/dev_admin/__init__.py +0 -0
  39. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/dev_admin/metrics.py +0 -0
  40. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/dev_reload.py +0 -0
  41. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/dotenv/__init__.py +0 -0
  42. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/frond/FROND.md +0 -0
  43. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/frond/__init__.py +0 -0
  44. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/frond/engine.py +0 -0
  45. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/auth/meta.json +0 -0
  46. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  47. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/database/meta.json +0 -0
  48. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  49. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/error-overlay/meta.json +0 -0
  50. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  51. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/orm/meta.json +0 -0
  52. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  53. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  54. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/queue/meta.json +0 -0
  55. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  56. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/rest-api/meta.json +0 -0
  57. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  58. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/templates/meta.json +0 -0
  59. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  60. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  61. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/graphql/__init__.py +0 -0
  62. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/i18n/__init__.py +0 -0
  63. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/mcp/__init__.py +0 -0
  64. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/mcp/protocol.py +0 -0
  65. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/mcp/tools.py +0 -0
  66. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/messenger/__init__.py +0 -0
  67. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/migration/__init__.py +0 -0
  68. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/migration/runner.py +0 -0
  69. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/orm/__init__.py +0 -0
  70. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/orm/fields.py +0 -0
  71. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/css/tina4.css +0 -0
  72. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/css/tina4.min.css +0 -0
  73. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/favicon.ico +0 -0
  74. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/images/logo.svg +0 -0
  75. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  76. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/js/frond.min.js +0 -0
  77. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  78. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/js/tina4.min.js +0 -0
  79. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/js/tina4js.min.js +0 -0
  80. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/swagger/index.html +0 -0
  81. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  82. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/query_builder/__init__.py +0 -0
  83. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/queue/__init__.py +0 -0
  84. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/queue_backends/__init__.py +0 -0
  85. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/queue_backends/kafka_backend.py +0 -0
  86. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/queue_backends/mongo_backend.py +0 -0
  87. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  88. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/__init__.py +0 -0
  89. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  90. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_badges.scss +0 -0
  91. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  92. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_cards.scss +0 -0
  93. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_forms.scss +0 -0
  94. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_grid.scss +0 -0
  95. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_modals.scss +0 -0
  96. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_nav.scss +0 -0
  97. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_reset.scss +0 -0
  98. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_tables.scss +0 -0
  99. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_typography.scss +0 -0
  100. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  101. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/_variables.scss +0 -0
  102. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/base.scss +0 -0
  103. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/colors.scss +0 -0
  104. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/scss/tina4css/tina4.scss +0 -0
  105. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/seeder/__init__.py +0 -0
  106. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/service/__init__.py +0 -0
  107. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/session/__init__.py +0 -0
  108. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/session_handlers/__init__.py +0 -0
  109. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  110. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/session_handlers/redis_handler.py +0 -0
  111. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/session_handlers/valkey_handler.py +0 -0
  112. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/swagger/__init__.py +0 -0
  113. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/components/crud.twig +0 -0
  114. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  115. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  116. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/docker/python/Dockerfile +0 -0
  117. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  118. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/302.twig +0 -0
  119. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/401.twig +0 -0
  120. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/403.twig +0 -0
  121. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/404.twig +0 -0
  122. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/500.twig +0 -0
  123. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/502.twig +0 -0
  124. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/503.twig +0 -0
  125. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/errors/base.twig +0 -0
  126. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/frontend/README.md +0 -0
  127. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/templates/readme.md +0 -0
  128. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/test_client/__init__.py +0 -0
  129. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  130. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  131. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  132. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  133. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  134. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  135. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  136. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  137. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  138. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  139. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  140. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  141. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/validator/__init__.py +0 -0
  142. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/websocket/__init__.py +0 -0
  143. {tina4_python-3.10.55 → tina4_python-3.10.59}/tina4_python/websocket/backplane.py +0 -0
  144. {tina4_python-3.10.55 → tina4_python-3.10.59}/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.55
3
+ Version: 3.10.59
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.55"
11
+ __version__ = "3.10.59"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -1175,19 +1175,84 @@ def _find_production_server():
1175
1175
  return None
1176
1176
 
1177
1177
 
1178
+ def _kill_port(port: int) -> None:
1179
+ """Kill whatever process is listening on *port*.
1180
+
1181
+ Uses lsof on macOS/Linux and netstat + taskkill on Windows.
1182
+ Raises RuntimeError if the port cannot be freed.
1183
+ """
1184
+ import subprocess
1185
+ import time
1186
+
1187
+ print(f" Port {port} in use — killing existing process...")
1188
+
1189
+ if sys.platform == "win32":
1190
+ # Find PID via netstat
1191
+ try:
1192
+ result = subprocess.run(
1193
+ ["netstat", "-ano"],
1194
+ capture_output=True, text=True, timeout=5
1195
+ )
1196
+ pid = None
1197
+ for line in result.stdout.splitlines():
1198
+ if f":{port}" in line and ("LISTENING" in line or "ESTABLISHED" in line):
1199
+ parts = line.split()
1200
+ if parts:
1201
+ pid = parts[-1]
1202
+ break
1203
+ if pid and pid.isdigit():
1204
+ subprocess.run(["taskkill", "/PID", pid, "/F"], timeout=5)
1205
+ except Exception as e:
1206
+ raise RuntimeError(f"Could not free port {port}: {e}") from e
1207
+ else:
1208
+ # macOS / Linux — use lsof
1209
+ try:
1210
+ result = subprocess.run(
1211
+ ["lsof", "-ti", f":{port}"],
1212
+ capture_output=True, text=True, timeout=5
1213
+ )
1214
+ pids = result.stdout.strip().splitlines()
1215
+ if not pids:
1216
+ return # Nothing found — port may have freed itself
1217
+ for pid_str in pids:
1218
+ pid_str = pid_str.strip()
1219
+ if pid_str.isdigit():
1220
+ os.kill(int(pid_str), signal.SIGTERM)
1221
+ except FileNotFoundError:
1222
+ # lsof not available — try fuser
1223
+ try:
1224
+ result = subprocess.run(
1225
+ ["fuser", f"{port}/tcp"],
1226
+ capture_output=True, text=True, timeout=5
1227
+ )
1228
+ for pid_str in result.stdout.split():
1229
+ if pid_str.isdigit():
1230
+ os.kill(int(pid_str), signal.SIGTERM)
1231
+ except Exception as e:
1232
+ raise RuntimeError(f"Could not free port {port}: {e}") from e
1233
+ except Exception as e:
1234
+ raise RuntimeError(f"Could not free port {port}: {e}") from e
1235
+
1236
+ # Give the OS a moment to reclaim the port
1237
+ time.sleep(0.5)
1238
+ print(f" Port {port} freed")
1239
+
1240
+
1178
1241
  def _find_available_port(start: int, max_tries: int = 10) -> int:
1179
- """Try successive ports starting from *start*, return the first available."""
1242
+ """Check if *start* is available; if not, kill the process on it and return *start*.
1243
+
1244
+ The auto-increment behaviour is intentionally removed — the server always
1245
+ claims the requested port. If killing fails a RuntimeError is raised.
1246
+ """
1180
1247
  import socket
1181
- for offset in range(max_tries):
1182
- port = start + offset
1183
- try:
1184
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1185
- s.bind(("127.0.0.1", port))
1186
- s.close()
1187
- return port
1188
- except OSError:
1189
- continue
1190
- return start
1248
+ try:
1249
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1250
+ s.bind(("127.0.0.1", start))
1251
+ s.close()
1252
+ return start
1253
+ except OSError:
1254
+ _kill_port(start)
1255
+ return start
1191
1256
 
1192
1257
 
1193
1258
  def _open_browser(url: str):
@@ -1238,7 +1303,7 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: i
1238
1303
  color = "\033[34m" if sys.stdout.isatty() else ""
1239
1304
  reset = "\033[0m" if sys.stdout.isatty() else ""
1240
1305
 
1241
- ai_port_line = f"\n AI Port: http://{display}:{ai_port} (no-reload)" if ai_port else ""
1306
+ ai_port_line = f"\n Test Port: http://{display}:{ai_port} (stable — no hot-reload)" if ai_port else ""
1242
1307
 
1243
1308
  banner = f"""{color}
1244
1309
  ______ _ __ __
@@ -1300,11 +1365,8 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1300
1365
  # Resolve host/port (CLI arg > ENV > default)
1301
1366
  host, port = resolve_config(cli_host=host, cli_port=port)
1302
1367
 
1303
- # Auto-increment port if already in use
1304
- original_port = port
1368
+ # Claim the requested port kill whatever is on it if needed
1305
1369
  port = _find_available_port(port)
1306
- if port != original_port:
1307
- Log.info(f"Port {original_port} in use — using {port} instead")
1308
1370
 
1309
1371
  # Detect production server (unless TINA4_DEBUG is true)
1310
1372
  from tina4_python.dotenv import is_truthy
@@ -1328,7 +1390,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1328
1390
 
1329
1391
  # Determine AI dev port (port+1) when debug is on and not suppressed
1330
1392
  _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
1393
+ _ai_port = (port + 1000) if (is_debug and not _no_ai_port) else None
1332
1394
 
1333
1395
  # Banner — printed directly to stdout, not through the logger
1334
1396
  _print_banner(host, port, server_name, ai_port=_ai_port)
@@ -1336,7 +1398,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1336
1398
  display = "localhost" if host in ("0.0.0.0", "::") else host
1337
1399
  Log.info(f"Server started http://{display}:{port} ({server_name})")
1338
1400
  if _ai_port:
1339
- Log.info(f"AI dev port: http://{display}:{_ai_port} (no-reload)")
1401
+ Log.info(f"Test port: http://{display}:{_ai_port} (stable — no hot-reload)")
1340
1402
 
1341
1403
  # Open browser after a short delay (unless --no-browser)
1342
1404
  _skip_browser = no_browser or os.environ.get("TINA4_NO_BROWSER", "").lower() in ("true", "1", "yes")
@@ -1464,7 +1526,7 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
1464
1526
 
1465
1527
  server = await start_server(_handle_connection, host, port)
1466
1528
 
1467
- # AI dev port (port + 1) — no-reload, no live-reload WebSocket
1529
+ # Test port (port + 1000) — stable, no live-reload WebSocket
1468
1530
  ai_server = None
1469
1531
  if _ai_port:
1470
1532
  try:
@@ -211,9 +211,9 @@ class ORM(metaclass=ORMMeta):
211
211
  from tina4_python.database import Database
212
212
  username = os.environ.get("DATABASE_USERNAME", "")
213
213
  password = os.environ.get("DATABASE_PASSWORD", "")
214
- global _database
215
- _database = Database(url, username, password)
216
- return _database
214
+ db = Database(url, username, password)
215
+ orm_bind(db)
216
+ return db
217
217
  raise RuntimeError(
218
218
  "No database bound. Call orm_bind(db) or set DATABASE_URL in .env"
219
219
  )
File without changes