Skip to content

Releases: Neoteroi/BlackSheep

v2.6.2

25 Feb 15:37

Choose a tag to compare

  • Fix regression that broke compatibility with Starlette mounts #668.
    Add integration tests to verify support for Starlette and Piccolo-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
    with PYTHONOPTIMIZE=2 (or -OO) where docstrings are stripped and cannot be
    used to enrich OpenAPI Documentation automatically.

    • Add save_spec(destination) method to OpenAPIHandler: writes both the JSON
      and YAML variants of the current in-memory spec to disk.
    • Add spec_file parameter to OpenAPIHandler: when set, build_docs loads 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_FILE environment 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 UserWarning when PYTHONOPTIMIZE >= 2 and 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 support for PKCE (Proof Key for Code Exchange, RFC 7636) to the OpenID Connect implementation.

    • New use_pkce: bool setting on OpenIDSettings. When enabled, a code_verifier
      is generated per sign-in request, stored inside the signed state parameter, and
      the corresponding code_challenge (S256) is sent to the authorization endpoint.
      The verifier is passed automatically in the token exchange request.
    • New response_mode: str | None setting on OpenIDSettings, with automatic
      detection via get_response_mode():
      • PKCE with client_secret (confidential client, recommended for web apps) →
        "form_post" — the authorization code arrives via POST form 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 a GET redirect 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.
    • The callback route is registered as GET or POST automatically based on the
      effective response_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
        ),
    )
  • Fix #514, reported by @jesseandringa — add support for
    multiple request body formats on a single endpoint.

    • New MultiFormatBodyBinder: holds an ordered list of inner BodyBinders and
      dispatches get_value to the first whose matches_content_type returns True.
      Returns HTTP 415 Unsupported Media Type when the binder is required and no
      inner binder matches the request's Content-Type.

    • Union annotation syntax — the normalization layer detects a union of
      body-binder BoundValue types 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] and XMLBinder: parse application/xml / text/xml
      request bodies using defusedxml,
      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 with pip 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 under requestBody.content,
      all referencing the same schema.

v2.6.1

23 Feb 06:26

Choose a tag to compare

  • Fix missing escaping in multipart/form-data filenames and content-disposition headers.

  • Fix #193, adding support for a2wsgi. Reported by @jordemort.

  • raw_path is optional in ASGI specification. If not present, now the instantiate_request method automatically obtains it from path and sets it in the scope.

  • Static files are now served with Content-Length header instead of Transfer-Encoding: chunked when file size is known. This improves compatibility with WSGI servers via a2wsgi.

  • Automatically run the Application start logic if the __call__ method is called with http or websocket messages. This is useful when lifespan events are not supported, like when using WSGI.

  • 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

30 Jan 21:07
9accc9b

Choose a tag to compare

  • Fix problem in workflow and the PyPy distribution wheels.

v2.5.0

29 Jan 21:29
ebd7022

Choose a tag to compare

  • 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 HTTP2Connection class using the h2 library for HTTP/2 protocol handling.
  • Add new HTTP11Connection class using the h11 library for consistent HTTP/1.1 handling.
  • Both connection types use asyncio.open_connection streams for a unified architecture.
  • HTTP/2 connections support stream multiplexing, allowing multiple concurrent requests over a single TCP connection.
  • Add http2 parameter to ClientSession (defaults to True) to control HTTP/2 usage.
  • Protocol detection is performed once per host and cached for efficiency.
  • Add h2>=4.0.0,<5.0.0 as a new dependency.
  • Refactor HTTP/1.1 client implementation to use the h11 library instead of custom request writing methods from blacksheep.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 HTTP11Connection class replaces the asyncio.Protocol-based approach with a streams-based implementation.
  • Remove the legacy ClientConnection class.
  • 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 httptools for anything.
  • Correct bugs in the code serving static files: HTTP 304 should not be returned for Range requests, and handling of ranges.

v2.4.6

13 Jan 17:28

Choose a tag to compare

  • Fix CRLF injection vulnerability in the BlackSheep HTTP Client, reported by Jinho Ju (@tr4ce-ju).
  • Add a SECURITY.md file.
  • Fix #646.
  • Modify the Cookie repr to 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

15 Nov 20:34
1fcd119

Choose a tag to compare

  • Fix regression #636.

v2.4.4

15 Nov 18:12
9c6d06b

Choose a tag to compare

  • Add support for annotated types in OpenAPIHandler return types, by @tyzhnenko. This feature is important to support automatic generation of OpenAPI Documentation when returning instances of Response (e.g. Annotated[Response, ProductDetails]).
  • Introduce MiddlewareList and MiddlewareCategory to 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 the Application and to the ClientSession classes.
  • Add support for list[str] as a value for no-cache and private directives 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 NotFound exception handler).
  • Change the text of Bad Request response body when the input from the client causes a TypeError when 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 arm64 architecture for Linux and Windows, and use cibuildwheel for 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_Address instead of PaginatedSetOfAddress, Dict_str_int instead of DictOfstrAndint). This can be controlled via the programming_names parameter in DefaultSerializer or the APP_OPENAPI_PROGRAMMING_NAMES environment variable, setting it to a truthy value ('1' or 'true').
  • Make EnvironmentSettings read-only, refactor to not use dataclass.
  • Attach EnvironmentSettings to the Application object 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 HTTPSchemeMiddleware to set request scheme when running behind reverse proxies or load balancers with TLS termination. See #631.
  • Add support for APP_HTTP_SCHEME environment variable to explicitly set the request scheme to http or https.
  • Add support for APP_FORCE_HTTPS environment 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 either APP_HTTP_SCHEME or APP_FORCE_HTTPS is set.
  • EnvironmentSettings now includes http_scheme and force_https properties 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_secret to use secrets.token_urlsafe(48) by default.
  • Improve OpenIDSettings, CookieAuthentication, and AntiForgeryHandler to handle secrets using the Secret class from essentials.secrets. Passing secrets as str directly issues a deprecation warning and won't be supported in 2.5.x or 2.6.x.

Several issues were reported by @ockan, including issues in the documentation.

v2.4.3

19 Oct 07:23
8f82962

Choose a tag to compare

  • Add Python 3.14 and remove 3.9 from the build matrix.
  • Drop support for Python 3.9 (it reached EOL in October 2025).
  • Fix bug #605, that prevented the JWTBearerAuthentication scheme from being documented properly in OpenAPI Specification files.
  • Deprecate the auth_mode parameter for the JWTBearerAuthentication constructor, and add a new scheme parameter that will replace it.
  • Improve the code to not require returning an empty Identity() object in authentication handlers when authentication is not successful.
  • Upgrade GuardPost to 1.0.4, as it includes improved features and a built-in strategy to protect against brute-force authentication attempts (opt-in).
  • Upgrade pydantic to 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

04 Oct 08:16
433ae98

Choose a tag to compare

  • 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 JWTBearerAuthentication class to support validating JWTs with both asymmetric and symmetric encryption.
  • Improve the code that generates OpenAPI Documentation to automatically include security securitySchemes and security sections by Authentication handlers configured in the application. The feature can be extended with user-defined authentication handlers.
  • Improve the @auth decorator to support specifying sufficient roles to authorize requests (@auth(roles=["admin"])).
  • Upgrade GuardPost to 1.0.3, as it includes improved features to handle roles and JWT validation using symmetric encryption.
  • Upgrade essentials to 1.1.8 as it includes a Secret class 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_otlp function), 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.0 and therefore update the dependency to cryptography>=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

28 Sep 15:33
d07e40f

Choose a tag to compare

  • 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.bindings to make the code more configurable and easier to maintain.
  • Add support for StrEnum and IntEnum to binders for request handlers' parameters. See #588. Enums can be mapped by key and by value. The class that matches StrEnum make case sensitive checks; override the __missing__ method of your user-defined enums to support case insensitive checks; or define a custom StrEnumConverter class. This feature requires Python >= 3.11.
  • Add support for Literal to 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 input datetime parameters. 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 using python-dateutil for those who desire doing so.
  • Fix erroneous assumption when parsing a request body declared as bytes. When the declared requested input body is bytes, 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 convert functions in custom BoundValue classes 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 list using the default logic.
  • Upgrade pytest-asyncio to 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_handlers dictionary 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 license field in pyproject.toml.