tina4-python 3.13.82__tar.gz → 3.13.83__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 (171) hide show
  1. {tina4_python-3.13.82 → tina4_python-3.13.83}/PKG-INFO +1 -1
  2. {tina4_python-3.13.82 → tina4_python-3.13.83}/pyproject.toml +1 -1
  3. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/server.py +48 -3
  4. {tina4_python-3.13.82 → tina4_python-3.13.83}/.gitignore +0 -0
  5. {tina4_python-3.13.82 → tina4_python-3.13.83}/README.md +0 -0
  6. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/CLAUDE.md +0 -0
  7. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/HtmlElement.py +0 -0
  8. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/Testing.py +0 -0
  9. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/__init__.py +0 -0
  10. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/context/__init__.py +0 -0
  17. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/context/chunker.py +0 -0
  18. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/__init__.py +0 -0
  19. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/cache.py +0 -0
  20. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/constants.py +0 -0
  21. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/events.py +0 -0
  22. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/middleware.py +0 -0
  23. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/rate_limiter.py +0 -0
  24. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/request.py +0 -0
  25. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/response.py +0 -0
  26. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/core/router.py +0 -0
  27. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/crud/__init__.py +0 -0
  28. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/__init__.py +0 -0
  29. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/adapter.py +0 -0
  30. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/connection.py +0 -0
  31. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/firebird.py +0 -0
  32. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/mongodb.py +0 -0
  33. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/mssql.py +0 -0
  34. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/mysql.py +0 -0
  35. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/odbc.py +0 -0
  36. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/postgres.py +0 -0
  37. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/database/sqlite.py +0 -0
  38. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/debug/__init__.py +0 -0
  39. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/debug/error_overlay.py +0 -0
  40. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/dev_admin/__init__.py +0 -0
  41. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/dev_admin/metrics.py +0 -0
  42. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/dev_admin/plan.py +0 -0
  43. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/dev_admin/project_index.py +0 -0
  44. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/docs.py +0 -0
  45. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/docstore/__init__.py +0 -0
  46. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/dotenv/__init__.py +0 -0
  47. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/env.py +0 -0
  48. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/frond/FROND.md +0 -0
  49. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/frond/__init__.py +0 -0
  50. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/frond/engine.py +0 -0
  51. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/auth/meta.json +0 -0
  52. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  53. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/database/meta.json +0 -0
  54. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  55. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/error-overlay/meta.json +0 -0
  56. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  57. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/orm/meta.json +0 -0
  58. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  59. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  60. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/queue/meta.json +0 -0
  61. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  62. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/rest-api/meta.json +0 -0
  63. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  64. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/templates/meta.json +0 -0
  65. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  66. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  67. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/graphql/__init__.py +0 -0
  68. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/i18n/__init__.py +0 -0
  69. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/mcp/__init__.py +0 -0
  70. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/mcp/protocol.py +0 -0
  71. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/mcp/tools.py +0 -0
  72. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/messenger/__init__.py +0 -0
  73. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/migration/__init__.py +0 -0
  74. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/migration/runner.py +0 -0
  75. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/mqtt/__init__.py +0 -0
  76. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/mqtt/message.py +0 -0
  77. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/orm/__init__.py +0 -0
  78. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/orm/fields.py +0 -0
  79. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/orm/model.py +0 -0
  80. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/__feedback/widget.js +0 -0
  81. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/css/tina4.css +0 -0
  82. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/css/tina4.min.css +0 -0
  83. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/favicon.ico +0 -0
  84. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/images/logo.svg +0 -0
  85. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  86. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/js/frond.js +0 -0
  87. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/js/frond.min.js +0 -0
  88. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  89. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  90. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/js/tina4.min.js +0 -0
  91. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/js/tina4js.min.js +0 -0
  92. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/swagger/index.html +0 -0
  93. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  94. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/query_builder/__init__.py +0 -0
  95. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue/__init__.py +0 -0
  96. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue/job.py +0 -0
  97. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue/kafka_backend.py +0 -0
  98. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue/lite_backend.py +0 -0
  99. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue/mongo_backend.py +0 -0
  100. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue/rabbitmq_backend.py +0 -0
  101. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue_backends/__init__.py +0 -0
  102. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue_backends/kafka_backend.py +0 -0
  103. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue_backends/mongo_backend.py +0 -0
  104. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  105. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/__init__.py +0 -0
  106. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/models/Attachment.py +0 -0
  107. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/models/Channel.py +0 -0
  108. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/models/ChannelMember.py +0 -0
  109. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/models/Message.py +0 -0
  110. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/models/Workspace.py +0 -0
  111. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/models/__init__.py +0 -0
  112. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/realtime/storage.py +0 -0
  113. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/__init__.py +0 -0
  114. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  115. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_badges.scss +0 -0
  116. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  117. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_cards.scss +0 -0
  118. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_forms.scss +0 -0
  119. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_grid.scss +0 -0
  120. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_modals.scss +0 -0
  121. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_nav.scss +0 -0
  122. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_pagination.scss +0 -0
  123. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_reset.scss +0 -0
  124. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_tables.scss +0 -0
  125. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_typography.scss +0 -0
  126. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  127. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/_variables.scss +0 -0
  128. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/base.scss +0 -0
  129. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/colors.scss +0 -0
  130. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/scss/tina4css/tina4.scss +0 -0
  131. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/seeder/__init__.py +0 -0
  132. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/service/__init__.py +0 -0
  133. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/session/__init__.py +0 -0
  134. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/session_handlers/__init__.py +0 -0
  135. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  136. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/session_handlers/redis_handler.py +0 -0
  137. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/session_handlers/valkey_handler.py +0 -0
  138. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/swagger/__init__.py +0 -0
  139. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/components/crud.twig +0 -0
  140. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  141. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  142. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/docker/python/Dockerfile +0 -0
  143. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  144. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/302.twig +0 -0
  145. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/401.twig +0 -0
  146. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/403.twig +0 -0
  147. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/404.twig +0 -0
  148. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/500.twig +0 -0
  149. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/502.twig +0 -0
  150. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/503.twig +0 -0
  151. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/errors/base.twig +0 -0
  152. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/frontend/README.md +0 -0
  153. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/templates/readme.md +0 -0
  154. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/test/__init__.py +0 -0
  155. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/test_client/__init__.py +0 -0
  156. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  157. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  158. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  159. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  160. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  161. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  162. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  163. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  164. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  165. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  166. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  167. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  168. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/validator/__init__.py +0 -0
  169. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/websocket/__init__.py +0 -0
  170. {tina4_python-3.13.82 → tina4_python-3.13.83}/tina4_python/websocket/backplane.py +0 -0
  171. {tina4_python-3.13.82 → tina4_python-3.13.83}/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.13.82
3
+ Version: 3.13.83
4
4
  Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.82"
3
+ version = "3.13.83"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -2182,6 +2182,20 @@ def _try_static(path: str) -> Response | None:
2182
2182
  serves at the matching URL — no custom ``@get("/")`` route needed.
2183
2183
  """
2184
2184
  clean = path.lstrip("/")
2185
+
2186
+ # The framework ships the Swagger UI as STATIC assets under
2187
+ # tina4_python/public/swagger/. Static serving is independent of the gated
2188
+ # /swagger routes, so without this check the UI stays reachable in production
2189
+ # via '/swagger/', '/swagger/index.html' or '/swagger/oauth2-redirect.html'
2190
+ # even when swagger is disabled -- silently bypassing TINA4_SWAGGER_ENABLED /
2191
+ # TINA4_DEBUG. (A bare '/swagger' already 404s because index resolution below
2192
+ # only fires for '' or a trailing slash, which is why this leak hid.)
2193
+ # Checked BEFORE index resolution so the trailing-slash form is caught too.
2194
+ if clean == "swagger" or clean.startswith("swagger/"):
2195
+ from tina4_python.swagger import is_enabled as _swagger_enabled
2196
+ if not _swagger_enabled():
2197
+ return None
2198
+
2185
2199
  # Index resolution: '/' or '/foo/' -> append 'index.html' so SPA builds
2186
2200
  # in src/public/ Just Work without a custom root route.
2187
2201
  if clean == "" or clean.endswith("/"):
@@ -2419,6 +2433,33 @@ def resolve_config(cli_host: str | None = None, cli_port: int | None = None) ->
2419
2433
  return host, port
2420
2434
 
2421
2435
 
2436
+ def banner_surface_lines(
2437
+ port: int, *, swagger_enabled: bool, dev_admin_enabled: bool
2438
+ ) -> tuple[str, str]:
2439
+ """Build the startup banner's optional surface lines (issue #99).
2440
+
2441
+ Only advertise a surface that is actually REACHABLE. In production, or with
2442
+ ``TINA4_DEBUG`` off, ``/swagger`` and ``/__dev`` return 404 -- printing them
2443
+ anyway both misleads an operator into believing a dev surface is exposed and
2444
+ sends a developer to a dead link.
2445
+
2446
+ Kept as a pure function of (port, two booleans) so the contract is unit
2447
+ testable without booting a server and grepping stdout. Parity: PHP
2448
+ ``App::bannerSurfaceLines``, Ruby ``Tina4.banner_surface_lines``, Node
2449
+ ``bannerSurfaceLines``.
2450
+
2451
+ :return: ``(swagger_line, dashboard_line)`` -- each either empty, or a
2452
+ newline followed by the banner row, ready to interpolate.
2453
+ """
2454
+ swagger_line = (
2455
+ f"\n Swagger: http://localhost:{port}/swagger" if swagger_enabled else ""
2456
+ )
2457
+ dashboard_line = (
2458
+ f"\n Dashboard: http://localhost:{port}/__dev" if dev_admin_enabled else ""
2459
+ )
2460
+ return swagger_line, dashboard_line
2461
+
2462
+
2422
2463
  def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: int | None = None):
2423
2464
  """Print the Tina4 Slant ASCII banner to stdout (not through the logger)."""
2424
2465
  from tina4_python.dotenv import is_truthy
@@ -2433,6 +2474,12 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: i
2433
2474
 
2434
2475
  ai_port_line = f"\n Test Port: http://{display}:{ai_port} (stable — no hot-reload)" if ai_port else ""
2435
2476
 
2477
+ # Only advertise a surface that is actually reachable (issue #99).
2478
+ from tina4_python.swagger import is_enabled as _swagger_enabled
2479
+ swagger_line, dashboard_line = banner_surface_lines(
2480
+ port, swagger_enabled=_swagger_enabled(), dev_admin_enabled=is_debug
2481
+ )
2482
+
2436
2483
  banner = f"""{color}
2437
2484
  ______ _ __ __
2438
2485
  /_ __/(_)___ ____ _/ // /
@@ -2442,9 +2489,7 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: i
2442
2489
  {reset}
2443
2490
  Tina4 Python v{__version__} — The Intelligent Native Application 4ramework
2444
2491
 
2445
- Server: http://{display}:{port} ({server_name})
2446
- Swagger: http://localhost:{port}/swagger
2447
- Dashboard: http://localhost:{port}/__dev
2492
+ Server: http://{display}:{port} ({server_name}){swagger_line}{dashboard_line}
2448
2493
  Debug: {"ON" if is_debug else "OFF"} (Log level: {log_level}){ai_port_line}
2449
2494
  """
2450
2495
  print(banner)
File without changes