11from  contextlib  import  contextmanager 
2+ from  zlib  import  crc32 
23
3- from  django .db  import  connection , transaction 
4+ from  django .db  import  DEFAULT_DB_ALIAS ,  connection ,  connections , transaction 
45from  django .db .migrations .executor  import  MigrationExecutor 
56
67
@@ -25,3 +26,126 @@ def migrations_are_complete() -> bool:
2526    executor  =  MigrationExecutor (connection )
2627    plan  =  executor .migration_plan (executor .loader .graph .leaf_nodes ())
2728    return  not  bool (plan )
29+ 
30+ 
31+ # NOTE: the django_pglocks_advisory_lock context manager was forked from the django-pglocks v1.0.4 
32+ # that was licensed under the MIT license 
33+ 
34+ 
35+ @contextmanager  
36+ def  django_pglocks_advisory_lock (lock_id , shared = False , wait = True , using = None ):
37+ 
38+     if  using  is  None :
39+         using  =  DEFAULT_DB_ALIAS 
40+ 
41+     # Assemble the function name based on the options. 
42+ 
43+     function_name  =  'pg_' 
44+ 
45+     if  not  wait :
46+         function_name  +=  'try_' 
47+ 
48+     function_name  +=  'advisory_lock' 
49+ 
50+     if  shared :
51+         function_name  +=  '_shared' 
52+ 
53+     release_function_name  =  'pg_advisory_unlock' 
54+     if  shared :
55+         release_function_name  +=  '_shared' 
56+ 
57+     # Format up the parameters. 
58+ 
59+     tuple_format  =  False 
60+ 
61+     if  isinstance (
62+         lock_id ,
63+         (
64+             list ,
65+             tuple ,
66+         ),
67+     ):
68+         if  len (lock_id ) !=  2 :
69+             raise  ValueError ("Tuples and lists as lock IDs must have exactly two entries." )
70+ 
71+         if  not  isinstance (lock_id [0 ], int ) or  not  isinstance (lock_id [1 ], int ):
72+             raise  ValueError ("Both members of a tuple/list lock ID must be integers" )
73+ 
74+         tuple_format  =  True 
75+     elif  isinstance (lock_id , str ):
76+         # Generates an id within postgres integer range (-2^31 to 2^31 - 1). 
77+         # crc32 generates an unsigned integer in Py3, we convert it into 
78+         # a signed integer using 2's complement (this is a noop in Py2) 
79+         pos  =  crc32 (lock_id .encode ("utf-8" ))
80+         lock_id  =  (2 ** 31  -  1 ) &  pos 
81+         if  pos  &  2 ** 31 :
82+             lock_id  -=  2 ** 31 
83+     elif  not  isinstance (lock_id , int ):
84+         raise  ValueError ("Cannot use %s as a lock id"  %  lock_id )
85+ 
86+     if  tuple_format :
87+         base  =  "SELECT %s(%d, %d)" 
88+         params  =  (
89+             lock_id [0 ],
90+             lock_id [1 ],
91+         )
92+     else :
93+         base  =  "SELECT %s(%d)" 
94+         params  =  (lock_id ,)
95+ 
96+     acquire_params  =  (function_name ,) +  params 
97+ 
98+     command  =  base  %  acquire_params 
99+     cursor  =  connections [using ].cursor ()
100+ 
101+     cursor .execute (command )
102+ 
103+     if  not  wait :
104+         acquired  =  cursor .fetchone ()[0 ]
105+     else :
106+         acquired  =  True 
107+ 
108+     try :
109+         yield  acquired 
110+     finally :
111+         if  acquired :
112+             release_params  =  (release_function_name ,) +  params 
113+ 
114+             command  =  base  %  release_params 
115+             cursor .execute (command )
116+ 
117+         cursor .close ()
118+ 
119+ 
120+ @contextmanager  
121+ def  advisory_lock (* args , lock_session_timeout_milliseconds = 0 , ** kwargs ):
122+     """Context manager that wraps the pglocks advisory lock 
123+ 
124+     This obtains a named lock in postgres, idenfied by the args passed in 
125+     usually the lock identifier is a simple string. 
126+ 
127+     @param: wait If True, block until the lock is obtained 
128+     @param: shared Whether or not the lock is shared 
129+     @param: lock_session_timeout_milliseconds Postgres-level timeout 
130+     @param: using django database identifier 
131+     """ 
132+     if  connection .vendor  ==  "postgresql" :
133+         cur  =  None 
134+         idle_in_transaction_session_timeout  =  None 
135+         idle_session_timeout  =  None 
136+         if  lock_session_timeout_milliseconds  >  0 :
137+             with  connection .cursor () as  cur :
138+                 idle_in_transaction_session_timeout  =  cur .execute ("SHOW idle_in_transaction_session_timeout" ).fetchone ()[0 ]
139+                 idle_session_timeout  =  cur .execute ("SHOW idle_session_timeout" ).fetchone ()[0 ]
140+                 cur .execute ("SET idle_in_transaction_session_timeout = %s" , (lock_session_timeout_milliseconds ,))
141+                 cur .execute ("SET idle_session_timeout = %s" , (lock_session_timeout_milliseconds ,))
142+         with  django_pglocks_advisory_lock (* args , ** kwargs ) as  internal_lock :
143+             yield  internal_lock 
144+             if  lock_session_timeout_milliseconds  >  0 :
145+                 with  connection .cursor () as  cur :
146+                     cur .execute ("SET idle_in_transaction_session_timeout = %s" , (idle_in_transaction_session_timeout ,))
147+                     cur .execute ("SET idle_session_timeout = %s" , (idle_session_timeout ,))
148+     elif  connection .vendor  ==  "sqlite" :
149+         yield  True 
150+     else :
151+         raise  RuntimeError (f'Advisory lock not implemented for database type { connection .vendor }  ' )
0 commit comments