From V1 to V2
This page describes the most relevant differences between version 1 and version 2 of the web framework. The most relevant changes are:
- Improved project templates and the
blacksheep-clito bootstrap new projects. - Automatic import of
routesandcontrollers. - Added functions to support the notion of application environment.
- Improved dependency injection, with support for alternatives to
rodi. - Improved server side rendering, with support for alternatives to
Jinja2. - Added support for dependency injection in authentication and authorization handlers.
- Removed the
@app.routedecorator and moved it to theRouterclass. - Improved the
Routerclass to support sub-routers and filters. - Improved the OIDC features to support storing tokens in the HTML5 Storage API instead of cookies.
- Some classes have been renamed to better follow Python naming conventions.
The full list of changes is at the bottom of this page. It includes changes that were applied to version 1 of the framework, too.
BlackSheep-CLI¶
The second version of the framework features improved project templates, with a dedicated CLI for project scaffolding. For more information on the CLI, read More about the CLI.

The improved project templates also include a strategy to validate settings
using Pydantic.
Automatic import of routes and controllers¶
The second version of the framework includes features to considerably reduce code verbosity when defining routes and controllers.
The framework now exposes methods of a default singleton Router instance, to
be used to register routes independently from application instantiation. This
enables a much cleaner code API, and consistent with the existing API to
register controllers.
from blacksheep import get, post
@get("/api/examples")
async def get_examples() -> list[str]:
...
@post("/api/examples")
async def add_example(self, example: str):
...
For more information on the above, read Using the default router and other routers.
All modules inside routes and controllers packages are imported
automatically in v2. Automatic import works relative to where a BlackSheep
application is instantiated. In the structure described below, the modules in
app.controllers and app.routes namespace are imported automatically when an
application is instantiated inside app.main.
app/
├── __init__.py
├── controllers
│ ├── __init__.py
│ ├── home.py
│ └── example.py
├── routes
│ ├── __init__.py
│ └── example.py
└──main.py
The difference in code verbosity is considerable, because previously defining routes and controllers explicitly was not sufficient to have them registered in applications.
Notion of application environment¶
The namespace blacksheep.server.env provides an abstraction layer to support
the notion of application environment. It provides functions that can be used
to apply logic depending on whether the application is running for local
development, or a different kind of environment (e.g. dev, test, prod).
These functions use the environment variable APP_ENV to determine the type
of environment, defaulting to production if such variable is missing.
from blacksheep.server.env import is_development, is_production
# is_development returns true if APP_ENV (lower) is in {"local", "dev", "development"}
# is_production returns true if APP_ENV (lower) is missing or in {"prod", "production"}
Changes to dependency injection¶
In v2, rodi and BlackSheep have been modified to enable alternative
implementations of dependency injection. rodi now defines a
ContainerProtocol with a basic API to register and resolve dependencies, and
BlackSheep relies on that protocol instead of its specific implementation in
rodi.
For more information, read the dedicated part in the Dependency Injection page.
Changes to server side rendering¶
BlackSheep v2 has been modified to not be strictly reliant on Jinja2 for
template rendering. To achieve this, two new namespaces have been added:
blacksheep.server.rendering.abc, defining an abstractRendererclass,blacksheep.settings.html, defining a code API to control renderer settings
The code API of the view and view_async functions in the
blacksheep.server.responses namespace has been improved, using the renderer
configured in blacksheep.settings.html.
The following examples show how a view can be rendered, having a template
defined at the path views/home.jinja:
from blacksheep import Application, get
from blacksheep.server.responses import view
app = Application()
@get("/")
def home():
return view("home", {"example": "Hello", "foo": "World"})
from blacksheep import Application
from blacksheep.server.templating import use_templates
from jinja2 import PackageLoader
app = Application()
get = app.router.get
view = use_templates(app, loader=PackageLoader("app", "views"))
@get("/")
def home():
return view("home", {"example": "Hello", "foo": "World"})
Template:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>{{example}}</h1>
<p>{{foo}}</p>
</body>
</html>
For more information, read the updated page describing Server Side Rendering.
Improvements to authentication and authorization handlers¶
GuardPost has been modified to support the new ContainerProtocol in rodi,
and authentication and authorization handlers now support dependency injection.
Removed the app.route method¶
The Application class was modified to remove the route method, which is now
available in the Router class. The reason for this change it to make the
code API consistent between methods used to register request handlers.
The route method of the singleton default Router instance is also exposed
by blacksheep package like the other methods to register request handlers.
from blacksheep import Application, route
app = Application()
@route("/")
def home():
return "Example"
from blacksheep import Application
app = Application()
@app.route("/")
def home():
return "Example"
Improvements to the Router class¶
The Router class has been improved to support sub-routers and filters.
For more information, read Using sub-routers and filters.
Improvements to OIDC support¶
The functions that implement OpenID Connect (OIDC) support have been improved to support storing tokens (id_token, access_token, refresh_token) in any kind of store, and with built-in support for the HTML5 Storage API.
Examples in GitHub
Refer to the OIDC examples
The following partial example shows how to use the use_openid_connect
function to configure a web app to:
- use OpenID Connect with Entra ID to implement authentication
- store
id_token,access_token, andrefresh_tokenusing the HTML5 Storage API - configure the back-end API to use
JWT Bearerauthentication (clients must send requests withAuthorization: Bearer <JWT>headers)
"""
This example shows how to configure an OpenID Connect integration having tokens
exchanged with the client using the HTML5 Storage API, instead of response cookies.
This scenario enables better reusability of web APIs.
See how the id_token is used in ./static/index.html to authenticate following requests
('Authorization: Bearer ***' headers), and how the refresh token endpoint can be used
to obtain fresh tokens.
"""
import uvicorn
from blacksheep.server.application import Application
from blacksheep.server.authentication.jwt import JWTBearerAuthentication
from blacksheep.server.authentication.oidc import (
JWTOpenIDTokensHandler,
OpenIDSettings,
use_openid_connect,
)
from dotenv import load_dotenv
from common.routes import register_routes
from common.secrets import Secrets
load_dotenv()
secrets = Secrets.from_env()
app = Application(show_error_details=True)
AUTHORITY = (
"https://login.microsoftonline.com/b62b317a-19c2-40c0-8650-2d9672324ac4/v2.0"
)
CLIENT_ID = "499adb65-5e26-459e-bc35-b3e1b5f71a9d"
use_openid_connect(
app,
OpenIDSettings(
authority=AUTHORITY,
client_id=CLIENT_ID,
client_secret=secrets.aad_client_secret,
scope=(
"openid profile offline_access email "
"api://65d21481-4f1a-4731-9508-ad965cb4d59f/example"
),
),
auth_handler=JWTOpenIDTokensHandler(
JWTBearerAuthentication(
authority=AUTHORITY,
valid_audiences=[CLIENT_ID],
),
),
)
register_routes(app, static_home=True)
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=5000, log_level="debug")
Changes to follow naming conventions¶
Some classes have been renamed to better follow Python naming conventions. For
example the aliases 'HtmlContent' and 'JsonContent' that were kept for backward
compatibility in v1, as alternative names for HTMLContent and
JSONContent, were removed in v2.
List of changes¶
The full list of changes in alpha versions released for v2:
- Renames the
pluginsnamespace tosettings. - Upgrades
rodito v2, which includes improvements. - Adds support for alternative implementation of containers for dependency
injection, using the new
ContainerProtocolinrodi. - Upgrades
guardpostto v1, which includes support for dependency injection in authentication handlers and authorization requirements. - Adds support for Binders instantiated using dependency injection. However, binders are still instantiated once per request handler and are still singletons.
- Adds a method to make the
Requestobject accessible through dependency injection (register_http_context). This is not a recommended practice, but it can be desired in some circumstances. - Removes the direct dependency on
Jinja2and adds support for alternative ways to achieve Server Side Rendering (SSR) of HTML; however,Jinja2is still the default library if the user doesn´t specify how HTML should be rendered. - Adds options to control
Jinja2settings through environment variables. - Removes the deprecated
ServeFilesOptionsclass. - Improves how custom binders can be defined, reducing code verbosity for custom types. This is an important feature to implement common validation of common parameters across multiple endpoints.
- Adds support for binder types defining OpenAPI Specification for their parameters.
- Fixes bug #305 (
ClientSession ssl=Falsenot working as intended). - Refactors the classes for OpenID Connect integration to support alternative ways to share tokens with clients, and JWT Bearer token authentication out of the box, in alternative to cookie-based authentication.
- It adds built-in support for storing tokens (
id_token,access_token, andrefresh_token) using the HTML5 Storage API (supportinglocalStorageandsessionStorage). Refresh tokens, if present, are automatically protected to prevent leaking. See the OIDC examples for more information. - Renames
blacksheep.server.authentication.oidc.TokensStoretoTokensStore. - Removes the
tokens_storeparameter from theuse_openid_connectmethod; it is still available as an optional parameter of the two built-in classes used to handle tokens. - Replaces
request.identitywithrequest.user. The propertyidentityis still kept for backward compatibility, but it will be removed inv3. - Removes 'HtmlContent' and 'JsonContent' that were kept as alternative names
for
HTMLContentandJSONContent. - Refactors the
ClientSessionto own by default a connections pool, if none is specified for it. The connections pool is automatically disposed of when the client is exited, if it was created for the client. - Makes the
ClientSessionmore user-friendly, supporting headers defined asdict[str, str]orlist[tuple[str, str]]. - Improves the type annotations of the
ClientSession. - Corrects a bug in the
ClientSessionthat would cause a task lock when the connection is lost while downloading files. - Corrects a bug in the
ClientSessioncausingset-cookieheaders to not be properly handled during redirects. - Renames the client connection pool classes to remove the prefix "Client".
- Corrects bug of the
Requestclass that would prevent settingurlusing a string instead of an instance ofURL. - Corrects bug of the
Requestclass that prevented thehostproperty from working properly after updatingurl(causingfollow_redirectsto not work properly inClientSession. - Upgrades the
essentials-openapidependency, fixing #316. - Corrects the
Requestclass to not generate more than oneCookieheader when multiple cookies are set, to respect the specification. - Adds
@app.lifespanto support registering objects that must be initialized at application start, and disposed of at application shutdown. The solution supports registering as many objects as desired. - Adds features to handle
cache-controlresponse headers: a decorator for request handlers and a middleware to set a default value for allGETrequests resulting in responses with status200. - Adds features to control
cache-controlheader for the default document (e.g.index.html) when serving static files; see issue 297. - Fixes bug in
sessionsthat prevented updating the session data when using thesetand__delitem__methods; scottrutherford's contribution.
@app.lifespan example:
from blacksheep import Application
from blacksheep.client.session import ClientSession
app = Application()
@app.lifespan
async def register_http_client():
async with ClientSession() as client:
print("HTTP client created and registered as singleton")
app.services.register(ClientSession, instance=client)
yield
print("HTTP client disposed of")
@router.get("/")
async def home(http_client: ClientSession):
print(http_client)
return {"ok": True, "client_instance_id": id(http_client)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=44777, log_level="debug", lifespan="on")
- Adds support for user-defined filters for server routes (
RouteFilterclass). - Adds built-in support for routing based on request headers.
- Adds built-in support for routing based on request query parameters.
- Adds built-in support for routing based on host header value.
- Adds a
query.setterto theRequestclass, to set queries usingdict[str, str | sequence[str]]as input. - The functions registered to application events don't need anymore to define
the
appargument (they can be functions without any argument). - Adds `Cache-Control: no-cache, no-store' to all responses generated for the OpenID Connect flow.
- Adds support for automatic import of modules defined under
controllersandroutespackages, relative to where theApplicationclass is instantiated. Fix #334. - Adds a
GzipMiddlewarethat can be used to enablegzipcompression, using the built-in module. Contributed by @tyzhnenko - Improves how tags are generated for OpenAPI Documentation: adds the
possibility to document tags explicitly and control their order, otherwise
sorts them alphabetically by default, when using controllers or specifying
tags for routes. Contributed by @tyzhnenko
- Adds a strategy to control features depending on application environment:
is_development,is_productiondepending onAPP_ENVenvironment variable. For more information, see Defining application environment. - Makes the client
ConnectionPoolsa context manager, its__exit__method closes all itsTCP-IPconnections. - Improves exception handling so it is possible to specify how specific types
of
HTTPExceptionmust be handled (#342). - Improves the error message when a list of objects if expected for an incoming request body, and a non-list value is received (#341).
- Replaces
chardetandcchardetwithcharset-normalizer. Contributed by @mementum. - Upgrades all dependencies.
- Adopts
pyproject.toml. - Fixes bug in CORS handling when multiple origins are allowed.
- Adds a
Vary: Originresponse header for CORS requests when the value ofAccess-Control-Allow-Originheader is a specific URL. - Adds algorithms parameter to JWTBearerAuthentication constructor, by @tyzhnenko.
- Improves the code API to define security definitions in OpenAPI docs, by @tyzhnenko.
- Applies a correction to the auto-import function for routes and controllers.
- Add support for
StreamedContentwith specific content length; fixing #374 both on the client and the server side. - Fix #373, about missing
closing ASGI message when an async generator does not yield a closing empty
bytes sequence (
b""). - Make version dynamic in
pyproject.toml, simplifying how the version can be queried at runtime (see #362). - Fix #372. Use the ASGI
scope
root_pathwhen possible, asbase_path. - Fix #371. Returns status 403 Forbidden when the user is authenticated but not authorized to perform an action.
- Fixes
TypeErrorwhen writing a request without a host header. - Add support for
Pydanticv2: meaning feature parity with support for Pydantic v1 (generating OpenAPI Documentation). - Add support for
Uniontypes in sub-properties of request handlers input and output types, for generating OpenAPI Documentation, both using simple classes and Pydantic #389 - Resolves bug in
2.0a10caused by incompatibility issue withCython 3. - Pins
Cythonto3.0.2in the build job. - Fixes bug #394, causing the
Contentmax body size to be 2147483647. (C int max value). Reported and fixed by @thomafred. - Add support for
.jinjaextension by @thearchitector. - Makes the
.jinjaextension default for Jinja templates. - Adds support for Python 3.12, by @bymoye
- Replaces
pkg_resourceswithimportlib.resourcesfor all supported Python versions except for3.8. - Runs tests against Pydantic
2.4.2instead of Pydantic2.0to check support for Pydantic v2. - Adds
.webpand.webmto the list of extensions of files that are served by default.
Last modified on: 2023-12-18 17:52:09