-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracing source of QueriesDisabledError with nested serializers #50
Comments
Hello! Sorry for the slow reply on this and thanks for opening the issue! This is very clever and definitely solves a problem that I've experienced (I would normally fix it by commenting out serializer fields and then gradually adding them back until the error is raised). I'm not totally sure whether this should live inside zen-queries itself though. It's probably not something you'd want to be default behaviour because it's monkeying around with the internals of the serializer so much. It also feels maybe a bit out of scope: zen-queries tries to be quite minimal and this is quite a lot of complicated code to maintain. I'd like to preserve it somehow though. A few ideas, in no particular order:
Open to other suggestions and persuasions! Thanks again |
Thanks for the reply - and no worries!
I've used this trick as well, and in my opinion this offers a significant time savings over that method, especially when you're dealing with nested serializers.
Completely agree. I proposed this to demonstrate the result I'm looking for, not a solution that's ready to merge. It is a bit of a hack, since I had to copy
I agree, and since posting the issue I've actually added the following at the top: def to_representation(self, instance):
if not (settings.DEBUG and self.debug):
return super().to_representation(instance) When I get a
I can see where you're coming from, especially in trying to keep it as minimal as possible. However, it would be more convenient, and logical in my opinion, for it to be part of zen-queries, since developers will only ever run into I was initially going to say that if it were a separate library, it could be installed as a dev dependency. However, I'm pretty sure the way it works now, the mixin has to be included in all serializers or else you don't get logging at lower levels of nesting. If we could get it to the point where it did not have to be included on every serializer, then it could remain a dev dependency, and opted-in by adding the mixin to the root serializer when the error is raised. I like that better than adding the I'm open for whatever you think is best moving forward. |
Here's an alternate approach. This overrides UsageNormal:
After spotting a
Problem solved, switch back to normal:
Here's the new mixin, about 30 lines: PaperTrailMixinimport logging
class PaperTrailMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def upgrade_field_and_children_to_repr(field_name, field, indent_level):
indent = '. ' * indent_level
# for list serializers (many=True), override the child's to_repr instead:
_field = field.child if hasattr(field, 'child') else field
def wrap_to_representation(func):
def wrapper(instance):
logging.debug(f'{indent}{f"{field_name} --> "}{_field.__class__.__name__} | {instance.__class__.__name__} | {instance}')
try:
func(instance)
except Exception as e:
logging.debug(f'{indent} ===== {e.__class__.__name__} - {field_name} =====')
raise e
return wrapper
_field.to_representation = wrap_to_representation(_field.to_representation)
if hasattr(_field, 'fields'):
for child_field_name, child_field in _field.fields.items():
upgrade_field_and_children_to_repr(child_field_name, child_field, indent_level + 1)
upgrade_field_and_children_to_repr('ROOT', self, 0) And the output is similar: LogsROOT --> OrderSerializer | Order | 1
. id --> IntegerField | int | 1
. ordered_at --> DateTimeField | datetime | 2022-08-17 18:33:23.727706+00:00
. customer --> Summary | User | rockybalboa
. . id --> IntegerField | int | 4
. . get_full_name --> ReadOnlyField | str | Rocky Balboa
. received_by --> Summary | User | hulkhogan
. . id --> IntegerField | int | 3
. . get_full_name --> ReadOnlyField | str | Hulk Hogan
. delivered_by --> Summary | User | maverick
. . id --> IntegerField | int | 5
. . get_full_name --> ReadOnlyField | str | Maverick Mitchell
. order_pizzas --> OrderPizzaSerializer | OrderPizza | 1
. . id --> IntegerField | int | 1
. . pizza --> Summary | Pizza | Pepperoni
. . . id --> IntegerField | int | 2
. . . name --> CharField | str | Pepperoni
. ===== QueriesDisabledError - order_pizzas =====
===== QueriesDisabledError - ROOT =====
Internal Server Error: /api/v1/orders/
Traceback (most recent call last):
File "...\env\lib\site-packages\django\db\models\fields\related_descriptors.py", line 173, in __get__
rel_obj = self.field.get_cached_value(instance)
File "...\env\lib\site-packages\django\db\models\fields\mixins.py", line 15, in get_cached_value
return instance._state.fields_cache[cache_name]
KeyError: 'baked_by'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
raise QueriesDisabledError(sql)
zen_queries.decorators.QueriesDisabledError: SELECT * FROM "users_user" ... It's slightly less intuitive than the other approach in that it doesn't clearly log out the field that caused the error. You can pretty easily get there, if you know how to read it:
But I think it's better in every other way, so I could live with it if that's the only downside. My vote would be to include this in zen-queries, and not require a separate package to be installed. Probably add it to |
The QueriesDisabledViewMixin is great at preventing queries during serialization. Once it's raised, however, I've had difficulty tracing the root cause - mostly when working with deeply nested serializers that have the same model serialized in multiple places.
To demonstrate, I extended your Pizza/Toppings example to include several models with multiple foreign keys to the User model:
Models
Serializers and Viewsets
When I make a request to
/api/v1/orders/
, since I "forgot" to prefetchOrderPizza.baked_by
, it raises aQueriesDisabledError
. However, it just tells me that it's trying to query a User. Without digging into it, it's not readily apparent which field on which serializer is causing the issue:To solve this, I put together a "PaperTrailMixin" for each serializer:
PaperTrailMixin
When a QueriesDisabledError is raised, it logs the following:
With this it's much easier to pinpoint where I need to add prefetches or select_related's to solve the error.
Thanks!
The text was updated successfully, but these errors were encountered: