tina4-python 3.13.29__tar.gz → 3.13.30__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 (157) hide show
  1. {tina4_python-3.13.29 → tina4_python-3.13.30}/PKG-INFO +1 -1
  2. {tina4_python-3.13.29 → tina4_python-3.13.30}/pyproject.toml +1 -1
  3. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/CLAUDE.md +1 -1
  4. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/__init__.py +1 -1
  5. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/router.py +48 -7
  6. {tina4_python-3.13.29 → tina4_python-3.13.30}/.gitignore +0 -0
  7. {tina4_python-3.13.29 → tina4_python-3.13.30}/README.md +0 -0
  8. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/rate_limiter.py +0 -0
  22. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/core/server.py +0 -0
  25. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/mongodb.py +0 -0
  31. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/dev_admin/__init__.py +0 -0
  39. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/dev_admin/metrics.py +0 -0
  40. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/dev_admin/plan.py +0 -0
  41. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/dev_admin/project_index.py +0 -0
  42. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/docs.py +0 -0
  43. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/dotenv/__init__.py +0 -0
  44. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/env.py +0 -0
  45. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/frond/FROND.md +0 -0
  46. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/frond/__init__.py +0 -0
  47. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/frond/engine.py +0 -0
  48. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/auth/meta.json +0 -0
  49. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  50. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/database/meta.json +0 -0
  51. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  52. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/error-overlay/meta.json +0 -0
  53. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  54. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/orm/meta.json +0 -0
  55. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  56. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  57. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/queue/meta.json +0 -0
  58. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  59. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/rest-api/meta.json +0 -0
  60. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  61. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/templates/meta.json +0 -0
  62. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  63. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  64. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/graphql/__init__.py +0 -0
  65. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/i18n/__init__.py +0 -0
  66. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/mcp/__init__.py +0 -0
  67. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/mcp/protocol.py +0 -0
  68. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/mcp/tools.py +0 -0
  69. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/messenger/__init__.py +0 -0
  70. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/migration/__init__.py +0 -0
  71. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/migration/runner.py +0 -0
  72. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/orm/__init__.py +0 -0
  73. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/orm/fields.py +0 -0
  74. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/orm/model.py +0 -0
  75. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/__feedback/widget.js +0 -0
  76. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/css/tina4.css +0 -0
  77. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/css/tina4.min.css +0 -0
  78. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/favicon.ico +0 -0
  79. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/images/logo.svg +0 -0
  80. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  81. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/js/frond.js +0 -0
  82. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/js/frond.min.js +0 -0
  83. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  84. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  85. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/js/tina4.min.js +0 -0
  86. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/js/tina4js.min.js +0 -0
  87. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/swagger/index.html +0 -0
  88. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  89. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/query_builder/__init__.py +0 -0
  90. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue/__init__.py +0 -0
  91. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue/job.py +0 -0
  92. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue/kafka_backend.py +0 -0
  93. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue/lite_backend.py +0 -0
  94. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue/mongo_backend.py +0 -0
  95. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue/rabbitmq_backend.py +0 -0
  96. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue_backends/__init__.py +0 -0
  97. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue_backends/kafka_backend.py +0 -0
  98. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue_backends/mongo_backend.py +0 -0
  99. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  100. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/__init__.py +0 -0
  101. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  102. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_badges.scss +0 -0
  103. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  104. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_cards.scss +0 -0
  105. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_forms.scss +0 -0
  106. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_grid.scss +0 -0
  107. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_modals.scss +0 -0
  108. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_nav.scss +0 -0
  109. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_reset.scss +0 -0
  110. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_tables.scss +0 -0
  111. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_typography.scss +0 -0
  112. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  113. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/_variables.scss +0 -0
  114. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/base.scss +0 -0
  115. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/colors.scss +0 -0
  116. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/scss/tina4css/tina4.scss +0 -0
  117. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/seeder/__init__.py +0 -0
  118. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/service/__init__.py +0 -0
  119. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/session/__init__.py +0 -0
  120. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/session_handlers/__init__.py +0 -0
  121. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  122. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/session_handlers/redis_handler.py +0 -0
  123. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/session_handlers/valkey_handler.py +0 -0
  124. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/swagger/__init__.py +0 -0
  125. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/components/crud.twig +0 -0
  126. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  127. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  128. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/docker/python/Dockerfile +0 -0
  129. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  130. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/302.twig +0 -0
  131. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/401.twig +0 -0
  132. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/403.twig +0 -0
  133. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/404.twig +0 -0
  134. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/500.twig +0 -0
  135. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/502.twig +0 -0
  136. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/503.twig +0 -0
  137. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/errors/base.twig +0 -0
  138. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/frontend/README.md +0 -0
  139. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/templates/readme.md +0 -0
  140. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/test/__init__.py +0 -0
  141. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/test_client/__init__.py +0 -0
  142. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  143. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  144. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  145. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  146. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  147. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  148. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  149. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  150. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  151. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  152. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  153. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  154. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/validator/__init__.py +0 -0
  155. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/websocket/__init__.py +0 -0
  156. {tina4_python-3.13.29 → tina4_python-3.13.30}/tina4_python/websocket/backplane.py +0 -0
  157. {tina4_python-3.13.29 → tina4_python-3.13.30}/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.29
3
+ Version: 3.13.30
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.29"
3
+ version = "3.13.30"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -529,7 +529,7 @@ TINA4_SESSION_SAMESITE=Lax # SameSite attribute for session cookie
529
529
 
530
530
  ### Authentication & Security
531
531
  - Use `Auth.hash_password()` from `tina4_python.auth` to hash passwords — never use hashlib directly.
532
- - Use `Auth.check_password(hash, password)` from `tina4_python.auth` to verify passwords.
532
+ - Use `Auth.check_password(password, hash)` from `tina4_python.auth` to verify passwords.
533
533
 
534
534
  ## Templates (Twig)
535
535
 
@@ -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.13.29"
11
+ __version__ = "3.13.30"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -171,11 +171,12 @@ class Router:
171
171
  - ``event`` is ``"open"``, ``"message"``, or ``"close"``
172
172
  - ``data`` is the message payload (str for message, None for open/close)
173
173
  """
174
- pattern, param_names = _compile_pattern(path)
174
+ pattern, param_names, param_types = _compile_pattern(path)
175
175
  route = {
176
176
  "path": path,
177
177
  "pattern": pattern,
178
178
  "param_names": param_names,
179
+ "param_types": param_types,
179
180
  "handler": handler,
180
181
  }
181
182
  _ws_routes.append(route)
@@ -188,8 +189,9 @@ class Router:
188
189
  m = route["pattern"].match(path)
189
190
  if m:
190
191
  params = {}
192
+ _types = route.get("param_types", {})
191
193
  for i, name in enumerate(route["param_names"]):
192
- params[name] = m.group(i + 1)
194
+ params[name] = _cast_param(m.group(i + 1), _types.get(name))
193
195
  return route, params
194
196
  return None, {}
195
197
 
@@ -303,7 +305,7 @@ class Router:
303
305
  combined_mw = list(cls._group_middleware) + list(handler_mw) + route_mw
304
306
  effective_middleware = combined_mw or []
305
307
 
306
- pattern, param_names = _compile_pattern(path)
308
+ pattern, param_names, param_types = _compile_pattern(path)
307
309
 
308
310
  # Auth default: GET=public, writes=secured (unless custom middleware handles auth)
309
311
  m = method.upper()
@@ -329,6 +331,7 @@ class Router:
329
331
  "path": path,
330
332
  "pattern": pattern,
331
333
  "param_names": param_names,
334
+ "param_types": param_types,
332
335
  "handler": handler,
333
336
  "middleware": effective_middleware,
334
337
  "auth_required": auth_required,
@@ -359,8 +362,9 @@ class Router:
359
362
  m = route["pattern"].match(path)
360
363
  if m:
361
364
  params = {}
365
+ _types = route.get("param_types", {})
362
366
  for i, name in enumerate(route["param_names"]):
363
- params[name] = m.group(i + 1)
367
+ params[name] = _cast_param(m.group(i + 1), _types.get(name))
364
368
  return route, params
365
369
 
366
370
  # Second pass: HEAD auto-fallback to GET when no HEAD route registered
@@ -371,8 +375,9 @@ class Router:
371
375
  m = route["pattern"].match(path)
372
376
  if m:
373
377
  params = {}
378
+ _types = route.get("param_types", {})
374
379
  for i, name in enumerate(route["param_names"]):
375
- params[name] = m.group(i + 1)
380
+ params[name] = _cast_param(m.group(i + 1), _types.get(name))
376
381
  return route, params
377
382
 
378
383
  return None, {}
@@ -454,8 +459,36 @@ _TYPE_PATTERNS = {
454
459
  ".*": ".+",
455
460
  }
456
461
 
462
+ # Type names whose captured value is coerced from str to a Python scalar before
463
+ # it reaches the handler. Mirrors Ruby's ``cast_param`` (lib/tina4/router.rb):
464
+ # ``int``/``integer`` → ``int``, ``float``/``number`` → ``float``. Every other
465
+ # type (string, alpha, alnum, slug, uuid, path) and untyped params stay ``str``.
466
+ _TYPE_CASTS = {
467
+ "int": int,
468
+ "integer": int,
469
+ "float": float,
470
+ "number": float,
471
+ }
472
+
457
473
 
458
- def _compile_pattern(path: str) -> tuple[re.Pattern, list[str]]:
474
+ def _cast_param(value: str, type_hint: str | None):
475
+ """Coerce a captured route param to its declared Python type.
476
+
477
+ The URL regex already guarantees the segment matches the type's pattern
478
+ (e.g. ``{id:int}`` only matches digits), so the cast normally can't fail.
479
+ We still guard it: a coercion failure must never crash routing — fall back
480
+ to the raw string, exactly as Ruby leaves unknown types untouched.
481
+ """
482
+ caster = _TYPE_CASTS.get(type_hint)
483
+ if caster is None:
484
+ return value
485
+ try:
486
+ return caster(value)
487
+ except (TypeError, ValueError):
488
+ return value
489
+
490
+
491
+ def _compile_pattern(path: str) -> tuple[re.Pattern, list[str], dict[str, str]]:
459
492
  """Convert a route path to a regex pattern.
460
493
 
461
494
  Supports:
@@ -468,9 +501,16 @@ def _compile_pattern(path: str) -> tuple[re.Pattern, list[str]]:
468
501
  /api/files/{p:path} → greedy (matches remaining path)
469
502
  /api/docs/* → bare-wildcard catch-all (key "*")
470
503
 
504
+ Returns ``(pattern, param_names, param_types)`` where ``param_types`` maps
505
+ each declared name to its type hint (``"int"``, ``"float"``, …). Untyped
506
+ params and the ``*`` wildcard are absent from the map. The map drives
507
+ coercion in :meth:`Router.match` so typed params arrive at the handler as
508
+ Python scalars (mirrors Ruby's ``cast_param``).
509
+
471
510
  Unknown type names raise ``ValueError`` at route registration time.
472
511
  """
473
512
  param_names = []
513
+ param_types: dict[str, str] = {}
474
514
  regex_parts = []
475
515
 
476
516
  segments = path.strip("/").split("/")
@@ -490,6 +530,7 @@ def _compile_pattern(path: str) -> tuple[re.Pattern, list[str]]:
490
530
  f"Valid types: {', '.join(sorted(k for k in _TYPE_PATTERNS if k != '.*'))}."
491
531
  )
492
532
  regex_parts.append("(" + _TYPE_PATTERNS[type_hint] + ")")
533
+ param_types[name] = type_hint
493
534
  else:
494
535
  name = inner
495
536
  regex_parts.append("([^/]+)")
@@ -498,7 +539,7 @@ def _compile_pattern(path: str) -> tuple[re.Pattern, list[str]]:
498
539
  regex_parts.append(re.escape(segment))
499
540
 
500
541
  pattern_str = "^/" + "/".join(regex_parts) + "/?$"
501
- return re.compile(pattern_str), param_names
542
+ return re.compile(pattern_str), param_names, param_types
502
543
 
503
544
 
504
545
  # Decorator functions — the public API
File without changes