1+ from __future__ import annotations
2+
13from copy import deepcopy
4+ from typing import TYPE_CHECKING , Any
25
36from django .apps import apps
47from django .core .exceptions import AppRegistryNotReady
5- from django .db .models .fields .related import ForeignObjectRel
68from django .db .models import signals as _signals
9+ from django .db .models .fields .related import ForeignObjectRel
710from django .dispatch import Signal
811
12+ if TYPE_CHECKING :
13+ from collections .abc import Callable , Hashable
14+
15+ from django .db import models
916
1017__all__ = ("pre_save_changed" , "post_save_changed" )
1118
1219
13- IMMUTABLE_TYPES_WHITELIST = tuple ([ tuple , frozenset , float , str , int ] )
20+ IMMUTABLE_TYPES_WHITELIST : tuple [ type , ...] = ( tuple , frozenset , float , str , int )
1421
1522
1623class ChangedSignal (Signal ):
@@ -19,7 +26,14 @@ class ChangedSignal(Signal):
1926 The given receiver is only called when one or more of the given fields has changed.
2027 """
2128
22- def connect (self , receiver , sender = None , fields = None , dispatch_uid = None , ** kwargs ):
29+ def connect ( # type: ignore[override]
30+ self ,
31+ receiver : Callable [..., Any ],
32+ sender : type [models .Model ] | None = None ,
33+ fields : list [str ] | None = None ,
34+ dispatch_uid : Hashable | None = None ,
35+ ** kwargs : Any ,
36+ ) -> None :
2337 """
2438 Connect a FieldSignal. Usage::
2539
@@ -28,7 +42,7 @@ def connect(self, receiver, sender=None, fields=None, dispatch_uid=None, **kwarg
2842
2943 if not apps .models_ready :
3044 # We require access to Model._meta.get_fields(), which isn't available yet.
31- # (This error would be raised below anyway, but we want to add a more meaningful message)
45+ # (This error would be raised below anyway, but we want a more meaningful message)
3246 raise AppRegistryNotReady (
3347 "django-fieldsignals signals must be connected after the app cache is ready. "
3448 "Connect the signal in your AppConfig.ready() handler."
@@ -48,40 +62,41 @@ def connect(self, receiver, sender=None, fields=None, dispatch_uid=None, **kwarg
4862 if not isinstance (sender , type ):
4963 raise ValueError ("sender should be a model class" )
5064
51- def is_reverse_rel (f ) :
65+ def is_reverse_rel (f : Any ) -> bool :
5266 return f .many_to_many or f .one_to_many or isinstance (f , ForeignObjectRel )
5367
5468 if fields is None :
55- fields = sender ._meta .get_fields ()
56- fields = [f for f in fields if not is_reverse_rel (f )]
69+ resolved_fields = sender ._meta .get_fields ()
70+ resolved_fields = [f for f in resolved_fields if not is_reverse_rel (f )]
5771 else :
58- fields = [f for f in sender ._meta .get_fields () if f .name in set (fields )]
59- for f in fields :
72+ resolved_fields = [f for f in sender ._meta .get_fields () if f .name in set (fields )]
73+ for f in resolved_fields :
6074 if is_reverse_rel (f ):
6175 raise ValueError (
6276 "django-fieldsignals doesn't handle reverse related fields "
63- "({f.name} is a {f.__class__.__name__})" . format ( f = f )
77+ f "({ f .name } is a { f .__class__ .__name__ } )"
6478 )
6579
66- if not fields :
80+ if not resolved_fields :
6781 raise ValueError ("fields must be non-empty" )
6882
69- proxy_receiver = self ._make_proxy_receiver (receiver , sender , fields )
83+ proxy_receiver = self ._make_proxy_receiver (receiver , sender , resolved_fields )
7084
71- super (ChangedSignal , self ).connect (
72- proxy_receiver , sender = sender , weak = False , dispatch_uid = dispatch_uid
73- )
85+ super ().connect (proxy_receiver , sender = sender , weak = False , dispatch_uid = dispatch_uid )
7486
7587 ### post_init : initialize the list of fields for each instance
76- def post_init_closure (sender , instance , ** kwargs ) :
77- self .get_and_update_changed_fields (receiver , instance , fields )
88+ def post_init_closure (sender : type , instance : models . Model , ** kwargs : Any ) -> None :
89+ self .get_and_update_changed_fields (receiver , instance , resolved_fields )
7890
7991 _signals .post_init .connect (
80- post_init_closure , sender = sender , weak = False , dispatch_uid = (self , receiver )
92+ post_init_closure ,
93+ sender = sender ,
94+ weak = False ,
95+ dispatch_uid = (self , receiver ), # type: ignore[arg-type]
8196 )
8297 self .connect_source_signals (sender )
8398
84- def connect_source_signals (self , sender ) :
99+ def connect_source_signals (self , sender : type ) -> None :
85100 """
86101 Connects the source signals required to trigger updates for this
87102 ChangedSignal.
@@ -91,30 +106,41 @@ def connect_source_signals(self, sender):
91106 # override in subclasses
92107 pass
93108
94- def _make_proxy_receiver (self , receiver , sender , fields ):
109+ def _make_proxy_receiver (
110+ self ,
111+ receiver : Callable [..., Any ],
112+ sender : type ,
113+ fields : list [Any ],
114+ ) -> Callable [..., Any ]:
95115 """
96116 Takes a receiver function and creates a closure around it that knows what fields
97117 to watch. The original receiver is called for an instance iff the value of
98118 at least one of the fields has changed since the last time it was called.
99119 """
100120
101- def pr (instance , * args , ** kwargs ):
102- changed_fields = self .get_and_update_changed_fields (
103- receiver , instance , fields
104- )
121+ def pr (instance : Any , * args : Any , ** kwargs : Any ) -> None :
122+ changed_fields = self .get_and_update_changed_fields (receiver , instance , fields )
105123 if changed_fields :
106124 receiver (
107- instance = instance , changed_fields = changed_fields , * args , ** kwargs
125+ * args ,
126+ instance = instance ,
127+ changed_fields = changed_fields ,
128+ ** kwargs ,
108129 )
109130
110- pr ._original_receiver = receiver
111- pr ._fields = fields
131+ pr ._original_receiver = receiver # type: ignore[attr-defined]
132+ pr ._fields = fields # type: ignore[attr-defined]
112133
113134 pr .__doc__ = receiver .__doc__
114135 pr .__name__ = receiver .__name__
115136 return pr
116137
117- def get_and_update_changed_fields (self , receiver , instance , fields ):
138+ def get_and_update_changed_fields (
139+ self ,
140+ receiver : Callable [..., Any ],
141+ instance : Any ,
142+ fields : list [Any ],
143+ ) -> dict [str , tuple [Any , Any ]]:
118144 """
119145 Takes a receiver and a model instance, and a list of field instances.
120146 Gets the old and new values for each of the given fields, and stores their
@@ -135,7 +161,7 @@ def get_and_update_changed_fields(self, receiver, instance, fields):
135161 if key not in instance ._fieldsignals_originals :
136162 instance ._fieldsignals_originals [key ] = {}
137163 originals = instance ._fieldsignals_originals [key ]
138- changed_fields = {}
164+ changed_fields : dict [ str , tuple [ Any , Any ]] = {}
139165
140166 deferred_fields = instance .get_deferred_fields ()
141167
@@ -159,24 +185,35 @@ def get_and_update_changed_fields(self, receiver, instance, fields):
159185
160186
161187class PreSaveChangedSignal (ChangedSignal ):
162- def _on_model_pre_save (self , sender , instance = None , ** kwargs ):
188+ def _on_model_pre_save (
189+ self , sender : type , instance : Any = None , ** kwargs : Any
190+ ) -> list [tuple [Callable [..., Any ], Any ]]:
163191 return self .send (sender , instance = instance )
164192
165- def connect_source_signals (self , sender ) :
193+ def connect_source_signals (self , sender : type ) -> None :
166194 _signals .pre_save .connect (
167- self ._on_model_pre_save , sender = sender , dispatch_uid = id (self )
195+ self ._on_model_pre_save ,
196+ sender = sender ,
197+ dispatch_uid = id (self ), # type: ignore[arg-type]
168198 )
169199
170200
171201class PostSaveChangedSignal (ChangedSignal ):
172202 def _on_model_post_save (
173- self , sender , instance = None , created = None , using = None , ** kwargs
174- ):
203+ self ,
204+ sender : type ,
205+ instance : Any = None ,
206+ created : bool | None = None ,
207+ using : str | None = None ,
208+ ** kwargs : Any ,
209+ ) -> list [tuple [Callable [..., Any ], Any ]]:
175210 return self .send (sender , instance = instance , created = created , using = using )
176211
177- def connect_source_signals (self , sender ) :
212+ def connect_source_signals (self , sender : type ) -> None :
178213 _signals .post_save .connect (
179- self ._on_model_post_save , sender = sender , dispatch_uid = id (self )
214+ self ._on_model_post_save ,
215+ sender = sender ,
216+ dispatch_uid = id (self ), # type: ignore[arg-type]
180217 )
181218
182219
0 commit comments