From d432ff755730ecbb2ecd540611ac36110755623d Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Tue, 18 Nov 2025 21:44:00 +0100 Subject: [PATCH 01/14] Start updating docs for 2.4.5 --- blacksheep/docs/authentication.md | 6 +-- blacksheep/docs/authorization.md | 12 ++--- blacksheep/docs/binders.md | 14 +++--- blacksheep/docs/compression.md | 4 +- blacksheep/docs/controllers.md | 2 +- blacksheep/docs/dependency-injection.md | 4 +- blacksheep/docs/openapi.md | 67 ++++++++++++++++++++++++- blacksheep/docs/openid-connect.md | 18 +++---- blacksheep/docs/remotes.md | 55 +++++++++++++++----- blacksheep/docs/requests.md | 2 +- blacksheep/docs/routing.md | 6 +-- blacksheep/docs/sessions.md | 6 +-- 12 files changed, 145 insertions(+), 51 deletions(-) diff --git a/blacksheep/docs/authentication.md b/blacksheep/docs/authentication.md index fb32ec1..33ecad1 100644 --- a/blacksheep/docs/authentication.md +++ b/blacksheep/docs/authentication.md @@ -767,7 +767,7 @@ app.use_authentication().add( valid_audiences=["my-service"], valid_issuers=["my-issuer"], algorithms=["HS256"], # âŸĩ symmetric algorithms: HS256, HS384, HS512 - auth_mode="JWT Symmetric" + scheme="JWT Symmetric" ) ) @@ -840,7 +840,7 @@ app.use_authentication().add( valid_audiences=["internal-api"], valid_issuers=["internal-issuer"], algorithms=["HS256"], - auth_mode="JWT Internal" + scheme="JWT Internal" ) ) @@ -851,7 +851,7 @@ app.use_authentication().add( valid_audiences=["external-client-id"], valid_issuers=["https://login.microsoftonline.com/tenant-id/v2.0"], algorithms=["RS256"], - auth_mode="JWT External" + scheme="JWT External" ) ) ``` diff --git a/blacksheep/docs/authorization.md b/blacksheep/docs/authorization.md index 8a3e8e3..f940738 100644 --- a/blacksheep/docs/authorization.md +++ b/blacksheep/docs/authorization.md @@ -42,7 +42,7 @@ class ExampleAuthHandler(AuthenticationHandler): def __init__(self): pass - async def authenticate(self, context: Request) -> Optional[Identity]: + async def authenticate(self, context: Request) -> Identity | None: header_value = context.get_first_header(b"Authorization") if header_value: # TODO: parse and validate the value of the authorization @@ -62,7 +62,7 @@ app.use_authorization().add(Policy(Authenticated, AuthenticatedRequirement())) @get("/") -async def for_anybody(user: Optional[User]): +async def for_anybody(user: User | None): if user is None: return json({"anonymous": True}) @@ -138,7 +138,7 @@ class ExampleAuthHandler(AuthenticationHandler): def __init__(self): pass - async def authenticate(self, context: Request) -> Optional[Identity]: + async def authenticate(self, context: Request) -> Identity | None: header_value = context.get_first_header(b"Authorization") if header_value: # TODO: parse and validate the value of the authorization @@ -173,7 +173,7 @@ app.use_authorization().add(Policy(Authenticated, AuthenticatedRequirement())).a @get("/") -async def for_anybody(user: Optional[User]): +async def for_anybody(user: User | None): # This method can be used by anybody if user is None: return json({"anonymous": True}) @@ -220,7 +220,7 @@ from blacksheep.server.authorization import allow_anonymous @allow_anonymous() @get("/") -async def for_anybody(user: Optional[User]): +async def for_anybody(user: User | None): if user is None: return json({"anonymous": True}) @@ -248,7 +248,7 @@ class GitHubAuthHandler(AuthenticationHandler): def scheme(self) -> str: return "github" - async def authenticate(self, context: Request) -> Optional[Identity]: + async def authenticate(self, context: Request) -> Identity | None: ... diff --git a/blacksheep/docs/binders.md b/blacksheep/docs/binders.md index bed24ed..25d1192 100644 --- a/blacksheep/docs/binders.md +++ b/blacksheep/docs/binders.md @@ -142,8 +142,8 @@ from typing import Optional @get("/foo") async def example( - page: Optional[int], - search: Optional[str], + page: int | None, + search: str | None, ): # page is read from the query string, if specified, otherwise defaults to None # search is read from the query string, if specified, otherwise defaults to None @@ -172,8 +172,8 @@ from blacksheep import FromQuery, get @get("/foo") async def example( - page: FromQuery[Optional[int]], - search: FromQuery[Optional[str]], + page: FromQuery[int | None], + search: FromQuery[str | None], ): # page.value defaults to None # search.value defaults to None @@ -226,7 +226,7 @@ class FromAcceptHeader(FromHeader[str]): name = "Accept" -class FromFooCookie(FromCookie[Optional[str]]): +class FromFooCookie(FromCookie[str | None]): name = "foo" @@ -264,7 +264,7 @@ class CustomBinder(Binder): handle = FromCustomValue - async def get_value(self, request: Request) -> Optional[str]: + async def get_value(self, request: Request) -> str | None: # TODO: implement here the desired logic to read a value from # the request object return "example" @@ -346,7 +346,7 @@ class UserProfile: name: str email: str created_at: datetime - age: Optional[int] = None + age: int | None = None class UserProfileBinder(BoundValue[UserProfile]): """ diff --git a/blacksheep/docs/compression.md b/blacksheep/docs/compression.md index 97b93d1..e18fea7 100644 --- a/blacksheep/docs/compression.md +++ b/blacksheep/docs/compression.md @@ -49,8 +49,8 @@ The following table describes options for the `GzipMiddleware` constructor. | ------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | min_size | `int` (default 500) | The minimum size before applying compression to response bodies. | | comp_level | `int` (default 5) | The compression level, as passed to `gzip.compress` function. | -| handled_types | `Optional[Iterable[bytes]]` | Control which content types can be compressed by the specific instance of `GzipMiddleware`. | -| executor | `Optional[Executor]` (default `None`) | Control which instance of `concurrent.future.Executor` is used to compress - if not specified the default executor handled by `run_in_executor` is used. | +| handled_types | `Iterable[bytes] | None` | Control which content types can be compressed by the specific instance of `GzipMiddleware`. | +| executor | `Executor | None` (default `None`) | Control which instance of `concurrent.future.Executor` is used to compress - if not specified the default executor handled by `run_in_executor` is used. | When `handled_types` is not specified for an instance of `GzipMiddleware`, compression is applied by default to content types containing any of the diff --git a/blacksheep/docs/controllers.md b/blacksheep/docs/controllers.md index 85a87da..36e8d0f 100644 --- a/blacksheep/docs/controllers.md +++ b/blacksheep/docs/controllers.md @@ -288,7 +288,7 @@ class BaseController(Controller): path: ClassVar[str] = "base" @classmethod - def route(cls) -> Optional[str]: + def route(cls) -> str | None: return f"/api/{cls.path}" @get("/foo") # /api/base/foo diff --git a/blacksheep/docs/dependency-injection.md b/blacksheep/docs/dependency-injection.md index dedcfb3..1223161 100644 --- a/blacksheep/docs/dependency-injection.md +++ b/blacksheep/docs/dependency-injection.md @@ -311,7 +311,7 @@ class Cat: class CatsRepository(ABC): @abstractmethod - async def get_cat_by_id(self, id: str) -> Optional[Cat]: + async def get_cat_by_id(self, id: str) -> Cat | None: pass # ------------------ @@ -319,7 +319,7 @@ class CatsRepository(ABC): # the concrete implementation will be defined in a dedicated package class PostgreSQLCatsRepository(CatsRepository): - async def get_cat_by_id(self, id: str) -> Optional[Cat]: + async def get_cat_by_id(self, id: str) -> Cat | None: # TODO: implement raise Exception("Not implemented") diff --git a/blacksheep/docs/openapi.md b/blacksheep/docs/openapi.md index 0680e23..a78a593 100644 --- a/blacksheep/docs/openapi.md +++ b/blacksheep/docs/openapi.md @@ -270,6 +270,71 @@ async def hidden_endpoint(): return "This endpoint won't appear in documentation" ``` +### Response annotation + +Since version `2.4.4`, `Annotated` is supported to have proper automatic documentation +of response objects. Example: + +```python {linenums="1" hl_lines="21"} +from typing import Annotated + +from openapidocs.v3 import Info +from pydantic import BaseModel + +from blacksheep import Application, get, json +from blacksheep.messages import Response +from blacksheep.server.openapi.v3 import OpenAPIHandler + +app = Application() + +docs = OpenAPIHandler(info=Info(title="Example API", version="0.0.1")) +docs.bind_app(app) + + +class Example(BaseModel): + foo: str + + +@get("/foo") +async def get_foo() -> Annotated[Response, Example]: + response = json(Example(foo="Hello!")) + response.add_header(b"X-Foo", b"Foo") + return response +``` + +Produces the following documentation: + +```yaml {linenums="1" hl_lines="11-14 17-19"} +openapi: 3.1.0 +info: + title: Example API + version: 0.0.1 +paths: + /foo: + get: + responses: + '200': + description: Success response + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + operationId: get_foo +servers: [] +components: + schemas: + Example: + properties: + foo: + title: Foo + type: string + required: + - foo + title: Example + type: object +tags: [] +``` + ### Documenting only certain routes To document only certain routes, use an include function like in the example @@ -986,7 +1051,7 @@ the desired result: ```python class CustomOpenAPIHandler(OpenAPIHandler): - def get_operation_id(self, docs: Optional[EndpointDocs], handler) -> str: + def get_operation_id(self, docs: EndpointDocs | None, handler) -> str: return handler.__name__.capitalize().replace("_", " ") ``` diff --git a/blacksheep/docs/openid-connect.md b/blacksheep/docs/openid-connect.md index 35a7531..4125c9e 100644 --- a/blacksheep/docs/openid-connect.md +++ b/blacksheep/docs/openid-connect.md @@ -82,8 +82,8 @@ async def home(user: Identity): | ------------------ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app | Application | Instance of BlackSheep application. | | settings | OpenIDSettings | Instance of OpenIDSettings. | -| auth_handler | Optional[OpenIDTokensHandler] = None (CookiesOpenIDTokensHandler) | Instance of OpenIDTokensHandler that can handle tokens for requests and responses for the OpenID Connect flow. This class is responsible for communicating tokens to clients, and restoring tokens context for following requests. | -| parameters_builder | Optional[ParametersBuilder] = None | Optional instance of `ParametersBuilder`, used to handle parameters configured in redirects and requests to the authorization server. | +| auth_handler | OpenIDTokensHandler | None = None (CookiesOpenIDTokensHandler) | Instance of OpenIDTokensHandler that can handle tokens for requests and responses for the OpenID Connect flow. This class is responsible for communicating tokens to clients, and restoring tokens context for following requests. | +| parameters_builder | ParametersBuilder | None = None | Optional instance of `ParametersBuilder`, used to handle parameters configured in redirects and requests to the authorization server. | | is_default | bool = True | If default, clients are automatically redirected to the `sign-in` page when a non-authenticated user tries to access in `GET` a web page that requires authentication. | ### OpenIDSettings @@ -93,10 +93,10 @@ The `OpenIDSettings` class has the following properties: | Parameter | Type, default | Description | | ------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | client_id | str | ID of the application in the identity server. | -| authority | Optional[str] = None | If specified, URL of the authorization server. | -| audience | Optional[str] = None | If specified, the `audience` for requests using scopes to an API ([ref.](https://auth0.com/docs/configure/apis/scopes/sample-use-cases-scopes-and-claims#request-custom-api-access)). | -| client_secret | Optional[str] = None | For requests that use `Authorization Code Grant` flow, the secret of the client application in the identity server. | -| discovery_endpoint | Optional[str] = None | If specified, the exact URL to the discovery point (useful with Okta when using custom scopes for an authorization server). | +| authority | str | None = None | If specified, URL of the authorization server. | +| audience | str | None = None | If specified, the `audience` for requests using scopes to an API ([ref.](https://auth0.com/docs/configure/apis/scopes/sample-use-cases-scopes-and-claims#request-custom-api-access)). | +| client_secret | str | None = None | For requests that use `Authorization Code Grant` flow, the secret of the client application in the identity server. | +| discovery_endpoint | str | None = None | If specified, the exact URL to the discovery point (useful with Okta when using custom scopes for an authorization server). | | entry_path | str = "/sign-in" | The local entry path for sign-in (this redirects to the sign-in page of the identity server). | | logout_path | str = "/sign-out" | The local path to the sign-out endpoint (this removes authentication cookie). | | post_logout_redirect_path | str = "/" | The local path to which a user is redirected after signing-out. | @@ -104,10 +104,10 @@ The `OpenIDSettings` class has the following properties: | refresh_token_path | str = "/refresh-token" | The local path used to handle refresh tokens to obtain new tokens. | | scope | str = "openid profile email" | The scope of the request, by default an `id_token` is obtained with email and profile. | | response_type | str = "code" | Type of OAuth response. | -| redirect_uri | Optional[str] = None | If specified, the redirect URL that must match the one configured for the application. If not provided, a redirect_url is obtained automatically (see note đŸ—Ąī¸). | +| redirect_uri | str | None = None | If specified, the redirect URL that must match the one configured for the application. If not provided, a redirect_url is obtained automatically (see note đŸ—Ąī¸). | | scheme_name | str = "OpenIDConnect" | The name of the authentication scheme, affecting the name of authentication cookies (see note 🍒). | -| error_redirect_path | Optional[str] = None | If specified, the local path to which a user is redirected in case of error. | -| end_session_endpoint | Optional[str] = None | If specified, the local path to which the user can log out. | +| error_redirect_path | str | None = None | If specified, the local path to which a user is redirected in case of error. | +| end_session_endpoint | str | None = None | If specified, the local path to which the user can log out. | Notes: diff --git a/blacksheep/docs/remotes.md b/blacksheep/docs/remotes.md index cd08931..c5108f9 100644 --- a/blacksheep/docs/remotes.md +++ b/blacksheep/docs/remotes.md @@ -1,14 +1,13 @@ The `blacksheep.server.remotes` namespace provides classes and functions to -handle information related to remote proxies and clients. - -Web applications in production environments are often hosted behind servers -such as Apache, IIS, or NGINX. Proxy servers typically obscure some information -from the original web request before it reaches the web application. +handle information related to remote proxies and clients. Web applications in production +environments are often hosted behind servers such as Apache, IIS, NGINX, or Kubernetes +ingress controllers. Proxy servers typically obscure some information from the original +web request before it reaches the web application. For example: -- When HTTPS requests are proxied over HTTP, the original scheme (HTTPS) is - lost and must be forwarded in a header. +- When TLS termination is used, meaning that HTTPS requests are proxied over HTTP, the + original scheme (HTTPS) is lost. - When an application receives a request from a proxy instead of its true source, the original client IP address must also be forwarded in a header. - The path of web requests can be altered during proxying (e.g., NGINX @@ -20,6 +19,7 @@ redirects, authentication, link generation (when absolute URLs are required), and client geolocation. This page documents how to configure BlackSheep to work with proxy servers and load balancers, using the provided classes to handle: +- [X] Forcing HTTP scheme. - [X] X-Forwarded headers. - [X] Forwarded header. - [X] Trusted hosts. @@ -29,6 +29,35 @@ For information on how to handle the prefix of routes when exposing a web application behind a proxy, refer to the dedicated page [_Behind Proxies_](./behind-proxies.md). +## Forcing HTTP scheme + +In many cases, when web applications are exposed behind proxies that do TLS termination +(the proxy or ingress accepts HTTPS requests and proxies to the back-end listening on +HTTP), we simply need to instruct the web application to generate URLs to itself using +`https` scheme instead of `http`. + +/// tab | Since version 2.4.4 + +Since version `2.4.4`, the framework includes built-in features to force the HTTP scheme +of generated URLs. + +TODO: document env variables. + +/// + +/// tab | Before version 2.4.4 + +Before `2.4.4`, the framework did not include any specific code to force the HTTP scheme +and it required applying a middleware. + +TODO: like in FinOps API. + +```python + +``` + +/// + ## Handling X-Forwarded headers `X-Forwarded` headers are the _de-facto_ standard headers to propagate @@ -71,9 +100,9 @@ Options of the `XForwardedHeadersMiddleware` class: | Parameter | Type, default | Description | | -------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| allowed_hosts | Optional[Sequence[str]] = None | Sequence of allowed hosts. If configured, requests that send a different host in the `Host` header or `X-Forwarded-Host` header are replied with Bad Request. | -| known_proxies | Optional[Sequence[IPAddress]] = None | Sequence of allowed proxies IP addresses. If configured, requests that send different proxies IPs in the request scope or `X-Forwarded-For` header are replied with Bad Request. | -| known_networks | Optional[Sequence[IPNetwork]] = None | Sequence of allowed proxies networks. If configured, requests that send a foreign proxy IP in the request scope or `X-Forwarded-For` header are replied with Bad Request. | +| allowed_hosts | `Sequence[str] | None` = None | Sequence of allowed hosts. If configured, requests that send a different host in the `Host` header or `X-Forwarded-Host` header are replied with Bad Request. | +| known_proxies | Sequence[IPAddress] | None = None | Sequence of allowed proxies IP addresses. If configured, requests that send different proxies IPs in the request scope or `X-Forwarded-For` header are replied with Bad Request. | +| known_networks | Sequence[IPNetwork] | None = None | Sequence of allowed proxies networks. If configured, requests that send a foreign proxy IP in the request scope or `X-Forwarded-For` header are replied with Bad Request. | | forward_limit | int = 1 | Maximum number of allowed forwards, by default 1. | When `known_proxies` is not provided, it is set by default to handle `localhost`: @@ -107,9 +136,9 @@ Options of the `ForwardedHeadersMiddleware` class: | Parameter | Type, default | Description | | -------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| allowed_hosts | Optional[Sequence[str]] = None | Sequence of allowed hosts. If configured, requests that send a different host in the `Host` header or `Forwarded` header are replied with Bad Request. | -| known_proxies | Optional[Sequence[IPAddress]] = None | Sequence of allowed proxies IP addresses. If configured, requests that send different proxies IPs in the request scope or `Forwarded` header are replied with Bad Request. | -| known_networks | Optional[Sequence[IPNetwork]] = None | Sequence of allowed proxies networks. If configured, requests that send a foreign proxy IP in the request scope or `Forwarded` header are replied with Bad Request. | +| allowed_hosts | Sequence[str] | None = None | Sequence of allowed hosts. If configured, requests that send a different host in the `Host` header or `Forwarded` header are replied with Bad Request. | +| known_proxies | Sequence[IPAddress] | None = None | Sequence of allowed proxies IP addresses. If configured, requests that send different proxies IPs in the request scope or `Forwarded` header are replied with Bad Request. | +| known_networks | Sequence[IPNetwork] | None = None | Sequence of allowed proxies networks. If configured, requests that send a foreign proxy IP in the request scope or `Forwarded` header are replied with Bad Request. | | forward_limit | int = 1 | Maximum number of allowed forwards, by default 1. | When `known_proxies` is not provided, it is set by default to handle `localhost`: diff --git a/blacksheep/docs/requests.md b/blacksheep/docs/requests.md index ef55df3..9a94e02 100644 --- a/blacksheep/docs/requests.md +++ b/blacksheep/docs/requests.md @@ -100,7 +100,7 @@ class FromAcceptHeader(FromHeader[str]): name = "Accept" -class FromFooCookie(FromCookie[Optional[str]]): +class FromFooCookie(FromCookie[str | None]): name = "foo" diff --git a/blacksheep/docs/routing.md b/blacksheep/docs/routing.md index c986d84..1c19517 100644 --- a/blacksheep/docs/routing.md +++ b/blacksheep/docs/routing.md @@ -553,10 +553,10 @@ One option to keep track of the route that matches a request is to wrap the ```python def wrap_get_route_match( - fn: Callable[[Request], Optional[RouteMatch]] - ) -> Callable[[Request], Optional[RouteMatch]]: + fn: Callable[[Request], RouteMatch | None] + ) -> Callable[[Request], RouteMatch | None]: @wraps(fn) - def get_route_match(request: Request) -> Optional[RouteMatch]: + def get_route_match(request: Request) -> RouteMatch | None: match = fn(request) request.route = match.pattern.decode() if match else "Not Found" # type: ignore return match diff --git a/blacksheep/docs/sessions.md b/blacksheep/docs/sessions.md index 972f41e..53d98ad 100644 --- a/blacksheep/docs/sessions.md +++ b/blacksheep/docs/sessions.md @@ -120,9 +120,9 @@ For this scenario, the `use_sessions` method accepts the following parameters: store: str, *, session_cookie: str = "session", - serializer: Optional[SessionSerializer] = None, - signer: Optional[Signer] = None, - session_max_age: Optional[int] = None, + serializer: SessionSerializer | None = None, + signer: Signer | None = None, + session_max_age: int | None = None, ) -> None: ... ``` From b8078593bfa4c3b292ea4bfcab437af13f552a7d Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 21:13:33 +0100 Subject: [PATCH 02/14] Correct recommended Python version --- blacksheep/docs/contributing.md | 3 +-- blacksheep/docs/getting-started.md | 3 +-- blacksheep/docs/mvc-project-template.md | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/blacksheep/docs/contributing.md b/blacksheep/docs/contributing.md index 82d8ea9..1b5370f 100644 --- a/blacksheep/docs/contributing.md +++ b/blacksheep/docs/contributing.md @@ -13,8 +13,7 @@ among these projects, only BlackSheep is using `Cython`. Building `blacksheep` locally requires the following: * one of the supported [Python](https://www.python.org/downloads/) versions; it - is recommended to use one of the latest two stable versions (e.g. Python 3.8 - or 3.9 as of the 1st of May 2021) + is recommended to use one of the latest two stable versions * a `C` compiler, required to use [Cython](https://cython.readthedocs.io/en/latest/src/quickstart/install.html) (refer to Cython's documentation for more information on this subject) diff --git a/blacksheep/docs/getting-started.md b/blacksheep/docs/getting-started.md index f2b1150..7b947d4 100644 --- a/blacksheep/docs/getting-started.md +++ b/blacksheep/docs/getting-started.md @@ -11,8 +11,7 @@ application. It provides an overview of the following topics: ### Requirements -* [Python](https://www.python.org) version >= **3.10** (3.8 and 3.9 are - supported but not recommended for this tutorial) +* [Python](https://www.python.org) version >= **3.10** * Ensure the Python executable is included in the `$PATH` environment variable. (tip: if you install Python on Windows using the official installer, enable the checkbox to update your `$PATH` variable during the installation) diff --git a/blacksheep/docs/mvc-project-template.md b/blacksheep/docs/mvc-project-template.md index 7c44b1c..2e8662d 100644 --- a/blacksheep/docs/mvc-project-template.md +++ b/blacksheep/docs/mvc-project-template.md @@ -15,8 +15,7 @@ reading this one. ### Requirements -* [Python](https://www.python.org) version >= **3.10** (3.8 and 3.9 are - supported but not recommended for this tutorial) +* [Python](https://www.python.org) version >= **3.10** * Ensure the Python executable is included in the `$PATH` environment variable. (tip: if you install Python on Windows using the official installer, enable the checkbox to update your `$PATH` variable during the installation) From a6673d043610d8a80d907cca1b0e3cbc8378c3f3 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 21:20:23 +0100 Subject: [PATCH 03/14] Document improvements for middlewares --- blacksheep/docs/middlewares.md | 155 +++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/blacksheep/docs/middlewares.md b/blacksheep/docs/middlewares.md index 04dfad8..1e8c7c4 100644 --- a/blacksheep/docs/middlewares.md +++ b/blacksheep/docs/middlewares.md @@ -7,6 +7,8 @@ This page covers: - [X] Introduction to middlewares. - [X] How to use function decorators to avoid code repetition. +- [X] Middleware management with MiddlewareList and MiddlewareCategory. +- [X] Organizing middlewares by categories and priorities. ## Introduction to middlewares @@ -104,6 +106,159 @@ When middlewares are defined for an application, resolution chains are built at its start. Every handler configured in the application router is replaced by a chain, executing middlewares in order, down to the registered handler. +## Middleware management with MiddlewareList and MiddlewareCategory + +/// admonition | New in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, middleware management has been enhanced with `MiddlewareList` and `MiddlewareCategory` to simplify middleware ordering and organization. + +/// + +The `MiddlewareList` is a specialized container that provides better control over middleware ordering through categories and priorities. This addresses common issues where middleware order matters significantly, such as ensuring authentication happens before authorization, or that CORS headers are set early in the pipeline. + +### Middleware categories + +The `MiddlewareCategory` enum defines predefined categories that represent the typical order middlewares should be executed: + +```python +from blacksheep.middlewares import MiddlewareCategory + +# Available categories (in execution order): +MiddlewareCategory.INIT # 10 - CORS, security headers, early configuration +MiddlewareCategory.SESSION # 20 - Session handling +MiddlewareCategory.AUTH # 30 - Authentication +MiddlewareCategory.AUTHZ # 40 - Authorization +MiddlewareCategory.BUSINESS # 50 - User business logic middlewares (default) +MiddlewareCategory.MESSAGE # 60 - Request/Response modification +``` + +### Adding categorized middlewares + +You can now specify a category and priority when adding middlewares: + +```python +from blacksheep import Application +from blacksheep.middlewares import MiddlewareCategory + +app = Application() + +# Add middleware with category and priority +app.middlewares.append( + cors_middleware, + category=MiddlewareCategory.INIT, + priority=0 # Lower priority = executed first within category +) + +app.middlewares.append( + auth_middleware, + category=MiddlewareCategory.AUTH, + priority=0 +) + +app.middlewares.append( + custom_business_logic, + category=MiddlewareCategory.BUSINESS, + priority=10 +) + +# If no category is specified, defaults to BUSINESS +app.middlewares.append(logging_middleware) +``` + +### Priority within categories + +Within each category, middlewares are ordered by their priority value (lower values execute first): + +```python +# These will execute in order: middleware_a, middleware_b, middleware_c +app.middlewares.append(middleware_a, MiddlewareCategory.AUTH, priority=0) +app.middlewares.append(middleware_b, MiddlewareCategory.AUTH, priority=5) +app.middlewares.append(middleware_c, MiddlewareCategory.AUTH, priority=10) +``` + +### Backward compatibility + +The traditional `append()` and `insert()` methods continue to work: + +```python +# Traditional approach (still supported) +app.middlewares.append(my_middleware) + +# Insert at specific position (defaults to INIT category for backward compatibility) +app.middlewares.insert(0, early_middleware) +``` + +### Example: Complete middleware setup + +Here's a comprehensive example showing how to organize middlewares by category: + +```python +from blacksheep import Application +from blacksheep.middlewares import MiddlewareCategory +from blacksheep.server.cors import CORSMiddleware +from blacksheep.server.authentication import AuthenticationMiddleware +from blacksheep.server.authorization import AuthorizationMiddleware + +app = Application() + +# CORS and security headers (execute first) +app.middlewares.append( + CORSMiddleware(), + category=MiddlewareCategory.INIT, + priority=0 +) + +# Session handling +app.middlewares.append( + session_middleware, + category=MiddlewareCategory.SESSION, + priority=0 +) + +# Authentication (after sessions) +app.middlewares.append( + AuthenticationMiddleware(), + category=MiddlewareCategory.AUTH, + priority=0 +) + +# Authorization (after authentication) +app.middlewares.append( + AuthorizationMiddleware(), + category=MiddlewareCategory.AUTHZ, + priority=0 +) + +# Custom business logic +app.middlewares.append( + rate_limiting_middleware, + category=MiddlewareCategory.BUSINESS, + priority=0 +) + +app.middlewares.append( + custom_logging_middleware, + category=MiddlewareCategory.BUSINESS, + priority=10 +) + +# Response modification (execute last) +app.middlewares.append( + response_time_middleware, + category=MiddlewareCategory.MESSAGE, + priority=0 +) +``` + +### Benefits of categorized middlewares + +1. **Predictable ordering**: Middlewares execute in a logical, predictable order based on their category. +2. **Easier maintenance**: You can add middlewares without worrying about their position in a flat list. +3. **Better organization**: Categories make it clear what each middleware's purpose is. +4. **Flexible priorities**: Fine-tune execution order within categories using priority values. +5. **Backward compatibility**: Existing code continues to work without changes. + ## Wrapping request handlers When a common portion of logic should be applied to certain request handlers, From 0c0434b644619fe9dc376f185740dbc73767de04 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 21:23:46 +0100 Subject: [PATCH 04/14] Update cache-control.md --- blacksheep/docs/cache-control.md | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/blacksheep/docs/cache-control.md b/blacksheep/docs/cache-control.md index 5d12829..b591084 100644 --- a/blacksheep/docs/cache-control.md +++ b/blacksheep/docs/cache-control.md @@ -5,6 +5,7 @@ It covers: request handlers. - [X] Using the `CacheControlMiddleware` to configure a common header for all request handlers globally. +- [X] Support for field-specific directives using `list[str]` values. ## About Cache-Control @@ -12,6 +13,46 @@ The `Cache-Control` response header can be used to describe how responses can be cached by clients. For information on this subject, it is recommended to refer to the [`mozilla.org` documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). +## Field-specific cache directives + +/// admonition | New in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, the `no_cache` and `private` directives support `list[str]` values to specify field-specific caching rules. + +/// + +Some cache directives like `no-cache` and `private` can be applied to specific response fields. BlackSheep supports this through `list[str]` values: + +```python +from blacksheep.server.headers.cache import cache_control + +# Apply no-cache to specific headers +@cache_control(no_cache=["Set-Cookie", "Authorization"]) +async def sensitive_endpoint(): + # This generates: Cache-Control: no-cache="Set-Cookie, Authorization" + return "Sensitive data" + +# Apply private directive to specific fields +@cache_control(private=["Set-Cookie"]) +async def user_specific_endpoint(): + # This generates: Cache-Control: private="Set-Cookie" + return "User-specific content" + +# Boolean values still work as before +@cache_control(no_cache=True, no_store=True) +async def no_caching_endpoint(): + # This generates: Cache-Control: no-cache, no-store + return "Never cache this" +``` + +When using `list[str]` values: + +- **`no_cache=["field1", "field2"]`** → `no-cache="field1, field2"` +- **`private=["field1", "field2"]`** → `private="field1, field2"` +- **`no_cache=True`** → `no-cache` (applies to entire response) +- **`private=True`** → `private` (applies to entire response) + ## Using the cache_control decorator The following example illustrates how the `cache_control` decorator can be used @@ -36,6 +77,20 @@ async def home(): async def get_cats(): ... + +@get("/api/user-profile") +@cache_control(private=["Set-Cookie", "Authorization"]) +async def get_user_profile(): + # Only Set-Cookie and Authorization headers are private + return "User-specific data" + + +@get("/api/sensitive") +@cache_control(no_cache=["Authorization"], max_age=60) +async def get_sensitive_data(): + # Don't cache Authorization header, but allow 60s caching for other data + return "Partially cacheable data" + ``` /// admonition | Decorators order. @@ -83,6 +138,15 @@ from blacksheep.server.headers.cache import cache_control, CacheControlMiddlewar app = Application() +# Global cache control with field-specific directives +app.middlewares.append( + CacheControlMiddleware( + no_cache=["Set-Cookie", "Authorization"], + max_age=300 + ) +) + +# Or traditional boolean approach app.middlewares.append(CacheControlMiddleware(no_cache=True, no_store=True)) ``` From d51acfdc0b785de21bc7cd598e1dfe5a4b820d61 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 21:28:51 +0100 Subject: [PATCH 05/14] Update binders.md --- blacksheep/docs/binders.md | 122 +++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 6 deletions(-) diff --git a/blacksheep/docs/binders.md b/blacksheep/docs/binders.md index 25d1192..2609c33 100644 --- a/blacksheep/docs/binders.md +++ b/blacksheep/docs/binders.md @@ -109,12 +109,122 @@ creating an instance of CreateCatInput from it. If an exception occurs while trying to parse the request payload or when instantiating the `CreateCatInput`, the framework produces automatically a `400 Bad Request` response for the client. -When mapping the request's payload to an instance of the desired type, the type -is instantiated using `cls(**data)`. If it necessary to parse dates or other -complex types that are not handled by JSON deserialization, this must be done -in the constructor of the class. To handle gracefully a JSON payload having -extra unused properties, use `**kwargs` in your class constructor: `__init__(one, -two, three, **kwargs)`. +/// admonition | Improved in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, extra properties in request bodies are **automatically ignored by default** when mapping to user-defined dataclasses, Pydantic v2 models, or plain classes. This applies to nested properties, lists, and dictionaries as well. + +/// + +When mapping the request's payload to an instance of the desired type, BlackSheep automatically handles extra properties that don't match the target class structure. **You no longer need to explicitly handle extra properties** in most cases. + +### Automatic Extra Property Handling + +BlackSheep now ignores extra fields by default: + +```python +from dataclasses import dataclass +from blacksheep import FromJSON, post + +@dataclass +class CreateUserInput: + name: str + email: str + # age field is not defined here + +@post("/api/users") +async def create_user(input: FromJSON[CreateUserInput]): + # This works even if the request includes extra fields like "age", "country", etc. + return {"created": input.value.name} + +# Request body with extra fields (automatically ignored): +# { +# "name": "John Doe", +# "email": "john@example.com", +# "age": 30, # <- ignored +# "country": "USA", # <- ignored +# "preferences": {...} # <- ignored +# } +``` + +### Nested Properties and Collections + +The improvement also applies to nested objects, lists, and dictionaries: + +```python +from dataclasses import dataclass +from typing import List + +@dataclass +class Address: + street: str + city: str + # postal_code not defined + +@dataclass +class User: + name: str + addresses: List[Address] + +@post("/api/users") +async def create_user_with_addresses(input: FromJSON[User]): + # Extra fields in nested Address objects are also ignored + return {"user_created": input.value.name} + +# Request body (extra fields at all levels are ignored): +# { +# "name": "John Doe", +# "extra_field": "ignored", # <- ignored at root level +# "addresses": [ +# { +# "street": "123 Main St", +# "city": "Springfield", +# "postal_code": "12345", # <- ignored in nested Address +# "country": "USA" # <- ignored in nested Address +# } +# ] +# } +``` + +### Legacy Behavior and Custom Control + +If you need the previous behavior or want explicit control over extra properties: + +```python +from dataclasses import dataclass + +@dataclass +class LegacyInput: + name: str + email: str + + def __init__(self, name: str, email: str, **kwargs): + # Explicitly handle extra properties if needed + self.name = name + self.email = email + self.extra_data = kwargs # Store extra fields + +# Or use Pydantic for more control: +from pydantic import BaseModel + +class StrictInput(BaseModel): + name: str + email: str + + class Config: + extra = "forbid" # Reject extra fields + # or extra = "allow" # Allow and preserve extra fields +``` + +### Benefits of Automatic Extra Property Handling + +1. **Better Developer Experience**: No need to add `**kwargs` to handle extra client data +2. **Robust API Evolution**: APIs remain backward-compatible when clients send additional fields +3. **Cleaner Code**: Focus on the data you need, ignore what you don't +4. **Consistent Behavior**: Works the same way across dataclasses, Pydantic models, and plain classes +5. **Deep Handling**: Automatically applies to nested objects and collections + +This improvement makes BlackSheep APIs more resilient to client-side changes and reduces the need for defensive programming when handling request payloads. ## Optional parameters From b3ef742ffa1f14947e5670bcc5c610c216872954 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 21:33:43 +0100 Subject: [PATCH 06/14] Update openapi.md --- blacksheep/docs/openapi.md | 142 +++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/blacksheep/docs/openapi.md b/blacksheep/docs/openapi.md index a78a593..5d3eb2b 100644 --- a/blacksheep/docs/openapi.md +++ b/blacksheep/docs/openapi.md @@ -12,6 +12,7 @@ OpenAPI Specification file. This page describes the following: - [X] Options to display OpenAPI Documentation. - [X] How to implement a custom `UIProvider`. - [X] How to document authentication schemes. +- [X] How to specify OpenAPI tags for Controller classes. ## Introduction to OpenAPI Documentation @@ -270,6 +271,140 @@ async def hidden_endpoint(): return "This endpoint won't appear in documentation" ``` +### Specifying OpenAPI tags for Controllers + +/// admonition | New in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, you can specify OpenAPI tags directly on Controller classes to organize and categorize your API endpoints in the documentation. + +/// + +You can specify OpenAPI tags for Controller classes using the `@docs.tags()` decorator. This allows you to organize your API endpoints into logical groups in the generated OpenAPI documentation: + +```python +from blacksheep import Application +from blacksheep.server.controllers import Controller, get, post, put, delete +from blacksheep.server.openapi.v3 import OpenAPIHandler +from openapidocs.v3 import Info +from dataclasses import dataclass +from uuid import UUID + +app = Application() + +docs = OpenAPIHandler(info=Info(title="Pet Store API", version="1.0.0")) +docs.bind_app(app) + + +@dataclass +class Pet: + id: UUID + name: str + category: str + + +@dataclass +class CreatePetInput: + name: str + category: str + + +@docs.tags("Pets") +class PetsController(Controller): + """ + Controller for managing pets in the store. + """ + + @classmethod + def route(cls) -> str: + return "/api/pets" + + @get() + async def get_pets(self) -> list[Pet]: + """Get all pets""" + return [] + + @get("/{pet_id}") + async def get_pet(self, pet_id: UUID) -> Pet: + """Get a pet by ID""" + pass + + @post() + async def create_pet(self, input: CreatePetInput) -> Pet: + """Create a new pet""" + pass + + @put("/{pet_id}") + async def update_pet(self, pet_id: UUID, input: CreatePetInput) -> Pet: + """Update an existing pet""" + pass + + @delete("/{pet_id}") + async def delete_pet(self, pet_id: UUID) -> None: + """Delete a pet""" + pass + + +@docs.tags("Orders", "Store") +class OrdersController(Controller): + """ + Controller for managing store orders. + """ + + @classmethod + def route(cls) -> str: + return "/api/orders" + + @get() + async def get_orders(self) -> list[dict]: + """Get all orders""" + return [] + + @post() + async def create_order(self, order_data: dict) -> dict: + """Create a new order""" + return {} +``` + +In this example: +- All endpoints in `PetsController` will be tagged with **"Pets"** +- All endpoints in `OrdersController` will be tagged with both **"Orders"** and **"Store"** +- The tags help organize the API documentation into logical sections + +### Multiple tags per Controller + +You can specify multiple tags for a single Controller by passing multiple string arguments: + +```python +@docs.tags("Users", "Authentication", "Admin") +class UserManagementController(Controller): + @classmethod + def route(cls) -> str: + return "/api/admin/users" + + @get() + async def list_users(self): + """List all users (Admin only)""" + return [] +``` + +### Benefits of Controller tags + +1. **Better organization**: Group related endpoints together in the OpenAPI UI +2. **Improved navigation**: Users can easily find endpoints by category +3. **Cleaner documentation**: Logical grouping makes the API easier to understand +4. **Consistent tagging**: All endpoints in a Controller automatically inherit the same tags +5. **Multiple categorization**: Controllers can belong to multiple logical groups + +### Tag display in OpenAPI UI + +When you specify tags on Controllers, they appear in the OpenAPI documentation UI (Swagger, ReDoc, Scalar) as collapsible sections. Users can: +- Expand/collapse tag sections to focus on specific functionality +- Filter endpoints by tags (depending on the UI provider) +- See a hierarchical organization of your API surface + +The tags are displayed in alphabetical order by default, making it easy for API consumers to navigate your documentation. + ### Response annotation Since version `2.4.4`, `Annotated` is supported to have proper automatic documentation @@ -782,6 +917,13 @@ that `$ref values must be RFC3986-compliant percent-encoded URIs`. A generic type with more arguments, like `Foo[T, U, X]` gets represented with `FooOfTAndUAndX`. +/// admonition | Alternative naming style in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, you can enable alternative programming-style naming for generic types using underscore notation. Set the environment variable `APP_OPENAPI_USE_UNDERSCORE_GENERIC_NAMES` to `'1'` or `'true'` to use names like `GenericType_T` instead of `GenericTypeOfT`. + +/// + /// ### Describing parameters From 32ab795da43ceba0b8b95cf7e43cb07b45844690 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 21:40:10 +0100 Subject: [PATCH 07/14] Fixes --- blacksheep/docs/openapi.md | 3 ++- blacksheep/docs/settings.md | 31 ++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/blacksheep/docs/openapi.md b/blacksheep/docs/openapi.md index 5d3eb2b..0f02041 100644 --- a/blacksheep/docs/openapi.md +++ b/blacksheep/docs/openapi.md @@ -399,6 +399,7 @@ class UserManagementController(Controller): ### Tag display in OpenAPI UI When you specify tags on Controllers, they appear in the OpenAPI documentation UI (Swagger, ReDoc, Scalar) as collapsible sections. Users can: + - Expand/collapse tag sections to focus on specific functionality - Filter endpoints by tags (depending on the UI provider) - See a hierarchical organization of your API surface @@ -920,7 +921,7 @@ A generic type with more arguments, like `Foo[T, U, X]` gets represented with /// admonition | Alternative naming style in BlackSheep 2.4.4 type: info -Starting from BlackSheep 2.4.4, you can enable alternative programming-style naming for generic types using underscore notation. Set the environment variable `APP_OPENAPI_USE_UNDERSCORE_GENERIC_NAMES` to `'1'` or `'true'` to use names like `GenericType_T` instead of `GenericTypeOfT`. +Starting from BlackSheep 2.4.4, you can enable alternative programming-style naming for generic types using underscore notation. Set the environment variable `APP_OPENAPI_PROGRAMMING_NAMES` to `'1'` or `'true'` to use names like `GenericType_T` instead of `GenericTypeOfT`. /// diff --git a/blacksheep/docs/settings.md b/blacksheep/docs/settings.md index ca7ef01..7470501 100644 --- a/blacksheep/docs/settings.md +++ b/blacksheep/docs/settings.md @@ -9,21 +9,22 @@ This page describes: ## Environmental variables -| Name | Category | Description | -| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| APP_ENV | Settings | This environment variable is read to determine the environment of the application. For more information, refer to [_Defining application environment_](/blacksheep/settings/#defining-application-environment). | -| APP_DEFAULT_ROUTER | Settings | Allows disabling the use of the default router exposed by BlackSheep. To disable the default router: `APP_DEFAULT_ROUTER=0`. | -| APP_SIGNAL_HANDLER | Settings | Allows enabling a callback to the `{signal.SIGINT, signal.SIGTERM}` signals to detect when the application process is stopping. Information is used in `blacksheep.server.process.is_stopping()` and is useful when handling long-lasting connections. See _[Server-Sent Events](./server-sent-events.md)_. | -| APP_JINJA_EXTENSION | Settings | Allows configuring the file extension used to work with Jinja templates. By default it is `.jinja`. | -| APP_JINJA_PACKAGE_NAME | Settings | Allows configuring the package name used by the default Jinja templates loader. By default it is `app` (as by default views are loaded from `app/views`. | -| APP_JINJA_PACKAGE_PATH | Settings | Allows configuring the path used by the default Jinja templates loader. By default it is `views` (as by default views are loaded from `app/views`. | -| APP_JINJA_DEBUG | Settings | Allows enabling Jinja debug mode, setting this env variable to a truthy value. | -| APP_JINJA_ENABLE_ASYNC | Settings | Allows enabling Jinja async mode, setting this env variable to a truthy value. | -| APP_SHOW_ERROR_DETAILS | Settings | If "1" or "true", configures the application to display web pages with error details in case of HTTP 500 Internal Server Error. | -| APP_MOUNT_AUTO_EVENTS | Settings | If "1" or "true", automatically binds lifecycle events of mounted apps between children and parents BlackSheep applications. | -| APP_ROUTE_PREFIX | Settings | Allows configuring a global prefix for all routes handled by the application. For more information, refer to: [Behind proxies](/blacksheep/behind-proxies/). | -| APP_SECRET_i | Secrets | Allows configuring the secrets used by the application to protect data. | -| BLACKSHEEP_SECRET_PREFIX | Secrets | Allows specifying the prefix of environment variables used to configure application secrets, defaults to "APP_SECRET" if not specified. | +| Name | Category | Description | +| ----------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APP_ENV | Settings | This environment variable is read to determine the environment of the application. For more information, refer to [_Defining application environment_](/blacksheep/settings/#defining-application-environment). | +| APP_DEFAULT_ROUTER | Settings | Allows disabling the use of the default router exposed by BlackSheep. To disable the default router: `APP_DEFAULT_ROUTER=0`. | +| APP_SIGNAL_HANDLER | Settings | Allows enabling a callback to the `{signal.SIGINT, signal.SIGTERM}` signals to detect when the application process is stopping. Information is used in `blacksheep.server.process.is_stopping()` and is useful when handling long-lasting connections. See _[Server-Sent Events](./server-sent-events.md)_. | +| APP_JINJA_EXTENSION | Settings | Allows configuring the file extension used to work with Jinja templates. By default it is `.jinja`. | +| APP_JINJA_PACKAGE_NAME | Settings | Allows configuring the package name used by the default Jinja templates loader. By default it is `app` (as by default views are loaded from `app/views`. | +| APP_JINJA_PACKAGE_PATH | Settings | Allows configuring the path used by the default Jinja templates loader. By default it is `views` (as by default views are loaded from `app/views`. | +| APP_JINJA_DEBUG | Settings | Allows enabling Jinja debug mode, setting this env variable to a truthy value. | +| APP_JINJA_ENABLE_ASYNC | Settings | Allows enabling Jinja async mode, setting this env variable to a truthy value. | +| APP_SHOW_ERROR_DETAILS | Settings | If "1" or "true", configures the application to display web pages with error details in case of HTTP 500 Internal Server Error. | +| APP_MOUNT_AUTO_EVENTS | Settings | If "1" or "true", automatically binds lifecycle events of mounted apps between children and parents BlackSheep applications. | +| APP_OPENAPI_PROGRAMMING_NAMES | Settings | If "1" or "true", uses underscores to document generic types like in: `list[Address]` -> `list_Address` in the OpenAPI documentation file. | +| APP_ROUTE_PREFIX | Settings | Allows configuring a global prefix for all routes handled by the application. For more information, refer to: [Behind proxies](/blacksheep/behind-proxies/). | +| APP_SECRET_i | Secrets | Allows configuring the secrets used by the application to protect data. | +| BLACKSHEEP_SECRET_PREFIX | Secrets | Allows specifying the prefix of environment variables used to configure application secrets, defaults to "APP_SECRET" if not specified. | ## Defining application environment From f2e824364ea227457a60337c810c63fe1da7adb3 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:03:50 +0100 Subject: [PATCH 08/14] Document more features added to 2.4.4 --- blacksheep/docs/application.md | 135 +++++++++++++++++ blacksheep/docs/behind-proxies.md | 241 +++++++++++++++++++++++++++++- 2 files changed, 371 insertions(+), 5 deletions(-) diff --git a/blacksheep/docs/application.md b/blacksheep/docs/application.md index e5a8f3f..5c19b5e 100644 --- a/blacksheep/docs/application.md +++ b/blacksheep/docs/application.md @@ -58,6 +58,141 @@ settings and configuration roots. Refer to [_Getting started with the MVC projec for more information. /// +## EnvironmentSettings + +/// admonition | New in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, the `Application` object includes an `env_settings` property that provides runtime access to environment-based configuration settings. + +/// + +The `Application` object automatically attaches an `EnvironmentSettings` instance that contains configuration values read from environment variables. This feature provides transparency and enables runtime inspection of the application's configuration, which is useful for debugging, testing, and administrative purposes. + +### Accessing Environment Settings + +You can access the environment settings through the `env_settings` property: + +```python +from blacksheep import Application + +app = Application() + +# Access environment settings at runtime +print(f"Show error details: {app.env_settings.show_error_details}") +print(f"Force HTTPS: {app.env_settings.force_https}") +print(f"HTTP scheme: {app.env_settings.http_scheme}") +``` + +### Available Environment Settings + +The `EnvironmentSettings` object includes the following properties (all read-only): + +| Property | Environment Variable | Type | Description | +|----------|---------------------|------|-------------| +| `show_error_details` | `APP_SHOW_ERROR_DETAILS` | `bool` | Whether to display detailed error information | +| `force_https` | `APP_FORCE_HTTPS` | `bool` | Whether to force HTTPS scheme and enable HSTS | +| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | Explicitly set request scheme (`http` or `https`) | + +### Practical Use Cases + +#### Testing and Assertions + +Environment settings are particularly useful for testing configuration: + +```python +import os +from blacksheep import Application + +# Set environment variable +os.environ["APP_FORCE_HTTPS"] = "true" + +app = Application() + +# Assert configuration in tests +assert app.env_settings.force_https is True +assert app.env_settings.http_scheme is None # Not set + +# Clean up +del os.environ["APP_FORCE_HTTPS"] +``` + +#### Health Check Endpoints + +Create health check endpoints that expose configuration information: + +```python +from blacksheep import Application, get + +app = Application() + +@get("/health") +async def health_check(): + return { + "status": "healthy", + "config": { + "force_https": app.env_settings.force_https, + "http_scheme": app.env_settings.http_scheme, + "show_error_details": app.env_settings.show_error_details + } + } +``` + +#### Admin Tools and Configuration Inspection + +Build administrative interfaces that display current configuration: + +```python +from blacksheep import Application, get +from blacksheep.server.authorization import auth + +app = Application() + +@auth(roles=["admin"]) +@get("/admin/config") +async def admin_config(): + """Administrative endpoint to inspect application configuration""" + return { + "environment_settings": { + "show_error_details": app.env_settings.show_error_details, + "force_https": app.env_settings.force_https, + "http_scheme": app.env_settings.http_scheme, + }, + "runtime_info": { + "debug_mode": app.debug, + "middleware_count": len(app.middlewares), + } + } +``` + +#### Debugging and Development + +Use environment settings for conditional debugging logic: + +```python +from blacksheep import Application + +app = Application() + +@app.on_start +async def configure_logging(): + if app.env_settings.show_error_details: + # Enable verbose logging in development + import logging + logging.getLogger().setLevel(logging.DEBUG) + print("Debug logging enabled due to APP_SHOW_ERROR_DETAILS=true") +``` + +### Benefits of Runtime Configuration Access + +1. **Transparency**: Easy inspection of how the application is configured +2. **Testing**: Reliable assertions about configuration state in tests +3. **Debugging**: Quick access to configuration values during development +4. **Monitoring**: Health checks and admin endpoints can expose configuration +5. **Conditional Logic**: Runtime decisions based on configuration values + +The `EnvironmentSettings` object is read-only, ensuring that configuration remains stable throughout the application lifecycle while still providing full visibility into the current settings. + ### Configuring exceptions handlers The BlackSheep `Application` object has an `exceptions_handlers` dictionary diff --git a/blacksheep/docs/behind-proxies.md b/blacksheep/docs/behind-proxies.md index 1657cc5..0d637c8 100644 --- a/blacksheep/docs/behind-proxies.md +++ b/blacksheep/docs/behind-proxies.md @@ -143,11 +143,12 @@ For example, the `get_absolute_url_to_path` defined in `blacksheep.messages` will handle the information and return an absolute URL to the server according to both scenarios. -| Feature | Description | -| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `request.base_path` | Returns the `base_path` of a web request, when the ASGI scope includes a `root_path`, or a route prefix is used. | -| `blacksheep.messages.get_absolute_url_to_path` | Returns an absolute URL path to a given destination, including the current `root_path` or route prefix. Useful when working with redirects. | -| OpenAPI Documentation | Since version `2.1.0`, it uses relative links to serve the OpenAPI Specification files (YAML and JSON), and relative paths to support any path prefix. | +| Feature | Description | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `request.base_path` | Returns the `base_path` of a web request, when the ASGI scope includes a `root_path`, or a route prefix is used. | +| `blacksheep.messages.get_absolute_url_to_path` | Returns an absolute URL path to a given destination, including the current `root_path` or route prefix. Useful when working with redirects. | +| `HTTPSchemeMiddleware` | Since version `2.4.4`, automatically configures request scheme for TLS termination scenarios using `APP_FORCE_HTTPS` or `APP_HTTP_SCHEME` environment variables. | +| OpenAPI Documentation | Since version `2.1.0`, it uses relative links to serve the OpenAPI Specification files (YAML and JSON), and relative paths to support any path prefix. | /// details | Jinja2 template helper type: tip @@ -157,3 +158,233 @@ The BlackSheep MVC template includes an example of helper function to in Jinja templates. /// + +## HTTPS Scheme Configuration + +/// admonition | New in BlackSheep 2.4.4 + type: info + +Starting from BlackSheep 2.4.4, BlackSheep provides automatic scheme configuration for applications running behind reverse proxies or load balancers with TLS termination. + +/// + +When applications run behind reverse proxies or load balancers that handle TLS termination, the backend application receives HTTP requests even though clients connect via HTTPS. This can cause issues with: + +- **URL generation**: Links and redirects may use `http://` instead of `https://` +- **OpenID Connect flows**: Authentication redirects require correct scheme URLs +- **Security headers**: HSTS headers may not be applied appropriately +- **Cookie security**: Secure cookies may not work properly + +### Automatic Scheme Configuration + +BlackSheep automatically configures request scheme handling based on environment variables. This feature is applied during application startup when specific environment variables are detected. + +#### Force HTTPS with HSTS + +To force all requests to use HTTPS scheme and automatically enable HSTS (HTTP Strict Transport Security) headers: + +```bash +# Environment variable +APP_FORCE_HTTPS=true +``` + +```python +from blacksheep import Application, get + +app = Application() + +# When APP_FORCE_HTTPS=true, BlackSheep automatically: +# 1. Sets request.scheme = "https" for all requests +# 2. Adds HSTS middleware for security headers +# 3. Ensures proper URL generation in proxied environments + +@get("/redirect-example") +async def redirect_example(request): + # This will generate https:// URLs even if the backend receives http:// + redirect_url = request.url.replace(path="/dashboard") + return redirect(redirect_url) +``` + +#### Explicit Scheme Configuration + +To explicitly set the request scheme without enabling HSTS: + +```bash +# Force HTTPS scheme +APP_HTTP_SCHEME=https + +# Or force HTTP scheme (for development) +APP_HTTP_SCHEME=http +``` + +```python +from blacksheep import Application, get + +app = Application() + +# When APP_HTTP_SCHEME is set, BlackSheep automatically: +# - Sets request.scheme to the specified value +# - Does NOT add HSTS headers (unlike APP_FORCE_HTTPS) + +@get("/api/info") +async def api_info(request): + return { + "scheme": request.scheme, # Will be "https" if APP_HTTP_SCHEME=https + "host": request.host, + "url": str(request.url) + } +``` + +### Manual HTTPSchemeMiddleware + +For more control, you can manually configure the `HTTPSchemeMiddleware`: + +```python +from blacksheep import Application +from blacksheep.server.remotes.scheme import HTTPSchemeMiddleware +from blacksheep.middlewares import MiddlewareCategory + +app = Application() + +# Manual configuration +app.middlewares.append( + HTTPSchemeMiddleware("https"), + category=MiddlewareCategory.INIT, + priority=-100 # Execute early in the middleware chain +) + +@get("/manual-config") +async def manual_config_example(request): + # request.scheme will always be "https" + return {"configured_scheme": request.scheme} +``` + +### Environment Variable Priority + +When both environment variables are set, `APP_FORCE_HTTPS` takes precedence over `APP_HTTP_SCHEME`: + +```bash +# This configuration will use APP_FORCE_HTTPS +APP_FORCE_HTTPS=true +APP_HTTP_SCHEME=http # This is ignored +``` + +| Environment Variable | Behavior | HSTS Headers | Use Case | +| ----------------------- | -------------------------- | ------------- | ------------------------------- | +| `APP_FORCE_HTTPS=true` | Forces HTTPS scheme | ✅ Enabled | Production with TLS termination | +| `APP_HTTP_SCHEME=https` | Forces HTTPS scheme | ❌ Not enabled | Custom HTTPS setup | +| `APP_HTTP_SCHEME=http` | Forces HTTP scheme | ❌ Not enabled | Development/testing | +| Neither set | Uses actual request scheme | ❌ Not enabled | Default behavior | + +### Use Cases and Benefits + +#### 1. Load Balancers with TLS Termination + +```mermaid +sequenceDiagram + participant Client + participant LoadBalancer as Load Balancer
(TLS Termination) + participant App as BlackSheep App
(APP_FORCE_HTTPS=true) + + Client->>LoadBalancer: HTTPS Request + LoadBalancer->>App: HTTP Request (decrypted) + App-->>LoadBalancer: HTTP Response with https:// URLs + LoadBalancer-->>Client: HTTPS Response + HSTS headers +``` + +#### 2. OpenID Connect Integration + +```python +from blacksheep import Application +from blacksheep.server.openid.oidc import OpenIDSettings + +# With APP_FORCE_HTTPS=true, OpenID Connect redirects work correctly +app = Application() + +# OpenID Connect will generate correct https:// redirect URLs +oidc_settings = OpenIDSettings( + authority="https://your-authority.com", + client_id="your-client-id", + # redirect_uri will automatically use https:// scheme +) +``` + +#### 3. API Gateway Deployments + +```python +# Deployment behind AWS API Gateway, Azure API Management, etc. +# Set APP_FORCE_HTTPS=true to ensure proper URL generation + +from blacksheep import Application, get + +app = Application() + +@get("/api/resource/{id}") +async def get_resource(request, id: str): + # Generate links to other resources with correct scheme + base_url = request.url.replace(path="") + return { + "id": id, + "self": f"{base_url}/api/resource/{id}", + "related": f"{base_url}/api/resource/{id}/related" + } +``` + +### Configuration in Application Settings + +Access the current scheme configuration through the application's environment settings: + +```python +from blacksheep import Application + +app = Application() + +@app.on_start +async def log_configuration(): + print(f"Force HTTPS: {app.env_settings.force_https}") + print(f"HTTP Scheme: {app.env_settings.http_scheme}") + + if app.env_settings.force_https: + print("🔒 HTTPS enforcement and HSTS headers enabled") + elif app.env_settings.http_scheme: + print(f"🔧 Scheme forced to: {app.env_settings.http_scheme}") + else: + print("â„šī¸ Using actual request scheme") +``` + +### Troubleshooting + +**Problem**: URLs still generate with `http://` scheme + +**Solution**: Ensure the environment variable is set correctly: + +```bash +# Check if the variable is set +echo $APP_FORCE_HTTPS + +# Set it correctly (case-sensitive) +export APP_FORCE_HTTPS=true +# or +export APP_FORCE_HTTPS=1 +``` + +**Problem**: HSTS headers not appearing + +**Solution**: Use `APP_FORCE_HTTPS` instead of `APP_HTTP_SCHEME` for automatic HSTS: + +```bash +# This enables HSTS +APP_FORCE_HTTPS=true + +# This does NOT enable HSTS +APP_HTTP_SCHEME=https +``` + +**Problem**: Scheme middleware not applying + +**Solution**: The middleware is only applied automatically when environment variables are detected at startup. Check the application startup logs or environment settings: + +```python +# Verify configuration is detected +print(f"App env settings: {app.env_settings.__dict__}") +``` From 99c2f9022d3bf71c74a3a978a6114ae9e8982300 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:20:11 +0100 Subject: [PATCH 09/14] Update settings.md --- blacksheep/docs/settings.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blacksheep/docs/settings.md b/blacksheep/docs/settings.md index 7470501..0aea5d1 100644 --- a/blacksheep/docs/settings.md +++ b/blacksheep/docs/settings.md @@ -13,6 +13,8 @@ This page describes: | ----------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | APP_ENV | Settings | This environment variable is read to determine the environment of the application. For more information, refer to [_Defining application environment_](/blacksheep/settings/#defining-application-environment). | | APP_DEFAULT_ROUTER | Settings | Allows disabling the use of the default router exposed by BlackSheep. To disable the default router: `APP_DEFAULT_ROUTER=0`. | +| APP_FORCE_HTTPS | Settings | If "1" or "true", automatically configures the application to set HSTS response headers and use `https` scheme. See [_Behind Proxies_](./behind-proxies.md). | +| APP_HTTP_SCHEME | Settings | Allows to configure the request scheme. See [_Behind Proxies_](./behind-proxies.md). | | APP_SIGNAL_HANDLER | Settings | Allows enabling a callback to the `{signal.SIGINT, signal.SIGTERM}` signals to detect when the application process is stopping. Information is used in `blacksheep.server.process.is_stopping()` and is useful when handling long-lasting connections. See _[Server-Sent Events](./server-sent-events.md)_. | | APP_JINJA_EXTENSION | Settings | Allows configuring the file extension used to work with Jinja templates. By default it is `.jinja`. | | APP_JINJA_PACKAGE_NAME | Settings | Allows configuring the package name used by the default Jinja templates loader. By default it is `app` (as by default views are loaded from `app/views`. | From 928cb8b6506499b1686ed4691b8ac35f9eca1555 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:24:02 +0100 Subject: [PATCH 10/14] Update openapi.md --- blacksheep/docs/openapi.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/blacksheep/docs/openapi.md b/blacksheep/docs/openapi.md index 0f02041..aaffc20 100644 --- a/blacksheep/docs/openapi.md +++ b/blacksheep/docs/openapi.md @@ -273,12 +273,9 @@ async def hidden_endpoint(): ### Specifying OpenAPI tags for Controllers -/// admonition | New in BlackSheep 2.4.4 - type: info - Starting from BlackSheep 2.4.4, you can specify OpenAPI tags directly on Controller classes to organize and categorize your API endpoints in the documentation. -/// +Before BlackSheep 2.4.4, OpenAPI tags were automatically generated based on the controller class name. For example, a `PetsController` class would automatically generate a "Pets" tag for all its endpoints. The new `@docs.tags()` decorator provides explicit control over tag assignment and supports multiple tags per controller. You can specify OpenAPI tags for Controller classes using the `@docs.tags()` decorator. This allows you to organize your API endpoints into logical groups in the generated OpenAPI documentation: @@ -367,6 +364,7 @@ class OrdersController(Controller): ``` In this example: + - All endpoints in `PetsController` will be tagged with **"Pets"** - All endpoints in `OrdersController` will be tagged with both **"Orders"** and **"Store"** - The tags help organize the API documentation into logical sections From d3b7ad662e51dc91a21455a0ee89604ac8083980 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:29:01 +0100 Subject: [PATCH 11/14] Update openapi.md --- blacksheep/docs/openapi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blacksheep/docs/openapi.md b/blacksheep/docs/openapi.md index aaffc20..3aed882 100644 --- a/blacksheep/docs/openapi.md +++ b/blacksheep/docs/openapi.md @@ -273,9 +273,9 @@ async def hidden_endpoint(): ### Specifying OpenAPI tags for Controllers -Starting from BlackSheep 2.4.4, you can specify OpenAPI tags directly on Controller classes to organize and categorize your API endpoints in the documentation. +Before BlackSheep 2.4.4, OpenAPI tags were automatically generated based on the controller class name. For example, a `Pets(Controller)` class would automatically generate a "Pets" tag for all its endpoints. The new `@docs.tags()` decorator provides explicit control over tag assignment and supports multiple tags per controller. -Before BlackSheep 2.4.4, OpenAPI tags were automatically generated based on the controller class name. For example, a `PetsController` class would automatically generate a "Pets" tag for all its endpoints. The new `@docs.tags()` decorator provides explicit control over tag assignment and supports multiple tags per controller. +Starting from BlackSheep 2.4.4, you can specify OpenAPI tags directly on Controller classes to organize and categorize your API endpoints in the documentation. You can specify OpenAPI tags for Controller classes using the `@docs.tags()` decorator. This allows you to organize your API endpoints into logical groups in the generated OpenAPI documentation: From a61f64c211436ae4491c6f11f5d2b9a38c531033 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:29:50 +0100 Subject: [PATCH 12/14] Update application.md --- blacksheep/docs/application.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blacksheep/docs/application.md b/blacksheep/docs/application.md index 5c19b5e..9828c46 100644 --- a/blacksheep/docs/application.md +++ b/blacksheep/docs/application.md @@ -88,11 +88,11 @@ print(f"HTTP scheme: {app.env_settings.http_scheme}") The `EnvironmentSettings` object includes the following properties (all read-only): -| Property | Environment Variable | Type | Description | -|----------|---------------------|------|-------------| +| Property | Environment Variable | Type | Description | +| -------------------- | ------------------------ | ------ | --------------------------------------------- | | `show_error_details` | `APP_SHOW_ERROR_DETAILS` | `bool` | Whether to display detailed error information | -| `force_https` | `APP_FORCE_HTTPS` | `bool` | Whether to force HTTPS scheme and enable HSTS | -| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | Explicitly set request scheme (`http` or `https`) | +| `force_https` | `APP_FORCE_HTTPS` | `bool` | Whether to force HTTPS scheme and enable HSTS | +| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | Explicitly set request scheme (`http` or `https`) | ### Practical Use Cases From f5ffc601961babdecf2433bfb5ab347f057badd0 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:35:42 +0100 Subject: [PATCH 13/14] Fixes --- blacksheep/docs/application.md | 10 +++++----- blacksheep/docs/cache-control.md | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/blacksheep/docs/application.md b/blacksheep/docs/application.md index 9828c46..962e2b5 100644 --- a/blacksheep/docs/application.md +++ b/blacksheep/docs/application.md @@ -88,11 +88,11 @@ print(f"HTTP scheme: {app.env_settings.http_scheme}") The `EnvironmentSettings` object includes the following properties (all read-only): -| Property | Environment Variable | Type | Description | -| -------------------- | ------------------------ | ------ | --------------------------------------------- | -| `show_error_details` | `APP_SHOW_ERROR_DETAILS` | `bool` | Whether to display detailed error information | -| `force_https` | `APP_FORCE_HTTPS` | `bool` | Whether to force HTTPS scheme and enable HSTS | -| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | Explicitly set request scheme (`http` or `https`) | +| Property | Environment Variable | Type | Description | +| -------------------- | ------------------------ | ------------ | --------------------------------------------- | +| `show_error_details` | `APP_SHOW_ERROR_DETAILS` | `bool` | Whether to display detailed error information | +| `force_https` | `APP_FORCE_HTTPS` | `bool` | Whether to force HTTPS scheme and enable HSTS | +| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | Explicitly set request scheme (`http` or `https`) | ### Practical Use Cases diff --git a/blacksheep/docs/cache-control.md b/blacksheep/docs/cache-control.md index b591084..818e559 100644 --- a/blacksheep/docs/cache-control.md +++ b/blacksheep/docs/cache-control.md @@ -18,7 +18,8 @@ refer to the [`mozilla.org` documentation](https://developer.mozilla.org/en-US/d /// admonition | New in BlackSheep 2.4.4 type: info -Starting from BlackSheep 2.4.4, the `no_cache` and `private` directives support `list[str]` values to specify field-specific caching rules. +Starting from BlackSheep 2.4.4, the `no_cache` and `private` directives support `list[str]` values to specify field-specific caching rules. Earlier versions would require to handle multiple headers +using comma separated values like: `field1, field2`. /// From f90b488591022ddc77b27a8af9189bb820f642d8 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Sat, 13 Dec 2025 22:47:03 +0100 Subject: [PATCH 14/14] Update application.md --- blacksheep/docs/application.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/blacksheep/docs/application.md b/blacksheep/docs/application.md index 962e2b5..c7c0431 100644 --- a/blacksheep/docs/application.md +++ b/blacksheep/docs/application.md @@ -49,6 +49,7 @@ the environment variable `APP_SHOW_ERROR_DETAILS` to control whether the application displays detailed error information. Setting `APP_SHOW_ERROR_DETAILS=1` or `APP_SHOW_ERROR_DETAILS=True` enables this feature. + /// /// admonition | Settings strategy @@ -56,18 +57,17 @@ feature. BlackSheep project templates include a strategy to handle application settings and configuration roots. Refer to [_Getting started with the MVC project template_](./mvc-project-template.md) for more information. + /// ## EnvironmentSettings -/// admonition | New in BlackSheep 2.4.4 - type: info - -Starting from BlackSheep 2.4.4, the `Application` object includes an `env_settings` property that provides runtime access to environment-based configuration settings. - -/// - -The `Application` object automatically attaches an `EnvironmentSettings` instance that contains configuration values read from environment variables. This feature provides transparency and enables runtime inspection of the application's configuration, which is useful for debugging, testing, and administrative purposes. +Starting from BlackSheep 2.4.4, the `Application` object includes an `env_settings` +property that provides runtime access to environment-based configuration settings. +The `Application` object automatically attaches an `EnvironmentSettings` instance that +contains configuration values read from environment variables. This feature provides +transparency and enables runtime inspection of the application's configuration, which +is useful for debugging, testing, and administrative purposes. ### Accessing Environment Settings @@ -88,11 +88,17 @@ print(f"HTTP scheme: {app.env_settings.http_scheme}") The `EnvironmentSettings` object includes the following properties (all read-only): -| Property | Environment Variable | Type | Description | -| -------------------- | ------------------------ | ------------ | --------------------------------------------- | -| `show_error_details` | `APP_SHOW_ERROR_DETAILS` | `bool` | Whether to display detailed error information | -| `force_https` | `APP_FORCE_HTTPS` | `bool` | Whether to force HTTPS scheme and enable HSTS | -| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | Explicitly set request scheme (`http` or `https`) | +| Property | Environment Variable | Type | Default | Description | +| -------------------- | ------------------------ | ------------ | ------- | --------------------------------------------- | +| `env` | `APP_ENV` | `str` | `"production"` | Application environment (e.g., "local", "dev", "production") | +| `show_error_details` | `APP_SHOW_ERROR_DETAILS` | `bool` | `False` | Whether to display detailed error information | +| `mount_auto_events` | `APP_MOUNT_AUTO_EVENTS` | `bool` | `True` | Whether to automatically mount application events | +| `use_default_router` | `APP_DEFAULT_ROUTER` | `bool` | `True` | Whether to use the default router | +| `add_signal_handler` | `APP_SIGNAL_HANDLER` | `bool` | `False` | Whether to add signal handlers for graceful shutdown | +| `http_scheme` | `APP_HTTP_SCHEME` | `str | None` | `None` | Explicitly set request scheme (`http` or `https`) | +| `force_https` | `APP_FORCE_HTTPS` | `bool` | `False` | Whether to force HTTPS scheme and enable HSTS | + +Refer to [_Settings_](./settings.md) for more information. ### Practical Use Cases