faststream_fastapi 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. faststream_fastapi-1.0.0/LICENSE +21 -0
  2. faststream_fastapi-1.0.0/PKG-INFO +151 -0
  3. faststream_fastapi-1.0.0/README.md +111 -0
  4. faststream_fastapi-1.0.0/pyproject.toml +139 -0
  5. faststream_fastapi-1.0.0/src/faststream_fastapi/__about__.py +5 -0
  6. faststream_fastapi-1.0.0/src/faststream_fastapi/__init__.py +11 -0
  7. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/__init__.py +0 -0
  8. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/asyncapi_router.py +115 -0
  9. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/background_middleware.py +22 -0
  10. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/config.py +23 -0
  11. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fastapi_compat.py +152 -0
  12. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/__init__.py +0 -0
  13. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/_compat.py +6 -0
  14. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/application.py +3 -0
  15. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/broker.py +4 -0
  16. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/constants.py +3 -0
  17. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/context.py +8 -0
  18. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/di.py +3 -0
  19. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/logger.py +3 -0
  20. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/get_dependant.py +182 -0
  21. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/logger.py +11 -0
  22. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/replace_context.py +119 -0
  23. faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/wrap_callable_to_fastapi_compatible.py +180 -0
  24. faststream_fastapi-1.0.0/src/faststream_fastapi/asyncapi_config.py +71 -0
  25. faststream_fastapi-1.0.0/src/faststream_fastapi/context.py +31 -0
  26. faststream_fastapi-1.0.0/src/faststream_fastapi/faststream_api.py +165 -0
  27. faststream_fastapi-1.0.0/src/faststream_fastapi/stream_message.py +25 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Kirpichnikov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: faststream_fastapi
3
+ Version: 1.0.0
4
+ Summary: FastAPI integration for FastStream
5
+ Keywords: rabbitmq,kafka,nats,redis,mqtt,asyncapi,framework,message brokers,fastapi
6
+ Author: Ivan Kirpichnikov
7
+ Author-email: Ivan Kirpichnikov <mmssvvvv570@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: Implementation :: CPython
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Topic :: Software Development
25
+ Classifier: Topic :: System :: Networking
26
+ Classifier: Topic :: System :: Distributed Computing
27
+ Classifier: Typing :: Typed
28
+ Classifier: Intended Audience :: Developers
29
+ Classifier: Intended Audience :: Information Technology
30
+ Classifier: Intended Audience :: System Administrators
31
+ Classifier: Environment :: Web Environment
32
+ Classifier: Framework :: AsyncIO
33
+ Classifier: Framework :: Pydantic
34
+ Classifier: Framework :: Pydantic :: 1
35
+ Classifier: Framework :: Pydantic :: 2
36
+ Requires-Dist: fastapi<1.0.0
37
+ Requires-Dist: faststream>=0.7.0
38
+ Requires-Python: >=3.10
39
+ Description-Content-Type: text/markdown
40
+
41
+ # FastAPI Plugin for FastStream
42
+
43
+ A plugin that allows you to use **Depends** and **FastAPI** other objects in the **FastStream**
44
+
45
+ # Features
46
+
47
+ ### Use FastAPI Dependency Injection
48
+ In **FastStream** handlers, it will be possible to use the familiar DI from **FastAPI**
49
+ ```py
50
+ from fastapi import Path, Body, Header, Depends
51
+
52
+ class BodyModel(BaseModel):
53
+ field: int
54
+
55
+ @broker.subscriber("subject.{num}")
56
+ async def subscriber_handler(
57
+ num: Annotated[int, Path()],
58
+ body: Annotated[BodyModel, Body()],
59
+ x_user_id: Annotated[int, Header()],
60
+ my_dep: Annotated[int, Depends(int)],
61
+ ) -> None:
62
+ ...
63
+ ```
64
+
65
+ ### Сan use FastAPI dependency overrides
66
+ ```py
67
+ fastapi = FastAPI()
68
+ fastapi.dependency_overrides[Dep] = lambda: "Dep"
69
+
70
+ @broker.subscriber("subject")
71
+ async def subscriber(dep: Annotated[str, De[]]) -> None:
72
+ assert dep == "Dep"
73
+ ```
74
+
75
+ ### Using the FastStream Context
76
+ ```py
77
+ from faststream import Context
78
+
79
+ @broker.subscriber("subject")
80
+ async def subscriber_handler(context_data: Annotated[int, Context("data")]) -> Response: ...
81
+ ```
82
+
83
+ ### Use FastAPI Request and Response
84
+ ```py
85
+ from fastapi import Request, Response
86
+ from fastapi.responses import JSONResponse
87
+
88
+ @broker.subscriber("subject")
89
+ async def subscriber_handler(request: Request) -> Response:
90
+ return JSONResponse({"data": 1})
91
+ ```
92
+
93
+ ### Minimalistic plugin connection to your application
94
+
95
+ All you need to do is wrap the **FastAPI** with the **FastStreamAPI** object from the **plugin**
96
+
97
+ ```py
98
+ application = FastStreamAPI(
99
+ NatsBroker(),
100
+ application=FastAPI(),
101
+ )
102
+ uvicorn.run(application)
103
+ ```
104
+
105
+ ### Managing a lifespan state
106
+ Now the **lifespan state** is also available in **FastStream handlers**
107
+
108
+ ```py
109
+ @asynccontextmanager
110
+ async def lifespan(app: FastAPI):
111
+ yield {"lifespan_data": "LIFESPAN DATA"}
112
+
113
+ @broker.subscriber("subject")
114
+ async def subscriber(request: Request) -> None:
115
+ assert request.state.lifespan_data == "LIFESPAN DATA"
116
+ ```
117
+
118
+ ### The ability to configure AsyncAPI
119
+
120
+ The ability to configure **AsyncAPI** using **SpecificationFactory** from **FastStream** and **AsyncAPIConfig** from the **plugin**
121
+
122
+ ```py
123
+ FastStreamAPI(
124
+ ...,
125
+ specification=AsyncAPI(
126
+ title="My app",
127
+ version="1.0.0",
128
+ description="...",
129
+ ...,
130
+ ),
131
+ asyncapi_path="/fs_docs",
132
+ # or
133
+ asyncapi_path=AsyncAPIRouter(
134
+ "/fs_docs",
135
+ description="...",
136
+ ...
137
+ ),
138
+ )
139
+ ```
140
+
141
+ ### Backgrounds tasks
142
+
143
+ You can use **BackgroundTasks** from **FastAPI** in **FastStream handlers**
144
+
145
+ ```py
146
+ @broker.subscriber("subject")
147
+ async def handler1(
148
+ tasks: BackgroundTasks,
149
+ ) -> None:
150
+ tasks.add_task(...)
151
+ ```
@@ -0,0 +1,111 @@
1
+ # FastAPI Plugin for FastStream
2
+
3
+ A plugin that allows you to use **Depends** and **FastAPI** other objects in the **FastStream**
4
+
5
+ # Features
6
+
7
+ ### Use FastAPI Dependency Injection
8
+ In **FastStream** handlers, it will be possible to use the familiar DI from **FastAPI**
9
+ ```py
10
+ from fastapi import Path, Body, Header, Depends
11
+
12
+ class BodyModel(BaseModel):
13
+ field: int
14
+
15
+ @broker.subscriber("subject.{num}")
16
+ async def subscriber_handler(
17
+ num: Annotated[int, Path()],
18
+ body: Annotated[BodyModel, Body()],
19
+ x_user_id: Annotated[int, Header()],
20
+ my_dep: Annotated[int, Depends(int)],
21
+ ) -> None:
22
+ ...
23
+ ```
24
+
25
+ ### Сan use FastAPI dependency overrides
26
+ ```py
27
+ fastapi = FastAPI()
28
+ fastapi.dependency_overrides[Dep] = lambda: "Dep"
29
+
30
+ @broker.subscriber("subject")
31
+ async def subscriber(dep: Annotated[str, De[]]) -> None:
32
+ assert dep == "Dep"
33
+ ```
34
+
35
+ ### Using the FastStream Context
36
+ ```py
37
+ from faststream import Context
38
+
39
+ @broker.subscriber("subject")
40
+ async def subscriber_handler(context_data: Annotated[int, Context("data")]) -> Response: ...
41
+ ```
42
+
43
+ ### Use FastAPI Request and Response
44
+ ```py
45
+ from fastapi import Request, Response
46
+ from fastapi.responses import JSONResponse
47
+
48
+ @broker.subscriber("subject")
49
+ async def subscriber_handler(request: Request) -> Response:
50
+ return JSONResponse({"data": 1})
51
+ ```
52
+
53
+ ### Minimalistic plugin connection to your application
54
+
55
+ All you need to do is wrap the **FastAPI** with the **FastStreamAPI** object from the **plugin**
56
+
57
+ ```py
58
+ application = FastStreamAPI(
59
+ NatsBroker(),
60
+ application=FastAPI(),
61
+ )
62
+ uvicorn.run(application)
63
+ ```
64
+
65
+ ### Managing a lifespan state
66
+ Now the **lifespan state** is also available in **FastStream handlers**
67
+
68
+ ```py
69
+ @asynccontextmanager
70
+ async def lifespan(app: FastAPI):
71
+ yield {"lifespan_data": "LIFESPAN DATA"}
72
+
73
+ @broker.subscriber("subject")
74
+ async def subscriber(request: Request) -> None:
75
+ assert request.state.lifespan_data == "LIFESPAN DATA"
76
+ ```
77
+
78
+ ### The ability to configure AsyncAPI
79
+
80
+ The ability to configure **AsyncAPI** using **SpecificationFactory** from **FastStream** and **AsyncAPIConfig** from the **plugin**
81
+
82
+ ```py
83
+ FastStreamAPI(
84
+ ...,
85
+ specification=AsyncAPI(
86
+ title="My app",
87
+ version="1.0.0",
88
+ description="...",
89
+ ...,
90
+ ),
91
+ asyncapi_path="/fs_docs",
92
+ # or
93
+ asyncapi_path=AsyncAPIRouter(
94
+ "/fs_docs",
95
+ description="...",
96
+ ...
97
+ ),
98
+ )
99
+ ```
100
+
101
+ ### Backgrounds tasks
102
+
103
+ You can use **BackgroundTasks** from **FastAPI** in **FastStream handlers**
104
+
105
+ ```py
106
+ @broker.subscriber("subject")
107
+ async def handler1(
108
+ tasks: BackgroundTasks,
109
+ ) -> None:
110
+ tasks.add_task(...)
111
+ ```
@@ -0,0 +1,139 @@
1
+ [build-system]
2
+ requires = ["uv_build==0.11.21"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "faststream_fastapi"
7
+ description = "FastAPI integration for FastStream"
8
+ readme = "README.md"
9
+ authors = [
10
+ { name = "Ivan Kirpichnikov", email = "mmssvvvv570@gmail.com" },
11
+ ]
12
+ license = "MIT"
13
+ license-files = ["LICENSE"]
14
+
15
+ requires-python = ">=3.10"
16
+ version = "1.0.0"
17
+
18
+ keywords = [
19
+ "rabbitmq",
20
+ "kafka",
21
+ "nats",
22
+ "redis",
23
+ "mqtt",
24
+ "asyncapi",
25
+ "framework",
26
+ "message brokers",
27
+ "fastapi",
28
+ ]
29
+
30
+ classifiers = [
31
+ "Development Status :: 5 - Production/Stable",
32
+ "Programming Language :: Python",
33
+ "Programming Language :: Python :: Implementation :: CPython",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3 :: Only",
36
+ "Programming Language :: Python :: 3.10",
37
+ "Programming Language :: Python :: 3.11",
38
+ "Programming Language :: Python :: 3.12",
39
+ "Programming Language :: Python :: 3.13",
40
+ "Programming Language :: Python :: 3.14",
41
+ "Operating System :: OS Independent",
42
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
43
+ "Topic :: Software Development :: Libraries :: Python Modules",
44
+ "Topic :: Software Development :: Libraries",
45
+ "Topic :: Software Development",
46
+ "Topic :: System :: Networking",
47
+ "Topic :: System :: Distributed Computing",
48
+ "Typing :: Typed",
49
+ "Intended Audience :: Developers",
50
+ "Intended Audience :: Information Technology",
51
+ "Intended Audience :: System Administrators",
52
+ "Environment :: Web Environment",
53
+ "Framework :: AsyncIO",
54
+ "Framework :: Pydantic",
55
+ "Framework :: Pydantic :: 1",
56
+ "Framework :: Pydantic :: 2",
57
+ ]
58
+
59
+ dependencies = [
60
+ "fastapi<1.0.0",
61
+ "faststream>=0.7.0",
62
+ ]
63
+
64
+ [dependency-groups]
65
+ linters = [
66
+ "ruff==0.15.20",
67
+ "typos==1.48.0",
68
+ "codespell==2.4.3",
69
+ ]
70
+ static_analysis = [
71
+ "mypy==2.1.0",
72
+ "bandit==1.9.4",
73
+ "semgrep==1.169.0",
74
+ "zizmor==1.27.0",
75
+ ]
76
+ docs = [
77
+ "zensical==0.0.50",
78
+ "mkdocstrings[python]==1.0.6",
79
+ ]
80
+
81
+ testing = [
82
+ "pytest==9.0.3",
83
+ "pytest-asyncio==1.3.0",
84
+ "pytest-xdist>=3.8.0",
85
+ "pytest-timeout==2.4.0",
86
+ "pytest-cov>=6.2.1",
87
+ "covdefaults>=2.3.0",
88
+ { include-group = "testing-dependencies" },
89
+ ]
90
+ testing-dependencies = [
91
+ "httpx2==2.5.0",
92
+ "asgi-lifespan==2.1.0",
93
+ "faststream[rabbit,kafka,confluent,nats,redis,mqtt]>=0.7.0",
94
+ ]
95
+
96
+ dev = [
97
+ { include-group = "docs"},
98
+ { include-group = "testing" },
99
+ { include-group = "linters" },
100
+ { include-group = "static_analysis" },
101
+ ]
102
+
103
+ [tool.uv.build-backend]
104
+ module-root = "src"
105
+ module-name = "faststream_fastapi"
106
+
107
+ [tool.pytest.ini_options]
108
+ minversion = "7.0"
109
+ timeout = 30
110
+ addopts = "--strict-markers --strict-config -q -m 'not slow'"
111
+ testpaths = ["tests"]
112
+ markers = [
113
+ "rabbit",
114
+ "kafka",
115
+ "confluent",
116
+ "nats",
117
+ "redis",
118
+ "redis_cluster",
119
+ "mqtt",
120
+ "slow",
121
+ "connected",
122
+ "all",
123
+ ]
124
+ asyncio_default_fixture_loop_scope = "function"
125
+
126
+ [tool.coverage.run]
127
+ source = ["src/"]
128
+ relative_files = true
129
+
130
+ [tool.coverage.paths]
131
+ source = ["src/", "/app/src/"]
132
+
133
+ [tool.coverage.report]
134
+ exclude_lines = [
135
+ "pass",
136
+ "def __str__",
137
+ "raise AssertionError",
138
+ "raise NotImplementedError",
139
+ ]
@@ -0,0 +1,5 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("faststream_fastapi")
4
+
5
+ SERVICE_NAME = f"faststream_fastapi-{__version__}"
@@ -0,0 +1,11 @@
1
+ from faststream_fastapi.asyncapi_config import AsyncAPIConfig
2
+ from faststream_fastapi.context import Context
3
+ from faststream_fastapi.faststream_api import FastStreamAPI
4
+ from faststream_fastapi.stream_message import StreamMessage
5
+
6
+ __all__ = (
7
+ "AsyncAPIConfig",
8
+ "Context",
9
+ "FastStreamAPI",
10
+ "StreamMessage",
11
+ )
@@ -0,0 +1,115 @@
1
+ import json
2
+ from collections.abc import Awaitable, Callable, Sequence
3
+ from typing import Any
4
+
5
+ from fastapi import APIRouter, Request, Response
6
+ from fastapi.responses import HTMLResponse, JSONResponse
7
+ from faststream.asgi.factories.asyncapi.try_it_out import TryItOutProcessor
8
+ from faststream.specification.asyncapi.site import get_asyncapi_html
9
+ from faststream.specification.base import SpecificationFactory
10
+
11
+ from faststream_fastapi._internal.fs_re_exports.broker import BrokerUsecase
12
+ from faststream_fastapi.asyncapi_config import AsyncAPIConfig
13
+
14
+
15
+ class AsyncAPIRouter(APIRouter):
16
+ def __init__(
17
+ self,
18
+ brokers: Sequence[BrokerUsecase[Any, Any]],
19
+ config: AsyncAPIConfig,
20
+ schema: SpecificationFactory,
21
+ ) -> None:
22
+ super().__init__(include_in_schema=config.include_in_schema)
23
+
24
+ self._config = config
25
+
26
+ try:
27
+ try_processor = TryItOutProcessor(*brokers)
28
+ except ValueError:
29
+ try_processor = None
30
+
31
+ self.get(self._config.path)(self.make_serve_asyncapi_schema(schema))
32
+
33
+ if self._config.asyncapi_json_path is not None:
34
+ self.get(self._config.asyncapi_json_path)(self.make_download_app_json_schema(schema))
35
+
36
+ if self._config.asyncapi_yaml_path is not None:
37
+ self.get(self._config.asyncapi_yaml_path)(self.make_download_app_yaml_schema(schema))
38
+
39
+ if try_processor is not None and self._config.try_it_out_path is not None:
40
+ self.post(self._config.try_it_out_path, include_in_schema=False)(
41
+ self.make_try_asyncapi_schema(try_processor),
42
+ )
43
+
44
+ def make_download_app_json_schema(
45
+ self,
46
+ schema: SpecificationFactory,
47
+ ) -> Callable[[], Awaitable[Response]]:
48
+ async def download_app_json_schema() -> Response:
49
+ return Response(
50
+ content=json.dumps(
51
+ schema.to_specification().to_jsonable(),
52
+ indent=2,
53
+ ),
54
+ headers={"Content-Type": "application/json"},
55
+ )
56
+
57
+ return download_app_json_schema
58
+
59
+ def make_download_app_yaml_schema(
60
+ self,
61
+ schema: SpecificationFactory,
62
+ ) -> Callable[[], Awaitable[Response]]:
63
+ async def download_app_yaml_schema() -> Response:
64
+ return Response(
65
+ content=schema.to_specification().to_yaml(),
66
+ headers={
67
+ "Content-Type": "application/octet-stream",
68
+ },
69
+ )
70
+
71
+ return download_app_yaml_schema
72
+
73
+ def make_serve_asyncapi_schema(
74
+ self,
75
+ schema: SpecificationFactory,
76
+ ) -> Callable[[], Awaitable[Response]]:
77
+ async def serve_asyncapi_schema() -> Response:
78
+ return HTMLResponse(
79
+ content=get_asyncapi_html(
80
+ schema.to_specification(),
81
+ sidebar=self._config.sidebar,
82
+ info=self._config.info,
83
+ servers=self._config.servers,
84
+ operations=self._config.operations,
85
+ messages=self._config.messages,
86
+ schemas=self._config.schemas,
87
+ errors=self._config.errors,
88
+ expand_message_examples=self._config.expand_message_examples,
89
+ asyncapi_js_url=self._config.asyncapi_js_url,
90
+ asyncapi_css_url=self._config.asyncapi_css_url,
91
+ try_it_out_plugin_url=self._config.try_it_out_plugin_url,
92
+ try_it_out_path=self._config.try_it_out_path,
93
+ ),
94
+ )
95
+
96
+ return serve_asyncapi_schema
97
+
98
+ def make_try_asyncapi_schema(
99
+ self,
100
+ try_processor: TryItOutProcessor,
101
+ ) -> Callable[[Request], Awaitable[Response]]:
102
+ async def try_asyncapi_schema(request: Request) -> Response:
103
+ try:
104
+ body = await request.json()
105
+ except Exception as e: # noqa: BLE001
106
+ return JSONResponse({"details": f"Invalid JSON: {e}"}, 400)
107
+
108
+ result = await try_processor.process(body)
109
+ return Response(
110
+ content=result.body,
111
+ status_code=result.status_code,
112
+ media_type="application/json",
113
+ )
114
+
115
+ return try_asyncapi_schema
@@ -0,0 +1,22 @@
1
+ from types import TracebackType
2
+ from typing import cast
3
+
4
+ from fastapi.background import BackgroundTasks
5
+ from faststream.middlewares import BaseMiddleware
6
+
7
+
8
+ class _BackgroundMiddleware(BaseMiddleware):
9
+ async def __aexit__(
10
+ self,
11
+ exc_type: type[BaseException] | None = None,
12
+ exc_val: BaseException | None = None,
13
+ exc_tb: TracebackType | None = None,
14
+ ) -> bool | None:
15
+ background = cast(
16
+ "BackgroundTasks | None",
17
+ getattr(self.context.get_local("message"), "background", None),
18
+ )
19
+ if exc_type is None and background is not None:
20
+ await background()
21
+
22
+ return await super().after_processed(exc_type, exc_val, exc_tb)
@@ -0,0 +1,23 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from fastapi import FastAPI
5
+ from fastapi.datastructures import State
6
+
7
+
8
+ class ConfigAsgiStateError(ValueError):
9
+ def __str__(self) -> str:
10
+ return "ASGI state already is set"
11
+
12
+
13
+ @dataclass
14
+ class Config:
15
+ application: FastAPI
16
+ dependency_overrides_provider: Any | None
17
+ asgi_state: State | None = None
18
+
19
+ def set_asgi_state(self, asgi_state: State) -> None:
20
+ if self.asgi_state is not None:
21
+ raise ConfigAsgiStateError # pragma: no cover
22
+
23
+ self.asgi_state = asgi_state