tfranzel / drf-spectacular Goto Github PK
View Code? Open in Web Editor NEWSane and flexible OpenAPI 3 schema generation for Django REST framework.
Home Page: https://drf-spectacular.readthedocs.io
License: BSD 3-Clause "New" or "Revised" License
Sane and flexible OpenAPI 3 schema generation for Django REST framework.
Home Page: https://drf-spectacular.readthedocs.io
License: BSD 3-Clause "New" or "Revised" License
I have a class
class SessionAuthenticationWithHeader(SessionAuthentication):
"""
This class defines authenticate_header
which means django'll send 401 instead of 403
"""
def authenticate_header(self, request):
# that's non standard auth type so browser won't show default pop-up
# see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
return 'session realm="api"'
When I add this class in settings
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': (
'SessionAuthenticationWithHeader',
),
}
I get an error
Traceback (most recent call last):
File "./manage.py", line 15, in <module>
execute_from_command_line(sys.argv)
File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
utility.execute()
File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 395, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 328, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 369, in execute
output = self.handle(*args, **options)
File "/opt/drf_spectacular/management/commands/spectacular.py", line 33, in handle
schema = generator.get_schema(request=None, public=True)
File "/opt/drf_spectacular/openapi.py", line 116, in get_schema
'paths': self.parse(None if public else request),
File "/opt/drf_spectacular/openapi.py", line 84, in parse
operation = schema.get_operation(path, method, self.registry)
File "/opt/drf_spectacular/utils.py", line 103, in get_operation
return super().get_operation(path, method, registry)
File "/opt/drf_spectacular/openapi.py", line 158, in get_operation
auth = self.get_auth(path, method)
File "/opt/drf_spectacular/utils.py", line 121, in get_auth
return super().get_auth(path, method)
File "/opt/drf_spectacular/openapi.py", line 184, in get_auth
self.resolve_authentication(method, a) for a in self.view.get_authenticators()
File "/opt/drf_spectacular/openapi.py", line 184, in <listcomp>
self.resolve_authentication(method, a) for a in self.view.get_authenticators()
File "/opt/drf_spectacular/openapi.py", line 789, in resolve_authentication
raise ValueError('no auth scheme registered for {}'.format(authentication.__name__))
AttributeError: 'SessionAuthenticationWithHeader' object has no attribute '__name__'
spectacular apparently is used by a few people now. i'd like to get some feedback on an idea before doing potentially invasive schema changes.
Theoretically we could make all read-only fields required
. Which is imho a better representation of what DRF is actually doing. afaik DRF will always render a field even if it is null
. so it is indeed required
and not optional. note that this would only apply to responses and not requests.
It is an easy change, but i would like to get feedback beforehand. i don't want to break your schemas/clients. For our purposes it looks very good and the generated clients also honor it well.
i will create a pull request for you to check this out.
Actually, one more note about the EvidenceRelationship schema. I have further updated the serializer to return two different relationships in a response:
@extend_schema_field(serializers.ChoiceField(choices=EvidenceRelationship.choices))
def get_expert_consensus_relationship(self, obj: Evidence) -> EvidenceRelationship:
return self.get_consensus(obj, expert=True)
@extend_schema_field(serializers.ChoiceField(choices=EvidenceRelationship.choices))
def get_community_consensus_relationship(self, obj: Evidence) -> EvidenceRelationship:
return self.get_consensus(obj, expert=False)
This does work, but it's generating two separate enum types in the spec:
ExpertConsensusRelationshipEnum:
enum:
- PROVES
- SUPPORTS
- UNRELATED
- INCONCLUSIVE
- DISPUTES
- DISPROVES
- SPLIT
type: string
CommunityConsensusRelationshipEnum:
enum:
- PROVES
- SUPPORTS
- UNRELATED
- INCONCLUSIVE
- DISPUTES
- DISPROVES
- SPLIT
type: string
Is there any way to tell drf-spectacular that these are the same enum?
Originally posted by @KunstDerFuge in #68 (comment)
There are different versions of API in my current project.
Like /api/v{version}/...
Is it possible to give different serializers depending on the version of api? I saw a similar implementation in the framework fastapi-versioning
Is there any possible solution to this problem? I will be very grateful for your advice
Describe the bug
Very low priority, but worth starting to investigate..
/usr/lib/python3.8/site-packages/django_filters/rest_framework/backends.py:147: UserWarning: <class 'foo.views.FooReportView'> is not compatible with schema generation
warnings.warn(
If nothing else, the occurrence of these warnings is distracting, and not as informative as the warnings from drf-spectacular. Very likely they existing because of the way DRF core swagger support is/was built, and it is a useful warning for the benefit of people using the DRF core schema generation.
To Reproduce
I should be able to get a snippet if it is necessary, but it would probably be easier to replicate with a new sample meeting the criteria explained fairly easily based on the django-filters code which is getting triggered.
Expected behavior
Some of the time, but rarely it seems, drf-spectacular is doing a reasonable job despite django-filter saying there is a problem. I would need to study the generated schema more closely to understand this.
Other times, the warning from django-filters is appropriate, and drf-spectacular also emits a warning about the same thing.
Long term, the warning should be avoided because it is either redundant or incorrect. It could be avoided either by drf-spectacular pre-empting the same conditions and not invoking that code, or by filtering the warning in advance or reformatting it if it was an unexpected warning and include it in the warnings that drf-spectacular counts.
Hi
Thanks for this cool project.
I have a problem with the response scheme
I would like to see the support of the object in the answer. I will show the code, I hope you will understand what I want to see
from unittest import mock
import uuid
from django.contrib.auth.models import AbstractUser
from django.db import models
from rest_framework import routers, serializers, viewsets
from drf_spectacular.openapi import AutoSchema, SchemaGenerator
from drf_spectacular.renderers import NoAliasOpenAPIRenderer
class Album(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=100)
genre = models.CharField(
choices=(('POP', 'Pop'), ('ROCK', 'Rock')),
max_length=10
)
year = models.IntegerField()
released = models.BooleanField()
class Song(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
album = models.ForeignKey(Album, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
length = models.IntegerField()
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
class AuthorSerializer(serializers.ModelSerializer):
name = models.CharField(max_length=100)
class Meta:
model = User
fields = (
'id',
'name',
)
class SongSerializer(serializers.ModelSerializer):
top10 = serializers.SerializerMethodField()
author = serializers.SerializerMethodField()
class Meta:
fields = ['id', 'title', 'length', 'top10', 'author']
model = Song
def get_top10(self) -> bool:
return True
def get_author(self, obj):
return AuthorSerializer(obj.user).data
class AlbumSerializer(serializers.ModelSerializer):
songs = SongSerializer(many=True, read_only=True)
class Meta:
fields = '__all__'
model = Album
class AlbumModelViewset(viewsets.ModelViewSet):
serializer_class = AlbumSerializer
queryset = Album.objects.none()
def create(self, request, *args, **kwargs):
"""
Special documentation about creating albums
There is even more info here
"""
return super().create(request, *args, **kwargs)
@mock.patch('rest_framework.settings.api_settings.DEFAULT_SCHEMA_CLASS', AutoSchema)
def test_basics():
router = routers.SimpleRouter()
router.register('albums', AlbumModelViewset, basename="album")
generator = SchemaGenerator(patterns=router.urls)
schema = generator.get_schema(request=None, public=True)
schema_yml = NoAliasOpenAPIRenderer().render(schema, renderer_context={})
with open('test_basic.yml') as fh:
assert schema_yml.decode() == fh.read()
When I generate the schema, then author
filed is string, but should be Author object
Like this
Is there any way to solve this problem?
https://github.com/django-oscar/django-oscar-api/blob/e57b34b/oscarapi/urls.py says basket_pk
has type int
However I get warnings
WARNING #130: could not derive type of path parameter "basket_pk" because model "<class 'oscar.apps.basket.models.Line'>" did contain no such field. consider annotating parameter with @extend_schema. defaulting to "string".
...
WARNING #144: could not derive type of path parameter "basket_pk" because model "<class 'oscar.apps.basket.models.LineAttribute'>" did contain no such field. consider annotating parameter with @extend_schema. defaulting to "string".
OscarHyperlinkedModelSerializer
from https://github.com/django-oscar/django-oscar-api/blob/master/oscarapi/serializers/utils.py uses rest_framework.serializers.HyperlinkedModelSerializer
, but the problem seems to be a customisation
extra_url_kwargs
https://github.com/openwisp/django-rest-framework-gis causes warning
could not resolve serializer field GeometryField(allow_null=True, required=False). defaulting to "string"
It definitely isnt a string ;-)
https://pypi.org/project/django-phonenumber-field/ 2.0.1 (unfortunately locked to that by current django-oscar & django-oscar-api & django-oscar-api-checkout):
could not resolve serializer field ModelField(help_text='In case we need to call you about your order', model_field=<phonenumber_field.modelfields.PhoneNumberField: phone_number>, required=False, validators=[<function validate_international_phonenumber>, <django.core.validators.MaxLengthValidator object>]). defaulting to "string"
The master
versions should let me update to django-phonenumber-field v3, but poetry is making that difficult to upgrade with git (python-poetry/poetry#2336). Doing a fresh install to get around that.
As mentioned in #41 , https://github.com/dhcode/openapi-ui displays only "untagged" for drf-spectacular schema, because it doesnt have tags at the top level.
An example with tags in the top level can be seen on
Describe the bug
DRF DurationField is a string, but is showing a warning.
Warning #35: could not resolve serializer field DurationField(). defaulting to "string"
https://github.com/encode/django-rest-framework/blob/aed7496/rest_framework/fields.py#L1363
https://github.com/django/django/blob/1f817da/django/utils/dateparse.py#L125
Expected behavior
No warnings for core DRF fields.
Mental note for #27 (comment) ;-)
In my project I use this serializer
class CustomSerializer(serializers.ModelSerializer):
uid = serializers.RelatedField()
reply_to = serializers.RelatedField()
text = CharField()
And I would like to get the reply_to
parameter in the schema as int, but I got string
How can i do this?
It seems to me that one could add typing int
for reply_to
parameter
For example
class CustomSerializer(serializers.ModelSerializer):
uid = serializers.RelatedField()
reply_to:int = serializers.RelatedField()
text = CharField()
But it does not work
This is while processing django-oscar-api. Ideally the lack of a get_serializer is noticed once per view and only one warning is emitted.
WARNING #741: Exception raised while getting serializer from CheckoutView. Hint: Is get_serializer_class() returning None or is get_queryset() not working without a request? Ignoring the view for now. (Exception: type object 'CheckoutView' has no attribute 'get_serializer')
WARNING #742: Exception raised while getting serializer from CheckoutView. Hint: Is get_serializer_class() returning None or is get_queryset() not working without a request? Ignoring the view for now. (Exception: type object 'CheckoutView' has no attribute 'get_serializer')
WARNING #743: Exception raised while getting serializer from CheckoutView. Hint: Is get_serializer_class() returning None or is get_queryset() not working without a request? Ignoring the view for now. (Exception: type object 'CheckoutView' has no attribute 'get_serializer')
I am seeing
could not resolve model field "catalogue.ProductClass.slug" due to missing mapping.either your field is custom and not based on a known subclasses or we missed something. let us know.
I havent determined which class it is, but both are derived from django.db.models.SlugField
, which drf-spectacular might need a specialised handler for.
https://github.com/django-oscar/django-oscar/blob/2.0.4/src/oscar/models/fields/slugfield.py
or
https://github.com/django-oscar/django-oscar/blob/2.0.4/src/oscar/models/fields/autoslugfield.py
When I add another set of views which has a model that includes a status
attribute, the previously defined StatusEnum
is dropped and its values are moved inline to the pre-existing schemas where it was being referenced.
I can understand why that is happening, but it is a bit unusual to have components disappear when an extra unrelated API is added. These two APIs are separate - they are each only used wihin different tags. The name of the enum could be prefixed with the tag name so I had a OscarapiStatus
and a SubscriptionStatus
.
This will improve the search for the project in the github repositories, will bring more people to your project
I suggest:
openapi3
python
drf
swagger
django
I have several versioned schemas. How can I get schema-ui views for each version?
Describe the bug
(line numbers refer to master atm)
Traceback (most recent call last):
File "./manage.py", line 14, in <module>
main()
File "./manage.py", line 10, in main
execute_from_command_line(sys.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
utility.execute()
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 328, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 369, in execute
output = self.handle(*args, **options)
File "/usr/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 44, in handle
schema = generator.get_schema(request=None, public=True)
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 152, in get_schema
paths=self.parse(request, public),
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 132, in parse
operation = view.schema.get_operation(path, path_regex, method, self.registry)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 76, in get_operation
operation['responses'] = self._get_response_bodies()
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 790, in _get_response_bodies
return {'200': self._get_response_for_code(response_serializers)}
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 815, in _get_response_for_code
component = self.resolve_serializer(serializer, 'response')
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 887, in resolve_serializer
component.schema = self._map_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 560, in _map_serializer
schema = self._map_basic_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 614, in _map_basic_serializer
schema = self._map_serializer_field(field, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 397, in _map_serializer_field
schema = self._map_serializer_field(field.child_relation, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 409, in _map_serializer_field
schema = self._map_model_field(field.parent.Meta.model._meta.pk, direction)
AttributeError: 'ManyRelatedField' object has no attribute 'Meta'
To Reproduce
WritableNestedModelSerializer
is from https://github.com/beda-software/drf-writable-nested/blob/master/drf_writable_nested/serializers.py , but isnt related afaics.
class Foo(models.Model):
name = models.CharField(max_length=255)
class Segment(models.Model):
name = models.CharField(max_length=255)
foo = models.ForeignKey(
'Foo',
on_delete=models.CASCADE,
related_name='segments',
null=True,
)
class SegmentSerializer(WritableNestedModelSerializer):
class Meta:
model = Segment
fields = ('id', 'name', 'foo')
class FooRetrieveSerializer(serializers.ModelSerializer):
segments = serializers.PrimaryKeyRelatedField(
read_only=True, many=True)
class Meta:
model = Foo
fields = (
'id',
'name',
'segments',
)
Expected behavior
Should at least be an 'array of object', but ideally the type&schema of the object is also detected and exposed.
I'm trying to implement a GenericSerializer to be used for all requests/responses and dynamically pass the required fields as needed.
Given three methods: X, Y and Z with different schema definitions. get_fields() indeed prints out different values. however the swagger ui takes only the first method X and place its values for all the rest of the methods Y and Z.
an attempt to generate a unique name for each method/schema did not succeed.
This is a feature request.
Rather than having lots of settings and decorators, and difficulty altering the schema for installable apps, it would be nice to be able to have an external yaml file which is overlaid(merged) with the generated schema.
I would be interesting in building this if acceptable to the maintainers.
Commented out the fixes from https://gist.github.com/tfranzel/c29f6b79967c6d4b89b78e779e2a798f , and a different backtrace appears
Traceback (most recent call last):
File "./manage.py", line 46, in <module>
main()
File "./manage.py", line 42, in main
execute_from_command_line(sys.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute()
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 323, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 364, in execute
output = self.handle(*args, **options)
File "/usr/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 44, in handle
schema = generator.get_schema(request=None, public=True)
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 153, in get_schema
paths=self.parse(request, public),
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 133, in parse
operation = view.schema.get_operation(path, path_regex, method, self.registry)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 77, in get_operation
operation['responses'] = self._get_response_bodies()
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 816, in _get_response_bodies
return {'200': self._get_response_for_code(response_serializers)}
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 841, in _get_response_for_code
component = self.resolve_serializer(serializer, 'response')
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 913, in resolve_serializer
component.schema = self._map_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 582, in _map_serializer
schema = self._map_basic_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 636, in _map_basic_serializer
schema = self._map_serializer_field(field, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 419, in _map_serializer_field
schema = self.resolve_serializer(field.child, direction).ref
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 913, in resolve_serializer
component.schema = self._map_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 582, in _map_serializer
schema = self._map_basic_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 636, in _map_basic_serializer
schema = self._map_serializer_field(field, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 437, in _map_serializer_field
del schema['readOnly']
KeyError: 'readOnly'
I'll get some better stack information shortly to identify where this and the other problem are coming from.
Upgrading from 0.9.3 ..
Traceback (most recent call last):
File "./manage.py", line 46, in <module>
main()
File "./manage.py", line 42, in main
execute_from_command_line(sys.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute()
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 323, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 364, in execute
output = self.handle(*args, **options)
File "/usr/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 44, in handle
schema = generator.get_schema(request=None, public=True)
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 153, in get_schema
paths=self.parse(request, public),
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 133, in parse
operation = view.schema.get_operation(path, path_regex, method, self.registry)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 77, in get_operation
operation['responses'] = self._get_response_bodies()
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 816, in _get_response_bodies
return {'200': self._get_response_for_code(response_serializers)}
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 841, in _get_response_for_code
component = self.resolve_serializer(serializer, 'response')
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 913, in resolve_serializer
component.schema = self._map_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 582, in _map_serializer
schema = self._map_basic_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 636, in _map_basic_serializer
schema = self._map_serializer_field(field, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 419, in _map_serializer_field
schema = self.resolve_serializer(field.child, direction).ref
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 913, in resolve_serializer
component.schema = self._map_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 582, in _map_serializer
schema = self._map_basic_serializer(serializer, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 636, in _map_basic_serializer
schema = self._map_serializer_field(field, direction)
File "/usr/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 409, in _map_serializer_field
schema = serializer_field_extension.map_serializer_field(self, direction)
TypeError: map_serializer_field() takes 2 positional arguments but 3 were given
In case this is the cause, I am using the fixes from here https://gist.github.com/tfranzel/c29f6b79967c6d4b89b78e779e2a798f
Probably very ill-advised, I am testing https://github.com/and3rson/drfbro , and have some unrelated mods to get it semi-functional, and no settings
related to it, but I am seeing the following when running dj admin command `spectacular. (i.e. not when using drfbro with spectacular)
ManualSchema
is coming from django-rest-framework, rather than from drfbro itself. I have not yet checked whether the use of .schema
in their view is reasonable or not; if it isnt, maybe this is just a unfortunate conflict. Ill dig more into it - just capturing the problem atm.
Traceback (most recent call last):
File "manage.py", line 46, in <module>
main()
File "manage.py", line 42, in main
execute_from_command_line(sys.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
utility.execute()
File "/usr/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 328, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/lib/python3.8/site-packages/django/core/management/base.py", line 369, in execute
output = self.handle(*args, **options)
File "/usr/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 44, in handle
schema = generator.get_schema(request=None, public=True)
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 153, in get_schema
paths=self.parse(request, public),
File "/usr/lib/python3.8/site-packages/drf_spectacular/generators.py", line 133, in parse
operation = view.schema.get_operation(path, path_regex, method, self.registry)
AttributeError: 'ManualSchema' object has no attribute 'get_operation'
The mapping of endpoint to serializer/model/whatever is incredibly useful information which can be used for other introspection type tools. It would be useful even if it was only created/updated when explicitly requested, such as a flag for the management command.
Immediate uses within drf-spectacular abound, but the most obvious and easy is to compare with reruns to detect changes in the mappings, and notify the invoker as mapping changes likely indicates the cause of some unexpected/beneficial change in generated API document.
Another use would be to augment admindocs views to provide a more detailed explanation of the data from that view. It might even be possible to inject docstrings where they are missing in the format needed by admindocs so it can perform its duty better.
The longer term concept which made me think of this is to create an API edition of https://github.com/meshy/django-schema-graph , which visually shows the openapi entities interlinked.
It would also be possible to use this data in generation without tackling the complexity of round-tripping. It would be wonderful to be able to create new mappings in the Django admin (i.e. a generated vs manual flag on the mapping data), which would allow manually mapping an APIView like djstripe's Subscription API to the appropriate serializer/model (c.f. #64).
Hi!
Pls, change this:
to:
serializer_model_mapping = {value: key for key, value in serializer.model_serializer_mapping.items()}
return {
'oneOf': [c.ref for c in sub_components],
'discriminator': {
'propertyName': serializer.resource_type_field_name,
'mapping': {
serializer.to_resource_type(serializer_model_mapping[sub_components[0].object]): c.ref['$ref']
for c in subcomponents
},
}
}
When grabbing a models "ID", should probably be using model.pk
- https://docs.djangoproject.com/en/dev/ref/models/instances/#the-pk-property.
A models id
field is accessable through .pk
, as the models PK is always accessible there. When dealing with custom PKs, this causes drf-spectacular to fall over a AttributeError: type object 'MethodCall' has no attribute 'id'
error.
For example, I have a model as follows:
class MethodCall(models.Model):
transaction = models.OneToOneField('Transaction', primary_key=True, on_delete=models.CASCADE)
...
As the transaction
field has primary_key=True
, Django doesn't generate a .id
field. However transaction
is still accessable via .pk
.
This is where it's an issue for me:
drf-spectacular/drf_spectacular/openapi.py
Line 359 in bca26d3
It may also be an issue here:
drf-spectacular/drf_spectacular/openapi.py
Line 399 in bca26d3
Describe the bug
They are really going to kill it this time...
/usr/lib/python3.8/site-packages/drf_spectacular/plumbing.py:6 DeprecationWarning('Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working')
The DRF default for DEFAULT_METADATA_CLASS is documented at https://github.com/encode/django-rest-framework/blob/master/docs/api-guide/metadata.md . The OPTIONS
action allows any type of response, but I guess the content-type should be negotiated. It seems reasonable that drf-spectacular would provide an OpenAPI provider for this.
Or, perhaps find some metadata interchange mechanism with https://github.com/drf-forms/drf-schema-adapter for them to expose/maintain it as part of larger effort. If you dont like the idea of providing a DEFAULT_METADATA_CLASS
here, I will create an issue there pointing back here recommending they use this as the basis of their openapi adapter.
In my current project i i find use query_serializer. Like this
class PublicCommentsRequest(Serializer):
include = CharField(required=False)
order_by = CharField(required=False)
order_direction = ChoiceField(choices=['ASC', 'DESC'], required=False)
start_datetime = DateTimeField(required=False)
end_datetime = DateTimeField(required=False)
unsafe = BooleanField(required=False)
swagger_auto_schema(
query_serializer=PublicCommentsRequest,
deprecated=True,
tags=['comments'],
operation_id='public:get_comments',
),
Could you add similar functionality?
I could use extra_parameters
, but it looks complicated
Thanks
When I used swagger_auto_schema
I was getting a schema with request parameters like this
from rest_framework.views import APIView
class RequestSerializer(serializers.Serializer):
device_uuid = UUIDField()
email = EmailField()
class MyView(views.APIView):
renderer_classes = [JSONRenderer]
@swagger_auto_schema(
request_body=RequestSerializer,
responses={'200': empty_response}, # None
)
def post(self, request, *args, **kwargs):
serializer = RequestSerializer(data=request.data)
...
return Response()
And in swagger it looks like this
When i try to use extend_schema it is not working
class MyView(views.APIView):
renderer_classes = [EnvelopedJSONRenderer]
@extend_schema(
request=RequestSerializer,
responses={200: None},
)
def post(self, request, *args, **kwargs):
serializer = RequestSerializer(data=request.data)
...
return Response()
How can I also do as I did before?
I am seeing a lot of occurrences of this
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 265, in get_auth
auths.append(scheme.get_security_requirement(self.view))
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/contrib/authentication.py", line 32, in get_security_requirement
return {self.name: view.required_scopes}
AttributeError: 'api_root' object has no attribute 'required_scopes'
That is the django-oscar-api, but it is so many others also.
Would it be possible for this to trigger a warning, and affected parts of the API to be discarded from the schema?
Hi,
I'm using a ListSerializer
in my views and when I try to load do openapi, just raise the following exception:
...
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/views.py", line 34, in get
public=spectacular_settings.SERVE_PUBLIC
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 107, in get_schema
paths=self.parse(None if public else request),
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 87, in parse
operation = view.schema.get_operation(path, method, self.registry)
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 136, in get_operation
request_body = self._get_request_body(path, method)
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 743, in _get_request_body
component = self.resolve_serializer(method, serializer)
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 867, in resolve_serializer
component.schema = self._map_serializer(method, serializer)
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 605, in _map_serializer
return self._map_basic_serializer(method, serializer)
File "/usr/local/lib/python3.6/site-packages/drf_spectacular/openapi.py", line 611, in _map_basic_serializer
for field in serializer.fields.values():
AttributeError: 'ListSerializer' object has no attribute 'fields'
ListSerializer
doesn't have fields
attribute, but can be extracted from child
attribute. Do you think that makes sense drf-spectacular
to support this kind of serializers too?
Attempting to generate a spec on a model with a custom function field gives a warning:
could not resolve field on model <class 'model_name'> with path "function_name". this is likely a custom field that does some unknown magic. maybe consider annotating the field/property? defaulting to "string".
But, I have annotated the function's return type already. Do I need to do something special in the serializer or view? Using @extend_schema with the parameter definition doesn't help.
I'm using DRF's generics.RetrieveUpdateAPIView.
(Thank you so much for the excellent efforts on this project!)
In drf_yasg I can get the schema and put it in endpoint automatically. Could you implement the same functionality?
...
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
...
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
...
]
Now I use the template and do it manually. I read the schema from the file and then use the swagger template
decimals can be both string and number. utilize COERCE_DECIMAL_TO_STRING
to detect the case
implement funtionality comparable to encode/django-rest-framework@603aac7
When using the SECURITY
setting, the defined security schemes are added at the global level.
This go's well when you do not have other authentication_classes
defined. When you do have authentication_classes
defined, the global defined security schemes are not added to the path.security
section and thus not available for the endpoints.
My current solution is to override AuthSchema.get_auth and return None when settings.DEFAULT_AUTHENTICATION_CLASSES equals view.authentication_classes
class SettingSecureAutoSchema(AutoSchema):
def get_auth(self):
if api_settings.DEFAULT_AUTHENTICATION_CLASSES == self.view.authentication_classes:
return None
return super().get_auth()
An other solution is to add everything that is in SECURE
to the path.security section
.
Is this the direction you want to go? Are should we introduce a list in settings where we can add extra OpenApiAuthenticationExtension
classes?
Describe the bug
When generating a scheme, the renderer_classes is not used. This leads to a problem when the serializer is wrapped in an additional list. We do not use pagination in the viewset.
To Reproduce
Our example
class EnvelopedJSONRenderer(UnicodeJsonRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
if renderer_context and renderer_context['response'].status_code < 400:
if data and 'data' in data:
# data is already enveloped (eg after pagination)
enveloped_data = {'status': 'ok', **data}
else:
enveloped_data = {'status': 'ok', 'data': data}
else:
enveloped_data = {'status': 'error', 'error': data}
return super().render(enveloped_data, accepted_media_type, renderer_context)
class ResponseWithStatus(serializers.Serializer):
status = serializers.ChoiceField(choices=['ok', 'error'])
class ApiSerializer(ResponseWithStatus):
data = NotImplemented
@memoize
def list_of(serializer_cls):
return type(
f'ApiList{serializer_cls.__name__}',
(ApiSerializer,),
{'data': serializer_cls(many=True)},
)
@method_decorator(
extend_schema(
tags=['favorites'],
responses={
'200': list_of(FavoriteSerializer),
'401': ResponseWithStatusAndError,
},
operation_id='get_favorites',
),
'list',
)
class FavoritesViewSet(
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
lookup_field = 'article_uid'
permission_classes = [IsAuthenticated]
serializer_class = FavoriteSerializer
renderer_classes = (EnvelopedJSONRenderer,)
queryset = Favorite.objects.all().select_related('article')
After that we got schema:
[{
"status": "ok",
"data": [
{
"article_path": "string"
}
]
}]
Expected behavior
We need to get the right scheme without wrapped list
{
"status": "ok",
"data": [
{
"article_path": "string"
}
]
}
I also want to say that the drf_yasg
package correctly processes this situation. Maybe you can see the implementation
Hi!
Is there a way to annotate SlugRelatedField?
At the moment it is incorrect format:
https://github.com/tfranzel/drf-spectacular/blob/master/tests/test_extend_schema.yml#L53
Must be like "200"
Details:
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#patterned-fields-1
In drf_yasg I can create different schema views for different endpoints. Can you implement the same functionality?
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version="v1",
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
)
urlpatterns = [
url(r"^swagger(?P<format>\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), name="schema-json"),
]
I think it should look like an extension of @extend_schema
decorator
Example
@extend_schema(deprecated=True)
...
I am trying to get https://github.com/dhcode/openapi-ui working, but it uses Accept: application/json, application/yaml
, which causes the following backtrace as a result, and it also displays only "untagged" but will be a separate issue.
[10:46:14][ERROR] django.request log.py:log_response:228 | Internal Server Error: /schema/
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 145, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 143, in _get_response
response = response.render()
File "/usr/local/lib/python3.6/site-packages/django/template/response.py", line 106, in render
self.content = self.rendered_content
File "/usr/local/lib/python3.6/site-packages/rest_framework/response.py", line 70, in rendered_content
ret = renderer.render(self.data, accepted_media_type, context)
File "/usr/local/src/drf-spectacular/drf_spectacular/renderers.py", line 14, in render
return yaml.dump(data, default_flow_style=False, sort_keys=False, Dumper=Dumper).encode('utf-8')
File "/usr/local/lib/python3.6/site-packages/yaml/__init__.py", line 290, in dump
return dump_all([data], stream, Dumper=Dumper, **kwds)
File "/usr/local/lib/python3.6/site-packages/yaml/__init__.py", line 278, in dump_all
dumper.represent(data)
File "/usr/local/lib/python3.6/site-packages/yaml/representer.py", line 27, in represent
node = self.represent_data(data)
File "/usr/local/lib/python3.6/site-packages/yaml/representer.py", line 48, in represent_data
node = self.yaml_representers[data_types[0]](self, data)
File "/usr/local/lib/python3.6/site-packages/yaml/representer.py", line 207, in represent_dict
return self.represent_mapping('tag:yaml.org,2002:map', data)
File "/usr/local/lib/python3.6/site-packages/yaml/representer.py", line 118, in represent_mapping
node_value = self.represent_data(item_value)
File "/usr/local/lib/python3.6/site-packages/yaml/representer.py", line 58, in represent_data
node = self.yaml_representers[None](self, data)
File "/usr/local/lib/python3.6/site-packages/yaml/representer.py", line 231, in represent_undefined
raise RepresenterError("cannot represent an object", data)
yaml.representer.RepresenterError: ('cannot represent an object', ErrorDetail(string='Could not satisfy the request Accept header.', code='not_acceptable'))
[10:46:14][ERROR] django.server basehttp.py:log_message:154 | "GET /schema/ HTTP/1.1" 500 133266
There have been a lot of feature additions recently. Some work silently in the background, other don't. Someone coming onboard now might have a hard time using some of the more advanced features.
drf-spectacular
drf-yasg
easier. some feature basically do the same with a different name.@extend_schema(|_fields|_serializer)
, Extensions
and hooks
and their mechanics.WARNING #813: Encountered 2 components with identical names "User" and different classes <class 'cookie.serializers.UserSerializer'> and <class 'oscarapi.serializers.login.UserSerializer'>. This will very likely result in an incorrect schema. Try renaming one.
I think drf-yasg uses ref_name
to bypass this problem
https://drf-yasg.readthedocs.io/en/stable/custom_spec.html#serializer-meta-nested-class
InlineConcreteBundle:
type: object
properties:
id:
type: integer
readOnly: true
triggering_product:
type: integer
description: Which product should trigger this bundle?
suggested_products:
type: array
items:
type: integer
description: Which product(s) should this bundle suggest when triggered?
description: Which product(s) should this bundle suggest when triggered?
required:
- id
- suggested_products
- triggering_product
Using silk_profile
from https://pypi.org/project/django-silk/ produces
WARNING #285: Unable to guess serializer for wrapped_target. This is graceful fallback handling for APIViews. Consider using GenericAPIView as view base class, if view is under your control. ignoring view for now.
The code causing this is probably my own. While I can workaround this by expanding these to use GenericAPIView, using these decorators from core drf allows for very small handlers. I only have 8 of these warnings, so it isnt a high priority for me, but worth finding a solution for as long as @api_view
is still core part of drf.
@api_view(['POST'])
@silk_profile(name='Update user info')
@authentication_classes((JSONWebTokenAuthentication,))
def update_user_info(request, format=None):
...
This occurs with silk 3 and silk 4
git grep --context 3 wrapped_target
silk/profiling/dynamic.py- decorator = silk_profile(name, _dynamic=True)
silk/profiling/dynamic.py- func_name = func
silk/profiling/dynamic.py- cls, func = _get_func(module, func_name)
silk/profiling/dynamic.py: wrapped_target = decorator(func)
silk/profiling/dynamic.py- if cls:
silk/profiling/dynamic.py: setattr(cls, func_name.split('.')[-1], wrapped_target)
silk/profiling/dynamic.py- else:
silk/profiling/dynamic.py: setattr(module, func_name, wrapped_target)
silk/profiling/dynamic.py-
silk/profiling/dynamic.py-
silk/profiling/dynamic.py-def _get_parent_module(module):
--
silk/profiling/profiler.py-
silk/profiling/profiler.py- def __call__(self, target):
silk/profiling/profiler.py- if self._should_meta_profile:
silk/profiling/profiler.py: def wrapped_target(*args, **kwargs):
silk/profiling/profiler.py- request = DataCollector().request
silk/profiling/profiler.py- if request:
silk/profiling/profiler.py- start_time = timezone.now()
--
silk/profiling/profiler.py- result = target(*args, **kwargs)
silk/profiling/profiler.py- return result
silk/profiling/profiler.py-
silk/profiling/profiler.py: return wrapped_target
silk/profiling/profiler.py- return target
silk/profiling/profiler.py-
silk/profiling/profiler.py-
--
silk/profiling/profiler.py-
silk/profiling/profiler.py- def __call__(self, target):
silk/profiling/profiler.py- if self._silk_installed():
silk/profiling/profiler.py: def wrapped_target(*args, **kwargs):
silk/profiling/profiler.py- with silk_meta_profiler():
silk/profiling/profiler.py- try:
silk/profiling/profiler.py- func_code = target.__code__
--
silk/profiling/profiler.py- self._finalise_queries()
silk/profiling/profiler.py- return result
silk/profiling/profiler.py-
silk/profiling/profiler.py: return wrapped_target
silk/profiling/profiler.py- else:
silk/profiling/profiler.py- Logger.warning('Cannot execute silk_profile as silk is not installed correctly.')
silk/profiling/profiler.py- return target
It may be that silk isnt doing the wrapping "properly" in the modern way to facilitate introspection of the underlying object, and the issue needs to be raised upstream.
Hi!
Currenlty it doesn't work (the "readonly" is ignored):
Fix:
Change this:
drf-spectacular/drf_spectacular/plumbing.py
Line 236 in 9daf0ad
return {'allOf': [{'$ref': f'#/components/{self.type}/{self.name}'}]}
Result (inheritance with overriding):
(it doesn't look like the correct way to fix it. so, it is just example)
Describe the bug
The new alpha ordering in components.schemas
is very nice, albeit I now need to find a way to verify a very large re-ordered diff, but it will be worth the pain if the new order is stable. It is a great problem to have, and im sure helpful tools abound for it.
However, I have a dejected BundleTypeEnum
that has been pushed out of place to the end of the array for no apparent reason, coming from the API exposed by https://pypi.org/project/django-oscar-bundles/ . Note I am not using that API atm, so this is extremely low priority for me, and an isolated case, but it would be great to have the whole alpha ordering thing fully solved.
...
BundleGroup:
type: object
properties:
id:
type: integer
readOnly: true
bundle_type:
$ref: '#/components/schemas/BundleTypeEnum'
name:
type: string
maxLength: 200
headline:
type: string
description: CTA headline in cart display
description:
type: string
image:
type: string
nullable: true
triggering_parents:
type: array
items:
type: integer
suggested_parents:
type: array
items:
type: integer
concrete_bundles:
type: array
items:
$ref: '#/components/schemas/InlineConcreteBundle'
user_configurable_bundles:
type: array
items:
$ref: '#/components/schemas/InlineUserConfigurableBundle'
required:
- concrete_bundles
- id
- triggering_parents
- user_configurable_bundles
Category:
type: object
properties:
...
...
TitleEnum:
enum:
- Mr
- Miss
- Mrs
- Ms
- Dr
type: string
BundleTypeEnum:
enum:
- default
type: string
securitySchemes:
basicAuth:
type: http
scheme: basic
...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.