fast-tools
is a FastApi/Starlette
toolset, Most of the tools can be used in FastApi/Starlette, a few tools only support FastApi
which is divided into the lack of compatibility with FastApi
Note: this is alpha quality code still, the API may change, and things may fall apart while you try it.
```python
project_name = ('FastApi'[:2] + 'Starlette'[:2]).lower() + '-tools' print(project_name) # 'fast-tools' ``` 中文文档
fast-tools
and can also be used aloneFastApi
,Starlette
, more....from fast_tools.base import RedisHelper
app: 'FastApi' = FastAPI() redis_helper: 'RedisHelper' = RedisHelper() # init object
@app.on_event("startup") async def startup(): # create redis conn pool and connect redis_helper.init(await aioredis.create_pool('redis://localhost', minsize=1, maxsize=10, encoding='utf-8'))
app.on_event("shutdown") async def shutdown(): # close redis conn pool await redis_helper.close()
@app.get("/") async def root() -> dict: info = await redis_helper.client.info() return {"info": info}
if name == 'main': import uvicorn uvicorn.run(app) ```
Most of python's web framework routing lookups traverse the entire routing table. If the current url matches the registered url of the route, the lookup is successful. It can be found that the time complexity of the route lookup is O(n).
I guess the reason why the python web framework uses the traversal routing table is to support /api/user/{user_id}
while keeping it simple.
It can be found that the time complexity of each route lookup is O(n). When the number of routes reaches a certain level, the matching time will becomes slower, but when we use middleware, if we need to check whether the route is matched, then It needs to be matched again, and this piece of ours can be controlled, so we need to optimize the routing matching speed here.
The fastest route matching speed is dict, but it cannot support urls similar to /api/user/{user_id}
. Fortunately, the url matches the data structure of the trie, so the trie is used to refactor the route search, which can be as fast as possible Match the approximate area of the route, and then perform regular matching to check whether the route is correct.
```Python
from typing import (
List,
Optional
)
from fastapi import FastAPI
from starlette.routing import Route
from fast_tools.base import RouteTrie
app: 'FastAPI' = FastAPI()
@app.get("/") async def root() -> dict: return {"Hello": "World"}
@app.get("/api/users/login") async def user_login() -> str: return 'ok'
route_trie: RouteTrie = RouteTrie() # init route trie route_trie.insert_by_app(app) # load route from app
def print_route(route_list: Optional[List[Route]]): """print route list """ if route_list: for route in route_list: print(f'route:{route} url:{route.path}') else: print(f'route:{route_list} url: not found')
print_route(route_trie.search('/')) print_route(route_trie.search('/api/users/login')) ``` Simply compare the efficiency of the built-in route matching and trie matching
Starlette
and FastAPI
, which can monitor the status of each URL, such as the number of connections, the number of responses, the number of requests, the number of errors, and the number of current requests.FastApi
,Starlette
pip install prometheus_client
```python from typing import Optional
from fastapi import FastAPI from fast_tools.exporter import PrometheusMiddleware, get_metrics from fast_tools.base.route_trie import RouteTrie
app = FastAPI() route_trie = RouteTrie()
app.add_middleware( PrometheusMiddleware, route_trie=route_trie, # use route trie, speed up routing query block_url_set={"/metrics"} # not monitor url: /metrics )
app.add_route("/metrics", get_metrics) ```
FastApi
```python
author = 'so1n' date = '2020-08' from fastapi import FastAPI, Depends, Header, Query from fast_tools.cbv import cbv_decorator, Cbv
app = FastAPI()
def get_user_agent(user_agent: str = Header("User-Agent")) -> str: return user_agent
class TestCbv(object): # Don't worry about the parent attribute. # Every time the get or post method is called, a new object is actually created and passed in through self. # Different requests will not share the same object. host: str = Header('host') user_agent: str = Depends(get_user_agent)
def __init__(self, test_default_id: int = Query(123)):
"""support __init__ method param"""
self.test_default_id = test_default_id
def _response(self):
return {"message": "hello, world", "user_agent": self.user_agent, "host": self.host, "id": self.test_default_id}
@cbv_decorator(status_code=203) # only support fastapi.route.add_api_route keywords param
def get(self):
return self._response()
def post(self):
return self._response()
app.include_router(Cbv(TestCbv).router)
if name == 'main': import uvicorn uvicorn.run(app) ```
Pydantic
and Type Hints, so it can quickly convert or verify parameters without using a large amount of code.FastApi
,Starlette
```python
from typing import List, Optionalfrom pydantic.fields import Json
from fast_tools.config import Config
class MyConfig(Config): DEBUG: bool HOST: str PORT: int
REDIS_ADDRESS: str
REDIS_PASS: Optional[str] = None # Set the default value, if the configuration file does not have this value and does not set the default value, an error will be reported
MYSQL_DB_HOST: str
MYSQL_DB_NAME: str
MYSQL_DB_PASS: str
MYSQL_DB_USER: str
ES_HOST: Json[List]
TEST_LIST_INT: Json[List]
YML_ES_HOST: Optional[List[str]] = None
YML_TEST_LIST_INT: Optional[List[int]] = None
``` config supports the following parameters: - config_file: config file,Support ini and yml config files, f the value is empty, data is pulled from environment variables (but only a global dictionary is pulled), see example - group: group can specify a configuration group. When using ini and yml files, multiple group configurations are supported, such as dev configuration and test configuration. If you don't want to configure this option in the code, you can directly configure group=test in the environment variable. - global_key: Specify that group as the global configuration. When using ini and yml files, multiple group configurations are supported, and there is also a global configuration, which can be shared by multiple groups (if the group does not have a corresponding configuration, it will be referenced to the global_key Configuration, if there is no reference)
see example
contextvars
, you can conveniently call what you need in the route, without the need to call like requests.app.state, and it can also support type hints to facilitate writing code.FastApi
,Starlette
```python import asyncio import httpx import uuid from contextvars import ( copy_context, Context ) from functools import partial from fastapi import ( FastAPI, Request, Response ) from fast_tools.context import ( ContextBaseModel, ContextMiddleware, CustomHelper, HeaderHelper, )
app = FastAPI() client = httpx.AsyncClient()
class ContextModel(ContextBaseModel): # ContextBaseModel save data to contextvars request_id: str = HeaderHelper( 'X-Request-Id', default_func=lambda request: str(uuid.uuid4()) ) ip: str = HeaderHelper( 'X-Real-IP', default_func=lambda request: request.client.host ) user_agent: str = HeaderHelper('User-Agent')
# CustomHelper is a encapsulation of Context calls, and data can be read in the current context (if you want to set data, you need to instantiate it first)
http_client: httpx.AsyncClient = CustomHelper('http_client')
async def before_request(self, request: Request):
"""The method that will be called before the request is executed"""
self.http_client = httpx.AsyncClient()
async def after_response(self, request: Request, response: Response):
"""The method that will be called after the request is executed"""
pass
async def before_reset_context(self, request: Request, response: Response):
"""The method that will be called before the context is destroyed"""
await self.http_client.aclose()
app.add_middleware(ContextMiddleware, context_model=ContextModel())
async def test_ensure_future(): print(f'test_ensure_future {ContextModel.http_client}')
def test_run_in_executor(): print(f'test_run_in_executor {ContextModel.http_client}')
def test_call_soon(): print(f'test_call_soon {ContextModel.http_client}')
@app.get("/") async def root(): # Python will automatically copy the context asyncio.ensure_future(test_ensure_future()) loop: 'asyncio.get_event_loop()' = asyncio.get_event_loop()
# Python will automatically copy the context
loop.call_soon(test_call_soon)
# When opening another thread for processing, you need to copy the context yourself
ctx: Context = copy_context()
await loop.run_in_executor(None, partial(ctx.run, test_run_in_executor))
return {
"message": ContextModel.to_dict(is_safe_return=True), # Only return data that can be converted to json
"local_ip": (await ContextModel.http_client.get('http://icanhazip.com')).text
}
if name == 'main': import uvicorn uvicorn.run(app) ```
url_replace_handle
to handle urlFastApi
,Starlette
pip install aiostatsd ```python from typing import Optional
from fastapi import FastAPI from fast_tools.statsd_middleware import StatsdClient, StatsdMiddleware from fast_tools.base.route_trie import RouteTrie
app = FastAPI() client = StatsdClient() route_trie = RouteTrie()
app.add_middleware( StatsdMiddleware, client=client, route_trie=route_trie, url_replace_handle=lambda url: url.replace('/', '_'), # Metric naming does not support'/' symbol block_url_set={"/"} ) app.on_event("shutdown")(client.close)
@app.on_event("startup") async def startup_event(): await client.connect() route_trie.insert_by_app(app)
@app.get("/") async def root(): return {"Hello": "World"}
@app.get("/api/users/{user_id}/items/{item_id}") async def read_user_item( user_id: int, item_id: str, q: Optional[str] = None, short: bool = False ): """ copy from:https://fastapi.tiangolo.com/tutorial/query-params/#multiple-path-and-query-parameters """ item = {"item_id": item_id, "owner_id": user_id} if q: item.update({"q": q}) if not short: item.update( {"description": "This is an amazing item that has a long description"} ) return item
@app.get("/api/users/login") async def user_login(): return 'ok'
if name == 'main': import uvicorn uvicorn.run(app) ```
task
, so task
is not recommended, but it may be used in the evolution of the architectureFastApi
,Starlette
```python
import time
from fastapi import FastAPI
from fast_tools.task import background_task
from fast_tools.task import stop_taskapp = FastAPI()
@app.on_event("startup")
@background_task(seconds=10) def test_task() -> None: print(f'test.....{int(time.time())}')
app.on_event("shutdown")(stop_task)
if name == 'main': import uvicorn uvicorn.run(app) ```
FastApi
,Starlette
import aioredis from fastapi import FastAPI from starlette.responses import JSONResponse
from fast_tools.base import RedisHelper from fast_tools.cache import ( cache, cache_control )
app = FastAPI() redis_helper: 'RedisHelper' = RedisHelper()
@app.on_event("startup") async def startup(): redis_helper.init(await aioredis.create_pool('redis://localhost', minsize=1, maxsize=10, encoding='utf-8'))
@app.on_event("shutdown") async def shutdown(): if not redis_helper.closed: await redis_helper.close()
@app.get("/") @cache(redis_helper, 60) async def root() -> dict: """Read the dict data and send the corresponding response data according to the response (the default is JSONResponse)""" return {"timestamp": time.time()}
@app.get("/api/users/login") @cache(redis_helper, 60, after_cache_response_list=[cache_control]) async def user_login() -> JSONResponse: """The response type cache does not cache the entire instance, but caches the main data in the instance, and re-splices it into a new respnose the next time it returns to the cache.""" return JSONResponse({"timestamp": time.time()})
@app.get("api/null") @cache(redis_helper, 60) async def test_not_return_annotation(): """Functions without return annotation will not be cached""" return JSONResponse({"timestamp": time.time()})
if name == 'main': import uvicorn uvicorn.run(app) ```
FastApi
,Starlette
```python
from typing import Optional, Tupleimport aioredis from fastapi import FastAPI, Request from fast_tools.base import RedisHelper from fast_tools import limit
def limit_func(requests: Request) -> Tuple[str, str]: """limit needs to determine the current request key and group according to the function""" return requests.session['user'], requests.session['group']
app = FastAPI() redis_helper: 'RedisHelper' = RedisHelper()
@app.on_event("startup") async def startup(): redis_helper.init(await aioredis.create_pool('redis://localhost', minsize=1, maxsize=10, encoding='utf-8'))
app.add_middleware( limit.LimitMiddleware, func=limit_func, rule_dict={ r"^/api": [limit.Rule(second=1, gen_token_num=10, group='admin'), limit.Rule(second=1, group='user')] } )
@app.get("/") @limit.limit( [limit.Rule(second=10, gen_token_num=1)], limit.backend.RedisFixedWindowBackend(redis_helper), limit_func=limit.func.client_ip ) async def root(): return {"Hello": "World"}
@app.get("/api/users/{user_id}/items/{item_id}") async def read_user_item( user_id: int, item_id: str, q: Optional[str] = None, short: bool = False ): """ copy from:https://fastapi.tiangolo.com/tutorial/query-params/#multiple-path-and-query-parameters """ item = {"item_id": item_id, "owner_id": user_id} if q: item.update({"q": q}) if not short: item.update( {"description": "This is an amazing item that has a long description"} ) return item
@app.get("/api/users/login") async def user_login(): return 'ok'
if name == 'main': import uvicorn uvicorn.run(app) ```
FastApi
,Starlette
Bumps starlette from 0.14.2 to 0.25.0.
Sourced from starlette's releases.
Version 0.25.0
Fixed
- Limit the number of fields and files when parsing
multipart/form-data
on theMultipartParser
8c74c2c and #2036.Version 0.24.0
Added
- Allow
StaticFiles
to follow symlinks #1683.- Allow
Request.form()
as a context manager #1903.- Add
size
attribute toUploadFile
#1405.- Add
env_prefix
argument toConfig
#1990.- Add template context processors #1904.
- Support
str
anddatetime
onexpires
parameter on theResponse.set_cookie
method #1908.Changed
- Lazily build the middleware stack #2017.
- Make the
file
argument required onUploadFile
#1413.- Use debug extension instead of custom response template extension #1991.
Fixed
- Fix url parsing of ipv6 urls on
URL.replace
#1965.Version 0.23.1
Fixed
- Only stop receiving stream on
body_stream
if body is empty on theBaseHTTPMiddleware
#1940.Version 0.23.0
Added
- Add
headers
parameter to theTestClient
#1966.Deprecated
- Deprecate
Starlette
andRouter
decorators #1897.Fixed
- Fix bug on
FloatConvertor
regex #1973.Version 0.22.0
Changed
- Bypass
GZipMiddleware
when response includesContent-Encoding
#1901.Fixed
- Remove unneeded
unquote()
from query parameters on theTestClient
#1953.- Make sure
MutableHeaders._list
is actually alist
#1917.- Import compatibility with the next version of
AnyIO
#1936.Version 0.21.0
This release replaces the underlying HTTP client used on the
TestClient
(requests
:arrow_right:httpx
), and as those clients differ a bit on their API, your test suite will likely break. To make the migration smoother, you can use thebump-testclient
tool.Changed
- Replace
requests
withhttpx
inTestClient
#1376.
... (truncated)
Sourced from starlette's changelog.
0.25.0
February 14, 2023
Fix
- Limit the number of fields and files when parsing
multipart/form-data
on theMultipartParser
8c74c2c and #2036.0.24.0
February 6, 2023
Added
- Allow
StaticFiles
to follow symlinks #1683.- Allow
Request.form()
as a context manager #1903.- Add
size
attribute toUploadFile
#1405.- Add
env_prefix
argument toConfig
#1990.- Add template context processors #1904.
- Support
str
anddatetime
onexpires
parameter on theResponse.set_cookie
method #1908.Changed
- Lazily build the middleware stack #2017.
- Make the
file
argument required onUploadFile
#1413.- Use debug extension instead of custom response template extension #1991.
Fixed
- Fix url parsing of ipv6 urls on
URL.replace
#1965.0.23.1
December 9, 2022
Fixed
- Only stop receiving stream on
body_stream
if body is empty on theBaseHTTPMiddleware
#1940.0.23.0
December 5, 2022
Added
- Add
headers
parameter to theTestClient
#1966.Deprecated
- Deprecate
Starlette
andRouter
decorators #1897.Fixed
- Fix bug on
FloatConvertor
regex #1973.0.22.0
November 17, 2022
... (truncated)
fc48089
Version 0.25.0 (#2035)bb4d8f9
🐛 Close all the multipart files on error (#2036)8c74c2c
Merge pull request from GHSA-74m5-2c7w-9w3x5771a78
Fix test not passing in 32-bit architectures (#2033)337ae24
Document that UploadFile's filename
and content_type
can be None
(#2029)218a6b4
Version 0.24.0 (#1983)e05b632
Feature: Add size attribute to UploadFile (#1405)c568b55
allow using Request.form() as a context manager (#1903)0a63a6e
Support str
and datetime
on expires
parameter on the set_cookie
metho...94a22b8
Fix url parsing of ipv6 urls on URL.replace
(#1965)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase
.
Bumps certifi from 2021.10.8 to 2022.12.7.
9e9e840
2022.12.07b81bdb2
2022.09.24939a28f
2022.09.14aca828a
2022.06.15.2de0eae1
Only use importlib.resources's new files() / Traversable API on Python ≥3.11 ...b8eb5e9
2022.06.15.147fb7ab
Fix deprecation warning on Python 3.11 (#199)b0b48e0
fixes #198 -- update link in license9d514b4
2022.06.154151e88
Add py.typed to MANIFEST.in to package in sdist (#196)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase
.
Bumps protobuf from 3.19.1 to 3.19.5.
Sourced from protobuf's releases.
Protocol Buffers v3.19.5
C++
- Reduce memory consumption of MessageSet parsing
- This release addresses a Security Advisory for C++ and Python users
Protocol Buffers v3.19.4
Python
Ruby
- Fixed a data loss bug that could occur when the number of
optional
fields in a message is an exact multiple of 32. (#9440).PHP
- Fixed a data loss bug that could occur when the number of
optional
fields in a message is an exact multiple of 32. (#9440).Protocol Buffers v3.19.3
Python
- Fix missing Windows wheel for Python 3.10 on PyPI
Protocol Buffers v3.19.2
Java
- Improve performance characteristics of UnknownFieldSet parsing (#9371)
- This release addresses a Security Advisory for Java users
b464cfb
Updating changelog40859fb
Updating version.json and repo version numbers to: 19.53b175f1
Merge pull request #10543 from deannagarcia/3.19.xc05b5f3
Add missing includes0299c03
Apply patch0a722f1
Update version.json with "lts": true (#10533)d5eb60a
Merge pull request #10530 from protocolbuffers/deannagarcia-patch-66cf1f78
Update version.json97fc844
Merge pull request #10504 from deannagarcia/3.19.x29d60a2
Add version fileDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase
.
Bumps ujson from 4.3.0 to 5.4.0.
Sourced from ujson's releases.
5.4.0
Added
- Add support for arbitrary size integers (#548)
@JustAnotherArchivist
Fixed
- CVE-2022-31116:
- Replace
wchar_t
string decoding implementation with auint32_t
-based one (#555)@JustAnotherArchivist
- Fix handling of surrogates on decoding (#550)
@JustAnotherArchivist
- CVE-2022-31117: Potential double free of buffer during string decoding
@JustAnotherArchivist
- Fix memory leak on encoding errors when the buffer was resized (#549)
@JustAnotherArchivist
- Integer parsing: always detect overflows (#544)
@NaN-git
- Fix handling of surrogates on encoding (#530)
@JustAnotherArchivist
5.3.0
Added
Changed
- Benchmark refactor - argparse CLI (#533)
@Erotemic
Fixed
- Fix segmentation faults when errors occur while handling unserialisable objects (#531)
@JustAnotherArchivist
- Fix segmentation fault when an exception is raised while converting a dict key to a string (#526)
@JustAnotherArchivist
- Fix memory leak dumping on non-string dict keys (#521)
@JustAnotherArchivist
- Fix ref counting on repeated default function calls (#524)
@JustAnotherArchivist
- Remove redundant
wheel
dependency frompyproject.toml
(#535)@hugovk
5.2.0
Added
- Support parsing NaN, Infinity and -Infinity (#514)
@Erotemic
- Support dynamically linking against system double-conversion library (#508)
@musicinmybrain
- Add env var to control stripping debug info (#507)
@musicinmybrain
- Add
JSONDecodeError
(#498)@JustAnotherArchivist
Fixed
- Fix buffer overflows (CVE-2021-45958) (#519)
@JustAnotherArchivist
- Upgrade Black to fix Click (#515)
@hugovk
- simplify exception handling on integer overflow (#510)
@RouquinBlanc
- Remove dead code that used to handle the separate int type in Python 2 (#509)
@JustAnotherArchivist
- Fix exceptions on encoding list or dict elements and non-overflow errors on int handling getting silenced (#505)
@JustAnotherArchivist
5.1.0
Changed
... (truncated)
9c20de0
Merge pull request from GHSA-fm67-cv37-96ffb21da40
Fix double free on string decoding if realloc fails67ec071
Merge pull request #555 from JustAnotherArchivist/fix-decode-surrogates-2bc7bdff
Replace wchar_t string decoding implementation with a uint32_t-based onecc70119
Merge pull request #548 from JustAnotherArchivist/arbitrary-ints4b5cccc
Merge pull request #553 from bwoodsend/pypy-ciabe26fc
Merge pull request #551 from bwoodsend/bye-bye-travis3efb5cc
Delete old TravisCI workflow and references.404de1a
xfail test_decode_surrogate_characters() on Windows PyPy.f7e66dc
Switch to musl docker base images.Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase
.
Bumps httpx from 0.21.1 to 0.23.0.
Sourced from httpx's releases.
Version 0.23.0
0.23.0 (23rd May, 2022)
Changed
- Drop support for Python 3.6. (#2097)
- Use
utf-8
as the default character set, instead of falling back tocharset-normalizer
for auto-detection. To enable automatic character set detection, see the documentation. (#2165)Fixed
- Fix
URL.copy_with
for some oddly formed URL cases. (#2185)- Digest authentication should use case-insensitive comparison for determining which algorithm is being used. (#2204)
- Fix console markup escaping in command line client. (#1866)
- When files are used in multipart upload, ensure we always seek to the start of the file. (#2065)
- Ensure that
iter_bytes
never yields zero-length chunks. (#2068)- Preserve
Authorization
header for redirects that are to the same origin, but are anhttp
-to-https
upgrade. (#2074)- When responses have binary output, don't print the output to the console in the command line client. Use output like
<16086 bytes of binary data>
instead. (#2076)- Fix display of
--proxies
argument in the command line client help. (#2125)- Close responses when task cancellations occur during stream reading. (#2156)
- Fix type error on accessing
.request
onHTTPError
exceptions. (#2158)Version 0.22.0
0.22.0 (26th January, 2022)
Added
- Support for the SOCKS5 proxy protocol via the
socksio
package. (#2034)- Support for custom headers in multipart/form-data requests (#1936)
Fixed
- Don't perform unreliable close/warning on
__del__
with unclosed clients. (#2026)- Fix
Headers.update(...)
to correctly handle repeated headers (#2038)Version 0.21.3
0.21.3 (6th January, 2022)
Fixed
- Fix streaming uploads using
SyncByteStream
orAsyncByteStream
. Regression in 0.21.2. (#2016)Version 0.21.2
0.21.2 (5th January, 2022)
Fixed
Sourced from httpx's changelog.
0.23.0 (23rd May, 2022)
Changed
- Drop support for Python 3.6. (#2097)
- Use
utf-8
as the default character set, instead of falling back tocharset-normalizer
for auto-detection. To enable automatic character set detection, see the documentation. (#2165)Fixed
- Fix
URL.copy_with
for some oddly formed URL cases. (#2185)- Digest authentication should use case-insensitive comparison for determining which algorithm is being used. (#2204)
- Fix console markup escaping in command line client. (#1866)
- When files are used in multipart upload, ensure we always seek to the start of the file. (#2065)
- Ensure that
iter_bytes
never yields zero-length chunks. (#2068)- Preserve
Authorization
header for redirects that are to the same origin, but are anhttp
-to-https
upgrade. (#2074)- When responses have binary output, don't print the output to the console in the command line client. Use output like
<16086 bytes of binary data>
instead. (#2076)- Fix display of
--proxies
argument in the command line client help. (#2125)- Close responses when task cancellations occur during stream reading. (#2156)
- Fix type error on accessing
.request
onHTTPError
exceptions. (#2158)0.22.0 (26th January, 2022)
Added
- Support for the SOCKS5 proxy protocol via the
socksio
package. (#2034)- Support for custom headers in multipart/form-data requests (#1936)
Fixed
- Don't perform unreliable close/warning on
__del__
with unclosed clients. (#2026)- Fix
Headers.update(...)
to correctly handle repeated headers (#2038)0.21.3 (6th January, 2022)
Fixed
- Fix streaming uploads using
SyncByteStream
orAsyncByteStream
. Regression in 0.21.2. (#2016)0.21.2 (5th January, 2022)
Fixed
89cdd90
Version 0.23.0 (#2214)1c33a28
Make charset auto-detection optional. (#2165)940d61b
Removed curio from async.md (#2240)14a1704
Switch to explicit typing.Optional
throughout (#2096)9673a35
Drop async_generator requirement (#2228)5eba32a
Remove RequestBodyUnavailable
from module docstring (#2226)6f31bc4
Bump mkdocs-material from 8.1.4 to 8.2.14 (#2218)c5eb4b8
Bump cryptography from 36.0.2 to 37.0.2 (#2217)1a526cf
Bump actions/checkout from 2 to 3 (#2216)7a53543
Bump actions/setup-python from 1 to 3 (#2215)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase
.
fastapi starlette