Skip to content

Commit f5fb64d

Browse files
committed
Restore original comments and docstrings
1 parent 0926a5e commit f5fb64d

File tree

1 file changed

+37
-3
lines changed

1 file changed

+37
-3
lines changed

fieldsignals/signals.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,24 @@ def connect( # type: ignore[override]
4141
"""
4242

4343
if not apps.models_ready:
44+
# We require access to Model._meta.get_fields(), which isn't available yet.
45+
# (This error would be raised below anyway, but we want a more meaningful message)
4446
raise AppRegistryNotReady(
4547
"django-fieldsignals signals must be connected after the app cache is ready. "
4648
"Connect the signal in your AppConfig.ready() handler."
4749
)
4850

51+
# Validate arguments
52+
4953
if kwargs.get("weak", False):
54+
# TODO: weak refs? I'm hella confused.
55+
# We can't go passing our proxy receivers around as weak refs, since they're
56+
# defined as closures and hence don't exist by the time they're called.
57+
# However, we can probably make _make_proxy_receiver() create weakrefs to
58+
# the original receiver if required. Patches welcome
5059
raise NotImplementedError("This signal doesn't yet handle weak refs")
5160

61+
# Check it's a class, don't check if it's a model class (useful for tests)
5262
if not isinstance(sender, type):
5363
raise ValueError("sender should be a model class")
5464

@@ -74,6 +84,7 @@ def is_reverse_rel(f: Any) -> bool:
7484

7585
super().connect(proxy_receiver, sender=sender, weak=False, dispatch_uid=dispatch_uid)
7686

87+
### post_init : initialize the list of fields for each instance
7788
def post_init_closure(sender: type, instance: models.Model, **kwargs: Any) -> None:
7889
self.get_and_update_changed_fields(receiver, instance, resolved_fields)
7990

@@ -89,7 +100,10 @@ def connect_source_signals(self, sender: type) -> None:
89100
"""
90101
Connects the source signals required to trigger updates for this
91102
ChangedSignal.
103+
104+
(post_init has already been connected during __init__)
92105
"""
106+
# override in subclasses
93107
pass
94108

95109
def _make_proxy_receiver(
@@ -100,7 +114,8 @@ def _make_proxy_receiver(
100114
) -> Callable[..., Any]:
101115
"""
102116
Takes a receiver function and creates a closure around it that knows what fields
103-
to watch.
117+
to watch. The original receiver is called for an instance iff the value of
118+
at least one of the fields has changed since the last time it was called.
104119
"""
105120

106121
def pr(instance: Any, *args: Any, **kwargs: Any) -> None:
@@ -115,9 +130,9 @@ def pr(instance: Any, *args: Any, **kwargs: Any) -> None:
115130

116131
pr._original_receiver = receiver # type: ignore[attr-defined]
117132
pr._fields = fields # type: ignore[attr-defined]
133+
118134
pr.__doc__ = receiver.__doc__
119135
pr.__name__ = receiver.__name__
120-
121136
return pr
122137

123138
def get_and_update_changed_fields(
@@ -128,8 +143,18 @@ def get_and_update_changed_fields(
128143
) -> dict[str, tuple[Any, Any]]:
129144
"""
130145
Takes a receiver and a model instance, and a list of field instances.
131-
Returns a dict of changed fields: {"fieldname": ("old value", "new value")}
146+
Gets the old and new values for each of the given fields, and stores their
147+
new values for next time.
148+
149+
Returns a dict like this:
150+
{
151+
"fieldname1" : ("old value", "new value"),
152+
}
132153
"""
154+
# instance._fieldsignals_originals looks like this:
155+
# {
156+
# (id(<signal instance>), id(<receiver>)) : {"field_name": "old value",},
157+
# }
133158
key = (id(self), id(receiver))
134159
if not hasattr(instance, "_fieldsignals_originals"):
135160
instance._fieldsignals_originals = {}
@@ -143,13 +168,18 @@ def get_and_update_changed_fields(
143168
for field in fields:
144169
if field.attname in deferred_fields:
145170
continue
171+
# using value_from_object instead of getattr() means we don't traverse foreignkeys
146172
new_value = field.to_python(field.value_from_object(instance))
147173
old_value = originals.get(field.name, None)
148174
if old_value != new_value:
149175
if not isinstance(new_value, IMMUTABLE_TYPES_WHITELIST):
176+
# For mutable types, make a copy of the value before storing it.
177+
# Otherwise, the 'originals' dict may well get modified elsewhere, and
178+
# that's going to make change detection impossible
150179
new_value = deepcopy(new_value)
151180

152181
changed_fields[field.name] = (old_value, new_value)
182+
# now update, for next time
153183
originals[field.name] = new_value
154184
return changed_fields
155185

@@ -187,5 +217,9 @@ def connect_source_signals(self, sender: type) -> None:
187217
)
188218

189219

220+
### API:
221+
190222
pre_save_changed = PreSaveChangedSignal()
191223
post_save_changed = PostSaveChangedSignal()
224+
225+
# TODO other signals?

0 commit comments

Comments
 (0)