bustapi 0.13.9__tar.gz → 0.14.0__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 (262) hide show
  1. {bustapi-0.13.9 → bustapi-0.14.0}/CHANGELOG.md +7 -0
  2. {bustapi-0.13.9 → bustapi-0.14.0}/Cargo.lock +29 -33
  3. {bustapi-0.13.9 → bustapi-0.14.0}/Cargo.toml +1 -1
  4. {bustapi-0.13.9 → bustapi-0.14.0}/PKG-INFO +1 -1
  5. {bustapi-0.13.9 → bustapi-0.14.0}/docs/changelog.md +7 -0
  6. {bustapi-0.13.9 → bustapi-0.14.0}/pyproject.toml +1 -1
  7. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/__init__.py +1 -1
  8. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/app.py +34 -15
  9. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/login.py +39 -3
  10. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/core/helpers.py +11 -0
  11. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/dispatch.py +42 -12
  12. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/http/request.py +8 -0
  13. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/routing/blueprints.py +31 -16
  14. bustapi-0.14.0/tests/test_issue_29.py +119 -0
  15. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_login_manager.py +51 -0
  16. {bustapi-0.13.9 → bustapi-0.14.0}/- +0 -0
  17. {bustapi-0.13.9 → bustapi-0.14.0}/.github/FUNDING.yml +0 -0
  18. {bustapi-0.13.9 → bustapi-0.14.0}/.github/workflows/ci-multiplatform.yml +0 -0
  19. {bustapi-0.13.9 → bustapi-0.14.0}/.github/workflows/ci.yml +0 -0
  20. {bustapi-0.13.9 → bustapi-0.14.0}/.github/workflows/docs.yml +0 -0
  21. {bustapi-0.13.9 → bustapi-0.14.0}/.github/workflows/manual-publish.yml +0 -0
  22. {bustapi-0.13.9 → bustapi-0.14.0}/.gitignore +0 -0
  23. {bustapi-0.13.9 → bustapi-0.14.0}/.python-version +0 -0
  24. {bustapi-0.13.9 → bustapi-0.14.0}/AGENT_KNOWLEDGE.md +0 -0
  25. {bustapi-0.13.9 → bustapi-0.14.0}/Dockerfile +0 -0
  26. {bustapi-0.13.9 → bustapi-0.14.0}/LICENSE +0 -0
  27. {bustapi-0.13.9 → bustapi-0.14.0}/README.md +0 -0
  28. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/CACHE_BENCHMARK.md +0 -0
  29. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/README.md +0 -0
  30. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/app.py +0 -0
  31. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/benchmark_all.py +0 -0
  32. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/benchmark_avg.py +0 -0
  33. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/benchmark_cache.py +0 -0
  34. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/benchmark_combined.png +0 -0
  35. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/bustapi.md +0 -0
  36. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/bustapi_bench.py +0 -0
  37. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/bustapi_internal.py +0 -0
  38. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/comparison_bench.py +0 -0
  39. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/comprehensive_benchmark.py +0 -0
  40. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/gen_graph.py +0 -0
  41. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/quick_bench.py +0 -0
  42. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/resource_bench.py +0 -0
  43. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/rps_comparison.png +0 -0
  44. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/run_comparison_auto.py +0 -0
  45. {bustapi-0.13.9 → bustapi-0.14.0}/benchmarks/ws_benchmark.py +0 -0
  46. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/async.md +0 -0
  47. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/blueprints.md +0 -0
  48. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/dependency_injection.md +0 -0
  49. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/deployment.md +0 -0
  50. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/middleware.md +0 -0
  51. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/security.md +0 -0
  52. {bustapi-0.13.9 → bustapi-0.14.0}/docs/advanced/validation.md +0 -0
  53. {bustapi-0.13.9 → bustapi-0.14.0}/docs/api-reference.md +0 -0
  54. {bustapi-0.13.9 → bustapi-0.14.0}/docs/assets/logo.png +0 -0
  55. {bustapi-0.13.9 → bustapi-0.14.0}/docs/complete-api-reference.md +0 -0
  56. {bustapi-0.13.9 → bustapi-0.14.0}/docs/deployment-performance-guide.md +0 -0
  57. {bustapi-0.13.9 → bustapi-0.14.0}/docs/deployment.md +0 -0
  58. {bustapi-0.13.9 → bustapi-0.14.0}/docs/examples-guide.md +0 -0
  59. {bustapi-0.13.9 → bustapi-0.14.0}/docs/index.md +0 -0
  60. {bustapi-0.13.9 → bustapi-0.14.0}/docs/installation.md +0 -0
  61. {bustapi-0.13.9 → bustapi-0.14.0}/docs/overrides/.gitkeep +0 -0
  62. {bustapi-0.13.9 → bustapi-0.14.0}/docs/quickstart.md +0 -0
  63. {bustapi-0.13.9 → bustapi-0.14.0}/docs/release_track.md +0 -0
  64. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/advanced/async_support.html +0 -0
  65. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/advanced/blueprints.html +0 -0
  66. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/advanced/dependency_injection.html +0 -0
  67. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/advanced/middleware.html +0 -0
  68. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/advanced/validation.html +0 -0
  69. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/api/all.html +0 -0
  70. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/api/app.html +0 -0
  71. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/api/context.html +0 -0
  72. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/api/utilities.html +0 -0
  73. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/assets/logo.png +0 -0
  74. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/changelog.html +0 -0
  75. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/core_concepts/request_data.html +0 -0
  76. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/core_concepts/responses.html +0 -0
  77. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/core_concepts/routing.html +0 -0
  78. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/core_concepts/templates.html +0 -0
  79. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/examples/index.html +0 -0
  80. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/getting_started/index.html +0 -0
  81. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/getting_started/quickstart.html +0 -0
  82. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/index.html +0 -0
  83. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/script.js +0 -0
  84. {bustapi-0.13.9 → bustapi-0.14.0}/docs/site/style.css +0 -0
  85. {bustapi-0.13.9 → bustapi-0.14.0}/docs/stylesheets/extra.css +0 -0
  86. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/authentication.md +0 -0
  87. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/caching.md +0 -0
  88. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/cli.md +0 -0
  89. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/database.md +0 -0
  90. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/documentation.md +0 -0
  91. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/error_handling.md +0 -0
  92. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/jwt.md +0 -0
  93. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/logging.md +0 -0
  94. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/multiprocessing.md +0 -0
  95. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/request_data.md +0 -0
  96. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/responses.md +0 -0
  97. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/routing.md +0 -0
  98. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/sessions.md +0 -0
  99. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/static_files.md +0 -0
  100. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/templates.md +0 -0
  101. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/turbo-routes.md +0 -0
  102. {bustapi-0.13.9 → bustapi-0.14.0}/docs/user-guide/video-streaming.md +0 -0
  103. {bustapi-0.13.9 → bustapi-0.14.0}/docs/websockets.md +0 -0
  104. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/08_auto_docs.py +0 -0
  105. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/12_test_modes.py +0 -0
  106. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/13_error_handling.py +0 -0
  107. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/14_middleware.py +0 -0
  108. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/16_middleware_session.py +0 -0
  109. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/20_file_uploads.py +0 -0
  110. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/24_body_and_depends.py +0 -0
  111. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/27_video_stream.py +0 -0
  112. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/28_websocket.py +0 -0
  113. {bustapi-0.13.9 → bustapi-0.14.0}/examples/advanced/29_turbo_websocket.py +0 -0
  114. {bustapi-0.13.9 → bustapi-0.14.0}/examples/basics/01_hello_world.py +0 -0
  115. {bustapi-0.13.9 → bustapi-0.14.0}/examples/basics/02_parameters.py +0 -0
  116. {bustapi-0.13.9 → bustapi-0.14.0}/examples/basics/03_async.py +0 -0
  117. {bustapi-0.13.9 → bustapi-0.14.0}/examples/basics/04_request_data.py +0 -0
  118. {bustapi-0.13.9 → bustapi-0.14.0}/examples/basics/19_all_types.py +0 -0
  119. {bustapi-0.13.9 → bustapi-0.14.0}/examples/database/07_database_raw.py +0 -0
  120. {bustapi-0.13.9 → bustapi-0.14.0}/examples/database/10_database_sqlmodel.py +0 -0
  121. {bustapi-0.13.9 → bustapi-0.14.0}/examples/routing/06_blueprints.py +0 -0
  122. {bustapi-0.13.9 → bustapi-0.14.0}/examples/routing/09_complex_routing.py +0 -0
  123. {bustapi-0.13.9 → bustapi-0.14.0}/examples/routing/21_path_validation.py +0 -0
  124. {bustapi-0.13.9 → bustapi-0.14.0}/examples/routing/22_path_docs.py +0 -0
  125. {bustapi-0.13.9 → bustapi-0.14.0}/examples/routing/23_query_validation.py +0 -0
  126. {bustapi-0.13.9 → bustapi-0.14.0}/examples/routing/blueprint_with_jwt.py +0 -0
  127. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/10_rate_limit_demo.py +0 -0
  128. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/11_security_demo.py +0 -0
  129. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/17_jwt_auth.py +0 -0
  130. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/17_safe_features.py +0 -0
  131. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/18_jwt_cookies.py +0 -0
  132. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/18_safe_advanced.py +0 -0
  133. {bustapi-0.13.9 → bustapi-0.14.0}/examples/security/18_session_login.py +0 -0
  134. {bustapi-0.13.9 → bustapi-0.14.0}/examples/templates/05_templates.py +0 -0
  135. {bustapi-0.13.9 → bustapi-0.14.0}/examples/templates/26_complex_template.py +0 -0
  136. {bustapi-0.13.9 → bustapi-0.14.0}/examples/templates/templates/index.html +0 -0
  137. {bustapi-0.13.9 → bustapi-0.14.0}/examples/turbo/cache_test.py +0 -0
  138. {bustapi-0.13.9 → bustapi-0.14.0}/examples/turbo/fair_benchmark.py +0 -0
  139. {bustapi-0.13.9 → bustapi-0.14.0}/examples/turbo/typed_turbo_example.py +0 -0
  140. {bustapi-0.13.9 → bustapi-0.14.0}/examples/ws/app.py +0 -0
  141. {bustapi-0.13.9 → bustapi-0.14.0}/examples/ws/templates/index.html +0 -0
  142. {bustapi-0.13.9 → bustapi-0.14.0}/examples/ws/websockets_demo.py +0 -0
  143. {bustapi-0.13.9 → bustapi-0.14.0}/examples/ws/ws_limit.py +0 -0
  144. {bustapi-0.13.9 → bustapi-0.14.0}/mkdocs.yml +0 -0
  145. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/__init__.py +0 -0
  146. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/csrf.py +0 -0
  147. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/decorators.py +0 -0
  148. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/password.py +0 -0
  149. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/tokens.py +0 -0
  150. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/auth/user.py +0 -0
  151. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/cli/__init__.py +0 -0
  152. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/cli/main.py +0 -0
  153. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/context.py +0 -0
  154. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/core/__init__.py +0 -0
  155. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/core/asgi.py +0 -0
  156. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/core/exceptions.py +0 -0
  157. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/core/logging.py +0 -0
  158. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/dependencies.py +0 -0
  159. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/documentation/__init__.py +0 -0
  160. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/documentation/generator.py +0 -0
  161. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/extraction.py +0 -0
  162. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/fastapi_compat.py +0 -0
  163. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/hooks.py +0 -0
  164. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/http/__init__.py +0 -0
  165. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/http/response.py +0 -0
  166. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/jwt.py +0 -0
  167. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/logging.py +0 -0
  168. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/middleware.py +0 -0
  169. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/multiprocess.py +0 -0
  170. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/params.py +0 -0
  171. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/py.typed +0 -0
  172. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/responses.py +0 -0
  173. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/routing/__init__.py +0 -0
  174. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/routing/decorators.py +0 -0
  175. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/safe/__init__.py +0 -0
  176. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/safe/concurrency.py +0 -0
  177. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/safe/types.py +0 -0
  178. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/security/__init__.py +0 -0
  179. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/security/extension.py +0 -0
  180. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/security/rate_limit.py +0 -0
  181. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/server/__init__.py +0 -0
  182. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/server/runner.py +0 -0
  183. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/server/wsgi.py +0 -0
  184. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/sessions.py +0 -0
  185. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/templating/__init__.py +0 -0
  186. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/templating/engine.py +0 -0
  187. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/templating/mixin.py +0 -0
  188. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/testing/__init__.py +0 -0
  189. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/testing/client.py +0 -0
  190. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/utils.py +0 -0
  191. {bustapi-0.13.9 → bustapi-0.14.0}/python/bustapi/websocket.py +0 -0
  192. {bustapi-0.13.9 → bustapi-0.14.0}/run_all_examples.py +0 -0
  193. {bustapi-0.13.9 → bustapi-0.14.0}/scratch/test_abort.py +0 -0
  194. {bustapi-0.13.9 → bustapi-0.14.0}/scratch/test_headers.py +0 -0
  195. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/app.rs +0 -0
  196. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/converters.rs +0 -0
  197. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/handlers.rs +0 -0
  198. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/mod.rs +0 -0
  199. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/request.rs +0 -0
  200. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/typed_turbo.rs +0 -0
  201. {bustapi-0.13.9 → bustapi-0.14.0}/src/bindings/websocket.rs +0 -0
  202. {bustapi-0.13.9 → bustapi-0.14.0}/src/crypto.rs +0 -0
  203. {bustapi-0.13.9 → bustapi-0.14.0}/src/jwt.rs +0 -0
  204. {bustapi-0.13.9 → bustapi-0.14.0}/src/lib.rs +0 -0
  205. {bustapi-0.13.9 → bustapi-0.14.0}/src/logger.rs +0 -0
  206. {bustapi-0.13.9 → bustapi-0.14.0}/src/rate_limiter.rs +0 -0
  207. {bustapi-0.13.9 → bustapi-0.14.0}/src/request/methods.rs +0 -0
  208. {bustapi-0.13.9 → bustapi-0.14.0}/src/request/mod.rs +0 -0
  209. {bustapi-0.13.9 → bustapi-0.14.0}/src/request/tests.rs +0 -0
  210. {bustapi-0.13.9 → bustapi-0.14.0}/src/response/builders.rs +0 -0
  211. {bustapi-0.13.9 → bustapi-0.14.0}/src/response/methods.rs +0 -0
  212. {bustapi-0.13.9 → bustapi-0.14.0}/src/response/mod.rs +0 -0
  213. {bustapi-0.13.9 → bustapi-0.14.0}/src/router/handlers.rs +0 -0
  214. {bustapi-0.13.9 → bustapi-0.14.0}/src/router/matching.rs +0 -0
  215. {bustapi-0.13.9 → bustapi-0.14.0}/src/router/middleware.rs +0 -0
  216. {bustapi-0.13.9 → bustapi-0.14.0}/src/router/mod.rs +0 -0
  217. {bustapi-0.13.9 → bustapi-0.14.0}/src/router/tests.rs +0 -0
  218. {bustapi-0.13.9 → bustapi-0.14.0}/src/server/handlers.rs +0 -0
  219. {bustapi-0.13.9 → bustapi-0.14.0}/src/server/mod.rs +0 -0
  220. {bustapi-0.13.9 → bustapi-0.14.0}/src/server/startup.rs +0 -0
  221. {bustapi-0.13.9 → bustapi-0.14.0}/src/server/stream.rs +0 -0
  222. {bustapi-0.13.9 → bustapi-0.14.0}/src/static_files.rs +0 -0
  223. {bustapi-0.13.9 → bustapi-0.14.0}/src/templating.rs +0 -0
  224. {bustapi-0.13.9 → bustapi-0.14.0}/src/watcher.rs +0 -0
  225. {bustapi-0.13.9 → bustapi-0.14.0}/src/websocket/config.rs +0 -0
  226. {bustapi-0.13.9 → bustapi-0.14.0}/src/websocket/mod.rs +0 -0
  227. {bustapi-0.13.9 → bustapi-0.14.0}/src/websocket/session.rs +0 -0
  228. {bustapi-0.13.9 → bustapi-0.14.0}/src/websocket/turbo.rs +0 -0
  229. {bustapi-0.13.9 → bustapi-0.14.0}/static/style.css +0 -0
  230. {bustapi-0.13.9 → bustapi-0.14.0}/templates/base.html +0 -0
  231. {bustapi-0.13.9 → bustapi-0.14.0}/templates/complex.html +0 -0
  232. {bustapi-0.13.9 → bustapi-0.14.0}/tests/docs_test_all.py +0 -0
  233. {bustapi-0.13.9 → bustapi-0.14.0}/tests/docs_test_api_reference.py +0 -0
  234. {bustapi-0.13.9 → bustapi-0.14.0}/tests/docs_test_installation.py +0 -0
  235. {bustapi-0.13.9 → bustapi-0.14.0}/tests/docs_test_quickstart.py +0 -0
  236. {bustapi-0.13.9 → bustapi-0.14.0}/tests/docs_test_simple.py +0 -0
  237. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_050_features.py +0 -0
  238. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_async_hooks.py +0 -0
  239. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_async_login.py +0 -0
  240. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_auth.py +0 -0
  241. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_discussion_21.py +0 -0
  242. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_discussion_22.py +0 -0
  243. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_discussion_28.py +0 -0
  244. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_examples.py +0 -0
  245. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_hot_reload.py +0 -0
  246. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_issue_18.py +0 -0
  247. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_issue_23.py +0 -0
  248. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_issue_24.py +0 -0
  249. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_jwt.py +0 -0
  250. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_jwt_cookies.py +0 -0
  251. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_middleware.py +0 -0
  252. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_multiprocess_stability.py +0 -0
  253. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_new_examples.py +0 -0
  254. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_orm_conflict.py +0 -0
  255. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_path_standalone.py +0 -0
  256. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_path_validation.py +0 -0
  257. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_rate_limit_rust.py +0 -0
  258. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_safe.py +0 -0
  259. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_security_unit.py +0 -0
  260. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_sessions.py +0 -0
  261. {bustapi-0.13.9 → bustapi-0.14.0}/tests/test_zero_copy.py +0 -0
  262. {bustapi-0.13.9 → bustapi-0.14.0}/tests/verify_router.py +0 -0
@@ -2,9 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented here.
4
4
 
5
+ ## [0.14.0] - 2026-06-17
6
+
7
+ ### Added
8
+ - **Auth Login**: Enhanced `login_user()` to accept a raw `user_id` (string, integer, or UUID) directly as the user argument, resolving Discussion #27.
9
+ - **Blueprint Routing Stabilization**: Stabilized Blueprint routing structure (Issue #29) with support for relative `url_for()` endpoint resolution, nested lifecycle hooks (`before_request`, `after_request`, `teardown_request`), and blueprint-specific error handlers (`@bp.errorhandler`).
10
+
5
11
  ## [0.13.9] - 2026-06-12
6
12
 
7
13
  ### Fixed
14
+ - **Blueprint Routing**: Fixed an issue where routes registered via `app.register_blueprint()` were not added to the application's `url_map`. This ensures `url_for()` can correctly reverse-map Blueprint endpoints (Issue #29).
8
15
  - **Session Permanence**: Fixed a bug in `SessionMixin` where `session.permanent` was not serialized into the session payload, causing it to drop across requests. It now functions correctly as a persistent session setting.
9
16
  - **Login Manager Remember**: `login_user(user, remember=True)` now automatically sets `session.permanent = True` as expected, tying persistent logins to the application's permanent session lifetime configuration.
10
17
 
@@ -318,9 +318,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
318
318
 
319
319
  [[package]]
320
320
  name = "alloc-stdlib"
321
- version = "0.2.2"
321
+ version = "0.2.4"
322
322
  source = "registry+https://github.com/rust-lang/crates.io-index"
323
- checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
323
+ checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
324
324
  dependencies = [
325
325
  "alloc-no-stdlib",
326
326
  ]
@@ -405,9 +405,9 @@ dependencies = [
405
405
 
406
406
  [[package]]
407
407
  name = "brotli"
408
- version = "8.0.3"
408
+ version = "8.0.4"
409
409
  source = "registry+https://github.com/rust-lang/crates.io-index"
410
- checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610"
410
+ checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
411
411
  dependencies = [
412
412
  "alloc-no-stdlib",
413
413
  "alloc-stdlib",
@@ -416,9 +416,9 @@ dependencies = [
416
416
 
417
417
  [[package]]
418
418
  name = "brotli-decompressor"
419
- version = "5.0.1"
419
+ version = "5.0.3"
420
420
  source = "registry+https://github.com/rust-lang/crates.io-index"
421
- checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924"
421
+ checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
422
422
  dependencies = [
423
423
  "alloc-no-stdlib",
424
424
  "alloc-stdlib",
@@ -432,7 +432,7 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
432
432
 
433
433
  [[package]]
434
434
  name = "bustapi_core"
435
- version = "0.13.9"
435
+ version = "0.14.0"
436
436
  dependencies = [
437
437
  "actix-files",
438
438
  "actix-http",
@@ -491,9 +491,9 @@ dependencies = [
491
491
 
492
492
  [[package]]
493
493
  name = "cc"
494
- version = "1.2.63"
494
+ version = "1.2.64"
495
495
  source = "registry+https://github.com/rust-lang/crates.io-index"
496
- checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
496
+ checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
497
497
  dependencies = [
498
498
  "find-msvc-tools",
499
499
  "jobserver",
@@ -722,9 +722,6 @@ name = "deranged"
722
722
  version = "0.5.8"
723
723
  source = "registry+https://github.com/rust-lang/crates.io-index"
724
724
  checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
725
- dependencies = [
726
- "powerfmt",
727
- ]
728
725
 
729
726
  [[package]]
730
727
  name = "derive_more"
@@ -1329,9 +1326,9 @@ dependencies = [
1329
1326
 
1330
1327
  [[package]]
1331
1328
  name = "js-sys"
1332
- version = "0.3.100"
1329
+ version = "0.3.102"
1333
1330
  source = "registry+https://github.com/rust-lang/crates.io-index"
1334
- checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162"
1331
+ checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
1335
1332
  dependencies = [
1336
1333
  "cfg-if",
1337
1334
  "futures-util",
@@ -2132,9 +2129,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
2132
2129
 
2133
2130
  [[package]]
2134
2131
  name = "syn"
2135
- version = "2.0.117"
2132
+ version = "2.0.118"
2136
2133
  source = "registry+https://github.com/rust-lang/crates.io-index"
2137
- checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
2134
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
2138
2135
  dependencies = [
2139
2136
  "proc-macro2",
2140
2137
  "quote",
@@ -2202,12 +2199,11 @@ dependencies = [
2202
2199
 
2203
2200
  [[package]]
2204
2201
  name = "time"
2205
- version = "0.3.47"
2202
+ version = "0.3.49"
2206
2203
  source = "registry+https://github.com/rust-lang/crates.io-index"
2207
- checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
2204
+ checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
2208
2205
  dependencies = [
2209
2206
  "deranged",
2210
- "itoa",
2211
2207
  "num-conv",
2212
2208
  "powerfmt",
2213
2209
  "serde_core",
@@ -2217,15 +2213,15 @@ dependencies = [
2217
2213
 
2218
2214
  [[package]]
2219
2215
  name = "time-core"
2220
- version = "0.1.8"
2216
+ version = "0.1.9"
2221
2217
  source = "registry+https://github.com/rust-lang/crates.io-index"
2222
- checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
2218
+ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
2223
2219
 
2224
2220
  [[package]]
2225
2221
  name = "time-macros"
2226
- version = "0.2.27"
2222
+ version = "0.2.29"
2227
2223
  source = "registry+https://github.com/rust-lang/crates.io-index"
2228
- checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
2224
+ checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
2229
2225
  dependencies = [
2230
2226
  "num-conv",
2231
2227
  "time-core",
@@ -2438,9 +2434,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
2438
2434
 
2439
2435
  [[package]]
2440
2436
  name = "wasip2"
2441
- version = "1.0.3+wasi-0.2.9"
2437
+ version = "1.0.4+wasi-0.2.12"
2442
2438
  source = "registry+https://github.com/rust-lang/crates.io-index"
2443
- checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
2439
+ checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
2444
2440
  dependencies = [
2445
2441
  "wit-bindgen 0.57.1",
2446
2442
  ]
@@ -2456,9 +2452,9 @@ dependencies = [
2456
2452
 
2457
2453
  [[package]]
2458
2454
  name = "wasm-bindgen"
2459
- version = "0.2.123"
2455
+ version = "0.2.125"
2460
2456
  source = "registry+https://github.com/rust-lang/crates.io-index"
2461
- checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563"
2457
+ checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
2462
2458
  dependencies = [
2463
2459
  "cfg-if",
2464
2460
  "once_cell",
@@ -2469,9 +2465,9 @@ dependencies = [
2469
2465
 
2470
2466
  [[package]]
2471
2467
  name = "wasm-bindgen-macro"
2472
- version = "0.2.123"
2468
+ version = "0.2.125"
2473
2469
  source = "registry+https://github.com/rust-lang/crates.io-index"
2474
- checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc"
2470
+ checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
2475
2471
  dependencies = [
2476
2472
  "quote",
2477
2473
  "wasm-bindgen-macro-support",
@@ -2479,9 +2475,9 @@ dependencies = [
2479
2475
 
2480
2476
  [[package]]
2481
2477
  name = "wasm-bindgen-macro-support"
2482
- version = "0.2.123"
2478
+ version = "0.2.125"
2483
2479
  source = "registry+https://github.com/rust-lang/crates.io-index"
2484
- checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b"
2480
+ checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
2485
2481
  dependencies = [
2486
2482
  "bumpalo",
2487
2483
  "proc-macro2",
@@ -2492,9 +2488,9 @@ dependencies = [
2492
2488
 
2493
2489
  [[package]]
2494
2490
  name = "wasm-bindgen-shared"
2495
- version = "0.2.123"
2491
+ version = "0.2.125"
2496
2492
  source = "registry+https://github.com/rust-lang/crates.io-index"
2497
- checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92"
2493
+ checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
2498
2494
  dependencies = [
2499
2495
  "unicode-ident",
2500
2496
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "bustapi_core"
3
- version = "0.13.9"
3
+ version = "0.14.0"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bustapi
3
- Version: 0.13.9
3
+ Version: 0.14.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -2,9 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented here.
4
4
 
5
+ ## [0.14.0] - 2026-06-17
6
+
7
+ ### Added
8
+ - **Auth Login**: Enhanced `login_user()` to accept a raw `user_id` (string, integer, or UUID) directly as the user argument, resolving Discussion #27.
9
+ - **Blueprint Routing Stabilization**: Stabilized Blueprint routing structure (Issue #29) with support for relative `url_for()` endpoint resolution, nested lifecycle hooks (`before_request`, `after_request`, `teardown_request`), and blueprint-specific error handlers (`@bp.errorhandler`).
10
+
5
11
  ## [0.13.9] - 2026-06-12
6
12
 
7
13
  ### Fixed
14
+ - **Blueprint Routing**: Fixed an issue where routes registered via `app.register_blueprint()` were not added to the application's `url_map`. This ensures `url_for()` can correctly reverse-map Blueprint endpoints (Issue #29).
8
15
  - **Session Permanence**: Fixed a bug in `SessionMixin` where `session.permanent` was not serialized into the session payload, causing it to drop across requests. It now functions correctly as a persistent session setting.
9
16
  - **Login Manager Remember**: `login_user(user, remember=True)` now automatically sets `session.permanent = True` as expected, tying persistent logins to the application's permanent session lifetime configuration.
10
17
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "bustapi"
7
- version = "0.13.9"
7
+ version = "0.14.0"
8
8
  description = "High-performance Python web framework with Rust backend"
9
9
  authors = [
10
10
  {name = "GrandpaEJ", email = ""}
@@ -22,7 +22,7 @@ import platform
22
22
  import sys
23
23
  from http import HTTPStatus
24
24
 
25
- __version__ = "0.13.9"
25
+ __version__ = "0.14.0"
26
26
  __author__ = "BustAPI" # with GrandpaEJ
27
27
  __email__ = ""
28
28
 
@@ -144,7 +144,6 @@ class BustAPI(
144
144
  raise RuntimeError(f"Failed to initialize Rust backend: {e}") from e
145
145
 
146
146
  def register_blueprint(self, blueprint: Blueprint, **options) -> None:
147
- url_prefix = options.get("url_prefix", blueprint.url_prefix)
148
147
  self.blueprints[blueprint.name] = blueprint
149
148
 
150
149
  # Register blueprint template context processors
@@ -155,20 +154,8 @@ class BustAPI(
155
154
  blueprint.app_context_processor_funcs
156
155
  )
157
156
 
158
- for rule, endpoint, view_func, methods in blueprint.deferred_functions:
159
- if url_prefix:
160
- rule = url_prefix.rstrip("/") + "/" + rule.lstrip("/")
161
- full_endpoint = f"{blueprint.name}.{endpoint}"
162
- self.view_functions[full_endpoint] = view_func
163
- for method in methods:
164
- if inspect.iscoroutinefunction(view_func):
165
- self._rust_app.add_async_route(
166
- method, rule, create_async_wrapper(self, view_func, rule)
167
- )
168
- else:
169
- self._rust_app.add_route(
170
- method, rule, create_sync_wrapper(self, view_func, rule)
171
- )
157
+ # Register all deferred functions (routes, hooks, etc.)
158
+ blueprint.register(self, options)
172
159
 
173
160
  def add_websocket_route(
174
161
  self, path: str, handler: Any, config: Optional[Any] = None
@@ -184,6 +171,38 @@ class BustAPI(
184
171
  return make_response(*args)
185
172
 
186
173
  def _handle_exception(self, exception: Exception) -> Response:
174
+ from .http.request import request
175
+
176
+ # 1. Try blueprint-specific error handlers first
177
+ bp = (
178
+ self.blueprints.get(request.blueprint)
179
+ if (request and request.blueprint)
180
+ else None
181
+ )
182
+ if bp:
183
+ for exc_class_or_code, handler in bp.error_handler_spec.items():
184
+ if isinstance(exc_class_or_code, type) and isinstance(
185
+ exception, exc_class_or_code
186
+ ):
187
+ rv = handler(exception)
188
+ return (
189
+ self._make_response(*rv)
190
+ if isinstance(rv, tuple)
191
+ else self._make_response(rv)
192
+ )
193
+ elif isinstance(exc_class_or_code, int):
194
+ if (
195
+ hasattr(exception, "code")
196
+ and exception.code == exc_class_or_code
197
+ ):
198
+ rv = handler(exception)
199
+ return (
200
+ self._make_response(*rv)
201
+ if isinstance(rv, tuple)
202
+ else self._make_response(rv)
203
+ )
204
+
205
+ # 2. Try app-wide error handlers
187
206
  for exc_class_or_code, handler in self.error_handler_spec.items():
188
207
  if isinstance(exc_class_or_code, type) and isinstance(
189
208
  exception, exc_class_or_code
@@ -94,7 +94,7 @@ def login_user(user, remember: bool = False, fresh: bool = True) -> bool:
94
94
  Log in a user.
95
95
 
96
96
  Args:
97
- user: User object (must have get_id() method)
97
+ user: User object (must have get_id() method) or user_id (str, int)
98
98
  remember: Create persistent session
99
99
  fresh: Mark session as fresh (from password login)
100
100
 
@@ -106,6 +106,8 @@ def login_user(user, remember: bool = False, fresh: bool = True) -> bool:
106
106
  if not session:
107
107
  return False
108
108
 
109
+ is_user_id = False
110
+
109
111
  # Get user ID
110
112
  # 1. Support custom conflict-free get_login_id() first
111
113
  user_id = getattr(user, "get_login_id", lambda: None)()
@@ -122,7 +124,14 @@ def login_user(user, remember: bool = False, fresh: bool = True) -> bool:
122
124
  try:
123
125
  user_id = str(user.id)
124
126
  except AttributeError:
125
- return False
127
+ # 5. Check if `user` itself is the user ID (string, integer, etc.)
128
+ import uuid
129
+
130
+ if isinstance(user, (str, int, uuid.UUID)):
131
+ user_id = str(user)
132
+ is_user_id = True
133
+ else:
134
+ return False
126
135
 
127
136
  # Get login manager config
128
137
  login_manager = getattr(request, "app", None)
@@ -146,7 +155,34 @@ def login_user(user, remember: bool = False, fresh: bool = True) -> bool:
146
155
  session.permanent = True
147
156
 
148
157
  # Update request
149
- request._login_user = user
158
+ if is_user_id:
159
+ loaded_user = None
160
+ if login_manager and login_manager._user_loader_callback:
161
+ import inspect
162
+
163
+ try:
164
+ res = login_manager._user_loader_callback(user_id)
165
+ if not inspect.isawaitable(res):
166
+ loaded_user = res
167
+ except Exception:
168
+ pass
169
+
170
+ if loaded_user is not None:
171
+ request._login_user = loaded_user
172
+ else:
173
+ from .user import BaseUser
174
+
175
+ class SimpleUser(BaseUser):
176
+ def __init__(self, uid: str):
177
+ self.id = uid
178
+
179
+ def get_id(self) -> str:
180
+ return self.id
181
+
182
+ request._login_user = SimpleUser(user_id)
183
+ else:
184
+ request._login_user = user
185
+
150
186
  request._login_fresh = fresh
151
187
 
152
188
  return True
@@ -59,6 +59,17 @@ def url_for(endpoint: str, **values) -> str:
59
59
  # Get current application to access url_map
60
60
  app = _get_current_object()
61
61
 
62
+ # Handle relative endpoint (e.g. '.index' -> 'blueprint.index')
63
+ if endpoint.startswith("."):
64
+ from ..http.request import request
65
+
66
+ if request and request.blueprint:
67
+ endpoint = f"{request.blueprint}{endpoint}"
68
+ else:
69
+ raise RuntimeError(
70
+ "Working outside of request context or no blueprint active"
71
+ )
72
+
62
73
  # Find rule for endpoint
63
74
  rule = None
64
75
  for r, options in app.url_map.items():
@@ -130,8 +130,12 @@ def create_sync_wrapper(app: "BustAPI", handler: Callable, rule: str) -> Callabl
130
130
  request.session = session
131
131
 
132
132
  # 3. Before Request Hooks
133
- if app.before_request_funcs:
134
- for before_func in app.before_request_funcs:
133
+ bp = app.blueprints.get(request.blueprint) if request.blueprint else None
134
+ before_funcs = (
135
+ bp.before_request_funcs if bp else []
136
+ ) + app.before_request_funcs
137
+ if before_funcs:
138
+ for before_func in before_funcs:
135
139
  res = before_func()
136
140
  if inspect.isawaitable(res):
137
141
  res = async_to_sync(res)
@@ -262,8 +266,12 @@ def create_sync_wrapper(app: "BustAPI", handler: Callable, rule: str) -> Callabl
262
266
  # 5. Pipeline Cleanup and Hooks
263
267
  if app.middleware_manager.middlewares:
264
268
  response = app.middleware_manager.process_response(request, response)
265
- if app.after_request_funcs:
266
- for after_func in app.after_request_funcs:
269
+ bp = app.blueprints.get(request.blueprint) if request.blueprint else None
270
+ after_funcs = (
271
+ bp.after_request_funcs if bp else []
272
+ ) + app.after_request_funcs
273
+ if after_funcs:
274
+ for after_func in after_funcs:
267
275
  res = after_func(response)
268
276
  if inspect.isawaitable(res):
269
277
  res = async_to_sync(res)
@@ -276,8 +284,12 @@ def create_sync_wrapper(app: "BustAPI", handler: Callable, rule: str) -> Callabl
276
284
  except Exception as e:
277
285
  return app._response_to_rust_format(app._handle_exception(e))
278
286
  finally:
279
- if app.teardown_request_funcs:
280
- for f in app.teardown_request_funcs:
287
+ bp = app.blueprints.get(request.blueprint) if request.blueprint else None
288
+ teardown_funcs = (
289
+ bp.teardown_request_funcs if bp else []
290
+ ) + app.teardown_request_funcs
291
+ if teardown_funcs:
292
+ for f in teardown_funcs:
281
293
  try:
282
294
  res = f(None)
283
295
  if inspect.isawaitable(res):
@@ -325,8 +337,14 @@ def create_async_wrapper(app: "BustAPI", handler: Callable, rule: str) -> Callab
325
337
  session = app.session_interface.open_session(app, request)
326
338
  request.session = session
327
339
 
328
- if app.before_request_funcs:
329
- for before_func in app.before_request_funcs:
340
+ bp = (
341
+ app.blueprints.get(request.blueprint) if request.blueprint else None
342
+ )
343
+ before_funcs = (
344
+ bp.before_request_funcs if bp else []
345
+ ) + app.before_request_funcs
346
+ if before_funcs:
347
+ for before_func in before_funcs:
330
348
  res = before_func()
331
349
  if inspect.isawaitable(res):
332
350
  res = await res
@@ -424,8 +442,14 @@ def create_async_wrapper(app: "BustAPI", handler: Callable, rule: str) -> Callab
424
442
  response = app.middleware_manager.process_response(
425
443
  request, response
426
444
  )
427
- if app.after_request_funcs:
428
- for after_func in app.after_request_funcs:
445
+ bp = (
446
+ app.blueprints.get(request.blueprint) if request.blueprint else None
447
+ )
448
+ after_funcs = (
449
+ bp.after_request_funcs if bp else []
450
+ ) + app.after_request_funcs
451
+ if after_funcs:
452
+ for after_func in after_funcs:
429
453
  res = after_func(response)
430
454
  if inspect.isawaitable(res):
431
455
  res = await res
@@ -438,8 +462,14 @@ def create_async_wrapper(app: "BustAPI", handler: Callable, rule: str) -> Callab
438
462
  except Exception as e:
439
463
  return app._handle_exception(e)
440
464
  finally:
441
- if app.teardown_request_funcs:
442
- for f in app.teardown_request_funcs:
465
+ bp = (
466
+ app.blueprints.get(request.blueprint) if request.blueprint else None
467
+ )
468
+ teardown_funcs = (
469
+ bp.teardown_request_funcs if bp else []
470
+ ) + app.teardown_request_funcs
471
+ if teardown_funcs:
472
+ for f in teardown_funcs:
443
473
  try:
444
474
  res = f(None)
445
475
  if inspect.isawaitable(res):
@@ -49,6 +49,7 @@ class Request:
49
49
  self._user = None
50
50
  self._login_user = None
51
51
  self._login_fresh = None
52
+ self.endpoint = None
52
53
 
53
54
  @classmethod
54
55
  def _from_rust_request(cls, rust_request) -> "Request":
@@ -363,6 +364,13 @@ class Request:
363
364
  """Set the authenticated user."""
364
365
  self._user = value
365
366
 
367
+ @property
368
+ def blueprint(self) -> Optional[str]:
369
+ """The name of the current blueprint, if any."""
370
+ if self.endpoint and "." in self.endpoint:
371
+ return self.endpoint.split(".", 1)[0]
372
+ return None
373
+
366
374
 
367
375
  class EnvironHeaders:
368
376
  """
@@ -62,7 +62,7 @@ class Blueprint:
62
62
  self.cli_group = cli_group
63
63
 
64
64
  # Deferred functions to be registered when blueprint is registered with app
65
- self.deferred_functions: List[Tuple[str, str, Callable, List[str]]] = []
65
+ self.deferred_functions: List[Callable[["BlueprintSetupState"], None]] = []
66
66
 
67
67
  # Error handlers
68
68
  self.error_handler_spec: Dict[Union[int, type], Callable] = {}
@@ -95,14 +95,23 @@ class Blueprint:
95
95
 
96
96
  def decorator(f: Callable) -> Callable:
97
97
  endpoint = options.pop("endpoint", f.__name__)
98
- methods = options.pop("methods", ["GET"])
99
-
100
- # Store route info for later registration
101
- self.deferred_functions.append((rule, endpoint, f, methods))
98
+ self.add_url_rule(rule, endpoint, f, **options)
102
99
  return f
103
100
 
104
101
  return decorator
105
102
 
103
+ def add_url_rule(
104
+ self,
105
+ rule: str,
106
+ endpoint: Optional[str] = None,
107
+ view_func: Optional[Callable] = None,
108
+ **options,
109
+ ) -> None:
110
+ """
111
+ Register a URL rule with the blueprint.
112
+ """
113
+ self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
114
+
106
115
  def get(self, rule: str, **options) -> Callable:
107
116
  """Convenience decorator for GET routes."""
108
117
  return self.route(rule, methods=["GET"], **options)
@@ -181,7 +190,7 @@ class Blueprint:
181
190
  Returns:
182
191
  The original function
183
192
  """
184
- self.before_app_request_funcs.append(f)
193
+ self.record_once(lambda s: s.app.before_request_funcs.append(f))
185
194
  return f
186
195
 
187
196
  def after_app_request(self, f: Callable) -> Callable:
@@ -194,7 +203,7 @@ class Blueprint:
194
203
  Returns:
195
204
  The original function
196
205
  """
197
- self.after_app_request_funcs.append(f)
206
+ self.record_once(lambda s: s.app.after_request_funcs.append(f))
198
207
  return f
199
208
 
200
209
  def teardown_app_request(self, f: Callable) -> Callable:
@@ -208,7 +217,7 @@ class Blueprint:
208
217
  Returns:
209
218
  The original function
210
219
  """
211
- self.teardown_app_request_funcs.append(f)
220
+ self.record_once(lambda s: s.app.teardown_request_funcs.append(f))
212
221
  return f
213
222
 
214
223
  def errorhandler(self, code_or_exception: Union[int, type]) -> Callable:
@@ -240,8 +249,7 @@ class Blueprint:
240
249
  """
241
250
 
242
251
  def decorator(f: Callable) -> Callable:
243
- # This will be registered with the app when blueprint is registered
244
- self.error_handler_spec[f"app_{code_or_exception}"] = f
252
+ self.record_once(lambda s: s.app.errorhandler(code_or_exception)(f))
245
253
  return f
246
254
 
247
255
  return decorator
@@ -331,8 +339,7 @@ class Blueprint:
331
339
  Args:
332
340
  func: Function to record
333
341
  """
334
- # TODO: Implement blueprint recording system
335
- pass
342
+ self.deferred_functions.append(func)
336
343
 
337
344
  def record_once(self, func: Callable) -> None:
338
345
  """
@@ -341,8 +348,7 @@ class Blueprint:
341
348
  Args:
342
349
  func: Function to record
343
350
  """
344
- # TODO: Implement blueprint recording system
345
- pass
351
+ self.deferred_functions.append(func)
346
352
 
347
353
  def make_setup_state(self, app, options, first_registration: bool = False):
348
354
  """
@@ -377,6 +383,9 @@ class Blueprint:
377
383
  view_func=app.send_static_file,
378
384
  )
379
385
 
386
+ for deferred in self.deferred_functions:
387
+ deferred(state)
388
+
380
389
  @property
381
390
  def has_static_folder(self) -> bool:
382
391
  """Check if blueprint has a static folder."""
@@ -455,6 +464,8 @@ class BlueprintSetupState:
455
464
  self.first_registration = first_registration
456
465
 
457
466
  self.url_prefix = options.get("url_prefix")
467
+ if self.url_prefix is None:
468
+ self.url_prefix = blueprint.url_prefix
458
469
  self.subdomain = options.get("subdomain")
459
470
  self.url_defaults = options.get("url_defaults")
460
471
 
@@ -475,10 +486,14 @@ class BlueprintSetupState:
475
486
  **options: Additional options
476
487
  """
477
488
  if self.url_prefix is not None:
489
+ prefix = self.url_prefix.rstrip("/")
490
+ if not prefix.startswith("/"):
491
+ prefix = f"/{prefix}"
492
+
478
493
  if rule:
479
- rule = f"{self.url_prefix.rstrip('/')}/{rule.lstrip('/')}"
494
+ rule = f"{prefix}/{rule.lstrip('/')}"
480
495
  else:
481
- rule = self.url_prefix
496
+ rule = prefix
482
497
 
483
498
  options.setdefault("subdomain", self.subdomain)
484
499
  if endpoint is None: