From e64d8a7e4d3cc869f321eb6099590f6a0b88e031 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Tue, 24 Feb 2026 21:55:41 +0100 Subject: [PATCH 1/2] Add multiple Content-Type examples --- README.md | 1 + content-types/README.md | 92 ++++++++++++++++++++++++++++++++++ content-types/requirements.txt | 3 ++ content-types/server.py | 35 +++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 content-types/README.md create mode 100644 content-types/requirements.txt create mode 100644 content-types/server.py diff --git a/README.md b/README.md index c44c395..a498e5a 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,4 @@ Examples for BlackSheep. | [./otel](./otel/) | Shows how to use [OpenTelemetry](https://opentelemetry.io/) integration with [Grafana](https://grafana.com/), and with [Azure Application Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview). | | [./multipart](./multipart/) | Shows how to define custom classes to handle multipart/form-data input. | | [./a2wsgi](./a2wsgi/) | Shows an example to use [`a2wsgi`](https://github.com/abersheeran/a2wsgi) with [`Gunicorn`](https://gunicorn.org/) or [`uWSGI`](https://uwsgi-docs.readthedocs.io/en/latest/). | +| [./content-types](./content-types/) | Shows how multiple content-types can be used and documented since version `2.6.2`. | diff --git a/content-types/README.md b/content-types/README.md new file mode 100644 index 0000000..d71db82 --- /dev/null +++ b/content-types/README.md @@ -0,0 +1,92 @@ +# Content Types Example + +This example demonstrates how to handle requests with multiple content types in +[BlackSheep](https://github.com/Neoteroi/BlackSheep) (requires **BlackSheep >= 2.6.2**). + +## Overview + +The `POST /foo` endpoint accepts request bodies in any of the following formats: + +- `application/json` +- `application/xml` +- `text/xml` + +This is achieved using a union type annotation combining `FromJSON` and `FromXML` +binders: + +```python +@post("/foo") +async def create_foo(data: FromJSON[CreateFooInput] | FromXML[CreateFooInput]): + return Foo(data.foo) +``` + +BlackSheep inspects the `Content-Type` header of the incoming request and +automatically deserializes the body into the appropriate type, so the handler +receives a plain `CreateFooInput` instance regardless of which format the client +used. + +## Running the example + +```bash +pip install blacksheep>=2.6.2 uvicorn +python server.py +``` + +The OpenAPI documentation is available at `http://localhost:8000/docs` once the +server is running. + +## Generated OpenAPI specification + +```yaml +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/Foo' + operationId: get_foo + post: + responses: {} + operationId: create_foo + parameters: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateFooInput' + application/xml: + schema: + $ref: '#/components/schemas/CreateFooInput' + text/xml: + schema: + $ref: '#/components/schemas/CreateFooInput' + required: true +servers: [] +components: + schemas: + Foo: + type: object + required: + - foo + properties: + foo: + type: string + nullable: false + CreateFooInput: + type: object + required: + - foo + properties: + foo: + type: string + nullable: false +tags: [] +``` diff --git a/content-types/requirements.txt b/content-types/requirements.txt new file mode 100644 index 0000000..b0e86d9 --- /dev/null +++ b/content-types/requirements.txt @@ -0,0 +1,3 @@ +blacksheep>=2.6.2 +defusedxml==0.7.1 +uvicorn==0.41.0 diff --git a/content-types/server.py b/content-types/server.py new file mode 100644 index 0000000..051dbf0 --- /dev/null +++ b/content-types/server.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass + +from blacksheep import Application, get, post, FromJSON, FromXML +from blacksheep.server.openapi.v3 import OpenAPIHandler +from openapidocs.v3 import Info + +app = Application() + +docs = OpenAPIHandler(info=Info(title="Example API", version="0.0.1")) +docs.bind_app(app) + + +@dataclass +class Foo: + foo: str + + +@dataclass +class CreateFooInput: + foo: str + + +@get("/foo") +async def get_foo() -> Foo: + return Foo("Hello!") + + +@post("/foo") +async def create_foo(data: FromJSON[CreateFooInput] | FromXML[CreateFooInput]): + return Foo(data.foo) + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app) From 94298873b90db9f415682b7a4a6feb8ad5693974 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Tue, 24 Feb 2026 21:56:58 +0100 Subject: [PATCH 2/2] Update README.md --- content-types/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content-types/README.md b/content-types/README.md index d71db82..e866fbf 100644 --- a/content-types/README.md +++ b/content-types/README.md @@ -3,6 +3,8 @@ This example demonstrates how to handle requests with multiple content types in [BlackSheep](https://github.com/Neoteroi/BlackSheep) (requires **BlackSheep >= 2.6.2**). +For context, see https://github.com/Neoteroi/BlackSheep/pull/669 and https://github.com/Neoteroi/BlackSheep/issues/514. + ## Overview The `POST /foo` endpoint accepts request bodies in any of the following formats: