From 35bdc60b77f1d6a030e4d486e3f813828d2084a6 Mon Sep 17 00:00:00 2001 From: ghplvh <1771956812@qq.com> Date: Fri, 21 Aug 2026 21:30:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20mysql=20=E5=8D=95=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E7=B2=BE=E7=AE=80=E5=88=86=E6=94=AF=20=E2=80=94=20SQLAlchemy?= =?UTF-8?q?=202.0=20async=20+=20aiomysql=20+=20alembic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 9 +--- AGENTS.md | 6 ++- README.md | 6 ++- docker-compose.yml | 11 ---- pyproject.toml | 8 +-- src/config.py | 16 +----- src/items/dependencies.py | 19 ++----- src/items/mongo.py | 57 --------------------- src/main.py | 29 ++++------- src/mongo.py | 31 ------------ tests/conftest.py | 13 ++--- uv.lock | 104 +------------------------------------- 12 files changed, 31 insertions(+), 278 deletions(-) delete mode 100644 src/items/mongo.py delete mode 100644 src/mongo.py diff --git a/.env.example b/.env.example index 3786359..0c7fbaa 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,6 @@ -# ===== 数据库后端:mysql 或 mongodb ===== -DB_BACKEND=mongodb - -# ===== MySQL(DB_BACKEND=mysql 时必填)===== +# ===== MySQL ===== MYSQL_DSN=mysql+aiomysql://root:root@127.0.0.1:3306/app?charset=utf8mb4 -# ===== MongoDB(DB_BACKEND=mongodb 时必填)===== -MONGO_DSN=mongodb://127.0.0.1:27017 -MONGO_DB=app - # ===== 应用 ===== APP_NAME=fastapi-template APP_ENV=dev diff --git a/AGENTS.md b/AGENTS.md index 264ddca..bb4bc6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,8 @@ -# AGENTS.md — fastapi-template 开发规范 +# AGENTS.md — fastapi-template 开发规范(mysql 分支) + +> 本分支为 **MySQL 单后端**:ORM 模型写 `src/{domain}/mysql.py`, +> 改表结构必须 `uv run alembic revision --autogenerate` 生成迁移 + `alembic upgrade head` 执行。 + 本文件是 AI Agent / 开发者在本仓库工作的**必读规范**。先读这个,再动手。 通用 FastAPI 最佳实践参考 [docs/AGENTS_GENERIC.md](./docs/AGENTS_GENERIC.md) 与 [docs/BEST_PRACTICES_ZH.md](./docs/BEST_PRACTICES_ZH.md)。 diff --git a/README.md b/README.md index 9b657a1..bda2651 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# fastapi-template +# fastapi-template(mysql 分支) + +> 本分支为 **MySQL(SQLAlchemy 2.0 async + aiomysql + alembic)单后端**精简版。 +> 双后端可切换版见 `main` 分支,MongoDB 版见 `mongodb` 分支。 + 开箱即用的 FastAPI 项目模板。**一条环境变量切换 MySQL / MongoDB**,业务代码零改动。 diff --git a/docker-compose.yml b/docker-compose.yml index 42961f2..379fc48 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,12 +5,9 @@ services: - "8000:8000" env_file: .env environment: - # compose 内网地址覆盖 .env 里的 127.0.0.1 MYSQL_DSN: mysql+aiomysql://root:root@mysql:3306/app?charset=utf8mb4 - MONGO_DSN: mongodb://mongo:27017 depends_on: - mysql - - mongo mysql: image: mysql:8.0 @@ -22,13 +19,5 @@ services: volumes: - mysql_data:/var/lib/mysql - mongo: - image: mongo:8.0 - ports: - - "27017:27017" - volumes: - - mongo_data:/data/db - volumes: mysql_data: - mongo_data: diff --git a/pyproject.toml b/pyproject.toml index 786a3c4..8f0c039 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,17 @@ [project] name = "fastapi-template" version = "0.1.0" -description = "开箱即用的 FastAPI 模板:MySQL(SQLAlchemy2.0 async) / MongoDB(Beanie) 可切换" +description = "FastAPI 模板(MySQL + SQLAlchemy 2.0 async 单后端分支)" requires-python = ">=3.11" dependencies = [ "fastapi>=0.115", "uvicorn[standard]>=0.30", "pydantic>=2.7", "pydantic-settings>=2.4", - # MySQL backend "sqlalchemy[asyncio]>=2.0", "aiomysql>=0.2.0", "alembic>=1.13", - # MongoDB backend - "beanie>=2.0", - "pymongo>=4.9", - "cryptography>=50.0.0", + "cryptography>=43", # aiomysql 连 MySQL8 认证需要 ] [dependency-groups] diff --git a/src/config.py b/src/config.py index d7cb5fd..b17a12c 100644 --- a/src/config.py +++ b/src/config.py @@ -1,28 +1,14 @@ -"""全局配置:pydantic-settings,环境变量 / .env 驱动。""" - -from enum import StrEnum +"""全局配置:pydantic-settings,环境变量 / .env 驱动。(mysql 单后端分支)""" from pydantic_settings import BaseSettings, SettingsConfigDict -class DBBackend(StrEnum): - MYSQL = "mysql" - MONGODB = "mongodb" - - class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") - # 数据库后端开关:mysql / mongodb - DB_BACKEND: DBBackend = DBBackend.MONGODB - # MySQL(SQLAlchemy async DSN) MYSQL_DSN: str = "mysql+aiomysql://root:root@127.0.0.1:3306/app?charset=utf8mb4" - # MongoDB - MONGO_DSN: str = "mongodb://127.0.0.1:27017" - MONGO_DB: str = "app" - # 应用 APP_NAME: str = "fastapi-template" APP_ENV: str = "dev" diff --git a/src/items/dependencies.py b/src/items/dependencies.py index fb7f079..2eab05f 100644 --- a/src/items/dependencies.py +++ b/src/items/dependencies.py @@ -1,21 +1,17 @@ -"""items 域依赖:按 DB_BACKEND 选择仓储实现。 - -router 只依赖 ItemRepoDep;切库只改 .env,业务代码零改动。 -""" +"""items 域依赖:MySQL(SQLAlchemy async) 仓储。""" from typing import Annotated, Protocol from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession -from src.config import settings from src.database import get_db from src.items.mysql import MySQLItemRepo from src.items.schemas import ItemCreate, ItemOut, ItemUpdate class ItemRepo(Protocol): - """仓储协议:新增域时定义同样的 Protocol,两后端各自实现。""" + """仓储协议:业务层只依赖它,不关心底层实现。""" async def create(self, data: ItemCreate) -> ItemOut: ... async def get(self, item_id: str) -> ItemOut | None: ... @@ -24,17 +20,8 @@ class ItemRepo(Protocol): async def delete(self, item_id: str) -> bool: ... -async def _get_mysql_repo(session: Annotated[AsyncSession, Depends(get_db)]) -> ItemRepo: +async def get_item_repo(session: Annotated[AsyncSession, Depends(get_db)]) -> ItemRepo: return MySQLItemRepo(session) -async def _get_mongo_repo() -> ItemRepo: - from src.items.mongo import MongoItemRepo - - return MongoItemRepo() - - -# 模块加载时按配置选定实现 -get_item_repo = _get_mysql_repo if settings.DB_BACKEND == "mysql" else _get_mongo_repo - ItemRepoDep = Annotated[ItemRepo, Depends(get_item_repo)] diff --git a/src/items/mongo.py b/src/items/mongo.py deleted file mode 100644 index 69cbe87..0000000 --- a/src/items/mongo.py +++ /dev/null @@ -1,57 +0,0 @@ -"""items 域 MongoDB 实现:Beanie ODM。""" - -from datetime import UTC, datetime - -from beanie import Document, PydanticObjectId -from pydantic import Field - -from src.items.schemas import ItemCreate, ItemOut, ItemUpdate - - -class ItemDoc(Document): - name: str = Field(max_length=128) - description: str | None = None - created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) - - class Settings: - name = "items" - - def to_out(self) -> ItemOut: - return ItemOut( - id=str(self.id), - name=self.name, - description=self.description, - created_at=self.created_at, - ) - - -class MongoItemRepo: - """与 MySQLItemRepo 同接口,router 无感知切换。""" - - async def create(self, data: ItemCreate) -> ItemOut: - doc = ItemDoc(name=data.name, description=data.description) - await doc.insert() - return doc.to_out() - - async def get(self, item_id: str) -> ItemOut | None: - doc = await ItemDoc.get(PydanticObjectId(item_id)) - return doc.to_out() if doc else None - - async def list(self, skip: int = 0, limit: int = 20) -> tuple[int, list[ItemOut]]: - total = await ItemDoc.count() - docs = await ItemDoc.find_all().skip(skip).limit(limit).to_list() - return total, [d.to_out() for d in docs] - - async def update(self, item_id: str, data: ItemUpdate) -> ItemOut | None: - doc = await ItemDoc.get(PydanticObjectId(item_id)) - if not doc: - return None - await doc.set(data.model_dump(exclude_unset=True)) - return doc.to_out() - - async def delete(self, item_id: str) -> bool: - doc = await ItemDoc.get(PydanticObjectId(item_id)) - if not doc: - return False - await doc.delete() - return True diff --git a/src/main.py b/src/main.py index b13154c..4da1d93 100644 --- a/src/main.py +++ b/src/main.py @@ -1,8 +1,5 @@ -"""应用入口:FastAPI app + lifespan。 - -数据库按 settings.DB_BACKEND 初始化: -- mysql → 建 engine,启动时自动建表(生产用 alembic,见 README) -- mongodb → motor client + beanie init +"""应用入口:FastAPI app + lifespan(mysql 单后端分支)。 +启动时自动建表仅限开发;生产用 alembic upgrade head。 """ from collections.abc import AsyncGenerator @@ -11,26 +8,18 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from src.config import DBBackend, settings +from src.config import settings +from src.database import Base, close_engine, engine from src.exceptions import register_exception_handlers from src.items.router import router as items_router @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: - if settings.DB_BACKEND == DBBackend.MYSQL: - from src.database import Base, close_engine, engine - - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) - yield - await close_engine() - else: - from src.mongo import close_mongo, init_mongo - - await init_mongo() - yield - await close_mongo() + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + yield + await close_engine() def create_app() -> FastAPI: @@ -51,7 +40,7 @@ def create_app() -> FastAPI: @app.get("/health", tags=["meta"]) async def health() -> dict: - return {"status": "ok", "db_backend": str(settings.DB_BACKEND)} + return {"status": "ok", "db_backend": "mysql"} return app diff --git a/src/mongo.py b/src/mongo.py deleted file mode 100644 index e072472..0000000 --- a/src/mongo.py +++ /dev/null @@ -1,31 +0,0 @@ -"""MongoDB:pymongo async client + beanie 初始化。 -仅 DB_BACKEND=mongodb 时使用;mysql 模式下本模块不被引用。 -(beanie 2.x 基于 pymongo 原生异步客户端,不再需要 motor) -""" - -from beanie import init_beanie -from pymongo import AsyncMongoClient - -from src.config import settings - -_client: AsyncMongoClient | None = None - - -def _collect_documents() -> list[type]: - """汇总所有 beanie Document。新增域时在此登记。""" - from src.items import mongo as item_mongo - - return [item_mongo.ItemDoc] - - -async def init_mongo() -> None: - global _client - _client = AsyncMongoClient(settings.MONGO_DSN) - await init_beanie(database=_client[settings.MONGO_DB], document_models=_collect_documents()) - - -async def close_mongo() -> None: - global _client - if _client is not None: - await _client.close() - _client = None diff --git a/tests/conftest.py b/tests/conftest.py index 04d3a9a..8493ef3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,5 @@ """测试基座:httpx ASGITransport 进程内测试,不起真实服务。 - -默认跑 mongodb 后端(需要本地 27017 有 mongo)。 -跑 mysql 后端:DB_BACKEND=mysql MYSQL_DSN=... uv run pytest +需要本地 MySQL;测试库 app_test,与开发库隔离。 """ import os @@ -9,12 +7,9 @@ import os import pytest_asyncio from httpx import ASGITransport, AsyncClient -# 测试用独立库,避免污染开发数据 -os.environ.setdefault("MONGO_DB", "app_test") -if os.environ.get("DB_BACKEND") == "mysql": - os.environ["MYSQL_DSN"] = os.environ.get( - "MYSQL_DSN", "mysql+aiomysql://root:root@127.0.0.1:3306/app_test?charset=utf8mb4" - ) +os.environ.setdefault( + "MYSQL_DSN", "mysql+aiomysql://root:root@127.0.0.1:3306/app_test?charset=utf8mb4" +) from src.main import create_app diff --git a/uv.lock b/uv.lock index f06972c..fc291aa 100644 --- a/uv.lock +++ b/uv.lock @@ -59,22 +59,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] -[[package]] -name = "beanie" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "lazy-model" }, - { name = "pydantic" }, - { name = "pymongo" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/a1/4abc93d5437999c55465f68c3c81e4735cdfe22827072c0e9cab00f63d11/beanie-2.2.0.tar.gz", hash = "sha256:2dc116e7f4a6650f8066f95851d34d898983af6ffaf5366dcfef892993537a51", size = 70115, upload-time = "2026-08-07T17:15:24.494Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/ae/2584fb9871b58f3c5334110f8ce00bd1421fe5fbda28eeac9a8d01d10ba6/beanie-2.2.0-py3-none-any.whl", hash = "sha256:4074f893ef00c52b8538b814c9dfd55130eea4ed0579abfae72853657c9029e4", size = 93116, upload-time = "2026-08-07T17:15:22.777Z" }, -] - [[package]] name = "certifi" version = "2026.7.22" @@ -259,15 +243,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" }, ] -[[package]] -name = "dnspython" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, -] - [[package]] name = "fastapi" version = "0.141.1" @@ -291,12 +266,10 @@ source = { virtual = "." } dependencies = [ { name = "aiomysql" }, { name = "alembic" }, - { name = "beanie" }, { name = "cryptography" }, { name = "fastapi" }, { name = "pydantic" }, { name = "pydantic-settings" }, - { name = "pymongo" }, { name = "sqlalchemy", extra = ["asyncio"] }, { name = "uvicorn", extra = ["standard"] }, ] @@ -313,12 +286,10 @@ dev = [ requires-dist = [ { name = "aiomysql", specifier = ">=0.2.0" }, { name = "alembic", specifier = ">=1.13" }, - { name = "beanie", specifier = ">=2.0" }, - { name = "cryptography", specifier = ">=50.0.0" }, + { name = "cryptography", specifier = ">=43" }, { name = "fastapi", specifier = ">=0.115" }, { name = "pydantic", specifier = ">=2.7" }, { name = "pydantic-settings", specifier = ">=2.4" }, - { name = "pymongo", specifier = ">=4.9" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.30" }, ] @@ -506,18 +477,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "lazy-model" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/85/e25dc36dee49cf0726c03a1558b5c311a17095bc9361bcbf47226cb3075a/lazy-model-0.4.0.tar.gz", hash = "sha256:a851d85d0b518b0b9c8e626bbee0feb0494c0e0cb5636550637f032dbbf9c55f", size = 8256, upload-time = "2025-08-07T20:05:34.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/54/653ea0d7c578741e9867ccf0cbf47b7eac09ff22e4238f311ac20671a911/lazy_model-0.4.0-py3-none-any.whl", hash = "sha256:95ea59551c1ac557a2c299f75803c56cc973923ef78c67ea4839a238142f7927", size = 13749, upload-time = "2025-08-07T20:05:36.303Z" }, -] - [[package]] name = "mako" version = "1.4.1" @@ -771,67 +730,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] -[[package]] -name = "pymongo" -version = "4.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/64/50be6fbac9c79fe2e4c17401a467da2d8764d82833d83cec325afe5cab32/pymongo-4.17.0.tar.gz", hash = "sha256:70ffa08ba641468cc068cf46c06b34f01a8ce3489f6411309fcb5ceabe6b2fc0", size = 2523370, upload-time = "2026-04-20T16:39:53.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/e2/336d86f221cf1b56b2ed9330d4a3b98f9f38f0b37829ae9a9184617d5419/pymongo-4.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4141e6c6a339789b2974efa00ecd9409101672d77a0e3ee2cc3839eedf8ec4df", size = 874668, upload-time = "2026-04-20T16:37:41.39Z" }, - { url = "https://files.pythonhosted.org/packages/34/8e/75d3c6c935d187ab59c61e9c15d9aab3f274b563eaf1706e8cae5f508dec/pymongo-4.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e68c76b84e0c132d9dbf9307f12ff8185702328187a87b9aca8c941303873433", size = 875294, upload-time = "2026-04-20T16:37:43.432Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ec/62e855744489dbcd54fd778aae4d80fa4c4819e8fb228ca0cf6f21a03997/pymongo-4.17.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba2195d4f386f839a52a23ea1cfd60ffaaba78a3d7841db51b7e433001139918", size = 1496233, upload-time = "2026-04-20T16:37:45.518Z" }, - { url = "https://files.pythonhosted.org/packages/82/e8/93e4e5e5ce8fdf8929dabeefe24aafa5ce046028eed0dfa8eeb936e72c49/pymongo-4.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446ff4bfcb6ec2a2e50998c860986a1e992136f998b7f53e7a717fb8aa5a0b9", size = 1522927, upload-time = "2026-04-20T16:37:47.492Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/425dc1d21e0f17bdea0072fc463f662f7fa06d2852af52975c9eced3c07c/pymongo-4.17.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2a0d5ac205728c86e0a02192f1aa5f865b0d7d51f8df6101c01a69a7fc620d72", size = 1583468, upload-time = "2026-04-20T16:37:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9d/f08b07eeffda1a43c1759f0fa625e88ae12360996eb56d42aad832fa7dff/pymongo-4.17.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:485c8a8eaa4c739f00a331fc73757898ee7c092c214a79e63866ff76aaf282ff", size = 1572787, upload-time = "2026-04-20T16:37:51.061Z" }, - { url = "https://files.pythonhosted.org/packages/e9/c2/6855a07aafa7b894929af23675b6fb9634800ce43122b76a62f6eeb8da2a/pymongo-4.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2dfcc795f5b9fedbe179a11fdf6051581479d196582a3fe819a92a00e9b9969", size = 1526184, upload-time = "2026-04-20T16:37:53.358Z" }, - { url = "https://files.pythonhosted.org/packages/4e/05/c952bac7db71c1942ea3559fcd308b49754cc5004b455935fb4000d1f37b/pymongo-4.17.0-cp311-cp311-win32.whl", hash = "sha256:c2292144505fb12156b981bd440f3dc994a883da06ac726c0c8692ccdbc1c510", size = 852621, upload-time = "2026-04-20T16:37:55.28Z" }, - { url = "https://files.pythonhosted.org/packages/11/c0/c04da9f4c0c6252404598f4e394b862a58a9e866822a70ae261c8a018fdf/pymongo-4.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:2e190827834fce70ecdf9d46796c6dbc0ce08ea87dc2ff5bc6f3f5579b605cb9", size = 867852, upload-time = "2026-04-20T16:37:57.233Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b2/c7b4870fbeef471e947d3e014676f5910d02e0197074d692ebcf24ec049a/pymongo-4.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:a8f9c40a09bb7d4b9fc8b1da65ecf6efa79bda5cb2756f39d9b6940fac1d19ae", size = 855019, upload-time = "2026-04-20T16:37:58.983Z" }, - { url = "https://files.pythonhosted.org/packages/98/90/60bcb508840135d5ee46b51b1a950f548338aa8145a8366dbe6639ae51ac/pymongo-4.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53ffa94b2340dbf6b055e09a0090618c60482c158ecfc9565642fc996bf0944", size = 930529, upload-time = "2026-04-20T16:38:00.936Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e9/313840f1e52c6dfac47f704428cbfbce59956ebe7633bffc92b03f74f0ad/pymongo-4.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6fe0de9d0f6791abce3471230b32b4817bf89d27b1182b6a550e1ec0fa72aa9a", size = 930665, upload-time = "2026-04-20T16:38:02.915Z" }, - { url = "https://files.pythonhosted.org/packages/78/35/9d3565ea45b1606f635c1e2cd2563c28d66caafdc50f7ad7d979fcd1b363/pymongo-4.17.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e537e95514dae1aaa718f481ec03151a0f0394bcd05f1322896d8fc1330cb729", size = 1762369, upload-time = "2026-04-20T16:38:05.375Z" }, - { url = "https://files.pythonhosted.org/packages/95/ee/149b0d4b1a11c38bff6f14c23d5814c9b0843fd6dc38ad40596bdb1a62d2/pymongo-4.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37a8385c29881b43eab31f584100fa0eaddedd5607adf010147ba1810118be90", size = 1798044, upload-time = "2026-04-20T16:38:07.195Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d4/4cee4a7b8d8f6f0550ef6cd2fea42455c5ed619a220cb6ba4fb40d6a5bc8/pymongo-4.17.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3ee3d241ed77a4fc99ce3cff3b289c3ebce37f61fdd7349d3592c23b82c8784", size = 1878567, upload-time = "2026-04-20T16:38:09.121Z" }, - { url = "https://files.pythonhosted.org/packages/45/ef/7fe366c84952619ee2f69973566c214775e083dd4df465751912153e4b72/pymongo-4.17.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9eb5d63a3c518cb0804ed678f5e2b875af032d89a7cf57a57360322cf6a4d222", size = 1864881, upload-time = "2026-04-20T16:38:10.896Z" }, - { url = "https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e97e03fa13327c87e3fdc5656acd01e71817f0c1dc3221cd8f30de136bf4ec3", size = 1800349, upload-time = "2026-04-20T16:38:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/b8/69/dafcf04f66e130ddd91aeb92e7a692480eda46dcd04ec1dbe82c06619e10/pymongo-4.17.0-cp312-cp312-win32.whl", hash = "sha256:6877214bff5f06f6884a9fc8d9016a4a7a5f51f537f5c51ac3a576f93e7dfb32", size = 900518, upload-time = "2026-04-20T16:38:15.541Z" }, - { url = "https://files.pythonhosted.org/packages/11/35/5c9262a459f988b4eb2605f70815240b77a0d4131136c4326d18f1822b89/pymongo-4.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9828485f72f63c7d802e0ec41f71906f633c2692621ab3af55ca990186b091b1", size = 920335, upload-time = "2026-04-20T16:38:17.665Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/e9c7265ee176faccf4e52c4797837e794d93569a1046f6b19a4acc36e5ad/pymongo-4.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:1195370a77baf003b59b10e91ecc4706297197f0dd9d29c840cc556dc08f7cee", size = 903289, upload-time = "2026-04-20T16:38:19.33Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6b/c1206879708b94e82fcd8b9653440ec271f79a3674d122192df383047f5a/pymongo-4.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:809ec74de3b9148ae43fa8df9faf53470f511c8d384f13b99d6f671f2a379f15", size = 985829, upload-time = "2026-04-20T16:38:21.031Z" }, - { url = "https://files.pythonhosted.org/packages/cb/cf/bb044ed85160e5c40f568c7c4f4e8ea16f40764ff5d302e5befbe8f6f814/pymongo-4.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a431b737816bf4cddd4fa0fcef04e424ad36b7692734a64150f872fb8f3208be", size = 985899, upload-time = "2026-04-20T16:38:23.409Z" }, - { url = "https://files.pythonhosted.org/packages/74/0a/f6dfd5ea3901e5d6888da8de8ba728971a1d447debab681cfc56f90d1208/pymongo-4.17.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4fab10f8403169ce92f3cea921609d9ee81107306caae06c08f592d4b8ad2b5", size = 2028569, upload-time = "2026-04-20T16:38:25.343Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c5/081f59a1c02ae8c0dc73ae58e563838c44eec81aeafa7d0b93a637841c9b/pymongo-4.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20323b0b1c1d33770ad1fc68d429c757734ce9ad3594421c3d6618f10572b1b9", size = 2072916, upload-time = "2026-04-20T16:38:27.291Z" }, - { url = "https://files.pythonhosted.org/packages/31/42/6e41d434297ffe8b30d9c3717916591a4a7be9075a0dcc2fafdfaaaa62ed/pymongo-4.17.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5a5de048e6da5c18e27cc2437e8c15b3b0cdc8385c15b41178b0caa3322a09c2", size = 2173234, upload-time = "2026-04-20T16:38:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/3d/cf/1e4a7db352ef9485831c7268dfe8402f0117b32a9ad54b16e810699e3617/pymongo-4.17.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dff3de1294fbbc1db0ba6b511f77b8e540601d092538a31312e99c8a91a78b1e", size = 2156784, upload-time = "2026-04-20T16:38:32.134Z" }, - { url = "https://files.pythonhosted.org/packages/12/10/6195be29962a61ebb5f4bd9e4c7519890b172f7968a0a0d880398c6ddb02/pymongo-4.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faf03e4c2aafd6de626dbd30ba246d369ae33f47f10629d1bbe40f72115027a6", size = 2074446, upload-time = "2026-04-20T16:38:34.004Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/33410b8819837ed370c738587306bdf060b59cef11823be212f4a07703c5/pymongo-4.17.0-cp313-cp313-win32.whl", hash = "sha256:c9786665926a09630c5d420c79762cfadbff35a9438bcbc4c81a9fb5ab9228b7", size = 948435, upload-time = "2026-04-20T16:38:35.922Z" }, - { url = "https://files.pythonhosted.org/packages/6f/77/c0ed522f798a286b99acaa7914ed8d9c80ab091f97f57c59ffed72906e5e/pymongo-4.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:5960519b4d7168f1ecdd3ea10c81b2aedeb9423651aca953cfbc8e76705d3b38", size = 972847, upload-time = "2026-04-20T16:38:37.888Z" }, - { url = "https://files.pythonhosted.org/packages/97/f0/c39480a2db385fde23861d0c8acda41cdaf1d43e46579db72c5c013a2e81/pymongo-4.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:0ff6bd2f735ab5356541e3e57d5b7dbfbc3f2ee1ccb10b6b0f82d58af69d1d8e", size = 951575, upload-time = "2026-04-20T16:38:40.544Z" }, - { url = "https://files.pythonhosted.org/packages/da/49/2b0250762a89737ed6f9cea238331baca061b89a8ddd10dd17fee52c3970/pymongo-4.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff5aa3f1c7e3f08eb0e7a016c91ba468b1850ccfd63d9b1f12f56350f4974cef", size = 1040945, upload-time = "2026-04-20T16:38:42.783Z" }, - { url = "https://files.pythonhosted.org/packages/89/1c/7a9b5447a08be20e84b6e5b17330917e8d6d9507daa3cd099a9309f11ad7/pymongo-4.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e816db649ba5d7de0568cf3a9f287a9dc9aad21cf0ca667ab156a7ef47fca0b0", size = 1041187, upload-time = "2026-04-20T16:38:45.358Z" }, - { url = "https://files.pythonhosted.org/packages/78/a1/71704f61632dfc90407a5834fe5f6132854937c4a3648f6c05c351d85a45/pymongo-4.17.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c4fded3a9f1d6a687e36ebd384ac6d00b9b00de1969aa74048e7051ec2a713", size = 2294806, upload-time = "2026-04-20T16:38:47.734Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b9/aff42be75108b96c2469b1d9329b912c15108f3e7ef32fdc86da8423c330/pymongo-4.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2db66aa8dd253a0fc1fad3b0d23d5b3993f7ebde02fbbd7727128debf2853675", size = 2348231, upload-time = "2026-04-20T16:38:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/f2/30/44c115b8ba1479942c15fd9480eb29a7da0ba68acd56983423ba0deb4a94/pymongo-4.17.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3987e96e7c7be4083d42e8ac2cc6c0d5b78db9973c90fce42ae800b616ca6b20", size = 2467614, upload-time = "2026-04-20T16:38:52.665Z" }, - { url = "https://files.pythonhosted.org/packages/d2/84/21ee95c8bf0ca7acae7ec7eb365d740bf8fc0156c194baf2c3bdfcb85ec0/pymongo-4.17.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cee36b3c0d0354f880fa7a7fdcdaf2bb5e542c2281e25c1bfadf8cfe21eba7d2", size = 2445970, upload-time = "2026-04-20T16:38:55.175Z" }, - { url = "https://files.pythonhosted.org/packages/06/89/081d7f1809d5ca09d1e47e49f2111b245f5694de3a7af32cd3a353a6f43f/pymongo-4.17.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:320b34457b20bbcc79997801f95d25ce00472915ca5241167242b42c4359e027", size = 2348605, upload-time = "2026-04-20T16:38:57.557Z" }, - { url = "https://files.pythonhosted.org/packages/ea/c3/0d949f9d3f2a341c1f635c398c16615e96f89f51ff424ed81e914cf1a4de/pymongo-4.17.0-cp314-cp314-win32.whl", hash = "sha256:df4a644af9ae132d4bfdb2e9516ea51a615fd881caddfbfbd071cf1354844479", size = 1004119, upload-time = "2026-04-20T16:39:00.309Z" }, - { url = "https://files.pythonhosted.org/packages/f7/55/5c3a3db1048054c695c75c5964cc8bedc2247fdb5a75ef6fab4ec8bb013e/pymongo-4.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:c797f8a80957134f6dd9690367a0f8f5906d672119af2c6aa55f0c527b656bed", size = 1032314, upload-time = "2026-04-20T16:39:02.665Z" }, - { url = "https://files.pythonhosted.org/packages/e0/19/e235f39906134cb0ffd5574c5a59c355ef5380f0499644ab94994afbb109/pymongo-4.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:68fca71e05ee5da23a8d73cee8379dfb3d26e609a377cae731d742771ed96946", size = 1007627, upload-time = "2026-04-20T16:39:04.678Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e0/c4c1a86791415b14c684fa0908f9da96de91594a3fd1fa1b8dc689fbb800/pymongo-4.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b4384700cffc3f1dd98e088bc0072dedf6d7d68a230bb4b972665cf69c071c1e", size = 1099151, upload-time = "2026-04-20T16:39:06.969Z" }, - { url = "https://files.pythonhosted.org/packages/81/4b/69c67f3e23fd9b23b9bedc7ebd23754881cc9d5c5d5b2a9811e96b07f475/pymongo-4.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:93641192644fa1ee0f34030e774fd31022a27ad11ba22cb1716142231524f8bd", size = 1099346, upload-time = "2026-04-20T16:39:08.996Z" }, - { url = "https://files.pythonhosted.org/packages/a2/19/a5208f62f9508a26d73acc69bd3821b8c8adae253679a3c26d2f9652f0d5/pymongo-4.17.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75bc3aa5b94fdb7138d357ec6ca61cd97e0c79f4f7f0bd3efe9639b15cc50942", size = 2619034, upload-time = "2026-04-20T16:39:11.049Z" }, - { url = "https://files.pythonhosted.org/packages/77/27/426cba1ec5973082a56d4150798529bfdf4151c31391ed1fbbecb23ef2ac/pymongo-4.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e8f8e23c6df7c6d6929f5e734980b227706e73ee847517c9ba5af90f7fc466", size = 2689939, upload-time = "2026-04-20T16:39:13.617Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2e/f70993d1255e33f6ee59a4ec4371cc65bff7a7e3fda7d55c3386f25287e8/pymongo-4.17.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15d3f3d732aecac1f8d481bde4029755615639bd3076f258a2147210aec8515a", size = 2824994, upload-time = "2026-04-20T16:39:16.057Z" }, - { url = "https://files.pythonhosted.org/packages/b3/eb/87b0e988ba889e1fcc3430c2cfc166b251872c813e92b43174298bee17ff/pymongo-4.17.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5f62862d0f87be481fa1fe8cb811994486773c94a2b61e509285e3f2890763", size = 2801745, upload-time = "2026-04-20T16:39:18.476Z" }, - { url = "https://files.pythonhosted.org/packages/67/4c/3f83412d086f682d4d468761d66ddc49cf161e786ea74073045eb4491c60/pymongo-4.17.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64837adbbd72073301af51bb0fc80e3d7707fe5527cea1033ba0320f0b2f881b", size = 2684636, upload-time = "2026-04-20T16:39:20.878Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d8/b75f6f4ab6c8beb50b0270a4f1e2530b5774f5e116563440e1677ca1820f/pymongo-4.17.0-cp314-cp314t-win32.whl", hash = "sha256:b93b22eedc62598cf5ee9d8c8007a8e9121c50fd88137012d8985500e9dc3151", size = 1056356, upload-time = "2026-04-20T16:39:22.996Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5e/648c8a238eef18a25ed8a169ea6542d4a860bbec3e95b3d9badac2935c71/pymongo-4.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3689ea34f6b647c7d1e7bdc60fcfb214b2789ed1359a7fb96569c69f50e5f18f", size = 1090964, upload-time = "2026-04-20T16:39:24.989Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cb/d9780b66939c4fc1f024bcc7be23a2abcfe06a9745ca8fa76dc73395482e/pymongo-4.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9543d8f84c2e5608565c08ac679774811e6730770d8a645439b073422a4276fb", size = 1058526, upload-time = "2026-04-20T16:39:27.924Z" }, -] - [[package]] name = "pymysql" version = "1.2.0"