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.
Files changed (331) hide show
  1. veloceframework-0.1.0/.gitignore +49 -0
  2. veloceframework-0.1.0/CHANGELOG.md +555 -0
  3. veloceframework-0.1.0/LICENSE +21 -0
  4. veloceframework-0.1.0/PKG-INFO +173 -0
  5. veloceframework-0.1.0/README.md +129 -0
  6. veloceframework-0.1.0/pyproject.toml +143 -0
  7. veloceframework-0.1.0/src/veloce/__init__.py +314 -0
  8. veloceframework-0.1.0/src/veloce/_handler_plan.py +405 -0
  9. veloceframework-0.1.0/src/veloce/_types.py +26 -0
  10. veloceframework-0.1.0/src/veloce/app.py +3316 -0
  11. veloceframework-0.1.0/src/veloce/background.py +47 -0
  12. veloceframework-0.1.0/src/veloce/blueprints.py +222 -0
  13. veloceframework-0.1.0/src/veloce/cli.py +209 -0
  14. veloceframework-0.1.0/src/veloce/config.py +318 -0
  15. veloceframework-0.1.0/src/veloce/contrib/__init__.py +1 -0
  16. veloceframework-0.1.0/src/veloce/contrib/openapi.py +705 -0
  17. veloceframework-0.1.0/src/veloce/contrib/staticfiles.py +412 -0
  18. veloceframework-0.1.0/src/veloce/contrib/templating.py +260 -0
  19. veloceframework-0.1.0/src/veloce/dependency.py +813 -0
  20. veloceframework-0.1.0/src/veloce/encoders.py +105 -0
  21. veloceframework-0.1.0/src/veloce/exception_handlers.py +43 -0
  22. veloceframework-0.1.0/src/veloce/exceptions.py +359 -0
  23. veloceframework-0.1.0/src/veloce/helpers.py +640 -0
  24. veloceframework-0.1.0/src/veloce/http/__init__.py +28 -0
  25. veloceframework-0.1.0/src/veloce/http/cache_control.py +130 -0
  26. veloceframework-0.1.0/src/veloce/http/cookies.py +86 -0
  27. veloceframework-0.1.0/src/veloce/http/datastructures.py +795 -0
  28. veloceframework-0.1.0/src/veloce/http/dates.py +85 -0
  29. veloceframework-0.1.0/src/veloce/http/header_set.py +97 -0
  30. veloceframework-0.1.0/src/veloce/http/header_utils.py +117 -0
  31. veloceframework-0.1.0/src/veloce/http/request.py +1058 -0
  32. veloceframework-0.1.0/src/veloce/http/response.py +1285 -0
  33. veloceframework-0.1.0/src/veloce/instrumentation.py +46 -0
  34. veloceframework-0.1.0/src/veloce/json_provider.py +79 -0
  35. veloceframework-0.1.0/src/veloce/markup.py +92 -0
  36. veloceframework-0.1.0/src/veloce/middleware/__init__.py +34 -0
  37. veloceframework-0.1.0/src/veloce/middleware/base.py +82 -0
  38. veloceframework-0.1.0/src/veloce/middleware/compression.py +100 -0
  39. veloceframework-0.1.0/src/veloce/middleware/cors.py +184 -0
  40. veloceframework-0.1.0/src/veloce/middleware/csrf.py +162 -0
  41. veloceframework-0.1.0/src/veloce/middleware/logging.py +86 -0
  42. veloceframework-0.1.0/src/veloce/middleware/proxy_fix.py +149 -0
  43. veloceframework-0.1.0/src/veloce/middleware/security.py +244 -0
  44. veloceframework-0.1.0/src/veloce/middleware/sessions.py +197 -0
  45. veloceframework-0.1.0/src/veloce/passwords.py +218 -0
  46. veloceframework-0.1.0/src/veloce/py.typed +0 -0
  47. veloceframework-0.1.0/src/veloce/routing/__init__.py +17 -0
  48. veloceframework-0.1.0/src/veloce/routing/converters.py +189 -0
  49. veloceframework-0.1.0/src/veloce/routing/params.py +186 -0
  50. veloceframework-0.1.0/src/veloce/routing/router.py +880 -0
  51. veloceframework-0.1.0/src/veloce/safe.py +145 -0
  52. veloceframework-0.1.0/src/veloce/security/__init__.py +33 -0
  53. veloceframework-0.1.0/src/veloce/security/api_key.py +57 -0
  54. veloceframework-0.1.0/src/veloce/security/http.py +230 -0
  55. veloceframework-0.1.0/src/veloce/security/oauth2.py +186 -0
  56. veloceframework-0.1.0/src/veloce/serving/__init__.py +5 -0
  57. veloceframework-0.1.0/src/veloce/serving/protocol.py +227 -0
  58. veloceframework-0.1.0/src/veloce/sessions.py +188 -0
  59. veloceframework-0.1.0/src/veloce/signals.py +181 -0
  60. veloceframework-0.1.0/src/veloce/signing.py +196 -0
  61. veloceframework-0.1.0/src/veloce/sse.py +130 -0
  62. veloceframework-0.1.0/src/veloce/status.py +97 -0
  63. veloceframework-0.1.0/src/veloce/testclient.py +1128 -0
  64. veloceframework-0.1.0/src/veloce/views.py +142 -0
  65. veloceframework-0.1.0/src/veloce/watchdog.py +175 -0
  66. veloceframework-0.1.0/src/veloce/websocket.py +703 -0
  67. veloceframework-0.1.0/tests/__init__.py +0 -0
  68. veloceframework-0.1.0/tests/conftest.py +34 -0
  69. veloceframework-0.1.0/tests/test_aborter.py +60 -0
  70. veloceframework-0.1.0/tests/test_accept_headers.py +151 -0
  71. veloceframework-0.1.0/tests/test_add_api_route.py +67 -0
  72. veloceframework-0.1.0/tests/test_add_api_websocket_route.py +49 -0
  73. veloceframework-0.1.0/tests/test_add_event_handler.py +72 -0
  74. veloceframework-0.1.0/tests/test_add_exception_handler.py +79 -0
  75. veloceframework-0.1.0/tests/test_add_middleware_class.py +42 -0
  76. veloceframework-0.1.0/tests/test_add_url_rule_stub.py +56 -0
  77. veloceframework-0.1.0/tests/test_add_websocket_route.py +40 -0
  78. veloceframework-0.1.0/tests/test_after_this_request.py +98 -0
  79. veloceframework-0.1.0/tests/test_annotated_depends.py +173 -0
  80. veloceframework-0.1.0/tests/test_app.py +434 -0
  81. veloceframework-0.1.0/tests/test_app_cli_click.py +90 -0
  82. veloceframework-0.1.0/tests/test_app_context.py +83 -0
  83. veloceframework-0.1.0/tests/test_app_ctor_exception_handlers.py +69 -0
  84. veloceframework-0.1.0/tests/test_app_ctor_middleware.py +79 -0
  85. veloceframework-0.1.0/tests/test_app_endpoint_view_functions.py +76 -0
  86. veloceframework-0.1.0/tests/test_app_extra.py +35 -0
  87. veloceframework-0.1.0/tests/test_app_global_dependencies.py +73 -0
  88. veloceframework-0.1.0/tests/test_app_jinja_env.py +44 -0
  89. veloceframework-0.1.0/tests/test_app_jinja_loader.py +23 -0
  90. veloceframework-0.1.0/tests/test_app_metadata.py +88 -0
  91. veloceframework-0.1.0/tests/test_app_state_namespace.py +43 -0
  92. veloceframework-0.1.0/tests/test_app_test_client.py +39 -0
  93. veloceframework-0.1.0/tests/test_app_webhooks.py +86 -0
  94. veloceframework-0.1.0/tests/test_appcontext_signals.py +74 -0
  95. veloceframework-0.1.0/tests/test_asgi_middleware_interop.py +239 -0
  96. veloceframework-0.1.0/tests/test_asgi_mount_and_env.py +282 -0
  97. veloceframework-0.1.0/tests/test_asgi_response_quirks.py +100 -0
  98. veloceframework-0.1.0/tests/test_async_io.py +155 -0
  99. veloceframework-0.1.0/tests/test_async_safety.py +278 -0
  100. veloceframework-0.1.0/tests/test_async_test_client.py +190 -0
  101. veloceframework-0.1.0/tests/test_base_http_middleware.py +195 -0
  102. veloceframework-0.1.0/tests/test_base_view.py +124 -0
  103. veloceframework-0.1.0/tests/test_before_first_request.py +98 -0
  104. veloceframework-0.1.0/tests/test_before_serving_signal_ns.py +61 -0
  105. veloceframework-0.1.0/tests/test_blueprint.py +194 -0
  106. veloceframework-0.1.0/tests/test_blueprint_nested.py +106 -0
  107. veloceframework-0.1.0/tests/test_blueprint_url_processors.py +112 -0
  108. veloceframework-0.1.0/tests/test_blueprints_introspect.py +88 -0
  109. veloceframework-0.1.0/tests/test_body_embed.py +54 -0
  110. veloceframework-0.1.0/tests/test_build_error.py +93 -0
  111. veloceframework-0.1.0/tests/test_cache_control.py +129 -0
  112. veloceframework-0.1.0/tests/test_cli.py +153 -0
  113. veloceframework-0.1.0/tests/test_cli_runner_and_dispatch.py +71 -0
  114. veloceframework-0.1.0/tests/test_conditional_request_headers.py +78 -0
  115. veloceframework-0.1.0/tests/test_config_loaders.py +265 -0
  116. veloceframework-0.1.0/tests/test_content_disposition.py +53 -0
  117. veloceframework-0.1.0/tests/test_context_processor.py +159 -0
  118. veloceframework-0.1.0/tests/test_cookie_helpers.py +103 -0
  119. veloceframework-0.1.0/tests/test_cookie_list_marker.py +89 -0
  120. veloceframework-0.1.0/tests/test_cookies_and_validation.py +148 -0
  121. veloceframework-0.1.0/tests/test_cors_spec.py +202 -0
  122. veloceframework-0.1.0/tests/test_csrf_middleware.py +250 -0
  123. veloceframework-0.1.0/tests/test_current_app.py +86 -0
  124. veloceframework-0.1.0/tests/test_custom_converter.py +71 -0
  125. veloceframework-0.1.0/tests/test_default_config.py +53 -0
  126. veloceframework-0.1.0/tests/test_default_options_response.py +87 -0
  127. veloceframework-0.1.0/tests/test_dependency_overrides.py +69 -0
  128. veloceframework-0.1.0/tests/test_dependency_parallel.py +195 -0
  129. veloceframework-0.1.0/tests/test_depends_no_arg.py +69 -0
  130. veloceframework-0.1.0/tests/test_dev_server_tls.py +76 -0
  131. veloceframework-0.1.0/tests/test_dispatch_aliases.py +178 -0
  132. veloceframework-0.1.0/tests/test_e2e_smoke.py +301 -0
  133. veloceframework-0.1.0/tests/test_ensure_sync.py +97 -0
  134. veloceframework-0.1.0/tests/test_error_handler_spec.py +71 -0
  135. veloceframework-0.1.0/tests/test_errorhandler_alias.py +80 -0
  136. veloceframework-0.1.0/tests/test_event_loop_watchdog.py +186 -0
  137. veloceframework-0.1.0/tests/test_exception_handlers_module.py +48 -0
  138. veloceframework-0.1.0/tests/test_file_response_etag.py +96 -0
  139. veloceframework-0.1.0/tests/test_fileresponse_disposition_type.py +38 -0
  140. veloceframework-0.1.0/tests/test_fileresponse_last_modified.py +59 -0
  141. veloceframework-0.1.0/tests/test_final_coverage.py +312 -0
  142. veloceframework-0.1.0/tests/test_form_list_marker.py +71 -0
  143. veloceframework-0.1.0/tests/test_formdata_multidict.py +240 -0
  144. veloceframework-0.1.0/tests/test_fuzz_parsers.py +124 -0
  145. veloceframework-0.1.0/tests/test_gzip_include_types.py +95 -0
  146. veloceframework-0.1.0/tests/test_handle_user_exception.py +102 -0
  147. veloceframework-0.1.0/tests/test_handler_plan.py +315 -0
  148. veloceframework-0.1.0/tests/test_head_method.py +94 -0
  149. veloceframework-0.1.0/tests/test_header_convert_underscores.py +56 -0
  150. veloceframework-0.1.0/tests/test_header_list_marker.py +99 -0
  151. veloceframework-0.1.0/tests/test_header_set.py +104 -0
  152. veloceframework-0.1.0/tests/test_header_utils.py +154 -0
  153. veloceframework-0.1.0/tests/test_headers_extra_methods.py +65 -0
  154. veloceframework-0.1.0/tests/test_host_matching.py +67 -0
  155. veloceframework-0.1.0/tests/test_http_dates.py +84 -0
  156. veloceframework-0.1.0/tests/test_http_digest.py +72 -0
  157. veloceframework-0.1.0/tests/test_http_exceptions.py +234 -0
  158. veloceframework-0.1.0/tests/test_include_router.py +60 -0
  159. veloceframework-0.1.0/tests/test_instance_path.py +33 -0
  160. veloceframework-0.1.0/tests/test_instrumentation.py +221 -0
  161. veloceframework-0.1.0/tests/test_iteration2.py +513 -0
  162. veloceframework-0.1.0/tests/test_iteration3.py +345 -0
  163. veloceframework-0.1.0/tests/test_iteration4.py +306 -0
  164. veloceframework-0.1.0/tests/test_jinja_globals.py +72 -0
  165. veloceframework-0.1.0/tests/test_json_provider.py +92 -0
  166. veloceframework-0.1.0/tests/test_json_response_classes.py +76 -0
  167. veloceframework-0.1.0/tests/test_jsonify_config.py +110 -0
  168. veloceframework-0.1.0/tests/test_lifespan_context.py +75 -0
  169. veloceframework-0.1.0/tests/test_logger_trap.py +84 -0
  170. veloceframework-0.1.0/tests/test_markup_escape.py +92 -0
  171. veloceframework-0.1.0/tests/test_max_content_length.py +121 -0
  172. veloceframework-0.1.0/tests/test_message_flashed_signal.py +62 -0
  173. veloceframework-0.1.0/tests/test_method_view.py +134 -0
  174. veloceframework-0.1.0/tests/test_mount_staticfiles.py +80 -0
  175. veloceframework-0.1.0/tests/test_multidict_semantics.py +199 -0
  176. veloceframework-0.1.0/tests/test_new_features.py +914 -0
  177. veloceframework-0.1.0/tests/test_oauth2_extra_schemes.py +110 -0
  178. veloceframework-0.1.0/tests/test_oauth2_password_form.py +63 -0
  179. veloceframework-0.1.0/tests/test_oauth2_password_form_strict.py +57 -0
  180. veloceframework-0.1.0/tests/test_on_json_loading_failed.py +83 -0
  181. veloceframework-0.1.0/tests/test_openapi_array_query.py +87 -0
  182. veloceframework-0.1.0/tests/test_openapi_customization.py +122 -0
  183. veloceframework-0.1.0/tests/test_openapi_external_docs.py +28 -0
  184. veloceframework-0.1.0/tests/test_openapi_extra.py +85 -0
  185. veloceframework-0.1.0/tests/test_openapi_info_summary.py +39 -0
  186. veloceframework-0.1.0/tests/test_openapi_param_constraints.py +77 -0
  187. veloceframework-0.1.0/tests/test_openapi_param_examples.py +60 -0
  188. veloceframework-0.1.0/tests/test_openapi_param_title.py +70 -0
  189. veloceframework-0.1.0/tests/test_openapi_responses.py +181 -0
  190. veloceframework-0.1.0/tests/test_openapi_security.py +198 -0
  191. veloceframework-0.1.0/tests/test_options_auto.py +79 -0
  192. veloceframework-0.1.0/tests/test_param_constraints.py +103 -0
  193. veloceframework-0.1.0/tests/test_param_include_in_schema.py +59 -0
  194. veloceframework-0.1.0/tests/test_param_multiple_of.py +73 -0
  195. veloceframework-0.1.0/tests/test_passwords.py +137 -0
  196. veloceframework-0.1.0/tests/test_path_converters.py +241 -0
  197. veloceframework-0.1.0/tests/test_perf_cache_invariants.py +92 -0
  198. veloceframework-0.1.0/tests/test_preprocess_blueprint_hooks.py +108 -0
  199. veloceframework-0.1.0/tests/test_propagate_exceptions.py +129 -0
  200. veloceframework-0.1.0/tests/test_proxy_fix.py +183 -0
  201. veloceframework-0.1.0/tests/test_pydantic_param_validation.py +378 -0
  202. veloceframework-0.1.0/tests/test_query_list_marker.py +71 -0
  203. veloceframework-0.1.0/tests/test_range_header.py +117 -0
  204. veloceframework-0.1.0/tests/test_redirect_helper.py +49 -0
  205. veloceframework-0.1.0/tests/test_reliability_fixes.py +377 -0
  206. veloceframework-0.1.0/tests/test_render_async.py +58 -0
  207. veloceframework-0.1.0/tests/test_render_template_helpers.py +81 -0
  208. veloceframework-0.1.0/tests/test_request_aliases.py +67 -0
  209. veloceframework-0.1.0/tests/test_request_args_files.py +103 -0
  210. veloceframework-0.1.0/tests/test_request_auth.py +132 -0
  211. veloceframework-0.1.0/tests/test_request_blueprint_url_rule.py +79 -0
  212. veloceframework-0.1.0/tests/test_request_client.py +63 -0
  213. veloceframework-0.1.0/tests/test_request_cors_headers.py +49 -0
  214. veloceframework-0.1.0/tests/test_request_data_stream.py +51 -0
  215. veloceframework-0.1.0/tests/test_request_endpoint.py +63 -0
  216. veloceframework-0.1.0/tests/test_request_get_data.py +52 -0
  217. veloceframework-0.1.0/tests/test_request_get_json.py +77 -0
  218. veloceframework-0.1.0/tests/test_request_if_match.py +47 -0
  219. veloceframework-0.1.0/tests/test_request_if_range.py +33 -0
  220. veloceframework-0.1.0/tests/test_request_is_disconnected.py +34 -0
  221. veloceframework-0.1.0/tests/test_request_is_multipart_form.py +63 -0
  222. veloceframework-0.1.0/tests/test_request_is_xhr_values.py +76 -0
  223. veloceframework-0.1.0/tests/test_request_max_content_length.py +32 -0
  224. veloceframework-0.1.0/tests/test_request_mimetype.py +78 -0
  225. veloceframework-0.1.0/tests/test_request_misc_headers.py +54 -0
  226. veloceframework-0.1.0/tests/test_request_proxy.py +46 -0
  227. veloceframework-0.1.0/tests/test_request_root_path.py +45 -0
  228. veloceframework-0.1.0/tests/test_request_scheme.py +56 -0
  229. veloceframework-0.1.0/tests/test_request_session.py +83 -0
  230. veloceframework-0.1.0/tests/test_request_state.py +90 -0
  231. veloceframework-0.1.0/tests/test_request_streaming.py +211 -0
  232. veloceframework-0.1.0/tests/test_request_url_accessors.py +72 -0
  233. veloceframework-0.1.0/tests/test_request_url_for.py +59 -0
  234. veloceframework-0.1.0/tests/test_request_view_args.py +33 -0
  235. veloceframework-0.1.0/tests/test_response_background.py +149 -0
  236. veloceframework-0.1.0/tests/test_response_cache_control_vary.py +92 -0
  237. veloceframework-0.1.0/tests/test_response_charset_setter.py +37 -0
  238. veloceframework-0.1.0/tests/test_response_conditional.py +110 -0
  239. veloceframework-0.1.0/tests/test_response_content_enc_lang.py +58 -0
  240. veloceframework-0.1.0/tests/test_response_cookies_headerlist.py +68 -0
  241. veloceframework-0.1.0/tests/test_response_date_location.py +99 -0
  242. veloceframework-0.1.0/tests/test_response_dates.py +64 -0
  243. veloceframework-0.1.0/tests/test_response_etag_freeze.py +96 -0
  244. veloceframework-0.1.0/tests/test_response_header_safety.py +76 -0
  245. veloceframework-0.1.0/tests/test_response_iter_chunked.py +48 -0
  246. veloceframework-0.1.0/tests/test_response_json.py +52 -0
  247. veloceframework-0.1.0/tests/test_response_media_type.py +43 -0
  248. veloceframework-0.1.0/tests/test_response_mimetype.py +40 -0
  249. veloceframework-0.1.0/tests/test_response_mimetype_params.py +41 -0
  250. veloceframework-0.1.0/tests/test_response_model.py +201 -0
  251. veloceframework-0.1.0/tests/test_response_model_exclude_defaults.py +85 -0
  252. veloceframework-0.1.0/tests/test_response_param_injection.py +132 -0
  253. veloceframework-0.1.0/tests/test_response_range_headers.py +64 -0
  254. veloceframework-0.1.0/tests/test_response_retry_after_age.py +72 -0
  255. veloceframework-0.1.0/tests/test_response_set_data.py +52 -0
  256. veloceframework-0.1.0/tests/test_response_shape_aliases.py +71 -0
  257. veloceframework-0.1.0/tests/test_response_status_line.py +46 -0
  258. veloceframework-0.1.0/tests/test_response_vary_allow.py +82 -0
  259. veloceframework-0.1.0/tests/test_response_www_authenticate.py +49 -0
  260. veloceframework-0.1.0/tests/test_route_callbacks.py +70 -0
  261. veloceframework-0.1.0/tests/test_route_defaults.py +60 -0
  262. veloceframework-0.1.0/tests/test_route_metadata.py +164 -0
  263. veloceframework-0.1.0/tests/test_router.py +126 -0
  264. veloceframework-0.1.0/tests/test_router_default_response_class.py +62 -0
  265. veloceframework-0.1.0/tests/test_router_level_dependencies.py +91 -0
  266. veloceframework-0.1.0/tests/test_router_level_responses.py +115 -0
  267. veloceframework-0.1.0/tests/test_safe.py +153 -0
  268. veloceframework-0.1.0/tests/test_security_headers.py +68 -0
  269. veloceframework-0.1.0/tests/test_security_scopes.py +162 -0
  270. veloceframework-0.1.0/tests/test_send_file_helper.py +61 -0
  271. veloceframework-0.1.0/tests/test_send_static_file.py +56 -0
  272. veloceframework-0.1.0/tests/test_server_protocol.py +80 -0
  273. veloceframework-0.1.0/tests/test_server_side_sessions.py +339 -0
  274. veloceframework-0.1.0/tests/test_session_object.py +119 -0
  275. veloceframework-0.1.0/tests/test_session_permanent.py +90 -0
  276. veloceframework-0.1.0/tests/test_session_proxy.py +71 -0
  277. veloceframework-0.1.0/tests/test_session_rotation.py +110 -0
  278. veloceframework-0.1.0/tests/test_session_transaction.py +71 -0
  279. veloceframework-0.1.0/tests/test_set_cookie_expires.py +96 -0
  280. veloceframework-0.1.0/tests/test_set_cookie_max_age_timedelta.py +42 -0
  281. veloceframework-0.1.0/tests/test_set_cookie_partitioned.py +45 -0
  282. veloceframework-0.1.0/tests/test_shell_context.py +102 -0
  283. veloceframework-0.1.0/tests/test_signals.py +162 -0
  284. veloceframework-0.1.0/tests/test_signing.py +196 -0
  285. veloceframework-0.1.0/tests/test_static_conditional.py +111 -0
  286. veloceframework-0.1.0/tests/test_staticfiles_dirindex.py +105 -0
  287. veloceframework-0.1.0/tests/test_staticfiles_range.py +103 -0
  288. veloceframework-0.1.0/tests/test_staticfiles_streaming.py +67 -0
  289. veloceframework-0.1.0/tests/test_staticfiles_symlink.py +41 -0
  290. veloceframework-0.1.0/tests/test_status_websocket_codes.py +61 -0
  291. veloceframework-0.1.0/tests/test_stream_with_context.py +77 -0
  292. veloceframework-0.1.0/tests/test_streaming_response_asgi.py +103 -0
  293. veloceframework-0.1.0/tests/test_strict_slashes.py +87 -0
  294. veloceframework-0.1.0/tests/test_subdomain_routing.py +108 -0
  295. veloceframework-0.1.0/tests/test_swagger_ui_passthrough.py +91 -0
  296. veloceframework-0.1.0/tests/test_teardown_appcontext.py +126 -0
  297. veloceframework-0.1.0/tests/test_template_add_helpers.py +77 -0
  298. veloceframework-0.1.0/tests/test_template_autoescape.py +127 -0
  299. veloceframework-0.1.0/tests/test_template_folder.py +59 -0
  300. veloceframework-0.1.0/tests/test_template_helpers.py +157 -0
  301. veloceframework-0.1.0/tests/test_testclient_asgi.py +116 -0
  302. veloceframework-0.1.0/tests/test_testclient_cookies.py +81 -0
  303. veloceframework-0.1.0/tests/test_testclient_files.py +94 -0
  304. veloceframework-0.1.0/tests/test_testclient_redirects.py +122 -0
  305. veloceframework-0.1.0/tests/test_testclient_request.py +69 -0
  306. veloceframework-0.1.0/tests/test_trace_route.py +45 -0
  307. veloceframework-0.1.0/tests/test_trusted_host_and_https.py +183 -0
  308. veloceframework-0.1.0/tests/test_update_template_context.py +60 -0
  309. veloceframework-0.1.0/tests/test_uploadfile_save.py +123 -0
  310. veloceframework-0.1.0/tests/test_url_for_extended.py +102 -0
  311. veloceframework-0.1.0/tests/test_url_map.py +68 -0
  312. veloceframework-0.1.0/tests/test_url_path_for_alias.py +84 -0
  313. veloceframework-0.1.0/tests/test_url_processor_inspect.py +87 -0
  314. veloceframework-0.1.0/tests/test_url_processors.py +189 -0
  315. veloceframework-0.1.0/tests/test_validation_error_body.py +79 -0
  316. veloceframework-0.1.0/tests/test_vel2_correctness_bugs.py +218 -0
  317. veloceframework-0.1.0/tests/test_websocket_asgi.py +119 -0
  318. veloceframework-0.1.0/tests/test_websocket_client_state.py +49 -0
  319. veloceframework-0.1.0/tests/test_websocket_cookies.py +29 -0
  320. veloceframework-0.1.0/tests/test_websocket_depends.py +82 -0
  321. veloceframework-0.1.0/tests/test_websocket_di_parity.py +234 -0
  322. veloceframework-0.1.0/tests/test_websocket_exception.py +102 -0
  323. veloceframework-0.1.0/tests/test_websocket_iter.py +73 -0
  324. veloceframework-0.1.0/tests/test_websocket_origin.py +123 -0
  325. veloceframework-0.1.0/tests/test_websocket_query_params.py +52 -0
  326. veloceframework-0.1.0/tests/test_websocket_raw_messages.py +53 -0
  327. veloceframework-0.1.0/tests/test_websocket_request_validation.py +103 -0
  328. veloceframework-0.1.0/tests/test_websocket_spec.py +383 -0
  329. veloceframework-0.1.0/tests/test_websocket_state.py +62 -0
  330. veloceframework-0.1.0/tests/test_websocket_subprotocol.py +88 -0
  331. 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.