django-filthyfields 2.0.2__tar.gz → 2.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. {django_filthyfields-2.0.2/src/django_filthyfields.egg-info → django_filthyfields-2.0.3}/PKG-INFO +1 -1
  2. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/pyproject.toml +8 -4
  3. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3/src/django_filthyfields.egg-info}/PKG-INFO +1 -1
  4. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/filthyfields/filthyfields.py +22 -4
  5. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/LICENSE +0 -0
  6. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/MANIFEST.in +0 -0
  7. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/README.md +0 -0
  8. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/setup.cfg +0 -0
  9. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/setup.py +0 -0
  10. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/django_filthyfields.egg-info/SOURCES.txt +0 -0
  11. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/django_filthyfields.egg-info/dependency_links.txt +0 -0
  12. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/django_filthyfields.egg-info/requires.txt +0 -0
  13. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/django_filthyfields.egg-info/top_level.txt +0 -0
  14. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/filthyfields/__init__.py +0 -0
  15. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/filthyfields/_descriptor.c +0 -0
  16. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/filthyfields/_descriptor.py +0 -0
  17. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/filthyfields/compare.py +0 -0
  18. {django_filthyfields-2.0.2 → django_filthyfields-2.0.3}/src/filthyfields/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-filthyfields
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Tracking dirty fields on a Django model instance.
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: Homepage, https://github.com/oliverhaas/django-filthyfields
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-filthyfields"
3
- version = "2.0.2"
3
+ version = "2.0.3"
4
4
  description = "Tracking dirty fields on a Django model instance."
5
5
  readme = "README.md"
6
6
  license = "BSD-3-Clause"
@@ -36,9 +36,10 @@ Repository = "https://github.com/oliverhaas/django-filthyfields.git"
36
36
 
37
37
  [dependency-groups]
38
38
  dev = [
39
- "cython>=3.2.4",
39
+ "cython==3.2.4",
40
40
  "django-stubs==6.0.3",
41
41
  "mypy==1.20.2",
42
+ "pillow==12.2.0",
42
43
  "pre-commit==4.6.0",
43
44
  "pytest==9.0.3",
44
45
  "pytest-asyncio==1.3.0",
@@ -46,8 +47,8 @@ dev = [
46
47
  "pytest-django==4.12.0",
47
48
  "pytest-xdist==3.8.0",
48
49
  "ruff==0.15.12",
49
- "setuptools>=80.10.1",
50
- "ty==0.0.32",
50
+ "setuptools==82.0.1",
51
+ "ty==0.0.33",
51
52
  ]
52
53
  docs = ["mkdocs==1.6.1", "mkdocs-material==9.7.6", "mike==2.2.0"]
53
54
 
@@ -180,5 +181,8 @@ python_version = "3.14"
180
181
  strict = true
181
182
  plugins = ["mypy_django_plugin.main"]
182
183
 
184
+ [tool.ty.environment]
185
+ python-version = "3.14"
186
+
183
187
  [tool.django-stubs]
184
188
  django_settings_module = "tests.django_settings"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-filthyfields
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Tracking dirty fields on a Django model instance.
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: Homepage, https://github.com/oliverhaas/django-filthyfields
@@ -54,12 +54,26 @@ class _TrackingFieldFileMixin:
54
54
  """``FieldFile`` mixin that records ``save()`` / ``delete()`` into the instance's diff."""
55
55
 
56
56
  def save(self, name: str, content: File[Any], save: bool = True) -> None:
57
+ instance = self.instance # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
58
+ field_name = self.field.name # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
57
59
  old_name = self.name or "" # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
58
- super().save(name, content, save=save) # type: ignore[misc] # ty: ignore[unresolved-attribute]
60
+ # Suppress _FileDiffDescriptor tracking during super().save: Django's
61
+ # FieldFile.save mutates self.name then calls _set_instance_attribute,
62
+ # which (for ImageFieldFile) does setattr(instance, attname, content).
63
+ # That hits __set__ with a stale-relative-to-mixin baseline and writes
64
+ # a wrong entry to _state_diff. The mixin is the source of truth here.
65
+ # The set is left in __dict__ even when empty so a concurrent thread
66
+ # under free-threading can't pop it between our discard and another
67
+ # thread's setdefault — set.add/discard/membership are atomic.
68
+ inflight = instance.__dict__.setdefault("_dirty_filefield_inflight", set())
69
+ inflight.add(field_name)
70
+ try:
71
+ super().save(name, content, save=save) # type: ignore[misc] # ty: ignore[unresolved-attribute]
72
+ finally:
73
+ inflight.discard(field_name)
59
74
  new_name = self.name or "" # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
60
- instance = self.instance # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
61
75
  if not instance._state.adding:
62
- _track_file_change(instance, self.field.name, old_name, new_name) # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
76
+ _track_file_change(instance, field_name, old_name, new_name)
63
77
 
64
78
  def delete(self, save: bool = True) -> None:
65
79
  old_name = self.name or "" # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
@@ -102,9 +116,13 @@ class _FileDiffDescriptor(FileDescriptor):
102
116
  attname = self.field.attname
103
117
  field_name = self.field.name
104
118
 
119
+ # FieldFile.save sets a sentinel for the duration of super().save so the
120
+ # mixin owns tracking; we must not record a diff entry from this path.
121
+ inflight = d.get("_dirty_filefield_inflight")
105
122
  state = getattr(instance, "_state", None)
106
123
  should_track = (
107
- state is not None
124
+ (inflight is None or field_name not in inflight)
125
+ and state is not None
108
126
  and not state.adding
109
127
  and attname in d
110
128
  and _should_track_field(instance, field_name, attname)