tina4-python 3.10.59__tar.gz → 3.10.65__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.59 → tina4_python-3.10.65}/PKG-INFO +1 -1
  2. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/HtmlElement.py +1 -1
  3. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/Testing.py +1 -1
  4. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/__init__.py +2 -2
  5. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/cli/__init__.py +52 -0
  6. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/router.py +6 -6
  7. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/server.py +2 -2
  8. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/dev_admin/metrics.py +63 -10
  9. {tina4_python-3.10.59 → tina4_python-3.10.65}/.gitignore +0 -0
  10. {tina4_python-3.10.59 → tina4_python-3.10.65}/README.md +0 -0
  11. {tina4_python-3.10.59 → tina4_python-3.10.65}/pyproject.toml +0 -0
  12. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/CLAUDE.md +0 -0
  13. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/ai/__init__.py +0 -0
  14. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/api/__init__.py +0 -0
  15. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/auth/__init__.py +0 -0
  16. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/cache/__init__.py +0 -0
  17. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/container/__init__.py +0 -0
  18. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/__init__.py +0 -0
  19. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/cache.py +0 -0
  20. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/constants.py +0 -0
  21. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/events.py +0 -0
  22. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/middleware.py +0 -0
  23. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/request.py +0 -0
  24. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/core/response.py +0 -0
  25. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/mongodb.py +0 -0
  31. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/dev_admin/__init__.py +0 -0
  39. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/dev_reload.py +0 -0
  40. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/dotenv/__init__.py +0 -0
  41. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/frond/FROND.md +0 -0
  42. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/frond/__init__.py +0 -0
  43. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/frond/engine.py +0 -0
  44. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/auth/meta.json +0 -0
  45. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  46. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/database/meta.json +0 -0
  47. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  48. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/error-overlay/meta.json +0 -0
  49. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  50. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/orm/meta.json +0 -0
  51. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  52. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  53. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/queue/meta.json +0 -0
  54. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  55. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/rest-api/meta.json +0 -0
  56. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  57. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/templates/meta.json +0 -0
  58. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  59. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  60. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/graphql/__init__.py +0 -0
  61. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/i18n/__init__.py +0 -0
  62. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/mcp/__init__.py +0 -0
  63. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/mcp/protocol.py +0 -0
  64. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/mcp/tools.py +0 -0
  65. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/messenger/__init__.py +0 -0
  66. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/migration/__init__.py +0 -0
  67. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/migration/runner.py +0 -0
  68. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/orm/__init__.py +0 -0
  69. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/orm/fields.py +0 -0
  70. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/orm/model.py +0 -0
  71. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/css/tina4.css +0 -0
  72. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/css/tina4.min.css +0 -0
  73. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/favicon.ico +0 -0
  74. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/images/logo.svg +0 -0
  75. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  76. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/js/frond.min.js +0 -0
  77. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  78. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/js/tina4.min.js +0 -0
  79. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/js/tina4js.min.js +0 -0
  80. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/swagger/index.html +0 -0
  81. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  82. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/query_builder/__init__.py +0 -0
  83. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/queue/__init__.py +0 -0
  84. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/queue_backends/__init__.py +0 -0
  85. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/queue_backends/kafka_backend.py +0 -0
  86. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/queue_backends/mongo_backend.py +0 -0
  87. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  88. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/__init__.py +0 -0
  89. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  90. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_badges.scss +0 -0
  91. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  92. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_cards.scss +0 -0
  93. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_forms.scss +0 -0
  94. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_grid.scss +0 -0
  95. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_modals.scss +0 -0
  96. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_nav.scss +0 -0
  97. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_reset.scss +0 -0
  98. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_tables.scss +0 -0
  99. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_typography.scss +0 -0
  100. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  101. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/_variables.scss +0 -0
  102. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/base.scss +0 -0
  103. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/colors.scss +0 -0
  104. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/scss/tina4css/tina4.scss +0 -0
  105. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/seeder/__init__.py +0 -0
  106. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/service/__init__.py +0 -0
  107. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/session/__init__.py +0 -0
  108. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/session_handlers/__init__.py +0 -0
  109. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  110. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/session_handlers/redis_handler.py +0 -0
  111. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/session_handlers/valkey_handler.py +0 -0
  112. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/swagger/__init__.py +0 -0
  113. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/components/crud.twig +0 -0
  114. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  115. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  116. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/docker/python/Dockerfile +0 -0
  117. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  118. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/302.twig +0 -0
  119. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/401.twig +0 -0
  120. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/403.twig +0 -0
  121. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/404.twig +0 -0
  122. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/500.twig +0 -0
  123. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/502.twig +0 -0
  124. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/503.twig +0 -0
  125. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/errors/base.twig +0 -0
  126. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/frontend/README.md +0 -0
  127. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/templates/readme.md +0 -0
  128. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/test_client/__init__.py +0 -0
  129. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  130. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  131. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  132. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  133. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  134. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  135. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  136. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  137. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  138. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  139. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  140. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  141. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/validator/__init__.py +0 -0
  142. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/websocket/__init__.py +0 -0
  143. {tina4_python-3.10.59 → tina4_python-3.10.65}/tina4_python/websocket/backplane.py +0 -0
  144. {tina4_python-3.10.59 → tina4_python-3.10.65}/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.59
3
+ Version: 3.10.65
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
@@ -1,5 +1,5 @@
1
1
  """
2
- Tina4 - This is not a 4ramework.
2
+ Tina4 - The Intelligent Native Application 4ramework.
3
3
  Programmatic HTML builder - avoids string concatenation.
4
4
 
5
5
  Usage:
@@ -1,4 +1,4 @@
1
- # Tina4 Python v3.0 — This is not a 4ramework.
1
+ # Tina4 Python v3.0 — The Intelligent Native Application 4ramework.
2
2
  # Copyright 2007 - present Tina4
3
3
  # License: MIT https://opensource.org/licenses/MIT
4
4
  """
@@ -1,4 +1,4 @@
1
- # Tina4 Python v3.0 — This is not a 4ramework.
1
+ # Tina4 Python v3.0 — The Intelligent Native Application 4ramework.
2
2
  # Copyright 2007 - present Tina4
3
3
  # License: MIT https://opensource.org/licenses/MIT
4
4
  """
@@ -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.59"
11
+ __version__ = "3.10.65"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -137,6 +137,7 @@ def main():
137
137
  "build": _build,
138
138
  "ai": _ai,
139
139
  "generate": _generate,
140
+ "console": _console,
140
141
  "help": _help,
141
142
  }
142
143
 
@@ -166,6 +167,7 @@ Commands:
166
167
  test Run test suite
167
168
  build Build distributable package
168
169
  ai [--all] Install AI coding assistant context
170
+ console Start interactive REPL with framework loaded
169
171
 
170
172
  Generators:
171
173
  generate model <Name> [--fields "name:string,price:float"]
@@ -185,6 +187,56 @@ https://tina4.com
185
187
  """)
186
188
 
187
189
 
190
+ # ── Console ───────────────────────────────────────────────────────────
191
+
192
+ def _console(args=None):
193
+ """Start an interactive REPL with the framework loaded."""
194
+ import code
195
+ import os
196
+
197
+ # Load environment
198
+ from tina4_python.dotenv import load_env
199
+ load_env()
200
+
201
+ # Import everything the user needs
202
+ from tina4_python import get, post, put, patch, delete, Router, Database, ORM, Auth, Queue, Frond
203
+ from tina4_python.debug import Log
204
+ from tina4_python.api import Api
205
+ from tina4_python.core.events import on, emit
206
+
207
+ # Try to connect database from DATABASE_URL
208
+ db = None
209
+ db_url = os.environ.get("DATABASE_URL")
210
+ if db_url:
211
+ try:
212
+ db = Database(db_url)
213
+ print(f" Database: {db_url}")
214
+ except Exception as e:
215
+ print(f" Database: failed ({e})")
216
+
217
+ # Auto-discover routes
218
+ from tina4_python.core.server import _auto_discover
219
+ _auto_discover("src")
220
+ route_count = len(Router.get_routes())
221
+ print(f" Routes: {route_count} discovered")
222
+
223
+ banner = (
224
+ "\n Tina4 Python Console\n"
225
+ " Type Python code. Framework is loaded.\n"
226
+ " Available: db, Router, ORM, Database, Auth, Api, Log, Queue\n"
227
+ " Exit: Ctrl+D or exit()\n"
228
+ )
229
+
230
+ local_vars = {
231
+ "db": db, "Database": Database, "ORM": ORM, "Router": Router,
232
+ "Auth": Auth, "Api": Api, "Log": Log, "Queue": Queue,
233
+ "Frond": Frond, "get": get, "post": post, "put": put,
234
+ "patch": patch, "delete": delete, "on": on, "emit": emit,
235
+ }
236
+
237
+ code.interact(banner=banner, local=local_vars)
238
+
239
+
188
240
  # ── Init ──────────────────────────────────────────────────────────────
189
241
 
190
242
  def _init(args):
@@ -79,22 +79,22 @@ class RouteGroup:
79
79
  self._middleware = middleware or []
80
80
 
81
81
  def get(self, path: str, handler, **options) -> RouteRef:
82
- return self._router.add("GET", self._prefix + path, handler, middleware=self._middleware, **options)
82
+ return self._router.add("GET", path, handler, middleware=self._middleware, **options)
83
83
 
84
84
  def post(self, path: str, handler, **options) -> RouteRef:
85
- return self._router.add("POST", self._prefix + path, handler, middleware=self._middleware, **options)
85
+ return self._router.add("POST", path, handler, middleware=self._middleware, **options)
86
86
 
87
87
  def put(self, path: str, handler, **options) -> RouteRef:
88
- return self._router.add("PUT", self._prefix + path, handler, middleware=self._middleware, **options)
88
+ return self._router.add("PUT", path, handler, middleware=self._middleware, **options)
89
89
 
90
90
  def patch(self, path: str, handler, **options) -> RouteRef:
91
- return self._router.add("PATCH", self._prefix + path, handler, middleware=self._middleware, **options)
91
+ return self._router.add("PATCH", path, handler, middleware=self._middleware, **options)
92
92
 
93
93
  def delete(self, path: str, handler, **options) -> RouteRef:
94
- return self._router.add("DELETE", self._prefix + path, handler, middleware=self._middleware, **options)
94
+ return self._router.add("DELETE", path, handler, middleware=self._middleware, **options)
95
95
 
96
96
  def any(self, path: str, handler, **options) -> RouteRef:
97
- return self._router.add("ANY", self._prefix + path, handler, middleware=self._middleware, **options)
97
+ return self._router.add("ANY", path, handler, middleware=self._middleware, **options)
98
98
 
99
99
  def group(self, prefix: str, callback, middleware=None):
100
100
  merged = list(self._middleware) + (middleware or [])
@@ -254,7 +254,7 @@ h1{{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}}
254
254
  <div class="hero">
255
255
  <img src="/images/tina4-logo-icon.webp" class="logo" alt="Tina4">
256
256
  <h1>Tina4Python</h1>
257
- <p class="tagline">This Is Now A 4Framework</p>
257
+ <p class="tagline">The Intelligent Native Application 4ramework</p>
258
258
  <div class="actions">
259
259
  <a href="https://tina4.com/python" class="btn" target="_blank">Website</a>
260
260
  <a href="/__dev" class="btn">Dev Admin</a>
@@ -1312,7 +1312,7 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: i
1312
1312
  / / / / / / / /_/ /__ __/
1313
1313
  /_/ /_/_/ /_/\\__,_/ /_/
1314
1314
  {reset}
1315
- Tina4 Python v{__version__} — This Is Now A 4Framework
1315
+ Tina4 Python v{__version__} — The Intelligent Native Application 4ramework
1316
1316
 
1317
1317
  Server: http://{display}:{port} ({server_name})
1318
1318
  Swagger: http://localhost:{port}/swagger
@@ -105,7 +105,7 @@ def quick_metrics(root: str = "src") -> dict:
105
105
  total_functions += functions
106
106
 
