From ee0a5cf0f668cdac1e41fa04c703c8f77d0e6aa9 Mon Sep 17 00:00:00 2001 From: chakcy <947105045@qq.com> Date: Mon, 13 Apr 2026 15:55:12 +0800 Subject: [PATCH] =?UTF-8?q?modify:=20diskcache=20=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache.db | Bin 0 -> 32768 bytes config.toml | 13 +- database.db | Bin 0 -> 16384 bytes public/config.toml | 9 +- public/main.py | 5 +- pyproject.toml | 5 +- pyrightconfig.json | 2 +- requirements.txt | 27 +- scheduler_jobs.db | Bin 0 -> 16384 bytes src/chongming/__init__.py | 1 - src/chongming/app/__init__.py | 53 +++- src/chongming/app/api/deps.py | 4 +- src/chongming/app/api/routers/auth.py | 30 +- src/chongming/app/api/routers/todo.py | 18 +- src/chongming/app/core/cache.py | 248 ++++++++++++++++ src/chongming/app/core/config.py | 23 +- src/chongming/app/core/jwt_cache.py | 161 +++-------- src/chongming/app/core/logger.py | 46 +-- src/chongming/app/core/scheduler.py | 281 +++++++++++++++++++ src/chongming/app/model/user.py | 1 + src/chongming/app/service/auth.py | 4 +- src/chongming/app/service/user.py | 3 +- src/chongming/app/task/__init__.py | 20 ++ src/chongming/app/task/execute_background.py | 8 + src/chongming/scripts/build.py | 42 +-- uv.lock | 108 +++++-- 26 files changed, 824 insertions(+), 288 deletions(-) create mode 100644 cache.db create mode 100644 database.db create mode 100644 scheduler_jobs.db create mode 100644 src/chongming/app/core/cache.py create mode 100644 src/chongming/app/core/scheduler.py create mode 100644 src/chongming/app/task/__init__.py create mode 100644 src/chongming/app/task/execute_background.py diff --git a/cache.db b/cache.db new file mode 100644 index 0000000000000000000000000000000000000000..1d40b456fa59373314ba93c0c4d0e6c84ab3d2a2 GIT binary patch literal 32768 zcmeI(!Ef7C90zbaFHM}J@$1xeZZ%bVorKoXWTk4SO`4FFwr&@ZC0m-R-N`bJ*P7aK z7CY^VgtQHD;UD0@AHazV$KK$=35gR|4sc;NkcK!RY{zcg(UwR90oB)17U%i-=jYFR ze${)=TYpe7T%A3#JC^FQ1#W~(B)B__aU4gykKlcxE$i(h!ykS+A00U3n{d}ZzL=-K zak{c#)`GzDspL09L z_O{+x*In0WZ99r%yss-w-PB!uS=Dy*<+@yI$gEK>udT^-c2Yi8T5WiTR^*E8<*Zd% za3Wi+)tOvczRT*hO|~Sjm8)#y{)!(g3c}Xq2HR83uFl>Lzl*F^S-I{Pf^FWF>oVKb z54?j{{gkV>nOt3Yww&!1T|Tks-F8zQr0BhTVbQ~Kym<#P7$V>QCB6JZ$=eu(3R`xUtlkdNL(lllX2du=fNj9d$n_dnydN zQ7u2%kXgLQVYZ{V(_TV>c_+?1Pm{DHN&JVFtG;W7-ICuo4Mm+(X{jQIz0Nf2*!xD) z3*C_Y4(oose6Li0$lj437J|-XsZv`C;zn5ttPjJy-mX$$u{#P=R87+zCqO@*610gZ zl-CEYmNqI4_Ev$p>Ne|LE9lLl^2Bmjc2hs;%twZ)w^U1K4SB0k2s=hU)J%gi$>q%m`0S!rh~ioL$PYj`)pJytj8aQo|#Leq~%GT3+l{yYhJiYYB573KA0xRkDoG4Fno#3_vR0d z%@XAN?MZj1|K)jRPXXFih1;s%NdJ9=?ArWPv|M z)7XArtEhxx<-=IMTRKn!8U!E!0SG_<0uX=z1Rwwb2>gS9TU=PLRSU7`hx|pn_`{SZ zKJNu0(odx$kJ)WT9k+SeVbpYw*$w5gsO6H*r22SLW3*u|u2-vtw^bS3M_h+=FHRR~ z+lLODPO!@3DRganN2j(&ZmUgh$8omED(gA@pvSXdeSfE+-#-ka$j=3KcP0^$`axHU zCr*d0mcwi}x1Nfo!@=Y%GZVTA49#BgY;454uUj&j*~E( zk=yaYYo6QQEuFDjs*A8;HC{IhW13NIOe5nk*4@m}=r>fi%2LSatNPr+Ay2!B7)TNK zvyJspyK!$2aaF?1@_s4g=OOjoTwVInAOHafKmY;|fB*y_009U<00I!0V*%X%=lIJ- zX%K(_1Rwwb2tWV=5P$##AOL}>fTc(L|Bv4T1Rwwb2tWV=5P$##AOHafKwz!~J^^sG Bp}GJ7 literal 0 HcmV?d00001 diff --git a/public/config.toml b/public/config.toml index c1aef49..b401d09 100644 --- a/public/config.toml +++ b/public/config.toml @@ -20,12 +20,17 @@ pool_pre_ping = true connect_args.timeout = 15 connect_args.check_same_thread = false +[scheduler] +job_store_path = "scheduler_jobs.db" + +[cache] +cache_store_path = "./" + [security] secret_key = "your-secret-key-change-in-production" algorithm = "HS256" access_token_expire_minutes = 30 refresh_token_expire_days = 7 -jwt_cache_db_path = "jwt_cache" max_sessions_per_user = 5 [development] @@ -62,6 +67,8 @@ workers = 4 level = "INFO" file = "logs/prod.log" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +max_size = 10485760 # 10MB +backup_count = 5 # 最多保存 10 个日志文件 [production.cors] allow_origins = ["https://yourdomain.com", "http://localhost:8000", "http://127.0.0.1:8000"] diff --git a/public/main.py b/public/main.py index 3b03f92..2c31449 100644 --- a/public/main.py +++ b/public/main.py @@ -1,7 +1,7 @@ # main.py - 修复 reload 问题 #!/usr/bin/env python3 """ -FastAPI + Tortoise-ORM + chongming 示例 +FastAPI chongming 示例 主入口文件,适配 chongming 打包系统 """ import sys @@ -19,9 +19,8 @@ def app_run(app_config: dict, default_config: dict): default_config: 默认配置 """ import uvicorn - from app.core.logger import setup_logging, get_logger # type: ignore + from app.core.logger import get_logger # type: ignore - setup_logging(app_config["logging"]) logger = get_logger("app") # 获取服务器配置 diff --git a/pyproject.toml b/pyproject.toml index d56f41c..5a29c12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,14 @@ readme = "README.md" requires-python = ">=3.8" dependencies = [ "aiosqlite", + "apscheduler", "bcrypt==4.0.1", + "diskcache>=5.6.3", "fastapi[uvicorn]", "module-bank", "passlib[bcrypt]", "pyjwt", "sqlite-vfs", - "sqlitedict", "sqlmodel[aiosqlite]", "tomli", "typing-extensions", @@ -42,4 +43,4 @@ packages = ["src/chongming"] [project.scripts] build = "chongming.scripts.build:build" -serve = "chongming:serve" \ No newline at end of file +serve = "chongming:serve" diff --git a/pyrightconfig.json b/pyrightconfig.json index 1b0c4fb..b7c8911 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -4,6 +4,6 @@ "pythonVersion": "3.11", "extraPaths": [ "./", - "./.venv/lib/python3.12/site-packages" + "./.venv/lib/python3.11/site-packages" ] } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 528d3f4..ff0bd60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,13 @@ -aerich -fastapi[uvicorn] -module-bank==0.1.1 -passlib -pydantic-settings -pydantic[email] -sqlite-vfs -toml -tortoise-orm -uvicorn[standard] -sqlitedict -pyjwt +aiosqlite +apscheduler bcrypt==4.0.1 -pyinstaller -aerich -requests \ No newline at end of file +fastapi[uvicorn] +module-bank +passlib[bcrypt] +pyjwt +sqlite-vfs +sqlitedict +sqlmodel[aiosqlite] +tomli +typing-extensions +uvicorn \ No newline at end of file diff --git a/scheduler_jobs.db b/scheduler_jobs.db new file mode 100644 index 0000000000000000000000000000000000000000..31c0f1feb1c042119472b9f68930ab0033218ab3 GIT binary patch literal 16384 zcmeI&+iue^7yw|W8S4Q`Cxo;K0n248v7!nI1QLR76fxSh&=mySL~i1y-cn~I&Q>8I zF~ngY-_1M#Pr*y@1Uv$_+;G7Kf4VVt;Bbq^Z#9Zz`;YCfx%tyB-feKntX>cfsI)GS z31S%Jx@8eUGH{H+@uQ@mvisWj(ea0KhLqo)%on~A^LRuGkTk-+h-#a$f zu2nlVYrc7-_Q3jUxYcS}`)^?t_smdQFB|E}nHl4GLDH^&V8;F7sqGbXsRuLWPG;Kn%xs|r#-(= zZ9lZ?wTD)byQN&|T55c9amFAc!RWCMIlN6{8H}zEdRF%Dz=TIF$?r)X4kQ1Ozy}Ko zpa2S>01BW03ZMWApa2S>z&|ALV!=2vo!)pNX|!w$I$+gLYsKc9+wY$pdH13MePpA( z(+6!+$*;Chp6a4Xud*=WfzWkBrMRnAc4wqkW4%~7T3vPef$#=gcxAe>Qcebx*^oIg zB$!?5EPG)P3-`*wCOa9bRa%DJ^H`|WnO!+&dAl_#b0Jx{N`0*g^V`+VcEqMGt!r3k z->9clCdvGt1cGUm>BiibTtskpk3ohkL%j*jNiP?@0IH9mI-gXbJ!{8eg*(eW)30<} zt9&)$^!#F<3a?K&?0wXlgH$_|whQ?-#3!d@!rj$PJ(o*=9?KUlo=sgkt@~4>xC|5S zR#a1Eb~e*L1(p!+GG9_yM4i-)I(eN;Kfc5zY*{zsuF7n&1)!65S&zoP1b7;Hk?xx> zVeInS=;A4~Cf}`AlfQ*NG2l^;hs^du>afv^5?w#5GEP8!7CB6BsQiEqZE%xRI4sh2 zQ|0%(dw?~SbMR~!g)am?SWo~3Pyhu`00mG01yBG5Pyhu`00j<@z%g_B=Zl}M{Qny% rd^01BW03ZMWApa2RGa@0&`lfR9aCdqyWfl+`B literal 0 HcmV?d00001 diff --git a/src/chongming/__init__.py b/src/chongming/__init__.py index 842d461..7b820bb 100644 --- a/src/chongming/__init__.py +++ b/src/chongming/__init__.py @@ -8,7 +8,6 @@ def serve(): env = config["default"]["env"] server_config = config[env]["server"] log_config = config[env]["logging"] - setup_logging(log_config) logget = get_logger("app") logget.info(f"启动服务: {server_config}") logget.info(f"启动日志: {log_config}") diff --git a/src/chongming/app/__init__.py b/src/chongming/app/__init__.py index f111a16..df3d0cb 100644 --- a/src/chongming/app/__init__.py +++ b/src/chongming/app/__init__.py @@ -1,15 +1,26 @@ from contextlib import asynccontextmanager -from fastapi import FastAPI +from fastapi import FastAPI, Request, Depends +from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from sqlmodel import SQLModel, text from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.exc import OperationalError +from pydantic import BaseModel from .core.config import get_config -from .core.jwt_cache import get_jwt_cache +from .core.cache import get_cache from .core.logger import get_logger +from .core.scheduler import get_task_service, get_task_service_instance, TaskService from .api import api_router +from .task.execute_background import execute_background_task +from .task import init_tasks_callback + + +class TaskRequest(BaseModel): + task_name: str + interval: int + config = get_config() logger = get_logger("app") @@ -32,6 +43,12 @@ async def lifespan(app: FastAPI): if database_config is None: raise ValueError("配置不存在") + task_service = get_task_service_instance() + + await task_service.start(init_tasks_callback) + + app.state.task_service = task_service + # 启动时:创建引擎和会话工厂,初始化数据库表 engine = create_async_engine(database["url"], **database_config) async_session_maker = async_sessionmaker( @@ -61,8 +78,9 @@ async def lifespan(app: FastAPI): yield # 应用运行期间 # 关闭时:释放引擎 + await task_service.shutdown(wait=True) await engine.dispose() - get_jwt_cache().close() + get_cache().close() cors = config[env].get("cors", {}) @@ -87,19 +105,36 @@ app.include_router(api_router, prefix="/api/v1") async def root(): """根路径,返回应用信息""" return { - "message": f"欢迎使用 {config["default"]["app"]["name"]}", + "message": f"欢迎使用 {config['default']['app']['name']}", "version": config["default"]["app"]["version"], "docs": "/docs" if config[env]["debug"] else None, "health": "/api/v1/health", } +@app.post("/schedule") +async def add_task( + request: TaskRequest, task_service: TaskService = Depends(get_task_service) +): + """添加定时任务接口""" + job = await task_service.add_interval_job( + execute_background_task, + seconds=request.interval, + args=[request.task_name], + job_id=request.task_name, + ) + return {"status": "success", "job_id": job.id} + + # 全局异常处理器 @app.exception_handler(Exception) -async def global_exception_handler(request, exc): +async def global_exception_handler(_: Request, exc: Exception): """全局异常处理器""" logger.error(f"全局异常: {exc}") - return { - "error": "内部服务器错误", - "detail": str(exc) if config[env]["debug"] else "请查看服务器日志", - } + return JSONResponse( + status_code=500, + content={ + "error": "内部服务器错误", + "detail": str(exc) if config[env]["debug"] else "请查看服务器日志", + }, + ) diff --git a/src/chongming/app/api/deps.py b/src/chongming/app/api/deps.py index 3fe9b06..775e039 100644 --- a/src/chongming/app/api/deps.py +++ b/src/chongming/app/api/deps.py @@ -5,11 +5,11 @@ from ..service.auth import get_auth_service from ..core.jwt_cache import TokenData -security = HTTPBearer() +access_scheme = HTTPBearer(scheme_name="AccessToken") async def get_current_user( - credentials: HTTPAuthorizationCredentials = Depends(security), + credentials: HTTPAuthorizationCredentials = Depends(access_scheme), ) -> TokenData: """ 获取当前用户依赖项 diff --git a/src/chongming/app/api/routers/auth.py b/src/chongming/app/api/routers/auth.py index d30942c..23892e1 100644 --- a/src/chongming/app/api/routers/auth.py +++ b/src/chongming/app/api/routers/auth.py @@ -3,7 +3,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.ext.asyncio import AsyncSession from ...core.database import get_session -from ...core.jwt_cache import get_jwt_cache, TokenData +from ...core.jwt_cache import TokenData from ...core.security import verify_password from ...service.user import UserService from ...service.auth import get_auth_service @@ -13,7 +13,8 @@ from ..deps import get_current_user router = APIRouter(tags=["authentication"]) -security = HTTPBearer() +access_scheme = HTTPBearer(scheme_name="AccessToken") +refresh_scheme = HTTPBearer(scheme_name="RefreshToken") @router.post("/register", response_model=UserRead) @@ -50,29 +51,14 @@ async def login( raise HTTPException(status_code=400, detail="邮箱或密码错误") if not user.is_active: raise HTTPException(status_code=400, detail="用户被禁用") - - cache = get_jwt_cache() - user_data_dict = { - "sub": str(user.id), - "email": user.email, - "username": user.username, - } - token = cache.create_token( - user_id=str(user.id), - user_data=user_data_dict, - token_type="access", - device_id=None, - user_agent=request.headers.get("user-agent") if request else None, - ip_address=request.client.host if request else None, # type: ignore - ) - - return {"access_token": token, "token_type": "bearer"} + auth_service = get_auth_service() + return await auth_service.create_tokens(user, request, device_id=None) @router.post("/refresh", response_model=dict, summary="刷新访问令牌") async def refresh_token( request: Request, - credentials: HTTPAuthorizationCredentials = Depends(security), + credentials: HTTPAuthorizationCredentials = Depends(refresh_scheme), session: AsyncSession = Depends(get_session), ): auth_service = get_auth_service() @@ -92,7 +78,7 @@ async def refresh_token( @router.post("/logout") async def logout( - credentials: HTTPAuthorizationCredentials = Depends(security), + credentials: HTTPAuthorizationCredentials = Depends(access_scheme), ): auth_service = get_auth_service() token = credentials.credentials @@ -100,7 +86,7 @@ async def logout( return {"message": "登出成功"} -@router.post("/logout-all", summary="等出所有设备") +@router.post("/logout-all", summary="登出所有设备") async def logout_all(current_user: TokenData = Depends(get_current_user)): auth_service = get_auth_service() await auth_service.logout_all(current_user.user_id) diff --git a/src/chongming/app/api/routers/todo.py b/src/chongming/app/api/routers/todo.py index 1dd1ca7..19a4423 100644 --- a/src/chongming/app/api/routers/todo.py +++ b/src/chongming/app/api/routers/todo.py @@ -10,11 +10,11 @@ from ...model.user import User from ..deps import get_current_user -router = APIRouter() +router = APIRouter(dependencies=[Depends(get_current_user)]) # -------------------- 路由 -------------------- -@router.post("/", response_model=TodoRead, dependencies=[Depends(get_current_user)]) +@router.post("/", response_model=TodoRead) async def create_todo( todo: TodoCreate, session: AsyncSession = Depends(get_session), @@ -22,18 +22,14 @@ async def create_todo( return await TodoService.create_todo(todo, session) -@router.get( - "/", response_model=List[TodoRead], dependencies=[Depends(get_current_user)] -) +@router.get("/", response_model=List[TodoRead]) async def read_todos( offset: int = 0, limit: int = 100, session: AsyncSession = Depends(get_session) ): return await TodoService.get_todos(offset, limit, session) -@router.get( - "/{todo_id}", response_model=TodoRead, dependencies=[Depends(get_current_user)] -) +@router.get("/{todo_id}", response_model=TodoRead) async def read_todo(todo_id: int, session: AsyncSession = Depends(get_session)): todo = await TodoService.get_todo_by_id(todo_id, session) if not todo: @@ -41,9 +37,7 @@ async def read_todo(todo_id: int, session: AsyncSession = Depends(get_session)): return todo -@router.patch( - "/{todo_id}", response_model=TodoRead, dependencies=[Depends(get_current_user)] -) +@router.patch("/{todo_id}", response_model=TodoRead) async def update_todo( todo_id: int, todo_update: TodoUpdate, session: AsyncSession = Depends(get_session) ): @@ -51,7 +45,7 @@ async def update_todo( return todo -@router.delete("/{todo_id}", dependencies=[Depends(get_current_user)]) +@router.delete("/{todo_id}") async def delete_todo(todo_id: int, session: AsyncSession = Depends(get_session)): is_deleted = await TodoService.delete_todo(todo_id, session) if not is_deleted: diff --git a/src/chongming/app/core/cache.py b/src/chongming/app/core/cache.py new file mode 100644 index 0000000..41dca00 --- /dev/null +++ b/src/chongming/app/core/cache.py @@ -0,0 +1,248 @@ +import time +from typing import Any, Optional + +import diskcache + +from ..core.config import get_config + + +class Cache: + def __init__(self): + config = get_config() + cache_dir = config["cache"]["cache_store_path"] + self._cache = diskcache.Cache(cache_dir) + + def set( + self, + key: str, + value: Any, + ex: Optional[int] = None, + px: Optional[int] = None, + raw: bool = False, + ) -> bool: + """ + 设置缓存 + + Args: + key: 键 + value: 值 + ex: 过期时间(秒) + px: 过期时间(毫秒) + + Returns: + bool: 是否设置成功 + """ + try: + expire_at = None + expire_seconds = None + + if ex is not None: + expire_seconds = ex + expire_at = time.time() + ex + elif px is not None: + expire_seconds = px / 1000.0 + expire_at = time.time() + expire_seconds + + stored_value = (value, expire_at) + + self._cache.set(key, stored_value, expire=expire_seconds) + return True + + except Exception as e: + print(f"[缓存] set 错误: {e}") + return False + + def get(self, key: str, default: Any = None) -> Any: + """ + 获取缓存 + + Args: + key: 键 + default: 默认值 + + Returns: + 缓存的值,如果key不存在或已过期返回default + """ + try: + stored = self._cache.get(key) + if stored is None: + return default + value, expire_at = stored # type: ignore + + if expire_at is not None and time.time() > expire_at: # type: ignore + self._cache.delete(key) + return default + + return value + + except Exception as e: + print(f"[缓存] get 错误: {e}") + return default + + def delete(self, *keys: str) -> int: + """ + 删除一个或多个key + + Args: + keys: 要删除的key + + Returns: + 成功删除的数量 + """ + deleted = 0 + for key in keys: + if self._cache.delete(key): + deleted += 1 + return deleted + + def exists(self, key: str) -> bool: + """ + 检查key是否存在且未过期 + + Args: + key: 键 + + Returns: + bool: 是否存在 + """ + return self.get(key, None) is not None + + def expire(self, key: str, _time: int) -> bool: + """ + 设置过期时间 + + Args: + key: 键 + time: 过期时间(秒) + + Returns: + bool: 是否设置成功 + """ + try: + stored = self._cache.get(key) + if stored is None: + return False + value, _ = stored # type: ignore + expire_at = time.time() + _time + new_stored = (value, expire_at) + self._cache.set(key, new_stored, expire=_time) + return True + except Exception as e: + print(f"[缓存] expire 错误: {e}") + return False + + def ttl(self, key: str) -> int: + """ + 获取key的剩余存活时间 + + Args: + key: 键 + + Returns: + int: 剩余秒数,-1表示永不过期,-2表示key不存在 + """ + try: + stored = self._cache.get(key) + if stored is None: + return -2 + + _, expire_at = stored # type: ignore + if expire_at is None: + return -1 + + remaining = expire_at - time.time() # type: ignore + return max(0, int(remaining)) + except Exception as e: + print(f"[缓存] ttl 错误: {e}") + return -2 + + def keys(self, pattern: Optional[str] = None) -> list: + """ + 获取所有key,支持简单的通配符匹配(*) + + Args: + pattern: 匹配模式,如 "user:*" + + Returns: + list: key列表 + """ + all_keys = list(self._cache.iterkeys()) + + if pattern and "*" in pattern: + prefix = pattern.replace("*", "") + all_keys = [k for k in all_keys if k.startswith(prefix)] # type: ignore + + return all_keys + + def clear(self) -> bool: + """清空所有缓存。""" + try: + self._cache.clear() + return True + except Exception as e: + print(f"[缓存] clear 错误: {e}") + return False + + def size(self) -> int: + """获取当前缓存数量(不含过期但尚未清理的项)。""" + # diskcache 的 __len__ 会返回当前有效条目数 + return len(list(self._cache.iterkeys())) + + def get_all(self) -> dict: + """获取所有未过期的缓存数据。""" + result = {} + for key in self._cache.iterkeys(): + value = self.get(str(key)) # get 内部会检查并清理过期 + if value is not None: + result[key] = value + return result + + def close(self): + """关闭缓存。""" + self._cache.close() + + +_cache: Optional[Cache] = None + + +def get_cache() -> Cache: + global _cache + if _cache is None: + _cache = Cache() + return _cache + + +# 装饰器:缓存函数结果 +def cached(ttl: Optional[int] = None): + """ + 函数结果缓存装饰器 + + Args: + ttl: 缓存时间(秒),None表示永不过期 + + Example: + @cached(ttl=60) + def get_user(user_id: int): + return {"id": user_id, "name": "test"} + """ + + def decorator(func): + def wrapper(*args, **kwargs): + # 生成缓存key + key = f"{func.__name__}:{str(args)}:{str(kwargs)}" + cache = get_cache() + # 尝试从缓存获取 + result = cache.get(key) + if result is not None: + return result + + # 执行函数 + result = func(*args, **kwargs) + + # 存入缓存 + cache.set(key, result, ex=ttl) + return result + + return wrapper + + return decorator diff --git a/src/chongming/app/core/config.py b/src/chongming/app/core/config.py index ce1b459..0dd32bf 100644 --- a/src/chongming/app/core/config.py +++ b/src/chongming/app/core/config.py @@ -1,6 +1,6 @@ import sys from pathlib import Path -from typing import TypedDict, Literal, Optional, cast +from typing import TypedDict, Literal, Optional, cast, List if sys.version_info >= (3, 11): @@ -53,7 +53,6 @@ class Security(TypedDict): secret_key: str algorithm: Literal["HS256"] access_token_expire_minutes: int - jwt_cache_db_path: str refresh_token_expire_days: int max_sessions_per_user: int @@ -74,20 +73,20 @@ class Logging(TypedDict): class Cors(TypedDict, total=False): - allow_origins: list[str] + allow_origins: List[str] allow_credentials: bool - allow_methods: list[str] - allow_headers: list[str] + allow_methods: List[str] + allow_headers: List[str] class ModuleSystem(TypedDict, total=False): type: str - path: list[str] + path: List[str] class FileSystem(TypedDict, total=False): type: str - path: list[str] + path: List[str] class Env(TypedDict, total=False): @@ -99,9 +98,19 @@ class Env(TypedDict, total=False): file_system: FileSystem +class Scheduler(TypedDict): + job_store_path: str + + +class Cache(TypedDict): + cache_store_path: str + + class Config(TypedDict): default: Default database: Database + scheduler: Scheduler + cache: Cache security: Security development: Env production: Env diff --git a/src/chongming/app/core/jwt_cache.py b/src/chongming/app/core/jwt_cache.py index 756ff4e..fd0aff3 100644 --- a/src/chongming/app/core/jwt_cache.py +++ b/src/chongming/app/core/jwt_cache.py @@ -1,17 +1,15 @@ -import jwt import json from datetime import datetime, timedelta -from typing import Optional, Dict, Any, List, TypedDict -from sqlitedict import SqliteDict +from typing import Optional, Dict, Any, List from pydantic import BaseModel from dataclasses import dataclass, asdict - -# from pydantic import BaseModel import hashlib import threading -from pathlib import Path + +import jwt from .config import get_config +from .cache import get_cache @dataclass @@ -43,43 +41,9 @@ class JWTCache: def __init__(self, db_path: Optional[str] = None): self.config = get_config() - if db_path is None: - db_path = self.config["security"]["jwt_cache_db_path"] - - db_file_path = Path(db_path) - db_file_path.mkdir(parents=True, exist_ok=True) - self.lock = threading.RLock() - # 主 Token 缓存 - self.token_db = SqliteDict( - rf"{db_path}/tokens.cache", - tablename="tokens", - autocommit=True, - encode=self._json_encode, - decode=self._json_decode, - journal_mode="WAL", - ) - - # 黑名单缓存 - self.blacklist_db = SqliteDict( - rf"{db_path}/blacklist.cache", - tablename="blacklist", - autocommit=True, - encode=json.dumps, - decode=json.loads, - journal_mode="WAL", - ) - - # 用户会话索引 - self.user_sessions_db = SqliteDict( - rf"{db_path}/user_sessions.cache", - tablename="user_sessions", - autocommit=True, - encode=json.dumps, - decode=json.loads, - journal_mode="WAL", - ) + self.cache = get_cache() self.secret_key = self.config["security"]["secret_key"] self.algorithm = self.config["security"]["algorithm"] @@ -168,7 +132,7 @@ class JWTCache: ) # 存储到缓存 - self.token_db[token_hash] = asdict(cache_data) + self.cache.set(f"token:{token_hash}", asdict(cache_data)) # 更新用户会话索引 self._update_user_session_index(user_id, token_hash, token_type) @@ -189,8 +153,8 @@ class JWTCache: ): """更新用户会话索引""" with self.lock: - key = f"{user_id}:{token_type}" - user_tokens = self.user_sessions_db.get(key, []) + key = f"user:{user_id}:{token_type}" + user_tokens = self.cache.get(key, []) # 添加新 token user_tokens.append(token_hash) @@ -200,36 +164,36 @@ class JWTCache: if len(user_tokens) > max_tokens: user_tokens = user_tokens[-max_tokens:] - self.user_sessions_db[key] = user_tokens + self.cache.set(key, user_tokens) def _enforce_session_limit(self, user_id: str): """强制用户会话限制""" max_sessions = self.config["security"]["max_sessions_per_user"] - access_key = f"{user_id}:access" - refresh_key = f"{user_id}:refresh" + access_key = f"user:{user_id}:access" + refresh_key = f"user:{user_id}:refresh" # 检查 access token 数量 - access_tokens = self.user_sessions_db.get(access_key, []) + access_tokens = self.cache.get(access_key, []) if len(access_tokens) > max_sessions: # 删除最早的 token tokens_to_remove = access_tokens[:-max_sessions] for token_hash in tokens_to_remove: - if token_hash in self.token_db: - del self.token_db[token_hash] + if self.cache.exists(f"token:{token_hash}"): + self.cache.delete(f"token:{token_hash}") # 更新索引 - self.user_sessions_db[access_key] = access_tokens[-max_sessions:] + self.cache.set(access_key, access_tokens[-max_sessions:]) # 检查 refresh token 数量 - refresh_tokens = self.user_sessions_db.get(refresh_key, []) + refresh_tokens = self.cache.get(refresh_key, []) if len(refresh_tokens) > max_sessions: tokens_to_remove = refresh_tokens[:-max_sessions] for token_hash in tokens_to_remove: - if token_hash in self.token_db: - del self.token_db[token_hash] + if self.cache.exists(f"token:{token_hash}"): + self.cache.delete(f"token:{token_hash}") - self.user_sessions_db[refresh_key] = refresh_tokens[-max_sessions:] + self.cache.set(refresh_key, refresh_tokens[-max_sessions:]) def validate_token( self, token: str, token_type: str = "access" @@ -259,14 +223,11 @@ class JWTCache: if not user_id or not jti: return None - # 检查黑名单 token_hash = self._hash_token(token) - if self._is_blacklisted(token_hash): - return None # 检查缓存 with self.lock: - cache_data = self.token_db.get(token_hash) + cache_data = self.cache.get(f"token:{token_hash}") if not cache_data: return None @@ -279,12 +240,12 @@ class JWTCache: if datetime.utcnow() > expires_at: # 标记为不活跃 cache_data["is_active"] = False - self.token_db[token_hash] = cache_data + self.cache.set(f"token:{token_hash}", cache_data) return None # 更新最后使用时间 cache_data["last_used"] = datetime.utcnow().isoformat() - self.token_db[token_hash] = cache_data + self.cache.set(f"token:{token_hash}", cache_data) return { "user_id": user_id, @@ -300,68 +261,38 @@ class JWTCache: except Exception: return None - def add_to_blacklist(self, token: str, reason: str = "logout"): - """添加 Token 到黑名单""" - token_hash = self._hash_token(token) - expire_time = datetime.utcnow() + timedelta(days=7) # 黑名单保留7天 - - self.blacklist_db[token_hash] = { - "reason": reason, - "blacklisted_at": datetime.utcnow().isoformat(), - "expires_at": expire_time.isoformat(), - } - - def _is_blacklisted(self, token_hash: str) -> bool: - """检查 Token 是否在黑名单中""" - with self.lock: - if token_hash not in self.blacklist_db: - return False - - blacklist_data = self.blacklist_db[token_hash] - expires_at = datetime.fromisoformat(blacklist_data["expires_at"]) - - # 如果黑名单条目已过期,删除它 - if datetime.utcnow() > expires_at: - del self.blacklist_db[token_hash] - return False - - return True - def invalidate_token(self, token: str): """使单个 Token 失效""" with self.lock: token_hash = self._hash_token(token) - # 添加到黑名单 - self.add_to_blacklist(token) - # 标记为不活跃 - if token_hash in self.token_db: - cache_data = self.token_db[token_hash] + if self.cache.exists(f"token:{token_hash}"): + cache_data = self.cache.get(f"token:{token_hash}") cache_data["is_active"] = False - self.token_db[token_hash] = cache_data + self.cache.set(f"token:{token_hash}", cache_data) def invalidate_user_tokens(self, user_id: str, token_type: Optional[str] = None): """使用户的所有 Token 失效""" with self.lock: if token_type: - keys = [f"{user_id}:{token_type}"] + keys = [f"user:{user_id}:{token_type}"] else: - keys = [f"{user_id}:access", f"{user_id}:refresh"] + keys = [f"user:{user_id}:access", f"{user_id}:refresh"] for key in keys: - token_hashes = self.user_sessions_db.get(key, []) + token_hashes = self.cache.get(key, []) for token_hash in token_hashes: # 添加到黑名单 # 注意:这里我们只有 token_hash,没有原始 token # 所以只能标记为不活跃 - if token_hash in self.token_db: - cache_data = self.token_db[token_hash] + if self.cache.exists(f"token:{token_hash}"): + cache_data = self.cache.get(f"token:{token_hash}") cache_data["is_active"] = False - self.token_db[token_hash] = cache_data + self.cache.set(f"token:{token_hash}", cache_data) # 清空用户会话索引 - self.user_sessions_db[key] = [] + self.cache.set(key, []) def get_user_sessions(self, user_id: str) -> List[Dict[str, Any]]: """获取用户的所有活跃会话""" @@ -369,12 +300,12 @@ class JWTCache: sessions = [] for token_type in ["access", "refresh"]: - key = f"{user_id}:{token_type}" - token_hashes = self.user_sessions_db.get(key, []) + key = f"user:{user_id}:{token_type}" + token_hashes = self.cache.get(key, []) for token_hash in token_hashes: - if token_hash in self.token_db: - cache_data = self.token_db[token_hash] + if self.cache.exists(f"token:{token_hash}"): + cache_data = self.cache.get(f"token:{token_hash}") # 检查是否活跃且未过期 if cache_data.get("is_active", True): @@ -403,28 +334,14 @@ class JWTCache: current_time = datetime.utcnow() # 清理过期 Token - for token_hash, data in list(self.token_db.items()): - expires_at = datetime.fromisoformat(data["expires_at"]) + for token_hash in self.cache.keys("token:*"): + expires_at = datetime.fromisoformat(self.cache.get(token_hash)) if current_time > expires_at: - del self.token_db[token_hash] - cleaned_count += 1 - - # 清理过期黑名单 - for token_hash, data in list(self.blacklist_db.items()): - expires_at = datetime.fromisoformat(data["expires_at"]) - if current_time > expires_at: - del self.blacklist_db[token_hash] + self.cache.delete(token_hash) cleaned_count += 1 return cleaned_count - def close(self): - with self.lock: - """关闭所有数据库连接""" - self.token_db.close() - self.blacklist_db.close() - self.user_sessions_db.close() - # 全局缓存实例 _jwt_cache = None diff --git a/src/chongming/app/core/logger.py b/src/chongming/app/core/logger.py index 22ad3cc..9d30d91 100644 --- a/src/chongming/app/core/logger.py +++ b/src/chongming/app/core/logger.py @@ -1,30 +1,43 @@ import logging +import datetime from pathlib import Path -from .config import Logging from logging.handlers import RotatingFileHandler +from ..core.config import get_config + + +config = get_config() +env = config["default"]["env"] +server_config = config[env]["server"] +log_config = config[env]["logging"] + class ExcludeWatchFilesFilter(logging.Filter): + def __init__(self): + super().__init__() + self._filter_starts_with_tulpe = ( + "watchfiles", + "aiosqlite", + "apscheduler", + "sqlalchemy", + "sqlitedict", + ) + + # 重写过滤方法 def filter(self, record): - # 如果日志记录来自 watchfiles 模块,则不记录(返回 False) - return not record.name.startswith("watchfiles") + return not record.name.startswith(self._filter_starts_with_tulpe) -def setup_logging(log_config: Logging): +def setup_logging(logger: logging.Logger, log_name: str = "app"): log_level = getattr(logging, log_config["level"], logging.INFO) log_format = log_config["format"] # 创建文件夹 log_file_path = Path(log_config["file"]) - log_file_dir = log_file_path.parent - if log_file_dir != Path("."): - log_file_dir.mkdir(parents=True, exist_ok=True) - - # 设置特定日志级别的过滤 - logging.getLogger("watchfiles").setLevel(logging.WARNING) + if log_file_path != Path("."): + log_file_path.mkdir(parents=True, exist_ok=True) # 配置根日志记录器 - root_logger = logging.getLogger() - root_logger.setLevel(log_level) + logger.setLevel(log_level) # 创建格式化器 formatter = logging.Formatter(log_format) @@ -34,7 +47,7 @@ def setup_logging(log_config: Logging): backup_count = log_config.get("backup_count", 5) # 默认保留 5 个备份 file_handler = RotatingFileHandler( - log_config["file"], + f"{log_config['file']}/{log_name}-{datetime.date.today()}.log", encoding="utf-8", maxBytes=max_bytes, backupCount=backup_count, @@ -48,8 +61,8 @@ def setup_logging(log_config: Logging): console_handler.addFilter(ExcludeWatchFilesFilter()) # 添加处理器到根日志记录器 - root_logger.addHandler(file_handler) - root_logger.addHandler(console_handler) + logger.addHandler(file_handler) + logger.addHandler(console_handler) print( f"日志系统已配置 - 级别: {logging.getLevelName(log_level)}, 文件: {log_config['file']} " @@ -59,6 +72,5 @@ def setup_logging(log_config: Logging): def get_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) - # 为每个logger添加过滤器 - logger.addFilter(ExcludeWatchFilesFilter()) + setup_logging(logger, name) return logger diff --git a/src/chongming/app/core/scheduler.py b/src/chongming/app/core/scheduler.py new file mode 100644 index 0000000..ea1788c --- /dev/null +++ b/src/chongming/app/core/scheduler.py @@ -0,0 +1,281 @@ +""" +APScheduler 集成模块 +提供持久化任务调度服务 +""" + +import os +import sys +import tempfile +from contextlib import suppress +from typing import Dict, List, Optional, Callable, AsyncGenerator, TypedDict, Any +from datetime import datetime + +from fastapi import Request +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore +from apscheduler.executors.asyncio import AsyncIOExecutor +from apscheduler.job import Job +from apscheduler.triggers.interval import IntervalTrigger +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.date import DateTrigger + +from ..core.config import get_config +from ..core.logger import get_logger + + +config = get_config() +logger = get_logger("scheduler") + + +def acquire_scheduler_lock(): + lock_path = os.path.join(tempfile.gettempdir(), "chongming_scheduler.lock") + lock_file = open(lock_path, "w") + try: + if sys.platform == "win32": + import msvcrt + + msvcrt.locking(lock_file.fileno(), msvcrt.LK_NBLCK, 1) + else: + import fcntl + + fcntl.flock(lock_file.fileno(), fcntl.LOCK_NB | fcntl.LOCK_EX) + return lock_file, True + except (IOError, OSError): + lock_file.close() + return None, False + + +class TaskService: + """任务调度服务""" + + def __init__(self): + # 使用独立的 SQLite 数据库存储任务(与业务数据库分开) + self.job_store_db_path = config.get("scheduler", {}).get( + "job_store_path", "scheduler_jobs.db" + ) + job_store_url = f"sqlite:///{self.job_store_db_path}" + + # 配置 job stores 和 executors + jobstores = {"default": SQLAlchemyJobStore(url=job_store_url)} + executors = {"default": AsyncIOExecutor()} + job_defaults = { + "coalesce": True, # 合并错过的任务 + "max_instances": 3, # 最大并发实例数 + "misfire_grace_time": 30, # 错过任务的宽限时间(秒) + } + + self.scheduler = AsyncIOScheduler( + jobstores=jobstores, + executors=executors, + job_defaults=job_defaults, + timezone="Asia/Shanghai", + ) + self._started = False + self.lock_file, self.is_scheduler_worker = acquire_scheduler_lock() + + async def start(self, init_tasks_callback: Optional[Callable] = None): + """启动调度器""" + if self.is_scheduler_worker: + if not self._started: + self.scheduler.start() + self._started = True + logger.info("任务调度器已启动") + if init_tasks_callback: + await init_tasks_callback(self) + + async def shutdown(self, wait: bool = True): + """关闭调度器""" + if self.lock_file: + with suppress(Exception): + for job in await self.get_jobs(): + await self.pause_job(job.get("id", "")) + + if self._started: + self.scheduler.shutdown(wait=wait) + self._started = False + logger.info("任务调度器已关闭") + if sys.platform == "win32": + import msvcrt + + msvcrt.locking(self.lock_file.fileno(), msvcrt.LK_UNLCK, 1) + else: + import fcntl + + fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) + self.lock_file.close() + + async def add_interval_job( + self, + func: Callable, + seconds: int = 0, + minutes: int = 0, + hours: int = 0, + days: int = 0, + job_id: Optional[str] = None, + args: Optional[List] = None, + kwargs: Optional[Dict] = None, + replace_existing: bool = True, + ) -> Job: + """添加间隔执行任务""" + trigger = IntervalTrigger( + seconds=seconds, minutes=minutes, hours=hours, days=days + ) + job = self.scheduler.add_job( + func, + trigger=trigger, + id=job_id, + args=args or [], + kwargs=kwargs or {}, + replace_existing=replace_existing, + ) + logger.info(f"添加间隔任务: {job.id}") + return job + + async def add_cron_job( + self, + func: Callable, + job_id: Optional[str] = None, + year: Optional[int] = None, + month: Optional[int] = None, + day: Optional[int] = None, + week: Optional[int] = None, + day_of_week: Optional[int] = None, + hour: Optional[int] = None, + minute: Optional[int] = None, + second: Optional[int] = None, + args: Optional[List] = None, + kwargs: Optional[Dict] = None, + replace_existing: bool = True, + ) -> Job: + """添加 cron 表达式任务""" + trigger = CronTrigger( + year=year, + month=month, + day=day, + week=week, + day_of_week=day_of_week, + hour=hour, + minute=minute, + second=second, + ) + job = self.scheduler.add_job( + func, + trigger=trigger, + id=job_id, + args=args or [], + kwargs=kwargs or {}, + replace_existing=replace_existing, + ) + logger.info(f"添加 cron 任务: {job.id}") + return job + + async def add_date_job( + self, + func: Callable, + run_date: datetime, + job_id: Optional[str] = None, + args: Optional[List] = None, + kwargs: Optional[Dict] = None, + ) -> Job: + """添加一次性任务""" + trigger = DateTrigger(run_date=run_date) + job = self.scheduler.add_job( + func, trigger=trigger, id=job_id, args=args or [], kwargs=kwargs or {} + ) + logger.info(f"添加一次性任务: {job.id}") + return job + + async def remove_job(self, job_id: str) -> bool: + """删除任务""" + try: + self.scheduler.remove_job(job_id) + logger.info(f"删除任务: {job_id}") + return True + except Exception as e: + logger.error(f"删除任务失败 {job_id}: {e}") + return False + + async def pause_job(self, job_id: str) -> bool: + """暂停任务""" + try: + self.scheduler.pause_job(job_id) + logger.info(f"暂停任务: {job_id}") + return True + except Exception as e: + logger.error(f"暂停任务失败 {job_id}: {e}") + return False + + async def resume_job(self, job_id: str) -> bool: + """恢复任务""" + try: + self.scheduler.resume_job(job_id) + logger.info(f"恢复任务: {job_id}") + return True + except Exception as e: + logger.error(f"恢复任务失败 {job_id}: {e}") + return False + + async def get_job(self, job_id: str) -> Optional[Dict]: + """获取任务信息""" + job = self.scheduler.get_job(job_id) + if job: + return self._job_to_dict(job) + return None + + async def get_jobs(self) -> List[Dict]: + """获取所有任务""" + jobs = self.scheduler.get_jobs() + return [self._job_to_dict(job) for job in jobs] + + async def reschedule_job( + self, job_id: str, trigger_type: str, **trigger_args + ) -> Optional[Job]: + """重新调度任务(修改触发时间)""" + try: + job = self.scheduler.get_job(job_id) + if not job: + return None + + if trigger_type == "interval": + trigger = IntervalTrigger(**trigger_args) + elif trigger_type == "cron": + trigger = CronTrigger(**trigger_args) + elif trigger_type == "date": + trigger = DateTrigger(**trigger_args) + else: + raise ValueError(f"不支持的 trigger 类型: {trigger_type}") + + job.reschedule(trigger=trigger) + logger.info(f"重新调度任务: {job_id}") + return job + except Exception as e: + logger.error(f"重新调度任务失败 {job_id}: {e}") + return None + + def _job_to_dict(self, job: Job) -> Dict: + """将 Job 对象转换为字典""" + return { + "id": job.id, + "name": job.name, + "next_run_time": ( + job.next_run_time.isoformat() if job.next_run_time else None + ), + "trigger": str(job.trigger), + "pending": job.pending, + "args": job.args, + "kwargs": job.kwargs, + } + + +_task_service: Optional[TaskService] = None + + +def get_task_service_instance(): + global _task_service + if _task_service is None: + _task_service = TaskService() + return _task_service + + +async def get_task_service(request: Request) -> AsyncGenerator: + return request.app.state.task_service diff --git a/src/chongming/app/model/user.py b/src/chongming/app/model/user.py index 02cbf3f..9be0624 100644 --- a/src/chongming/app/model/user.py +++ b/src/chongming/app/model/user.py @@ -1,4 +1,5 @@ from typing import Optional + from sqlmodel import SQLModel, Field from pydantic import BaseModel diff --git a/src/chongming/app/service/auth.py b/src/chongming/app/service/auth.py index 46b4f18..917949b 100644 --- a/src/chongming/app/service/auth.py +++ b/src/chongming/app/service/auth.py @@ -8,9 +8,9 @@ from passlib.context import CryptContext from sqlalchemy.ext.asyncio import AsyncSession from ..model.user import User -from .user import UserService from ..core.jwt_cache import get_jwt_cache, TokenResponse, TokenData from ..core.config import get_config +from .user import UserService class AuthService: @@ -105,7 +105,7 @@ class AuthService: request: Optional[Request] = None, ) -> Optional[str]: """使用刷新令牌获取新的访问令牌""" - validation = self.jwt_cache.validate_token(refresh_token, token_type="access") + validation = self.jwt_cache.validate_token(refresh_token, token_type="refresh") if not validation: return None diff --git a/src/chongming/app/service/user.py b/src/chongming/app/service/user.py index e140a78..381835d 100644 --- a/src/chongming/app/service/user.py +++ b/src/chongming/app/service/user.py @@ -1,6 +1,7 @@ +from typing import Optional + from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from typing import Optional from ..model.user import User from ..core.security import get_password_hash diff --git a/src/chongming/app/task/__init__.py b/src/chongming/app/task/__init__.py new file mode 100644 index 0000000..45182a2 --- /dev/null +++ b/src/chongming/app/task/__init__.py @@ -0,0 +1,20 @@ +from typing import Callable, Optional + +from ..core.scheduler import TaskService +from .execute_background import execute_background_task + + +task_dict = {"execute_background_task": execute_background_task} + + +def get_task(task_name: str) -> Optional[Callable]: + return task_dict.get(task_name, None) + + +async def init_tasks_callback(task_service: TaskService): + await task_service.add_interval_job( + execute_background_task, + seconds=1, + args=["task_name"], + job_id="task_name", + ) diff --git a/src/chongming/app/task/execute_background.py b/src/chongming/app/task/execute_background.py new file mode 100644 index 0000000..0aac9e8 --- /dev/null +++ b/src/chongming/app/task/execute_background.py @@ -0,0 +1,8 @@ +from ..core.scheduler import logger + + +async def execute_background_task(task_name: str): + from datetime import datetime + + """模拟后台任务执行""" + logger.info(f"Executing task: {task_name} at {datetime.utcnow()}") diff --git a/src/chongming/scripts/build.py b/src/chongming/scripts/build.py index 3663928..a9ad951 100644 --- a/src/chongming/scripts/build.py +++ b/src/chongming/scripts/build.py @@ -8,46 +8,6 @@ import sys import asyncio -def create_admin(): - try: - cmd = [ - sys.executable, - "../src/chongming/scripts/create_admin.py", - ] - print(f"正在运行 create_admin.py: {' '.join(cmd)}") - - # 创建包含 UTF-8 编码的环境变量 - env = os.environ.copy() - env["PYTHONIOENCODING"] = "utf-8" - env["PYTHONUTF8"] = "1" - - result = subprocess.run( - cmd, - cwd="./build", - capture_output=True, - text=True, - encoding="utf-8", # 明确指定编码 - errors="replace", # 替换无法解码的字符 - env=env, # 传递修改后的环境变量 - ) - - if result.returncode == 0: - print("创建管理员用户成功") - print(result.stdout) - else: - print("创建管理员用户失败") - # 安全地打印错误信息,避免编码问题 - try: - print("错误信息:", result.stderr) - except UnicodeEncodeError: - print( - "错误信息:", result.stderr.encode("ascii", "ignore").decode("ascii") - ) - except Exception as e: - print("创建管理员用户失败") - print(f"异常: {e}") - - def run_pyarmor_obfuscate(): # pyarmor obfuscate --output=obfuscated_script.py original_script.py obfuscate_files = [ @@ -113,6 +73,8 @@ def run_pyinstaller(): "--hidden-import", "aiosqlite", "--hidden-import", + "apscheduler", + "--hidden-import", "pyjwt", "--hidden-import", "sqlitedict", diff --git a/uv.lock b/uv.lock index 53efcd4..1056b2b 100644 --- a/uv.lock +++ b/uv.lock @@ -101,6 +101,33 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] +[[package]] +name = "apscheduler" +version = "3.11.2" +source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } +dependencies = [ + { name = "backports-zoneinfo", marker = "python_full_version < '3.9'" }, + { name = "tzlocal", version = "5.2", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.9'" }, + { name = "tzlocal", version = "5.3.1", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" } +wheels = [ + { url = "https://mirrors.ustc.edu.cn/pypi/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" }, +] + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } +sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/ad/85/475e514c3140937cf435954f78dedea1861aeab7662d11de232bdaa90655/backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2", size = 74098, upload-time = "2020-06-23T13:51:22.041Z" } +wheels = [ + { url = "https://mirrors.ustc.edu.cn/pypi/packages/4a/6d/eca004eeadcbf8bd64cc96feb9e355536147f0577420b44d80c7cac70767/backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987", size = 35816, upload-time = "2020-06-23T13:51:21.244Z" }, + { url = "https://mirrors.ustc.edu.cn/pypi/packages/c1/8f/9b1b920a6a95652463143943fa3b8c000cb0b932ab463764a6f2a2416560/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1", size = 72147, upload-time = "2020-06-23T13:51:17.562Z" }, + { url = "https://mirrors.ustc.edu.cn/pypi/packages/1a/ab/3e941e3fcf1b7d3ab3d0233194d99d6a0ed6b24f8f956fc81e47edc8c079/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9", size = 74033, upload-time = "2020-06-23T13:51:14.592Z" }, + { url = "https://mirrors.ustc.edu.cn/pypi/packages/c0/34/5fdb0a3a28841d215c255be8fc60b8666257bb6632193c86fd04b63d4a31/backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328", size = 36803, upload-time = "2020-06-23T13:51:07.517Z" }, + { url = "https://mirrors.ustc.edu.cn/pypi/packages/78/cc/e27fd6493bbce8dbea7e6c1bc861fe3d3bc22c4f7c81f4c3befb8ff5bfaf/backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6", size = 38967, upload-time = "2020-06-23T13:51:13.735Z" }, +] + [[package]] name = "bcrypt" version = "4.0.1" @@ -312,7 +339,9 @@ source = { editable = "." } dependencies = [ { name = "aiosqlite", version = "0.20.0", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.9'" }, { name = "aiosqlite", version = "0.22.1", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.9'" }, + { name = "apscheduler" }, { name = "bcrypt" }, + { name = "diskcache" }, { name = "fastapi", version = "0.124.4", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.9'" }, { name = "fastapi", version = "0.128.8", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version == '3.9.*'" }, { name = "fastapi", version = "0.135.1", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.10'" }, @@ -321,7 +350,6 @@ dependencies = [ { name = "pyjwt", version = "2.9.0", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.9'" }, { name = "pyjwt", version = "2.11.0", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.9'" }, { name = "sqlite-vfs" }, - { name = "sqlitedict" }, { name = "sqlmodel", version = "0.0.29", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.9'" }, { name = "sqlmodel", version = "0.0.34", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version == '3.9.*'" }, { name = "sqlmodel", version = "0.0.37", source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.10'" }, @@ -342,13 +370,14 @@ dev = [ [package.metadata] requires-dist = [ { name = "aiosqlite" }, + { name = "apscheduler" }, { name = "bcrypt", specifier = "==4.0.1" }, + { name = "diskcache", specifier = ">=5.6.3" }, { name = "fastapi", extras = ["uvicorn"] }, { name = "module-bank" }, { name = "passlib", extras = ["bcrypt"] }, { name = "pyjwt" }, { name = "sqlite-vfs" }, - { name = "sqlitedict" }, { name = "sqlmodel", extras = ["aiosqlite"] }, { name = "tomli" }, { name = "typing-extensions" }, @@ -463,6 +492,15 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } +sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://mirrors.ustc.edu.cn/pypi/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -544,7 +582,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235, upload-time = "2024-09-20T17:07:18.761Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168, upload-time = "2024-09-20T17:36:43.774Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826, upload-time = "2024-09-20T17:39:16.921Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443, upload-time = "2024-09-20T17:44:21.896Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295, upload-time = "2024-09-20T17:08:37.951Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544, upload-time = "2024-09-20T17:08:27.894Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456, upload-time = "2024-09-20T17:44:11.755Z" }, @@ -553,7 +590,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479, upload-time = "2024-09-20T17:07:22.332Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404, upload-time = "2024-09-20T17:36:45.588Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813, upload-time = "2024-09-20T17:39:19.052Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517, upload-time = "2024-09-20T17:44:24.101Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831, upload-time = "2024-09-20T17:08:40.577Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413, upload-time = "2024-09-20T17:08:31.728Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619, upload-time = "2024-09-20T17:44:14.222Z" }, @@ -562,7 +598,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260, upload-time = "2024-09-20T17:08:07.301Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064, upload-time = "2024-09-20T17:36:47.628Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420, upload-time = "2024-09-20T17:39:21.258Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035, upload-time = "2024-09-20T17:44:26.501Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105, upload-time = "2024-09-20T17:08:42.048Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077, upload-time = "2024-09-20T17:08:33.707Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975, upload-time = "2024-09-20T17:44:15.989Z" }, @@ -571,7 +606,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload-time = "2024-09-20T17:08:26.312Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload-time = "2024-09-20T17:36:48.983Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload-time = "2024-09-20T17:39:22.705Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736, upload-time = "2024-09-20T17:44:28.544Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347, upload-time = "2024-09-20T17:08:45.56Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583, upload-time = "2024-09-20T17:08:36.85Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039, upload-time = "2024-09-20T17:44:18.287Z" }, @@ -579,7 +613,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490, upload-time = "2024-09-20T17:17:09.501Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731, upload-time = "2024-09-20T17:36:50.376Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304, upload-time = "2024-09-20T17:39:24.55Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537, upload-time = "2024-09-20T17:44:31.102Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506, upload-time = "2024-09-20T17:08:47.852Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753, upload-time = "2024-09-20T17:08:38.079Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731, upload-time = "2024-09-20T17:44:20.556Z" }, @@ -587,7 +620,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/97/83/bdf5f69fcf304065ec7cf8fc7c08248479cfed9bcca02bf0001c07e000aa/greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9", size = 271017, upload-time = "2024-09-20T17:08:54.806Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/31/4a/2d4443adcb38e1e90e50c653a26b2be39998ea78ca1a4cf414dfdeb2e98b/greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111", size = 642888, upload-time = "2024-09-20T17:36:53.307Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/5a/c9/b5d9ac1b932aa772dd1eb90a8a2b30dbd7ad5569dcb7fdac543810d206b4/greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81", size = 655451, upload-time = "2024-09-20T17:39:28.564Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/a8/18/218e21caf7caba5b2236370196eaebc00987d4a2b2d3bf63cc4d4dd5a69f/greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba", size = 651409, upload-time = "2024-09-20T17:44:34.134Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/a7/25/de419a2b22fa6e18ce3b2a5adb01d33ec7b2784530f76fa36ba43d8f0fac/greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8", size = 650661, upload-time = "2024-09-20T17:08:50.932Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/d8/88/0ce16c0afb2d71d85562a7bcd9b092fec80a7767ab5b5f7e1bbbca8200f8/greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1", size = 605959, upload-time = "2024-09-20T17:08:43.376Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/5a/10/39a417ad0afb0b7e5b150f1582cdeb9416f41f2e1df76018434dfac4a6cc/greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd", size = 1132341, upload-time = "2024-09-20T17:44:25.225Z" }, @@ -597,7 +629,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/8c/82/8051e82af6d6b5150aacb6789a657a8afd48f0a44d8e91cb72aaaf28553a/greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", size = 270027, upload-time = "2024-09-20T17:08:27.964Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f9/74/f66de2785880293780eebd18a2958aeea7cbe7814af1ccef634f4701f846/greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", size = 634822, upload-time = "2024-09-20T17:36:54.764Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/68/23/acd9ca6bc412b02b8aa755e47b16aafbe642dde0ad2f929f836e57a7949c/greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f", size = 646866, upload-time = "2024-09-20T17:39:30.2Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/a9/ab/562beaf8a53dc9f6b2459f200e7bc226bb07e51862a66351d8b7817e3efd/greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", size = 641985, upload-time = "2024-09-20T17:44:36.168Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/03/d3/1006543621f16689f6dc75f6bcf06e3c23e044c26fe391c16c253623313e/greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", size = 641268, upload-time = "2024-09-20T17:08:52.469Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/2f/c1/ad71ce1b5f61f900593377b3f77b39408bce5dc96754790311b49869e146/greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", size = 597376, upload-time = "2024-09-20T17:08:46.096Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f7/ff/183226685b478544d61d74804445589e069d00deb8ddef042699733950c7/greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", size = 1123359, upload-time = "2024-09-20T17:44:27.559Z" }, @@ -618,7 +649,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/ff/d6/b3db928fc329b1b19ba32ffe143d2305f3aaafc583f5e1074c74ec445189/greenlet-3.2.5-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:34cc7cf8ab6f4b85298b01e13e881265ee7b3c1daf6bc10a2944abc15d4f87c3", size = 275803, upload-time = "2026-02-20T20:06:42.541Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/b3/ff/ab0ad4ff3d9e1faa266de4f6c79763b33fccd9265995f2940192494cc0ec/greenlet-3.2.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c11fe0cfb0ce33132f0b5d27eeadd1954976a82e5e9b60909ec2c4b884a55382", size = 633556, upload-time = "2026-02-20T20:30:41.594Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/da/dd/7b3ac77099a1671af8077ecedb12c9a1be1310e4c35bb69fd34c18ab6093/greenlet-3.2.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a145f4b1c4ed7a2c94561b7f18b4beec3d3fb6f0580db22f7ed1d544e0620b34", size = 644943, upload-time = "2026-02-20T20:37:23.084Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/56/f0/bea7e7909ea9045b0c5055dad1ec9b81c82b761b4567e625f4f8349acfa1/greenlet-3.2.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:edbf4ab9a7057ee430a678fe2ef37ea5d69125d6bdc7feb42ed8d871c737e63b", size = 640849, upload-time = "2026-02-20T20:43:57.305Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/0f/36/84630e9ff1dfc8b7690957c0f77834a84eabdbd9c4977c3a2d0cbd5325c2/greenlet-3.2.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1d01bdd67db3e5711e6246e451d7a0f75fae7bbf40adde129296a7f9aa7cc9", size = 639841, upload-time = "2026-02-20T20:07:17.473Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/12/c4/6a2ee6c676dea7a05a3c3c1291fbc8ea44f26456b0accc891471293825af/greenlet-3.2.5-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd593db7ee1fa8a513a48a404f8cc4126998a48025e3f5cbbc68d51be0a6bf66", size = 588813, upload-time = "2026-02-20T20:07:56.171Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/01/c0/75e75c2c993aa850292561ec80f5c263e3924e5843aa95a38716df69304c/greenlet-3.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac8db07bced2c39b987bba13a3195f8157b0cfbce54488f86919321444a1cc3c", size = 1117377, upload-time = "2026-02-20T20:32:48.452Z" }, @@ -626,7 +656,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/d8/7b/c6e1192c795c0c12871e199237909a6bd35757d92c8472c7c019959b8637/greenlet-3.2.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acabf468466d18017e2ae5fbf1a5a88b86b48983e550e1ae1437b69a83d9f4ac", size = 276916, upload-time = "2026-02-20T20:06:18.166Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/3e/b6/9887b559f3e1952d23052ec352e9977e808a2246c7cb8282a38337221e88/greenlet-3.2.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:472841de62d60f2cafd60edd4fd4dd7253eb70e6eaf14b8990dcaf177f4af957", size = 636107, upload-time = "2026-02-20T20:30:43.362Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/8a/be/e3e48b63bbc27d660fa1d98aecb64906b90a12e686a436169c1330ef34b2/greenlet-3.2.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d951e7d628a6e8b68af469f0fe4f100ef64c4054abeb9cdafbfaa30a920c950", size = 648240, upload-time = "2026-02-20T20:37:24.608Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/17/f6/2cbe999683f759f14f598234f04ae8ba6f22953a624b3a7a630003e6bfff/greenlet-3.2.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:87b791dd0e031a574249af717ac36f7031b18c35329561c1e0368201c18caf1f", size = 644170, upload-time = "2026-02-20T20:43:59.002Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/4c/ac/e731ed62576e91e533b36d0d97325adc2786674ab9e48ed8a6a24f4ef4e9/greenlet-3.2.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8317d732e2ae0935d9ed2af2ea876fa714cf6f3b887a31ca150b54329b0a6e9", size = 643313, upload-time = "2026-02-20T20:07:19.012Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/70/64/99e5cdceb494bd4c1341c45b93f322601d2c8a5e1e4d1c7a2d24c5ed0570/greenlet-3.2.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce8aed6fdd5e07d3cbb988cbdc188266a4eb9e1a52db9ef5c6526e59962d3933", size = 591295, upload-time = "2026-02-20T20:07:57.286Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/ee/e9/968e11f388c2b8792d3b8b40a57984c894a3b4745dae3662dce722653bc5/greenlet-3.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:60c06b502d56d5451f60ca665691da29f79ed95e247bcf8ce5024d7bbe64acb9", size = 1120277, upload-time = "2026-02-20T20:32:50.103Z" }, @@ -634,7 +663,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/ad/32/022b21523eee713e7550162d5ca6aed23f913cc2c6232b154b9fd9badc07/greenlet-3.2.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2acb30e77042f747ca81f0a10cc153296567e92e666c5e1b117f4595afd43352", size = 278412, upload-time = "2026-02-20T20:03:15.02Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/90/c5/8a3b0ed3cc34d8b988a44349437dfa0941f9c23ac108175f7b4ccea97111/greenlet-3.2.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:393c03c26c865f17f31d8db2f09603fadbe0581ad85a5d5908b131549fc38217", size = 644616, upload-time = "2026-02-20T20:30:44.823Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/b1/2c/2627bea183554695016af6cae93d7474fa90f61e5a6601a84ae7841cb720/greenlet-3.2.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:04e6a202cde56043fd355fefd1552c4caa5c087528121871d950eb4f1b51fa99", size = 658813, upload-time = "2026-02-20T20:37:26.255Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/44/c6/a80fc96f7cca7962dd972875d12c52dfabc94cb02bfeb19f3e7e169fca44/greenlet-3.2.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d5583b2ffa677578a384337ee13125bdf9a427485d689014b39d638a4f3d8dbe", size = 653512, upload-time = "2026-02-20T20:44:00.343Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/2f/1b/75a5aeff487a26ba427a3837da6372f1fe6f2a9c6b2898e28ac99d491c11/greenlet-3.2.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45fcea7b697b91290b36eafc12fff479aca6ba6500d98ef6f34d5634c7119cbe", size = 655426, upload-time = "2026-02-20T20:07:20.124Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/53/91/9b5dfb4f3c88f8247c7a8f4c3759f0740bfa6bb0c59a9f6bf938e913df56/greenlet-3.2.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96e2bb8a56b7e1aed1dbfbbe0050cb2ecca99c7c91892fd1771e3afab63b3e3", size = 611138, upload-time = "2026-02-20T20:07:58.966Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/b4/8d/d0b086410512d9859c84e9242a9b341de9f5566011ddf3a3f6886b842b61/greenlet-3.2.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d7456e67b0be653dfe643bb37d9566cd30939c80f858e2ce6d2d54951f75b14a", size = 1126896, upload-time = "2026-02-20T20:32:52.198Z" }, @@ -642,7 +670,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/dd/95/d5d332fb73affaf7a1fbe80e49c2c7eae4f17c645af24a3b3fa25736d6f0/greenlet-3.2.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f2cc88b50b9006b324c1b9f5f3552f9d4564c78af57cdfb4c7baf4f0aa089146", size = 277166, upload-time = "2026-02-20T20:03:57.077Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/6c/77/89458e20db5a4f1c64f9a0191561227e76d809941ca2d7529006d17d3450/greenlet-3.2.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e66872daffa360b2537170b73ad530f14fa31785b1bc78080125d92edf0a6def", size = 644674, upload-time = "2026-02-20T20:30:46.118Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/90/f8/9962175d2f2eaa629a7fd7545abacc8c4deda3baa4e52c1526d2eb5f5546/greenlet-3.2.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c5445ddb7b586d870dad32ca9fc47c287d6022a528d194efdb8912093c5303ad", size = 658834, upload-time = "2026-02-20T20:37:27.466Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/81/71/52c21a7106ce5218aa6fa59ec32825b2655f875a09b69f68bd3e5d01feb3/greenlet-3.2.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd904626b8779810062cb455514594776e3cba3b8c0ba4939894df9f7b384971", size = 653091, upload-time = "2026-02-20T20:44:01.927Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f5/d7/826d0e080f0a7ad5ec47c8d143bbd3ca0887657bb806595fe2434d12938a/greenlet-3.2.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:752c896a8c976548faafe8a306d446c6a4c68d4fd24699b84d4393bd9ac69a8e", size = 655760, upload-time = "2026-02-20T20:07:21.551Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/41/cc/33bd4c2f816be8c8e16f71740c4130adf3a66a3dd2ba29de72b9d8dd1096/greenlet-3.2.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499b809e7738c8af0ff9ac9d5dd821cb93f4293065a9237543217f0b252f950a", size = 614132, upload-time = "2026-02-20T20:08:00.351Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/48/79/f3891dcfc59097474a53cc3c624f2f2465e431ab493bda043b8c873fb20a/greenlet-3.2.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2c7429f6e9cea7cbf2637d86d3db12806ba970f7f972fcab39d6b54b4457cbaf", size = 1125286, upload-time = "2026-02-20T20:32:54.032Z" }, @@ -650,13 +677,11 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/f6/9d/4e9b941be05f8da7ba804c6413761d2c11cca05994cbf0a015bd729419f0/greenlet-3.2.5-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7123b29e6bad2f3f89681be4ef316480fca798ebe8d22fbaced9cc3775007a4f", size = 277627, upload-time = "2026-02-20T20:06:04.798Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/23/cb/a73625c9a35138330014ecf3740c0d62e0c2b5e7279bb7f2586b1b199fac/greenlet-3.2.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e8fe0c72603201a86b2e038daf9b6c8570715f8779566419cff543b6ace88de", size = 690001, upload-time = "2026-02-20T20:30:47.754Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/83/49/6d1531109507bce7dfb23acf57a87013627ed3ac058851176e443a6a9134/greenlet-3.2.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:050703a60603db0e817364d69e048c70af299040c13a7e67792b9e62d4571196", size = 702953, upload-time = "2026-02-20T20:37:29.125Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/90/ac/6d8fff3b273fc60ad4b46f8411fe91c1e4cca064dfff68d096bc982fa6d0/greenlet-3.2.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:04633da773ae432649a3f092a8e4add390732cc9e1ab52c8ff2c91b8dc86f202", size = 698353, upload-time = "2026-02-20T20:44:03.547Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f7/38/f958ee90fab93529b30cc1e4a59b27c1112b640570043a84af84da3b3b98/greenlet-3.2.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6712bfd520530eb67331813f7112d3ee18e206f48b3d026d8a96cd2d2ad20251", size = 698995, upload-time = "2026-02-20T20:07:22.663Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/51/c1/a603906e79716d61f08afedaf8aed62017661457aef233d62d6e57ecd511/greenlet-3.2.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bc06a78fa3ffbe2a75f1ebc7e040eacf6fa1050a9432953ab111fbbbf0d03c1", size = 661175, upload-time = "2026-02-20T20:08:01.477Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/3f/8f/f880ff4587d236b4d06893fb34da6b299aa0d00f6c8259673f80e1b6d63c/greenlet-3.2.5-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:dbe0e81e24982bb45907ca20152b31c2e3300ca352fdc4acbd4956e4a2cbc195", size = 274946, upload-time = "2026-02-20T20:05:21.979Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/3c/50/f6c78b8420187fdfe97fcf2e6d1dd243a7742d272c32fd4d4b1095474b37/greenlet-3.2.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15871afc0d78ec87d15d8412b337f287fc69f8f669346e391585824970931c48", size = 631781, upload-time = "2026-02-20T20:30:48.845Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/26/d6/3277f92e1961e6e9f41d9f173ea74b5c1f7065072637669f761626f26cc0/greenlet-3.2.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5bf0d7d62e356ef2e87e55e46a4e930ac165f9372760fb983b5631bb479e9d3a", size = 643740, upload-time = "2026-02-20T20:37:30.639Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/f8/8a/c37b87659378759f158dbe03eaeb7ed002a8968f1c649b2972f5323f99b2/greenlet-3.2.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:e3f03ddd7142c758ab41c18089a1407b9959bd276b4e6dfbd8fd06403832c87a", size = 639098, upload-time = "2026-02-20T20:44:07.287Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/2a/6a/4f79d2e7b5ef3723fc5ffea0d6cb22627e5f95e0f19c973fa12bf1cf7891/greenlet-3.2.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6dff6433742073e5b6ad40953a78a0e8cddcb3f6869e5ea635d29a810ca5e7d0", size = 638382, upload-time = "2026-02-20T20:07:23.883Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/4d/59/7aadf33f23c65dbf4db27e7f5b60c414797a61e954352ae4a86c5c8b0553/greenlet-3.2.5-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdd67619cefe1cc9fcab57c8853d2bb36eca9f166c0058cc0d428d471f7c785c", size = 587516, upload-time = "2026-02-20T20:08:02.841Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/1d/46/b3422959f830de28a4eea447414e6bd7b980d755892f66ab52ad805da1c4/greenlet-3.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3828b309dfb1f117fe54867512a8265d8d4f00f8de6908eef9b885f4d8789062", size = 1115818, upload-time = "2026-02-20T20:32:55.786Z" }, @@ -675,7 +700,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/38/3f/9859f655d11901e7b2996c6e3d33e0caa9a1d4572c3bc61ed0faa64b2f4c/greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d", size = 277747, upload-time = "2026-02-20T20:16:21.325Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/fb/07/cb284a8b5c6498dbd7cba35d31380bb123d7dceaa7907f606c8ff5993cbf/greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13", size = 579202, upload-time = "2026-02-20T20:47:28.955Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/ed/45/67922992b3a152f726163b19f890a85129a992f39607a2a53155de3448b8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e", size = 590620, upload-time = "2026-02-20T20:55:55.581Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/03/5f/6e2a7d80c353587751ef3d44bb947f0565ec008a2e0927821c007e96d3a7/greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7", size = 602132, upload-time = "2026-02-20T21:02:43.261Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/ad/55/9f1ebb5a825215fadcc0f7d5073f6e79e3007e3282b14b22d6aba7ca6cb8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f", size = 591729, upload-time = "2026-02-20T20:20:58.395Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/24/b4/21f5455773d37f94b866eb3cf5caed88d6cea6dd2c6e1f9c34f463cba3ec/greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef", size = 1551946, upload-time = "2026-02-20T20:49:31.102Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/00/68/91f061a926abead128fe1a87f0b453ccf07368666bd59ffa46016627a930/greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca", size = 1618494, upload-time = "2026-02-20T20:21:06.541Z" }, @@ -683,7 +707,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046, upload-time = "2026-02-20T21:02:45.234Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, @@ -692,7 +715,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, @@ -701,7 +723,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, @@ -710,7 +731,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, @@ -719,7 +739,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://mirrors.ustc.edu.cn/pypi/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, { url = "https://mirrors.ustc.edu.cn/pypi/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, @@ -1442,12 +1461,6 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/ef/26/a3f51ea9d10efe2c4f85cac78dcbd898b437d2331cf25d470f804e4498b7/sqlite_vfs-0.1.0-py3-none-any.whl", hash = "sha256:c6e27a35e504bb6360fb0d9083d79572367a903788b3b14aea5f1188130e9314", size = 10768, upload-time = "2025-11-08T08:15:18.588Z" }, ] -[[package]] -name = "sqlitedict" -version = "2.1.0" -source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } -sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/12/9a/7620d1e9dcb02839ed6d4b14064e609cdd7a8ae1e47289aa0456796dd9ca/sqlitedict-2.1.0.tar.gz", hash = "sha256:03d9cfb96d602996f1d4c2db2856f1224b96a9c431bdd16e78032a72940f9e8c", size = 21846, upload-time = "2022-12-03T13:39:13.102Z" } - [[package]] name = "sqlmodel" version = "0.0.29" @@ -1635,6 +1648,47 @@ wheels = [ { url = "https://mirrors.ustc.edu.cn/pypi/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "tzdata" +version = "2026.1" +source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } +sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" } +wheels = [ + { url = "https://mirrors.ustc.edu.cn/pypi/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.2" +source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "backports-zoneinfo", marker = "python_full_version < '3.9'" }, + { name = "tzdata", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201, upload-time = "2023-10-22T17:41:38.449Z" } +wheels = [ + { url = "https://mirrors.ustc.edu.cn/pypi/packages/97/3f/c4c51c55ff8487f2e6d0e618dba917e3c3ee2caae6cf0fbb59c9b1876f2e/tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8", size = 17859, upload-time = "2023-10-22T17:41:36.511Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://mirrors.ustc.edu.cn/pypi/web/simple/" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "tzdata", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://mirrors.ustc.edu.cn/pypi/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + [[package]] name = "uvicorn" version = "0.33.0"