1
+ import copy
2
+ import time
3
+
1
4
import django
5
+ from django .conf import settings
2
6
from django .core .exceptions import ImproperlyConfigured
7
+ from django .db .backends .utils import logger
3
8
from django .utils .version import get_version_tuple
9
+ from pymongo .cursor import Cursor
4
10
5
11
6
12
def check_django_compatability ():
@@ -18,3 +24,89 @@ def check_django_compatability():
18
24
f"You must use the latest version of django-mongodb { A } .{ B } .x "
19
25
f"with Django { A } .{ B } .y (found django-mongodb { __version__ } )."
20
26
)
27
+
28
+
29
+ class CollectionDebugWrapper :
30
+ def __init__ (self , collection , db ):
31
+ self .collection = collection
32
+ self .db = db
33
+
34
+ def __getattr__ (self , attr ):
35
+ return getattr (self .collection , attr )
36
+
37
+ def profile_call (self , func , args = (), kwargs = None ):
38
+ start = time .monotonic ()
39
+ retval = func (* args , ** kwargs or {})
40
+ duration = time .monotonic () - start
41
+ return duration , retval
42
+
43
+ def log (self , op , duration , args , kwargs = None ):
44
+ msg = "(%.3f) %s"
45
+ args = " " .join (str (arg ) for arg in args )
46
+ operation = f"{ self .collection .name } .{ op } ({ args } )"
47
+ kwargs = {k : v for k , v in kwargs .items () if v }
48
+ if kwargs :
49
+ operation += f"; kwargs={ kwargs } "
50
+ if len (settings .DATABASES ) > 1 :
51
+ msg += f"; alias={ self .db .alias } "
52
+ self .db .queries_log .append (
53
+ {
54
+ "sql" : operation ,
55
+ "time" : "%.3f" % duration ,
56
+ }
57
+ )
58
+ logger .debug (
59
+ msg ,
60
+ duration ,
61
+ operation ,
62
+ extra = {
63
+ "duration" : duration ,
64
+ "sql" : operation ,
65
+ "kwargs" : kwargs ,
66
+ "alias" : self .db .alias ,
67
+ },
68
+ )
69
+
70
+ def find (self , * args , ** kwargs ):
71
+ return DebugCursor (self , self .collection , * args , ** kwargs )
72
+
73
+ def logging_wrapper (method ):
74
+ def wrapper (self , * args , ** kwargs ):
75
+ func = getattr (self .collection , method )
76
+ # Collection.insert_one() mutates args[0] (the document) by adding
77
+ # the _id. deepcopy() to avoid logging that version.
78
+ original_args = copy .deepcopy (args )
79
+ duration , retval = self .profile_call (func , args , kwargs )
80
+ self .log (method , duration , original_args , kwargs )
81
+ return retval
82
+
83
+ return wrapper
84
+
85
+ # These are the operations that this backend uses.
86
+ count_documents = logging_wrapper ("count_documents" )
87
+ insert_one = logging_wrapper ("insert_one" )
88
+ delete_many = logging_wrapper ("delete_many" )
89
+ update_many = logging_wrapper ("update_many" )
90
+
91
+ del logging_wrapper
92
+
93
+
94
+ class DebugCursor (Cursor ):
95
+ def __init__ (self , collection_wrapper , * args , ** kwargs ):
96
+ self .collection_wrapper = collection_wrapper
97
+ super ().__init__ (* args , ** kwargs )
98
+
99
+ def _refresh (self ):
100
+ super_method = super ()._refresh
101
+ if self ._Cursor__id is not None :
102
+ return super_method ()
103
+ # self.__id is None: first time the .find() iterator is
104
+ # entered. find() profiling happens here.
105
+ duration , retval = self .collection_wrapper .profile_call (super_method )
106
+ kwargs = {
107
+ "limit" : self ._Cursor__limit ,
108
+ "skip" : self ._Cursor__skip ,
109
+ "sort" : self ._Cursor__ordering ,
110
+ }
111
+ self .collection_wrapper .log ("find" , duration , [self ._Cursor__spec ], kwargs )
112
+ return retval
0 commit comments