107
107
  file_details.append({
108
- "path": str(f.relative_to(".")),
108
+ "path": str(f.relative_to(root_path)),
109
109
  "loc": loc,
110
110
  "blank": blank,
111
111
  "comment": comment,
@@ -211,7 +211,10 @@ def full_analysis(root: str = "src") -> dict:
211
211
  except (SyntaxError, OSError):
212
212
  continue
213
213
 
214
- rel_path = str(f.relative_to("."))
214
+ try:
215
+ rel_path = str(f.relative_to(root_path))
216
+ except ValueError:
217
+ rel_path = str(f.relative_to(root_path.parent)) if root_path.parent != f else f.name
215
218
  lines = source.splitlines()
216
219
  loc = sum(1 for l in lines if l.strip() and not l.strip().startswith("#"))
217
220
 
@@ -495,19 +498,69 @@ def _count_halstead(node: ast.AST, stats: dict):
495
498
  def _has_matching_test(rel_path: str) -> bool:
496
499
  """Check if a source file has a matching test file.
497
500
 
498
- Looks for tests/test_{module}.py or tests/test_{module_name}.py.
501
+ Three-stage detection:
502
+ 1. Filename matching — test_module.py, module_test.py, module_spec.py
503
+ 2. Path matching — any test file referencing the full import path
504
+ 3. Content matching — any test file mentioning the module or class name
499
505
  """
506
+ import re
507
+
500
508
  p = Path(rel_path)
501
- module = p.stem # e.g. "auth" from "tina4_python/auth/__init__.py"
509
+ module = p.stem # e.g. "sqlite" from "tina4_python/database/sqlite.py"
502
510
  if module == "__init__":
503
511
  module = p.parent.name # use parent dir name
504
- # Check common test file patterns
505
- test_patterns = [
506
- Path("tests") / f"test_{module}.py",
507
- Path("tests") / f"test_{module}s.py",
508
- Path("test") / f"test_{module}.py",
512
+
513
+ # Build the full dotted import path for deeper matching
514
+ # e.g. "tina4_python/database/sqlite.py" "tina4_python.database.sqlite"
515
+ parts = list(p.with_suffix("").parts)
516
+ dotted_path = ".".join(parts) # "tina4_python.database.sqlite"
517
+
518
+ # Also track the parent package name for broader matching
519
+ # e.g. "database" from "tina4_python/database/sqlite.py"
520
+ parent_module = p.parent.name if len(p.parts) > 1 else ""
521
+
522
+ # Stage 1: Filename patterns
523
+ test_dirs = [Path("tests"), Path("test"), Path("spec")]
524
+ for td in test_dirs:
525
+ patterns = [
526
+ td / f"test_{module}.py",
527
+ td / f"test_{module}s.py",
528
+ td / f"{module}_test.py",
529
+ td / f"{module}_spec.py",
530
+ ]
531
+ # Also check parent-named tests (test_database.py covers database/sqlite.py)
532
+ if parent_module and parent_module != module:
533
+ patterns.extend([
534
+ td / f"test_{parent_module}.py",
535
+ td / f"test_{parent_module}s.py",
536
+ td / f"{parent_module}_test.py",
537
+ ])
538
+ if any(tp.exists() for tp in patterns):
539
+ return True
540
+
541
+ # Stage 2+3: Content scan — check if ANY test file references this module
542
+ search_terms = [
543
+ re.compile(rf'\b{re.escape(module)}\b', re.IGNORECASE),
509
544
  ]
510
- return any(tp.exists() for tp in test_patterns)
545
+ # Add dotted path patterns for import matching
546
+ if dotted_path:
547
+ search_terms.append(re.compile(rf'{re.escape(dotted_path)}'))
548
+ # Add class name guesses (CamelCase from snake_case module name)
549
+ class_name = "".join(w.capitalize() for w in module.split("_"))
550
+ if class_name != module:
551
+ search_terms.append(re.compile(rf'\b{re.escape(class_name)}\b'))
552
+
553
+ for td in test_dirs:
554
+ if not td.is_dir():
555
+ continue
556
+ for test_file in td.rglob("*.py"):
557
+ try:
558
+ content = test_file.read_text(encoding="utf-8", errors="ignore")
559
+ if any(pat.search(content) for pat in search_terms):
560
+ return True
561
+ except OSError:
562
+ pass
563
+ return False
511
564
 
512
565
 
513
566
  def _maintainability_index(halstead_volume: float, avg_cc: float, loc: int) -> float:
File without changes