From 7fa75911cbcb14cd171fa729a1e706ead964d21f Mon Sep 17 00:00:00 2001 From: che_shuai Date: Mon, 26 Aug 2024 10:15:33 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=88=A0=E9=99=A4=E3=80=81=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=80=81=E6=9F=A5=E8=AF=A2=E6=94=AF=E6=8C=81=E4=B8=BB?= =?UTF-8?q?=E9=94=AE=E5=90=8D=E7=A7=B0=E9=9D=9EID,=E4=BB=8Esqlalchemy=20?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=AE=9A=E4=B9=89=E4=B8=AD=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E4=B8=BB=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlalchemy_crud_plus/crud.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/sqlalchemy_crud_plus/crud.py b/sqlalchemy_crud_plus/crud.py index 10abc80..d5ebaeb 100644 --- a/sqlalchemy_crud_plus/crud.py +++ b/sqlalchemy_crud_plus/crud.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from typing import Any, Generic, Iterable, Sequence, Type -from sqlalchemy import Row, RowMapping, Select, delete, select, update +from sqlalchemy import Row, RowMapping, Select, delete, select, update, inspect from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy_crud_plus.errors import MultipleResultsError @@ -13,6 +13,18 @@ class CRUDPlus(Generic[Model]): def __init__(self, model: Type[Model]): self.model = model + self.primary_key = self._get_primary_key() + + def _get_primary_key(self): + """ + Dynamically retrieve the primary key column(s) for the model. + """ + mapper = inspect(self.model) + primary_key = mapper.primary_key + if len(primary_key) == 1: + return primary_key[0] + else: + raise ValueError("Composite primary keys are not supported") async def create_model( self, @@ -69,7 +81,7 @@ async def select_model(self, session: AsyncSession, pk: int) -> Model | None: :param pk: The database primary key value. :return: """ - stmt = select(self.model).where(self.model.id == pk) + stmt = select(self.model).where(self.primary_key == pk) query = await session.execute(stmt) return query.scalars().first() @@ -166,7 +178,7 @@ async def update_model( instance_data = obj else: instance_data = obj.model_dump(exclude_unset=True) - stmt = update(self.model).where(self.model.id == pk).values(**instance_data) + stmt = update(self.model).where(self.primary_key == pk).values(**instance_data) result = await session.execute(stmt) if commit: await session.commit() @@ -218,7 +230,7 @@ async def delete_model( :param commit: If `True`, commits the transaction immediately. Default is `False`. :return: """ - stmt = delete(self.model).where(self.model.id == pk) + stmt = delete(self.model).where(self.primary_key == pk) result = await session.execute(stmt) if commit: await session.commit() From 66d8da3de8608534e5339f8c6782789374ac4370 Mon Sep 17 00:00:00 2001 From: che_shuai Date: Tue, 27 Aug 2024 09:46:07 +0800 Subject: [PATCH 2/4] custom error and update Document --- docs/usage/select_model.md | 7 +++++-- sqlalchemy_crud_plus/crud.py | 4 ++-- sqlalchemy_crud_plus/errors.py | 7 +++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/usage/select_model.md b/docs/usage/select_model.md index 4e6c45d..9dd7058 100644 --- a/docs/usage/select_model.md +++ b/docs/usage/select_model.md @@ -15,12 +15,15 @@ from pydantic import BaseModel from sqlalchemy_crud_plus import CRUDPlus -from sqlalchemy import DeclarativeBase as Base +from sqlalchemy import Mapped, mapped_column, DeclarativeBase as Base from sqlalchemy.ext.asyncio import AsyncSession class ModelIns(Base): # your sqlalchemy model + # define the primary_key + custom_id: Mapped[int] = mapped_column(init=False, primary_key=True, index=True, autoincrement=True) + pass @@ -30,6 +33,6 @@ class CreateIns(BaseModel): class CRUDIns(CRUDPlus[ModelIns]): - async def create(self, db: AsyncSession, pk: int) -> ModelIns: + async def select(self, db: AsyncSession, pk: int) -> ModelIns: return await self.select_model(db, pk) ``` diff --git a/sqlalchemy_crud_plus/crud.py b/sqlalchemy_crud_plus/crud.py index d5ebaeb..13cc378 100644 --- a/sqlalchemy_crud_plus/crud.py +++ b/sqlalchemy_crud_plus/crud.py @@ -5,7 +5,7 @@ from sqlalchemy import Row, RowMapping, Select, delete, select, update, inspect from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy_crud_plus.errors import MultipleResultsError +from sqlalchemy_crud_plus.errors import MultipleResultsError, CompositePrimaryKeysError from sqlalchemy_crud_plus.types import CreateSchema, Model, UpdateSchema from sqlalchemy_crud_plus.utils import apply_sorting, count, parse_filters @@ -24,7 +24,7 @@ def _get_primary_key(self): if len(primary_key) == 1: return primary_key[0] else: - raise ValueError("Composite primary keys are not supported") + raise CompositePrimaryKeysError("Composite primary keys are not supported") async def create_model( self, diff --git a/sqlalchemy_crud_plus/errors.py b/sqlalchemy_crud_plus/errors.py index eb9b884..5c66937 100644 --- a/sqlalchemy_crud_plus/errors.py +++ b/sqlalchemy_crud_plus/errors.py @@ -36,3 +36,10 @@ class MultipleResultsError(SQLAlchemyCRUDPlusException): def __init__(self, msg: str) -> None: super().__init__(msg) + + +class CompositePrimaryKeysError(SQLAlchemyCRUDPlusException): + """Error raised when a table have Composite primary keys.""" + + def __init__(self, msg: str) -> None: + super().__init__(msg) \ No newline at end of file From 00e6971cb67faaf2aaad9a0dad2500665130b6d4 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Tue, 27 Aug 2024 16:38:13 +0800 Subject: [PATCH 3/4] Update docs for primary key --- docs/advanced/primary_key.md | 16 +++++++++++++++- docs/usage/delete_model.md | 4 +++- docs/usage/select_model.md | 9 ++++----- docs/usage/update_model.md | 4 +++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/advanced/primary_key.md b/docs/advanced/primary_key.md index 6211408..6b21358 100644 --- a/docs/advanced/primary_key.md +++ b/docs/advanced/primary_key.md @@ -1,8 +1,22 @@ !!! note 主键参数命名 由于在 python 内部 id 的特殊性,我们设定 pk (参考 Django) 作为模型主键命名,所以在 crud 方法中,任何涉及到主键的地方,入参都为 `pk` - + ```py title="e.g." hl_lines="2" async def delete(self, db: AsyncSession, primary_key: int) -> int: return self.delete_model(db, pk=primary_key) ``` + +## 主键定义 + +!!! warning 自动主键 + + 我们在 SQLAlchemy CRUD Plus 内部通过 [inspect()](https://docs.sqlalchemy.org/en/20/core/inspection.html) 自动搜索表主键, + 而非强制绑定主键列必须命名为 id,感谢 [@DavidSche](https://github.com/DavidSche) 提供帮助 + +```py title="e.g." hl_lines="4" +class ModelIns(Base): + # your sqlalchemy model + # define your primary_key + custom_id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) +``` diff --git a/docs/usage/delete_model.md b/docs/usage/delete_model.md index bd626f1..45cbdc6 100644 --- a/docs/usage/delete_model.md +++ b/docs/usage/delete_model.md @@ -18,13 +18,15 @@ from pydantic import BaseModel from sqlalchemy_crud_plus import CRUDPlus +from sqlalchemy import Mapped, mapped_column from sqlalchemy import DeclarativeBase as Base from sqlalchemy.ext.asyncio import AsyncSession class ModelIns(Base): # your sqlalchemy model - pass + # define your primary_key + custom_id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) class CreateIns(BaseModel): diff --git a/docs/usage/select_model.md b/docs/usage/select_model.md index 9dd7058..960e168 100644 --- a/docs/usage/select_model.md +++ b/docs/usage/select_model.md @@ -15,16 +15,15 @@ from pydantic import BaseModel from sqlalchemy_crud_plus import CRUDPlus -from sqlalchemy import Mapped, mapped_column, DeclarativeBase as Base +from sqlalchemy import Mapped, mapped_column +from sqlalchemy import DeclarativeBase as Base from sqlalchemy.ext.asyncio import AsyncSession class ModelIns(Base): # your sqlalchemy model - # define the primary_key - custom_id: Mapped[int] = mapped_column(init=False, primary_key=True, index=True, autoincrement=True) - - pass + # define your primary_key + custom_id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) class CreateIns(BaseModel): diff --git a/docs/usage/update_model.md b/docs/usage/update_model.md index 4ba1bc6..6d3dddd 100644 --- a/docs/usage/update_model.md +++ b/docs/usage/update_model.md @@ -19,13 +19,15 @@ from pydantic import BaseModel from sqlalchemy_crud_plus import CRUDPlus +from sqlalchemy import Mapped, mapped_column from sqlalchemy import DeclarativeBase as Base from sqlalchemy.ext.asyncio import AsyncSession class ModelIns(Base): # your sqlalchemy model - pass + # define your primary_key + custom_id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) class UpdateIns(BaseModel): From 1804dff0c953032d23972b41e2db43974d25a3bb Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Tue, 27 Aug 2024 16:39:08 +0800 Subject: [PATCH 4/4] fix lint --- sqlalchemy_crud_plus/crud.py | 6 +++--- sqlalchemy_crud_plus/errors.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlalchemy_crud_plus/crud.py b/sqlalchemy_crud_plus/crud.py index 13cc378..2de200e 100644 --- a/sqlalchemy_crud_plus/crud.py +++ b/sqlalchemy_crud_plus/crud.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- from typing import Any, Generic, Iterable, Sequence, Type -from sqlalchemy import Row, RowMapping, Select, delete, select, update, inspect +from sqlalchemy import Row, RowMapping, Select, delete, inspect, select, update from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy_crud_plus.errors import MultipleResultsError, CompositePrimaryKeysError +from sqlalchemy_crud_plus.errors import CompositePrimaryKeysError, MultipleResultsError from sqlalchemy_crud_plus.types import CreateSchema, Model, UpdateSchema from sqlalchemy_crud_plus.utils import apply_sorting, count, parse_filters @@ -24,7 +24,7 @@ def _get_primary_key(self): if len(primary_key) == 1: return primary_key[0] else: - raise CompositePrimaryKeysError("Composite primary keys are not supported") + raise CompositePrimaryKeysError('Composite primary keys are not supported') async def create_model( self, diff --git a/sqlalchemy_crud_plus/errors.py b/sqlalchemy_crud_plus/errors.py index 5c66937..197c197 100644 --- a/sqlalchemy_crud_plus/errors.py +++ b/sqlalchemy_crud_plus/errors.py @@ -42,4 +42,4 @@ class CompositePrimaryKeysError(SQLAlchemyCRUDPlusException): """Error raised when a table have Composite primary keys.""" def __init__(self, msg: str) -> None: - super().__init__(msg) \ No newline at end of file + super().__init__(msg)