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.
- {bustapi-0.11.1 → bustapi-0.12.0}/CHANGELOG.md +13 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/Cargo.lock +7 -7
- {bustapi-0.11.1 → bustapi-0.12.0}/Cargo.toml +1 -1
- {bustapi-0.11.1 → bustapi-0.12.0}/PKG-INFO +1 -1
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/changelog.md +13 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/pyproject.toml +1 -1
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/__init__.py +1 -1
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/app.py +37 -2
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/documentation/generator.py +76 -31
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/http/response.py +10 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/params.py +36 -3
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/app.rs +13 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/router/handlers.rs +7 -1
- {bustapi-0.11.1 → bustapi-0.12.0}/src/watcher.rs +19 -5
- bustapi-0.12.0/tests/test_issue_18.py +63 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/- +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.github/FUNDING.yml +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/ci.yml +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/docs.yml +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.github/workflows/manual-publish.yml +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.gitignore +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/.python-version +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/Dockerfile +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/LICENSE +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/README.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/CACHE_BENCHMARK.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/README.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/app.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_all.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_avg.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_cache.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/benchmark_combined.png +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/bustapi.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/bustapi_bench.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/bustapi_internal.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/comparison_bench.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/comprehensive_benchmark.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/gen_graph.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/quick_bench.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/resource_bench.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/rps_comparison.png +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/run_comparison_auto.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/benchmarks/ws_benchmark.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/check_path.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/create_issues.sh +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/async.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/blueprints.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/dependency_injection.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/deployment.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/middleware.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/security.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/advanced/validation.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/api-reference.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/assets/logo.png +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/complete-api-reference.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/deployment-performance-guide.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/deployment.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/examples-guide.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/index.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/installation.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/overrides/.gitkeep +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/quickstart.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/release_track.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/async_support.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/blueprints.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/dependency_injection.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/middleware.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/advanced/validation.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/all.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/app.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/context.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/api/utilities.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/assets/logo.png +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/changelog.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/request_data.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/responses.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/routing.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/core_concepts/templates.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/examples/index.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/getting_started/index.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/getting_started/quickstart.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/index.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/script.js +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/site/style.css +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/stylesheets/extra.css +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/authentication.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/caching.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/cli.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/database.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/documentation.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/error_handling.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/jwt.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/logging.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/multiprocessing.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/request_data.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/responses.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/routing.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/sessions.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/static_files.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/templates.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/turbo-routes.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/user-guide/video-streaming.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/docs/websockets.md +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/08_auto_docs.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/12_test_modes.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/13_error_handling.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/14_middleware.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/16_middleware_session.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/20_file_uploads.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/24_body_and_depends.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/27_video_stream.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/28_websocket.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/advanced/29_turbo_websocket.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/01_hello_world.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/02_parameters.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/03_async.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/04_request_data.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/basics/19_all_types.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/database/07_database_raw.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/database/10_database_sqlmodel.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/06_blueprints.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/09_complex_routing.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/21_path_validation.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/22_path_docs.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/23_query_validation.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/routing/blueprint_with_jwt.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/10_rate_limit_demo.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/11_security_demo.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/17_jwt_auth.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/17_safe_features.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/18_safe_advanced.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/security/18_session_login.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/templates/05_templates.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/templates/26_complex_template.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/templates/templates/index.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/turbo/cache_test.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/turbo/fair_benchmark.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/turbo/typed_turbo_example.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/app.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/templates/index.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/websockets_demo.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/examples/ws/ws_limit.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/mkdocs.yml +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/csrf.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/decorators.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/login.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/password.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/tokens.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/auth/user.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/cli/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/cli/main.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/context.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/asgi.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/exceptions.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/helpers.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/logging.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/core/wsgi.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/dependencies.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/dispatch.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/documentation/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/extraction.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/fastapi_compat.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/hooks.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/http/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/http/request.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/jwt.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/logging.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/middleware.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/multiprocess.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/py.typed +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/responses.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/routing/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/routing/blueprints.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/routing/decorators.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/safe/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/safe/concurrency.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/safe/types.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/security/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/security/extension.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/security/rate_limit.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/server/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/server/runner.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/server/wsgi.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/sessions.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/templating/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/templating/engine.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/templating/mixin.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/testing/__init__.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/testing/client.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/utils.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/python/bustapi/websocket.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/run_all_examples.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/converters.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/handlers.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/mod.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/request.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/typed_turbo.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/bindings/websocket.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/crypto.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/jwt.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/lib.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/logger.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/rate_limiter.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/request/methods.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/request/mod.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/request/tests.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/response/builders.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/response/methods.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/response/mod.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/router/matching.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/router/middleware.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/router/mod.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/router/tests.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/server/handlers.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/server/mod.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/server/startup.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/server/stream.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/static_files.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/templating.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/config.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/mod.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/session.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/src/websocket/turbo.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/static/style.css +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/templates/base.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/templates/complex.html +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/test_trim.rs +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_all.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_api_reference.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_installation.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_quickstart.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/docs_test_simple.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_050_features.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_auth.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_examples.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_hot_reload.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_jwt.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_login_manager.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_middleware.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_multiprocess_stability.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_new_examples.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_path_standalone.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_path_validation.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_rate_limit_rust.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_safe.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_security_unit.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_sessions.py +0 -0
- {bustapi-0.11.1 → bustapi-0.12.0}/tests/test_zero_copy.py +0 -0
- {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.
|
|
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.
|
|
500
|
+
version = "1.2.61"
|
|
501
501
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
502
|
-
checksum = "
|
|
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.
|
|
1127
|
+
version = "0.4.11"
|
|
1128
1128
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1129
|
-
checksum = "
|
|
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.
|
|
1395
|
+
version = "0.2.186"
|
|
1396
1396
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1397
|
-
checksum = "
|
|
1397
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
1398
1398
|
|
|
1399
1399
|
[[package]]
|
|
1400
1400
|
name = "libmimalloc-sys"
|
|
@@ -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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
b_type_str = "
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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"]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
File without changes
|