Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support list in non-nested mutable json #51

Merged
merged 3 commits into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ This is essentially the SQLAlchemy `mutable JSON recipe`_. We define a simple au
name = Column(Text)
handles = Column(MutableJson)

Or, using the declarative mapping style:

.. code-block:: python

class Category(Base):
__tablename__ = "categories"

id = mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] = mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] = mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] = mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate ``UPDATE`` statement is generated.

.. code-block:: python
Expand Down
19 changes: 17 additions & 2 deletions sqlalchemy_json/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy.ext.mutable import Mutable, MutableDict
from sqlalchemy.ext.mutable import Mutable, MutableDict, MutableList
from sqlalchemy.types import JSON

from .track import TrackedDict, TrackedList
Expand Down Expand Up @@ -51,13 +51,28 @@ def coerce(cls, key, value):
return super(cls).coerce(key, value)


class MutableListOrDict(Mutable):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should maintain 2 empty lines between definitions (and I guess I should really include black or flake8 with a plugin in the CI runs..)

iloveitaly marked this conversation as resolved.
Show resolved Hide resolved
"""SQLAlchemy `mutable` extension with change tracking for a single-depth list or dict."""

@classmethod
def coerce(cls, key, value):
if value is None:
return value
if isinstance(value, cls):
return value
if isinstance(value, dict):
return MutableDict.coerce(key, value)
if isinstance(value, list):
return MutableList.coerce(key, value)
return super(cls).coerce(key, value)

def mutable_json_type(dbtype=JSON, nested=False):
"""Type creator for (optionally nested) mutable JSON column types.

The default backend data type is sqlalchemy.types.JSON, but can be set to
any other type by providing the `dbtype` parameter.
"""
mutable_type = NestedMutable if nested else MutableDict
mutable_type = NestedMutable if nested else MutableListOrDict
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure on the name here, but that might be a bit of a wider concern, naming this NestedJson isn't possible as the name gets taken by the column type later (which I think is more generally useful in exports), but getting it to better capture its meaning and then align it with the Nested... variant would be nice.

Suggestions are welcome if you have any.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I couldn't come up with a better name either :/

return mutable_type.as_mutable(dbtype)


Expand Down
20 changes: 20 additions & 0 deletions test/test_sqlalchemy_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ def author(session):
assert author.handles["twitter"] == "@JohnDoe"
return author

@pytest.fixture
def author_with_list(session):
author = Author(
name="John Doe",
handles=["@JohnDoe", "JohnDoe"],
)
session.add(author)
session.commit()
session.refresh(author)

assert author.name == "John Doe"
assert author.handles == ["@JohnDoe", "JohnDoe"]
return author

@pytest.fixture
def article(session, author):
Expand Down Expand Up @@ -156,3 +169,10 @@ def test_dict_merging(session, article):
"someone/somerepo": 10,
},
}

def test_mutable_json_list(session, author_with_list):
author_with_list.handles.append("@mike_bianco")
session.commit()

assert author_with_list.handles == ["@JohnDoe", "JohnDoe", "@mike_bianco"]