Skip to content
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

Question: How to get the schema for the currently active view and/or serializer (for validation)? #1349

Open
TauPan opened this issue Dec 10, 2024 · 2 comments

Comments

@TauPan
Copy link

TauPan commented Dec 10, 2024

Historically we've been validating some parts of our API with JSON Schema and the API was documented via drf-yasg which wasn't compatible. Now I'm finally migrating to drf-spectacular and with OAS_VERSION=3.1.0 it's even fully compatible with JSON Schema.

This raises the question how we could use this for validation as well.

I've figured out that when I'm e.g. inside the .validate() method of a serializer, I can use self.context['view'].schema._map_serializer(self, 'request') to get the request schema which I could just plug into the validate method of a jsonschema validator.

Problems I see:

  • I can't see any documentation for the .schema property of the django-rest-framework view. Apparently drf-spectacular uses that as well, though.

  • The _map_serializer method is undocumented.

  • The real workflow in drf-spectacular (from the schema view) uses a generator object and is a bit more involved, however it accounts for the post processing hooks etc. which my hacky attempt above doesn't, which might be a problem if we change validation information in those hooks.

I'd really appreciate a simple way to get at the correct and intended (by the developers) scheme for a view and/or serializer from inside its code. Another use-case for this would be a custom metadata class which adds a schema property to the returned object which contains the valid json schema.

It's not unlikely I'm missing something relevant here so I'd appreciate assistance.

Are there other approaches for using the generated schema for validation?

Thanks in advance and best regards

@tfranzel
Copy link
Owner

Hi,

not exactly sure what you try to do here. How is schema validation gonna help during your request processing?

I can't see any documentation for the .schema property of the django-rest-framework view. Apparently drf-spectacular uses that as well, though.

its part of the mechanics DRF provides to make schema generation possible.

The _map_serializer method is undocumented.

I wouldn't call it undocumented. its a private method, but in the context of spectacular means: "one of the methods not meant to be overridden as part of the regular workflow, but no real reasons why you can't with some caution"

The real workflow in drf-spectacular (from the schema view) uses a generator object and is a bit more involved, however it accounts for the post processing hooks etc. which my hacky attempt above doesn't, which might be a problem if we change validation information in those hooks.

You can try generating a schema outside of the generator but your mileage may vary. there a bunch of things that get initialized there and there are no official guarantees when used like this. It may work or not. It is likely possible to beat it into submission and making it work, but again you are on your own there.

The metadata classes in DRF are messy and seldom used. I consider it a waste of time.

I do not recommend running it solo out-of-context on a view or endpoint. I would, if necessary, generate the whole schema once, cache it, and then pick out the stuff you need on a case by case basis.

@TauPan
Copy link
Author

TauPan commented Dec 11, 2024

Hi!

not exactly sure what you try to do here. How is schema validation gonna help during your request processing?

This is about validating incoming data, e.g. in POST or PUT requests.

My approach here is that what is documented should also be validated and vice versa. I'd like the information to be as close to the implementation as possible, which is why I adapt model fields first, if possible (which sometimes is not possible with 11 years of production data which might require expensive migrations if I change the model, or for backward compatibility with old clients) then serializer fields and if necessary (!) I use @extend_schema, @extend_serializer_field etc. (preferring the latter, because it operates at a lower level).

In the latter case I would have to write python code to enforce the validation, which could become inconsistent with the documentation. So I'd prefer as little duplication as possible and using the generated schema for data validation would close that gap nicely for me. (Also because we already use jsonschema for data validation anyways. Some of our data structures are recursive, which was not possible to express with openapi 2 and drf-yasg at all.)

I can't see any documentation for the .schema property of the django-rest-framework view. Apparently drf-spectacular uses that as well, though.

its part of the mechanics DRF provides to make schema generation possible.

👍 🙏

The _map_serializer method is undocumented.

I wouldn't call it undocumented. its a private method, but in the context of spectacular means: "one of the methods not meant to be overridden as part of the regular workflow, but no real reasons why you can't with some caution"

The real workflow in drf-spectacular (from the schema view) uses a generator object and is a bit more involved, however it accounts for the post processing hooks etc. which my hacky attempt above doesn't, which might be a problem if we change validation information in those hooks.

You can try generating a schema outside of the generator but your mileage may vary. there a bunch of things that get initialized there and there are no official guarantees when used like this. It may work or not. It is likely possible to beat it into submission and making it work, but again you are on your own there.

Thanks for the explanation! I would not like to have my code break too often because the approach we use is unsupported.

The metadata classes in DRF are messy and seldom used. I consider it a waste of time.

You might be right here. We do have one client that uses the OPTIONS method because it was quicker for that particular purpose, but that lives in the same repository as the server, which makes it easy to coordinate releases here. Adapting the options method return value certainly is low priority for me.

I do not recommend running it solo out-of-context on a view or endpoint. I would, if necessary, generate the whole schema once, cache it, and then pick out the stuff you need on a case by case basis.

That does sound broadly like a feasible plan.

Would I call the view directly for all supported versions of my API? When I look at the SpectacularView class, I'm tempted to subclass it and add a method that just returns the result of generator.get_schema() for me and then cache it for my purposes.

It would look roughly like this:

    def _get_schema_response(self, request):
        return Response(
            data=self._get_schema_data(request)),
            headers={"Content-Disposition": f'inline; filename="{self._get_filename(request, version)}"'}
        )

    def _get_schema_data(self, request):
        # version specified as parameter to the view always takes precedence. after
        # that we try to source version through the schema view's own versioning_class.
        version = self.api_version or request.version or self._get_version_parameter(request)
        generator = self.generator_class(urlconf=self.urlconf, api_version=version, patterns=self.patterns)
        return generator.get_schema(request=request, public=self.serve_public)

(Edit: Yikes, that won't work that way because _get_schema_response needs version as well. I just did this in the github editor :-) You get the idea, I think.)

I would need to construct request objects for version 1 and 2 of our API.

I'm going to try that out. Thanks for the advice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants