mirror of
https://github.com/fastapi-practices/fastapi-best-architecture.git
synced 2026-09-21 13:12:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfc715aad8 | ||
|
|
4bc5ba53e6 | ||
|
|
6b4fd93e5f | ||
|
|
0c5956f828 | ||
|
|
122d17ab57 | ||
|
|
4fb14e480a | ||
|
|
ea4ac6ff41 | ||
|
|
bb5bcdf430 | ||
|
|
aad9afa9bb | ||
|
|
a0e6e12d85 | ||
|
|
5d680ff93f | ||
|
|
866b0e6ba4 | ||
|
|
5a49d20c9b | ||
|
|
7641d5993a |
@@ -1,3 +1,33 @@
|
||||
<a id="v1.11.2"></a>
|
||||
# [v1.11.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.11.2) - 2025-11-28
|
||||
|
||||
## What's Changed
|
||||
* Update changelog for v1.11.1 by [@wu-clan](https://github.com/wu-clan) in [#923](https://github.com/fastapi-practices/fastapi_best_architecture/pull/923)
|
||||
* Fix typos in data rule sql scripts by [@wu-clan](https://github.com/wu-clan) in [#926](https://github.com/fastapi-practices/fastapi_best_architecture/pull/926)
|
||||
* Restore captcha uuid naming in the login params by [@wu-clan](https://github.com/wu-clan) in [#928](https://github.com/fastapi-practices/fastapi_best_architecture/pull/928)
|
||||
* Add distributed deployment support for snowflake ID by [@downdawn](https://github.com/downdawn) in [#927](https://github.com/fastapi-practices/fastapi_best_architecture/pull/927)
|
||||
* Add env reqs for plugin install and uninstall by [@wu-clan](https://github.com/wu-clan) in [#929](https://github.com/fastapi-practices/fastapi_best_architecture/pull/929)
|
||||
* Optimize the use of some LRU caches by [@wu-clan](https://github.com/wu-clan) in [#932](https://github.com/fastapi-practices/fastapi_best_architecture/pull/932)
|
||||
* Update the i18n language file init location by [@wu-clan](https://github.com/wu-clan) in [#934](https://github.com/fastapi-practices/fastapi_best_architecture/pull/934)
|
||||
* Fix get column types in code generation by [@wu-clan](https://github.com/wu-clan) in [#935](https://github.com/fastapi-practices/fastapi_best_architecture/pull/935)
|
||||
* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#936](https://github.com/fastapi-practices/fastapi_best_architecture/pull/936)
|
||||
* Update the files interface filename to file by [@wu-clan](https://github.com/wu-clan) in [#937](https://github.com/fastapi-practices/fastapi_best_architecture/pull/937)
|
||||
* Update task application interface definitions by [@wu-clan](https://github.com/wu-clan) in [#938](https://github.com/fastapi-practices/fastapi_best_architecture/pull/938)
|
||||
* Update code generation interface definitions by [@wu-clan](https://github.com/wu-clan) in [#939](https://github.com/fastapi-practices/fastapi_best_architecture/pull/939)
|
||||
* Update the fba run CLI command output by [@wu-clan](https://github.com/wu-clan) in [#941](https://github.com/fastapi-practices/fastapi_best_architecture/pull/941)
|
||||
* Update the version number to 1.11.2 by [@wu-clan](https://github.com/wu-clan) in [#940](https://github.com/fastapi-practices/fastapi_best_architecture/pull/940)
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.1...v1.11.2
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/downdawn"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdowndawn.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@downdawn"></a>
|
||||
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
|
||||
|
||||
[Changes][v1.11.2]
|
||||
|
||||
|
||||
<a id="v1.11.1"></a>
|
||||
# [v1.11.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.11.1) - 2025-11-16
|
||||
|
||||
@@ -1161,6 +1191,7 @@
|
||||
[Changes][v1.0.0]
|
||||
|
||||
|
||||
[v1.11.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.1...v1.11.2
|
||||
[v1.11.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.0...v1.11.1
|
||||
[v1.11.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.4...v1.11.0
|
||||
[v1.10.4]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.3...v1.10.4
|
||||
|
||||
+24
-8
@@ -36,33 +36,49 @@ COPY --from=builder /fba /fba
|
||||
|
||||
COPY --from=builder /usr/local /usr/local
|
||||
|
||||
COPY deploy/backend/supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY deploy/backend/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
||||
WORKDIR /fba/backend
|
||||
|
||||
# === FastAPI server image ===
|
||||
FROM base_server AS fba_server
|
||||
|
||||
COPY deploy/backend/fba_server.conf /etc/supervisor/conf.d/
|
||||
COPY deploy/backend/supervisor/fba_server.conf /etc/supervisor/conf.d/
|
||||
|
||||
RUN mkdir -p /var/log/fba
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
CMD ["/usr/local/bin/granian", "main:app", "--interface", "asgi", "--host", "0.0.0.0", "--port","8000"]
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
|
||||
# === Celery server image ===
|
||||
FROM base_server AS fba_celery
|
||||
# === Celery Worker image ===
|
||||
FROM base_server AS fba_celery_worker
|
||||
|
||||
COPY deploy/backend/fba_celery.conf /etc/supervisor/conf.d/
|
||||
COPY deploy/backend/supervisor/fba_celery_worker.conf /etc/supervisor/conf.d/
|
||||
|
||||
RUN mkdir -p /var/log/fba
|
||||
|
||||
RUN chmod +x celery-start.sh
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
|
||||
# === Celery Beat image ===
|
||||
FROM base_server AS fba_celery_beat
|
||||
|
||||
COPY deploy/backend/supervisor/fba_celery_beat.conf /etc/supervisor/conf.d/
|
||||
|
||||
RUN mkdir -p /var/log/fba
|
||||
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
|
||||
# === Celery Flower image ===
|
||||
FROM base_server AS fba_celery_flower
|
||||
|
||||
COPY deploy/backend/supervisor/fba_celery_flower.conf /etc/supervisor/conf.d/
|
||||
|
||||
RUN mkdir -p /var/log/fba
|
||||
|
||||
EXPOSE 8555
|
||||
|
||||
CMD ["./celery-start.sh"]
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
|
||||
# Build image
|
||||
FROM ${SERVER_TYPE}
|
||||
|
||||
@@ -74,8 +74,6 @@
|
||||
|
||||
- `pre_start.sh`: Perform automatic database migration
|
||||
|
||||
- `celery-start.sh`: For celery docker script, implementation is not recommended
|
||||
|
||||
- `scripts/format.sh`: Perform ruff format check
|
||||
|
||||
- `scripts/lint.sh`: Perform prek formatting
|
||||
|
||||
+17
-1
@@ -1 +1,17 @@
|
||||
__version__ = '1.11.2'
|
||||
import sqlalchemy as sa
|
||||
|
||||
from backend.utils.import_parse import get_all_models
|
||||
|
||||
# import all models for auto create db tables
|
||||
for cls in get_all_models():
|
||||
if isinstance(cls, sa.Table):
|
||||
table_name = cls.name
|
||||
if table_name not in globals():
|
||||
globals()[table_name] = cls
|
||||
else:
|
||||
class_name = cls.__name__
|
||||
if class_name not in globals():
|
||||
globals()[class_name] = cls
|
||||
|
||||
|
||||
__version__ = '1.12.0'
|
||||
|
||||
@@ -8,17 +8,9 @@ from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
|
||||
from backend.app import get_app_models
|
||||
from backend.common.model import MappedBase
|
||||
from backend.core import path_conf
|
||||
from backend.database.db import SQLALCHEMY_DATABASE_URL
|
||||
from backend.plugin.tools import get_plugin_models
|
||||
|
||||
# import models
|
||||
for cls in get_app_models() + get_plugin_models():
|
||||
class_name = cls.__name__
|
||||
if class_name not in globals():
|
||||
globals()[class_name] = cls
|
||||
|
||||
if not os.path.exists(path_conf.ALEMBIC_VERSION_DIR):
|
||||
os.makedirs(path_conf.ALEMBIC_VERSION_DIR)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import os.path
|
||||
|
||||
from backend.core.path_conf import BASE_PATH
|
||||
from backend.utils.import_parse import get_model_objects
|
||||
|
||||
|
||||
def get_app_models() -> list[type]:
|
||||
"""获取 app 所有模型类"""
|
||||
app_path = BASE_PATH / 'app'
|
||||
list_dirs = os.listdir(app_path)
|
||||
|
||||
apps = [d for d in list_dirs if os.path.isdir(os.path.join(app_path, d)) and d != '__pycache__']
|
||||
|
||||
objs = []
|
||||
for app in apps:
|
||||
module_path = f'backend.app.{app}.model'
|
||||
obj = get_model_objects(module_path)
|
||||
if obj:
|
||||
objs.extend(obj)
|
||||
|
||||
return objs
|
||||
|
||||
|
||||
# import all app models for auto create db tables
|
||||
for cls in get_app_models():
|
||||
class_name = cls.__name__
|
||||
if class_name not in globals():
|
||||
globals()[class_name] = cls
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Path, Query, Request
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from sqlalchemy import ColumnElement
|
||||
|
||||
from backend.app.admin.model import Dept
|
||||
from backend.app.admin.schema.dept import CreateDeptParam, GetDeptDetail, GetDeptTree, UpdateDeptParam
|
||||
from backend.app.admin.service.dept_service import dept_service
|
||||
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
|
||||
from backend.common.security.jwt import DependsJwtAuth
|
||||
from backend.common.security.permission import RequestPermission
|
||||
from backend.common.security.permission import DataPermissionFilter, RequestPermission
|
||||
from backend.common.security.rbac import DependsRBAC
|
||||
from backend.database.db import CurrentSession, CurrentSessionTransaction
|
||||
|
||||
@@ -24,14 +26,14 @@ async def get_dept(
|
||||
@router.get('', summary='获取部门树', dependencies=[DependsJwtAuth])
|
||||
async def get_dept_tree(
|
||||
db: CurrentSession,
|
||||
request: Request,
|
||||
data_filter: Annotated[ColumnElement[bool], Depends(DataPermissionFilter(Dept))],
|
||||
name: Annotated[str | None, Query(description='部门名称')] = None,
|
||||
leader: Annotated[str | None, Query(description='部门负责人')] = None,
|
||||
phone: Annotated[str | None, Query(description='联系电话')] = None,
|
||||
status: Annotated[int | None, Query(description='状态')] = None,
|
||||
) -> ResponseSchemaModel[list[GetDeptTree]]:
|
||||
dept = await dept_service.get_tree(
|
||||
db=db, request_user=request.user, name=name, leader=leader, phone=phone, status=status
|
||||
db=db, data_filter=data_filter, name=name, leader=leader, phone=phone, status=status
|
||||
)
|
||||
return response_base.success(data=dept)
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import ColumnElement
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy_crud_plus import CRUDPlus, JoinConfig
|
||||
|
||||
from backend.app.admin.model import Dept, User
|
||||
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
|
||||
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
|
||||
from backend.common.security.permission import filter_data_permission
|
||||
from backend.utils.serializers import select_join_serialize
|
||||
|
||||
|
||||
@@ -37,7 +36,7 @@ class CRUDDept(CRUDPlus[Dept]):
|
||||
async def get_all(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
request_user: GetUserInfoWithRelationDetail,
|
||||
data_filter: ColumnElement[bool],
|
||||
name: str | None,
|
||||
leader: str | None,
|
||||
phone: str | None,
|
||||
@@ -47,7 +46,7 @@ class CRUDDept(CRUDPlus[Dept]):
|
||||
获取所有部门
|
||||
|
||||
:param db: 数据库会话
|
||||
:param request_user: 请求用户
|
||||
:param data_filter: 请求用户
|
||||
:param name: 部门名称
|
||||
:param leader: 负责人
|
||||
:param phone: 联系电话
|
||||
@@ -65,7 +64,6 @@ class CRUDDept(CRUDPlus[Dept]):
|
||||
if status is not None:
|
||||
filters['status'] = status
|
||||
|
||||
data_filter = filter_data_permission(request_user)
|
||||
return await self.select_models_order(db, 'sort', 'desc', data_filter, **filters)
|
||||
|
||||
async def create(self, db: AsyncSession, obj: CreateDeptParam) -> None:
|
||||
|
||||
@@ -25,7 +25,7 @@ from backend.app.admin.schema.user import (
|
||||
UpdateUserParam,
|
||||
)
|
||||
from backend.app.admin.utils.password_security import get_hash_password
|
||||
from backend.plugin.oauth2.crud.crud_user_social import user_social_dao
|
||||
from backend.utils.import_parse import import_module_cached
|
||||
from backend.utils.serializers import select_join_serialize
|
||||
from backend.utils.timezone import timezone
|
||||
|
||||
@@ -299,7 +299,13 @@ class CRUDUser(CRUDPlus[User]):
|
||||
user_role_stmt = delete(user_role).where(user_role.c.user_id == user_id)
|
||||
await db.execute(user_role_stmt)
|
||||
|
||||
await user_social_dao.delete_by_user_id(db, user_id)
|
||||
try:
|
||||
user_social = import_module_cached('backend.plugin.oauth2.crud.crud_user_social')
|
||||
user_social_dao = user_social.user_social_dao
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
await user_social_dao.delete_by_user_id(db, user_id)
|
||||
|
||||
return await self.delete_model(db, user_id)
|
||||
|
||||
|
||||
@@ -45,4 +45,4 @@ class GetDataRuleColumnDetail(SchemaBase):
|
||||
"""数据规则可用模型字段详情"""
|
||||
|
||||
key: str = Field(description='字段名')
|
||||
comment: str = Field(description='字段评论')
|
||||
comment: str | None = Field(description='字段评论')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.app.admin.crud.crud_data_rule import data_rule_dao
|
||||
@@ -14,8 +15,8 @@ from backend.app.admin.schema.data_rule import (
|
||||
from backend.app.admin.utils.cache import user_cache_manager
|
||||
from backend.common.exception import errors
|
||||
from backend.common.pagination import paging_data
|
||||
from backend.common.security.permission import get_data_permission_models
|
||||
from backend.core.conf import settings
|
||||
from backend.utils.import_parse import dynamic_import_data_model
|
||||
|
||||
|
||||
class DataRuleService:
|
||||
@@ -39,7 +40,8 @@ class DataRuleService:
|
||||
@staticmethod
|
||||
async def get_models() -> list[str]:
|
||||
"""获取所有数据规则可用模型"""
|
||||
return list(settings.DATA_PERMISSION_MODELS.keys())
|
||||
model_exclude = ['DataScope', 'DataRule', 'sys_role_data_scope', 'sys_data_scope_rule']
|
||||
return [m for m in list(get_data_permission_models().keys()) if m not in model_exclude]
|
||||
|
||||
@staticmethod
|
||||
async def get_columns(model: str) -> list[GetDataRuleColumnDetail]:
|
||||
@@ -49,13 +51,15 @@ class DataRuleService:
|
||||
:param model: 模型名称
|
||||
:return:
|
||||
"""
|
||||
if model not in settings.DATA_PERMISSION_MODELS:
|
||||
available_models = get_data_permission_models()
|
||||
if model not in available_models:
|
||||
raise errors.NotFoundError(msg='数据规则可用模型不存在')
|
||||
model_ins = dynamic_import_data_model(settings.DATA_PERMISSION_MODELS[model])
|
||||
model_ins = available_models[model]
|
||||
|
||||
table = model_ins if isinstance(model_ins, Table) else model_ins.__table__
|
||||
model_columns = [
|
||||
GetDataRuleColumnDetail(key=column.key, comment=column.comment)
|
||||
for column in model_ins.__table__.columns
|
||||
for column in table.columns
|
||||
if column.key not in settings.DATA_PERMISSION_COLUMN_EXCLUDE
|
||||
]
|
||||
return model_columns
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import ColumnElement
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.app.admin.crud.crud_dept import dept_dao
|
||||
from backend.app.admin.model import Dept
|
||||
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
|
||||
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
|
||||
from backend.common.exception import errors
|
||||
from backend.core.conf import settings
|
||||
from backend.database.redis import redis_client
|
||||
@@ -34,7 +34,7 @@ class DeptService:
|
||||
async def get_tree(
|
||||
*,
|
||||
db: AsyncSession,
|
||||
request_user: GetUserInfoWithRelationDetail,
|
||||
data_filter: ColumnElement[bool],
|
||||
name: str | None,
|
||||
leader: str | None,
|
||||
phone: str | None,
|
||||
@@ -44,15 +44,14 @@ class DeptService:
|
||||
获取部门树形结构
|
||||
|
||||
:param db: 数据库会话
|
||||
:param request_user: 请求用户
|
||||
:param data_filter: 请求用户
|
||||
:param name: 部门名称
|
||||
:param leader: 部门负责人
|
||||
:param phone: 联系电话
|
||||
:param status: 状态
|
||||
:return:
|
||||
"""
|
||||
|
||||
dept_select = await dept_dao.get_all(db, request_user, name, leader, phone, status)
|
||||
dept_select = await dept_dao.get_all(db, data_filter, name, leader, phone, status)
|
||||
tree_data = get_tree_data(dept_select)
|
||||
return tree_data
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ class UserService:
|
||||
raise errors.NotFoundError(msg='用户不存在')
|
||||
if pk == request.user.id:
|
||||
raise errors.ForbiddenError(msg='禁止修改自身权限')
|
||||
count = await user_dao.set_super(db, pk, is_super=not user.status)
|
||||
count = await user_dao.set_super(db, pk, is_super=not user.is_superuser)
|
||||
case UserPermissionType.staff:
|
||||
user = await user_dao.get(db, pk)
|
||||
if not user:
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
如果你想对任务进行目录层级划分,使任务结构更加清晰,你可以新建任意目录,但必须注意的是
|
||||
|
||||
1. 在 `backend/app/task/tasks` 目录下新建 python 包目录
|
||||
2. 新建目录后,务必更新 `conf.py` 配置中的 `CELERY_TASKS_PACKAGES`,将新建目录模块路径添加到此列表
|
||||
3. 在新建目录下,务必添加 `tasks.py` 文件,并在此文件中编写相关任务代码
|
||||
2. 在新建目录下,务必添加 `tasks.py` 文件,并在此文件中编写相关任务代码
|
||||
|
||||
## 消息代理
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import celery
|
||||
import celery_aio_pool
|
||||
|
||||
from backend.app.task.tasks.beat import LOCAL_BEAT_SCHEDULE
|
||||
from backend.common.enums import DataBaseType
|
||||
from backend.core.conf import settings
|
||||
from backend.core.path_conf import BASE_PATH
|
||||
|
||||
@@ -32,7 +33,7 @@ def init_celery() -> celery.Celery:
|
||||
broker_url = f'redis://:{settings.REDIS_PASSWORD}@{settings.REDIS_HOST}:{settings.REDIS_PORT}/{settings.CELERY_BROKER_REDIS_DATABASE}'
|
||||
|
||||
result_backend = f'db+postgresql+psycopg://{settings.DATABASE_USER}:{settings.DATABASE_PASSWORD}@{settings.DATABASE_HOST}:{settings.DATABASE_PORT}/{settings.DATABASE_SCHEMA}'
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
result_backend = result_backend.replace('postgresql+psycopg', 'mysql+pymysql')
|
||||
|
||||
# https://docs.celeryq.dev/en/stable/userguide/configuration.html
|
||||
@@ -51,6 +52,8 @@ def init_celery() -> celery.Celery:
|
||||
task_track_started=True,
|
||||
enable_utc=False,
|
||||
timezone=settings.DATETIME_TIMEZONE,
|
||||
worker_send_task_events=True,
|
||||
task_send_sent_event=True,
|
||||
)
|
||||
|
||||
# 在 Celery 中设置此参数无效
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# work && beat
|
||||
celery -A backend.app.task.celery worker -l info -P gevent -c 100 &
|
||||
|
||||
# beat
|
||||
celery -A backend.app.task.celery beat -l info &
|
||||
|
||||
# flower
|
||||
celery -A backend.app.task.celery flower --port=8555 --basic-auth=admin:123456
|
||||
+133
-20
@@ -5,12 +5,13 @@ import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, Literal
|
||||
|
||||
import anyio
|
||||
import cappa
|
||||
import granian
|
||||
|
||||
from cappa.output import error_format
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import IntPrompt
|
||||
from rich.prompt import IntPrompt, Prompt
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
from sqlalchemy import text
|
||||
@@ -20,14 +21,13 @@ from backend import __version__
|
||||
from backend.common.enums import DataBaseType, PrimaryKeyType
|
||||
from backend.common.exception.errors import BaseExceptionError
|
||||
from backend.core.conf import settings
|
||||
from backend.database.db import async_db_session
|
||||
from backend.plugin.code_generator.schema.code import ImportParam
|
||||
from backend.plugin.code_generator.service.business_service import gen_business_service
|
||||
from backend.plugin.code_generator.service.code_service import gen_service
|
||||
from backend.core.path_conf import BASE_PATH
|
||||
from backend.database.db import async_db_session, create_tables, drop_tables
|
||||
from backend.database.redis import redis_client
|
||||
from backend.plugin.tools import get_plugin_sql, get_plugins
|
||||
from backend.utils._await import run_await
|
||||
from backend.utils.console import console
|
||||
from backend.utils.file_ops import install_git_plugin, install_zip_plugin, parse_sql_script
|
||||
from backend.utils.import_parse import import_module_cached
|
||||
|
||||
output_help = '\n更多信息,尝试 "[cyan]--help[/]"'
|
||||
|
||||
@@ -39,6 +39,63 @@ class CustomReloadFilter(PythonFilter):
|
||||
super().__init__(extra_extensions=['.json', '.yaml', '.yml'])
|
||||
|
||||
|
||||
async def init() -> None:
|
||||
panel_content = Text()
|
||||
panel_content.append('【数据库配置】', style='bold green')
|
||||
panel_content.append('\n\n • 类型: ')
|
||||
panel_content.append(f'{settings.DATABASE_TYPE}', style='yellow')
|
||||
panel_content.append('\n • 数据库:')
|
||||
panel_content.append(f'{settings.DATABASE_SCHEMA}', style='yellow')
|
||||
panel_content.append('\n • 主键模式:')
|
||||
panel_content.append(
|
||||
f'{settings.DATABASE_PK_MODE}',
|
||||
style='yellow',
|
||||
)
|
||||
pk_details = panel_content.from_markup(
|
||||
'[link=https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html](了解详情)[/]'
|
||||
)
|
||||
panel_content.append(pk_details)
|
||||
panel_content.append('\n\n【Redis 配置】', style='bold green')
|
||||
panel_content.append('\n\n • 数据库:')
|
||||
panel_content.append(f'{settings.REDIS_DATABASE}', style='yellow')
|
||||
plugins = get_plugins()
|
||||
panel_content.append('\n\n【已安装插件】', style='bold green')
|
||||
panel_content.append('\n\n • ')
|
||||
if plugins:
|
||||
panel_content.append(f'{", ".join(plugins)}', style='yellow')
|
||||
else:
|
||||
panel_content.append('无', style='dim')
|
||||
|
||||
console.print(Panel(panel_content, title=f'fba v{__version__} 初始化', border_style='cyan', padding=(1, 2)))
|
||||
ok = Prompt.ask(
|
||||
'即将[red]重建数据库表[/red]并[red]执行所有 SQL 脚本[/red],确认继续吗?', choices=['y', 'n'], default='n'
|
||||
)
|
||||
|
||||
if ok.lower() == 'y':
|
||||
console.print('开始初始化...', style='white')
|
||||
try:
|
||||
console.print('丢弃数据库表', style='white')
|
||||
await drop_tables()
|
||||
console.print('丢弃 Redis 缓存', style='white')
|
||||
await redis_client.delete_prefix(settings.JWT_USER_REDIS_PREFIX)
|
||||
await redis_client.delete_prefix(settings.TOKEN_EXTRA_INFO_REDIS_PREFIX)
|
||||
await redis_client.delete_prefix(settings.TOKEN_REDIS_PREFIX)
|
||||
await redis_client.delete_prefix(settings.TOKEN_REFRESH_REDIS_PREFIX)
|
||||
console.print('创建数据库表', style='white')
|
||||
await create_tables()
|
||||
console.print('执行 SQL 脚本', style='white')
|
||||
sql_scripts = await get_sql_scripts()
|
||||
for sql_script in sql_scripts:
|
||||
console.print(f'正在执行:{sql_script}', style='white')
|
||||
await execute_sql_scripts(sql_script, is_init=True)
|
||||
console.print('初始化成功', style='green')
|
||||
console.print('\n快试试 [bold cyan]fba run[/bold cyan] 启动服务吧~')
|
||||
except Exception as e:
|
||||
raise cappa.Exit(f'初始化失败:{e}', code=1)
|
||||
else:
|
||||
console.print('已取消初始化', style='yellow')
|
||||
|
||||
|
||||
def run(host: str, port: int, reload: bool, workers: int) -> None: # noqa: FBT001
|
||||
url = f'http://{host}:{port}'
|
||||
docs_url = url + settings.FASTAPI_DOCS_URL
|
||||
@@ -124,7 +181,7 @@ async def install_plugin(
|
||||
raise cappa.Exit('path 和 repo_url 不能同时指定', code=1)
|
||||
|
||||
plugin_name = None
|
||||
console.print(Text('开始安装插件...', style='bold cyan'))
|
||||
console.print('开始安装插件...', style='bold cyan')
|
||||
|
||||
try:
|
||||
if path:
|
||||
@@ -132,18 +189,44 @@ async def install_plugin(
|
||||
if repo_url:
|
||||
plugin_name = await install_git_plugin(repo_url=repo_url)
|
||||
|
||||
console.print(Text(f'插件 {plugin_name} 安装成功', style='bold green'))
|
||||
console.print(f'插件 {plugin_name} 安装成功', style='bold green')
|
||||
|
||||
sql_file = await get_plugin_sql(plugin_name, db_type, pk_type)
|
||||
if sql_file and not no_sql:
|
||||
console.print(Text('开始自动执行插件 SQL 脚本...', style='bold cyan'))
|
||||
console.print('开始自动执行插件 SQL 脚本...', style='bold cyan')
|
||||
await execute_sql_scripts(sql_file)
|
||||
|
||||
except Exception as e:
|
||||
raise cappa.Exit(e.msg if isinstance(e, BaseExceptionError) else str(e), code=1)
|
||||
|
||||
|
||||
async def execute_sql_scripts(sql_scripts: str) -> None:
|
||||
async def get_sql_scripts() -> list[str]:
|
||||
sql_scripts = []
|
||||
db_dir = (
|
||||
BASE_PATH / 'sql' / 'mysql'
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE
|
||||
else BASE_PATH / 'sql' / 'postgresql'
|
||||
)
|
||||
main_sql_file = (
|
||||
db_dir / 'init_test_data.sql'
|
||||
if PrimaryKeyType.autoincrement == settings.DATABASE_PK_MODE
|
||||
else db_dir / 'init_snowflake_test_data.sql'
|
||||
)
|
||||
|
||||
main_sql_path = anyio.Path(main_sql_file)
|
||||
if await main_sql_path.exists():
|
||||
sql_scripts.append(str(main_sql_file))
|
||||
|
||||
plugins = get_plugins()
|
||||
for plugin in plugins:
|
||||
plugin_sql = await get_plugin_sql(plugin, settings.DATABASE_TYPE, settings.DATABASE_PK_MODE)
|
||||
if plugin_sql:
|
||||
sql_scripts.append(str(plugin_sql))
|
||||
|
||||
return sql_scripts
|
||||
|
||||
|
||||
async def execute_sql_scripts(sql_scripts: str, *, is_init: bool = False) -> None:
|
||||
async with async_db_session.begin() as db:
|
||||
try:
|
||||
stmts = await parse_sql_script(sql_scripts)
|
||||
@@ -152,7 +235,8 @@ async def execute_sql_scripts(sql_scripts: str) -> None:
|
||||
except Exception as e:
|
||||
raise cappa.Exit(f'SQL 脚本执行失败:{e}', code=1)
|
||||
|
||||
console.print(Text('SQL 脚本已执行完成', style='bold green'))
|
||||
if not is_init:
|
||||
console.print('SQL 脚本已执行完成', style='bold green')
|
||||
|
||||
|
||||
async def import_table(
|
||||
@@ -160,18 +244,27 @@ async def import_table(
|
||||
table_schema: str,
|
||||
table_name: str,
|
||||
) -> None:
|
||||
from backend.plugin.code_generator.schema.code import ImportParam
|
||||
from backend.plugin.code_generator.service.code_service import gen_service
|
||||
|
||||
try:
|
||||
obj = ImportParam(app=app, table_schema=table_schema, table_name=table_name)
|
||||
async with async_db_session.begin() as db:
|
||||
await gen_service.import_business_and_model(db=db, obj=obj)
|
||||
console.log('代码生成业务和模型列导入成功', style='bold green')
|
||||
console.log('\n快试试 [bold cyan]fba codegen[/bold cyan] 生成代码吧~')
|
||||
except Exception as e:
|
||||
raise cappa.Exit(e.msg if isinstance(e, BaseExceptionError) else str(e), code=1)
|
||||
|
||||
|
||||
def generate() -> None:
|
||||
async def generate() -> None:
|
||||
from backend.plugin.code_generator.service.business_service import gen_business_service
|
||||
from backend.plugin.code_generator.service.code_service import gen_service
|
||||
|
||||
try:
|
||||
ids = []
|
||||
results = run_await(gen_business_service.get_all)()
|
||||
async with async_db_session() as db:
|
||||
results = await gen_business_service.get_all(db=db)
|
||||
|
||||
if not results:
|
||||
raise cappa.Exit('[red]暂无可用的代码生成业务!请先通过 import 命令导入![/]')
|
||||
@@ -194,12 +287,20 @@ def generate() -> None:
|
||||
console.print(table)
|
||||
business = IntPrompt.ask('请从中选择一个业务编号', choices=[str(_id) for _id in ids])
|
||||
|
||||
gen_path = run_await(gen_service.generate)(pk=business)
|
||||
async with async_db_session.begin() as db:
|
||||
gen_path = await gen_service.generate(db=db, pk=business)
|
||||
except Exception as e:
|
||||
raise cappa.Exit(e.msg if isinstance(e, BaseExceptionError) else str(e), code=1)
|
||||
|
||||
console.print(Text('\n代码已生成完毕', style='bold green'))
|
||||
console.print(Text('\n详情请查看:'), Text(gen_path, style='bold magenta'))
|
||||
console.print('\n代码已生成完成', style='bold green')
|
||||
console.print(Text('\n详情请查看:'), Text(str(gen_path), style='bold magenta'))
|
||||
|
||||
|
||||
@cappa.command(help='初始化 fba 项目', default_long=True)
|
||||
@dataclass
|
||||
class Init:
|
||||
async def __call__(self) -> None:
|
||||
await init()
|
||||
|
||||
|
||||
@cappa.command(help='运行 API 服务', default_long=True)
|
||||
@@ -320,17 +421,29 @@ class Import:
|
||||
cappa.Arg(short='tn', help='数据库表名'),
|
||||
]
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
try:
|
||||
import_module_cached('backend.plugin.code_generator')
|
||||
except ImportError:
|
||||
raise cappa.Exit('代码生成插件不存在,请先安装此插件')
|
||||
|
||||
async def __call__(self) -> None:
|
||||
await import_table(self.app, self.table_schema, self.table_name)
|
||||
|
||||
|
||||
@cappa.command(name='codegen', help='代码生成(体验完整功能,请自行部署 fba vben 前端工程)', default_long=True)
|
||||
@dataclass
|
||||
class CodeGenerate:
|
||||
class CodeGenerator:
|
||||
subcmd: cappa.Subcommands[Import | None] = None
|
||||
|
||||
def __call__(self) -> None:
|
||||
generate()
|
||||
def __post_init__(self) -> None:
|
||||
try:
|
||||
import_module_cached('backend.plugin.code_generator')
|
||||
except ImportError:
|
||||
raise cappa.Exit('代码生成插件不存在,请先安装此插件')
|
||||
|
||||
async def __call__(self) -> None:
|
||||
await generate()
|
||||
|
||||
|
||||
@cappa.command(help='一个高效的 fba 命令行界面', default_long=True)
|
||||
@@ -340,7 +453,7 @@ class FbaCli:
|
||||
str,
|
||||
cappa.Arg(value_name='PATH', default='', show_default=False, help='在事务中执行 SQL 脚本'),
|
||||
]
|
||||
subcmd: cappa.Subcommands[Run | Celery | Add | CodeGenerate | None] = None
|
||||
subcmd: cappa.Subcommands[Init | Run | Celery | Add | CodeGenerator | None] = None
|
||||
|
||||
async def __call__(self) -> None:
|
||||
if self.sql:
|
||||
|
||||
@@ -2,6 +2,7 @@ from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from pydantic import ValidationError
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from uvicorn.protocols.http.h11_impl import STATUS_PHRASES
|
||||
|
||||
from backend.common.context import ctx
|
||||
@@ -75,7 +76,7 @@ async def _validation_exception_handler(exc: RequestValidationError | Validation
|
||||
return MsgSpecJSONResponse(status_code=StandardResponseCode.HTTP_422, content=content)
|
||||
|
||||
|
||||
def register_exception(app: FastAPI) -> None:
|
||||
def register_exception(app: FastAPI) -> None: # noqa: C901
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception_handler(request: Request, exc: HTTPException):
|
||||
"""
|
||||
@@ -194,3 +195,55 @@ def register_exception(app: FastAPI) -> None:
|
||||
status_code=StandardResponseCode.HTTP_500,
|
||||
content=content,
|
||||
)
|
||||
|
||||
if settings.MIDDLEWARE_CORS:
|
||||
|
||||
@app.exception_handler(StandardResponseCode.HTTP_500)
|
||||
async def cors_custom_code_500_exception_handler(request: Request, exc: BaseExceptionError | Exception):
|
||||
"""
|
||||
跨域自定义 500 异常处理
|
||||
|
||||
:param request: FastAPI 请求对象
|
||||
:param exc: 自定义异常
|
||||
:return:
|
||||
"""
|
||||
if isinstance(exc, BaseExceptionError):
|
||||
content = {
|
||||
'code': exc.code,
|
||||
'msg': exc.msg,
|
||||
'data': exc.data,
|
||||
}
|
||||
else:
|
||||
if settings.ENVIRONMENT == 'dev':
|
||||
content = {
|
||||
'code': StandardResponseCode.HTTP_500,
|
||||
'msg': str(exc),
|
||||
'data': None,
|
||||
}
|
||||
else:
|
||||
res = response_base.fail(res=CustomResponseCode.HTTP_500)
|
||||
content = res.model_dump()
|
||||
content.update(trace_id=get_request_trace_id())
|
||||
response = MsgSpecJSONResponse(
|
||||
status_code=exc.code if isinstance(exc, BaseExceptionError) else StandardResponseCode.HTTP_500,
|
||||
content=content,
|
||||
background=exc.background if isinstance(exc, BaseExceptionError) else None,
|
||||
)
|
||||
origin = request.headers.get('origin')
|
||||
if origin:
|
||||
cors = CORSMiddleware(
|
||||
app=app,
|
||||
allow_origins=settings.CORS_ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
expose_headers=settings.CORS_EXPOSE_HEADERS,
|
||||
)
|
||||
response.headers.update(cors.simple_headers)
|
||||
has_cookie = 'cookie' in request.headers
|
||||
if cors.allow_all_origins and has_cookie:
|
||||
response.headers['Access-Control-Allow-Origin'] = origin
|
||||
elif not cors.allow_all_origins and cors.is_allowed_origin(origin=origin):
|
||||
response.headers['Access-Control-Allow-Origin'] = origin
|
||||
response.headers.add_vary_header('Origin')
|
||||
return response
|
||||
|
||||
@@ -47,6 +47,13 @@ def default_formatter(record: logging.LogRecord) -> str:
|
||||
return settings.LOG_FORMAT if settings.LOG_FORMAT.endswith('\n') else f'{settings.LOG_FORMAT}\n'
|
||||
|
||||
|
||||
def request_id_filter(record: logging.LogRecord) -> logging.LogRecord:
|
||||
"""请求 ID 过滤器"""
|
||||
rid = get_request_trace_id()
|
||||
record['request_id'] = rid[: settings.TRACE_ID_LOG_LENGTH]
|
||||
return record
|
||||
|
||||
|
||||
def setup_logging() -> None:
|
||||
"""
|
||||
设置日志处理器
|
||||
@@ -75,12 +82,6 @@ def setup_logging() -> None:
|
||||
# 移除 loguru 默认处理器
|
||||
logger.remove()
|
||||
|
||||
# request_id 过滤器
|
||||
def request_id_filter(record: logging.LogRecord) -> logging.LogRecord:
|
||||
rid = get_request_trace_id()
|
||||
record['request_id'] = rid[: settings.TRACE_ID_LOG_LENGTH]
|
||||
return record
|
||||
|
||||
# 配置 loguru 处理器
|
||||
logger.configure(
|
||||
handlers=[
|
||||
|
||||
+7
-10
@@ -6,6 +6,7 @@ from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column
|
||||
|
||||
from backend.common.enums import DataBaseType, PrimaryKeyType
|
||||
from backend.core.conf import settings
|
||||
from backend.utils.snowflake import snowflake
|
||||
from backend.utils.timezone import timezone
|
||||
@@ -23,15 +24,11 @@ id_key = Annotated[
|
||||
autoincrement=True,
|
||||
sort_order=-999,
|
||||
comment='主键 ID',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# 雪花算法 Mapped 类型主键,使用方法与 id_key 相同
|
||||
# 详情:https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html
|
||||
snowflake_id_key = Annotated[
|
||||
int,
|
||||
mapped_column(
|
||||
)
|
||||
if PrimaryKeyType.autoincrement == settings.DATABASE_PK_MODE
|
||||
# 雪花算法 Mapped 类型主键
|
||||
# 详情:https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html
|
||||
else mapped_column(
|
||||
BigInteger,
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
@@ -46,7 +43,7 @@ snowflake_id_key = Annotated[
|
||||
class UniversalText(TypeDecorator[str]):
|
||||
"""PostgreSQL、MySQL 兼容性(长)文本类型"""
|
||||
|
||||
impl = LONGTEXT if settings.DATABASE_TYPE == 'mysql' else Text
|
||||
impl = LONGTEXT if DataBaseType.mysql == settings.DATABASE_TYPE else Text
|
||||
cache_ok = True
|
||||
|
||||
def process_bind_param(self, value: str | None, dialect) -> str | None: # noqa: ANN001
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from prometheus_client import Counter, Gauge, Histogram
|
||||
|
||||
from backend.core.conf import settings
|
||||
|
||||
PROMETHEUS_INFO_GAUGE = (
|
||||
Gauge(name='fba_app_info', documentation='fba 应用信息', labelnames=['app_name'])
|
||||
.labels(app_name=settings.GRAFANA_APP_NAME)
|
||||
.inc()
|
||||
)
|
||||
|
||||
PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE = Gauge(
|
||||
'fba_request_in_progress',
|
||||
'按方法和路径统计请求的衡量',
|
||||
['app_name', 'method', 'path'],
|
||||
)
|
||||
|
||||
PROMETHEUS_REQUEST_COUNTER = Counter('fba_request_total', '按方法和路径统计请求总数', ['app_name', 'method', 'path'])
|
||||
|
||||
PROMETHEUS_RESPONSE_COUNTER = Counter(
|
||||
'fba_response_total',
|
||||
'按方法、路径和状态码统计响应总数',
|
||||
['app_name', 'method', 'path', 'status_code'],
|
||||
)
|
||||
|
||||
PROMETHEUS_EXCEPTION_COUNTER = Counter(
|
||||
'fba_exception_total',
|
||||
'按方法,路径和异常类型统计异常总数',
|
||||
['app_name', 'method', 'path', 'exception_type'],
|
||||
)
|
||||
|
||||
PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM = Histogram(
|
||||
'fba_request_cost_time',
|
||||
'按方法和路径划分请求耗时的直方图(以 ms 为单位)',
|
||||
['app_name', 'method', 'path'],
|
||||
)
|
||||
@@ -3,6 +3,8 @@ from typing import Annotated, Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field, validate_email
|
||||
|
||||
from backend.common.enums import PrimaryKeyType
|
||||
from backend.core.conf import settings
|
||||
from backend.utils.timezone import timezone
|
||||
|
||||
CustomPhoneNumber = Annotated[str, Field(pattern=r'^1[3-9]\d{9}$')]
|
||||
@@ -28,6 +30,14 @@ class SchemaBase(BaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
if PrimaryKeyType.snowflake == settings.DATABASE_PK_MODE:
|
||||
from pydantic import field_serializer
|
||||
|
||||
# 详情:https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9
|
||||
@field_serializer('id', check_fields=False)
|
||||
def serialize_id(self, value: int) -> str:
|
||||
return str(value)
|
||||
|
||||
|
||||
def ser_string(value: Any) -> str | None:
|
||||
if value:
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Request
|
||||
from sqlalchemy import ColumnElement, and_, or_
|
||||
from sqlalchemy import Alias, ColumnElement, Table, and_, or_
|
||||
from sqlalchemy.orm.util import AliasedClass
|
||||
from sqlalchemy_crud_plus.types import Model
|
||||
|
||||
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
|
||||
from backend.common.context import ctx
|
||||
from backend.common.enums import RoleDataRuleExpressionType, RoleDataRuleOperatorType
|
||||
from backend.common.exception import errors
|
||||
from backend.core.conf import settings
|
||||
from backend.utils.import_parse import dynamic_import_data_model
|
||||
from backend.utils.import_parse import get_all_models
|
||||
|
||||
|
||||
class RequestPermission:
|
||||
@@ -41,75 +44,95 @@ class RequestPermission:
|
||||
ctx.permission = self.value
|
||||
|
||||
|
||||
def filter_data_permission(request_user: GetUserInfoWithRelationDetail) -> ColumnElement[bool]: # noqa: C901
|
||||
def get_data_permission_models() -> dict[str, object]:
|
||||
"""获取所有可用于数据权限的模型"""
|
||||
return {getattr(model, '__name__', str(model)): model for model in get_all_models()}
|
||||
|
||||
|
||||
def filter_data_permission( # noqa: C901
|
||||
request: Request, *models: type[Model] | AliasedClass | Alias | Table
|
||||
) -> ColumnElement[bool]:
|
||||
"""
|
||||
过滤数据权限,控制用户可见数据范围
|
||||
|
||||
使用场景:
|
||||
- 控制用户能看到哪些数据
|
||||
|
||||
:param request_user: 请求用户
|
||||
:param request: FastAPI 请求对象
|
||||
:param models: 需要应用数据权限的模型类
|
||||
:return:
|
||||
"""
|
||||
# 是否过滤数据权限
|
||||
if request_user.is_superuser:
|
||||
# 超级管理员不过滤
|
||||
if request.user.is_superuser:
|
||||
return or_(1 == 1)
|
||||
|
||||
for role in request_user.roles:
|
||||
# 角色未启用数据权限过滤
|
||||
for role in request.user.roles:
|
||||
if not role.is_filter_scopes:
|
||||
return or_(1 == 1)
|
||||
|
||||
# 获取数据规则
|
||||
data_rules = set()
|
||||
for role in request_user.roles:
|
||||
for role in request.user.roles:
|
||||
for scope in role.scopes:
|
||||
if scope.status:
|
||||
data_rules.update(scope.rules)
|
||||
|
||||
# 无规则用户不做过滤
|
||||
if not list(data_rules):
|
||||
if not data_rules:
|
||||
return or_(1 == 1)
|
||||
|
||||
# 获取目标模型
|
||||
model_map = (
|
||||
{getattr(model, '__name__', str(model)): model for model in models} if models else get_data_permission_models()
|
||||
)
|
||||
|
||||
where_and_list = []
|
||||
where_or_list = []
|
||||
|
||||
for data_rule in list(data_rules):
|
||||
# 验证规则模型
|
||||
rule_model = data_rule.model
|
||||
if rule_model not in settings.DATA_PERMISSION_MODELS:
|
||||
raise errors.NotFoundError(msg='数据规则可用模型不存在')
|
||||
model_ins = dynamic_import_data_model(settings.DATA_PERMISSION_MODELS[rule_model])
|
||||
for data_rule in data_rules:
|
||||
target_model = model_map.get(data_rule.model)
|
||||
if target_model is None:
|
||||
continue
|
||||
|
||||
# 验证规则列
|
||||
model_columns = [
|
||||
key for key in model_ins.__table__.columns.keys() if key not in settings.DATA_PERMISSION_COLUMN_EXCLUDE
|
||||
]
|
||||
column = data_rule.column
|
||||
if column not in model_columns:
|
||||
raise errors.NotFoundError(msg='数据规则可用模型列不存在')
|
||||
table = target_model if isinstance(target_model, Table) else target_model.__table__
|
||||
rule_column = data_rule.column
|
||||
if rule_column not in table.columns.keys():
|
||||
continue
|
||||
if rule_column in settings.DATA_PERMISSION_COLUMN_EXCLUDE:
|
||||
continue
|
||||
|
||||
# 构建过滤条件
|
||||
column_obj = getattr(model_ins, column)
|
||||
rule_expression = data_rule.expression
|
||||
column_obj = (
|
||||
getattr(target_model, rule_column) if not isinstance(target_model, Table) else table.columns[rule_column]
|
||||
)
|
||||
column_type = table.columns[rule_column].type.python_type
|
||||
|
||||
def cast_value(value: Any) -> Any:
|
||||
"""类型转换"""
|
||||
try:
|
||||
return column_type(value) if column_type is not str else value
|
||||
except (ValueError, TypeError):
|
||||
return value
|
||||
|
||||
condition = None
|
||||
match rule_expression:
|
||||
match data_rule.expression:
|
||||
case RoleDataRuleExpressionType.eq:
|
||||
condition = column_obj == data_rule.value
|
||||
condition = column_obj == cast_value(data_rule.value)
|
||||
case RoleDataRuleExpressionType.ne:
|
||||
condition = column_obj != data_rule.value
|
||||
condition = column_obj != cast_value(data_rule.value)
|
||||
case RoleDataRuleExpressionType.gt:
|
||||
condition = column_obj > data_rule.value
|
||||
condition = column_obj > cast_value(data_rule.value)
|
||||
case RoleDataRuleExpressionType.ge:
|
||||
condition = column_obj >= data_rule.value
|
||||
condition = column_obj >= cast_value(data_rule.value)
|
||||
case RoleDataRuleExpressionType.lt:
|
||||
condition = column_obj < data_rule.value
|
||||
condition = column_obj < cast_value(data_rule.value)
|
||||
case RoleDataRuleExpressionType.le:
|
||||
condition = column_obj <= data_rule.value
|
||||
condition = column_obj <= cast_value(data_rule.value)
|
||||
case RoleDataRuleExpressionType.in_:
|
||||
values = data_rule.value.split(',') if isinstance(data_rule.value, str) else data_rule.value
|
||||
values = [cast_value(v.strip()) for v in data_rule.value.split(',')]
|
||||
condition = column_obj.in_(values)
|
||||
case RoleDataRuleExpressionType.not_in:
|
||||
values = data_rule.value.split(',') if isinstance(data_rule.value, str) else data_rule.value
|
||||
values = [cast_value(v.strip()) for v in data_rule.value.split(',')]
|
||||
condition = column_obj.not_in(values)
|
||||
|
||||
# 根据运算符添加到对应列表
|
||||
@@ -128,3 +151,24 @@ def filter_data_permission(request_user: GetUserInfoWithRelationDetail) -> Colum
|
||||
where_list.append(or_(*where_or_list))
|
||||
|
||||
return or_(*where_list) if where_list else or_(1 == 1)
|
||||
|
||||
|
||||
# 此函数是为了简化调用方式,但目前无法正常工作: https://github.com/fastapi/fastapi/discussions/14438
|
||||
# def DataPermissionFilter(*models: type[Model] | AliasedClass | Alias | Table) -> type[ColumnElement[bool]]:
|
||||
# """
|
||||
# 指定模型的数据权限过滤器
|
||||
#
|
||||
# :param models: 模型类(可选,支持多个)
|
||||
# :return:
|
||||
# """
|
||||
# return Annotated[ColumnElement[bool], Depends(partial(filter_data_permission, *models))]
|
||||
|
||||
|
||||
class DataPermissionFilter:
|
||||
"""指定模型的数据权限过滤器"""
|
||||
|
||||
def __init__(self, *models: type[Model] | AliasedClass | Alias | Table) -> None:
|
||||
self.models = models
|
||||
|
||||
async def __call__(self, request: Request) -> ColumnElement[bool]:
|
||||
return filter_data_permission(request, *self.models)
|
||||
|
||||
@@ -23,7 +23,7 @@ class Settings(BaseSettings):
|
||||
|
||||
# FastAPI
|
||||
FASTAPI_API_V1_PATH: str = '/api/v1'
|
||||
FASTAPI_TITLE: str = 'FastAPI'
|
||||
FASTAPI_TITLE: str = 'fba'
|
||||
FASTAPI_DESCRIPTION: str = 'FastAPI Best Architecture'
|
||||
FASTAPI_DOCS_URL: str = '/docs'
|
||||
FASTAPI_REDOC_URL: str = '/redoc'
|
||||
@@ -42,6 +42,7 @@ class Settings(BaseSettings):
|
||||
DATABASE_POOL_ECHO: bool | Literal['debug'] = False
|
||||
DATABASE_SCHEMA: str = 'fba'
|
||||
DATABASE_CHARSET: str = 'utf8mb4'
|
||||
DATABASE_PK_MODE: Literal['autoincrement', 'snowflake'] = 'autoincrement'
|
||||
|
||||
# .env Redis
|
||||
REDIS_HOST: str
|
||||
@@ -111,9 +112,6 @@ class Settings(BaseSettings):
|
||||
COOKIE_REFRESH_TOKEN_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # 7 天
|
||||
|
||||
# 数据权限
|
||||
DATA_PERMISSION_MODELS: dict[str, str] = { # 允许进行数据过滤的 SQLA 模型,它必须以模块字符串的方式定义
|
||||
'Dept': 'backend.app.admin.model.Dept',
|
||||
}
|
||||
DATA_PERMISSION_COLUMN_EXCLUDE: list[str] = [ # 排除允许进行数据过滤的 SQLA 模型列
|
||||
'id',
|
||||
'sort',
|
||||
@@ -217,6 +215,11 @@ class Settings(BaseSettings):
|
||||
# I18n 配置
|
||||
I18N_DEFAULT_LANGUAGE: str = 'zh-CN'
|
||||
|
||||
# Grafana
|
||||
GRAFANA_METRICS: bool = False
|
||||
GRAFANA_APP_NAME: str = 'fba_server'
|
||||
GRAFANA_OTLP_GRPC_ENDPOINT: str = 'fba_alloy:4317'
|
||||
|
||||
##################################################
|
||||
# [ App ] task
|
||||
##################################################
|
||||
|
||||
+35
-18
@@ -9,10 +9,10 @@ import socketio
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi_limiter import FastAPILimiter
|
||||
from fastapi_pagination import add_pagination
|
||||
from prometheus_client import make_asgi_app
|
||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from starlette.types import ASGIApp
|
||||
from starlette_context.middleware import ContextMiddleware
|
||||
from starlette_context.plugins import RequestIdPlugin
|
||||
|
||||
@@ -33,8 +33,10 @@ from backend.plugin.tools import build_final_router
|
||||
from backend.utils.demo_site import demo_site
|
||||
from backend.utils.health_check import ensure_unique_route_names, http_limit_callback
|
||||
from backend.utils.openapi import simplify_operation_ids
|
||||
from backend.utils.otel import init_otel
|
||||
from backend.utils.serializers import MsgSpecJSONResponse
|
||||
from backend.utils.snowflake import snowflake
|
||||
from backend.utils.trace_id import OtelTraceIdPlugin
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -76,22 +78,7 @@ async def register_init(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
def register_app() -> FastAPI:
|
||||
"""注册 FastAPI 应用"""
|
||||
|
||||
class MyFastAPI(FastAPI):
|
||||
if settings.MIDDLEWARE_CORS:
|
||||
# Related issues
|
||||
# https://github.com/fastapi/fastapi/discussions/7847
|
||||
# https://github.com/fastapi/fastapi/discussions/8027
|
||||
def build_middleware_stack(self) -> ASGIApp:
|
||||
return CORSMiddleware(
|
||||
super().build_middleware_stack(),
|
||||
allow_origins=settings.CORS_ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
expose_headers=settings.CORS_EXPOSE_HEADERS,
|
||||
)
|
||||
|
||||
app = MyFastAPI(
|
||||
app = FastAPI(
|
||||
title=settings.FASTAPI_TITLE,
|
||||
version=__version__,
|
||||
description=settings.FASTAPI_DESCRIPTION,
|
||||
@@ -111,6 +98,9 @@ def register_app() -> FastAPI:
|
||||
register_page(app)
|
||||
register_exception(app)
|
||||
|
||||
if settings.GRAFANA_METRICS:
|
||||
register_metrics(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@@ -164,15 +154,29 @@ def register_middleware(app: FastAPI) -> None:
|
||||
app.add_middleware(AccessMiddleware)
|
||||
|
||||
# ContextVar
|
||||
plugins = [OtelTraceIdPlugin()] if settings.GRAFANA_METRICS else [RequestIdPlugin(validate=True)]
|
||||
app.add_middleware(
|
||||
ContextMiddleware,
|
||||
plugins=[RequestIdPlugin(validate=True)],
|
||||
plugins=plugins,
|
||||
default_error_response=MsgSpecJSONResponse(
|
||||
content={'code': StandardResponseCode.HTTP_400, 'msg': 'BAD_REQUEST', 'data': None},
|
||||
status_code=StandardResponseCode.HTTP_400,
|
||||
),
|
||||
)
|
||||
|
||||
# CORS
|
||||
# https://github.com/fastapi-practices/fastapi_best_architecture/pull/789/changes
|
||||
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/4031
|
||||
if settings.MIDDLEWARE_CORS:
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.CORS_ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
expose_headers=settings.CORS_EXPOSE_HEADERS,
|
||||
)
|
||||
|
||||
|
||||
def register_router(app: FastAPI) -> None:
|
||||
"""
|
||||
@@ -218,3 +222,16 @@ def register_socket_app(app: FastAPI) -> None:
|
||||
socketio_path='/ws/socket.io',
|
||||
)
|
||||
app.mount('/ws', socket_app)
|
||||
|
||||
|
||||
def register_metrics(app: FastAPI) -> None:
|
||||
"""
|
||||
注册指标
|
||||
|
||||
:param app: FastAPI 应用实例
|
||||
:return:
|
||||
"""
|
||||
metrics_app = make_asgi_app()
|
||||
app.mount('/metrics', metrics_app)
|
||||
|
||||
init_otel(app)
|
||||
|
||||
@@ -13,6 +13,7 @@ from sqlalchemy.ext.asyncio import (
|
||||
create_async_engine,
|
||||
)
|
||||
|
||||
from backend.common.enums import DataBaseType
|
||||
from backend.common.log import log
|
||||
from backend.common.model import MappedBase
|
||||
from backend.core.conf import settings
|
||||
@@ -26,14 +27,14 @@ def create_database_url(*, unittest: bool = False) -> URL:
|
||||
:return:
|
||||
"""
|
||||
url = URL.create(
|
||||
drivername='mysql+asyncmy' if settings.DATABASE_TYPE == 'mysql' else 'postgresql+asyncpg',
|
||||
drivername='mysql+asyncmy' if DataBaseType.mysql == settings.DATABASE_TYPE else 'postgresql+asyncpg',
|
||||
username=settings.DATABASE_USER,
|
||||
password=settings.DATABASE_PASSWORD,
|
||||
host=settings.DATABASE_HOST,
|
||||
port=settings.DATABASE_PORT,
|
||||
database=settings.DATABASE_SCHEMA if not unittest else f'{settings.DATABASE_SCHEMA}_test',
|
||||
)
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
url.update_query_dict({'charset': settings.DATABASE_CHARSET})
|
||||
return url
|
||||
|
||||
@@ -91,6 +92,12 @@ async def create_tables() -> None:
|
||||
await coon.run_sync(MappedBase.metadata.create_all)
|
||||
|
||||
|
||||
async def drop_tables() -> None:
|
||||
"""丢弃数据库表"""
|
||||
async with async_engine.begin() as conn:
|
||||
await conn.run_sync(MappedBase.metadata.drop_all)
|
||||
|
||||
|
||||
def uuid4_str() -> str:
|
||||
"""数据库引擎 UUID 类型兼容性解决方案"""
|
||||
return str(uuid4())
|
||||
|
||||
@@ -14,6 +14,13 @@ from backend.app.admin.service.opera_log_service import opera_log_service
|
||||
from backend.common.context import ctx
|
||||
from backend.common.enums import OperaLogCipherType, StatusType
|
||||
from backend.common.log import log
|
||||
from backend.common.prometheus.instruments import (
|
||||
PROMETHEUS_EXCEPTION_COUNTER,
|
||||
PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM,
|
||||
PROMETHEUS_REQUEST_COUNTER,
|
||||
PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE,
|
||||
PROMETHEUS_RESPONSE_COUNTER,
|
||||
)
|
||||
from backend.common.queue import batch_dequeue
|
||||
from backend.common.response.response_code import StandardResponseCode
|
||||
from backend.core.conf import settings
|
||||
@@ -43,6 +50,10 @@ class OperaLogMiddleware(BaseHTTPMiddleware):
|
||||
else:
|
||||
method = request.method
|
||||
args = await self.get_request_args(request)
|
||||
PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE.labels(
|
||||
app_name=settings.GRAFANA_APP_NAME, method=method, path=path
|
||||
).inc()
|
||||
PROMETHEUS_REQUEST_COUNTER.labels(app_name=settings.GRAFANA_APP_NAME, method=method, path=path).inc()
|
||||
|
||||
# 执行请求
|
||||
code = 200
|
||||
@@ -63,6 +74,12 @@ class OperaLogMiddleware(BaseHTTPMiddleware):
|
||||
code = exception.get('code')
|
||||
msg = exception.get('msg')
|
||||
log.error(f'请求异常: {msg}')
|
||||
PROMETHEUS_EXCEPTION_COUNTER.labels(
|
||||
app_name=settings.GRAFANA_APP_NAME,
|
||||
method=method,
|
||||
path=path,
|
||||
exception_type=type(e).__name__,
|
||||
).inc()
|
||||
break
|
||||
except Exception as e:
|
||||
elapsed = round((time.perf_counter() - ctx.perf_time) * 1000, 3)
|
||||
@@ -71,6 +88,20 @@ class OperaLogMiddleware(BaseHTTPMiddleware):
|
||||
status = StatusType.disable
|
||||
error = e
|
||||
log.error(f'请求异常: {e!s}')
|
||||
PROMETHEUS_EXCEPTION_COUNTER.labels(
|
||||
app_name=settings.GRAFANA_APP_NAME, method=method, path=path, exception_type=type(e).__name__
|
||||
).inc()
|
||||
else:
|
||||
PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM.labels(
|
||||
app_name=settings.GRAFANA_APP_NAME, method=method, path=path
|
||||
).observe(elapsed, exemplar={'TraceID': get_request_trace_id()})
|
||||
finally:
|
||||
PROMETHEUS_RESPONSE_COUNTER.labels(
|
||||
app_name=settings.GRAFANA_APP_NAME, method=method, path=path, status_code=code
|
||||
).inc()
|
||||
PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE.labels(
|
||||
app_name=settings.GRAFANA_APP_NAME, method=method, path=path
|
||||
).dec()
|
||||
|
||||
# 此信息只能在请求后获取
|
||||
route = request.scope.get('route')
|
||||
@@ -86,7 +117,7 @@ class OperaLogMiddleware(BaseHTTPMiddleware):
|
||||
log.debug(f'接口摘要:[{summary}]')
|
||||
log.debug(f'请求地址:[{ctx.ip}]')
|
||||
log.debug(f'请求参数:{args}')
|
||||
log.info(f'{request.client.host: <15} | {request.method: <8} | {code!s: <6} | {path} | {elapsed:.3f}ms')
|
||||
log.info(f'{ctx.ip: <15} | {request.method: <8} | {code!s: <6} | {path} | {elapsed:.3f}ms')
|
||||
if request.method != 'OPTIONS':
|
||||
log.debug('<-- 请求结束')
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from collections.abc import Sequence
|
||||
from sqlalchemy import Row, RowMapping, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.common.enums import DataBaseType
|
||||
from backend.core.conf import settings
|
||||
|
||||
|
||||
@@ -18,7 +19,7 @@ class CRUDGen:
|
||||
:param table_schema: 数据库 schema 名称
|
||||
:return:
|
||||
"""
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
sql = """
|
||||
SELECT table_name AS table_name, table_comment AS table_comment
|
||||
FROM information_schema.tables
|
||||
@@ -48,7 +49,7 @@ class CRUDGen:
|
||||
:param table_name: 表名
|
||||
:return:
|
||||
"""
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
sql = """
|
||||
SELECT table_name AS table_name, table_comment AS table_comment
|
||||
FROM information_schema.tables
|
||||
@@ -79,7 +80,7 @@ class CRUDGen:
|
||||
:param table_name: 表名
|
||||
:return:
|
||||
"""
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
sql = """
|
||||
SELECT column_name AS column_name,
|
||||
CASE WHEN column_key = 'PRI' THEN 1 ELSE 0 END AS is_pk,
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections.abc import Sequence
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.common.enums import DataBaseType
|
||||
from backend.common.exception import errors
|
||||
from backend.core.conf import settings
|
||||
from backend.plugin.code_generator.crud.crud_column import gen_column_dao
|
||||
@@ -32,7 +33,7 @@ class GenColumnService:
|
||||
@staticmethod
|
||||
async def get_types() -> list[str]:
|
||||
"""获取所有列类型"""
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
types = GenMySQLColumnType.get_member_keys()
|
||||
else:
|
||||
types = GenPostgreSQLColumnType.get_member_keys()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from functools import lru_cache
|
||||
|
||||
from backend.common.enums import DataBaseType
|
||||
from backend.core.conf import settings
|
||||
from backend.plugin.code_generator.enums import GenMySQLColumnType, GenPostgreSQLColumnType
|
||||
|
||||
@@ -12,7 +13,7 @@ def sql_type_to_sqlalchemy(typing: str) -> str:
|
||||
:param typing: SQL 类型字符串
|
||||
:return:
|
||||
"""
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
if typing in GenMySQLColumnType.get_member_keys():
|
||||
return typing
|
||||
else:
|
||||
@@ -30,7 +31,7 @@ def sql_type_to_pydantic(typing: str) -> str:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
if settings.DATABASE_TYPE == 'mysql':
|
||||
if DataBaseType.mysql == settings.DATABASE_TYPE:
|
||||
return GenMySQLColumnType[typing].value
|
||||
if typing == 'CHARACTER VARYING': # postgresql 中 DDL VARCHAR 的别名
|
||||
return 'str'
|
||||
|
||||
+12
-12
@@ -55,15 +55,15 @@ def get_plugins() -> list[str]:
|
||||
return plugin_packages
|
||||
|
||||
|
||||
def get_plugin_models() -> list[type]:
|
||||
def get_plugin_models() -> list[object]:
|
||||
"""获取插件所有模型类"""
|
||||
objs = []
|
||||
|
||||
for plugin in get_plugins():
|
||||
module_path = f'backend.plugin.{plugin}.model'
|
||||
obj = get_model_objects(module_path)
|
||||
if obj:
|
||||
objs.extend(obj)
|
||||
model_objs = get_model_objects(module_path)
|
||||
if model_objs:
|
||||
objs.extend(model_objs)
|
||||
|
||||
return objs
|
||||
|
||||
@@ -79,16 +79,16 @@ async def get_plugin_sql(plugin: str, db_type: DataBaseType, pk_type: PrimaryKey
|
||||
"""
|
||||
if db_type == DataBaseType.mysql:
|
||||
mysql_dir = PLUGIN_DIR / plugin / 'sql' / 'mysql'
|
||||
if pk_type == PrimaryKeyType.autoincrement:
|
||||
sql_file = mysql_dir / 'init.sql'
|
||||
else:
|
||||
sql_file = mysql_dir / 'init_snowflake.sql'
|
||||
sql_file = (
|
||||
mysql_dir / 'init.sql' if pk_type == PrimaryKeyType.autoincrement else mysql_dir / 'init_snowflake.sql'
|
||||
)
|
||||
else:
|
||||
postgresql_dir = PLUGIN_DIR / plugin / 'sql' / 'postgresql'
|
||||
if pk_type == PrimaryKeyType.autoincrement:
|
||||
sql_file = postgresql_dir / 'init.sql'
|
||||
else:
|
||||
sql_file = postgresql_dir / 'init_snowflake.sql'
|
||||
sql_file = (
|
||||
postgresql_dir / 'init.sql'
|
||||
if pk_type == PrimaryKeyType.autoincrement
|
||||
else postgresql_dir / 'init_snowflake.sql'
|
||||
)
|
||||
|
||||
path = anyio.Path(sql_file)
|
||||
if not await path.exists():
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import os.path
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Any, TypeVar
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from backend.common.exception import errors
|
||||
from backend.common.log import log
|
||||
|
||||
@@ -37,7 +40,7 @@ def dynamic_import_data_model(module_path: str) -> type[T]:
|
||||
raise errors.ServerError(msg='数据模型列动态解析失败,请联系系统超级管理员')
|
||||
|
||||
|
||||
def get_model_objects(module_path: str) -> list[type] | None:
|
||||
def get_model_objects(module_path: str) -> list[object] | None:
|
||||
"""
|
||||
获取模型对象
|
||||
|
||||
@@ -47,15 +50,43 @@ def get_model_objects(module_path: str) -> list[type] | None:
|
||||
try:
|
||||
module = import_module_cached(module_path)
|
||||
except ModuleNotFoundError:
|
||||
log.warning(f'模块 {module_path} 中不包含模型对象')
|
||||
return None
|
||||
except Exception:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise e from None
|
||||
|
||||
classes = []
|
||||
|
||||
for _name, obj in inspect.getmembers(module):
|
||||
if inspect.isclass(obj) and module_path in obj.__module__:
|
||||
if (inspect.isclass(obj) and module_path in obj.__module__) or (
|
||||
isinstance(obj, sa.Table) and obj.metadata is not None
|
||||
):
|
||||
classes.append(obj)
|
||||
|
||||
return classes
|
||||
|
||||
|
||||
def get_app_models() -> list[object]:
|
||||
"""获取 app 所有模型类"""
|
||||
from backend.core.path_conf import BASE_PATH
|
||||
|
||||
app_path = BASE_PATH / 'app'
|
||||
list_dirs = os.listdir(app_path)
|
||||
|
||||
apps = [d for d in list_dirs if os.path.isdir(os.path.join(app_path, d)) and d != '__pycache__']
|
||||
|
||||
objs = []
|
||||
for app in apps:
|
||||
module_path = f'backend.app.{app}.model'
|
||||
model_objs = get_model_objects(module_path)
|
||||
if model_objs:
|
||||
objs.extend(model_objs)
|
||||
|
||||
return objs
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_all_models() -> list[object]:
|
||||
"""获取所有模型类"""
|
||||
from backend.plugin.tools import get_plugin_models
|
||||
|
||||
return get_app_models() + get_plugin_models()
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from fastapi import FastAPI
|
||||
from opentelemetry import trace
|
||||
from opentelemetry._logs import set_logger_provider
|
||||
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
||||
from opentelemetry.instrumentation.logging import LoggingInstrumentor
|
||||
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
||||
from opentelemetry.sdk._logs._internal.export import BatchLogRecordProcessor
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
|
||||
from backend.common.log import log, request_id_filter
|
||||
from backend.core.conf import settings
|
||||
from backend.database.db import async_engine
|
||||
|
||||
|
||||
def init_otel(app: FastAPI) -> None:
|
||||
"""
|
||||
初始化 OpenTelemetry
|
||||
|
||||
:param app: FastAPI 应用实例
|
||||
:return:
|
||||
"""
|
||||
from backend import __version__
|
||||
|
||||
resource = Resource(
|
||||
attributes={
|
||||
'service.name': settings.GRAFANA_APP_NAME,
|
||||
'service.version': __version__,
|
||||
'deployment.environment': settings.ENVIRONMENT,
|
||||
},
|
||||
)
|
||||
|
||||
tracer_provider = TracerProvider(resource=resource)
|
||||
trace.set_tracer_provider(tracer_provider)
|
||||
|
||||
span_exporter = OTLPSpanExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True)
|
||||
tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
|
||||
|
||||
logger_provider = LoggerProvider(resource=resource)
|
||||
set_logger_provider(logger_provider)
|
||||
|
||||
log_exporter = OTLPLogExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True)
|
||||
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
|
||||
|
||||
otel_logging_handler = LoggingHandler(logger_provider=logger_provider)
|
||||
log.add( # type: ignore
|
||||
otel_logging_handler,
|
||||
level=settings.LOG_STD_LEVEL,
|
||||
format=settings.LOG_FORMAT,
|
||||
filter=lambda record: request_id_filter(record),
|
||||
)
|
||||
|
||||
LoggingInstrumentor().instrument(set_logging_format=True)
|
||||
SQLAlchemyInstrumentor().instrument(engine=async_engine.sync_engine)
|
||||
FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider)
|
||||
@@ -1,9 +1,31 @@
|
||||
from typing import Any
|
||||
|
||||
from opentelemetry import trace
|
||||
from starlette.requests import Request
|
||||
from starlette_context.plugins import Plugin
|
||||
|
||||
from backend.common.context import ctx
|
||||
from backend.core.conf import settings
|
||||
|
||||
|
||||
def get_request_trace_id() -> str:
|
||||
"""从请求头中获取追踪 ID"""
|
||||
"""从上下文中获取追踪 ID"""
|
||||
if ctx.exists():
|
||||
return ctx.get(settings.TRACE_ID_REQUEST_HEADER_KEY, settings.TRACE_ID_LOG_DEFAULT_VALUE)
|
||||
return settings.TRACE_ID_LOG_DEFAULT_VALUE
|
||||
|
||||
|
||||
class OtelTraceIdPlugin(Plugin):
|
||||
"""OpenTelemetry Trace ID 插件"""
|
||||
|
||||
key = settings.TRACE_ID_REQUEST_HEADER_KEY
|
||||
|
||||
async def process_request(self, request: Request) -> Any:
|
||||
"""从 OpenTelemetry span 中提取 trace_id"""
|
||||
span = trace.get_current_span()
|
||||
span_ctx = span.get_span_context()
|
||||
|
||||
if span_ctx.is_valid:
|
||||
return trace.format_trace_id(span_ctx.trace_id)
|
||||
|
||||
return settings.TRACE_ID_LOG_DEFAULT_VALUE
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
[program:fba_celery_worker]
|
||||
directory=/fba/backend
|
||||
command=/usr/local/bin/celery -A app.task.celery worker -P gevent -c 1000 --loglevel=INFO
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fba/fba_celery_worker.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=5
|
||||
|
||||
[program:fba_celery_beat]
|
||||
directory=/fba/backend
|
||||
command=/usr/local/bin/celery -A app.task.celery beat --loglevel=INFO
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fba/fba_celery_beat.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=5
|
||||
|
||||
[program:fba_celery_flower]
|
||||
directory=/fba/backend
|
||||
command=/usr/local/bin/celery -A app.task.celery flower --port=8555 --url-prefix=flower --basic-auth=admin:123456
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fba/fba_celery_flower.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=5
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
// ============================================================================
|
||||
// Grafana Alloy 配置文件
|
||||
// Alloy 是 Grafana 的可观测性数据收集器(原 Grafana Agent)
|
||||
// 官方文档: https://grafana.com/docs/alloy/latest/
|
||||
//
|
||||
// 数据流向:
|
||||
// 应用程序 -> OTLP Receiver -> Batch Processor -> Exporters -> 后端存储
|
||||
// - 日志 (Logs) -> Loki
|
||||
// - 指标 (Metrics) -> Prometheus
|
||||
// - 链路 (Traces) -> Tempo
|
||||
// ============================================================================
|
||||
|
||||
// OTLP 接收器配置
|
||||
// OpenTelemetry Protocol (OTLP) 是 OpenTelemetry 的标准数据传输协议
|
||||
// 支持同时接收日志、指标和链路追踪数据
|
||||
otelcol.receiver.otlp "default" {
|
||||
// gRPC 协议配置
|
||||
// gRPC 是高性能的 RPC 框架,适用于服务间通信
|
||||
grpc {
|
||||
endpoint = "0.0.0.0:4317"
|
||||
// 0.0.0.0 表示监听所有网络接口
|
||||
// 4317 是 OTLP gRPC 的标准端口
|
||||
// 应用程序通过此端口发送遥测数据
|
||||
}
|
||||
|
||||
// HTTP 协议配置
|
||||
// HTTP 协议更通用,适用于无法使用 gRPC 的场景
|
||||
http {
|
||||
endpoint = "0.0.0.0:4318"
|
||||
// 4318 是 OTLP HTTP 的标准端口
|
||||
// 支持 JSON 和 Protobuf 格式的数据
|
||||
}
|
||||
|
||||
// 输出配置
|
||||
// 定义接收到的数据发送到哪些处理器
|
||||
output {
|
||||
logs = [otelcol.processor.batch.default.input]
|
||||
metrics = [otelcol.processor.batch.default.input]
|
||||
traces = [otelcol.processor.batch.default.input]
|
||||
}
|
||||
}
|
||||
|
||||
// 批处理器配置
|
||||
// 将多个遥测数据项合并成批次,提高传输效率
|
||||
otelcol.processor.batch "default" {
|
||||
// 输出配置
|
||||
// 定义处理后的数据发送到哪些导出器
|
||||
output {
|
||||
logs = [otelcol.exporter.loki.default.input]
|
||||
metrics = [otelcol.exporter.prometheus.default.input]
|
||||
traces = [otelcol.exporter.otlp.tempo.input]
|
||||
}
|
||||
}
|
||||
|
||||
// Loki 导出器配置
|
||||
// 将日志数据发送到 Grafana Loki
|
||||
otelcol.exporter.loki "default" {
|
||||
forward_to = [loki.write.loki_server.receiver]
|
||||
}
|
||||
|
||||
// Loki 写入组件
|
||||
// 负责将日志数据推送到 Loki 服务器
|
||||
loki.write "loki_server" {
|
||||
endpoint {
|
||||
url = "http://fba_loki:3100/loki/api/v1/push"
|
||||
}
|
||||
}
|
||||
|
||||
// Prometheus 导出器配置
|
||||
// 将指标数据发送到 Prometheus
|
||||
otelcol.exporter.prometheus "default" {
|
||||
forward_to = [prometheus.remote_write.prom_server.receiver]
|
||||
}
|
||||
|
||||
// Prometheus 远程写入组件
|
||||
// 负责将指标数据推送到 Prometheus 服务器
|
||||
prometheus.remote_write "prom_server" {
|
||||
endpoint {
|
||||
url = "http://fba_prometheus:9090/api/v1/write"
|
||||
}
|
||||
}
|
||||
|
||||
// Prometheus 自身指标抓取
|
||||
// 监控 Prometheus 服务器本身的运行状态
|
||||
prometheus.scrape "prometheus" {
|
||||
targets = [{
|
||||
__address__ = "fba_prometheus:9090",
|
||||
}]
|
||||
forward_to = [prometheus.remote_write.prom_server.receiver]
|
||||
// 抓取的指标发送到远程写入组件
|
||||
|
||||
job_name = "fba_prometheus"
|
||||
// 任务名称
|
||||
// 将作为 job 标签添加到所有抓取的指标上
|
||||
|
||||
scrape_interval = "15s"
|
||||
// 抓取间隔
|
||||
}
|
||||
|
||||
// FastAPI 后端服务指标抓取
|
||||
// 监控 FastAPI 应用的性能和业务指标
|
||||
prometheus.scrape "fba_server" {
|
||||
targets = [{
|
||||
__address__ = "fba_server:8001",
|
||||
}]
|
||||
forward_to = [prometheus.remote_write.prom_server.receiver]
|
||||
// 抓取的指标发送到远程写入组件
|
||||
|
||||
job_name = "fba_server"
|
||||
// 任务名称
|
||||
// 用于在 Prometheus 查询中区分不同服务
|
||||
|
||||
scrape_interval = "5s"
|
||||
// 抓取间隔
|
||||
|
||||
scrape_timeout = "5s"
|
||||
// 抓取超时时间
|
||||
}
|
||||
|
||||
// Celery Exporter 指标抓取
|
||||
// 监控 Celery 异步任务队列的运行状态
|
||||
prometheus.scrape "fba_celery_exporter" {
|
||||
targets = [{
|
||||
__address__ = "fba_celery_exporter:9808",
|
||||
}]
|
||||
forward_to = [prometheus.remote_write.prom_server.receiver]
|
||||
// 抓取的指标发送到远程写入组件
|
||||
|
||||
job_name = "fba_celery_exporter"
|
||||
// 任务名称
|
||||
// 用于标识 Celery 相关的指标
|
||||
|
||||
scrape_interval = "5s"
|
||||
// 抓取间隔
|
||||
|
||||
scrape_timeout = "5s"
|
||||
// 抓取超时时间
|
||||
}
|
||||
|
||||
// Tempo 导出器配置
|
||||
// 将链路追踪数据发送到 Grafana Tempo
|
||||
otelcol.exporter.otlp "tempo" {
|
||||
client {
|
||||
endpoint = "fba_tempo:4317"
|
||||
|
||||
tls {
|
||||
insecure = true
|
||||
// 禁用 TLS 加密
|
||||
// 在 Docker 内部网络中通信时可以禁用
|
||||
// 生产环境建议启用 TLS
|
||||
|
||||
insecure_skip_verify = true
|
||||
// 跳过 TLS 证书验证
|
||||
// 仅在开发/测试环境使用
|
||||
// 生产环境应配置正确的证书
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# ============================================================================
|
||||
# Grafana 仪表盘 Provisioning 配置文件
|
||||
# 官方文档: https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
|
||||
# 用于自动加载和管理仪表盘,无需手动导入
|
||||
# ============================================================================
|
||||
|
||||
# API 版本号
|
||||
# 当前仅支持版本 1
|
||||
apiVersion: 1
|
||||
|
||||
# providers: 仪表盘提供者列表
|
||||
# 定义 Grafana 从哪里加载仪表盘以及如何管理它们
|
||||
providers:
|
||||
- name: 'fba_server'
|
||||
# 提供者名称,用于标识和日志记录
|
||||
|
||||
orgId: 1
|
||||
# 组织 ID
|
||||
# 指定仪表盘属于哪个组织
|
||||
# 默认值: 1(默认组织)
|
||||
|
||||
type: 'file'
|
||||
# 提供者类型
|
||||
# 可选值:
|
||||
# - file: 从本地文件系统加载仪表盘 JSON 文件
|
||||
|
||||
disableDeletion: true
|
||||
# 禁止通过 UI 删除仪表盘
|
||||
|
||||
editable: false
|
||||
# 是否允许通过 UI 编辑仪表盘
|
||||
|
||||
options:
|
||||
path: '/etc/grafana/dashboards'
|
||||
# 仪表盘 JSON 文件所在目录(容器内路径)
|
||||
# Grafana 会自动扫描此目录下的所有 .json 文件
|
||||
@@ -0,0 +1,164 @@
|
||||
# ============================================================================
|
||||
# Grafana 数据源 Provisioning 配置文件
|
||||
# 官方文档: https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources
|
||||
# 用于自动配置数据源,实现基础设施即代码
|
||||
# ============================================================================
|
||||
|
||||
# API 版本号
|
||||
# 当前仅支持版本 1
|
||||
apiVersion: 1
|
||||
|
||||
# datasources: 数据源列表
|
||||
# 定义 Grafana 连接的所有后端数据存储
|
||||
datasources:
|
||||
# ==========================================================================
|
||||
# Loki 数据源配置
|
||||
# Loki 是 Grafana 的日志聚合系统,类似于 Prometheus 但用于日志
|
||||
# 官方文档: https://grafana.com/docs/loki/latest/
|
||||
# ==========================================================================
|
||||
- name: Loki
|
||||
# 数据源显示名称,在 Grafana UI 中显示
|
||||
|
||||
uid: loki
|
||||
# 数据源唯一标识符
|
||||
# 用于在仪表盘和告警规则中引用此数据源
|
||||
|
||||
type: loki
|
||||
# 数据源类型
|
||||
# 必须与 Grafana 支持的数据源插件类型匹配
|
||||
|
||||
url: http://fba_loki:3100
|
||||
# Loki 服务器地址
|
||||
|
||||
isDefault: false
|
||||
# 是否设为默认数据源
|
||||
|
||||
jsonData:
|
||||
# 数据源特定的 JSON 配置
|
||||
|
||||
derivedFields:
|
||||
# 派生字段配置
|
||||
# 用于从日志中提取字段并创建链接到其他数据源
|
||||
# 这是实现日志到链路追踪关联的关键配置
|
||||
|
||||
- datasourceUid: tempo
|
||||
# 目标数据源的 UID
|
||||
# 点击链接时将跳转到此数据源
|
||||
|
||||
matcherRegex: '"traceid":"([a-f0-9]{32})"'
|
||||
# 正则表达式,用于从日志内容中提取 Trace ID
|
||||
|
||||
matcherType: regex
|
||||
# 匹配器类型
|
||||
# 可选值: regex(正则表达式)
|
||||
|
||||
name: TraceID
|
||||
# 派生字段的名称
|
||||
# 将显示在日志详情中
|
||||
|
||||
url: $${__value.raw}
|
||||
# 链接 URL 模板
|
||||
# $${__value.raw} 表示提取的原始值
|
||||
|
||||
urlDisplayLabel: 查看 Trace
|
||||
# 链接显示的文本标签
|
||||
|
||||
# ==========================================================================
|
||||
# Prometheus 数据源配置
|
||||
# Prometheus 是云原生监控系统,用于收集和存储时序指标数据
|
||||
# 官方文档: https://prometheus.io/docs/
|
||||
# ==========================================================================
|
||||
- name: Prometheus
|
||||
# 数据源显示名称
|
||||
|
||||
uid: prometheus
|
||||
# 数据源唯一标识符
|
||||
|
||||
type: prometheus
|
||||
# 数据源类型
|
||||
|
||||
url: http://fba_prometheus:9090
|
||||
# Prometheus 服务器地址
|
||||
|
||||
isDefault: true
|
||||
# 设为默认数据源
|
||||
|
||||
jsonData:
|
||||
httpMethod: POST
|
||||
# 查询使用的 HTTP 方法
|
||||
|
||||
exemplarTraceIdDestinations:
|
||||
# Exemplar(范例)配置
|
||||
# Exemplar 是指标数据点关联的 Trace ID
|
||||
# 用于从指标跳转到对应的链路追踪
|
||||
|
||||
- name: TraceID
|
||||
# Exemplar 中 Trace ID 的字段名称
|
||||
|
||||
datasourceUid: tempo
|
||||
# 目标 Tempo 数据源的 UID
|
||||
# 点击 Exemplar 时将跳转到此数据源查看 Trace
|
||||
|
||||
# ==========================================================================
|
||||
# Tempo 数据源配置
|
||||
# Tempo 是 Grafana 的分布式链路追踪后端
|
||||
# 官方文档: https://grafana.com/docs/tempo/latest/
|
||||
# ==========================================================================
|
||||
- name: Tempo
|
||||
# 数据源显示名称
|
||||
|
||||
uid: tempo
|
||||
# 数据源唯一标识符
|
||||
|
||||
type: tempo
|
||||
# 数据源类型
|
||||
|
||||
url: http://fba_tempo:3200
|
||||
# Tempo 服务器地址
|
||||
|
||||
jsonData:
|
||||
nodeGraph:
|
||||
enabled: true
|
||||
# 启用节点图功能
|
||||
# 可视化展示服务之间的调用关系图
|
||||
|
||||
tracesToLogsV2:
|
||||
# Trace 到日志的关联配置(V2 版本)
|
||||
# 允许从 Trace 详情页面跳转到相关日志
|
||||
|
||||
datasourceUid: loki
|
||||
# 日志数据源的 UID
|
||||
|
||||
filterByTraceID: true
|
||||
# 是否按 Trace ID 过滤日志
|
||||
|
||||
spanStartTimeShift: "-1m"
|
||||
# Span 开始时间偏移
|
||||
# 用于捕获 Span 开始前的相关日志
|
||||
|
||||
spanEndTimeShift: "1m"
|
||||
# Span 结束时间偏移
|
||||
# 用于捕获 Span 结束后的相关日志
|
||||
|
||||
customQuery: true
|
||||
# 启用自定义查询
|
||||
# 允许使用下面的 query 字段自定义日志查询语句
|
||||
|
||||
query: '{service_name="fba_server"} | json | traceid="${__span.traceId}"'
|
||||
# 自定义 LogQL 查询语句
|
||||
|
||||
search:
|
||||
hide: false
|
||||
# 是否隐藏搜索功能
|
||||
|
||||
serviceMap:
|
||||
datasourceUid: prometheus
|
||||
# 服务地图数据源
|
||||
# 使用 Prometheus 中的指标数据生成服务依赖图
|
||||
# 需要 Tempo 的 metrics_generator 功能配合
|
||||
|
||||
lokiSearch:
|
||||
datasourceUid: loki
|
||||
# Loki 搜索数据源
|
||||
# 允许在 Tempo 中使用 Loki 进行日志搜索
|
||||
# 实现基于日志内容查找 Trace 的功能
|
||||
@@ -0,0 +1,40 @@
|
||||
# ============================================================================
|
||||
# Grafana 主配置文件
|
||||
# 官方文档: https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/
|
||||
# ============================================================================
|
||||
|
||||
# [server] 服务器配置
|
||||
# 用于配置 Grafana HTTP 服务器的基本设置
|
||||
[server]
|
||||
# 协议类型
|
||||
protocol = http
|
||||
|
||||
# HTTP 服务监听端口
|
||||
http_port = 3000
|
||||
|
||||
# [security] 安全配置
|
||||
# 用于配置 Grafana 安全相关的默认设置
|
||||
[security]
|
||||
# 默认 Grafana 管理员用户名
|
||||
admin_user = admin
|
||||
|
||||
# 默认 Grafana 管理员密码
|
||||
admin_password = 123456
|
||||
|
||||
# 如果您将 Grafana 托管在 HTTPS
|
||||
;cookie_secure = true
|
||||
|
||||
# [users] 用户配置
|
||||
# 用于配置用户相关的默认设置
|
||||
[users]
|
||||
# 默认 UI 主题
|
||||
default_theme=system
|
||||
|
||||
# 默认界面语言
|
||||
default_language = zh-Hans
|
||||
|
||||
# [dashboards] 仪表盘配置
|
||||
# 用于配置仪表盘相关的默认设置
|
||||
[dashboards]
|
||||
# 默认首页仪表盘路径
|
||||
default_home_dashboard_path = /etc/grafana/dashboards/fba_server.json
|
||||
@@ -0,0 +1,13 @@
|
||||
# ============================================================================
|
||||
# Prometheus 配置文件
|
||||
# Prometheus 是云原生监控和告警系统,专注于时序指标数据
|
||||
# 官方文档: https://prometheus.io/docs/prometheus/latest/configuration/configuration/
|
||||
# ============================================================================
|
||||
|
||||
# global: 全局配置
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
# 指标抓取间隔
|
||||
|
||||
evaluation_interval: 15s
|
||||
# 规则评估间隔
|
||||
@@ -0,0 +1,127 @@
|
||||
# ============================================================================
|
||||
# Tempo 配置文件
|
||||
# Tempo 是 Grafana 的高性能、低成本分布式链路追踪后端
|
||||
# 官方文档: https://grafana.com/docs/tempo/latest/configuration/
|
||||
# ============================================================================
|
||||
|
||||
# server: HTTP 服务器配置
|
||||
server:
|
||||
http_listen_port: 3200
|
||||
# HTTP API 监听端口
|
||||
# 用于查询 Trace 数据和健康检查
|
||||
|
||||
# distributor: 分发器配置
|
||||
distributor:
|
||||
receivers:
|
||||
# 接收器配置
|
||||
|
||||
otlp:
|
||||
# OpenTelemetry Protocol (OTLP) 接收器
|
||||
# OTLP 是 OpenTelemetry 的原生协议,推荐使用
|
||||
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: "0.0.0.0:4317"
|
||||
# gRPC 协议端点
|
||||
|
||||
http:
|
||||
endpoint: "0.0.0.0:4318"
|
||||
# HTTP 协议端点
|
||||
|
||||
# storage: 存储配置
|
||||
storage:
|
||||
trace:
|
||||
backend: local
|
||||
# 存储后端类型
|
||||
# 生产环境建议使用对象存储
|
||||
|
||||
wal:
|
||||
path: /tmp/tempo/wal
|
||||
# Write-Ahead Log (WAL) 目录
|
||||
# WAL 用于在数据写入存储前的临时缓存
|
||||
# 确保数据不会因意外重启而丢失
|
||||
# 注意: 生产环境应使用持久化存储路径
|
||||
|
||||
local:
|
||||
path: /tmp/tempo/blocks
|
||||
# 本地存储的数据块目录
|
||||
# 存储压缩后的 Trace 数据块
|
||||
# 仅在 backend: local 时使用
|
||||
|
||||
# metrics_generator: 指标生成器配置
|
||||
# 从 Trace 数据自动生成 Prometheus 指标
|
||||
# 这是实现 Trace 到 Metrics 关联的关键功能
|
||||
metrics_generator:
|
||||
registry:
|
||||
external_labels:
|
||||
source: tempo
|
||||
# 外部标签
|
||||
# 添加到所有生成的指标上
|
||||
|
||||
storage:
|
||||
path: /tmp/tempo/generator/wal
|
||||
# 指标生成器的 WAL 目录
|
||||
# 用于临时存储待发送的指标数据
|
||||
|
||||
remote_write:
|
||||
# 远程写入配置
|
||||
|
||||
- url: http://fba_prometheus:9090/api/v1/write
|
||||
# Prometheus 远程写入 API 地址
|
||||
# 需要 Prometheus 启用 remote-write-receiver 功能
|
||||
|
||||
send_exemplars: true
|
||||
# 发送 Exemplar 数据
|
||||
# Exemplar 将指标数据点与 Trace ID 关联
|
||||
# 实现从指标图表点击跳转到对应 Trace
|
||||
|
||||
traces_storage:
|
||||
path: /tmp/tempo/generator/traces
|
||||
# Trace 数据临时存储路径
|
||||
# 用于指标生成器处理 Trace 数据
|
||||
|
||||
processor:
|
||||
# 处理器配置
|
||||
# 定义如何从 Trace 生成指标
|
||||
|
||||
span_metrics:
|
||||
# Span 指标处理器
|
||||
|
||||
dimensions:
|
||||
# 指标维度(标签)
|
||||
# 这些属性将作为 Prometheus 指标的标签
|
||||
|
||||
- service.name
|
||||
- http.method
|
||||
- http.target
|
||||
- http.status_code
|
||||
|
||||
service_graphs:
|
||||
# 服务图处理器
|
||||
# 生成服务间调用关系的指标
|
||||
# 用于 Grafana 中的服务地图可视化
|
||||
|
||||
dimensions:
|
||||
- service.name
|
||||
|
||||
local_blocks:
|
||||
# 本地块处理器
|
||||
# 处理本地存储的 Trace 数据块
|
||||
|
||||
flush_to_storage: true
|
||||
# 是否将处理后的数据刷新到存储
|
||||
|
||||
# overrides: 覆盖配置
|
||||
overrides:
|
||||
defaults:
|
||||
# 默认配置
|
||||
# 适用于所有租户(单租户模式下为全局配置)
|
||||
|
||||
metrics_generator:
|
||||
processors:
|
||||
# 启用的指标处理器列表
|
||||
# 只有在此列表中的处理器才会运行
|
||||
|
||||
- span-metrics
|
||||
- service-graphs
|
||||
- local-blocks
|
||||
@@ -31,7 +31,7 @@ server {
|
||||
}
|
||||
|
||||
location /flower/ {
|
||||
proxy_pass http://fba_server:8555;
|
||||
proxy_pass http://fba_celery_flower:8555;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[program:fba_celery_beat]
|
||||
directory=/fba/backend
|
||||
command=/usr/local/bin/celery -A app.task.celery beat --loglevel=INFO
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fba/fba_celery_beat.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=5
|
||||
@@ -0,0 +1,11 @@
|
||||
[program:fba_celery_flower]
|
||||
directory=/fba/backend
|
||||
command=/usr/local/bin/celery -A app.task.celery flower --port=8555 --url-prefix=flower --basic-auth=admin:123456
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fba/fba_celery_flower.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=5
|
||||
@@ -0,0 +1,11 @@
|
||||
[program:fba_celery_worker]
|
||||
directory=/fba/backend
|
||||
command=/usr/local/bin/celery -A app.task.celery worker -P gevent -c 1000 --loglevel=INFO
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fba/fba_celery_worker.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=5
|
||||
+162
-7
@@ -19,6 +19,8 @@ volumes:
|
||||
name: fba_static_upload
|
||||
fba_rabbitmq:
|
||||
name: fba_rabbitmq
|
||||
fba_prometheus_data:
|
||||
name: fba_prometheus_data
|
||||
|
||||
services:
|
||||
fba_server:
|
||||
@@ -30,11 +32,12 @@ services:
|
||||
- "8001:8001"
|
||||
container_name: fba_server
|
||||
restart: always
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
# 如果你是 mysql 用户,应将 fba_postgres 修改为 fba_mysql
|
||||
depends_on:
|
||||
- fba_postgres
|
||||
- fba_redis
|
||||
- fba_celery
|
||||
volumes:
|
||||
- ./deploy/backend/docker-compose/.env.server:/fba/backend/.env
|
||||
- fba_static:/fba/backend/app/static
|
||||
@@ -167,17 +170,18 @@ services:
|
||||
networks:
|
||||
- fba_network
|
||||
|
||||
fba_celery:
|
||||
fba_celery_worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- SERVER_TYPE=fba_celery
|
||||
image: fba_celery:latest
|
||||
ports:
|
||||
- "8555:8555"
|
||||
container_name: fba_celery
|
||||
- SERVER_TYPE=fba_celery_worker
|
||||
image: fba_celery_worker:latest
|
||||
# 如果你需要分布式部署 Worker,则必须移除此 container_name 配置
|
||||
container_name: fba_celery_worker
|
||||
restart: always
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
depends_on:
|
||||
- fba_rabbitmq
|
||||
volumes:
|
||||
@@ -191,3 +195,154 @@ services:
|
||||
wait-for-it -s fba_rabbitmq:5672 -t 300
|
||||
supervisord -c /etc/supervisor/supervisord.conf
|
||||
supervisorctl restart
|
||||
|
||||
fba_celery_beat:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- SERVER_TYPE=fba_celery_beat
|
||||
image: fba_celery_beat:latest
|
||||
container_name: fba_celery_beat
|
||||
restart: always
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
depends_on:
|
||||
- fba_rabbitmq
|
||||
- fba_celery_worker
|
||||
volumes:
|
||||
- ./deploy/backend/docker-compose/.env.server:/fba/backend/.env
|
||||
networks:
|
||||
- fba_network
|
||||
command:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
wait-for-it -s fba_rabbitmq:5672 -t 300
|
||||
supervisord -c /etc/supervisor/supervisord.conf
|
||||
supervisorctl restart
|
||||
|
||||
fba_celery_flower:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- SERVER_TYPE=fba_celery_flower
|
||||
image: fba_celery_flower:latest
|
||||
ports:
|
||||
- "8555:8555"
|
||||
container_name: fba_celery_flower
|
||||
restart: always
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
depends_on:
|
||||
- fba_rabbitmq
|
||||
- fba_celery_worker
|
||||
volumes:
|
||||
- ./deploy/backend/docker-compose/.env.server:/fba/backend/.env
|
||||
networks:
|
||||
- fba_network
|
||||
command:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
wait-for-it -s fba_rabbitmq:5672 -t 300
|
||||
supervisord -c /etc/supervisor/supervisord.conf
|
||||
supervisorctl restart
|
||||
|
||||
fba_celery_exporter:
|
||||
image: danihodovic/celery-exporter
|
||||
restart: always
|
||||
ports:
|
||||
- "9808:9808"
|
||||
container_name: fba_celery_exporter
|
||||
environment:
|
||||
- CE_BROKER_URL=amqp://guest:guest@fba_rabbitmq:5672//
|
||||
depends_on:
|
||||
- fba_rabbitmq
|
||||
networks:
|
||||
- fba_network
|
||||
|
||||
fba_loki:
|
||||
image: grafana/loki
|
||||
restart: always
|
||||
ports:
|
||||
- "3100:3100"
|
||||
container_name: fba_loki
|
||||
networks:
|
||||
- fba_network
|
||||
command: -config.file=/etc/loki/local-config.yaml
|
||||
|
||||
fba_prometheus:
|
||||
image: prom/prometheus
|
||||
restart: always
|
||||
ports:
|
||||
- "9090:9090"
|
||||
container_name: fba_prometheus
|
||||
depends_on:
|
||||
- fba_loki
|
||||
volumes:
|
||||
- ./deploy/backend/grafana/fba_prometheus.yml:/workspace/fba_prometheus.yml
|
||||
- fba_prometheus_data:/prometheus
|
||||
networks:
|
||||
- fba_network
|
||||
command:
|
||||
- --web.enable-remote-write-receiver
|
||||
- --enable-feature=exemplar-storage
|
||||
- --config.file=/workspace/fba_prometheus.yml
|
||||
|
||||
fba_alloy:
|
||||
image: grafana/alloy
|
||||
restart: always
|
||||
ports:
|
||||
- "4317:4317"
|
||||
- "4318:4318"
|
||||
- "12345:12345"
|
||||
container_name: fba_alloy
|
||||
depends_on:
|
||||
- fba_loki
|
||||
volumes:
|
||||
- ./deploy/backend/grafana/fba_config.alloy:/etc/alloy/fba_config.alloy
|
||||
networks:
|
||||
- fba_network
|
||||
command:
|
||||
- run
|
||||
- /etc/alloy/fba_config.alloy
|
||||
- --server.http.listen-addr=0.0.0.0:12345
|
||||
|
||||
fba_tempo:
|
||||
image: grafana/tempo:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "3200:3200"
|
||||
expose:
|
||||
- 4317
|
||||
- 4318
|
||||
container_name: fba_tempo
|
||||
depends_on:
|
||||
- fba_loki
|
||||
volumes:
|
||||
- ./deploy/backend/grafana/fba_tempo.yml:/etc/tempo/fba_tempo.yml
|
||||
networks:
|
||||
- fba_network
|
||||
command:
|
||||
- -config.file=/etc/tempo/fba_tempo.yml
|
||||
- -auth.enabled=false
|
||||
|
||||
fba_grafana:
|
||||
image: grafana/grafana
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
container_name: fba_grafana
|
||||
depends_on:
|
||||
- fba_loki
|
||||
- fba_prometheus
|
||||
volumes:
|
||||
- ./deploy/backend/grafana/fba_grafana.ini:/etc/grafana/grafana.ini
|
||||
- ./deploy/backend/grafana/dashboards/fba_celery.json:/etc/grafana/dashboards/fba_celery.json
|
||||
- ./deploy/backend/grafana/dashboards/fba_server.json:/etc/grafana/dashboards/fba_server.json
|
||||
- ./deploy/backend/grafana/fba_dashboards.yml:/etc/grafana/provisioning/dashboards/fba_dashboards.yml
|
||||
- ./deploy/backend/grafana/fba_datasource.yml:/etc/grafana/provisioning/datasources/fba_datasource.yml
|
||||
networks:
|
||||
- fba_network
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
pre-commit run --all-files --verbose --show-diff-on-failure
|
||||
prek run --all-files --verbose --show-diff-on-failure
|
||||
|
||||
+11
-5
@@ -18,7 +18,7 @@ dependencies = [
|
||||
"asyncpg>=0.31.0",
|
||||
"bcrypt>=5.0.0",
|
||||
"cappa>=0.30.4",
|
||||
"celery>=5.5.3",
|
||||
"celery>=5.6.0",
|
||||
# When celery version < 6.0.0
|
||||
# https://github.com/celery/celery/issues/7874
|
||||
"celery-aio-pool>=0.1.0rc8",
|
||||
@@ -27,7 +27,7 @@ dependencies = [
|
||||
"fast-captcha>=0.3.2",
|
||||
"fastapi-limiter>=0.1.6",
|
||||
"fastapi-pagination>=0.15.0",
|
||||
"fastapi[standard-no-fastapi-cloud-cli]>=0.122.0",
|
||||
"fastapi[standard-no-fastapi-cloud-cli]>=0.123.5",
|
||||
"flower>=2.0.1",
|
||||
"gevent>=25.9.1",
|
||||
"granian>=2.6.0",
|
||||
@@ -36,11 +36,17 @@ dependencies = [
|
||||
"jinja2>=3.1.6",
|
||||
"loguru>=0.7.3",
|
||||
"msgspec>=0.20.0",
|
||||
"opentelemetry-exporter-otlp-proto-grpc>=1.39.0",
|
||||
"opentelemetry-instrumentation-fastapi>=0.60b0",
|
||||
"opentelemetry-instrumentation-logging>=0.60b0",
|
||||
"opentelemetry-instrumentation-sqlalchemy>=0.60b0",
|
||||
"opentelemetry-sdk>=1.39.0",
|
||||
"prometheus-client>=0.23.1",
|
||||
"psutil>=7.1.3",
|
||||
# https://github.com/fastapi-practices/fastapi_best_architecture/issues/887
|
||||
"psycopg[binary]==3.2.10",
|
||||
"pwdlib>=0.3.0",
|
||||
"pydantic>=2.12.4",
|
||||
"pydantic>=2.12.5",
|
||||
"pydantic-settings>=2.12.0",
|
||||
"pymysql>=1.1.2",
|
||||
"python-jose>=3.5.0",
|
||||
@@ -49,7 +55,7 @@ dependencies = [
|
||||
"rtoml>=0.13.0",
|
||||
"sqlalchemy-crud-plus>=1.13.1",
|
||||
"sqlalchemy[asyncio]>=2.0.44",
|
||||
"sqlparse>=0.5.3",
|
||||
"sqlparse>=0.5.4",
|
||||
"starlette-context>=0.4.0",
|
||||
"user-agents>=2.2.0",
|
||||
]
|
||||
@@ -60,7 +66,7 @@ dev = [
|
||||
"pytest-sugar>=1.1.1",
|
||||
]
|
||||
lint = [
|
||||
"prek>=0.2.18",
|
||||
"prek>=0.2.19",
|
||||
]
|
||||
server = [
|
||||
"aio-pika>=9.5.8",
|
||||
|
||||
+90
-14
@@ -9,13 +9,15 @@ annotated-doc==0.0.4
|
||||
# via fastapi
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.11.0
|
||||
anyio==4.12.0
|
||||
# via
|
||||
# httpx
|
||||
# starlette
|
||||
# watchfiles
|
||||
asgiref==3.11.0
|
||||
# via fastapi-best-architecture
|
||||
# via
|
||||
# fastapi-best-architecture
|
||||
# opentelemetry-instrumentation-asgi
|
||||
async-timeout==5.0.1 ; python_full_version < '3.11.3'
|
||||
# via
|
||||
# asyncpg
|
||||
@@ -28,11 +30,11 @@ bcrypt==5.0.0
|
||||
# via fastapi-best-architecture
|
||||
bidict==0.23.1
|
||||
# via python-socketio
|
||||
billiard==4.2.3
|
||||
billiard==4.2.4
|
||||
# via celery
|
||||
cappa==0.30.4
|
||||
# via fastapi-best-architecture
|
||||
celery==5.5.3
|
||||
celery==5.6.0
|
||||
# via
|
||||
# celery-aio-pool
|
||||
# fastapi-best-architecture
|
||||
@@ -79,13 +81,14 @@ ecdsa==0.19.1
|
||||
# via python-jose
|
||||
email-validator==2.3.0
|
||||
# via fastapi
|
||||
exceptiongroup==1.3.1 ; python_full_version < '3.11'
|
||||
exceptiongroup==1.3.1
|
||||
# via
|
||||
# anyio
|
||||
# celery
|
||||
# pytest
|
||||
fast-captcha==0.3.2
|
||||
# via fastapi-best-architecture
|
||||
fastapi==0.122.0
|
||||
fastapi==0.123.5
|
||||
# via
|
||||
# fastapi-best-architecture
|
||||
# fastapi-limiter
|
||||
@@ -100,12 +103,16 @@ flower==2.0.1
|
||||
# via fastapi-best-architecture
|
||||
gevent==25.9.1
|
||||
# via fastapi-best-architecture
|
||||
googleapis-common-protos==1.72.0
|
||||
# via opentelemetry-exporter-otlp-proto-grpc
|
||||
granian==2.6.0
|
||||
# via fastapi-best-architecture
|
||||
greenlet==3.2.4
|
||||
# via
|
||||
# gevent
|
||||
# sqlalchemy
|
||||
grpcio==1.76.0
|
||||
# via opentelemetry-exporter-otlp-proto-grpc
|
||||
h11==0.16.0
|
||||
# via
|
||||
# httpcore
|
||||
@@ -126,6 +133,8 @@ idna==3.11
|
||||
# anyio
|
||||
# email-validator
|
||||
# httpx
|
||||
importlib-metadata==8.7.0
|
||||
# via opentelemetry-api
|
||||
iniconfig==2.3.0
|
||||
# via pytest
|
||||
ip2loc==1.0.0
|
||||
@@ -136,7 +145,7 @@ jinja2==3.1.6
|
||||
# via
|
||||
# fastapi
|
||||
# fastapi-best-architecture
|
||||
kombu==5.5.4
|
||||
kombu==5.6.1
|
||||
# via celery
|
||||
loguru==0.7.3
|
||||
# via fastapi-best-architecture
|
||||
@@ -152,19 +161,74 @@ mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
msgspec==0.20.0
|
||||
# via fastapi-best-architecture
|
||||
opentelemetry-api==1.39.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-instrumentation
|
||||
# opentelemetry-instrumentation-asgi
|
||||
# opentelemetry-instrumentation-fastapi
|
||||
# opentelemetry-instrumentation-logging
|
||||
# opentelemetry-instrumentation-sqlalchemy
|
||||
# opentelemetry-sdk
|
||||
# opentelemetry-semantic-conventions
|
||||
opentelemetry-exporter-otlp-proto-common==1.39.0
|
||||
# via opentelemetry-exporter-otlp-proto-grpc
|
||||
opentelemetry-exporter-otlp-proto-grpc==1.39.0
|
||||
# via fastapi-best-architecture
|
||||
opentelemetry-instrumentation==0.60b0
|
||||
# via
|
||||
# opentelemetry-instrumentation-asgi
|
||||
# opentelemetry-instrumentation-fastapi
|
||||
# opentelemetry-instrumentation-logging
|
||||
# opentelemetry-instrumentation-sqlalchemy
|
||||
opentelemetry-instrumentation-asgi==0.60b0
|
||||
# via opentelemetry-instrumentation-fastapi
|
||||
opentelemetry-instrumentation-fastapi==0.60b0
|
||||
# via fastapi-best-architecture
|
||||
opentelemetry-instrumentation-logging==0.60b0
|
||||
# via fastapi-best-architecture
|
||||
opentelemetry-instrumentation-sqlalchemy==0.60b0
|
||||
# via fastapi-best-architecture
|
||||
opentelemetry-proto==1.39.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-common
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
opentelemetry-sdk==1.39.0
|
||||
# via
|
||||
# fastapi-best-architecture
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
opentelemetry-semantic-conventions==0.60b0
|
||||
# via
|
||||
# opentelemetry-instrumentation
|
||||
# opentelemetry-instrumentation-asgi
|
||||
# opentelemetry-instrumentation-fastapi
|
||||
# opentelemetry-instrumentation-sqlalchemy
|
||||
# opentelemetry-sdk
|
||||
opentelemetry-util-http==0.60b0
|
||||
# via
|
||||
# opentelemetry-instrumentation-asgi
|
||||
# opentelemetry-instrumentation-fastapi
|
||||
packaging==25.0
|
||||
# via
|
||||
# kombu
|
||||
# opentelemetry-instrumentation
|
||||
# opentelemetry-instrumentation-sqlalchemy
|
||||
# pytest
|
||||
pillow==12.0.0
|
||||
# via fast-captcha
|
||||
pluggy==1.6.0
|
||||
# via pytest
|
||||
prek==0.2.18
|
||||
prek==0.2.19
|
||||
prometheus-client==0.23.1
|
||||
# via flower
|
||||
# via
|
||||
# fastapi-best-architecture
|
||||
# flower
|
||||
prompt-toolkit==3.0.52
|
||||
# via click-repl
|
||||
protobuf==6.33.2
|
||||
# via
|
||||
# googleapis-common-protos
|
||||
# opentelemetry-proto
|
||||
psutil==7.1.3
|
||||
# via fastapi-best-architecture
|
||||
psycopg==3.2.10
|
||||
@@ -179,7 +243,7 @@ pyasn1==0.6.1
|
||||
# rsa
|
||||
pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'
|
||||
# via cffi
|
||||
pydantic==2.12.4
|
||||
pydantic==2.12.5
|
||||
# via
|
||||
# fastapi
|
||||
# fastapi-best-architecture
|
||||
@@ -226,7 +290,7 @@ rich==14.2.0
|
||||
# cappa
|
||||
# rich-toolkit
|
||||
# typer
|
||||
rich-toolkit==0.16.0
|
||||
rich-toolkit==0.17.0
|
||||
# via fastapi-cli
|
||||
rsa==4.9.1
|
||||
# via python-jose
|
||||
@@ -240,8 +304,6 @@ six==1.17.0
|
||||
# via
|
||||
# ecdsa
|
||||
# python-dateutil
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
sqlalchemy==2.0.44
|
||||
# via
|
||||
# alembic
|
||||
@@ -249,7 +311,7 @@ sqlalchemy==2.0.44
|
||||
# sqlalchemy-crud-plus
|
||||
sqlalchemy-crud-plus==1.13.1
|
||||
# via fastapi-best-architecture
|
||||
sqlparse==0.5.3
|
||||
sqlparse==0.5.4
|
||||
# via fastapi-best-architecture
|
||||
starlette==0.50.0
|
||||
# via
|
||||
@@ -281,6 +343,11 @@ typing-extensions==4.15.0
|
||||
# exceptiongroup
|
||||
# fastapi
|
||||
# fastapi-pagination
|
||||
# grpcio
|
||||
# opentelemetry-api
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-sdk
|
||||
# opentelemetry-semantic-conventions
|
||||
# psycopg
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
@@ -299,6 +366,9 @@ tzdata==2025.2
|
||||
# via
|
||||
# kombu
|
||||
# psycopg
|
||||
# tzlocal
|
||||
tzlocal==5.3.1
|
||||
# via celery
|
||||
ua-parser==1.0.1
|
||||
# via user-agents
|
||||
ua-parser-builtins==0.18.0.post1
|
||||
@@ -326,8 +396,14 @@ websockets==15.0.1
|
||||
# via uvicorn
|
||||
win32-setctime==1.2.0 ; sys_platform == 'win32'
|
||||
# via loguru
|
||||
wrapt==1.17.3
|
||||
# via
|
||||
# opentelemetry-instrumentation
|
||||
# opentelemetry-instrumentation-sqlalchemy
|
||||
wsproto==1.3.2
|
||||
# via simple-websocket
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
zope-event==6.1
|
||||
# via gevent
|
||||
zope-interface==8.1.1
|
||||
|
||||
@@ -80,17 +80,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.11.0"
|
||||
version = "4.12.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -291,11 +290,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "billiard"
|
||||
version = "4.2.3"
|
||||
version = "4.2.4"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/6a/50/cc2b8b6e6433918a6b9a3566483b743dcd229da1e974be9b5f259db3aad7/billiard-4.2.3.tar.gz", hash = "sha256:96486f0885afc38219d02d5f0ccd5bec8226a414b834ab244008cbb0025b8dcb" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b3/cc/38b6f87170908bd8aaf9e412b021d17e85f690abe00edf50192f1a4566b9/billiard-4.2.3-py3-none-any.whl", hash = "sha256:989e9b688e3abf153f307b68a1328dfacfb954e30a4f920005654e276c69236b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -314,7 +313,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "celery"
|
||||
version = "5.5.3"
|
||||
version = "5.6.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "billiard" },
|
||||
@@ -322,13 +321,15 @@ dependencies = [
|
||||
{ name = "click-didyoumean" },
|
||||
{ name = "click-plugins" },
|
||||
{ name = "click-repl" },
|
||||
{ name = "exceptiongroup" },
|
||||
{ name = "kombu" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "tzlocal" },
|
||||
{ name = "vine" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ad/5f/b681ae3c89290d2ea6562ea96b40f5af6f6fc5f7743e2cd1a19e47721548/celery-5.6.0.tar.gz", hash = "sha256:641405206042d52ae460e4e9751a2e31b06cf80ab836fcf92e0b9311d7ea8113" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/01/4e/53a125038d6a814491a0ae3457435c13cf8821eb602292cf9db37ce35f62/celery-5.6.0-py3-none-any.whl", hash = "sha256:33cf01477b175017fc8f22c5ee8a65157591043ba8ca78a443fe703aa910f581" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -654,7 +655,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.122.0"
|
||||
version = "0.123.5"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
@@ -662,9 +663,9 @@ dependencies = [
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b2/de/3ee97a4f6ffef1fb70bf20561e4f88531633bb5045dc6cebc0f8471f764d/fastapi-0.122.0.tar.gz", hash = "sha256:cd9b5352031f93773228af8b4c443eedc2ac2aa74b27780387b853c3726fb94b" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/78/b8/c6e916565af8a8e1c8f5a4736b31a6995adb51dbd4cbefc8b022e753ecb9/fastapi-0.123.5.tar.gz", hash = "sha256:54bbb660ca231d3985474498b51c621ddcf8888d9a4c1ecb10aa40ec217e4965" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7a/93/aa8072af4ff37b795f6bbf43dcaf61115f40f49935c7dbb180c9afc3f421/fastapi-0.122.0-py3-none-any.whl", hash = "sha256:a456e8915dfc6c8914a50d9651133bd47ec96d331c5b44600baa635538a30d67" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c2/dc/faa52fe784892bb057934248ded02705d26ca3aca562876e61c239947036/fastapi-0.123.5-py3-none-any.whl", hash = "sha256:a9c708e47c0fa424139cddb8601d0f92d3111b77843c22e9c8d0164d65fe3c97" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -703,6 +704,12 @@ dependencies = [
|
||||
{ name = "jinja2" },
|
||||
{ name = "loguru" },
|
||||
{ name = "msgspec" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
|
||||
{ name = "opentelemetry-instrumentation-fastapi" },
|
||||
{ name = "opentelemetry-instrumentation-logging" },
|
||||
{ name = "opentelemetry-instrumentation-sqlalchemy" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "prometheus-client" },
|
||||
{ name = "psutil" },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
{ name = "pwdlib" },
|
||||
@@ -741,12 +748,12 @@ requires-dist = [
|
||||
{ name = "asyncpg", specifier = ">=0.31.0" },
|
||||
{ name = "bcrypt", specifier = ">=5.0.0" },
|
||||
{ name = "cappa", specifier = ">=0.30.4" },
|
||||
{ name = "celery", specifier = ">=5.5.3" },
|
||||
{ name = "celery", specifier = ">=5.6.0" },
|
||||
{ name = "celery-aio-pool", specifier = ">=0.1.0rc8" },
|
||||
{ name = "cryptography", specifier = ">=46.0.3" },
|
||||
{ name = "dulwich", specifier = ">=0.24.10" },
|
||||
{ name = "fast-captcha", specifier = ">=0.3.2" },
|
||||
{ name = "fastapi", extras = ["standard-no-fastapi-cloud-cli"], specifier = ">=0.122.0" },
|
||||
{ name = "fastapi", extras = ["standard-no-fastapi-cloud-cli"], specifier = ">=0.123.5" },
|
||||
{ name = "fastapi-limiter", specifier = ">=0.1.6" },
|
||||
{ name = "fastapi-pagination", specifier = ">=0.15.0" },
|
||||
{ name = "flower", specifier = ">=2.0.1" },
|
||||
@@ -757,10 +764,16 @@ requires-dist = [
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
{ name = "loguru", specifier = ">=0.7.3" },
|
||||
{ name = "msgspec", specifier = ">=0.20.0" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.39.0" },
|
||||
{ name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.60b0" },
|
||||
{ name = "opentelemetry-instrumentation-logging", specifier = ">=0.60b0" },
|
||||
{ name = "opentelemetry-instrumentation-sqlalchemy", specifier = ">=0.60b0" },
|
||||
{ name = "opentelemetry-sdk", specifier = ">=1.39.0" },
|
||||
{ name = "prometheus-client", specifier = ">=0.23.1" },
|
||||
{ name = "psutil", specifier = ">=7.1.3" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = "==3.2.10" },
|
||||
{ name = "pwdlib", specifier = ">=0.3.0" },
|
||||
{ name = "pydantic", specifier = ">=2.12.4" },
|
||||
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.12.0" },
|
||||
{ name = "pymysql", specifier = ">=1.1.2" },
|
||||
{ name = "python-jose", specifier = ">=3.5.0" },
|
||||
@@ -769,7 +782,7 @@ requires-dist = [
|
||||
{ name = "rtoml", specifier = ">=0.13.0" },
|
||||
{ name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.44" },
|
||||
{ name = "sqlalchemy-crud-plus", specifier = ">=1.13.1" },
|
||||
{ name = "sqlparse", specifier = ">=0.5.3" },
|
||||
{ name = "sqlparse", specifier = ">=0.5.4" },
|
||||
{ name = "starlette-context", specifier = ">=0.4.0" },
|
||||
{ name = "user-agents", specifier = ">=2.2.0" },
|
||||
]
|
||||
@@ -779,7 +792,7 @@ dev = [
|
||||
{ name = "pytest", specifier = ">=9.0.0" },
|
||||
{ name = "pytest-sugar", specifier = ">=1.1.1" },
|
||||
]
|
||||
lint = [{ name = "prek", specifier = ">=0.2.18" }]
|
||||
lint = [{ name = "prek", specifier = ">=0.2.19" }]
|
||||
server = [
|
||||
{ name = "aio-pika", specifier = ">=9.5.8" },
|
||||
{ name = "wait-for-it", specifier = ">=2.3.0" },
|
||||
@@ -900,6 +913,18 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c2/69/a7c4ba2ffbc7c7dbf6d8b4f5d0f0a421f7815d229f4909854266c445a3d4/gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "googleapis-common-protos"
|
||||
version = "1.72.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "granian"
|
||||
version = "2.6.0"
|
||||
@@ -1050,6 +1075,67 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "1.76.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/88/17/ff4795dc9a34b6aee6ec379f1b66438a3789cd1315aac0cbab60d92f74b3/grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4e/ff/35f9b96e3fa2f12e1dcd58a4513a2e2294a001d64dec81677361b7040c9a/grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3e/1c/8374990f9545e99462caacea5413ed783014b3b66ace49e35c533f07507b/grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1e/77/36fd7d7c75a6c12542c90a6d647a27935a1ecaad03e0ffdb7c42db6b04d2/grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/38/f7/e3cdb252492278e004722306c5a8935eae91e64ea11f0af3437a7de2e2b7/grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7e/20/340db7af162ccd20a0893b5f3c4a5d676af7b71105517e62279b5b61d95a/grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/10/f0/b2160addc1487bd8fa4810857a27132fb4ce35c1b330c2f3ac45d697b106/grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2c/2c/ac6f98aa113c6ef111b3f347854e99ebb7fb9d8f7bb3af1491d438f62af4/grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/90/84/7852f7e087285e3ac17a2703bc4129fafee52d77c6c82af97d905566857e/grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/10/30/d3d2adcbb6dd3ff59d6ac3df6ef830e02b437fb5c90990429fd180e52f30/grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
@@ -1234,6 +1320,18 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "8.7.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "zipp" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
@@ -1275,7 +1373,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "kombu"
|
||||
version = "5.5.4"
|
||||
version = "5.6.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "amqp" },
|
||||
@@ -1283,9 +1381,9 @@ dependencies = [
|
||||
{ name = "tzdata" },
|
||||
{ name = "vine" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ac/05/749ada8e51718445d915af13f1d18bc4333848e8faa0cb234028a3328ec8/kombu-5.6.1.tar.gz", hash = "sha256:90f1febb57ad4f53ca327a87598191b2520e0c793c75ea3b88d98e3b111282e4" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1613,6 +1711,173 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.39.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "importlib-metadata" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c0/0b/e5428c009d4d9af0515b0a8371a8aaae695371af291f45e702f7969dce6b/opentelemetry_api-1.39.0.tar.gz", hash = "sha256:6130644268c5ac6bdffaf660ce878f10906b3e789f7e2daa5e169b047a2933b9" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/05/85/d831a9bc0a9e0e1a304ff3d12c1489a5fbc9bf6690a15dcbdae372bbca45/opentelemetry_api-1.39.0-py3-none-any.whl", hash = "sha256:3c3b3ca5c5687b1b5b37e5c5027ff68eacea8675241b29f13110a8ffbb8f0459" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-exporter-otlp-proto-common"
|
||||
version = "1.39.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-proto" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/11/cb/3a29ce606b10c76d413d6edd42d25a654af03e73e50696611e757d2602f3/opentelemetry_exporter_otlp_proto_common-1.39.0.tar.gz", hash = "sha256:a135fceed1a6d767f75be65bd2845da344dd8b9258eeed6bc48509d02b184409" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ef/c6/215edba62d13a3948c718b289539f70e40965bc37fc82ecd55bb0b749c1a/opentelemetry_exporter_otlp_proto_common-1.39.0-py3-none-any.whl", hash = "sha256:3d77be7c4bdf90f1a76666c934368b8abed730b5c6f0547a2ec57feb115849ac" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-exporter-otlp-proto-grpc"
|
||||
version = "1.39.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "grpcio" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-common" },
|
||||
{ name = "opentelemetry-proto" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/7e/62/4db083ee9620da3065eeb559e9fc128f41a1d15e7c48d7c83aafbccd354c/opentelemetry_exporter_otlp_proto_grpc-1.39.0.tar.gz", hash = "sha256:7e7bb3f436006836c0e0a42ac619097746ad5553ad7128a5bd4d3e727f37fc06" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/56/e8/d420b94ffddfd8cff85bb4aa5d98da26ce7935dc3cf3eca6b83cd39ab436/opentelemetry_exporter_otlp_proto_grpc-1.39.0-py3-none-any.whl", hash = "sha256:758641278050de9bb895738f35ff8840e4a47685b7e6ef4a201fe83196ba7a05" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "packaging" },
|
||||
{ name = "wrapt" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/55/3c/bd53dbb42eff93d18e3047c7be11224aa9966ce98ac4cc5bfb860a32c95a/opentelemetry_instrumentation-0.60b0.tar.gz", hash = "sha256:4e9fec930f283a2677a2217754b40aaf9ef76edae40499c165bc7f1d15366a74" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5c/7b/5b5b9f8cfe727a28553acf9cd287b1d7f706f5c0a00d6e482df55b169483/opentelemetry_instrumentation-0.60b0-py3-none-any.whl", hash = "sha256:aaafa1483543a402819f1bdfb06af721c87d60dd109501f9997332862a35c76a" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation-asgi"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-instrumentation" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "opentelemetry-util-http" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b0/0a/715ea7044708d3c215385fb2a1c6ffe429aacb3cd23a348060aaeda52834/opentelemetry_instrumentation_asgi-0.60b0.tar.gz", hash = "sha256:928731218050089dca69f0fe980b8bfe109f384be8b89802d7337372ddb67b91" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9b/8c/c6c59127fd996107243ca45669355665a7daff578ddafb86d6d2d3b01428/opentelemetry_instrumentation_asgi-0.60b0-py3-none-any.whl", hash = "sha256:9d76a541269452c718a0384478f3291feb650c5a3f29e578fdc6613ea3729cf3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation-fastapi"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-instrumentation" },
|
||||
{ name = "opentelemetry-instrumentation-asgi" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "opentelemetry-util-http" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/fe/51/a021a7c929b5103fcb6bfdfa5a99abcaeb3b505faf9e3ee3ec14612c1ef9/opentelemetry_instrumentation_fastapi-0.60b0.tar.gz", hash = "sha256:5d34d67eb634a08bfe9e530680d6177521cd9da79285144e6d5a8f42683ed1b3" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b1/5a/e238c108eb65a726d75184439377a87d532050036b54e718e4c789b26d1a/opentelemetry_instrumentation_fastapi-0.60b0-py3-none-any.whl", hash = "sha256:415c6602db01ee339276ea4cabe3e80177c9e955631c087f2ef60a75e31bfaee" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation-logging"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-instrumentation" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/5b/8b/a1aed0b695a58a1f0bdcf90fae5e468cc0fd7de2b74b9816b17e45c38e43/opentelemetry_instrumentation_logging-0.60b0.tar.gz", hash = "sha256:6d87840666669cbbcd53d2230c7a33476862d0bf7f1adc67a95519c6ccfc8281" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/00/89/6d4f5d8d03376637bff5b8d9e91356104a6f1ce9a34a2099bb2f4bf5e2b5/opentelemetry_instrumentation_logging-0.60b0-py3-none-any.whl", hash = "sha256:af75b3020911b9b6a1b4b19819a165eb131ce9bfdd313062d578fc2dc9a5cd0f" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation-sqlalchemy"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-instrumentation" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "packaging" },
|
||||
{ name = "wrapt" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/8f/52/6a6e8ef8d0430487f1c65fdb81f2001d0b3b68de72f7437b54ff2330de44/opentelemetry_instrumentation_sqlalchemy-0.60b0.tar.gz", hash = "sha256:df9fdc6f48709138343971cef95a055f52d0262331115231bd5d678c1a452891" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e0/08/943c1c650e7826b0462f0a2528d37b9cf717957955df6259b9c25dfcaa51/opentelemetry_instrumentation_sqlalchemy-0.60b0-py3-none-any.whl", hash = "sha256:ff66ac39364bfb38fe127f823b36693c6f2a44f929926863640ac1575583a73f" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "1.39.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/48/b5/64d2f8c3393cd13ea2092106118f7b98461ba09333d40179a31444c6f176/opentelemetry_proto-1.39.0.tar.gz", hash = "sha256:c1fa48678ad1a1624258698e59be73f990b7fc1f39e73e16a9d08eef65dd838c" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e3/4d/d500e1862beed68318705732d1976c390f4a72ca8009c4983ff627acff20/opentelemetry_proto-1.39.0-py3-none-any.whl", hash = "sha256:1e086552ac79acb501485ff0ce75533f70f3382d43d0a30728eeee594f7bf818" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-sdk"
|
||||
version = "1.39.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/51/e3/7cd989003e7cde72e0becfe830abff0df55c69d237ee7961a541e0167833/opentelemetry_sdk-1.39.0.tar.gz", hash = "sha256:c22204f12a0529e07aa4d985f1bca9d6b0e7b29fe7f03e923548ae52e0e15dde" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a4/b4/2adc8bc83eb1055ecb592708efb6f0c520cc2eb68970b02b0f6ecda149cf/opentelemetry_sdk-1.39.0-py3-none-any.whl", hash = "sha256:90cfb07600dfc0d2de26120cebc0c8f27e69bf77cd80ef96645232372709a514" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-semantic-conventions"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/71/0e/176a7844fe4e3cb5de604212094dffaed4e18b32f1c56b5258bcbcba85c2/opentelemetry_semantic_conventions-0.60b0.tar.gz", hash = "sha256:227d7aa73cbb8a2e418029d6b6465553aa01cf7e78ec9d0bc3255c7b3ac5bf8f" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d0/56/af0306666f91bae47db14d620775604688361f0f76a872e0005277311131/opentelemetry_semantic_conventions-0.60b0-py3-none-any.whl", hash = "sha256:069530852691136018087b52688857d97bba61cd641d0f8628d2d92788c4f78a" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-util-http"
|
||||
version = "0.60b0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/38/0d/786a713445cf338131fef3a84fab1378e4b2ef3c3ea348eeb0c915eb804a/opentelemetry_util_http-0.60b0.tar.gz", hash = "sha256:e42b7bb49bba43b6f34390327d97e5016eb1c47949ceaf37c4795472a4e3a82d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/53/5d/a448862f6d10c95685ed0e703596b6bd1784074e7ad90bffdc550abb7b68/opentelemetry_util_http-0.60b0-py3-none-any.whl", hash = "sha256:4f366f1a48adb74ffa6f80aee26f96882e767e01b03cd1cfb948b6e1020341fe" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
@@ -1740,28 +2005,28 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "prek"
|
||||
version = "0.2.18"
|
||||
version = "0.2.19"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/04/78/4bfb14e48197a90962d1d6c62194621057b6227212ef1de18965f80f2af6/prek-0.2.18.tar.gz", hash = "sha256:88d6ff5343d3613d88799ddc47c9ab22d98b4b8e2c389b535310562e11eac452" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/0f/f5/d18c5981540da731b0dbb3499718256764d4e3c939011e78b7bb3637b626/prek-0.2.19.tar.gz", hash = "sha256:2995be837a97ea9b8d2f27894b1fc763594701bafbc8d26c46f80ea9e2cb28a9" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a9/38/fb45e4575394199c035ed03209f93e0a5b7ce7babf15ba6de1cc88aac44e/prek-0.2.18-py3-none-linux_armv6l.whl", hash = "sha256:9edd3f1e1dd1426b987b90aaf2fdebfd70061cbf2111973d22626700593fa26b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d1/17/da6f33ddd91540958af01ffb5c4d2c086766ba68026fa535720eaf3c36f2/prek-0.2.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cc723ad71b8f2345e9ce2622a7a863e63dcdbf157600de6f0b40b5e5cece04e0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0b/78/8a7c6d8c30abd27c6043f65a692021de4e5ac3d59a21dbf295e8b079a9f5/prek-0.2.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b4833e771aab7732fcbabd41f4a613c437e1db622f3de6a389efb83cd1859cb7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/97/8e/5e640218bdbc221984527859a489620b768b8481280feaf760c3a1511017/prek-0.2.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:1c532d08ebe3d5585927b40d5625780848357f06a8995d80a4c16e12c79fc1ed" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b4/eb/801f19208e77ff56dabe58c44ff4e2045c870e3af10ad66b1e03624ec674/prek-0.2.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:845f47e3d8fcc8f6b68eb90784856371b7ec681cd555cdee602d4aa0a1d98858" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bd/59/f3099b7fc927192f82793ec51cf40b8bb237f07735bb7143efc3cfb431ca/prek-0.2.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af94f0e576295db9df6ba8755f49e19ed8d58f3a3b9255f5e6a5a2e1949ddb5e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a8/82/66b709652aae6ec4e949cc9ea926b02f892346be882b291d7d37ae3b034f/prek-0.2.18-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e10a8d5180b72de83ee66164dc58999651eded88274f3fb0fc51a8df90debd41" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/65/80/0782c0e3441a302b023e9072769ed3bf930e20cca44b5d892210735bf46b/prek-0.2.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49bd9f1ef0e2f091b401bc3207e55ecfc0a051529ca1113badf5b55fa15b8cee" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/06/ca/ecc58fd6ae7a7d51127cb3f2310c7c299b76702f06a5f8ae8d091e49464e/prek-0.2.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa9d62fcfcb0076813f46787a8418f92d5ed0047c16b002f4219191f3c7828b8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/30/7a/a5a1514bf5c16eeb439e0dd933ae4516604e828bacced4214e72f4bdbbf4/prek-0.2.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3255bc6ebb850ca66892b95ecf6e33cd0009baa9fa0de5aed591efb79c035a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bb/b1/437bf874904cf550d6e0cf3c2cf6a7822e3479dc57e7edeb5c44caf16b9b/prek-0.2.18-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:2cb65df949aa974af151ffaf2543a02d00d120d3e47d0073b1173ff9f5c76242" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/43/d9/49e18569ec9cbe1c642ec0cfb4666dfe43039c4b622d32ed19ab28699773/prek-0.2.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e073cc9e09d5131b7ad04f0a4684be09bcc9cb382792c287df13b222aeeb4abf" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0e/46/6cbd9a18218bbf90680ee105f019e9d5912b66c1ff9b59bfc20a0dc2ef4c/prek-0.2.18-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:f01f92557e8bcc8f76f0354758c8903c19e4f95a49f16085f914e94c06414403" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/07/e0/2cc64269e7dbac0748a8995e5ee9d443d06c82735dd0a11ba266d33fc302/prek-0.2.18-py3-none-musllinux_1_1_i686.whl", hash = "sha256:4035b637b4d738da05cf08bb110376fc5f9f8d88c30ac251440c52d3242ea906" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/37/12/465d2b96529c45d9ce6ae281a99c5c482b8ef60f735cd0302002849ab9c7/prek-0.2.18-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:818571df61182491e3f08b5928f37d0325cad1f343aad9c276a482a3b9c4563a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/31/75/520db7fc842df383fee043f72d67fe38042495ab1ec6f644d751945ddbad/prek-0.2.18-py3-none-win32.whl", hash = "sha256:1988c5957087275008a5fa9b8d0f4e2461d64d8f54a6c59bd0a14dfee310fefc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ca/0c/16e91a5547b78f006ab0b9b4ef1508ac43344d21db9e92a41bbd08d48208/prek-0.2.18-py3-none-win_amd64.whl", hash = "sha256:64eddbb515723785c129c2ebe6a778358b8db27650879bf82fbd683eac08f504" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/70/4f/76662876895476fd0bfc8a64d265226c24615c0d973d602d416e99cb20c4/prek-0.2.18-py3-none-win_arm64.whl", hash = "sha256:cb541a8a87b50ea9562e909f51f86b2fb77a6d21df6985237ecfe50743bd9f6f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ef/ab/b23d83992e76738c3aeb254739e554f32d11be58121488b124918e461200/prek-0.2.19-py3-none-linux_armv6l.whl", hash = "sha256:8879bd9df431bfa689cd38633e324b76d6579cf1978f186849c75c9d7c0dc6b4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9f/4b/d2289899e3d05e825018e9e6b6d2b5e95e1b877f9b673cda352555b2b778/prek-0.2.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b57000eb18f5621c55ef255da8953aaaa316b013714e05709c76accc24487bf" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e4/9a/198336704e8faf86293626d39657136cb42bed7cd046a6fd183c7497e5ec/prek-0.2.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:598cad01b68fae2732ff552c93e8286071e58796c7f3c461c298ad8618d984ab" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/12/e6/d0f87337413a79eff029d1eb6a2d2b66d07f8bd59edbd30220d195654ad5/prek-0.2.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:dd8e5f36022fcbefbc9750e3f5067a01102b1c0808325489f5614bc012be1136" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3d/b5/e4f4f0acc8ad1fd967494b962bfabc821807b52a7c68d2bae30c65b588c4/prek-0.2.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:721271a31f51c8c7994d24e5e3816a249c482b56fe287ec31e96f47479432201" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c8/24/faa519d2b8b9ceba6dd43c3e993ebacfef33ddc4be2b781beef24b9ad79e/prek-0.2.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:116bd9152c68be9a77b40af15763ddba73d4744dd606fe6686c3d2e9eb7b52b1" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d4/19/2b92c8fbfe9872a233fc326cb969348dd2fc8db0d01323cf822b5c2990ff/prek-0.2.19-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:96aef4dfcf8c3c28d93ee93f78289e07a3ce60e1f2f91d3eb9f023dc75b62216" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ed/8e/934b5dff72aaf6df41ae59bf5324f2c3db8a123cb976387e85c16cc639b2/prek-0.2.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a1156a1f525d3f6b69bc1c07a0dea930cb9027f95bcb4d390fe5fe7fe88687e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/89/ba/990decf00161eed117fb7c71a9f2cba71879e77bf7a6e17d94659f72cd4e/prek-0.2.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:485b152f4a637aa761ab1634a6b9a38cd07cc6d23e3651f2c8eb4d7a507e132c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bf/57/b7b87ee878c45046cb220ecdd257ea0398984b104f983b7eb25a92fe86e2/prek-0.2.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7613493f39918247f2d97ebce8b3692fc738aa2ab8a9f76cfa800947756cfa42" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4b/50/33b8cd6fe3517be2a4303a72b20341a54b728e9fc0027e0cc65843210ee9/prek-0.2.19-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:a1546907f28b56c940f7d3cd9452052bcd97be31de92bf0a4bd07c4746929a00" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/db/8d/06b49a443a43a0fbf576e0d068ce4cce2a76367b19f42552f6182c3d06a0/prek-0.2.19-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7db50ebce756e6d67c4ed1935c6e1bcacf29662c9bfee4a0f401d3fb0385d922" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/18/d9/1e317d0697a98b2c57e2b4c1f8d3efeb6db86b81ce2df5211fdbb17f8b7c/prek-0.2.19-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:37f03e0796d753a9bd82ed09a7e901474e6a26b9a6ceffa5003a3fa703497a8e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0c/f0/0e32a9a7fc7876d08f5b118ecab6e81c5b433ddaa4daaa6c930c46c7d916/prek-0.2.19-py3-none-musllinux_1_1_i686.whl", hash = "sha256:3b0da9f14191e0adfb55e626ce3562d802800bc9aea96bbbdc1374290ef848c9" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e0/61/70204d48dd86a472d56dc75d2d9caf8236507a563428cb22fe5c90933978/prek-0.2.19-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:9cef90e8f8d5de9a165f2a81d9ce728bd3b99b65620a8427928e832a43fe4c4b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/14/5c/13361ca9aa8ce3bce50430b5f2afb2ea9e44639841a638f998c762c27708/prek-0.2.19-py3-none-win32.whl", hash = "sha256:a6d19d8b10bc4aba18cf1a7902de7bede05671f484adabeac2d96a02ac470ef1" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/d5/bf010982c063ed630bf75c058e2313ba546daa11343fa2ba3116a19f5b69/prek-0.2.19-py3-none-win_amd64.whl", hash = "sha256:34a638a2cb9808b3db8b7020c9cf03d445e9c957a36e09a443a19e1e71a2fb6c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/06/e3/42a5fb0f0dc2d0ae117e533e9da8a0fb41850526b27dd5dd5c0f37e878fa/prek-0.2.19-py3-none-win_arm64.whl", hash = "sha256:1a865880cc2362eb0698d938b811a796dfd5bcb49ef7abd02568f0f5e3b7ec88" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1899,6 +2164,21 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "6.33.2"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.1.3"
|
||||
@@ -2024,7 +2304,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.12.4"
|
||||
version = "2.12.5"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
@@ -2032,9 +2312,9 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2392,16 +2672,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "rich-toolkit"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/83/8e/ab512afd71d4e67bb611a57db92a0e967304c97ec61963e99103f5a88069/rich_toolkit-0.16.0.tar.gz", hash = "sha256:2f554b00b194776639f4d80f2706980756b659ceed9f345ebbd9de77d1bdd0f4" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ad/d0/8f8de36e1abf8339b497ce700dd7251ca465ffca4a1976969b0eaeb596fb/rich_toolkit-0.17.0.tar.gz", hash = "sha256:17ca7a32e613001aa0945ddea27a246f6de01dfc4c12403254c057a8ee542977" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3a/04/f4bfb5d8a258d395d7fb6fbaa0e3fe7bafae17a2a3e2387e6dea9d6474df/rich_toolkit-0.16.0-py3-none-any.whl", hash = "sha256:3f4307f678c5c1e22c36d89ac05f1cd145ed7174f19c1ce5a4d3664ba77c0f9e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b2/42/ef2ed40699567661d03b0b511ac46cf6cee736de8f3666819c12d6d20696/rich_toolkit-0.17.0-py3-none-any.whl", hash = "sha256:06fb47a5c5259d6b480287cd38aff5f551b6e1a307f90ed592453dd360e4e71e" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2518,15 +2798,6 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.44"
|
||||
@@ -2592,11 +2863,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2758,6 +3029,18 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "5.3.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ua-parser"
|
||||
version = "1.0.1"
|
||||
@@ -3069,6 +3352,75 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.17.3"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.3.2"
|
||||
@@ -3207,6 +3559,15 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.23.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zope-event"
|
||||
version = "6.1"
|
||||
|
||||
Reference in New Issue
Block a user