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.6.0
- Add support for handling
FromText,FromFilesin OpenAPI Documentation (#546). - Significantly improve support for
multipart/form-dataforms with memory-efficient streaming and file handling:- Add
Request.multipart_stream()method for true streaming parsing of multipart data without buffering entire request body in memory, ideal for handling large file uploads and large text fields. - Refactor
Request.form()andRequest.multipart()to useSpooledTemporaryFilefor memory-efficient file handling: small files (<1MB) are kept in memory, larger files automatically spill to temporary disk files. - Add
Request.dispose()method to properly clean upSpooledTemporaryFileresources and prevent resource leaks when handling file uploads. - Add
FileBufferclass wrappingSpooledTemporaryFileto provide a clean API for uploaded files withread(),seek(),close(), andsave_to()methods. - Add
FormPart.stream()async generator method to stream part data in chunks. - Add
save_to()method toFormPart,FileBuffer, andStreamedFormPartfor saving uploaded data to disk with automatic security validation viaensure_in_cwd()to prevent directory traversal attacks. - Add
FormPart.field()class method as a convenience for creating form parts from string values without manual encoding (acceptsname,value, optionalcontent_typeandcharset). - Add
FormPart.from_file()class method as a convenience for creating file upload parts that automatically opens files, detects MIME types from file extensions, and handles both file paths and pre-opened file handles. FormPartinstances provide better memory management and a cleaner API.- The framework automatically calls
Request.dispose()at the end of each request-response cycle to clean up file resources.
- Add
- Fix #501; accept to overwrite default headers when using
TestClient, contributed by @ticapix. - Modify the
MultiPartFormDatato inheritStreamedContentand remove the immediate call towrite_multipart_form_data()which loaded everything into memory.
Note
Support for efficient handling of multipart/form-data was previously neglected as
BlackSheep focused primarily on JSON-based Web APIs, Web Apps serving HTML and static
files, and raw request body streaming for large uploads.
This release improves support for this format, adding support for memory-efficient
handling of multipart/form-data, both when parsing on the server-side and
when writing payloads for the BlackSheep HTTP Client, through the MultiPartFormData
content class.
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.