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.
- faststream_fastapi-1.0.0/LICENSE +21 -0
- faststream_fastapi-1.0.0/PKG-INFO +151 -0
- faststream_fastapi-1.0.0/README.md +111 -0
- faststream_fastapi-1.0.0/pyproject.toml +139 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/__about__.py +5 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/__init__.py +11 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/__init__.py +0 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/asyncapi_router.py +115 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/background_middleware.py +22 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/config.py +23 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fastapi_compat.py +152 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/__init__.py +0 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/_compat.py +6 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/application.py +3 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/broker.py +4 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/constants.py +3 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/context.py +8 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/di.py +3 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/fs_re_exports/logger.py +3 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/get_dependant.py +182 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/logger.py +11 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/replace_context.py +119 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/_internal/wrap_callable_to_fastapi_compatible.py +180 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/asyncapi_config.py +71 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/context.py +31 -0
- faststream_fastapi-1.0.0/src/faststream_fastapi/faststream_api.py +165 -0
- 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,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
|
+
)
|
|
File without changes
|
|
@@ -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
|