tina4-python 3.13.134__tar.gz → 3.13.135__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 (173) hide show
  1. {tina4_python-3.13.134 → tina4_python-3.13.135}/PKG-INFO +1 -1
  2. {tina4_python-3.13.134 → tina4_python-3.13.135}/pyproject.toml +1 -1
  3. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/__init__.py +1 -1
  4. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/dev_admin/__init__.py +36 -7
  5. {tina4_python-3.13.134 → tina4_python-3.13.135}/.gitignore +0 -0
  6. {tina4_python-3.13.134 → tina4_python-3.13.135}/README.md +0 -0
  7. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/.tina4-metrics.json +0 -0
  8. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/CLAUDE.md +0 -0
  9. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/HtmlElement.py +0 -0
  10. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/Testing.py +0 -0
  11. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/_import_helper.py +0 -0
  12. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/ai/__init__.py +0 -0
  13. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/ai/client.py +0 -0
  14. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/api/__init__.py +0 -0
  15. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/auth/__init__.py +0 -0
  16. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/cache/__init__.py +0 -0
  17. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/cli/__init__.py +0 -0
  18. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/container/__init__.py +0 -0
  19. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/context/__init__.py +0 -0
  20. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/context/chunker.py +0 -0
  21. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/__init__.py +0 -0
  22. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/cache.py +0 -0
  23. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/constants.py +0 -0
  24. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/events.py +0 -0
  25. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/middleware.py +0 -0
  26. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/port_takeover.py +0 -0
  27. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/rate_limiter.py +0 -0
  28. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/request.py +0 -0
  29. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/response.py +0 -0
  30. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/router.py +0 -0
  31. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/core/server.py +0 -0
  32. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/crud/__init__.py +0 -0
  33. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/__init__.py +0 -0
  34. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/adapter.py +0 -0
  35. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/connection.py +0 -0
  36. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/database_url.py +0 -0
  37. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/firebird.py +0 -0
  38. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/mongodb.py +0 -0
  39. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/mssql.py +0 -0
  40. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/mysql.py +0 -0
  41. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/odbc.py +0 -0
  42. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/postgres.py +0 -0
  43. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/sql_translator.py +0 -0
  44. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/database/sqlite.py +0 -0
  45. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/debug/__init__.py +0 -0
  46. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/debug/error_overlay.py +0 -0
  47. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/dev_admin/metrics.py +0 -0
  48. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/dev_admin/plan.py +0 -0
  49. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/dev_admin/project_index.py +0 -0
  50. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/docs.py +0 -0
  51. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/docstore/__init__.py +0 -0
  52. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/dotenv/__init__.py +0 -0
  53. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/env.py +0 -0
  54. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/frond/FROND.md +0 -0
  55. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/frond/__init__.py +0 -0
  56. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/frond/compiler.py +0 -0
  57. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/frond/engine.py +0 -0
  58. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/frond/parser.py +0 -0
  59. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/auth/meta.json +0 -0
  60. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  61. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/database/meta.json +0 -0
  62. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  63. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/error-overlay/meta.json +0 -0
  64. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  65. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/orm/meta.json +0 -0
  66. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  67. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  68. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/queue/meta.json +0 -0
  69. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  70. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/rest-api/meta.json +0 -0
  71. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  72. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/templates/meta.json +0 -0
  73. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  74. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  75. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/__init__.py +0 -0
  76. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/adapter.py +0 -0
  77. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/adapters/__init__.py +0 -0
  78. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/adapters/arango.py +0 -0
  79. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/adapters/bolt.py +0 -0
  80. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/adapters/ultipa.py +0 -0
  81. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graph/graph_url.py +0 -0
  82. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/graphql/__init__.py +0 -0
  83. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/i18n/__init__.py +0 -0
  84. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/mcp/__init__.py +0 -0
  85. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/mcp/protocol.py +0 -0
  86. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/mcp/tools.py +0 -0
  87. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/messenger/__init__.py +0 -0
  88. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/migration/__init__.py +0 -0
  89. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/migration/runner.py +0 -0
  90. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/mqtt/__init__.py +0 -0
  91. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/mqtt/message.py +0 -0
  92. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/orm/__init__.py +0 -0
  93. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/orm/collection.py +0 -0
  94. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/orm/fields.py +0 -0
  95. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/orm/model.py +0 -0
  96. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/orm/point.py +0 -0
  97. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/__feedback/widget.js +0 -0
  98. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/css/tina4.css +0 -0
  99. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/css/tina4.min.css +0 -0
  100. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/favicon.ico +0 -0
  101. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/images/logo.svg +0 -0
  102. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  103. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/js/frond.js +0 -0
  104. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/js/frond.min.js +0 -0
  105. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  106. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/js/tina4.min.js +0 -0
  107. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/js/tina4js.min.js +0 -0
  108. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/swagger/index.html +0 -0
  109. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  110. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/push/__init__.py +0 -0
  111. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/query_builder/__init__.py +0 -0
  112. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/__init__.py +0 -0
  113. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/amqp_url.py +0 -0
  114. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/job.py +0 -0
  115. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/kafka_backend.py +0 -0
  116. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/lite_backend.py +0 -0
  117. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/mongo_backend.py +0 -0
  118. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue/rabbitmq_backend.py +0 -0
  119. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue_backends/__init__.py +0 -0
  120. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue_backends/kafka_backend.py +0 -0
  121. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue_backends/mongo_backend.py +0 -0
  122. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  123. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/__init__.py +0 -0
  124. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/models/Attachment.py +0 -0
  125. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/models/Channel.py +0 -0
  126. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/models/ChannelMember.py +0 -0
  127. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/models/Message.py +0 -0
  128. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/models/Workspace.py +0 -0
  129. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/models/__init__.py +0 -0
  130. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/realtime/storage.py +0 -0
  131. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/seeder/__init__.py +0 -0
  132. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/service/__init__.py +0 -0
  133. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/session/__init__.py +0 -0
  134. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/session_handlers/__init__.py +0 -0
  135. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/session_handlers/memcached_handler.py +0 -0
  136. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  137. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/session_handlers/redis_handler.py +0 -0
  138. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/session_handlers/valkey_handler.py +0 -0
  139. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/sso.py +0 -0
  140. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/swagger/__init__.py +0 -0
  141. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/components/crud.twig +0 -0
  142. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  143. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  144. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/docker/python/Dockerfile +0 -0
  145. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  146. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/302.twig +0 -0
  147. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/401.twig +0 -0
  148. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/403.twig +0 -0
  149. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/404.twig +0 -0
  150. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/500.twig +0 -0
  151. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/502.twig +0 -0
  152. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/503.twig +0 -0
  153. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/errors/base.twig +0 -0
  154. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/frontend/README.md +0 -0
  155. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/templates/readme.md +0 -0
  156. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/test/__init__.py +0 -0
  157. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/test_client/__init__.py +0 -0
  158. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  159. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  160. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  161. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  162. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  163. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  164. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  165. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  166. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  167. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  168. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  169. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  170. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/validator/__init__.py +0 -0
  171. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/websocket/__init__.py +0 -0
  172. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/websocket/backplane.py +0 -0
  173. {tina4_python-3.13.134 → tina4_python-3.13.135}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tina4-python
3
- Version: 3.13.134
3
+ Version: 3.13.135
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.134"
3
+ version = "3.13.135"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -51,7 +51,7 @@ def _resolve_version() -> str:
51
51
  #
52
52
  # test_version_constant.py now asserts this literal equals the pyproject
53
53
  # version, so the release bump cannot leave it behind again.
54
- return "3.13.134"
54
+ return "3.13.135"
55
55
 
56
56
 
57
57
  __version__ = _resolve_version()
@@ -1933,20 +1933,41 @@ def _route_count() -> int:
1933
1933
 
1934
1934
 
1935
1935
  async def _api_version_check(request, response):
1936
- """Proxy version check to PyPI to avoid browser CORS errors."""
1936
+ """Proxy the version check to PyPI so the browser dodges a CORS error.
1937
+
1938
+ A check that did not happen says so. This used to fall back to
1939
+ latest = current on any failure, and the toolbar renders that as a green
1940
+ "You are up to date!" -- so a developer several releases behind, on a
1941
+ machine with no route out, was told the opposite of the truth, and the
1942
+ toolbar's own "Could not check for updates" branch could never fire because
1943
+ the failure arrived as a success. latest is None when the check could not be
1944
+ made, and error says why.
1945
+
1946
+ The registry URL is TINA4_VERSION_CHECK_URL when set (a mirror, or a test's
1947
+ own server), else PyPI.
1948
+ """
1937
1949
  import urllib.request
1938
1950
  current = __version__
1939
- latest = current
1951
+ url = os.environ.get(
1952
+ "TINA4_VERSION_CHECK_URL", "https://pypi.org/pypi/tina4-python/json"
1953
+ )
1954
+
1955
+ def failed(why):
1956
+ return response({"current": current, "latest": None, "error": why})
1957
+
1940
1958
  try:
1941
1959
  req = urllib.request.Request(
1942
- "https://pypi.org/pypi/tina4-python/json",
1943
- headers={"User-Agent": "tina4-python/" + current},
1960
+ url, headers={"User-Agent": "tina4-python/" + current}
1944
1961
  )
1945
1962
  with urllib.request.urlopen(req, timeout=5) as resp:
1946
1963
  data = json.loads(resp.read().decode())
1947
- latest = data.get("info", {}).get("version", current)
1948
- except Exception:
1949
- pass # Offline or timeout — return current as latest
1964
+ except Exception as exc: # offline, timeout, DNS, unreadable body
1965
+ return failed(str(exc) or exc.__class__.__name__)
1966
+ # Reaching PyPI is not the same as learning the version: an answer we
1967
+ # cannot read a version out of is the same lie by another route.
1968
+ latest = (data.get("info") or {}).get("version")
1969
+ if not latest:
1970
+ return failed("PyPI did not report a version")
1950
1971
  return response({"current": current, "latest": latest})
1951
1972
 
1952
1973
 
@@ -2139,6 +2160,13 @@ def toolbar_js() -> str:
2139
2160
  el.className = 't4-ok';
2140
2161
  el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> &mdash; You are up to date!';
2141
2162
  }
2163
+ // A check that did not happen is not a clean bill of health. The server
2164
+ // sends latest: null when it could not reach the registry, and saying so is
2165
+ // the whole point -- "up to date" here would be a guess dressed as a fact.
2166
+ function couldNotCheck(el, why) {
2167
+ el.className = 't4-err';
2168
+ el.textContent = 'Could not check for updates' + (why ? ' (' + why + ')' : '');
2169
+ }
2142
2170
  function checkVersion() {
2143
2171
  if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
2144
2172
  modal.style.display = 'block';
@@ -2147,6 +2175,7 @@ def toolbar_js() -> str:
2147
2175
  el.textContent = 'Checking for updates...';
2148
2176
  fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
2149
2177
  var latest = d.latest, current = d.current;
2178
+ if (!latest) { couldNotCheck(el, d.error); return; }
2150
2179
  if (latest === current) { upToDate(el, latest); return; }
2151
2180
  var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
2152
2181
  var isNewer = false, i, c, l;