diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5f9576..0f8ebf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - name: ๐Ÿ”ง setup uv uses: ./.github/uv - name: ๐Ÿงช pytest - run: uv run pytest --cov fastapi_async_sqla --cov-report=term-missing --cov-report=xml + run: uv run pytest --cov fastsqla --cov-report=term-missing --cov-report=xml - name: "๐Ÿ” codecov: upload test coverage" uses: codecov/codecov-action@v4.2.0 env: diff --git a/README.md b/README.md index 865e07e..d3d4376 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,218 @@ -# FastAPI-Async-SQLA +# ๐Ÿš€ FastSQLA -[![PyPI - Version](https://img.shields.io/pypi/v/FastAPI-Async-SQLA?color=brightgreen)](https://pypi.org/project/FastAPI-Async-SQLA/) +[![PyPI - Version](https://img.shields.io/pypi/v/FastSQLA?color=brightgreen)](https://pypi.org/project/FastSQLA/) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org) -[![codecov](https://codecov.io/gh/hadrien/fastapi-async-sqla/graph/badge.svg?token=XK3YT60MWK)](https://codecov.io/gh/hadrien/fastapi-async-sqla) +[![codecov](https://codecov.io/gh/hadrien/fastsqla/graph/badge.svg?token=XK3YT60MWK)](https://codecov.io/gh/hadrien/fastsqla) -FastAPI-Async-SQLA is an [SQLAlchemy] extension for [FastAPI]. It supports asynchronous -SQLAlchemy sessions using SQLAlchemy >= 2.0 and provides pagination support. +`FastSQLA` is an [`SQLAlchemy`] extension for [`FastAPI`]. +It supports asynchronous `SQLAlchemy` sessions and includes built-in custimizable +pagination. -# Installing +## Features -Using [pip](https://pip.pypa.io/): +
+ Automatic SQLAlchemy configuration at app startup. + + Using [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan): +```python +from fastapi import FastAPI +from fastsqla import lifespan + +app = FastAPI(lifespan=lifespan) +``` +
+
+ Async SQLAlchemy session as a FastAPI dependency. + +```python +... +from fastsqla import Session +from sqlalchemy import select +... + +@app.get("/heros") +async def get_heros(session:Session): + stmt = select(...) + result = await session.execute(stmt) + ... +``` +
+
+ Built-in pagination. + +```python +... +from fastsqla import Page, Paginate +from sqlalchemy import select +... + +@app.get("/heros", response_model=Page[HeroModel]) +async def get_heros(paginate:Paginate): + return paginate(select(Hero)) +``` +
+
+ Allows pagination customization. + +```python +... +from fastapi import new_pagination +... + +Paginate = new_pagination(min_page_size=5, max_page_size=500) + +@app.get("/heros", response_model=Page[HeroModel]) +async def get_heros(paginate:Paginate): + return paginate(select(Hero)) ``` -pip install fastapi-async-sqla +
+ +And more ... + + +## Installing + +Using [uv](https://docs.astral.sh/uv/): +```bash +uv add fastsqla ``` -# Quick Example +Using [pip](https://pip.pypa.io/): +``` +pip install fastsqla +``` -Assuming it runs against a DB with a table `user` with 2 columns `id` and `name`: +## Quick Example ```python -# main.py +# example.py +from http import HTTPStatus + from fastapi import FastAPI, HTTPException -from fastapi_async_sqla import Base, Item, Page, Paginate, Session, lifespan -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Mapped, mapped_column +from fastsqla import Base, Item, Page, Paginate, Session, lifespan app = FastAPI(lifespan=lifespan) -class User(Base): - __tablename__ = "user" +class Hero(Base): + __tablename__ = "hero" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + secret_identity: Mapped[str] -class UserIn(BaseModel): +class HeroBase(BaseModel): name: str + secret_identity: str -class UserModel(UserIn): +class HeroModel(HeroBase): + model_config = ConfigDict(from_attributes=True) id: int -@app.get("/users", response_model=Page[UserModel]) +@app.get("/heros", response_model=Page[HeroModel]) async def list_users(paginate: Paginate): - return await paginate(select(User)) - - -@app.get("/users/{user_id}", response_model=Item[UserModel]) -async def get_user(user_id: int, session: Session): - user = await session.get(User, user_id) - if user is None: - raise HTTPException(404) - return {"data": user} + return await paginate(select(Hero)) + + +@app.get("/heros/{hero_id}", response_model=Item[HeroModel]) +async def get_user(hero_id: int, session: Session): + hero = await session.get(Hero, hero_id) + if hero is None: + raise HTTPException(HTTPStatus.NOT_FOUND, "Hero not found") + return {"data": hero} + + +@app.post("/heros", response_model=Item[HeroModel]) +async def create_user(new_hero: HeroBase, session: Session): + hero = Hero(**new_hero.model_dump()) + session.add(hero) + try: + await session.flush() + except IntegrityError: + raise HTTPException(HTTPStatus.CONFLICT, "Duplicate hero name") + return {"data": hero} +``` +> [!NOTE] +> Sqlite is used for the sake of the example. +> FastSQLA is compatible with all async db drivers that SQLAlchemy is compatible with. -@app.post("/users", response_model=Item[UserModel]) -async def create_user(new_user: UserIn, session: Session): - user = User(**new_user.model_dump()) - session.add(user) - await session.flush() - return {"data": user} -``` +
+ Create an sqlite3 db: -Creating a db using `sqlite3`: ```bash sqlite3 db.sqlite < + +
+ Install dependencies & run the app + ```bash -pip install aiosqlite +pip install uvicorn aiosqlite fastsqla +sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn example:app ``` -Running the app: +
+ +Execute `GET /heros?offset=10`: + ```bash -sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn main:app +curl -X 'GET' \ +'http://127.0.0.1:8000/heros?offset=10&limit=10' \ +-H 'accept: application/json' +``` +Returns: +```json +{ + "data": [ + { + "name": "The Flash", + "secret_identity": "Barry Allen", + "id": 11 + }, + { + "name": "Green Lantern", + "secret_identity": "Hal Jordan", + "id": 12 + } + ], + "meta": { + "offset": 10, + "total_items": 12, + "total_pages": 2, + "page_number": 2 + } +} ``` -[aiosqlite]: https://github.com/omnilib/aiosqlite -[FastAPI]: https://fastapi.tiangolo.com/ -[SQLAlchemy]: http://sqlalchemy.org/ +[`FastAPI`]: https://fastapi.tiangolo.com/ +[`SQLAlchemy`]: http://sqlalchemy.org/ diff --git a/pyproject.toml b/pyproject.toml index 0401549..22cd72a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "FastAPI-Async-SQLA" +name = "FastSQLA" version = "0.2.3" -description = "SQLAlchemy extension for FastAPI with support for asynchronous SQLAlchemy sessions and pagination." +description = "SQLAlchemy extension for FastAPI that supports asynchronous sessions and includes built-in pagination." readme = "README.md" requires-python = ">=3.12" authors = [{ name = "Hadrien David", email = "bonjour@hadriendavid.com" }] @@ -33,18 +33,14 @@ classifiers = [ ] keywords = ["FastAPI", "SQLAlchemy", "AsyncIO"] license = { text = "MIT License" } -dependencies = [ - "fastapi>=0.115.6", - "sqlalchemy[asyncio]>=2.0.34,<3", - "structlog>=24.4.0", -] +dependencies = ["fastapi>=0.115.6", "sqlalchemy[asyncio]>=2.0.37", "structlog>=24.4.0"] [project.urls] -Homepage = "https://github.com/hadrien/fastapi-async-sqla" -Documentation = "https://github.com/hadrien/fastapi-async-sqla" -Repository = "https://github.com/hadrien/fastapi-async-sqla" -Issues = "https://github.com/hadrien/fastapi-async-sqla/issues" -Changelog = "https://github.com/hadrien/fastapi-async-sqla/releases" +Homepage = "https://github.com/hadrien/fastsqla" +Documentation = "https://github.com/hadrien/fastsqla" +Repository = "https://github.com/hadrien/fastsqla" +Issues = "https://github.com/hadrien/fastsqla/issues" +Changelog = "https://github.com/hadrien/fastsqla/releases" [tool.uv] package = true diff --git a/src/fastapi_async_sqla.py b/src/fastsqla.py similarity index 97% rename from src/fastapi_async_sqla.py rename to src/fastsqla.py index a5077f8..e8e6269 100644 --- a/src/fastapi_async_sqla.py +++ b/src/fastsqla.py @@ -40,7 +40,7 @@ class Base(DeclarativeBase, DeferredReflection): class State(TypedDict): - fastapi_async_sqla_engine: AsyncEngine + fastsqla_engine: AsyncEngine @asynccontextmanager @@ -60,7 +60,7 @@ async def lifespan(_) -> AsyncGenerator[State, None]: await logger.ainfo("Configured SQLAlchemy.") - yield {"fastapi_async_sqla_engine": engine} + yield {"fastsqla_engine": engine} SessionFactory.configure(bind=None) await engine.dispose() @@ -120,7 +120,7 @@ class Collection(BaseModel, Generic[T]): data: list[T] -class Page(Collection): +class Page(Collection[T]): meta: Meta diff --git a/tests/conftest.py b/tests/conftest.py index 1f9aea8..1d6335b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ async def session(engine): def tear_down(): from sqlalchemy.orm import clear_mappers - from fastapi_async_sqla import Base + from fastsqla import Base yield diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 12ca0bc..50707e6 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,7 +6,7 @@ @fixture def app(environ): - from fastapi_async_sqla import lifespan + from fastsqla import lifespan app = FastAPI(lifespan=lifespan) return app diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py index e39f38d..cc07211 100644 --- a/tests/integration/test_base.py +++ b/tests/integration/test_base.py @@ -17,7 +17,7 @@ async def setup_tear_down(engine): async def test_lifespan_reflects_user_table(environ): - from fastapi_async_sqla import Base, lifespan + from fastsqla import Base, lifespan class User(Base): __tablename__ = "user" diff --git a/tests/integration/test_pagination.py b/tests/integration/test_pagination.py index b6aaccf..acfc6f0 100644 --- a/tests/integration/test_pagination.py +++ b/tests/integration/test_pagination.py @@ -56,7 +56,7 @@ async def setup_tear_down(engine, faker): @fixture def app(app): - from fastapi_async_sqla import ( + from fastsqla import ( Base, Page, Paginate, diff --git a/tests/integration/test_session_dependency.py b/tests/integration/test_session_dependency.py index 100bb75..d3431e1 100644 --- a/tests/integration/test_session_dependency.py +++ b/tests/integration/test_session_dependency.py @@ -24,7 +24,7 @@ async def setup_tear_down(engine): @fixture def app(setup_tear_down, app): - from fastapi_async_sqla import Base, Item, Session + from fastsqla import Base, Item, Session class User(Base): __tablename__ = "user" diff --git a/tests/unit/test_lifespan.py b/tests/unit/test_lifespan.py index 6eb50ea..60f63e6 100644 --- a/tests/unit/test_lifespan.py +++ b/tests/unit/test_lifespan.py @@ -2,14 +2,14 @@ async def test_it_returns_state(environ): - from fastapi_async_sqla import lifespan + from fastsqla import lifespan async with lifespan(None) as state: - assert "fastapi_async_sqla_engine" in state + assert "fastsqla_engine" in state async def test_it_binds_an_sqla_engine_to_sessionmaker(environ): - from fastapi_async_sqla import SessionFactory, lifespan + from fastsqla import SessionFactory, lifespan assert SessionFactory.kw["bind"] is None @@ -22,7 +22,7 @@ async def test_it_binds_an_sqla_engine_to_sessionmaker(environ): async def test_it_fails_on_a_missing_sqlalchemy_url(monkeypatch): - from fastapi_async_sqla import lifespan + from fastsqla import lifespan monkeypatch.delenv("SQLALCHEMY_URL", raising=False) with raises(Exception) as raise_info: @@ -33,7 +33,7 @@ async def test_it_fails_on_a_missing_sqlalchemy_url(monkeypatch): async def test_it_fails_on_not_async_engine(monkeypatch): - from fastapi_async_sqla import lifespan + from fastsqla import lifespan monkeypatch.setenv("SQLALCHEMY_URL", "sqlite:///:memory:") with raises(Exception) as raise_info: diff --git a/tests/unit/test_open_session.py b/tests/unit/test_open_session.py index 06b7f86..74bf53f 100644 --- a/tests/unit/test_open_session.py +++ b/tests/unit/test_open_session.py @@ -11,7 +11,7 @@ def tablename(request): @fixture(autouse=True) async def setup_tear_down(engine, tablename): - from fastapi_async_sqla import SessionFactory + from fastsqla import SessionFactory async with engine.connect() as conn: await conn.execute(text(f"create table {tablename} (data text unique)")) @@ -22,7 +22,7 @@ async def setup_tear_down(engine, tablename): async def test_it_commits_on_success(engine, tablename): - from fastapi_async_sqla import open_session + from fastsqla import open_session async with open_session() as session: await session.execute(text(f"insert into {tablename} values ('OK')")) @@ -34,9 +34,9 @@ async def test_it_commits_on_success(engine, tablename): async def test_it_re_raises_when_committing_fails(): - from fastapi_async_sqla import open_session + from fastsqla import open_session - with patch("fastapi_async_sqla.SessionFactory") as SessionFactory: + with patch("fastsqla.SessionFactory") as SessionFactory: session = AsyncMock() session.commit.side_effect = Exception("Simulating a failure.") SessionFactory.return_value = session @@ -48,7 +48,7 @@ async def test_it_re_raises_when_committing_fails(): async def test_it_rollback_on_failure(engine, tablename): - from fastapi_async_sqla import open_session + from fastsqla import open_session with raises(Exception): async with open_session() as session: diff --git a/uv.lock b/uv.lock index e5c52b9..fa1d7ae 100644 --- a/uv.lock +++ b/uv.lock @@ -254,8 +254,8 @@ wheels = [ ] [[package]] -name = "fastapi-async-sqla" -version = "0.2.1" +name = "fastsqla" +version = "0.2.3" source = { editable = "." } dependencies = [ { name = "fastapi" }, @@ -283,7 +283,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "fastapi", specifier = ">=0.115.6" }, - { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.34,<3" }, + { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.37" }, { name = "structlog", specifier = ">=24.4.0" }, ] @@ -330,19 +330,35 @@ wheels = [ [[package]] name = "greenlet" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/17/14/3bddb1298b9a6786539ac609ba4b7c9c0842e12aa73aaa4d8d73ec8f8185/greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", size = 182013 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/2f/461615adc53ba81e99471303b15ac6b2a6daa8d2a0f7f77fd15605e16d5b/greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", size = 273085 }, - { url = "https://files.pythonhosted.org/packages/e9/55/2c3cfa3cdbb940cf7321fbcf544f0e9c74898eed43bf678abf416812d132/greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", size = 660514 }, - { url = "https://files.pythonhosted.org/packages/38/77/efb21ab402651896c74f24a172eb4d7479f9f53898bd5e56b9e20bb24ffd/greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", size = 674295 }, - { url = "https://files.pythonhosted.org/packages/74/3a/92f188ace0190f0066dca3636cf1b09481d0854c46e92ec5e29c7cefe5b1/greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", size = 669395 }, - { url = "https://files.pythonhosted.org/packages/63/0f/847ed02cdfce10f0e6e3425cd054296bddb11a17ef1b34681fa01a055187/greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", size = 670455 }, - { url = "https://files.pythonhosted.org/packages/bd/37/56b0da468a85e7704f3b2bc045015301bdf4be2184a44868c71f6dca6fe2/greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", size = 625692 }, - { url = "https://files.pythonhosted.org/packages/7c/68/b5f4084c0a252d7e9c0d95fc1cfc845d08622037adb74e05be3a49831186/greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", size = 1152597 }, - { url = "https://files.pythonhosted.org/packages/a4/fa/31e22345518adcd69d1d6ab5087a12c178aa7f3c51103f6d5d702199d243/greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", size = 1181043 }, - { url = "https://files.pythonhosted.org/packages/53/80/3d94d5999b4179d91bcc93745d1b0815b073d61be79dd546b840d17adb18/greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", size = 293635 }, +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/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 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/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 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/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 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, ] [[package]] @@ -906,23 +922,31 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.34" +version = "2.0.37" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/fa/ca0fdd7b6b0cf53a8237a8ee7e487f8be16e4a2ee6d840d6e8e105cd9c86/sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22", size = 9556527 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/76/62eb5c62593d6d351f17202aa532f17b91c51b1b04e24a3a97530cb6118e/SQLAlchemy-2.0.34-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e", size = 2089191 }, - { url = "https://files.pythonhosted.org/packages/8a/7c/d43a14aef45bcb196f017ba2783eb3e42dd4c65c43be8b9f29bb5ec7d131/SQLAlchemy-2.0.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812", size = 2079662 }, - { url = "https://files.pythonhosted.org/packages/b7/25/ec59e5d3643d49d57ae59a62b6e5b3da39344617ce249f2561bfb4ac0458/SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2", size = 3229161 }, - { url = "https://files.pythonhosted.org/packages/fd/2e/e6129761dd5588a5623c6051c31e45935b72a5b17ed87b209e39a0b2a25c/SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b", size = 3240054 }, - { url = "https://files.pythonhosted.org/packages/70/08/4f994445215d7932bf2a490570fef9a5d1ba42cdf1cc9c48a6f7f04d1cfc/SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74", size = 3175538 }, - { url = "https://files.pythonhosted.org/packages/5e/19/4d4cc024cd7d50e25bf1c1ba61974b2b6e2fab8ea22f1569c47380b34e95/SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83", size = 3202149 }, - { url = "https://files.pythonhosted.org/packages/87/02/7ada4b6bfd5421aa7d65bd0ee9d76acc15b53ae26378b2ab8bba1ba3f78f/SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580", size = 2059547 }, - { url = "https://files.pythonhosted.org/packages/ad/fc/d1315ddb8529c768789954350268cd53167747649ddb709517c5e0a15c7e/SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a", size = 2085274 }, - { url = "https://files.pythonhosted.org/packages/09/14/5c9b872fba29ccedeb905d0a5c203ad86287b8bb1bb8eda96bfe8a05f65b/SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f", size = 1880671 }, +sdist = { url = "https://files.pythonhosted.org/packages/3b/20/93ea2518df4d7a14ebe9ace9ab8bb92aaf7df0072b9007644de74172b06c/sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb", size = 9626249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/62/e5de4a5e0c4f5ceffb2b461aaa2378c0ee00642930a8c38e5b80338add0f/SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef", size = 2102692 }, + { url = "https://files.pythonhosted.org/packages/01/44/3b65f4f16abeffd611da0ebab9e3aadfca45d041a78a67835c41c6d28289/SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4", size = 2093079 }, + { url = "https://files.pythonhosted.org/packages/a4/d8/e3a6622e86e3ae3a41ba470d1bb095c1f2dedf6b71feae0b4b94b5951017/SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4", size = 3242509 }, + { url = "https://files.pythonhosted.org/packages/3a/ef/5a53a6a60ac5a5d4ed28959317dac1ff72bc16773ccd9b3fe79713fe27f3/SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd", size = 3253368 }, + { url = "https://files.pythonhosted.org/packages/67/f2/30f5012379031cd5389eb06455282f926a4f99258e5ee5ccdcea27f30d67/SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098", size = 3188655 }, + { url = "https://files.pythonhosted.org/packages/fe/df/905499aa051605aeda62c1faf33d941ffb7fda291159ab1c24ef5207a079/SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb", size = 3215281 }, + { url = "https://files.pythonhosted.org/packages/94/54/f2769e7e356520f75016d82ca43ed85e47ba50e636a34124db4625ae5976/SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761", size = 2072972 }, + { url = "https://files.pythonhosted.org/packages/c2/7f/241f059e0b7edb85845368f43964d6b0b41733c2f7fffaa993f8e66548a5/SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff", size = 2098597 }, + { url = "https://files.pythonhosted.org/packages/45/d1/e63e56ceab148e69f545703a74b90c8c6dc0a04a857e4e63a4c07a23cf91/SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658", size = 2097968 }, + { url = "https://files.pythonhosted.org/packages/fd/e5/93ce63310347062bd42aaa8b6785615c78539787ef4380252fcf8e2dcee3/SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb", size = 2088445 }, + { url = "https://files.pythonhosted.org/packages/1b/8c/d0e0081c09188dd26040fc8a09c7d87f539e1964df1ac60611b98ff2985a/SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4", size = 3174880 }, + { url = "https://files.pythonhosted.org/packages/79/f7/3396038d8d4ea92c72f636a007e2fac71faae0b59b7e21af46b635243d09/SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94", size = 3188226 }, + { url = "https://files.pythonhosted.org/packages/ef/33/7a1d85716b29c86a744ed43690e243cb0e9c32e3b68a67a97eaa6b49ef66/SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0", size = 3121425 }, + { url = "https://files.pythonhosted.org/packages/27/11/fa63a77c88eb2f79bb8b438271fbacd66a546a438e4eaba32d62f11298e2/SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6", size = 3149589 }, + { url = "https://files.pythonhosted.org/packages/b6/04/fcdd103b6871f2110460b8275d1c4828daa806997b0fa5a01c1cd7fd522d/SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2", size = 2070746 }, + { url = "https://files.pythonhosted.org/packages/d4/7c/e024719205bdc1465b7b7d3d22ece8e1ad57bc7d76ef6ed78bb5f812634a/SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2", size = 2094612 }, + { url = "https://files.pythonhosted.org/packages/3b/36/59cc97c365f2f79ac9f3f51446cae56dfd82c4f2dd98497e6be6de20fb91/SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1", size = 1894113 }, ] [package.optional-dependencies]