-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathutils.py
122 lines (103 loc) · 3.81 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import copy
import time
import django
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db.backends.utils import logger
from django.utils.version import get_version_tuple
def check_django_compatability():
"""
Verify that this version of django-mongodb is compatible with the
installed version of Django. For example, any django-mongodb 5.0.x is
compatible with Django 5.0.y.
"""
from . import __version__
if django.VERSION[:2] != get_version_tuple(__version__)[:2]:
A = django.VERSION[0]
B = django.VERSION[1]
raise ImproperlyConfigured(
f"You must use the latest version of django-mongodb {A}.{B}.x "
f"with Django {A}.{B}.y (found django-mongodb {__version__})."
)
def set_wrapped_methods(cls):
"""Initialize the wrapped methods on cls."""
if hasattr(cls, "logging_wrapper"):
for attr in cls.wrapped_methods:
setattr(cls, attr, cls.logging_wrapper(attr))
del cls.logging_wrapper
return cls
@set_wrapped_methods
class OperationDebugWrapper:
# The PyMongo database and collection methods that this backend uses.
wrapped_methods = {
"aggregate",
"create_collection",
"create_indexes",
"drop",
"index_information",
"insert_many",
"delete_many",
"drop_index",
"rename",
"update_many",
}
def __init__(self, db, collection=None):
self.collection = collection
self.db = db
use_collection = collection is not None
self.collection_name = f"{collection.name}." if use_collection else ""
self.wrapped = self.collection if use_collection else self.db.database
def __getattr__(self, attr):
return getattr(self.wrapped, attr)
def profile_call(self, func, args=(), kwargs=None):
start = time.monotonic()
retval = func(*args, **kwargs or {})
duration = time.monotonic() - start
return duration, retval
def log(self, op, duration, args, kwargs=None):
# If kwargs are used by any operations in the future, they must be
# added to this logging.
msg = "(%.3f) %s"
args = ", ".join(repr(arg) for arg in args)
operation = f"db.{self.collection_name}{op}({args})"
if len(settings.DATABASES) > 1:
msg += f"; alias={self.db.alias}"
self.db.queries_log.append(
{
"sql": operation,
"time": "%.3f" % duration,
}
)
logger.debug(
msg,
duration,
operation,
extra={
"duration": duration,
"sql": operation,
"alias": self.db.alias,
},
)
def logging_wrapper(method):
def wrapper(self, *args, **kwargs):
func = getattr(self.wrapped, method)
# Collection.insert_many() mutates args (the documents) by adding
# _id. deepcopy() to avoid logging that version.
original_args = copy.deepcopy(args)
duration, retval = self.profile_call(func, args, kwargs)
self.log(method, duration, original_args, kwargs)
return retval
return wrapper
@set_wrapped_methods
class OperationCollector(OperationDebugWrapper):
def __init__(self, collected_sql=None, *, collection=None, db=None):
super().__init__(db, collection)
self.collected_sql = collected_sql
def log(self, op, args, kwargs=None):
args = ", ".join(repr(arg) for arg in args)
operation = f"db.{self.collection_name}{op}({args})"
self.collected_sql.append(operation)
def logging_wrapper(method):
def wrapper(self, *args, **kwargs):
self.log(method, args, kwargs)
return wrapper