veloceframework 0.1.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.
- veloceframework-0.1.0/.gitignore +49 -0
- veloceframework-0.1.0/CHANGELOG.md +555 -0
- veloceframework-0.1.0/LICENSE +21 -0
- veloceframework-0.1.0/PKG-INFO +173 -0
- veloceframework-0.1.0/README.md +129 -0
- veloceframework-0.1.0/pyproject.toml +143 -0
- veloceframework-0.1.0/src/veloce/__init__.py +314 -0
- veloceframework-0.1.0/src/veloce/_handler_plan.py +405 -0
- veloceframework-0.1.0/src/veloce/_types.py +26 -0
- veloceframework-0.1.0/src/veloce/app.py +3316 -0
- veloceframework-0.1.0/src/veloce/background.py +47 -0
- veloceframework-0.1.0/src/veloce/blueprints.py +222 -0
- veloceframework-0.1.0/src/veloce/cli.py +209 -0
- veloceframework-0.1.0/src/veloce/config.py +318 -0
- veloceframework-0.1.0/src/veloce/contrib/__init__.py +1 -0
- veloceframework-0.1.0/src/veloce/contrib/openapi.py +705 -0
- veloceframework-0.1.0/src/veloce/contrib/staticfiles.py +412 -0
- veloceframework-0.1.0/src/veloce/contrib/templating.py +260 -0
- veloceframework-0.1.0/src/veloce/dependency.py +813 -0
- veloceframework-0.1.0/src/veloce/encoders.py +105 -0
- veloceframework-0.1.0/src/veloce/exception_handlers.py +43 -0
- veloceframework-0.1.0/src/veloce/exceptions.py +359 -0
- veloceframework-0.1.0/src/veloce/helpers.py +640 -0
- veloceframework-0.1.0/src/veloce/http/__init__.py +28 -0
- veloceframework-0.1.0/src/veloce/http/cache_control.py +130 -0
- veloceframework-0.1.0/src/veloce/http/cookies.py +86 -0
- veloceframework-0.1.0/src/veloce/http/datastructures.py +795 -0
- veloceframework-0.1.0/src/veloce/http/dates.py +85 -0
- veloceframework-0.1.0/src/veloce/http/header_set.py +97 -0
- veloceframework-0.1.0/src/veloce/http/header_utils.py +117 -0
- veloceframework-0.1.0/src/veloce/http/request.py +1058 -0
- veloceframework-0.1.0/src/veloce/http/response.py +1285 -0
- veloceframework-0.1.0/src/veloce/instrumentation.py +46 -0
- veloceframework-0.1.0/src/veloce/json_provider.py +79 -0
- veloceframework-0.1.0/src/veloce/markup.py +92 -0
- veloceframework-0.1.0/src/veloce/middleware/__init__.py +34 -0
- veloceframework-0.1.0/src/veloce/middleware/base.py +82 -0
- veloceframework-0.1.0/src/veloce/middleware/compression.py +100 -0
- veloceframework-0.1.0/src/veloce/middleware/cors.py +184 -0
- veloceframework-0.1.0/src/veloce/middleware/csrf.py +162 -0
- veloceframework-0.1.0/src/veloce/middleware/logging.py +86 -0
- veloceframework-0.1.0/src/veloce/middleware/proxy_fix.py +149 -0
- veloceframework-0.1.0/src/veloce/middleware/security.py +244 -0
- veloceframework-0.1.0/src/veloce/middleware/sessions.py +197 -0
- veloceframework-0.1.0/src/veloce/passwords.py +218 -0
- veloceframework-0.1.0/src/veloce/py.typed +0 -0
- veloceframework-0.1.0/src/veloce/routing/__init__.py +17 -0
- veloceframework-0.1.0/src/veloce/routing/converters.py +189 -0
- veloceframework-0.1.0/src/veloce/routing/params.py +186 -0
- veloceframework-0.1.0/src/veloce/routing/router.py +880 -0
- veloceframework-0.1.0/src/veloce/safe.py +145 -0
- veloceframework-0.1.0/src/veloce/security/__init__.py +33 -0
- veloceframework-0.1.0/src/veloce/security/api_key.py +57 -0
- veloceframework-0.1.0/src/veloce/security/http.py +230 -0
- veloceframework-0.1.0/src/veloce/security/oauth2.py +186 -0
- veloceframework-0.1.0/src/veloce/serving/__init__.py +5 -0
- veloceframework-0.1.0/src/veloce/serving/protocol.py +227 -0
- veloceframework-0.1.0/src/veloce/sessions.py +188 -0
- veloceframework-0.1.0/src/veloce/signals.py +181 -0
- veloceframework-0.1.0/src/veloce/signing.py +196 -0
- veloceframework-0.1.0/src/veloce/sse.py +130 -0
- veloceframework-0.1.0/src/veloce/status.py +97 -0
- veloceframework-0.1.0/src/veloce/testclient.py +1128 -0
- veloceframework-0.1.0/src/veloce/views.py +142 -0
- veloceframework-0.1.0/src/veloce/watchdog.py +175 -0
- veloceframework-0.1.0/src/veloce/websocket.py +703 -0
- veloceframework-0.1.0/tests/__init__.py +0 -0
- veloceframework-0.1.0/tests/conftest.py +34 -0
- veloceframework-0.1.0/tests/test_aborter.py +60 -0
- veloceframework-0.1.0/tests/test_accept_headers.py +151 -0
- veloceframework-0.1.0/tests/test_add_api_route.py +67 -0
- veloceframework-0.1.0/tests/test_add_api_websocket_route.py +49 -0
- veloceframework-0.1.0/tests/test_add_event_handler.py +72 -0
- veloceframework-0.1.0/tests/test_add_exception_handler.py +79 -0
- veloceframework-0.1.0/tests/test_add_middleware_class.py +42 -0
- veloceframework-0.1.0/tests/test_add_url_rule_stub.py +56 -0
- veloceframework-0.1.0/tests/test_add_websocket_route.py +40 -0
- veloceframework-0.1.0/tests/test_after_this_request.py +98 -0
- veloceframework-0.1.0/tests/test_annotated_depends.py +173 -0
- veloceframework-0.1.0/tests/test_app.py +434 -0
- veloceframework-0.1.0/tests/test_app_cli_click.py +90 -0
- veloceframework-0.1.0/tests/test_app_context.py +83 -0
- veloceframework-0.1.0/tests/test_app_ctor_exception_handlers.py +69 -0
- veloceframework-0.1.0/tests/test_app_ctor_middleware.py +79 -0
- veloceframework-0.1.0/tests/test_app_endpoint_view_functions.py +76 -0
- veloceframework-0.1.0/tests/test_app_extra.py +35 -0
- veloceframework-0.1.0/tests/test_app_global_dependencies.py +73 -0
- veloceframework-0.1.0/tests/test_app_jinja_env.py +44 -0
- veloceframework-0.1.0/tests/test_app_jinja_loader.py +23 -0
- veloceframework-0.1.0/tests/test_app_metadata.py +88 -0
- veloceframework-0.1.0/tests/test_app_state_namespace.py +43 -0
- veloceframework-0.1.0/tests/test_app_test_client.py +39 -0
- veloceframework-0.1.0/tests/test_app_webhooks.py +86 -0
- veloceframework-0.1.0/tests/test_appcontext_signals.py +74 -0
- veloceframework-0.1.0/tests/test_asgi_middleware_interop.py +239 -0
- veloceframework-0.1.0/tests/test_asgi_mount_and_env.py +282 -0
- veloceframework-0.1.0/tests/test_asgi_response_quirks.py +100 -0
- veloceframework-0.1.0/tests/test_async_io.py +155 -0
- veloceframework-0.1.0/tests/test_async_safety.py +278 -0
- veloceframework-0.1.0/tests/test_async_test_client.py +190 -0
- veloceframework-0.1.0/tests/test_base_http_middleware.py +195 -0
- veloceframework-0.1.0/tests/test_base_view.py +124 -0
- veloceframework-0.1.0/tests/test_before_first_request.py +98 -0
- veloceframework-0.1.0/tests/test_before_serving_signal_ns.py +61 -0
- veloceframework-0.1.0/tests/test_blueprint.py +194 -0
- veloceframework-0.1.0/tests/test_blueprint_nested.py +106 -0
- veloceframework-0.1.0/tests/test_blueprint_url_processors.py +112 -0
- veloceframework-0.1.0/tests/test_blueprints_introspect.py +88 -0
- veloceframework-0.1.0/tests/test_body_embed.py +54 -0
- veloceframework-0.1.0/tests/test_build_error.py +93 -0
- veloceframework-0.1.0/tests/test_cache_control.py +129 -0
- veloceframework-0.1.0/tests/test_cli.py +153 -0
- veloceframework-0.1.0/tests/test_cli_runner_and_dispatch.py +71 -0
- veloceframework-0.1.0/tests/test_conditional_request_headers.py +78 -0
- veloceframework-0.1.0/tests/test_config_loaders.py +265 -0
- veloceframework-0.1.0/tests/test_content_disposition.py +53 -0
- veloceframework-0.1.0/tests/test_context_processor.py +159 -0
- veloceframework-0.1.0/tests/test_cookie_helpers.py +103 -0
- veloceframework-0.1.0/tests/test_cookie_list_marker.py +89 -0
- veloceframework-0.1.0/tests/test_cookies_and_validation.py +148 -0
- veloceframework-0.1.0/tests/test_cors_spec.py +202 -0
- veloceframework-0.1.0/tests/test_csrf_middleware.py +250 -0
- veloceframework-0.1.0/tests/test_current_app.py +86 -0
- veloceframework-0.1.0/tests/test_custom_converter.py +71 -0
- veloceframework-0.1.0/tests/test_default_config.py +53 -0
- veloceframework-0.1.0/tests/test_default_options_response.py +87 -0
- veloceframework-0.1.0/tests/test_dependency_overrides.py +69 -0
- veloceframework-0.1.0/tests/test_dependency_parallel.py +195 -0
- veloceframework-0.1.0/tests/test_depends_no_arg.py +69 -0
- veloceframework-0.1.0/tests/test_dev_server_tls.py +76 -0
- veloceframework-0.1.0/tests/test_dispatch_aliases.py +178 -0
- veloceframework-0.1.0/tests/test_e2e_smoke.py +301 -0
- veloceframework-0.1.0/tests/test_ensure_sync.py +97 -0
- veloceframework-0.1.0/tests/test_error_handler_spec.py +71 -0
- veloceframework-0.1.0/tests/test_errorhandler_alias.py +80 -0
- veloceframework-0.1.0/tests/test_event_loop_watchdog.py +186 -0
- veloceframework-0.1.0/tests/test_exception_handlers_module.py +48 -0
- veloceframework-0.1.0/tests/test_file_response_etag.py +96 -0
- veloceframework-0.1.0/tests/test_fileresponse_disposition_type.py +38 -0
- veloceframework-0.1.0/tests/test_fileresponse_last_modified.py +59 -0
- veloceframework-0.1.0/tests/test_final_coverage.py +312 -0
- veloceframework-0.1.0/tests/test_form_list_marker.py +71 -0
- veloceframework-0.1.0/tests/test_formdata_multidict.py +240 -0
- veloceframework-0.1.0/tests/test_fuzz_parsers.py +124 -0
- veloceframework-0.1.0/tests/test_gzip_include_types.py +95 -0
- veloceframework-0.1.0/tests/test_handle_user_exception.py +102 -0
- veloceframework-0.1.0/tests/test_handler_plan.py +315 -0
- veloceframework-0.1.0/tests/test_head_method.py +94 -0
- veloceframework-0.1.0/tests/test_header_convert_underscores.py +56 -0
- veloceframework-0.1.0/tests/test_header_list_marker.py +99 -0
- veloceframework-0.1.0/tests/test_header_set.py +104 -0
- veloceframework-0.1.0/tests/test_header_utils.py +154 -0
- veloceframework-0.1.0/tests/test_headers_extra_methods.py +65 -0
- veloceframework-0.1.0/tests/test_host_matching.py +67 -0
- veloceframework-0.1.0/tests/test_http_dates.py +84 -0
- veloceframework-0.1.0/tests/test_http_digest.py +72 -0
- veloceframework-0.1.0/tests/test_http_exceptions.py +234 -0
- veloceframework-0.1.0/tests/test_include_router.py +60 -0
- veloceframework-0.1.0/tests/test_instance_path.py +33 -0
- veloceframework-0.1.0/tests/test_instrumentation.py +221 -0
- veloceframework-0.1.0/tests/test_iteration2.py +513 -0
- veloceframework-0.1.0/tests/test_iteration3.py +345 -0
- veloceframework-0.1.0/tests/test_iteration4.py +306 -0
- veloceframework-0.1.0/tests/test_jinja_globals.py +72 -0
- veloceframework-0.1.0/tests/test_json_provider.py +92 -0
- veloceframework-0.1.0/tests/test_json_response_classes.py +76 -0
- veloceframework-0.1.0/tests/test_jsonify_config.py +110 -0
- veloceframework-0.1.0/tests/test_lifespan_context.py +75 -0
- veloceframework-0.1.0/tests/test_logger_trap.py +84 -0
- veloceframework-0.1.0/tests/test_markup_escape.py +92 -0
- veloceframework-0.1.0/tests/test_max_content_length.py +121 -0
- veloceframework-0.1.0/tests/test_message_flashed_signal.py +62 -0
- veloceframework-0.1.0/tests/test_method_view.py +134 -0
- veloceframework-0.1.0/tests/test_mount_staticfiles.py +80 -0
- veloceframework-0.1.0/tests/test_multidict_semantics.py +199 -0
- veloceframework-0.1.0/tests/test_new_features.py +914 -0
- veloceframework-0.1.0/tests/test_oauth2_extra_schemes.py +110 -0
- veloceframework-0.1.0/tests/test_oauth2_password_form.py +63 -0
- veloceframework-0.1.0/tests/test_oauth2_password_form_strict.py +57 -0
- veloceframework-0.1.0/tests/test_on_json_loading_failed.py +83 -0
- veloceframework-0.1.0/tests/test_openapi_array_query.py +87 -0
- veloceframework-0.1.0/tests/test_openapi_customization.py +122 -0
- veloceframework-0.1.0/tests/test_openapi_external_docs.py +28 -0
- veloceframework-0.1.0/tests/test_openapi_extra.py +85 -0
- veloceframework-0.1.0/tests/test_openapi_info_summary.py +39 -0
- veloceframework-0.1.0/tests/test_openapi_param_constraints.py +77 -0
- veloceframework-0.1.0/tests/test_openapi_param_examples.py +60 -0
- veloceframework-0.1.0/tests/test_openapi_param_title.py +70 -0
- veloceframework-0.1.0/tests/test_openapi_responses.py +181 -0
- veloceframework-0.1.0/tests/test_openapi_security.py +198 -0
- veloceframework-0.1.0/tests/test_options_auto.py +79 -0
- veloceframework-0.1.0/tests/test_param_constraints.py +103 -0
- veloceframework-0.1.0/tests/test_param_include_in_schema.py +59 -0
- veloceframework-0.1.0/tests/test_param_multiple_of.py +73 -0
- veloceframework-0.1.0/tests/test_passwords.py +137 -0
- veloceframework-0.1.0/tests/test_path_converters.py +241 -0
- veloceframework-0.1.0/tests/test_perf_cache_invariants.py +92 -0
- veloceframework-0.1.0/tests/test_preprocess_blueprint_hooks.py +108 -0
- veloceframework-0.1.0/tests/test_propagate_exceptions.py +129 -0
- veloceframework-0.1.0/tests/test_proxy_fix.py +183 -0
- veloceframework-0.1.0/tests/test_pydantic_param_validation.py +378 -0
- veloceframework-0.1.0/tests/test_query_list_marker.py +71 -0
- veloceframework-0.1.0/tests/test_range_header.py +117 -0
- veloceframework-0.1.0/tests/test_redirect_helper.py +49 -0
- veloceframework-0.1.0/tests/test_reliability_fixes.py +377 -0
- veloceframework-0.1.0/tests/test_render_async.py +58 -0
- veloceframework-0.1.0/tests/test_render_template_helpers.py +81 -0
- veloceframework-0.1.0/tests/test_request_aliases.py +67 -0
- veloceframework-0.1.0/tests/test_request_args_files.py +103 -0
- veloceframework-0.1.0/tests/test_request_auth.py +132 -0
- veloceframework-0.1.0/tests/test_request_blueprint_url_rule.py +79 -0
- veloceframework-0.1.0/tests/test_request_client.py +63 -0
- veloceframework-0.1.0/tests/test_request_cors_headers.py +49 -0
- veloceframework-0.1.0/tests/test_request_data_stream.py +51 -0
- veloceframework-0.1.0/tests/test_request_endpoint.py +63 -0
- veloceframework-0.1.0/tests/test_request_get_data.py +52 -0
- veloceframework-0.1.0/tests/test_request_get_json.py +77 -0
- veloceframework-0.1.0/tests/test_request_if_match.py +47 -0
- veloceframework-0.1.0/tests/test_request_if_range.py +33 -0
- veloceframework-0.1.0/tests/test_request_is_disconnected.py +34 -0
- veloceframework-0.1.0/tests/test_request_is_multipart_form.py +63 -0
- veloceframework-0.1.0/tests/test_request_is_xhr_values.py +76 -0
- veloceframework-0.1.0/tests/test_request_max_content_length.py +32 -0
- veloceframework-0.1.0/tests/test_request_mimetype.py +78 -0
- veloceframework-0.1.0/tests/test_request_misc_headers.py +54 -0
- veloceframework-0.1.0/tests/test_request_proxy.py +46 -0
- veloceframework-0.1.0/tests/test_request_root_path.py +45 -0
- veloceframework-0.1.0/tests/test_request_scheme.py +56 -0
- veloceframework-0.1.0/tests/test_request_session.py +83 -0
- veloceframework-0.1.0/tests/test_request_state.py +90 -0
- veloceframework-0.1.0/tests/test_request_streaming.py +211 -0
- veloceframework-0.1.0/tests/test_request_url_accessors.py +72 -0
- veloceframework-0.1.0/tests/test_request_url_for.py +59 -0
- veloceframework-0.1.0/tests/test_request_view_args.py +33 -0
- veloceframework-0.1.0/tests/test_response_background.py +149 -0
- veloceframework-0.1.0/tests/test_response_cache_control_vary.py +92 -0
- veloceframework-0.1.0/tests/test_response_charset_setter.py +37 -0
- veloceframework-0.1.0/tests/test_response_conditional.py +110 -0
- veloceframework-0.1.0/tests/test_response_content_enc_lang.py +58 -0
- veloceframework-0.1.0/tests/test_response_cookies_headerlist.py +68 -0
- veloceframework-0.1.0/tests/test_response_date_location.py +99 -0
- veloceframework-0.1.0/tests/test_response_dates.py +64 -0
- veloceframework-0.1.0/tests/test_response_etag_freeze.py +96 -0
- veloceframework-0.1.0/tests/test_response_header_safety.py +76 -0
- veloceframework-0.1.0/tests/test_response_iter_chunked.py +48 -0
- veloceframework-0.1.0/tests/test_response_json.py +52 -0
- veloceframework-0.1.0/tests/test_response_media_type.py +43 -0
- veloceframework-0.1.0/tests/test_response_mimetype.py +40 -0
- veloceframework-0.1.0/tests/test_response_mimetype_params.py +41 -0
- veloceframework-0.1.0/tests/test_response_model.py +201 -0
- veloceframework-0.1.0/tests/test_response_model_exclude_defaults.py +85 -0
- veloceframework-0.1.0/tests/test_response_param_injection.py +132 -0
- veloceframework-0.1.0/tests/test_response_range_headers.py +64 -0
- veloceframework-0.1.0/tests/test_response_retry_after_age.py +72 -0
- veloceframework-0.1.0/tests/test_response_set_data.py +52 -0
- veloceframework-0.1.0/tests/test_response_shape_aliases.py +71 -0
- veloceframework-0.1.0/tests/test_response_status_line.py +46 -0
- veloceframework-0.1.0/tests/test_response_vary_allow.py +82 -0
- veloceframework-0.1.0/tests/test_response_www_authenticate.py +49 -0
- veloceframework-0.1.0/tests/test_route_callbacks.py +70 -0
- veloceframework-0.1.0/tests/test_route_defaults.py +60 -0
- veloceframework-0.1.0/tests/test_route_metadata.py +164 -0
- veloceframework-0.1.0/tests/test_router.py +126 -0
- veloceframework-0.1.0/tests/test_router_default_response_class.py +62 -0
- veloceframework-0.1.0/tests/test_router_level_dependencies.py +91 -0
- veloceframework-0.1.0/tests/test_router_level_responses.py +115 -0
- veloceframework-0.1.0/tests/test_safe.py +153 -0
- veloceframework-0.1.0/tests/test_security_headers.py +68 -0
- veloceframework-0.1.0/tests/test_security_scopes.py +162 -0
- veloceframework-0.1.0/tests/test_send_file_helper.py +61 -0
- veloceframework-0.1.0/tests/test_send_static_file.py +56 -0
- veloceframework-0.1.0/tests/test_server_protocol.py +80 -0
- veloceframework-0.1.0/tests/test_server_side_sessions.py +339 -0
- veloceframework-0.1.0/tests/test_session_object.py +119 -0
- veloceframework-0.1.0/tests/test_session_permanent.py +90 -0
- veloceframework-0.1.0/tests/test_session_proxy.py +71 -0
- veloceframework-0.1.0/tests/test_session_rotation.py +110 -0
- veloceframework-0.1.0/tests/test_session_transaction.py +71 -0
- veloceframework-0.1.0/tests/test_set_cookie_expires.py +96 -0
- veloceframework-0.1.0/tests/test_set_cookie_max_age_timedelta.py +42 -0
- veloceframework-0.1.0/tests/test_set_cookie_partitioned.py +45 -0
- veloceframework-0.1.0/tests/test_shell_context.py +102 -0
- veloceframework-0.1.0/tests/test_signals.py +162 -0
- veloceframework-0.1.0/tests/test_signing.py +196 -0
- veloceframework-0.1.0/tests/test_static_conditional.py +111 -0
- veloceframework-0.1.0/tests/test_staticfiles_dirindex.py +105 -0
- veloceframework-0.1.0/tests/test_staticfiles_range.py +103 -0
- veloceframework-0.1.0/tests/test_staticfiles_streaming.py +67 -0
- veloceframework-0.1.0/tests/test_staticfiles_symlink.py +41 -0
- veloceframework-0.1.0/tests/test_status_websocket_codes.py +61 -0
- veloceframework-0.1.0/tests/test_stream_with_context.py +77 -0
- veloceframework-0.1.0/tests/test_streaming_response_asgi.py +103 -0
- veloceframework-0.1.0/tests/test_strict_slashes.py +87 -0
- veloceframework-0.1.0/tests/test_subdomain_routing.py +108 -0
- veloceframework-0.1.0/tests/test_swagger_ui_passthrough.py +91 -0
- veloceframework-0.1.0/tests/test_teardown_appcontext.py +126 -0
- veloceframework-0.1.0/tests/test_template_add_helpers.py +77 -0
- veloceframework-0.1.0/tests/test_template_autoescape.py +127 -0
- veloceframework-0.1.0/tests/test_template_folder.py +59 -0
- veloceframework-0.1.0/tests/test_template_helpers.py +157 -0
- veloceframework-0.1.0/tests/test_testclient_asgi.py +116 -0
- veloceframework-0.1.0/tests/test_testclient_cookies.py +81 -0
- veloceframework-0.1.0/tests/test_testclient_files.py +94 -0
- veloceframework-0.1.0/tests/test_testclient_redirects.py +122 -0
- veloceframework-0.1.0/tests/test_testclient_request.py +69 -0
- veloceframework-0.1.0/tests/test_trace_route.py +45 -0
- veloceframework-0.1.0/tests/test_trusted_host_and_https.py +183 -0
- veloceframework-0.1.0/tests/test_update_template_context.py +60 -0
- veloceframework-0.1.0/tests/test_uploadfile_save.py +123 -0
- veloceframework-0.1.0/tests/test_url_for_extended.py +102 -0
- veloceframework-0.1.0/tests/test_url_map.py +68 -0
- veloceframework-0.1.0/tests/test_url_path_for_alias.py +84 -0
- veloceframework-0.1.0/tests/test_url_processor_inspect.py +87 -0
- veloceframework-0.1.0/tests/test_url_processors.py +189 -0
- veloceframework-0.1.0/tests/test_validation_error_body.py +79 -0
- veloceframework-0.1.0/tests/test_vel2_correctness_bugs.py +218 -0
- veloceframework-0.1.0/tests/test_websocket_asgi.py +119 -0
- veloceframework-0.1.0/tests/test_websocket_client_state.py +49 -0
- veloceframework-0.1.0/tests/test_websocket_cookies.py +29 -0
- veloceframework-0.1.0/tests/test_websocket_depends.py +82 -0
- veloceframework-0.1.0/tests/test_websocket_di_parity.py +234 -0
- veloceframework-0.1.0/tests/test_websocket_exception.py +102 -0
- veloceframework-0.1.0/tests/test_websocket_iter.py +73 -0
- veloceframework-0.1.0/tests/test_websocket_origin.py +123 -0
- veloceframework-0.1.0/tests/test_websocket_query_params.py +52 -0
- veloceframework-0.1.0/tests/test_websocket_raw_messages.py +53 -0
- veloceframework-0.1.0/tests/test_websocket_request_validation.py +103 -0
- veloceframework-0.1.0/tests/test_websocket_spec.py +383 -0
- veloceframework-0.1.0/tests/test_websocket_state.py +62 -0
- veloceframework-0.1.0/tests/test_websocket_subprotocol.py +88 -0
- veloceframework-0.1.0/tests/test_yield_dependencies.py +265 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Byte-compiled / build artifacts
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Docs build output
|
|
12
|
+
site/
|
|
13
|
+
|
|
14
|
+
# Virtual envs
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
|
|
19
|
+
# Tooling caches
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
.coverage.*
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# Locks and credentials
|
|
28
|
+
.env
|
|
29
|
+
.env.*
|
|
30
|
+
!.env.example
|
|
31
|
+
.pypirc
|
|
32
|
+
|
|
33
|
+
# Editor / OS
|
|
34
|
+
.idea/
|
|
35
|
+
.vscode/
|
|
36
|
+
.DS_Store
|
|
37
|
+
Thumbs.db
|
|
38
|
+
|
|
39
|
+
# Local agent / editor config (local-only)
|
|
40
|
+
CLAUDE.local.md
|
|
41
|
+
CLAUDE.md
|
|
42
|
+
.claude/
|
|
43
|
+
.claude-dev-helper/
|
|
44
|
+
.playwright-mcp/
|
|
45
|
+
|
|
46
|
+
# Internal scratch (perf-audit dumps, bench notes) — never shipped
|
|
47
|
+
internal/
|
|
48
|
+
*.png
|
|
49
|
+
board-after-upload.png
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-05-23
|
|
10
|
+
|
|
11
|
+
First public release. Veloce is published to PyPI as `veloceframework`;
|
|
12
|
+
the import name `veloce` is unchanged.
|
|
13
|
+
|
|
14
|
+
### Highlights
|
|
15
|
+
|
|
16
|
+
- Async-first ASGI core with a hand-written radix-tree router, custom
|
|
17
|
+
request/response pipeline, in-memory `TestClient`, and a dependency
|
|
18
|
+
injection system that resolves precompiled plans (`HandlerPlan`) at
|
|
19
|
+
registration time so the per-request hot path performs no reflection.
|
|
20
|
+
- Feature surface covers Flask 3.x and FastAPI parity for the workflows
|
|
21
|
+
most apps reach for first — blueprints, dependency injection, OpenAPI
|
|
22
|
+
generation, Jinja templating, WebSockets, sessions, signals, and a
|
|
23
|
+
complete Werkzeug-shape request/response API.
|
|
24
|
+
- Performance contract: comparative benches in `benchmark.py` show
|
|
25
|
+
3-5x throughput vs equivalent FastAPI handlers and 4-7x vs Flask on
|
|
26
|
+
the JSON-hello and path-param hot paths.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
The entries below were authored during the `[Unreleased]` window and
|
|
31
|
+
ship as part of this release.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Per-request dispatch +21-39 % (profile-driven DSA pass).** Walked the
|
|
36
|
+
json-hello / path-param hot path under `cProfile` and applied seven
|
|
37
|
+
targeted shaves, each attributed to a measured delta:
|
|
38
|
+
* `Veloce._dispatch_request` defers `DependencyResolver()` until a
|
|
39
|
+
non-trivial route demands it. Trivial-plan routes (no injected
|
|
40
|
+
params, no dependencies) never construct the resolver — saves the
|
|
41
|
+
resolver allocation + two attribute writes per static-GET request.
|
|
42
|
+
* `DependencyResolver.__init__` no longer allocates a throwaway
|
|
43
|
+
`dict` + `WeakKeyDictionary` for `_overrides` / `_override_subplans`;
|
|
44
|
+
they default to module-level empty sentinels and the dispatcher
|
|
45
|
+
swaps in the real instances only when overrides exist.
|
|
46
|
+
* `Request.headers` is now a lazy property backed by `_headers_raw`
|
|
47
|
+
(raw ASGI `(bytes, bytes)` tuples). The `CIMultiDict` + per-tuple
|
|
48
|
+
`latin-1` decode is built only on first read. The hot path never
|
|
49
|
+
reads `request.headers`, so 2-3 us / req of work was being burned
|
|
50
|
+
on every dispatch.
|
|
51
|
+
* `_run_response_middleware` is gated at the main hot-path return
|
|
52
|
+
so the no-op coroutine + await is skipped when no middleware is
|
|
53
|
+
registered (avoids ~940 ns / req of frame setup).
|
|
54
|
+
* `_asgi_app` reads `scope["path"]` / `scope["query_string"]` via
|
|
55
|
+
subscript (ASGI mandates both keys), skipping `dict.get` default
|
|
56
|
+
handling.
|
|
57
|
+
* Built-in `Content-Type` strings (`application/json`,
|
|
58
|
+
`text/html; charset=utf-8`, `text/plain; charset=utf-8`,
|
|
59
|
+
`application/octet-stream`) and small `Content-Length` values
|
|
60
|
+
(0-2047) hit precomputed bytes caches; the per-request
|
|
61
|
+
`_reject_header_crlf(...).encode()` + `str(n).encode()`
|
|
62
|
+
allocations are skipped on cache hit.
|
|
63
|
+
* `Response._stream` joins `__slots__` initialised to `None`; the
|
|
64
|
+
`is_streamed` / `freeze` / `iter_encoded` / `iter_chunked` /
|
|
65
|
+
`cache_control` lookups become a direct slot load instead of a
|
|
66
|
+
`getattr(..., None)` walk.
|
|
67
|
+
In-loop bench (`bench/hot_dispatch_bench.py`) median of 3 runs:
|
|
68
|
+
static GET 68.1k → 94.8k req/s (+39 %),
|
|
69
|
+
path-param GET 54.5k → 66.0k (+21 %),
|
|
70
|
+
POST 64-byte body 62.0k → 77.2k (+25 %).
|
|
71
|
+
cProfile total time for 16k mixed dispatches dropped 1.51 s → 0.59 s
|
|
72
|
+
(~2.56×).
|
|
73
|
+
- **Stdlib `json` dropped in favour of `orjson` at the remaining two
|
|
74
|
+
sites.** `Config.from_prefixed_env`'s default `loads` is now
|
|
75
|
+
`orjson.loads`; `Config.from_file`'s default `load` is a new tiny
|
|
76
|
+
`_orjson_load(fp)` adaptor (orjson has no file-object loader).
|
|
77
|
+
`Swagger UI` HTML render emits `swagger_ui_parameters` and
|
|
78
|
+
`swagger_ui_init_oauth` via `orjson.dumps(...).decode()`. orjson
|
|
79
|
+
produces compact JSON (no space after `:`); the on-wire format
|
|
80
|
+
for embedded literals is now `"key":value` rather than `"key": value`
|
|
81
|
+
— the whitespace was never part of any contract and the JS parser
|
|
82
|
+
consumes both identically. Behaviour-equivalent for valid JSON;
|
|
83
|
+
catch sites unchanged because `orjson.JSONDecodeError` is a
|
|
84
|
+
`ValueError` subclass.
|
|
85
|
+
- **Per-request dispatch ~+17 %.** Profile-driven pass over the in-loop
|
|
86
|
+
ASGI hot path: `_setup_openapi` gated at call sites so the no-op
|
|
87
|
+
branch costs one attribute read instead of a frame; `_endpoint_blueprint`
|
|
88
|
+
no longer parsed three times per request when no blueprint hooks are
|
|
89
|
+
registered; `Headers`, the `current_app`/`current_request` contextvars,
|
|
90
|
+
and the `request_started`/`request_finished` signals hoisted to module
|
|
91
|
+
top instead of being re-imported per request; single-chunk request
|
|
92
|
+
body fast-path skips the `body_parts` list + `b"".join`;
|
|
93
|
+
`_reject_header_crlf` inlined as three short-circuited `in` checks;
|
|
94
|
+
`Signal.has_receivers_for` short-circuits on empty subscriber list;
|
|
95
|
+
`_run_teardowns` await skipped when no yield-dependencies registered.
|
|
96
|
+
In-loop bench (`bench/hot_dispatch_bench.py`): static GET 62.5k →
|
|
97
|
+
72.8k req/s (+16.5 %), path-param 47.8k → 57.0k (+19.4 %), POST 64-byte
|
|
98
|
+
body 54.8k → 64.2k (+17.2 %).
|
|
99
|
+
- **Router micro-ops.** `Router.match` tries the raw method on
|
|
100
|
+
`handlers.get` before `method.upper()` (RFC-conforming clients send
|
|
101
|
+
uppercase already); `_match_node` flattens static-only descent into a
|
|
102
|
+
`while` loop when the current node has no param/wildcard alternatives,
|
|
103
|
+
shaving one Python frame per static segment; single-param-child path
|
|
104
|
+
skips the rollback `del` (no alternative to back off to);
|
|
105
|
+
`FloatConverter.match` checks `"e" / "E"` directly instead of
|
|
106
|
+
allocating `value.lower()`.
|
|
107
|
+
- **Per-request rate-limit O(N) → O(1).** `RateLimitMiddleware` switches
|
|
108
|
+
from per-request list comprehension to `collections.deque` +
|
|
109
|
+
amortised `popleft`. Periodic eviction sweep now mutates in place
|
|
110
|
+
with a snapshot-then-recheck guard so an append racing with the
|
|
111
|
+
sweep is not silently dropped.
|
|
112
|
+
- **Override-dependency sub-plan cache hoisted to the app.** Each
|
|
113
|
+
request's fresh `DependencyResolver` shares
|
|
114
|
+
`Veloce._override_subplans`, eliminating the per-request `build_plan`
|
|
115
|
+
+ triple `inspect.is*function` probe on override hits. The cache is
|
|
116
|
+
cleared when `dependency_overrides` is reassigned.
|
|
117
|
+
- **Mount-prefix slash precomputed.** `_mounted_apps` /
|
|
118
|
+
`_asgi_mounts` now store `(prefix, prefix + "/", app)` so dispatch
|
|
119
|
+
doesn't reallocate `prefix + "/"` per request per mount.
|
|
120
|
+
- **Exception-handler signature cache.** `_call_exc_handler` memoises
|
|
121
|
+
`(wants_request, wants_exc)` flags per handler in a
|
|
122
|
+
`WeakKeyDictionary`, eliminating the `inspect.signature` walk per
|
|
123
|
+
raised exception.
|
|
124
|
+
- **`jsonable_encoder` primitives short-circuit.** The
|
|
125
|
+
`None | str | int | float | bool` branch is hoisted to the top of
|
|
126
|
+
the dispatch so leaf calls hit it before any of the heavier
|
|
127
|
+
`isinstance` checks.
|
|
128
|
+
|
|
129
|
+
### Fixed
|
|
130
|
+
|
|
131
|
+
- **ETag drift between StaticFiles and FileResponse.** `StaticFiles._compute_etag`
|
|
132
|
+
now delegates to `veloce.http.response._file_etag` so a static handler
|
|
133
|
+
and `FileResponse` over the same file emit identical ETags and
|
|
134
|
+
validate identically against `If-None-Match`. Signature changed
|
|
135
|
+
from `(path, mtime)` to `(path, size, mtime)` — `_`-prefixed and
|
|
136
|
+
therefore private, but flagged for subclassers.
|
|
137
|
+
- **WebSocket ASGI-mode unbounded queue.** `WebSocket.from_asgi`
|
|
138
|
+
builds `_receive_queue` with `maxsize=DEFAULT_RECV_QUEUE_MAXSIZE`
|
|
139
|
+
instead of an unbounded queue. The queue is unused in ASGI mode
|
|
140
|
+
today; the bound prevents a footgun if future changes start feeding
|
|
141
|
+
it.
|
|
142
|
+
|
|
143
|
+
### Added
|
|
144
|
+
|
|
145
|
+
- **Comparative bench harness** (`bench/comparative/`): head-to-head
|
|
146
|
+
latency and throughput measurements vs Flask and FastAPI under the
|
|
147
|
+
same uvicorn runtime. Each workload runs all three frameworks in
|
|
148
|
+
randomised order through a single `httpx.AsyncClient`, with a
|
|
149
|
+
discarded cold-cache round to dampen first-run penalties. Reports
|
|
150
|
+
median rps, p50, p99. `--seed` pins the schedule for reproducibility.
|
|
151
|
+
Initial workloads: `json-hello` and `path-param`. Results recorded
|
|
152
|
+
under `docs/bench/`. Veloce wins rps + p50 + p99 vs FastAPI on both
|
|
153
|
+
workloads, and wins rps vs Flask by ~57 % (Flask wins p50/p99 under
|
|
154
|
+
`asgiref.WsgiToAsgi` at low concurrency — see caveats in
|
|
155
|
+
`docs/bench/README.md`).
|
|
156
|
+
- `WebSocket.origin` accessor returns the handshake `Origin` header
|
|
157
|
+
(or `None`); `WebSocket.check_origin(allowed)` returns `True` only
|
|
158
|
+
when the origin is on the allow-list. Normalisation
|
|
159
|
+
(`.rstrip("/").lower()`) and wildcard (`"*"`) semantics match the
|
|
160
|
+
registered-once `WebSocketOriginMiddleware`, so allow-lists are
|
|
161
|
+
interchangeable between the two APIs. `Origin: null` (sandboxed
|
|
162
|
+
iframes / `file://`) is rejected. The pair lets handlers reject
|
|
163
|
+
Cross-Site WebSocket Hijacking before `accept()` — the WebSocket
|
|
164
|
+
handshake is plain HTTP, so Same-Origin Policy and CORS do not apply.
|
|
165
|
+
- **Application core** — `Veloce` app object with HTTP method decorators
|
|
166
|
+
(`get`/`post`/`put`/`patch`/`delete`/`head`/`options`/`trace`),
|
|
167
|
+
lifespan handling, configurable docs URLs, and `app.run()`.
|
|
168
|
+
- **Radix-tree router** — typed path converters (`int`, `float`, `str`,
|
|
169
|
+
`uuid`, `path`, custom registered converters), per-route
|
|
170
|
+
`strict_slashes`, subdomain and host constraints, and rule defaults.
|
|
171
|
+
- **Request / Response** — lazily-parsed `Request` with multi-value
|
|
172
|
+
query/header/cookie/form accessors, parsed conditional and range
|
|
173
|
+
headers, and a `Response` family (`JSONResponse`, `HTMLResponse`,
|
|
174
|
+
`PlainTextResponse`, `RedirectResponse`, `StreamingResponse`,
|
|
175
|
+
`FileResponse`, `ORJSONResponse`, `UJSONResponse`).
|
|
176
|
+
- **Dependency injection** — `Depends` / `Security` / `SecurityScopes`,
|
|
177
|
+
`yield`-style dependencies with teardown, `Annotated[...]` form,
|
|
178
|
+
bare `Depends()` annotation inference, and `app.dependency_overrides`.
|
|
179
|
+
- **Parameters** — `Query`, `Path`, `Header`, `Cookie`, `Body`, `Form`,
|
|
180
|
+
`File` markers with constraints (`ge`/`le`/`gt`/`lt`, `min_length`/
|
|
181
|
+
`max_length`, `pattern`, `multiple_of`), list-valued collection, and
|
|
182
|
+
`include_in_schema` / `title` / `examples`.
|
|
183
|
+
- **OpenAPI 3.1** — auto-generated schema, Swagger UI and ReDoc, security
|
|
184
|
+
scheme emission, route-level `responses` / `callbacks` /
|
|
185
|
+
`openapi_extra`, and a documentation-only `app.webhooks` router.
|
|
186
|
+
- **WebSockets** — ASGI WebSocket routing, raw message API, JSON/text/
|
|
187
|
+
bytes iteration, subprotocol negotiation, dependency injection, and
|
|
188
|
+
`WebSocketException` / `WebSocketRequestValidationError`.
|
|
189
|
+
- **Middleware** — CORS, GZip, TrustedHost, HTTPS redirect, sessions,
|
|
190
|
+
rate limiting, request ID, proxy header handling, plus a
|
|
191
|
+
dispatch-style base class.
|
|
192
|
+
- **Templating** — Jinja2 integration with async rendering, context
|
|
193
|
+
processors, and registered filters/globals/tests.
|
|
194
|
+
- **Sessions** — signed, timestamped cookie sessions with secret
|
|
195
|
+
rotation, a mutation-tracking `Session` container, and persistent
|
|
196
|
+
(`permanent`) sessions.
|
|
197
|
+
- **Utilities** — background tasks, signals, Server-Sent Events,
|
|
198
|
+
blueprints with nesting, class-based views, a CLI (`veloce run` /
|
|
199
|
+
`veloce routes` / `veloce shell`), and an in-memory `TestClient`.
|
|
200
|
+
- **Security helpers** — HTTP Basic / Bearer / Digest, API key schemes,
|
|
201
|
+
OAuth2 password and authorization-code flows, password hashing, and
|
|
202
|
+
signed-value serialisation.
|
|
203
|
+
- `veloce.status` gains `HTTP_208_ALREADY_REPORTED`, `HTTP_226_IM_USED`,
|
|
204
|
+
and `HTTP_421_MISDIRECTED_REQUEST` for full IANA HTTP status coverage.
|
|
205
|
+
- `constant_time_compare(a, b)` — a timing-safe secret-comparison helper
|
|
206
|
+
(wrapping `hmac.compare_digest`), exported from the top-level package.
|
|
207
|
+
- Veloce's `WebSocket` frame parser reassembles fragmented messages —
|
|
208
|
+
a `FIN=0` data frame followed by continuation frames (RFC 6455 §5.4);
|
|
209
|
+
control frames may be interleaved without disturbing the in-progress
|
|
210
|
+
message.
|
|
211
|
+
- `add_middleware` now accepts a standard ASGI middleware class — any
|
|
212
|
+
class that is not a veloce `Middleware` subclass is treated as ASGI
|
|
213
|
+
middleware and wraps the whole application (`cls(app, **options)`),
|
|
214
|
+
so the third-party ASGI ecosystem (tracing, profiling, observability)
|
|
215
|
+
plugs into a veloce app. The first-registered ASGI middleware is the
|
|
216
|
+
outermost wrapper. Native `Middleware` classes are unaffected.
|
|
217
|
+
- `AsyncTestClient` (and the `app.async_test_client()` factory) — the
|
|
218
|
+
async counterpart of `TestClient`. Used as `async with` inside an
|
|
219
|
+
async test, its request methods are coroutines awaited on the test's
|
|
220
|
+
own running event loop. Cookie persistence, redirect following, and
|
|
221
|
+
the JSON / form / files body shapes match `TestClient`.
|
|
222
|
+
- `app.mount(prefix, app)` now accepts any ASGI application, not only a
|
|
223
|
+
veloce sub-app. A non-veloce app is dispatched at the ASGI layer with
|
|
224
|
+
the matched prefix moved from the scope's `path` onto `root_path`;
|
|
225
|
+
veloce sub-apps keep their existing dispatch path. A mounted ASGI app
|
|
226
|
+
receives `http` and `websocket` scopes — the parent app owns the
|
|
227
|
+
`lifespan` cycle, so a mounted app must self-initialise rather than
|
|
228
|
+
rely on ASGI `lifespan` events. Mount prefixes must not overlap.
|
|
229
|
+
- `Config.from_env_file(path)` loads a dotenv-style `.env` file —
|
|
230
|
+
`KEY=VALUE` lines, `#` comments, an optional `export ` prefix, and
|
|
231
|
+
quoted values — into the app config (UPPERCASE keys only).
|
|
232
|
+
- `app.run(ssl_context=...)` — the built-in development server now
|
|
233
|
+
accepts an optional `ssl.SSLContext`, handed straight to
|
|
234
|
+
`loop.create_server(ssl=...)`, for local HTTPS testing. Left unset the
|
|
235
|
+
serving path is byte-for-byte the same plain-HTTP path as before.
|
|
236
|
+
Production should still terminate TLS at uvicorn or a reverse proxy.
|
|
237
|
+
- `EventLoopWatchdog` — an opt-in development aid that detects a
|
|
238
|
+
coroutine blocking the event loop (a synchronous driver, `time.sleep`,
|
|
239
|
+
a CPU-heavy loop) and logs a warning carrying the blocked stack and a
|
|
240
|
+
prescriptive hint (blocking-I/O vs CPU-bound). A loop heartbeat plus a
|
|
241
|
+
separate daemon thread spot the stall. Enable it with the
|
|
242
|
+
`EVENT_LOOP_WATCHDOG` config key; unset (the default) nothing is
|
|
243
|
+
constructed, so a production app pays nothing.
|
|
244
|
+
- `ServerSessionMiddleware` keeps the session payload server-side in a
|
|
245
|
+
pluggable `SessionStore` (default: an in-process `InMemorySessionStore`)
|
|
246
|
+
— the cookie carries only an opaque, high-entropy session id. Sessions
|
|
247
|
+
are now *revocable*: empty one in a handler (`session.clear()`) or
|
|
248
|
+
delete it straight from the store (`await store.delete(session_id)`),
|
|
249
|
+
and a tampered or stale cookie simply fails to resolve. A network
|
|
250
|
+
backend (e.g. Redis) plugs in by implementing the async `SessionStore`
|
|
251
|
+
interface. The existing signed-cookie `SessionMiddleware` is unchanged.
|
|
252
|
+
- `app.add_instrumentation(hook)` registers an observability hook called
|
|
253
|
+
once per finished HTTP request with a `RequestMetrics` record — method,
|
|
254
|
+
concrete path, matched route *template* (a low-cardinality metric
|
|
255
|
+
label), status code, and wall-clock duration. Hooks may be sync or
|
|
256
|
+
async; one that raises is logged and never breaks the response. With no
|
|
257
|
+
hook registered the request path pays nothing — not even a clock read.
|
|
258
|
+
The `request_started` / `request_finished` signals now also carry the
|
|
259
|
+
`Request`, so a tracing bridge can correlate a request's start with its
|
|
260
|
+
finish.
|
|
261
|
+
- Query / path / header / cookie parameters are now validated through
|
|
262
|
+
Pydantic for any annotation the fast scalar path does not cover —
|
|
263
|
+
`datetime`, `date`, `time`, `UUID`, `Decimal`, `Literal[...]` and other
|
|
264
|
+
rich types are parsed and rejected with a `422` on bad input, the same
|
|
265
|
+
treatment a request-body model already received. The `str` / `int` /
|
|
266
|
+
`float` / `bool` / `Enum` dispatch fast path is untouched. OpenAPI
|
|
267
|
+
parameter schemas now emit the matching `format` / `enum` keywords
|
|
268
|
+
instead of collapsing every non-primitive to a bare string.
|
|
269
|
+
|
|
270
|
+
### Changed
|
|
271
|
+
|
|
272
|
+
- A handler that returns a bare `str` now defaults to
|
|
273
|
+
`Content-Type: text/html; charset=utf-8` (previously `text/plain`), so a
|
|
274
|
+
bare-`str` return and `make_response(str)` produce the same media type.
|
|
275
|
+
- Multipart form parsing now uses the `python-multipart` streaming
|
|
276
|
+
parser instead of an in-memory `body.split` — it correctly handles a
|
|
277
|
+
boundary token that happens to occur inside binary file data, and a
|
|
278
|
+
malformed body degrades to the parts that parsed cleanly rather than a
|
|
279
|
+
`500`.
|
|
280
|
+
- `app.run()` starts the built-in **development** server; it now logs a
|
|
281
|
+
startup reminder that production deployments should run under uvicorn
|
|
282
|
+
(or another ASGI server). See the new Deployment guide.
|
|
283
|
+
- `Response.set_cookie` now defaults `samesite` to `"Lax"` — a
|
|
284
|
+
CSRF-resistant default that matches modern browser behaviour. Pass
|
|
285
|
+
`samesite=None` to omit the attribute, or `"None"` (with `secure=True`)
|
|
286
|
+
for a genuinely cross-site cookie.
|
|
287
|
+
- WebSocket dependency injection now runs through the same pre-planned
|
|
288
|
+
`HandlerPlan` / `DependencyResolver` as HTTP dispatch. WebSocket
|
|
289
|
+
dependencies gain `yield`-style teardown and `Security` /
|
|
290
|
+
`SecurityScopes` support, and path parameters are coerced to their
|
|
291
|
+
annotated type — previously WebSocket DI used a separate, weaker
|
|
292
|
+
resolver that supported none of these.
|
|
293
|
+
- An uploaded file is now backed by a `SpooledTemporaryFile`: each
|
|
294
|
+
multipart part streams into one as it is parsed, staying in memory
|
|
295
|
+
while small and rolling over to a real temp file on disk once it grows
|
|
296
|
+
past 1 MiB. A large upload no longer holds two or three full copies of
|
|
297
|
+
itself in RAM (raw body + per-part `bytearray` + `BytesIO`).
|
|
298
|
+
- `request.stream()` now yields the body in bounded 64 KiB chunks instead
|
|
299
|
+
of one chunk covering the whole body, so a handler can process a large
|
|
300
|
+
body incrementally.
|
|
301
|
+
|
|
302
|
+
### Fixed
|
|
303
|
+
|
|
304
|
+
- `register_blueprint` no longer drops a blueprint's routes registered with
|
|
305
|
+
`include_in_schema=False`, nor its WebSocket routes — every route is added
|
|
306
|
+
to the radix tree.
|
|
307
|
+
- `EventSourceResponse` encodes yielded `ServerSentEvent` objects over the
|
|
308
|
+
ASGI transport instead of raising `TypeError`.
|
|
309
|
+
- Dependency type hints are resolved from the right object for class
|
|
310
|
+
dependencies (`__init__`), callable instances (`__call__`), and
|
|
311
|
+
`functools.partial` wrappers, so their parameter types are coerced.
|
|
312
|
+
- The in-memory `TestClient` accepts WebSocket connect paths that include a
|
|
313
|
+
query string.
|
|
314
|
+
- `WebSocket.send()` — the raw ASGI-message escape hatch — now enforces the
|
|
315
|
+
same handshake state machine as `send_text` / `send_bytes`: sending before
|
|
316
|
+
`accept()` or after `close()` raises instead of proceeding silently.
|
|
317
|
+
- `WebSocket.receive_text` / `receive_bytes` / `receive_json` enforce the
|
|
318
|
+
same handshake state machine as their `send_*` siblings: calling them
|
|
319
|
+
before `accept()` raises `RuntimeError` (was: hung on an empty queue)
|
|
320
|
+
and calling them after `close()` raises `WebSocketDisconnect`.
|
|
321
|
+
- Multipart parsing no longer leaks a `SpooledTemporaryFile` when a request
|
|
322
|
+
is rejected by a DoS cap (oversized part or too many parts): the
|
|
323
|
+
in-progress part's spool and every spool already collected from
|
|
324
|
+
completed parts are closed on the reject path.
|
|
325
|
+
- Server-side sessions use a conditional store write: a session revoked by
|
|
326
|
+
a concurrent request (logout, `store.delete(...)`) while another request
|
|
327
|
+
is in flight is no longer resurrected when that request writes back —
|
|
328
|
+
`SessionStore` gains a race-safe `replace()` method.
|
|
329
|
+
- HTTP requests each get their own `DependencyResolver` instead of sharing
|
|
330
|
+
one: a concurrent request can no longer clear another in-flight request's
|
|
331
|
+
pending `yield`-dependency teardowns (database session close, file-handle
|
|
332
|
+
release), which previously could be silently skipped under load.
|
|
333
|
+
- `Config.from_env_file` strips an unquoted inline ` #` comment from a
|
|
334
|
+
`.env` value (`KEY=value # note` → `value`) while leaving a `#` inside a
|
|
335
|
+
quoted value intact.
|
|
336
|
+
- `app.mount()` rejects an overlapping prefix registration (a prefix equal
|
|
337
|
+
to, nested under, or containing an existing mount) with `ValueError`,
|
|
338
|
+
instead of silently shadowing one mount with another.
|
|
339
|
+
- `Request.files()` no longer returns duplicate `UploadFile` entries, nor
|
|
340
|
+
runs in O(n²), when several files are uploaded under one form field
|
|
341
|
+
name — it now iterates the form's `(key, value)` pairs once.
|
|
342
|
+
- `StaticFiles._etag_cache` is now a bounded LRU (default cap 1 024 entries
|
|
343
|
+
per instance, configurable via the class attribute `ETAG_CACHE_MAX`).
|
|
344
|
+
The previous unbounded dict grew for the lifetime of the worker on a
|
|
345
|
+
large static tree.
|
|
346
|
+
- `LoggingMiddleware` no longer keeps a per-instance dict keyed by
|
|
347
|
+
`id(request)`. The start timestamp lives on `request._state`, so it
|
|
348
|
+
cannot leak when a handler raises, and a recycled `id()` cannot
|
|
349
|
+
collide with a stale entry to log a nonsensical duration.
|
|
350
|
+
- `jsonable_encoder(obj, include=..., exclude=...)` forwards the filters
|
|
351
|
+
into recursive calls — `exclude={"password"}` now strips the field at
|
|
352
|
+
every depth, matching the dataclass branch.
|
|
353
|
+
- `Signal` actually filters by sender. A receiver connected with
|
|
354
|
+
`signal.connect(fn, sender=X)` fires only when `send(X)` runs (matched
|
|
355
|
+
by `is`, falling back to `==`). A receiver connected with the default
|
|
356
|
+
`sender=ANY_SENDER` (sentinel re-exported from `veloce.signals`) still
|
|
357
|
+
fires for every send.
|
|
358
|
+
- `UploadFile.read`/`write`/`seek`/`close` now offload the blocking
|
|
359
|
+
filesystem syscalls to a thread once the spool has rolled over to
|
|
360
|
+
disk; the cheap in-memory `BytesIO` path stays on the loop.
|
|
361
|
+
- `hash_password_async` / `verify_password_async` — async-safe wrappers
|
|
362
|
+
that run the scrypt KDF on a thread. The sync `hash_password` /
|
|
363
|
+
`verify_password` are unchanged; calling either from an `async def`
|
|
364
|
+
handler blocks the loop for ~100 ms, so async handlers should reach
|
|
365
|
+
for the `_async` variants. Both are exported from the top-level
|
|
366
|
+
package.
|
|
367
|
+
- `WebSocket._receive_queue` is now bounded (default `maxsize=64`;
|
|
368
|
+
configurable via the `recv_queue_maxsize` constructor argument). The
|
|
369
|
+
cap turns the previously-unbounded queue into a backpressure signal:
|
|
370
|
+
a peer that sends faster than the handler reads now blocks the
|
|
371
|
+
producer on `put` instead of growing the queue without limit.
|
|
372
|
+
- All four wall-clock perf checks in the test suite —
|
|
373
|
+
`TestPerformanceAfterFixes` (`tests/test_async_safety.py`),
|
|
374
|
+
`TestNoSyncIOInHotPath` (`tests/test_async_io.py`), and
|
|
375
|
+
`TestPerformance` (`tests/test_iteration3.py`) — are now marked
|
|
376
|
+
`@pytest.mark.perf` and excluded from the default `pytest` run via
|
|
377
|
+
`addopts = ["-m", "not perf"]` in `pyproject.toml`. The
|
|
378
|
+
relative-to-async budget alone was still flaky under full-suite CPU
|
|
379
|
+
contention; opt in with `pytest -m perf` on a quiet machine. Catastrophic
|
|
380
|
+
dispatch regressions remain gated in CI by
|
|
381
|
+
`bench/dispatch_bench.py --min-rps 2000`.
|
|
382
|
+
- Documentation corrected against the code: sync (`def`) handlers are
|
|
383
|
+
documented as supported (run in a thread-pool executor); the built-in
|
|
384
|
+
development server is documented as HTTP/1.1-only (WebSocket and HTTP/2
|
|
385
|
+
workloads run under an external ASGI server); the shipped
|
|
386
|
+
`ServerSessionMiddleware` / `SessionStore` replaces a stale "on the
|
|
387
|
+
roadmap" note; and scoped request hooks are clarified as a `Blueprint`
|
|
388
|
+
feature, not a plain `Router` one.
|
|
389
|
+
|
|
390
|
+
### Performance
|
|
391
|
+
|
|
392
|
+
- Radix-tree param-child lookup is now O(1) at registration via a
|
|
393
|
+
`(param_name, converter_type)` sidecar index (CL40 / CL41). The
|
|
394
|
+
ordered list remains the source of truth at match time so traversal
|
|
395
|
+
semantics are unchanged. `_split_path` also drops its `strip("/")`
|
|
396
|
+
pass — the empty-string filter on the split already handles
|
|
397
|
+
leading, trailing, and consecutive slashes.
|
|
398
|
+
- Independent sibling `Depends()` slots now resolve in parallel via
|
|
399
|
+
`asyncio.gather` when safe (CL10). The resolver looks ahead for
|
|
400
|
+
contiguous `K_DEPENDS` siblings and dispatches them concurrently
|
|
401
|
+
unless the run contains a `Security()` scope-pushing slot, a
|
|
402
|
+
yield-style dependency, or two siblings sharing a `use_cache=True`
|
|
403
|
+
callable — those cases preserve the sequential semantics that
|
|
404
|
+
protect the resolver's shared cache, security-scope stack, and
|
|
405
|
+
teardown ordering. Handlers with multiple I/O-bound deps now wait
|
|
406
|
+
for `max(durations)` instead of `sum(durations)`.
|
|
407
|
+
- Blueprint hooks (`before_request`, `after_request`,
|
|
408
|
+
`teardown_request`) are now bucketed by blueprint at registration
|
|
409
|
+
(CL22). Dispatch reads the bucket for the matched route's endpoint
|
|
410
|
+
prefix instead of iterating every blueprint's gated wrapper and
|
|
411
|
+
doing a `startswith` no-op on each — eliminates the O(B·H)
|
|
412
|
+
per-request overhead for apps with many blueprints.
|
|
413
|
+
- `StaticFiles` streams files at or above `STREAM_THRESHOLD` (1 MiB
|
|
414
|
+
default) via `StreamingResponse` instead of buffering the whole
|
|
415
|
+
body (CL4). Worker RSS no longer grows by the file size for the
|
|
416
|
+
duration of a large download. Range requests still buffer the
|
|
417
|
+
slice, which is already bounded by the client.
|
|
418
|
+
- `Request.mimetype` and `Request.mimetype_params` now share a single
|
|
419
|
+
cached parse (CL14): the first access populates `_parsed_ct` with a
|
|
420
|
+
`(mimetype, params)` tuple; subsequent reads on either property hit
|
|
421
|
+
the cache. Handlers that touch `content_type` repeatedly (form
|
|
422
|
+
parsers, validators) stop re-splitting the same string per access.
|
|
423
|
+
- `_find_exception_handler` now memoises the MRO walk per exception
|
|
424
|
+
type. The cache is cleared whenever a new error handler is
|
|
425
|
+
registered so a fresh registration takes effect for previously
|
|
426
|
+
cached subclasses.
|
|
427
|
+
- `SignedSerializer.loads` does a single `token.split(".", 2)`
|
|
428
|
+
instead of `token.count(".") != 2` + `token.split(".")`. The
|
|
429
|
+
early-validation path is now one pass over the string.
|
|
430
|
+
- `LoggingMiddleware` short-circuits when the logger has the access
|
|
431
|
+
level disabled — both the `time.monotonic()` clock read on entry
|
|
432
|
+
and the duration calculation on response.
|
|
433
|
+
- `SessionMiddleware._cookie` / cookie composition use
|
|
434
|
+
`"; ".join(parts)` instead of a chain of `+=` concatenations,
|
|
435
|
+
cutting intermediate string allocations.
|
|
436
|
+
- `StaticFiles` directory listing reads `is_dir` from `os.scandir`'s
|
|
437
|
+
cached dirent — saves a per-entry `os.path.isdir` syscall.
|
|
438
|
+
Behaviour note: symlinks inside a listed directory are now classified
|
|
439
|
+
via the symlink itself, not its target — a symlink to a subdirectory
|
|
440
|
+
renders as a plain entry rather than a directory entry. This avoids
|
|
441
|
+
advertising symlink targets in the listing and matches the
|
|
442
|
+
symlink-safety stance the static handler already takes.
|
|
443
|
+
- `Response.encode()` no longer rebuilds the header dict on every
|
|
444
|
+
response: the three framework defaults (`Content-Type`,
|
|
445
|
+
`Content-Length`, `Connection`) are emitted inline only when the
|
|
446
|
+
caller has not supplied them. A case-insensitive check at the same
|
|
447
|
+
time removes a latent duplicate-header bug where a user-supplied
|
|
448
|
+
`"content-type"` (lowercase) would land alongside the default
|
|
449
|
+
`"Content-Type"` in the encoded line. Reason phrase comes from the
|
|
450
|
+
module-level `{code: phrase}` map already added for `Response.status`.
|
|
451
|
+
- `StaticFiles` now satisfies the existence + stat with a single
|
|
452
|
+
executor `os.stat` call, classifying file/dir from `st_mode` —
|
|
453
|
+
the previous request path issued `isfile` and then a second `stat`
|
|
454
|
+
for size/mtime, doubling executor round-trips.
|
|
455
|
+
- WebSocket `_send_frame` hands the header + payload to the transport
|
|
456
|
+
as two separate buffers via `writelines` instead of
|
|
457
|
+
`bytearray.extend(data)` + `bytes(frame)`. On a 64 KiB frame that
|
|
458
|
+
saves a 64 KiB memcpy on the way out.
|
|
459
|
+
- SSE: single-line event payloads skip the `data.split("\n")` list
|
|
460
|
+
allocation; chunked SSE writes use `transport.writelines` instead
|
|
461
|
+
of concatenating size-line + body + trailer into one fresh bytes
|
|
462
|
+
per chunk.
|
|
463
|
+
- SSE status-line reuses the response-module `_STATUS_PHRASES` table
|
|
464
|
+
rather than `from http import HTTPStatus` + `HTTPStatus(code).phrase`
|
|
465
|
+
on every stream startup.
|
|
466
|
+
- `http_date(None)` (the per-response `Date:` header) caches the
|
|
467
|
+
RFC 9110 IMF-fixdate to one-second resolution — `formatdate()` ran
|
|
468
|
+
once per response despite the output only changing once a second.
|
|
469
|
+
- App-level hook dispatch (`teardown_request`, `teardown_appcontext`,
|
|
470
|
+
`_call_handler`) reads `iscoroutinefunction` from a memoised cache
|
|
471
|
+
keyed by `id(fn)`. Hooks register once and are dispatched many times;
|
|
472
|
+
the inline `inspect.iscoroutinefunction(...)` walk on every request
|
|
473
|
+
is replaced by a dict lookup.
|
|
474
|
+
- `CORSMiddleware` precomputes `", ".join(self.allow_methods)`,
|
|
475
|
+
`", ".join(self.allow_headers)`, and `", ".join(self.expose_headers)`
|
|
476
|
+
at construction. Per-response preflight emission now hits the
|
|
477
|
+
precomputed strings instead of rebuilding them on every cross-origin
|
|
478
|
+
reply.
|
|
479
|
+
- Cookie-based `SessionMiddleware` drops the
|
|
480
|
+
`json.dumps(sort_keys=True)` mutation tripwire (computed on entry
|
|
481
|
+
*and* on exit) in favour of the `Session.modified` flag the
|
|
482
|
+
`Session` container already maintains — saves two full
|
|
483
|
+
serialisations on every request that traverses the middleware.
|
|
484
|
+
- WebSocket frame unmasking is now bulk-XOR via `int.from_bytes` /
|
|
485
|
+
`to_bytes` over the tiled mask, replacing a Python-level per-byte
|
|
486
|
+
loop. Saves measurable CPU on any frame past a handful of bytes
|
|
487
|
+
(WebSocket payloads are usually hundreds to KiB-sized).
|
|
488
|
+
- `Response.status` reads the reason phrase from a module-level
|
|
489
|
+
`{code: phrase}` map built once at import time, instead of
|
|
490
|
+
constructing an `HTTPStatus(code)` IntEnum-walk on every access.
|
|
491
|
+
- `email.utils.parsedate_to_datetime` is now imported at module load
|
|
492
|
+
in `veloce.http.request` instead of re-imported inside four
|
|
493
|
+
conditional-request hot properties.
|
|
494
|
+
- `StaticFiles` caches `mimetypes.guess_type` per file path
|
|
495
|
+
(bounded LRU, 512 entries) — `guess_type` was running its full MIME
|
|
496
|
+
table walk on every static hit.
|
|
497
|
+
- `safe.secure_filename` uses a module-level compiled regex for the
|
|
498
|
+
underscore-run collapser, removing the per-call `re` cache lookup.
|
|
499
|
+
- Per-request reflection eliminated from the hot path: handler
|
|
500
|
+
signatures are inspected once at registration into a frozen
|
|
501
|
+
resolution plan.
|
|
502
|
+
- Static route lookup is O(1) per tree level — static child nodes are
|
|
503
|
+
indexed in a dict instead of scanned linearly, so match cost no longer
|
|
504
|
+
grows with the number of sibling routes.
|
|
505
|
+
- Request dispatch matches each route once per request instead of twice.
|
|
506
|
+
- The dependency resolver no longer re-imports its slot-kind constants on
|
|
507
|
+
every call, and `_call_handler` skips a per-request
|
|
508
|
+
coroutine-function probe by reading the precomputed handler plan.
|
|
509
|
+
- `StaticFiles` resolves the served root's real path once at construction
|
|
510
|
+
rather than on every request; the request-scoped `g` store is allocated
|
|
511
|
+
lazily, so handlers that never touch `g` pay no allocation.
|
|
512
|
+
- A parameter's `pattern` / `regex` constraint is compiled once at
|
|
513
|
+
declaration time instead of recompiled on every `validate` call.
|
|
514
|
+
- `CORSMiddleware` precomputes its origin allow-list as a frozenset and a
|
|
515
|
+
lowercased header allow-set at construction, so per-request CORS checks
|
|
516
|
+
are O(1) instead of scanning a list.
|
|
517
|
+
- `Jinja2Templates` `auto_reload` now follows the bound app's `debug`
|
|
518
|
+
flag when left unset — production rendering skips the per-render
|
|
519
|
+
template `stat` syscall. Pass an explicit `auto_reload=` to pin it.
|
|
520
|
+
- `response_model=list[Model]` dumps a handler-returned element that is
|
|
521
|
+
already an instance of the target model directly, skipping a
|
|
522
|
+
re-validation round-trip (and correctly preserving per-element
|
|
523
|
+
`exclude_unset`, matching the scalar `response_model` path).
|
|
524
|
+
- The ASGI entry point decodes request headers via a list comprehension
|
|
525
|
+
rather than a generator, trimming a per-header generator-frame resume.
|
|
526
|
+
- A route whose handler takes no injected parameters and declares no
|
|
527
|
+
dependencies is now dispatched through a trivial-route fast path that
|
|
528
|
+
skips the dependency resolver entirely instead of resolving to `{}`.
|
|
529
|
+
|
|
530
|
+
### Security
|
|
531
|
+
|
|
532
|
+
- `MAX_CONTENT_LENGTH` is now enforced incrementally — an oversized
|
|
533
|
+
request body is refused with `413` while still being received, before
|
|
534
|
+
the whole payload is buffered into memory.
|
|
535
|
+
- The built-in development server enforces a request-read timeout
|
|
536
|
+
(`HttpProtocol.REQUEST_TIMEOUT`, 30 s) — a half-sent request is dropped
|
|
537
|
+
with `408`, bounding how long a slowloris-style slow client can pin a
|
|
538
|
+
connection open.
|
|
539
|
+
- `WebSocketOriginMiddleware` rejects cross-site WebSocket handshakes
|
|
540
|
+
(CSWSH) by checking the handshake `Origin` against an allow-list.
|
|
541
|
+
- `SecurityHeadersMiddleware` attaches `X-Content-Type-Options`,
|
|
542
|
+
`X-Frame-Options`, `Referrer-Policy`, and optional HSTS / CSP /
|
|
543
|
+
`Permissions-Policy` response headers.
|
|
544
|
+
- `CSRFMiddleware` accepts a `secret` that HMAC-signs the token (with an
|
|
545
|
+
optional `max_age` expiry), so a cookie value carrying no valid server
|
|
546
|
+
signature is refused — raising the bar against cookie-injection CSRF.
|
|
547
|
+
The CSRF cookie now defaults to `Secure`.
|
|
548
|
+
- Multipart form parsing caps the part count and per-part size
|
|
549
|
+
(`MAX_FORM_PARTS` / `MAX_FORM_PART_SIZE` config keys), raising `413`
|
|
550
|
+
— a guard against algorithmic-complexity DoS from a maliciously
|
|
551
|
+
structured form.
|
|
552
|
+
- `app.use_secure_defaults()` applies a hardened baseline (secure session
|
|
553
|
+
cookies + `SecurityHeadersMiddleware`); `app.security_audit()` and the
|
|
554
|
+
new `veloce check` CLI command report configuration risks before
|
|
555
|
+
deployment.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Veloce maintainers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|