sqlalchemy-nested-mutable


Namesqlalchemy-nested-mutable JSON
Version 0.1.0 PyPI version JSON
download
home_pagehttps://github.com/wonderbeyond/sqlalchemy-nested-mutable
SummarySQLAlchemy Nested Mutable Types.
upload_time2023-06-06 09:13:54
maintainer
docs_urlNone
authorWonder
requires_python>=3.8,<4.0
licenseMIT
keywords sqlalchemy nested mutable types json
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            SQLAlchemy-Nested-Mutable
=========================

An advanced SQLAlchemy column type factory that helps map compound Python types (e.g. `list`, `dict`, *Pydantic Model* and their hybrids) to database types (e.g. `ARRAY`, `JSONB`),
And keep track of mutations in deeply nested data structures so that SQLAlchemy can emit proper *UPDATE* statements.

SQLAlchemy-Nested-Mutable is highly inspired by SQLAlchemy-JSON<sup>[[0]](https://github.com/edelooff/sqlalchemy-json)</sup><sup>[[1]](https://variable-scope.com/posts/mutation-tracking-in-nested-json-structures-using-sqlalchemy)</sup>.
However, it does not limit the mapped Python type to be `dict` or `list`.

---

## Why this package?

* By default, SQLAlchemy does not track in-place mutations for non-scalar data types
  such as `list` and `dict` (which are usually mapped with `ARRAY` and `JSON/JSONB`).

* Even though SQLAlchemy provides [an extension](https://docs.sqlalchemy.org/en/20/orm/extensions/mutable.html)
  to track mutations on compound objects, it's too shallow, i.e. it only tracks mutations on the first level of the compound object.

* There exists the [SQLAlchemy-JSON](https://github.com/edelooff/sqlalchemy-json) package
  to help track mutations on nested `dict` or `list` data structures.
  However, the db type is limited to `JSON(B)`.

* Also, I would like the mapped Python types can be subclasses of the Pydantic BaseModelModel,
  which have strong schemas, with the db type be schema-less JSON.


## Installation

```shell
pip install sqlalchemy-nested-mutable
```

## Usage

> NOTE the example below is first updated in `examples/user-addresses.py` and then updated here.

```python
from typing import Optional, List

import pydantic
import sqlalchemy as sa
from sqlalchemy.orm import Session, DeclarativeBase, Mapped, mapped_column
from sqlalchemy_nested_mutable import MutablePydanticBaseModel


class Base(DeclarativeBase):
    pass


class Addresses(MutablePydanticBaseModel):
    """A container for storing various addresses of users.

    NOTE: for working with pydantic model, use a subclass of `MutablePydanticBaseModel` for column mapping.
    However, the nested models (e.g. `AddressItem` below) should be direct subclasses of `pydantic.BaseModel`.
    """

    class AddressItem(pydantic.BaseModel):
        street: str
        city: str
        area: Optional[str]

    preferred: AddressItem
    work: Optional[AddressItem]
    home: Optional[AddressItem]
    others: List[AddressItem] = []


class User(Base):
    __tablename__ = "user_account"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(sa.String(30))
    addresses: Mapped[Addresses] = mapped_column(Addresses.as_mutable(), nullable=True)


engine = sa.create_engine("sqlite://")
Base.metadata.create_all(engine)

with Session(engine) as s:
    s.add(u := User(name="foo", addresses={"preferred": {"street": "bar", "city": "baz"}}))
    assert isinstance(u.addresses, MutablePydanticBaseModel)
    s.commit()

    u.addresses.preferred.street = "bar2"
    s.commit()
    assert u.addresses.preferred.street == "bar2"

    u.addresses.others.append(Addresses.AddressItem.parse_obj({"street": "bar3", "city": "baz3"}))
    s.commit()
    assert isinstance(u.addresses.others[0], Addresses.AddressItem)

    print(u.addresses.dict())
```

For more usage, please refer to the following test files:

* tests/test_mutable_list.py
* tests/test_mutable_dict.py
* tests/test_mutable_pydantic_type.py


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/wonderbeyond/sqlalchemy-nested-mutable",
    "name": "sqlalchemy-nested-mutable",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8,<4.0",
    "maintainer_email": "",
    "keywords": "SQLAlchemy,Nested,Mutable,Types,JSON",
    "author": "Wonder",
    "author_email": "wonderbeyond@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/a6/32/2640bff3cdedf612681d805c19dad2d47d0de6558390c63569d77ba204f8/sqlalchemy_nested_mutable-0.1.0.tar.gz",
    "platform": null,
    "description": "SQLAlchemy-Nested-Mutable\n=========================\n\nAn advanced SQLAlchemy column type factory that helps map compound Python types (e.g. `list`, `dict`, *Pydantic Model* and their hybrids) to database types (e.g. `ARRAY`, `JSONB`),\nAnd keep track of mutations in deeply nested data structures so that SQLAlchemy can emit proper *UPDATE* statements.\n\nSQLAlchemy-Nested-Mutable is highly inspired by SQLAlchemy-JSON<sup>[[0]](https://github.com/edelooff/sqlalchemy-json)</sup><sup>[[1]](https://variable-scope.com/posts/mutation-tracking-in-nested-json-structures-using-sqlalchemy)</sup>.\nHowever, it does not limit the mapped Python type to be `dict` or `list`.\n\n---\n\n## Why this package?\n\n* By default, SQLAlchemy does not track in-place mutations for non-scalar data types\n  such as `list` and `dict` (which are usually mapped with `ARRAY` and `JSON/JSONB`).\n\n* Even though SQLAlchemy provides [an extension](https://docs.sqlalchemy.org/en/20/orm/extensions/mutable.html)\n  to track mutations on compound objects, it's too shallow, i.e. it only tracks mutations on the first level of the compound object.\n\n* There exists the [SQLAlchemy-JSON](https://github.com/edelooff/sqlalchemy-json) package\n  to help track mutations on nested `dict` or `list` data structures.\n  However, the db type is limited to `JSON(B)`.\n\n* Also, I would like the mapped Python types can be subclasses of the Pydantic BaseModelModel,\n  which have strong schemas, with the db type be schema-less JSON.\n\n\n## Installation\n\n```shell\npip install sqlalchemy-nested-mutable\n```\n\n## Usage\n\n> NOTE the example below is first updated in `examples/user-addresses.py` and then updated here.\n\n```python\nfrom typing import Optional, List\n\nimport pydantic\nimport sqlalchemy as sa\nfrom sqlalchemy.orm import Session, DeclarativeBase, Mapped, mapped_column\nfrom sqlalchemy_nested_mutable import MutablePydanticBaseModel\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass Addresses(MutablePydanticBaseModel):\n    \"\"\"A container for storing various addresses of users.\n\n    NOTE: for working with pydantic model, use a subclass of `MutablePydanticBaseModel` for column mapping.\n    However, the nested models (e.g. `AddressItem` below) should be direct subclasses of `pydantic.BaseModel`.\n    \"\"\"\n\n    class AddressItem(pydantic.BaseModel):\n        street: str\n        city: str\n        area: Optional[str]\n\n    preferred: AddressItem\n    work: Optional[AddressItem]\n    home: Optional[AddressItem]\n    others: List[AddressItem] = []\n\n\nclass User(Base):\n    __tablename__ = \"user_account\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str] = mapped_column(sa.String(30))\n    addresses: Mapped[Addresses] = mapped_column(Addresses.as_mutable(), nullable=True)\n\n\nengine = sa.create_engine(\"sqlite://\")\nBase.metadata.create_all(engine)\n\nwith Session(engine) as s:\n    s.add(u := User(name=\"foo\", addresses={\"preferred\": {\"street\": \"bar\", \"city\": \"baz\"}}))\n    assert isinstance(u.addresses, MutablePydanticBaseModel)\n    s.commit()\n\n    u.addresses.preferred.street = \"bar2\"\n    s.commit()\n    assert u.addresses.preferred.street == \"bar2\"\n\n    u.addresses.others.append(Addresses.AddressItem.parse_obj({\"street\": \"bar3\", \"city\": \"baz3\"}))\n    s.commit()\n    assert isinstance(u.addresses.others[0], Addresses.AddressItem)\n\n    print(u.addresses.dict())\n```\n\nFor more usage, please refer to the following test files:\n\n* tests/test_mutable_list.py\n* tests/test_mutable_dict.py\n* tests/test_mutable_pydantic_type.py\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "SQLAlchemy Nested Mutable Types.",
    "version": "0.1.0",
    "project_urls": {
        "Documentation": "https://github.com/wonderbeyond/sqlalchemy-nested-mutable",
        "Homepage": "https://github.com/wonderbeyond/sqlalchemy-nested-mutable",
        "Repository": "https://github.com/wonderbeyond/sqlalchemy-nested-mutable"
    },
    "split_keywords": [
        "sqlalchemy",
        "nested",
        "mutable",
        "types",
        "json"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "944a85640f06fc5e00260bd238cbc39b11dc1048e9c8109279826a0ce1c6b655",
                "md5": "7afc30acdff8cf95753d2afd952bbdef",
                "sha256": "8df5c831d9b64971e73687cd73cbb8c851eb7cf87a28898f2c017fe6ce5c31df"
            },
            "downloads": -1,
            "filename": "sqlalchemy_nested_mutable-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "7afc30acdff8cf95753d2afd952bbdef",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8,<4.0",
            "size": 8332,
            "upload_time": "2023-06-06T09:13:53",
            "upload_time_iso_8601": "2023-06-06T09:13:53.447582Z",
            "url": "https://files.pythonhosted.org/packages/94/4a/85640f06fc5e00260bd238cbc39b11dc1048e9c8109279826a0ce1c6b655/sqlalchemy_nested_mutable-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a6322640bff3cdedf612681d805c19dad2d47d0de6558390c63569d77ba204f8",
                "md5": "3e8ac82ba938b36ce0c21a4610bdb332",
                "sha256": "3949080b389d5ea7b1fa035f5d1eb93b77149bfa7af33a54611c7db2bcb88892"
            },
            "downloads": -1,
            "filename": "sqlalchemy_nested_mutable-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "3e8ac82ba938b36ce0c21a4610bdb332",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8,<4.0",
            "size": 7537,
            "upload_time": "2023-06-06T09:13:54",
            "upload_time_iso_8601": "2023-06-06T09:13:54.419702Z",
            "url": "https://files.pythonhosted.org/packages/a6/32/2640bff3cdedf612681d805c19dad2d47d0de6558390c63569d77ba204f8/sqlalchemy_nested_mutable-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-06 09:13:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "wonderbeyond",
    "github_project": "sqlalchemy-nested-mutable",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "sqlalchemy-nested-mutable"
}
        
Elapsed time: 0.09258s