-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
AutoSchema fails when set on detail_route #5630
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
Comments
Hi @axnsan12. Thanks for the report (and the test-case). This use-case isn't currently supported. It's not documented that it should work. But it does seem like a good enhancement to be able to customise the schema for the extra action decorators declaratively. Current workaround will be to subclass Related #5621 (comment): We should Extract Method on the logic to adjust the generated fields list... |
Most class options work when set on a Upon looking closer, this seems to be related to A more concrete description of what I mean: class ViewInspector(object):
"""
Descriptor class on APIView.
Provide subclass for per-view schema generation
"""
def __get__(self, instance, owner):
"""
Enables `ViewInspector` as a Python _Descriptor_.
This is how `view.schema` knows about `view`.
`__get__` is called when the descriptor is accessed on the owner.
(That will be when view.schema is called in our case.)
`owner` is always the owner class. (An APIView, or subclass for us.)
`instance` is the view instance or `None` if accessed from the class,
rather than an instance.
See: https://docs.python.org/3/howto/descriptor.html for info on
descriptor usage.
"""
real_schema = getattr(instance, '_real_schema', self)
real_schema.view = instance
return real_schema
def __set__(self, instance, other):
"""
`__set__` is called when the descriptor is set on the owner.
(That will be when `view.schema = other` is called in our case.)
`other` is always the new value being set (most likely a ViewInspector,
or subclass for us.)
`instance` is the view instance or `None` if accessed from the class,
rather than an instance.
If __get__ is defined without defining __set__, this object becomes a
`non-data descriptor`, which has lower lookup priority than an object's
__dict__. This means that calls to setattr(instance, 'schema', other)
would cause the descriptor logic to be bypassed altogether. It is
necessary to also implement __set__ to become a `data descriptor` and
prevent this.
From the python documentation link above:
* data descriptors always override instance dictionaries.
* non-data descriptors may be overridden by instance dictionaries.
This essentially enables the replacing of a view's schema by setting
it on an instance, and is necessary to support e.g. setting the schema
on a detail_route declaration.
"""
if isinstance(other, ViewInspector):
instance._real_schema = other This works and passes the test, but feels a bit hackish and confusing. |
Hey @axnsan12. Yep. I can see why you'd think this may work. (Agreed it would be a nice enhancement.) No problem, in theory, adding a If you want to expand #5631 to include a more featured test case where the user is setting manual fields via the view's We can then think about an implementation. Just to note now: it may be that we turn down adding this — sticking with the subclassing approach — depending on whether it turns out suitably nice™. Similar to that is per-method custom fields, as per #5621. There the current best option is to allow people to handle their own case in a subclass without trying to do everything out-of-the-box. The subclasses are simple; the general case has many edge-cases to consider; thus the cost-benefit is on the side on not providing that. Obviously all such judgements are open to re-assesment in the face of a nice implementation. 🙂 |
Hello, I see now that this is a bit more complicated than I imagined, from a design point of view. I will come back to this after I play with views/schema generation and understand it better. |
Note for later: Make sure to see the test case in #5631 |
After further thought I think you may be right that special casing this might not be worth it, but there are arguments both for and against. Here are some of my thoughts on this:
The deeper problem, though, is that a So, overall I feel like making |
Hi @axnsan12. Thanks for your comments. Very useful. Part of this is down to view sets... — you're right that we need to switch on action + method, and that's a bit funcky. However, that is just view sets. (If you pushed hard enough you might find me saying "Don't use them — yeah they're great for getting up and running but..." — part of that is related to this kind of thing...) (Something about explicit is better than... 🙂) I don't see a barrier to having The question for me would be how best to provide the hooks to make that extensible because... We deliberately avoided adding more API to APIView. The single We'll keep this open. I'll see what I can think up for v3.8 in the new year.
I hear where you're coming from: It looks a lot nicer if you're using separate views instead of viewsets (see opening points.) Prior to v3.7 we had a single API-wide object doing if-else-if-else on the whole API. That's why it was impossible to customise. Bar these points about Viewsets, I think single object inspecting single view is about right. (Again, we really didn't want to bundle the reflection logic into |
Hello again, And what about
Do you think that would be too much API footprint? (as in, 1 attribute and 1 method vs just 1 attribute)... for the common case you would not have to override both, just like a lot of the time you can get away with |
Hello, any update? still get this error. Currently I have to use this:
|
This is currently a known limitation. "PRs welcome" 🙂 |
I'll take a stab at this in 5605. In general, that PR aims for greater framework support for extra viewset actions. I'm not incredibly familiar with core-api, but at the very least, 5606 should provide the methods necessary to implement schema support. |
So this isn't within the scope of 5605, but I did take a few minutes to look at this. Worth noting, the below fails at the class MyViewSet(GenericViewSet):
detail_schema = AutoSchema(...)
@detail_route(schema=detail_schema)
def a_detail_route(self, request, pk, **kwargs)
... The problem with the It wouldn't be too difficult to deprecate/change this:
|
btw, I'm happy to whip this together if the |
OK, we should at least look at it. (The same idea has come up a course le of times.) Minimal, what does that look like? Followed by a fix for detail route. |
@rpkilby Thinking about this quickly, I think my favoured option would be to just add a
(Do we need params? We have I'll leave it with you. We can discuss further on a PR. 👍 |
Hello! Tried it and yeah, seems to solve the issue. Great to see this implemented! 😄 |
Checklist
master
branch of Django REST framework.Steps to reproduce
Expected behavior
A schema is generated for the detail route using the given AutoSchema.
Actual behavior
Crashes:
AttributeError: 'AutoSchema' object has no attribute '_view'
The text was updated successfully, but these errors were encountered: