bustapi 0.11.1__tar.gz → 0.12.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 (252) hide show
  1. {bustapi-0.11.1 → bustapi-0.12.0}/CHANGELOG.md +13 -0
  2. {bustapi-0.11.1 → bustapi-0.12.0}/Cargo.lock +7 -7
  3. {bustapi-0.11.1 → bustapi-0.12.0}/Cargo.toml +1 -1
  4. {bustapi-0.11.1 → bustapi-0.12.0}/PKG-INFO +1 -1
  5. {bustapi-0.11.1 → bustapi-0.12.0}/docs/changelog.md +13 -0
  6. {bustapi-0.11.1 → bustapi-0.12.0}/pyproject.toml +1 -1
  7. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/__init__.py +1 -1
  8. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/app.py +37 -2
  9. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/documentation/generator.py +76 -31
  10. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/http/response.py +10 -0
  11. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/params.py +36 -3
  12. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/app.rs +13 -0
  13. {bustapi-0.11.1 → bustapi-0.12.0}/src/router/handlers.rs +7 -1
  14. {bustapi-0.11.1 → bustapi-0.12.0}/src/watcher.rs +19 -5
  15. bustapi-0.12.0/tests/test_issue_18.py +63 -0
  16. {bustapi-0.11.1 → bustapi-0.12.0}/- +0 -0
  17. {bustapi-0.11.1 → bustapi-0.12.0}/.github/FUNDING.yml +0 -0
  18. {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/ci-multiplatform.yml +0 -0
  19. {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/ci.yml +0 -0
  20. {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/docs.yml +0 -0
  21. {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/manual-publish.yml +0 -0
  22. {bustapi-0.11.1 → bustapi-0.12.0}/.gitignore +0 -0
  23. {bustapi-0.11.1 → bustapi-0.12.0}/.python-version +0 -0
  24. {bustapi-0.11.1 → bustapi-0.12.0}/Dockerfile +0 -0
  25. {bustapi-0.11.1 → bustapi-0.12.0}/LICENSE +0 -0
  26. {bustapi-0.11.1 → bustapi-0.12.0}/README.md +0 -0
  27. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/CACHE_BENCHMARK.md +0 -0
  28. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/README.md +0 -0
  29. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/app.py +0 -0
  30. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_all.py +0 -0
  31. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_avg.py +0 -0
  32. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_cache.py +0 -0
  33. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_combined.png +0 -0
  34. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/bustapi.md +0 -0
  35. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/bustapi_bench.py +0 -0
  36. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/bustapi_internal.py +0 -0
  37. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/comparison_bench.py +0 -0
  38. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/comprehensive_benchmark.py +0 -0
  39. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/gen_graph.py +0 -0
  40. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/quick_bench.py +0 -0
  41. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/resource_bench.py +0 -0
  42. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/rps_comparison.png +0 -0
  43. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/run_comparison_auto.py +0 -0
  44. {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/ws_benchmark.py +0 -0
  45. {bustapi-0.11.1 → bustapi-0.12.0}/check_path.py +0 -0
  46. {bustapi-0.11.1 → bustapi-0.12.0}/create_issues.sh +0 -0
  47. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/async.md +0 -0
  48. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/blueprints.md +0 -0
  49. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/dependency_injection.md +0 -0
  50. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/deployment.md +0 -0
  51. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/middleware.md +0 -0
  52. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/security.md +0 -0
  53. {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/validation.md +0 -0
  54. {bustapi-0.11.1 → bustapi-0.12.0}/docs/api-reference.md +0 -0
  55. {bustapi-0.11.1 → bustapi-0.12.0}/docs/assets/logo.png +0 -0
  56. {bustapi-0.11.1 → bustapi-0.12.0}/docs/complete-api-reference.md +0 -0
  57. {bustapi-0.11.1 → bustapi-0.12.0}/docs/deployment-performance-guide.md +0 -0
  58. {bustapi-0.11.1 → bustapi-0.12.0}/docs/deployment.md +0 -0
  59. {bustapi-0.11.1 → bustapi-0.12.0}/docs/examples-guide.md +0 -0
  60. {bustapi-0.11.1 → bustapi-0.12.0}/docs/index.md +0 -0
  61. {bustapi-0.11.1 → bustapi-0.12.0}/docs/installation.md +0 -0
  62. {bustapi-0.11.1 → bustapi-0.12.0}/docs/overrides/.gitkeep +0 -0
  63. {bustapi-0.11.1 → bustapi-0.12.0}/docs/quickstart.md +0 -0
  64. {bustapi-0.11.1 → bustapi-0.12.0}/docs/release_track.md +0 -0
  65. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/async_support.html +0 -0
  66. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/blueprints.html +0 -0
  67. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/dependency_injection.html +0 -0
  68. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/middleware.html +0 -0
  69. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/validation.html +0 -0
  70. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/all.html +0 -0
  71. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/app.html +0 -0
  72. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/context.html +0 -0
  73. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/utilities.html +0 -0
  74. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/assets/logo.png +0 -0
  75. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/changelog.html +0 -0
  76. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/request_data.html +0 -0
  77. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/responses.html +0 -0
  78. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/routing.html +0 -0
  79. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/templates.html +0 -0
  80. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/examples/index.html +0 -0
  81. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/getting_started/index.html +0 -0
  82. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/getting_started/quickstart.html +0 -0
  83. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/index.html +0 -0
  84. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/script.js +0 -0
  85. {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/style.css +0 -0
  86. {bustapi-0.11.1 → bustapi-0.12.0}/docs/stylesheets/extra.css +0 -0
  87. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/authentication.md +0 -0
  88. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/caching.md +0 -0
  89. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/cli.md +0 -0
  90. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/database.md +0 -0
  91. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/documentation.md +0 -0
  92. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/error_handling.md +0 -0
  93. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/jwt.md +0 -0
  94. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/logging.md +0 -0
  95. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/multiprocessing.md +0 -0
  96. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/request_data.md +0 -0
  97. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/responses.md +0 -0
  98. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/routing.md +0 -0
  99. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/sessions.md +0 -0
  100. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/static_files.md +0 -0
  101. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/templates.md +0 -0
  102. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/turbo-routes.md +0 -0
  103. {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/video-streaming.md +0 -0
  104. {bustapi-0.11.1 → bustapi-0.12.0}/docs/websockets.md +0 -0
  105. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/08_auto_docs.py +0 -0
  106. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/12_test_modes.py +0 -0
  107. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/13_error_handling.py +0 -0
  108. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/14_middleware.py +0 -0
  109. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/16_middleware_session.py +0 -0
  110. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/20_file_uploads.py +0 -0
  111. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/24_body_and_depends.py +0 -0
  112. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/27_video_stream.py +0 -0
  113. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/28_websocket.py +0 -0
  114. {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/29_turbo_websocket.py +0 -0
  115. {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/01_hello_world.py +0 -0
  116. {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/02_parameters.py +0 -0
  117. {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/03_async.py +0 -0
  118. {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/04_request_data.py +0 -0
  119. {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/19_all_types.py +0 -0
  120. {bustapi-0.11.1 → bustapi-0.12.0}/examples/database/07_database_raw.py +0 -0
  121. {bustapi-0.11.1 → bustapi-0.12.0}/examples/database/10_database_sqlmodel.py +0 -0
  122. {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/06_blueprints.py +0 -0
  123. {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/09_complex_routing.py +0 -0
  124. {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/21_path_validation.py +0 -0
  125. {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/22_path_docs.py +0 -0
  126. {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/23_query_validation.py +0 -0
  127. {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/blueprint_with_jwt.py +0 -0
  128. {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/10_rate_limit_demo.py +0 -0
  129. {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/11_security_demo.py +0 -0
  130. {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/17_jwt_auth.py +0 -0
  131. {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/17_safe_features.py +0 -0
  132. {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/18_safe_advanced.py +0 -0
  133. {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/18_session_login.py +0 -0
  134. {bustapi-0.11.1 → bustapi-0.12.0}/examples/templates/05_templates.py +0 -0
  135. {bustapi-0.11.1 → bustapi-0.12.0}/examples/templates/26_complex_template.py +0 -0
  136. {bustapi-0.11.1 → bustapi-0.12.0}/examples/templates/templates/index.html +0 -0
  137. {bustapi-0.11.1 → bustapi-0.12.0}/examples/turbo/cache_test.py +0 -0
  138. {bustapi-0.11.1 → bustapi-0.12.0}/examples/turbo/fair_benchmark.py +0 -0
  139. {bustapi-0.11.1 → bustapi-0.12.0}/examples/turbo/typed_turbo_example.py +0 -0
  140. {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/app.py +0 -0
  141. {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/templates/index.html +0 -0
  142. {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/websockets_demo.py +0 -0
  143. {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/ws_limit.py +0 -0
  144. {bustapi-0.11.1 → bustapi-0.12.0}/mkdocs.yml +0 -0
  145. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/__init__.py +0 -0
  146. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/csrf.py +0 -0
  147. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/decorators.py +0 -0
  148. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/login.py +0 -0
  149. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/password.py +0 -0
  150. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/tokens.py +0 -0
  151. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/user.py +0 -0
  152. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/cli/__init__.py +0 -0
  153. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/cli/main.py +0 -0
  154. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/context.py +0 -0
  155. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/__init__.py +0 -0
  156. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/asgi.py +0 -0
  157. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/exceptions.py +0 -0
  158. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/helpers.py +0 -0
  159. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/logging.py +0 -0
  160. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/wsgi.py +0 -0
  161. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/dependencies.py +0 -0
  162. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/dispatch.py +0 -0
  163. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/documentation/__init__.py +0 -0
  164. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/extraction.py +0 -0
  165. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/fastapi_compat.py +0 -0
  166. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/hooks.py +0 -0
  167. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/http/__init__.py +0 -0
  168. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/http/request.py +0 -0
  169. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/jwt.py +0 -0
  170. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/logging.py +0 -0
  171. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/middleware.py +0 -0
  172. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/multiprocess.py +0 -0
  173. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/py.typed +0 -0
  174. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/responses.py +0 -0
  175. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/routing/__init__.py +0 -0
  176. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/routing/blueprints.py +0 -0
  177. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/routing/decorators.py +0 -0
  178. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/safe/__init__.py +0 -0
  179. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/safe/concurrency.py +0 -0
  180. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/safe/types.py +0 -0
  181. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/security/__init__.py +0 -0
  182. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/security/extension.py +0 -0
  183. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/security/rate_limit.py +0 -0
  184. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/server/__init__.py +0 -0
  185. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/server/runner.py +0 -0
  186. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/server/wsgi.py +0 -0
  187. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/sessions.py +0 -0
  188. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/templating/__init__.py +0 -0
  189. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/templating/engine.py +0 -0
  190. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/templating/mixin.py +0 -0
  191. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/testing/__init__.py +0 -0
  192. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/testing/client.py +0 -0
  193. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/utils.py +0 -0
  194. {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/websocket.py +0 -0
  195. {bustapi-0.11.1 → bustapi-0.12.0}/run_all_examples.py +0 -0
  196. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/converters.rs +0 -0
  197. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/handlers.rs +0 -0
  198. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/mod.rs +0 -0
  199. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/request.rs +0 -0
  200. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/typed_turbo.rs +0 -0
  201. {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/websocket.rs +0 -0
  202. {bustapi-0.11.1 → bustapi-0.12.0}/src/crypto.rs +0 -0
  203. {bustapi-0.11.1 → bustapi-0.12.0}/src/jwt.rs +0 -0
  204. {bustapi-0.11.1 → bustapi-0.12.0}/src/lib.rs +0 -0
  205. {bustapi-0.11.1 → bustapi-0.12.0}/src/logger.rs +0 -0
  206. {bustapi-0.11.1 → bustapi-0.12.0}/src/rate_limiter.rs +0 -0
  207. {bustapi-0.11.1 → bustapi-0.12.0}/src/request/methods.rs +0 -0
  208. {bustapi-0.11.1 → bustapi-0.12.0}/src/request/mod.rs +0 -0
  209. {bustapi-0.11.1 → bustapi-0.12.0}/src/request/tests.rs +0 -0
  210. {bustapi-0.11.1 → bustapi-0.12.0}/src/response/builders.rs +0 -0
  211. {bustapi-0.11.1 → bustapi-0.12.0}/src/response/methods.rs +0 -0
  212. {bustapi-0.11.1 → bustapi-0.12.0}/src/response/mod.rs +0 -0
  213. {bustapi-0.11.1 → bustapi-0.12.0}/src/router/matching.rs +0 -0
  214. {bustapi-0.11.1 → bustapi-0.12.0}/src/router/middleware.rs +0 -0
  215. {bustapi-0.11.1 → bustapi-0.12.0}/src/router/mod.rs +0 -0
  216. {bustapi-0.11.1 → bustapi-0.12.0}/src/router/tests.rs +0 -0
  217. {bustapi-0.11.1 → bustapi-0.12.0}/src/server/handlers.rs +0 -0
  218. {bustapi-0.11.1 → bustapi-0.12.0}/src/server/mod.rs +0 -0
  219. {bustapi-0.11.1 → bustapi-0.12.0}/src/server/startup.rs +0 -0
  220. {bustapi-0.11.1 → bustapi-0.12.0}/src/server/stream.rs +0 -0
  221. {bustapi-0.11.1 → bustapi-0.12.0}/src/static_files.rs +0 -0
  222. {bustapi-0.11.1 → bustapi-0.12.0}/src/templating.rs +0 -0
  223. {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/config.rs +0 -0
  224. {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/mod.rs +0 -0
  225. {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/session.rs +0 -0
  226. {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/turbo.rs +0 -0
  227. {bustapi-0.11.1 → bustapi-0.12.0}/static/style.css +0 -0
  228. {bustapi-0.11.1 → bustapi-0.12.0}/templates/base.html +0 -0
  229. {bustapi-0.11.1 → bustapi-0.12.0}/templates/complex.html +0 -0
  230. {bustapi-0.11.1 → bustapi-0.12.0}/test_trim.rs +0 -0
  231. {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_all.py +0 -0
  232. {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_api_reference.py +0 -0
  233. {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_installation.py +0 -0
  234. {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_quickstart.py +0 -0
  235. {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_simple.py +0 -0
  236. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_050_features.py +0 -0
  237. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_auth.py +0 -0
  238. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_examples.py +0 -0
  239. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_hot_reload.py +0 -0
  240. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_jwt.py +0 -0
  241. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_login_manager.py +0 -0
  242. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_middleware.py +0 -0
  243. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_multiprocess_stability.py +0 -0
  244. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_new_examples.py +0 -0
  245. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_path_standalone.py +0 -0
  246. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_path_validation.py +0 -0
  247. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_rate_limit_rust.py +0 -0
  248. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_safe.py +0 -0
  249. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_security_unit.py +0 -0
  250. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_sessions.py +0 -0
  251. {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_zero_copy.py +0 -0
  252. {bustapi-0.11.1 → bustapi-0.12.0}/tests/verify_router.py +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented here.
4
4
 
5
+ ## [0.12.0] - 2026-04-27
6
+
7
+ ### Added
8
+ - **Auto DOCS Enhancements**:
9
+ - **Path Merging**: Multiple HTTP methods (e.g., GET and POST) on the same path are now correctly merged in OpenAPI documentation.
10
+ - **Rich Schema Extraction**: Added support for `Body(..., schema={...})` and `Query(...)` parameters. OpenAPI documentation now includes property descriptions, examples, and validation constraints.
11
+ - **Standardized Types**: Improved mapping of internal types to standard OpenAPI types (integer, string, boolean, etc.).
12
+
13
+ ### Fixed
14
+ - **Hot-Reload Stability**: Fixed an infinite restart loop in the file watcher by ignoring noisy directories (`__pycache__`, `.git`, `.venv`) and filtering by file extension.
15
+ - **Custom Error Handlers** ([#18](https://github.com/RUSTxPY/BustAPI/issues/18)): Fixed a bug where custom 404 error handlers were ignored by the Rust backend.
16
+ - **Response Formatting**: Fixed a bug where pre-formatted response objects returned in tuples were being incorrectly stringified.
17
+
5
18
  ## [0.11.1] - 2026-04-23
6
19
 
7
20
  ### Added
@@ -438,7 +438,7 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
438
438
 
439
439
  [[package]]
440
440
  name = "bustapi_core"
441
- version = "0.11.1"
441
+ version = "0.12.0"
442
442
  dependencies = [
443
443
  "actix-files",
444
444
  "actix-http",
@@ -497,9 +497,9 @@ dependencies = [
497
497
 
498
498
  [[package]]
499
499
  name = "cc"
500
- version = "1.2.60"
500
+ version = "1.2.61"
501
501
  source = "registry+https://github.com/rust-lang/crates.io-index"
502
- checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
502
+ checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
503
503
  dependencies = [
504
504
  "find-msvc-tools",
505
505
  "jobserver",
@@ -1124,9 +1124,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1124
1124
 
1125
1125
  [[package]]
1126
1126
  name = "hybrid-array"
1127
- version = "0.4.10"
1127
+ version = "0.4.11"
1128
1128
  source = "registry+https://github.com/rust-lang/crates.io-index"
1129
- checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214"
1129
+ checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5"
1130
1130
  dependencies = [
1131
1131
  "typenum",
1132
1132
  ]
@@ -1392,9 +1392,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
1392
1392
 
1393
1393
  [[package]]
1394
1394
  name = "libc"
1395
- version = "0.2.185"
1395
+ version = "0.2.186"
1396
1396
  source = "registry+https://github.com/rust-lang/crates.io-index"
1397
- checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
1397
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
1398
1398
 
1399
1399
  [[package]]
1400
1400
  name = "libmimalloc-sys"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "bustapi_core"
3
- version = "0.11.1"
3
+ version = "0.12.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.11.1
3
+ Version: 0.12.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented here.
4
4
 
5
+ ## [0.12.0] - 2026-04-27
6
+
7
+ ### Added
8
+ - **Auto DOCS Enhancements**:
9
+ - **Path Merging**: Multiple HTTP methods (e.g., GET and POST) on the same path are now correctly merged in OpenAPI documentation.
10
+ - **Rich Schema Extraction**: Added support for `Body(..., schema={...})` and `Query(...)` parameters. OpenAPI documentation now includes property descriptions, examples, and validation constraints.
11
+ - **Standardized Types**: Improved mapping of internal types to standard OpenAPI types (integer, string, boolean, etc.).
12
+
13
+ ### Fixed
14
+ - **Hot-Reload Stability**: Fixed an infinite restart loop in the file watcher by ignoring noisy directories (`__pycache__`, `.git`, `.venv`) and filtering by file extension.
15
+ - **Custom Error Handlers** ([#18](https://github.com/RUSTxPY/BustAPI/issues/18)): Fixed a bug where custom 404 error handlers were ignored by the Rust backend.
16
+ - **Response Formatting**: Fixed a bug where pre-formatted response objects returned in tuples were being incorrectly stringified.
17
+
5
18
  ## [0.11.1] - 2026-04-23
6
19
 
7
20
  ### Added
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "bustapi"
7
- version = "0.11.1"
7
+ version = "0.12.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.11.1"
25
+ __version__ = "0.12.0"
26
26
  __author__ = "BustAPI" # with GrandpaEJ
27
27
  __email__ = ""
28
28
 
@@ -166,6 +166,9 @@ class BustAPI(
166
166
  self._rust_app = None
167
167
  self._init_rust_backend()
168
168
 
169
+ # Register default 404 fallback
170
+ self._rust_app.set_not_found_handler(self._dispatch_not_found)
171
+
169
172
  def _init_rust_backend(self):
170
173
  """Initialize the Rust backend application."""
171
174
  try:
@@ -260,14 +263,46 @@ class BustAPI(
260
263
  if isinstance(exc_class_or_code, type) and isinstance(
261
264
  exception, exc_class_or_code
262
265
  ):
263
- return self._make_response(handler(exception))
266
+ rv = handler(exception)
267
+ return (
268
+ self._make_response(*rv)
269
+ if isinstance(rv, tuple)
270
+ else self._make_response(rv)
271
+ )
264
272
  elif isinstance(exc_class_or_code, int):
265
273
  if hasattr(exception, "code") and exception.code == exc_class_or_code:
266
- return self._make_response(handler(exception))
274
+ rv = handler(exception)
275
+ return (
276
+ self._make_response(*rv)
277
+ if isinstance(rv, tuple)
278
+ else self._make_response(rv)
279
+ )
267
280
 
268
281
  status = getattr(exception, "code", 500) if hasattr(exception, "code") else 500
269
282
  return Response(f"Internal Server Error: {str(exception)}", status=status)
270
283
 
284
+ def _dispatch_not_found(self, rust_request, params=None):
285
+ """Internal handler called by Rust when no route matches."""
286
+ from .core.exceptions import NotFound
287
+
288
+ # Create request context if not already present
289
+ # This is needed because this might be called directly from Rust
290
+ # without going through the usual wrapper if it's a 404
291
+ try:
292
+ request = Request._from_rust_request(rust_request)
293
+ request.app = self
294
+ token = _request_ctx.set(request)
295
+
296
+ try:
297
+ # Handle via the normal exception handling logic
298
+ response = self._handle_exception(NotFound())
299
+ return self._response_to_rust_format(response)
300
+ finally:
301
+ _request_ctx.reset(token)
302
+ except Exception as e:
303
+ self.logger.error(f"Error in 404 fallback: {e}")
304
+ return ("Not Found", 404, {"Content-Type": "text/plain"})
305
+
271
306
  def _response_to_rust_format(self, response: Response) -> Union[tuple, Response]:
272
307
  """Convert Python Response object to format expected by Rust."""
273
308
  # Check for FileResponse (has path attribute)
@@ -326,32 +326,68 @@ class BustAPIDocs:
326
326
  # Extract query parameters
327
327
  query_vals = self.app.query_validators.get((rule, method))
328
328
  if query_vals:
329
+ from ..params import Query
330
+
329
331
  if "parameters" not in operation:
330
332
  operation["parameters"] = []
331
333
  for q_name, (q_default, q_anno) in query_vals.items():
332
- q_type = "string"
333
- if q_anno is int:
334
- q_type = "integer"
335
- elif q_anno is float:
336
- q_type = "number"
337
- elif q_anno is bool:
338
- q_type = "boolean"
339
-
340
- operation["parameters"].append(
341
- {
342
- "name": q_name,
343
- "in": "query",
344
- "required": q_default is inspect.Parameter.empty,
345
- "schema": {"type": q_type},
346
- }
347
- )
334
+ if isinstance(q_default, Query):
335
+ q_type = "string"
336
+ if q_anno is int:
337
+ q_type = "integer"
338
+ elif q_anno is float:
339
+ q_type = "number"
340
+ elif q_anno is bool:
341
+ q_type = "boolean"
342
+
343
+ param_obj = q_default.to_openapi_parameter(
344
+ q_name,
345
+ q_type,
346
+ required=q_default.default is inspect.Parameter.empty,
347
+ )
348
+ operation["parameters"].append(param_obj)
349
+ else:
350
+ q_type = "string"
351
+ if q_anno is int:
352
+ q_type = "integer"
353
+ elif q_anno is float:
354
+ q_type = "number"
355
+ elif q_anno is bool:
356
+ q_type = "boolean"
357
+
358
+ operation["parameters"].append(
359
+ {
360
+ "name": q_name,
361
+ "in": "query",
362
+ "required": q_default is inspect.Parameter.empty,
363
+ "schema": {"type": q_type},
364
+ }
365
+ )
348
366
 
349
367
  # Extract request body
350
368
  body_vals = self.app.body_validators.get((rule, method))
351
369
  if body_vals:
352
- # Check if single Struct parameter
370
+ from ..params import Body
371
+
372
+ # Check if single Struct or Body parameter
353
373
  if len(body_vals) == 1:
354
374
  b_name, (b_default, b_anno) = list(body_vals.items())[0]
375
+
376
+ # Handle Body objects
377
+ if isinstance(b_default, Body):
378
+ schema_ref = b_default.to_json_schema()
379
+ operation["requestBody"] = {
380
+ "required": b_default.default
381
+ is inspect.Parameter.empty,
382
+ "content": {"application/json": {"schema": schema_ref}},
383
+ }
384
+ if b_default.description:
385
+ operation["requestBody"][
386
+ "description"
387
+ ] = b_default.description
388
+ path_item[method_lower] = operation
389
+ continue
390
+
355
391
  try:
356
392
  from ..safe.types import Struct
357
393
 
@@ -372,19 +408,25 @@ class BustAPIDocs:
372
408
  props = {}
373
409
  required_props = []
374
410
  for b_name, (b_default, b_anno) in body_vals.items():
375
- b_type_str = "string"
376
- if b_anno is int:
377
- b_type_str = "integer"
378
- elif b_anno is float:
379
- b_type_str = "number"
380
- elif b_anno is bool:
381
- b_type_str = "boolean"
382
- elif hasattr(b_anno, "__annotations__"):
383
- b_type_str = "object"
384
-
385
- props[b_name] = {"type": b_type_str}
386
- if b_default is inspect.Parameter.empty:
387
- required_props.append(b_name)
411
+ if isinstance(b_default, Body):
412
+ prop = b_default.to_json_schema()
413
+ props[b_name] = prop
414
+ if b_default.default is inspect.Parameter.empty:
415
+ required_props.append(b_name)
416
+ else:
417
+ b_type_str = "string"
418
+ if b_anno is int:
419
+ b_type_str = "integer"
420
+ elif b_anno is float:
421
+ b_type_str = "number"
422
+ elif b_anno is bool:
423
+ b_type_str = "boolean"
424
+ elif hasattr(b_anno, "__annotations__"):
425
+ b_type_str = "object"
426
+
427
+ props[b_name] = {"type": b_type_str}
428
+ if b_default is inspect.Parameter.empty:
429
+ required_props.append(b_name)
388
430
 
389
431
  schema_ref = {"type": "object", "properties": props}
390
432
  if required_props:
@@ -398,7 +440,10 @@ class BustAPIDocs:
398
440
  path_item[method_lower] = operation
399
441
 
400
442
  if path_item:
401
- schema["paths"][openapi_path] = path_item
443
+ if openapi_path in schema["paths"]:
444
+ schema["paths"][openapi_path].update(path_item)
445
+ else:
446
+ schema["paths"][openapi_path] = path_item
402
447
 
403
448
  self._schema_cache = schema
404
449
  return schema
@@ -281,14 +281,24 @@ def make_response(*args) -> Response:
281
281
  rv, status_or_headers = args
282
282
  if isinstance(status_or_headers, (int, str)):
283
283
  # (response, status)
284
+ if isinstance(rv, Response):
285
+ rv.status_code = int(status_or_headers)
286
+ return rv
284
287
  return Response(rv, status=status_or_headers)
285
288
  else:
286
289
  # (response, headers)
290
+ if isinstance(rv, Response):
291
+ rv.headers.update(status_or_headers)
292
+ return rv
287
293
  return Response(rv, headers=status_or_headers)
288
294
 
289
295
  if len(args) == 3:
290
296
  # (response, status, headers)
291
297
  rv, status, headers = args
298
+ if isinstance(rv, Response):
299
+ rv.status_code = int(status)
300
+ rv.headers.update(headers)
301
+ return rv
292
302
  return Response(rv, status=status, headers=headers)
293
303
 
294
304
  raise TypeError(f"make_response() takes 1 to 3 arguments ({len(args)} given)")
@@ -148,7 +148,16 @@ class Path:
148
148
  Returns:
149
149
  JSON Schema dictionary compatible with OpenAPI 3.0
150
150
  """
151
- schema: Dict[str, Any] = {"type": param_type}
151
+ # Map Python types to OpenAPI types
152
+ type_map = {
153
+ "int": "integer",
154
+ "float": "number",
155
+ "number": "number",
156
+ "str": "string",
157
+ "string": "string",
158
+ }
159
+ actual_type = type_map.get(param_type, param_type)
160
+ schema: Dict[str, Any] = {"type": actual_type}
152
161
 
153
162
  # Add title and description
154
163
  if self.title:
@@ -412,7 +421,18 @@ class Query:
412
421
  Returns:
413
422
  JSON Schema dictionary compatible with OpenAPI 3.0
414
423
  """
415
- schema: Dict[str, Any] = {"type": param_type}
424
+ # Map Python types to OpenAPI types
425
+ type_map = {
426
+ "int": "integer",
427
+ "float": "number",
428
+ "number": "number",
429
+ "str": "string",
430
+ "string": "string",
431
+ "bool": "boolean",
432
+ "boolean": "boolean",
433
+ }
434
+ actual_type = type_map.get(param_type, param_type)
435
+ schema: Dict[str, Any] = {"type": actual_type}
416
436
 
417
437
  # Add title and description
418
438
  if self.title:
@@ -730,7 +750,20 @@ class Body:
730
750
  continue
731
751
 
732
752
  field_type = field_schema.get("type", "string")
733
- prop = {"type": field_type}
753
+ # Map Python types to OpenAPI types
754
+ type_map = {
755
+ "int": "integer",
756
+ "float": "number",
757
+ "number": "number",
758
+ "str": "string",
759
+ "string": "string",
760
+ "bool": "boolean",
761
+ "boolean": "boolean",
762
+ "dict": "object",
763
+ "list": "array",
764
+ }
765
+ actual_type = type_map.get(field_type, field_type)
766
+ prop = {"type": actual_type}
734
767
 
735
768
  if "description" in field_schema:
736
769
  prop["description"] = field_schema["description"]
@@ -277,6 +277,19 @@ impl PyBustApp {
277
277
  Ok(())
278
278
  }
279
279
 
280
+ /// Set a fallback handler for 404 Not Found errors
281
+ pub fn set_not_found_handler(&self, handler: Py<PyAny>) -> PyResult<()> {
282
+ let py_handler = crate::bindings::handlers::PyRouteHandler::new(handler);
283
+ let state = self.state.clone();
284
+
285
+ self.runtime.block_on(async {
286
+ let mut routes = state.routes.write().await;
287
+ routes.not_found_handler = Some(Arc::new(py_handler));
288
+ });
289
+
290
+ Ok(())
291
+ }
292
+
280
293
  /// Run the server
281
294
  pub fn run(
282
295
  &self,
@@ -140,6 +140,7 @@ pub struct Router {
140
140
  pub(crate) routes: HashMap<(Method, String), Arc<dyn RouteHandler>>,
141
141
  pub(crate) middleware: Vec<Arc<dyn super::middleware::Middleware>>,
142
142
  pub(crate) redirect_slashes: bool,
143
+ pub not_found_handler: Option<Arc<dyn RouteHandler>>,
143
144
  }
144
145
 
145
146
  impl Router {
@@ -151,6 +152,7 @@ impl Router {
151
152
  routes: HashMap::new(),
152
153
  middleware: Vec::new(),
153
154
  redirect_slashes: true,
155
+ not_found_handler: None,
154
156
  }
155
157
  }
156
158
 
@@ -239,7 +241,11 @@ impl Router {
239
241
  } else {
240
242
  // Not found - check for redirect if enabled
241
243
  self.try_redirect(&req_data).unwrap_or_else(|| {
242
- ResponseData::error(http::StatusCode::NOT_FOUND, Some("Not Found"))
244
+ if let Some(ref handler) = self.not_found_handler {
245
+ handler.handle(req_data.clone())
246
+ } else {
247
+ ResponseData::error(http::StatusCode::NOT_FOUND, Some("Not Found"))
248
+ }
243
249
  })
244
250
  };
245
251
 
@@ -31,13 +31,27 @@ pub fn enable_hot_reload(path_str: String) {
31
31
  paths,
32
32
  ..
33
33
  })) => {
34
- // Filter out non-python files if needed, but for now watch all
35
- // Simple debounce: Wait a bit to avoid multiple restarts for one save
34
+ // Filter out noise and irrelevant files
36
35
  if let Some(path) = paths.first() {
37
- println!("Using: {}", path.display());
36
+ let path_str = path.to_string_lossy();
37
+ if path_str.contains("__pycache__")
38
+ || path_str.contains(".git")
39
+ || path_str.contains(".venv")
40
+ || path_str.contains(".egg-info")
41
+ {
42
+ continue;
43
+ }
44
+
45
+ // Check extension
46
+ let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
47
+ if !["py", "rs", "html", "css", "js", "json", "toml"].contains(&ext) {
48
+ continue;
49
+ }
50
+
51
+ println!("Using: {}", path_str);
52
+ println!("🔄 Restarting...");
53
+ restart_process();
38
54
  }
39
- println!("🔄 Restarting...");
40
- restart_process();
41
55
  }
42
56
  Ok(Err(e)) => eprintln!("watch error: {:?}", e),
43
57
  Err(e) => eprintln!("watch channel error: {:?}", e),
@@ -0,0 +1,63 @@
1
+ import os
2
+ import shutil
3
+
4
+ import pytest
5
+ from bustapi import BustAPI, render_template
6
+
7
+
8
+ @pytest.fixture
9
+ def app():
10
+ app = BustAPI(import_name="test_app")
11
+
12
+ # Setup templates
13
+ template_dir = os.path.join(os.getcwd(), "templates_test_18")
14
+ os.makedirs(template_dir, exist_ok=True)
15
+ with open(os.path.join(template_dir, "err.html"), "w") as f:
16
+ f.write(
17
+ "<html><body><h1>Error {{ status_code }}</h1><p>{{ message }}</p></body></html>"
18
+ )
19
+
20
+ app.template_folder = template_dir
21
+
22
+ @app.errorhandler(404)
23
+ def page_not_found(e):
24
+ return (
25
+ render_template(
26
+ "err.html", **{"status_code": 404, "message": "Custom 404 Page"}
27
+ ),
28
+ 404,
29
+ )
30
+
31
+ @app.route("/")
32
+ def index():
33
+ return "Home"
34
+
35
+ yield app
36
+
37
+ # Cleanup
38
+ shutil.rmtree(template_dir, ignore_errors=True)
39
+
40
+
41
+ def test_custom_404_handler(app):
42
+ with app.test_client() as client:
43
+ # Test valid route
44
+ resp = client.get("/")
45
+ assert resp.status_code == 200
46
+ assert resp.text == "Home"
47
+
48
+ # Test invalid route - should trigger custom handler
49
+ resp = client.get("/non-existent")
50
+ assert resp.status_code == 404
51
+ assert "Custom 404 Page" in resp.text
52
+ assert "<h1>Error 404</h1>" in resp.text
53
+
54
+
55
+ def test_default_500_handler(app):
56
+ @app.route("/error")
57
+ def cause_error():
58
+ raise ValueError("Something went wrong")
59
+
60
+ with app.test_client() as client:
61
+ resp = client.get("/error")
62
+ assert resp.status_code == 500
63
+ assert "Internal Server Error" in resp.text
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes