Releases: Neoteroi/BlackSheep
v2.6.2
-
Fix regression that broke compatibility with
Starlettemounts #668.
Add integration tests to verify support forStarletteandPiccolo-Admin. Reported by @snow-born and @sinisaos. -
Fix #561: fix support for
PYTHONOPTIMIZE=2. -
Add support for baking OpenAPI Specification files to disk, to support running
withPYTHONOPTIMIZE=2(or-OO) where docstrings are stripped and cannot be
used to enrich OpenAPI Documentation automatically.- Add
save_spec(destination)method toOpenAPIHandler: writes both the JSON
and YAML variants of the current in-memory spec to disk. - Add
spec_fileparameter toOpenAPIHandler: when set,build_docsloads the
pre-baked spec from disk instead of regenerating it. If the files do not exist
yet on startup they are generated and saved automatically (first-startup
auto-bake), then loaded from disk on every subsequent startup. - Add
APP_SPEC_FILEenvironment variable as a zero-code-change alternative to
spec_file=: set it in TEST / PROD environments to activate the baked-spec
path without any application code change. - Issue a
UserWarningwhenPYTHONOPTIMIZE >= 2and a request handler has no
docstring, advising the user to bake the spec file.
Proposed workflow:
# 1. bake_spec.py — run once in CI, without -OO import asyncio from myapp import app, docs asyncio.run(app.start()) docs.save_spec("./openapi.json") # also writes ./openapi.yaml
# 2. ship the files with the application, then in TEST / PROD: export APP_SPEC_FILE=./openapi.json
No application code change is required between environments.
- Add
-
Add support for PKCE (Proof Key for Code Exchange, RFC 7636) to the OpenID Connect implementation.
- New
use_pkce: boolsetting onOpenIDSettings. When enabled, acode_verifier
is generated per sign-in request, stored inside the signedstateparameter, and
the correspondingcode_challenge(S256) is sent to the authorization endpoint.
The verifier is passed automatically in the token exchange request. - New
response_mode: str | Nonesetting onOpenIDSettings, with automatic
detection viaget_response_mode():- PKCE with
client_secret(confidential client, recommended for web apps) →
"form_post"— the authorization code arrives viaPOSTform data. This is the
same behaviour as the existing secret-only flow, with PKCE added as an extra layer
of protection, consistent with OAuth 2.1
best practices. - PKCE without
client_secret(public client, e.g. native/desktop apps) →
"query"— the authorization code arrives via aGETredirect with the code in
the query string (this scenario is not supported by some identity providers). - The value can be overridden explicitly if a specific provider requires it.
- PKCE with
- The callback route is registered as
GETorPOSTautomatically based on the
effectiveresponse_mode. - New module-level helpers:
generate_pkce_code_verifier()and
generate_pkce_code_challenge(), both exported from
blacksheep.server.authentication.oidc.
PKCE + secret (extra layer of security for server-side web apps):
use_openid_connect( app, OpenIDSettings( client_id="...", client_secret=Secret("..."), # confidential client authority="https://<AUTHORITY>", use_pkce=True, # adds code_challenge on top of the secret ), )
- New
-
Fix #514, reported by @jesseandringa — add support for
multiple request body formats on a single endpoint.-
New
MultiFormatBodyBinder: holds an ordered list of innerBodyBinders and
dispatchesget_valueto the first whosematches_content_typereturnsTrue.
Returns HTTP 415 Unsupported Media Type when the binder is required and no
inner binder matches the request'sContent-Type. -
Union annotation syntax — the normalization layer detects a union of
body-binderBoundValuetypes and automatically builds a
MultiFormatBodyBinder:async def create_item(data: FromJSON[Item] | FromForm[Item]) -> Item: ... # optional variant async def create_item(data: FromJSON[Item] | FromForm[Item] | None) -> Item: ...
-
New
FromBody[T]convenience type: equivalent to
FromJSON[T] | FromForm[T], useful when you want to accept both structured
formats without being explicit:async def create_item(data: FromBody[Item]) -> Item: ...
-
Add new
FromXML[T]andXMLBinder: parseapplication/xml/text/xml
request bodies usingdefusedxml,
protecting against XXE injection, entity expansion (billion laughs),
and DTD-based attacks. Security exceptions propagate unmodified so the
application can distinguish attack attempts from ordinary malformed input.
Install the extra withpip install blacksheep[xml]. -
All new types compose freely:
async def create_item(data: FromJSON[Item] | FromXML[Item] | FromForm[Item]): ...
-
OpenAPI Specification is generated correctly for all combinations: each
accepted content type appears as a separate entry underrequestBody.content,
all referencing the same schema.
-
v2.6.1
-
Fix missing escaping in
multipart/form-datafilenames and content-disposition headers. -
Fix #193, adding support for
a2wsgi. Reported by @jordemort. -
raw_pathis optional in ASGI specification. If not present, now theinstantiate_requestmethod automatically obtains it frompathand sets it in the scope. -
Static files are now served with
Content-Lengthheader instead ofTransfer-Encoding: chunkedwhen file size is known. This improves compatibility withWSGIservers viaa2wsgi. -
Automatically run the
Applicationstart logic if the__call__method is called with http or websocket messages. This is useful whenlifespanevents are not supported, like when usingWSGI. -
Fix the issue #396. Requests for mounted apps are redirected to a directory (ending with '/') only if the request includes a
Sec-Fetch-Mode: navigate, which is used by modern browsers to inform the server the request is for navigation. This way, mounted apps serving HTML documents containing relative links work properly (their path must end with/). Reported by @satori1995. -
Fix the issue #256: add support for configuring names for routes, and for obtaining URLs by route name. Example:
from blacksheep.routing import URLResolver @app.router.get("/cats/{cat_id}", name="cat-detail") async def get_cat_detail(cat_id: int) -> Response: return Response(200) @app.router.get("/redirect") async def redirect_handler(url_resolver: URLResolver) -> Response: return redirect(url_resolver.url_for("cat-detail", cat_id="42"))
v2.5.1
- Fix problem in workflow and the PyPy distribution wheels.
v2.5.0
- Add native HTTP/2 support to the HTTP client with automatic protocol detection via ALPN (Application-Layer Protocol Negotiation). The client now automatically uses HTTP/2 when the server supports it, with seamless fallback to HTTP/1.1.
- Add new
HTTP2Connectionclass using theh2library for HTTP/2 protocol handling. - Add new
HTTP11Connectionclass using theh11library for consistent HTTP/1.1 handling. - Both connection types use
asyncio.open_connectionstreams for a unified architecture. - HTTP/2 connections support stream multiplexing, allowing multiple concurrent requests over a single TCP connection.
- Add
http2parameter toClientSession(defaults toTrue) to control HTTP/2 usage. - Protocol detection is performed once per host and cached for efficiency.
- Add
h2>=4.0.0,<5.0.0as a new dependency. - Refactor HTTP/1.1 client implementation to use the
h11library instead of custom request writing methods fromblacksheep.scribe. This provides a more robust and standards-compliant HTTP/1.1 state machine, and creates consistency with the HTTP/2 implementation pattern. - The new
HTTP11Connectionclass replaces theasyncio.Protocol-based approach with a streams-based implementation. - Remove the legacy
ClientConnectionclass. - Both HTTP/1.1 and HTTP/2 implementations follow the same architectural pattern.
- Remove the option of passing the event loop to the constructor of client classes.
- Stop using
httptoolsfor anything. - Correct bugs in the code serving static files:
HTTP 304should not be returned forRangerequests, and handling of ranges.
v2.4.6
- Fix CRLF injection vulnerability in the BlackSheep HTTP Client, reported by Jinho Ju (@tr4ce-ju).
- Add a
SECURITY.mdfile. - Fix #646.
- Modify the
Cookiereprto not include the value in full, as it can contain secrets that would leak in logs. - Improve type annotations for several modules, by @tyzhnenko.
v2.4.5
v2.4.4
- Add support for annotated types in
OpenAPIHandlerreturn types, by @tyzhnenko. This feature is important to support automatic generation of OpenAPI Documentation when returning instances ofResponse(e.g.Annotated[Response, ProductDetails]). - Introduce
MiddlewareListandMiddlewareCategoryto simplify middleware management and ordering of middlewares (see #620). Middlewares are now automatically sorted by category (INIT, SESSION, AUTH, AUTHZ, BUSINESS, MESSAGE) and optional priority within each category. This ensures proper execution order (e.g., CORS before authentication, authentication before authorization) without requiring developers to manually manage middleware insertion order. The system maintains backward compatibility while providing a more intuitive and error-resistant approach to middleware configuration. The same improvement is applied both to theApplicationand to theClientSessionclasses. - Add support for
list[str]as a value forno-cacheandprivatedirectives in code handling cache control headers, by @karpetrosyan. - Fix bug #619, that caused surprising behavior (requiring an explicit fallback or catch-all route to handle web requests that didn't match any route, otherwise middlewares would be bypassed for the defined
NotFoundexception handler). - Change the text of
Bad Requestresponse body when the input from the client causes aTypeErrorwhen trying to bind to an instance of the expected type (it reduces the amount of details sent to the client). - Improve the user experience by ignoring extra properties in request body by default, when mapping to user-defined dataclasses, Pydantic v2 models, or classes (see #614). Previously, extra properties were not ignored by default and required the user to explicitly code their input classes to allow extra properties. This is also done for sub-properties, lists, and dictionaries. The user can still control how exactly input bodies from clients are converted using custom binders or altering
blacksheep.server.bindings.class_converters. - Add support for specifying OpenAPI tags for controllers. This simplifies handling tags for documentation (#616).
- Improve the build matrix to build wheels for
arm64architecture for Linux and Windows, and usecibuildwheelfor Ubuntu and Windows, by @bymoye and @RobertoPrevato. - Update type annotations to Python >= 3.10.
- Fix bug that would prevent union types described using pipes from being properly represented in OpenAPI specification.
- Add support for alternative programming-style naming for generic types in OpenAPI specification files. When enabled, type names use underscore notation closer to actual type annotations (e.g.,
PaginatedSet_Addressinstead ofPaginatedSetOfAddress,Dict_str_intinstead ofDictOfstrAndint). This can be controlled via theprogramming_namesparameter inDefaultSerializeror theAPP_OPENAPI_PROGRAMMING_NAMESenvironment variable, setting it to a truthy value ('1' or 'true'). - Make
EnvironmentSettingsread-only, refactor to not usedataclass. - Attach
EnvironmentSettingsto theApplicationobject for runtime inspection, which is useful for: transparency and debugging, testing (assert app.env_settings.force_https is True), health check endpoints or admin tools can expose configuration. - Add
HTTPSchemeMiddlewareto set request scheme when running behind reverse proxies or load balancers with TLS termination. See #631. - Add support for
APP_HTTP_SCHEMEenvironment variable to explicitly set the request scheme tohttporhttps. - Add support for
APP_FORCE_HTTPSenvironment variable to force HTTPS scheme and automatically enable HSTS (HTTP Strict Transport Security) headers. - Add automatic scheme middleware configuration via
configure_scheme_middleware()- applied during application startup when eitherAPP_HTTP_SCHEMEorAPP_FORCE_HTTPSis set. EnvironmentSettingsnow includeshttp_schemeandforce_httpsproperties that are automatically populated from environment variables.- Request scheme is now automatically configured based on environment settings, to simplify correct URL generation in proxied environments (e.g. OIDC redirections).
- Improve the
generate_secretto usesecrets.token_urlsafe(48)by default. - Improve
OpenIDSettings,CookieAuthentication, andAntiForgeryHandlerto handle secrets using theSecretclass fromessentials.secrets. Passing secrets asstrdirectly issues a deprecation warning and won't be supported in2.5.xor2.6.x.
Several issues were reported by @ockan, including issues in the documentation.
v2.4.3
- Add Python
3.14and remove3.9from the build matrix. - Drop support for Python
3.9(it reached EOL in October 2025). - Fix bug #605, that prevented the
JWTBearerAuthenticationscheme from being documented properly in OpenAPI Specification files. - Deprecate the
auth_modeparameter for theJWTBearerAuthenticationconstructor, and add a newschemeparameter that will replace it. - Improve the code to not require returning an empty
Identity()object in authentication handlers when authentication is not successful. - Upgrade
GuardPostto1.0.4, as it includes improved features and a built-in strategy to protect against brute-force authentication attempts (opt-in). - Upgrade
pydanticto a version supported by Python 3.14. - Remove support for Pydantic v1 in Python 3.14. Support for Pydantic v1 will be removed soon.
- Fix regression causing an import error when trying to use OpenAPI features without installing dependencies for JWT validation #606.
- Add verification step to the main workflow to verify that basic functionalities work without optional dependencies.
v2.4.2
- Add significant improvements to authentication and authorization features.
- Add built-in support for API Key Authentication.
- Add built-in support for Basic Authentication.
- Add built-in support for JWT Bearer authentication validating JWTs signed using symmetric encryption (previously the built-in classes only supported using asymmetric encryption to validate JWTs).
- Improve the
JWTBearerAuthenticationclass to support validating JWTs with both asymmetric and symmetric encryption. - Improve the code that generates OpenAPI Documentation to automatically include security
securitySchemesandsecuritysections byAuthenticationhandlers configured in the application. The feature can be extended with user-defined authentication handlers. - Improve the
@authdecorator to support specifying sufficient roles to authorize requests (@auth(roles=["admin"])). - Upgrade
GuardPostto1.0.3, as it includes improved features to handle roles and JWT validation using symmetric encryption. - Upgrade
essentialsto1.1.8as it includes aSecretclass to handle secrets in code. This class is used for safe handling of secrets in API Keys, Basic Credentials, and symmetric encryption for JWT Bearer authentication. It will be used in the future in all circumstances where BlackSheep code needs user-defined secrets. - Remove the code that required four env variables to be configured for the OTLP exporter (in the
use_open_telemetry_otlpfunction), because it didn't cover legitimate use cases supported by the OpenTelemetry SDK. It is responsibility of the developers to configure env variables according to their preference for OTLP. - The framework has been tested for
cryptography>=46.0.0and therefore update the dependency tocryptography>=45.0.2,<47.0.0.
"""
This example shows a basic example of API Key and Basic Authentication in BlackSheep.
uvicorn apitest:app --port 44777
curl http://127.0.0.1:44777 -H "X-API-Key: Foo"
"""
from dataclasses import dataclass
from essentials.secrets import Secret
from openapidocs.v3 import Info
from blacksheep import Application, get
from blacksheep.server.authentication.apikey import APIKey, APIKeyAuthentication
from blacksheep.server.authentication.basic import BasicAuthentication, BasicCredentials
from blacksheep.server.authorization import auth, allow_anonymous
from blacksheep.server.openapi.v3 import OpenAPIHandler
app = Application()
app.use_authentication().add(
APIKeyAuthentication(
APIKey(
secret=Secret("$API_SECRET"), # Obtained from API_SECRET env var
roles=["user"],
),
param_name="X-API-Key",
)
).add(
BasicAuthentication(
BasicCredentials(
username="admin",
password=Secret("$ADMIN_PASSWORD"), # Obtained from ADMIN_PASSWORD env var
roles=["admin"],
)
)
)
app.use_authorization()
# See the generated docs and how they include security sections
docs = OpenAPIHandler(info=Info(title="Example API", version="0.0.1"))
docs.bind_app(app)
@dataclass
class Foo:
foo: str
@allow_anonymous()
@get("/")
async def get_foo() -> Foo:
return Foo("Hello!")
@auth()
@get("/claims")
async def get_claims(request):
return request.user.claims
@auth(roles=["admin"], authentication_schemes=["Basic"])
@get("/for-admins")
async def for_admins_only(request):
return request.user.claims
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, port=44777)Tip
For a tutorial on OTLP and how it can be used with BlackSheep and an
OpenTelemetry Collector self-hosted in Kubernetes, see:
https://robertoprevato.github.io/K8sStudies/k3s/monitoring/
This tutorial explains how to self-host a monitoring stack in a single node in Kubernetes,
but the BlackSheep OTLP example is applicable to Grafana Cloud, too.
v2.4.1
- Correct bug in controller inheritance that would prevent argument types and return type hints from working as expected (#594). By @martinmkhitaryan.
- Improve the code responsible of mapping input request parameters into instances of desired types. Change the inner workings of
blacksheep.server.bindingsto make the code more configurable and easier to maintain. - Add support for
StrEnumandIntEnumto binders for request handlers' parameters. See #588. Enums can be mapped by key and by value. The class that matchesStrEnummake case sensitive checks; override the__missing__method of your user-defined enums to support case insensitive checks; or define a customStrEnumConverterclass. This feature requiresPython >= 3.11. - Add support for
Literalto binders for request handlers' parameters. See #588. String literals are case sensitive by default. - Minor breaking change. Remove the dependency on
python-dateutil(#544), which was always used to parse inputdatetimeparameters. The datetime parsing logic is replaced with a function that only supports the most common ISO formats:%Y-%m-%dT%H:%M:%S.%f,%Y-%m-%dT%H:%M:%S,%Y-%m-%d, and is much more performant for such formats. The new code API offers a simple way to keep usingpython-dateutilfor those who desire doing so. - Fix erroneous assumption when parsing a request body declared as
bytes. When the declared requested input body isbytes, the framework has been corrected to not require URL-safe base64 encoded data, and to read the input body as-is. - Add support for defining
convertfunctions in customBoundValueclasses that are used to convert Python objects from parsed JSON into more specific classes. - Correct bug that prevented request body input to be mapped properly to a
listusing the default logic. - Upgrade
pytest-asyncioto the latest version. Fix #596. - Fix a Cython segmentation fault happening when the user defines an exception handler with a wrong signature (#592), or that contains a bug and causes exceptions itself. Replace the Application
exception_handlersdictionary with a user defined dictionary that validates values, and change a piece of code that causes a recursive error when an exception handler itself is buggy. - Add support for specifying the status code in view functions (#591).
- Fix
licensefield inpyproject.toml.