fluent-alchemy


Namefluent-alchemy JSON
Version 0.0.2 PyPI version JSON
download
home_pagehttps://github.com/turisesonia/fluent-alchemy
SummaryUse SQLAlchemy fluently
upload_time2024-04-09 07:37:06
maintainerNone
docs_urlNone
authorSam Yao
requires_python<4.0,>=3.8
licenseMIT
keywords sqlalchemy sqlalchemy activerecord active record
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Fluent Alchemy

輕鬆流暢地使用 SQLAclhemy

- [Installation](#installation)
- [Quick start](#quick-start)
- [Features](#features)
- [Mixins](#mixins)
- [Examples](#examples)

## Installation

```shell
pip insall fluent-alchemy
```

## Quick start

1. 宣告 Models 並繼承 `ActiveRecord`

```python
# models.py
from sqlalchemy.orm import DeclarativeBase
from fluent_alchemy import ActiveRecord

class Base(DeclarativeBase, ActiveRecord):
    pass

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(BigInteger(), primary_key=True)
    email: Mapped[str] = mapped_column(String(), unique=True)
    password: Mapped[str] = mapped_column(String())
    name: Mapped[str] = mapped_column(String(50))
    state: Mapped[bool] = mapped_column(Boolean())

```

2. 建立 SQLAclhemy Engine, 並指派給 `ActiveRecord` 內的 session handler

```python
from sqlalchemy import create_engine
from models import Base

engine = create_engine("sqlite://:memory:", echo=True)

Base.set_engine(engine)
```

3. 開始使用 model 操作 database !

```python
from models import User

users = User.all()
```

4. 使用完畢後,釋放 Session 資源

```python
from models import Base

Base.remove_scoped_session()
```

## Features

### Active Record
利用 `QueryBuilder` 來處理 SQLAlchemy 的 `select()` query statement

- Create

    ```python
    from models import User

    user = User.create(
        name="Justin",
        email="corey97@example.org",
        password="NioWe9Wn#+"
    )

    # or

    user = User(
        name="Justin",
        email="corey97@example.org",
        password="NioWe9Wn#+"
    )
    user.save()
    ```

- Read
    1. Find by id

        ```python
        user = User.find(1)
        ```

    2. 只回傳特定欄位

        ```python
        user = User.select(User.id, User.name, User.email).first()
        ```

    3. 透過 `where` 增加查詢條件

        ```python
        user = User.where(User.email == "corey97@example.org").first()
        ```

        ```python
        users = User.where(User.state.is_(True)).get()
        ```

- Update

    ```python
    user = User.find(1)
    user.passwod = "6xjVAY$p&D"

    user.save()
    ```

- Delete

    ```python
    user = User.find(1)
    user.delete()
    ```

- Pagenate

    ```python
    # setting page number and rows count per page
    pagination = User.paginate(page=1, per_page=15)

    """
    {
        "total": 100,
        "per_page": 15,
        "current_page": 1,
        "last_page": 7,
        "data": [ ... ], # ussers
    }
    """
    ```


## Mixins

### [`TimestampMixin`](./fluent_alchemy/mixins/timestamp.py)

讓指定的 Model class 繼承 `TimestampMixin`,讓該 Model 補上 `created_at`, `updated_at` 欄位。

```python
from fluent_alchemy import ActiveRecord, TimestampMixin

class Base(DeclarativeBase, ActiveRecord):
    pass

class User(Base, TimestampMixin):
    __tablename__ = "users"
    ...

###
user = User.find(1)

print(user.created_at)
print(user.updated_at)
```


### [`SoftDeleteMixin`](./fluent_alchemy/mixins/softdelete.py)

讓指定的 Model class 繼承 `SoftDeleteMixin`,就可以讓該 Model 擁有 Soft delete 的能力。

```python
from sqlalchemy.orm import DeclarativeBase
from fluent_alchemy import ActiveRecord, SoftDeleteMixin

class Base(DeclarativeBase, ActiveRecord):
    pass

class User(Base, SoftDeleteMixin):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(BigInteger(), primary_key=True)
    email: Mapped[str] = mapped_column(String(), unique=True)
    password: Mapped[str] = mapped_column(String())
    name: Mapped[str] = mapped_column(String(50))
    state: Mapped[bool] = mapped_column(Boolean())
```

`SoftDeleteMixin` 會自動補上 `deleted_at` 欄位,依此欄位來處理 soft delete 的資料。

```python
deleted_at: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(), nullable=True)
```

設定完成後,之後對此 Model 進行 Query 時,會在 statement 內的 WHERE 條件自動加上 `deleted_at IS NULL`。

#### 查詢已被標記刪除的資料
```python
users_deleted = User.where(...).get(with_trashed=True)
```

#### 強制刪除 (Force delete)
```python
user = User.find(1)

user.delete(force=True)
```

## Examples

### 在 FastAPI 內使用

```python

from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from app.models import BaseModel, User

def close_scoped_session():
    """
    Remove the scoped session at the enf of every request.
    """
    yield
    BaseModel.remove_scoped_session()

@asynccontextmanager
async def lifespan(app: FastAPI):
    """
    Set engine to the ScopedSessionHandler when FastAPI app started.
    """
    BaseModel.set_engine(engine)
    yield

app = FastAPI(
    title="MyApp",
    dependencies=[Depends(close_scoped_session)],
    lifespan=lifespan
)

@app.get("/users")
def index():
    return User.all()

```
            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/turisesonia/fluent-alchemy",
    "name": "fluent-alchemy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8",
    "maintainer_email": null,
    "keywords": "sqlalchemy, SQLAlchemy, activerecord, Active Record",
    "author": "Sam Yao",
    "author_email": "turisesonia@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/e7/27/4bca5f51e6ccadbf686f02ce1814b52d2458548f366934c4ab61a755dd0e/fluent_alchemy-0.0.2.tar.gz",
    "platform": null,
    "description": "# Fluent Alchemy\n\n\u8f15\u9b06\u6d41\u66a2\u5730\u4f7f\u7528 SQLAclhemy\n\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Features](#features)\n- [Mixins](#mixins)\n- [Examples](#examples)\n\n## Installation\n\n```shell\npip insall fluent-alchemy\n```\n\n## Quick start\n\n1. \u5ba3\u544a Models \u4e26\u7e7c\u627f `ActiveRecord`\n\n```python\n# models.py\nfrom sqlalchemy.orm import DeclarativeBase\nfrom fluent_alchemy import ActiveRecord\n\nclass Base(DeclarativeBase, ActiveRecord):\n    pass\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(BigInteger(), primary_key=True)\n    email: Mapped[str] = mapped_column(String(), unique=True)\n    password: Mapped[str] = mapped_column(String())\n    name: Mapped[str] = mapped_column(String(50))\n    state: Mapped[bool] = mapped_column(Boolean())\n\n```\n\n2. \u5efa\u7acb SQLAclhemy Engine, \u4e26\u6307\u6d3e\u7d66 `ActiveRecord` \u5167\u7684 session handler\n\n```python\nfrom sqlalchemy import create_engine\nfrom models import Base\n\nengine = create_engine(\"sqlite://:memory:\", echo=True)\n\nBase.set_engine(engine)\n```\n\n3. \u958b\u59cb\u4f7f\u7528 model \u64cd\u4f5c database !\n\n```python\nfrom models import User\n\nusers = User.all()\n```\n\n4. \u4f7f\u7528\u5b8c\u7562\u5f8c\uff0c\u91cb\u653e Session \u8cc7\u6e90\n\n```python\nfrom models import Base\n\nBase.remove_scoped_session()\n```\n\n## Features\n\n### Active Record\n\u5229\u7528 `QueryBuilder` \u4f86\u8655\u7406 SQLAlchemy \u7684 `select()` query statement\n\n- Create\n\n    ```python\n    from models import User\n\n    user = User.create(\n        name=\"Justin\",\n        email=\"corey97@example.org\",\n        password=\"NioWe9Wn#+\"\n    )\n\n    # or\n\n    user = User(\n        name=\"Justin\",\n        email=\"corey97@example.org\",\n        password=\"NioWe9Wn#+\"\n    )\n    user.save()\n    ```\n\n- Read\n    1. Find by id\n\n        ```python\n        user = User.find(1)\n        ```\n\n    2. \u53ea\u56de\u50b3\u7279\u5b9a\u6b04\u4f4d\n\n        ```python\n        user = User.select(User.id, User.name, User.email).first()\n        ```\n\n    3. \u900f\u904e `where` \u589e\u52a0\u67e5\u8a62\u689d\u4ef6\n\n        ```python\n        user = User.where(User.email == \"corey97@example.org\").first()\n        ```\n\n        ```python\n        users = User.where(User.state.is_(True)).get()\n        ```\n\n- Update\n\n    ```python\n    user = User.find(1)\n    user.passwod = \"6xjVAY$p&D\"\n\n    user.save()\n    ```\n\n- Delete\n\n    ```python\n    user = User.find(1)\n    user.delete()\n    ```\n\n- Pagenate\n\n    ```python\n    # setting page number and rows count per page\n    pagination = User.paginate(page=1, per_page=15)\n\n    \"\"\"\n    {\n        \"total\": 100,\n        \"per_page\": 15,\n        \"current_page\": 1,\n        \"last_page\": 7,\n        \"data\": [ ... ], # ussers\n    }\n    \"\"\"\n    ```\n\n\n## Mixins\n\n### [`TimestampMixin`](./fluent_alchemy/mixins/timestamp.py)\n\n\u8b93\u6307\u5b9a\u7684 Model class \u7e7c\u627f `TimestampMixin`\uff0c\u8b93\u8a72 Model \u88dc\u4e0a `created_at`, `updated_at` \u6b04\u4f4d\u3002\n\n```python\nfrom fluent_alchemy import ActiveRecord, TimestampMixin\n\nclass Base(DeclarativeBase, ActiveRecord):\n    pass\n\nclass User(Base, TimestampMixin):\n    __tablename__ = \"users\"\n    ...\n\n###\nuser = User.find(1)\n\nprint(user.created_at)\nprint(user.updated_at)\n```\n\n\n### [`SoftDeleteMixin`](./fluent_alchemy/mixins/softdelete.py)\n\n\u8b93\u6307\u5b9a\u7684 Model class \u7e7c\u627f `SoftDeleteMixin`\uff0c\u5c31\u53ef\u4ee5\u8b93\u8a72 Model \u64c1\u6709 Soft delete \u7684\u80fd\u529b\u3002\n\n```python\nfrom sqlalchemy.orm import DeclarativeBase\nfrom fluent_alchemy import ActiveRecord, SoftDeleteMixin\n\nclass Base(DeclarativeBase, ActiveRecord):\n    pass\n\nclass User(Base, SoftDeleteMixin):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(BigInteger(), primary_key=True)\n    email: Mapped[str] = mapped_column(String(), unique=True)\n    password: Mapped[str] = mapped_column(String())\n    name: Mapped[str] = mapped_column(String(50))\n    state: Mapped[bool] = mapped_column(Boolean())\n```\n\n`SoftDeleteMixin` \u6703\u81ea\u52d5\u88dc\u4e0a `deleted_at` \u6b04\u4f4d\uff0c\u4f9d\u6b64\u6b04\u4f4d\u4f86\u8655\u7406 soft delete \u7684\u8cc7\u6599\u3002\n\n```python\ndeleted_at: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(), nullable=True)\n```\n\n\u8a2d\u5b9a\u5b8c\u6210\u5f8c\uff0c\u4e4b\u5f8c\u5c0d\u6b64 Model \u9032\u884c Query \u6642\uff0c\u6703\u5728 statement \u5167\u7684 WHERE \u689d\u4ef6\u81ea\u52d5\u52a0\u4e0a `deleted_at IS NULL`\u3002\n\n#### \u67e5\u8a62\u5df2\u88ab\u6a19\u8a18\u522a\u9664\u7684\u8cc7\u6599\n```python\nusers_deleted = User.where(...).get(with_trashed=True)\n```\n\n#### \u5f37\u5236\u522a\u9664 (Force delete)\n```python\nuser = User.find(1)\n\nuser.delete(force=True)\n```\n\n## Examples\n\n### \u5728 FastAPI \u5167\u4f7f\u7528\n\n```python\n\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI, Depends\nfrom app.models import BaseModel, User\n\ndef close_scoped_session():\n    \"\"\"\n    Remove the scoped session at the enf of every request.\n    \"\"\"\n    yield\n    BaseModel.remove_scoped_session()\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    \"\"\"\n    Set engine to the ScopedSessionHandler when FastAPI app started.\n    \"\"\"\n    BaseModel.set_engine(engine)\n    yield\n\napp = FastAPI(\n    title=\"MyApp\",\n    dependencies=[Depends(close_scoped_session)],\n    lifespan=lifespan\n)\n\n@app.get(\"/users\")\ndef index():\n    return User.all()\n\n```",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Use SQLAlchemy fluently",
    "version": "0.0.2",
    "project_urls": {
        "Documentation": "https://github.com/turisesonia/fluent-alchemy",
        "Homepage": "https://github.com/turisesonia/fluent-alchemy",
        "Repository": "https://github.com/turisesonia/fluent-alchemy"
    },
    "split_keywords": [
        "sqlalchemy",
        " sqlalchemy",
        " activerecord",
        " active record"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0bb8dd775f5300af462bdf4b18a8c352e3e6e654ee2142d6308c163fd6183a35",
                "md5": "13f0c909c7e0139ba1f602d59cafae4a",
                "sha256": "a70d3e0ca14696a36a40b17c62a3e7d8157bceb273c71bae4a959833ad383ff0"
            },
            "downloads": -1,
            "filename": "fluent_alchemy-0.0.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "13f0c909c7e0139ba1f602d59cafae4a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8",
            "size": 10372,
            "upload_time": "2024-04-09T07:37:04",
            "upload_time_iso_8601": "2024-04-09T07:37:04.115621Z",
            "url": "https://files.pythonhosted.org/packages/0b/b8/dd775f5300af462bdf4b18a8c352e3e6e654ee2142d6308c163fd6183a35/fluent_alchemy-0.0.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e7274bca5f51e6ccadbf686f02ce1814b52d2458548f366934c4ab61a755dd0e",
                "md5": "9883a1d4372a45cea28e084d94e2304c",
                "sha256": "9ffb786ef63d397c879675c3715a5c253a3ec549b14069585a7ca3fbfff029a9"
            },
            "downloads": -1,
            "filename": "fluent_alchemy-0.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "9883a1d4372a45cea28e084d94e2304c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.8",
            "size": 8446,
            "upload_time": "2024-04-09T07:37:06",
            "upload_time_iso_8601": "2024-04-09T07:37:06.110729Z",
            "url": "https://files.pythonhosted.org/packages/e7/27/4bca5f51e6ccadbf686f02ce1814b52d2458548f366934c4ab61a755dd0e/fluent_alchemy-0.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-09 07:37:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "turisesonia",
    "github_project": "fluent-alchemy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "fluent-alchemy"
}
        
Elapsed time: 0.22398s