Extend pydantic models to also detect and record changes made to the model attributes.
Project description
Pydantic change detection
Installation
Just use pip install pydantic-changedetect
to install the library.
Note: pydantic-changedetect
is compatible with pydantic
versions 1.9
, 1.10
and even 2.x
(🥳) on
Python 3.8
, 3.9
, 3.10
and 3.11
. This is also ensured running all tests on all those versions
using tox
.
About
When working with database models it is pretty common to want to detect changes
to the model attributes. The ChangeDetectionMixin
just provides this mechanism
to any pydantic models. Changes will be detected and stored after the model
was constructed.
Using the ChangeDetectionMixin
the pydantic models are extended, so:
obj.model_changed_fields
contains a list of all changed fieldsobj.model_self_changed_fields
contains a list of all changed fields for the current object, ignoring all nested models.obj.model_changed_fields_recursive
contains a list of all changed fields and also include the named of the fields changed in nested models using a dotted field name syntax (likenested.field
).
obj.model_original
will include the original values of all changed fields in a dict.obj.model_has_changed
returns True if any field has changed.obj.model_set_changed()
manually sets fields as changed.obj.model_set_changed("field_a", "field_b")
will set multiple fields as changed.obj.model_set_changed("field_a", original="old")
will set a single field as changed and also store its original value.
obj.model_reset_changed()
resets all changed fields.obj.model_dump()
andobj.model_dump_json()
accept an additional parameterexclude_unchanged
, which - when set to True - will only export the changed fields.
Note: When using pydantic 1.x you need to useobj.dict()
andobj.json()
. Both also acceptexclude_unchanged
.obj.model_restore_original()
will create a new instance of the model containing its original state.obj.model_get_original_field_value("field_name")
will return the original value for just one field. It will callmodel_restore_original()
on the current field value if the field is set to aChangeDetectionMixin
instance (or list/dict of those).obj.model_mark_changed("marker_name")
andobj.model_unmark_changed("marker_name")
allow to add arbitrary change markers. An instance with a marker will be seen as changed (obj.model_has_changed == True
). Markers are stored inobj.model_changed_markers
as a set.
Example
import pydantic
from pydantic_changedetect import ChangeDetectionMixin
class Something(ChangeDetectionMixin, pydantic.BaseModel):
name: str
something = Something(name="something")
something.model_has_changed # = False
something.model_changed_fields # = set()
something.name = "something else"
something.model_has_changed # = True
something.model_changed_fields # = {"name"}
original = something.model_restore_original()
original.name # = "something"
original.model_has_changed # = False
When will a change be detected
pydantic-changedetect
will see changes when an attribute value is changes using an
attribute assignment like something.name = "something else"
in the example above. Such
an assignment will be seen as a change unless the new value is the same and the type of
the value is None
or of type str
, int
, float
, bool
or decimal.Decimal
.
Restrictions
ChangeDetectionMixin
currently cannot detect changes inside lists, dicts and
other structured objects. In those cases you are required to set the changed
state yourself using model_set_changed()
. It is recommended to pass the original
value to model_set_changed()
when you want to also keep track of the actual changes
compared to the original value. Be advised to .copy()
the original value
as lists/dicts will always be changed in place.
import pydantic
from pydantic_changedetect import ChangeDetectionMixin
class TodoList(ChangeDetectionMixin, pydantic.BaseModel):
items: list[str]
todos = TodoList(items=["release new version"])
original_items = todos.items.copy()
todos.items.append("create better docs") # This change will NOT be seen yet
todos.model_has_changed # = False
todos.model_set_changed("items", original=original_items) # Mark field as changed and store original value
todos.model_has_changed # = True
Changed markers
You may also just mark the model as changed. This can be done using changed markers. A change marker is just a string that is added as the marker, models with such an marker will also be seen as changed. Changed markers also allow to mark models as changed when related data was changed - for example to also update a parent object in the database when some children were changed.
import pydantic
from pydantic_changedetect import ChangeDetectionMixin
class Something(ChangeDetectionMixin, pydantic.BaseModel):
name: str
something = Something(name="something")
something.model_has_changed # = False
something.model_mark_changed("mood")
something.model_has_changed # = True
something.model_changed_markers # {"mood"}
something.model_unmark_changed("mood") # also will be reset on something.model_reset_changed()
something.model_has_changed # = False
Contributing
If you want to contribute to this project, feel free to just fork the project, create a dev branch in your fork and then create a pull request (PR). If you are unsure about whether your changes really suit the project please create an issue first, to talk about this.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for pydantic_changedetect-0.6.2.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 55bfe858c12ca33c4075b57b1a911a8adb25a3cbafda6cc1042e080052195460 |
|
MD5 | a8d7df0759846b59ec2e519998303db1 |
|
BLAKE2b-256 | af83c04a3893713f87d74fb387878fa84b09d0968ff6b87b80346347f37cc21e |
Hashes for pydantic_changedetect-0.6.2-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 045d6b39b20d4655443b51d9a00f6ffaeafb7c7c13f70e69494e8892dcec2b99 |
|
MD5 | 0bab4ee70f912d71494a64cf390ec61e |
|
BLAKE2b-256 | 365d412598a858eda5d79066dee2e2f53e0e71341d9d4f7718c2af752f46d312 |