Compare commits

..

1 Commits

Author SHA1 Message Date
chenweijia 40d25d2990 fix: 初始化异步数据库操作分支 2024-08-13 15:54:12 +08:00
14 changed files with 136 additions and 611 deletions

View File

@ -5,10 +5,9 @@ on:
branches: [master] branches: [master]
paths-ignore: paths-ignore:
- 'requirements.txt' - 'requirements.txt'
- '*RuntimeDockerfile' - 'RuntimeDockerfile'
- '.gitignore' - '.gitignore'
- 'README.md' - 'README.md'
- 'pyproject.toml'
- '.gitea/workflows/**' - '.gitea/workflows/**'
@ -28,8 +27,16 @@ jobs:
- run: | - run: |
var=${{ gitea.repository }} var=${{ gitea.repository }}
repo=${var##*/} repo=${var##*/}
version=$(grep -oP '(?<=version = ")(.*)(?=")' "pyproject.toml") url="https://hub.airpig.cn/api/v2.0/projects/library/repositories/${repo}/artifacts?page=1&page_size=10&with_tag=true&with_label=false&with_scan_overview=false&with_signature=false&with_immutable_status=false&with_accessory=false"
new_version="v${version}" version=$(curl -X 'GET' $url \
-H 'accept: application/json' \
-H 'X-Accept-Vulnerabilities: application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0' \
-u 'admin:Chenweijia!' | jq '.[0].tags[0].name')
if [ ! $version ]; then
new_version=v1.0.0
else
new_version=$(echo $version | sed 's/\"//g' | awk -F. '{$NF = $NF + 1;} 1' OFS=.)
fi
docker build -t hub.airpig.cn/library/$repo:$new_version . docker build -t hub.airpig.cn/library/$repo:$new_version .
docker push hub.airpig.cn/library/$repo:$new_version docker push hub.airpig.cn/library/$repo:$new_version
docker rmi hub.airpig.cn/library/$repo:$new_version docker rmi hub.airpig.cn/library/$repo:$new_version

View File

@ -6,7 +6,8 @@ on:
paths: paths:
- 'requirements.txt' - 'requirements.txt'
- 'poetry.lock' - 'poetry.lock'
- '*RuntimeDockerfile' - 'pyproject.toml'
- 'RuntimeDockerfile'
jobs: jobs:
build-runtime: build-runtime:
@ -24,9 +25,18 @@ jobs:
- run: | - run: |
var=${{ gitea.repository }} var=${{ gitea.repository }}
repo=${var##*/}"-runtime" repo=${var##*/}"-runtime"
version=$(grep -oP '(?<=version = ")(.*)(?=")' "pyproject.toml") echo $repo
new_version="v${version}" url="https://hub.airpig.cn/api/v2.0/projects/library/repositories/${repo}/artifacts?page=1&page_size=10&with_tag=true&with_label=false&with_scan_overview=false&with_signature=false&with_immutable_status=false&with_accessory=false"
docker build -t hub.airpig.cn/library/$repo:$new_version -f AlpineRuntimeDockerfile . version=$(curl -X 'GET' $url \
-H 'accept: application/json' \
-H 'X-Accept-Vulnerabilities: application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0' \
-u 'admin:Chenweijia!' | jq '.[0].tags[0].name')
if [ ! $version ]; then
new_version=v1.0.0
else
new_version=$(echo $version | sed 's/\"//g' | awk -F. '{$NF = $NF + 1;} 1' OFS=.)
fi
docker build -t hub.airpig.cn/library/$repo:$new_version -f RuntimeDockerfile .
docker push hub.airpig.cn/library/$repo:$new_version docker push hub.airpig.cn/library/$repo:$new_version
docker rmi hub.airpig.cn/library/$repo:$new_version docker rmi hub.airpig.cn/library/$repo:$new_version
- run: echo "This job's status is ${{ job.status }}." - run: echo "This job's status is ${{ job.status }}."

View File

@ -1,30 +0,0 @@
FROM python:3.11-alpine as builder
RUN pip install poetry -i https://mirrors.aliyun.com/pypi/simple/
WORKDIR /venv
COPY poetry.lock pyproject.toml /venv/
RUN poetry config virtualenvs.options.no-pip true \
&& poetry config virtualenvs.options.no-setuptools true \
&& poetry config virtualenvs.in-project true \
&& poetry install
FROM python:3.11-alpine as release
COPY --from=builder /venv /venv
ENV PATH="/venv/.venv/bin:${PATH}"
RUN chmod a+x /venv/.venv/bin/activate \
&& source /venv/.venv/bin/activate
WORKDIR /app
COPY . /app
ENV FAST_API_ENV=prod
CMD ["/usr/local/bin/uvicorn", "main:fast_api_app", "--reload", "--host", "0.0.0.0", "--port", "80"]

View File

@ -1,7 +1,7 @@
# FastAPI App 文档 # FastAPI App 文档
## 0 开发说明 ## 0 开发说明
Python(3.10+) and pip(20.2.4+) Python(3.8.6+) and pip(20.2.4+)
### 开发命名规范 ### 开发命名规范
> * 避免采用的名字 > * 避免采用的名字
> 不要使用字符l小写字母elO大写字母ohI大写字母eye作为单字符变量名。 > 不要使用字符l小写字母elO大写字母ohI大写字母eye作为单字符变量名。
@ -32,12 +32,12 @@ database=test
## 2 开发环境下安装依赖和运行项目 ## 2 开发环境下安装依赖和运行项目
``` bash ``` bash
pip install poetry -i https://pypi.tuna.tsinghua.edu.cn/simple/ \ pip install poetry -i https://mirrors.aliyun.com/pypi/simple/ \
&& poetry source add --priority=primary mirrors https://pypi.tuna.tsinghua.edu.cn/simple/ \ && poetry source add --priority=primary mirrors https://pypi.tuna.tsinghua.edu.cn/simple/ \
&& poetry config virtualenvs.path /install \ && poetry config virtualenvs.path /install \
&& poetry install && poetry install
./start.bat (windows) ./start.bat (windows)
./start.sh (mac/linux) ./start.sh (mac)
``` ```

View File

@ -30,8 +30,11 @@ class MySQLConfig(BaseSettings):
@property @property
def sqlalchemy_db_uri(self): def sqlalchemy_db_uri(self):
return f"mysql+pymysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}?charset=utf8" return f"mysql+pymysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}?charset=utf8mb4"
@property
def async_sqlalchemy_db_uri(self):
return f"mysql+aiomysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}?charset=utf8mb4"
class RedisConfig(RedisSettings): class RedisConfig(RedisSettings):
redis_host: Optional[str] = 'localhost' redis_host: Optional[str] = 'localhost'

51
main.py
View File

@ -1,26 +1,27 @@
import logging.config as logging_config import logging.config as logging_config
import os import os
from contextlib import asynccontextmanager
import fastapi_plugins import fastapi_plugins
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.exceptions import HTTPException, RequestValidationError from fastapi.exceptions import HTTPException, RequestValidationError
# from fastapi.middleware.wsgi import WSGIMiddleware from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi_sqlalchemy import DBSessionMiddleware from fastapi_async_sqlalchemy import SQLAlchemyMiddleware
from authx.exceptions import AuthXException
from config import init_config from config import init_config
# from src.middleware.flask import flask_app # from src.middleware.flask import flask_app
from src.utils.exception import (http_exception_handler,
request_validation_error_handler)
from src.utils.exception import http_exception_handler, request_validation_error_handler, authx_exception_handler
# 请求限制
import redis.asyncio as aio_redis
from fastapi_limiter import FastAPILimiter
def create_app(): def create_app():
mysql_config, redis_config = init_config() app = FastAPI()
@asynccontextmanager mysql_config, redis_config = init_config()
async def lifespan(app: FastAPI): # 添加sqlalchemy数据库中间件
# once the middleware is applied, any route can then access the database session from the global ``db``
app.add_middleware(SQLAlchemyMiddleware, db_url=mysql_config.async_sqlalchemy_db_uri)
@app.on_event("startup")
async def startup_event():
# 创建日志文件夹和临时文件上传文件夹 # 创建日志文件夹和临时文件上传文件夹
if not os.path.exists("files"): if not os.path.exists("files"):
os.mkdir("files") os.mkdir("files")
@ -31,33 +32,25 @@ def create_app():
logging_config.fileConfig('conf/log.ini') logging_config.fileConfig('conf/log.ini')
# 初始化配置文件 # 初始化配置文件
# Redis 缓存初始化 # Redis 缓存初始化
print(redis_config)
await fastapi_plugins.redis_plugin.init_app(app, redis_config) await fastapi_plugins.redis_plugin.init_app(app, redis_config)
await fastapi_plugins.redis_plugin.init() await fastapi_plugins.redis_plugin.init()
# 请求限制
await FastAPILimiter.init(redis=aio_redis.from_url("redis://localhost:6379", encoding="utf8")) @app.on_event("shutdown")
yield async def shutdown_event():
# 应用关闭时关闭redis连接
await fastapi_plugins.redis_plugin.terminate() await fastapi_plugins.redis_plugin.terminate()
await FastAPILimiter.close()
app = FastAPI(lifespan=lifespan)
# 添加sqlalchemy数据库中间件
# once the middleware is applied, any route can then access the database session from the global ``db``
app.add_middleware(DBSessionMiddleware, db_url=mysql_config.sqlalchemy_db_uri)
# 添加异常处理 # 添加异常处理
app.add_exception_handler(HTTPException, http_exception_handler) app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, request_validation_error_handler) app.add_exception_handler(RequestValidationError, request_validation_error_handler)
# AuthX异常处理
app.add_exception_handler(AuthXException, authx_exception_handler)
# 可以在这里挂载Flask的应用复用之前项目的相关代码 # 可以在这里挂载Flask的应用复用之前项目的相关代码
# app.mount("/v1", WSGIMiddleware(flask_app)) # app.mount("/v1", WSGIMiddleware(flask_app))
# 在这里添加API route # 在这里添加API route
from src.api import example,auth_example from src.api import example
app.include_router(example.router, tags=["API示例"], prefix="/v1/example") app.include_router(example.router, tags=["API示例"], prefix="/example")
app.include_router(auth_example.router, tags=["认证示例"], prefix="/v1/auth_example")
return app return app

401
poetry.lock generated
View File

@ -127,31 +127,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "authx"
version = "1.3.0"
description = "Ready to use and customizable Authentications and Oauth2 management for FastAPI"
optional = false
python-versions = ">=3.8"
files = [
{file = "authx-1.3.0-py3-none-any.whl", hash = "sha256:347485b78a5016e655ac78b0ff0dd0c2aef3935148af4a863145399a20c718e8"},
{file = "authx-1.3.0.tar.gz", hash = "sha256:1f7c102a74d082c6a8850c26ff1e8fba73a146589fd95d84531df41e091586a5"},
]
[package.dependencies]
fastapi = ">=0.111.0"
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<3.0.0"
pydantic-settings = ">=2.1.0"
pyjwt = {version = ">=2.6.0,<3.0.0", extras = ["crypto"]}
python-dateutil = ">=2.8,<3.0.0"
python-jose = ">=3.3.0,<4.0.0"
pytz = ">=2023.3,<2025.0"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "captcha" name = "captcha"
version = "0.6.0" version = "0.6.0"
@ -187,90 +162,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "cffi"
version = "1.17.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
files = [
{file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"},
{file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"},
{file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"},
{file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"},
{file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"},
{file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"},
{file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"},
{file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"},
{file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"},
{file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"},
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"},
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"},
{file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"},
{file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"},
{file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"},
{file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"},
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"},
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"},
{file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"},
{file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"},
{file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"},
{file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"},
{file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"},
{file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"},
{file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"},
{file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"},
{file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"},
{file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"},
]
[package.dependencies]
pycparser = "*"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.3.2" version = "3.3.2"
@ -410,60 +301,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "cryptography"
version = "43.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
{file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
{file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
{file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
{file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
{file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
{file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
{file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
]
[package.dependencies]
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
[package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
nox = ["nox"]
pep8test = ["check-sdist", "click", "mypy", "ruff"]
sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "dnspython" name = "dnspython"
version = "2.6.1" version = "2.6.1"
@ -489,29 +326,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "ecdsa"
version = "0.19.0"
description = "ECDSA cryptographic signature library (pure python)"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"},
{file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"},
]
[package.dependencies]
six = ">=1.9.0"
[package.extras]
gmpy = ["gmpy"]
gmpy2 = ["gmpy2"]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "email-validator" name = "email-validator"
version = "2.1.1" version = "2.1.1"
@ -583,6 +397,26 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "fastapi-async-sqlalchemy"
version = "0.6.1"
description = "SQLAlchemy middleware for FastAPI"
optional = false
python-versions = ">=3.7"
files = [
{file = "fastapi-async-sqlalchemy-0.6.1.tar.gz", hash = "sha256:c4e0c9832e5e7ef9d647e7eb134e6d326945dca28323e503a21f3d4ab2dee160"},
{file = "fastapi_async_sqlalchemy-0.6.1-py3-none-any.whl", hash = "sha256:0f4edfbc7b0f5fc2e0017cd903a953f4e0b01870f09e86cd0bc79087f3606bc4"},
]
[package.dependencies]
SQLAlchemy = ">=1.4.19"
starlette = ">=0.13.6"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "fastapi-cli" name = "fastapi-cli"
version = "0.0.4" version = "0.0.4"
@ -605,26 +439,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "fastapi-limiter"
version = "0.1.6"
description = "A request rate limiter for fastapi"
optional = false
python-versions = ">=3.9,<4.0"
files = [
{file = "fastapi_limiter-0.1.6-py3-none-any.whl", hash = "sha256:2e53179a4208b8f2c8795e38bb001324d3dc37d2800ff49fd28ec5caabf7a240"},
{file = "fastapi_limiter-0.1.6.tar.gz", hash = "sha256:6f5fde8efebe12eb33861bdffb91009f699369a3c2862cdc7c1d9acf912ff443"},
]
[package.dependencies]
fastapi = "*"
redis = ">=4.2.0rc1"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "fastapi-plugins" name = "fastapi-plugins"
version = "0.13.0" version = "0.13.0"
@ -656,26 +470,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "fastapi-sqlalchemy"
version = "0.2.1"
description = "Adds simple SQLAlchemy support to FastAPI"
optional = false
python-versions = ">=3.7"
files = [
{file = "FastAPI-SQLAlchemy-0.2.1.tar.gz", hash = "sha256:7a9d44e46cbc73c3f5ee8c444f7e0bcd3d01370a878740abd4cd4d2e900ce9af"},
{file = "FastAPI_SQLAlchemy-0.2.1-py3-none-any.whl", hash = "sha256:d3bfc6d9388a73a2c3726bc6bd7764cd82debfa71c16e3991c544b9701f48d96"},
]
[package.dependencies]
SQLAlchemy = ">=1.2"
starlette = ">=0.12.9"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.0.3" version = "3.0.3"
@ -1015,22 +809,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "itsdangerous"
version = "2.2.0"
description = "Safely pass data to untrusted environments and back."
optional = false
python-versions = ">=3.8"
files = [
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.1.4" version = "3.1.4"
@ -1356,38 +1134,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "pyasn1"
version = "0.6.0"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
{file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]]
name = "pycparser"
version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.7.3" version = "2.7.3"
@ -1551,31 +1297,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "pyjwt"
version = "2.9.0"
description = "JSON Web Token implementation in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"},
{file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"},
]
[package.dependencies]
cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
[package.extras]
crypto = ["cryptography (>=3.4.0)"]
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"]
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "pymysql" name = "pymysql"
version = "1.1.1" version = "1.1.1"
@ -1596,25 +1317,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
six = ">=1.5"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.0.1" version = "1.0.1"
@ -1634,32 +1336,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "python-jose"
version = "3.3.0"
description = "JOSE implementation in Python"
optional = false
python-versions = "*"
files = [
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
]
[package.dependencies]
ecdsa = "!=0.15"
pyasn1 = "*"
rsa = "*"
[package.extras]
cryptography = ["cryptography (>=3.4.0)"]
pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"]
pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "python-json-logger" name = "python-json-logger"
version = "2.0.7" version = "2.0.7"
@ -1871,25 +1547,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "rsa"
version = "4.9"
description = "Pure-Python RSA implementation"
optional = false
python-versions = ">=3.6,<4"
files = [
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
]
[package.dependencies]
pyasn1 = ">=0.1.3"
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "shellingham" name = "shellingham"
version = "1.5.4" version = "1.5.4"
@ -1906,22 +1563,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" reference = "mirrors"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors"
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@ -2475,4 +2116,4 @@ reference = "mirrors"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "d43623d3a4da31efcc1a54e1318db9ef412e1791ae9af50374d0c5d6faae6357" content-hash = "a2d3f4dd231a21f18cf3bb4faabebaa59ee95ec5597b388c83326036eda1b458"

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "fastapi-template" name = "fastapi-app-template"
version = "0.1.5" version = "0.1.0"
description = "" description = ""
authors = ["chenwj113 <chenwj113@gmail.com>"] authors = ["chenwj113 <chenwj113@gmail.com>"]
license = "MIT" license = "MIT"
@ -13,15 +13,12 @@ sqlalchemy = "2.0.0"
aioredis = "2.0.1" aioredis = "2.0.1"
aiomysql = "0.1.1" aiomysql = "0.1.1"
fastapi-plugins = "0.13.0" fastapi-plugins = "0.13.0"
fastapi-sqlalchemy = "^0.2.1"
passlib = "^1.7.4" passlib = "^1.7.4"
pytz = "^2024.1" pytz = "^2024.1"
qiniu = "^7.13.2" qiniu = "^7.13.2"
pillow = "^10.4.0" pillow = "^10.4.0"
captcha = "^0.6.0" captcha = "^0.6.0"
fastapi-limiter = "^0.1.6" fastapi-async-sqlalchemy = "^0.6.1"
authx = "^1.3.0"
itsdangerous = "^2.2.0"
[[tool.poetry.source]] [[tool.poetry.source]]

View File

@ -1,75 +1,25 @@
--index-url https://pypi.tuna.tsinghua.edu.cn/simple aiofiles==22.1.0
aioredis==2.0.1
aiojobs==1.2.1 ; python_version >= "3.10" and python_version < "4.0" aiomysql==0.1.1
aiomysql==0.1.1 ; python_version >= "3.10" and python_version < "4.0" bcrypt==4.0.1
aioredis==2.0.1 ; python_version >= "3.10" and python_version < "4.0" email_validator>=2.0.0
annotated-types==0.7.0 ; python_version >= "3.10" and python_version < "4.0" fastapi==0.111.0
anyio==4.4.0 ; python_version >= "3.10" and python_version < "4.0" fastapi-plugins==0.13.0
async-timeout==4.0.3 ; python_version >= "3.10" and python_version < "4.0" FastAPI-SQLAlchemy==0.2.1
authx==1.3.0 ; python_version >= "3.10" and python_version < "4.0" pydantic>=2.0.0
captcha==0.6.0 ; python_version >= "3.10" and python_version < "4.0" pydantic-settings==2.2.1
certifi==2024.6.2 ; python_version >= "3.10" and python_version < "4.0" # pydantic-sqlalchemy==0.0.9
cffi==1.17.0 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" python-multipart>=0.0.7
charset-normalizer==3.3.2 ; python_version >= "3.10" and python_version < "4.0" pytest==7.2.1
click==8.1.7 ; python_version >= "3.10" and python_version < "4.0" requests==2.28.2
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and (sys_platform == "win32" or platform_system == "Windows") sqlacodegen==2.3.0
cryptography==43.0.0 ; python_version >= "3.10" and python_version < "4.0" SQLAlchemy==2.0.1
dnspython==2.6.1 ; python_version >= "3.10" and python_version < "4.0" uvicorn==0.20.0
ecdsa==0.19.0 ; python_version >= "3.10" and python_version < "4.0" PyJWT==2.6.0
email-validator==2.1.1 ; python_version >= "3.10" and python_version < "4.0" passlib==1.7.4
exceptiongroup==1.2.1 ; python_version >= "3.10" and python_version < "3.11" Pillow==9.4.0
fastapi-cli==0.0.4 ; python_version >= "3.10" and python_version < "4.0" captcha==0.4
fastapi-limiter==0.1.6 ; python_version >= "3.10" and python_version < "4.0" jinja2==3.1.2
fastapi-plugins==0.13.0 ; python_version >= "3.10" and python_version < "4.0" pycryptodome==3.17
fastapi-sqlalchemy==0.2.1 ; python_version >= "3.10" and python_version < "4.0" qiniu==7.10.0
fastapi==0.111.0 ; python_version >= "3.10" and python_version < "4.0" pytz==2022.7.1
greenlet==3.0.3 ; python_version >= "3.10" and python_version < "4.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32")
h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0"
hiredis==2.3.2 ; python_version >= "3.10" and python_version < "4.0"
httpcore==1.0.5 ; python_version >= "3.10" and python_version < "4.0"
httptools==0.6.1 ; python_version >= "3.10" and python_version < "4.0"
httpx==0.27.0 ; python_version >= "3.10" and python_version < "4.0"
idna==3.7 ; python_version >= "3.10" and python_version < "4.0"
itsdangerous==2.2.0 ; python_version >= "3.10" and python_version < "4.0"
jinja2==3.1.4 ; python_version >= "3.10" and python_version < "4.0"
markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0"
markupsafe==2.1.5 ; python_version >= "3.10" and python_version < "4.0"
mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0"
orjson==3.10.4 ; python_version >= "3.10" and python_version < "4.0"
passlib==1.7.4 ; python_version >= "3.10" and python_version < "4.0"
pillow==10.4.0 ; python_version >= "3.10" and python_version < "4.0"
pyasn1==0.6.0 ; python_version >= "3.10" and python_version < "4.0"
pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
pydantic-core==2.18.4 ; python_version >= "3.10" and python_version < "4.0"
pydantic-settings==2.3.1 ; python_version >= "3.10" and python_version < "4.0"
pydantic==2.7.3 ; python_version >= "3.10" and python_version < "4.0"
pygments==2.18.0 ; python_version >= "3.10" and python_version < "4.0"
pyjwt[crypto]==2.9.0 ; python_version >= "3.10" and python_version < "4.0"
pymysql==1.1.1 ; python_version >= "3.10" and python_version < "4.0"
python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0"
python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0"
python-jose==3.3.0 ; python_version >= "3.10" and python_version < "4.0"
python-json-logger==2.0.7 ; python_version >= "3.10" and python_version < "4.0"
python-multipart==0.0.9 ; python_version >= "3.10" and python_version < "4.0"
pytz==2024.1 ; python_version >= "3.10" and python_version < "4.0"
pyyaml==6.0.1 ; python_version >= "3.10" and python_version < "4.0"
qiniu==7.13.2 ; python_version >= "3.10" and python_version < "4.0"
redis==5.0.5 ; python_version >= "3.10" and python_version < "4.0"
redis[hiredis]==5.0.5 ; python_version >= "3.10" and python_version < "4.0"
requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0"
rich==13.7.1 ; python_version >= "3.10" and python_version < "4.0"
rsa==4.9 ; python_version >= "3.10" and python_version < "4"
shellingham==1.5.4 ; python_version >= "3.10" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.10" and python_version < "4.0"
sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0"
sqlalchemy==2.0.0 ; python_version >= "3.10" and python_version < "4.0"
starlette==0.37.2 ; python_version >= "3.10" and python_version < "4.0"
tenacity==8.3.0 ; python_version >= "3.10" and python_version < "4.0"
typer==0.12.3 ; python_version >= "3.10" and python_version < "4.0"
typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0"
ujson==5.10.0 ; python_version >= "3.10" and python_version < "4.0"
urllib3==2.2.2 ; python_version >= "3.10" and python_version < "4.0"
uvicorn[standard]==0.30.1 ; python_version >= "3.10" and python_version < "4.0"
uvloop==0.19.0 ; (sys_platform != "win32" and sys_platform != "cygwin") and platform_python_implementation != "PyPy" and python_version >= "3.10" and python_version < "4.0"
watchfiles==0.22.0 ; python_version >= "3.10" and python_version < "4.0"
websockets==12.0 ; python_version >= "3.10" and python_version < "4.0"

View File

@ -1,16 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends
# 校验授权token
from src.middleware.auth import security
router = APIRouter()
@router.get('/', dependencies=[Depends(security.access_token_required)])
def index():
return {"message": "Hello World"}
@router.get('/login')
def login(username: str, password: str):
if username == "test" and password == "test":
token = security.create_access_token(uid=username)
return {"access_token": token}
raise HTTPException(401, detail={"message": "Bad credentials"})

View File

@ -5,10 +5,8 @@ import aioredis
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from fastapi_plugins import depends_redis from fastapi_plugins import depends_redis
from fastapi_sqlalchemy import db from fastapi_async_sqlalchemy import db
from sqlalchemy.sql import text from sqlalchemy.sql import text
from fastapi_limiter.depends import RateLimiter
from pydantic import BaseModel from pydantic import BaseModel
from src.service.example import get_user_by_id from src.service.example import get_user_by_id
@ -17,8 +15,8 @@ from src.dtos.example import UserExampleListPagesResult
router = APIRouter() router = APIRouter()
# 并发数限制 每5秒最多100次请求
@router.get("/", dependencies=[Depends(RateLimiter(times=100, seconds=5))]) @router.get("/")
def index(): def index():
return {"msg": "This is Index Page"} return {"msg": "This is Index Page"}
@ -66,8 +64,10 @@ async def get_user_list_pages(page: int = Query(..., description="当前页码")
# 数据库查询 # 数据库查询
@router.get('/get_db_version') @router.get('/get_db_version')
async def get_db_version(): async def get_db_version():
result = db.session.execute(text("SELECT version()")).first() async with db():
return dict(result=result.tuple()[0]) result = await db.session.execute(text("SELECT version()"))
version = result.first().tuple()[0]
return dict(result=version)
# Redis 缓存查询 # Redis 缓存查询
@router.get("/ping") @router.get("/ping")

View File

@ -1,13 +0,0 @@
from authx import AuthX, AuthXConfig
from datetime import timedelta
config = AuthXConfig()
config.JWT_ALGORITHM = "HS256"
config.JWT_SECRET_KEY = "SECRET_KEY"
config.JWT_TOKEN_LOCATION = ["headers", "query", "cookies", "json"]
config.JWT_HEADER_NAME = "X-Token"
config.JWT_HEADER_TYPE = ""
# access token expires in 24 hours
config.JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24)
security = AuthX(config=config)

View File

@ -4,7 +4,7 @@ from traceback import format_exc
import struct import struct
import sqlalchemy import sqlalchemy
from sqlalchemy import text from sqlalchemy import text
from fastapi_sqlalchemy import db from fastapi_async_sqlalchemy import db
from jinja2 import Template from jinja2 import Template
from pymysql import err from pymysql import err
from pymysql.constants import FIELD_TYPE from pymysql.constants import FIELD_TYPE

View File

@ -1,10 +1,9 @@
import logging import logging
from collections import defaultdict
from fastapi import Request, status from fastapi import Request, status
from fastapi.exceptions import HTTPException, RequestValidationError from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
import authx.exceptions as authx_exceptions
from src.dtos import BaseResponse, ErrorModel from src.dtos import BaseResponse, ErrorModel
logger = logging.getLogger("uvicorn.error") logger = logging.getLogger("uvicorn.error")
@ -12,32 +11,35 @@ logger = logging.getLogger("uvicorn.error")
def request_validation_error_handler(req: Request, exc: RequestValidationError): def request_validation_error_handler(req: Request, exc: RequestValidationError):
""" """
请求校验错误处理 请求响应校验错误处理
:param req: :param req:
:param exc: :param exc:
:return: :return:
""" """
errors = exc.errors() errors = exc.errors()
logger.error(f"{req.method} {req.url} , errors: {errors}") logger.error(f"{req.method} {req.url} , errors: {errors}")
group_err = defaultdict(list) response_errors = [error for error in errors if error["loc"][0] == "response"]
for err in errors: if len(response_errors) > 0:
group_err[err["type"]].append(err) message = "接口响应异常 "
details = "" else:
for err_type, err_list in group_err.items(): message = "接口请求异常 "
match err_type: json_err = [err["msg"] for err in errors if "value_error.jsondecode" == err["type"]]
case "missing" | "value_error.missing" : if json_err:
details += f"缺少字段:{', '.join([err['loc'][-1] for err in err_list])};" message += "请求参数JSON编码有误;"
case "value_error.jsondecode": value_err = [err["msg"] for err in errors if "value_error" == err["type"]]
details += "请求参数JSON编码有误;" if value_err:
case "value_error": message += "; ".join(value_err)
details += "; ".join([err["msg"] for err in err_list]) missing_err = [err["loc"][-1] for err in errors if "value_error.missing" == err["type"]]
case "type_error": if missing_err:
details += "; ".join([err["msg"].replace("value", f"{err['loc'][1]}") for err in err_list]) message += "缺少字段:{} ".format(", ".join(missing_err))
case "type_error.none.not_allowed": type_err = [err["msg"].replace("value", f"{err['loc'][1]}") for err in errors if "type_error" == err["type"]]
details += f"字段:{', '.join([err['loc'][-1] for err in err_list])} 不能为空;" if type_err:
case _: message += "; ".join(type_err)
pass not_none_err = [err["loc"][-1] for err in errors if "type_error.none.not_allowed" == err["type"]]
err_model = ErrorModel(code=status.HTTP_400_BAD_REQUEST, message="接口请求校验异常", details=details) if not_none_err:
message += "字段:{} 不能为空".format(", ".join(not_none_err))
err_model = ErrorModel(code=status.HTTP_400_BAD_REQUEST, message=message, details=message)
res = BaseResponse(error=err_model) res = BaseResponse(error=err_model)
return JSONResponse(status_code=200, content=res.dict()) return JSONResponse(status_code=200, content=res.dict())
@ -52,23 +54,4 @@ def http_exception_handler(req: Request, exc: HTTPException):
logger.error(f"{req.method} {req.url} , exception:{exc.detail}") logger.error(f"{req.method} {req.url} , exception:{exc.detail}")
err = ErrorModel(code=exc.status_code, message=exc.detail) err = ErrorModel(code=exc.status_code, message=exc.detail)
res = BaseResponse(result=None, error=err) res = BaseResponse(result=None, error=err)
return JSONResponse(status_code=exc.status_code, content=res.model_dump()) return JSONResponse(status_code=exc.status_code, content=res.dict())
def authx_exception_handler(req: Request, exc: authx_exceptions.AuthXException):
"""
AuthX Exception 错误处理
:param req:
:param exc:
:return:
"""
logger.error(f"{req.method} {req.url} , exception:{exc}")
match type(exc):
case authx_exceptions.MissingTokenError:
err = ErrorModel(code=status.HTTP_401_UNAUTHORIZED, message="请求头缺失X-Token")
case authx_exceptions.JWTDecodeError:
err = ErrorModel(code=status.HTTP_401_UNAUTHORIZED, message="X-Token解析失败")
case _:
err = ErrorModel(code=status.HTTP_401_UNAUTHORIZED, message="未知的错误导致认证失败")
res = BaseResponse(result=None, error=err)
return JSONResponse(status_code=err.code, content=res.model_dump())