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

RepresenterError when providing choices with instances derived of a base type #257

Closed
lerela opened this issue Jan 11, 2021 · 3 comments
Closed
Labels
fix confirmation pending issue has been fixed and confirmation from issue reporter is pending

Comments

@lerela
Copy link

lerela commented Jan 11, 2021

Describe the bug
I have a model with a field that has a choices parameter. This parameter is not a standard list of tuples but a django-extended-choices Choices class:

from django.db import models
from extended_choices import Choices

choices = Choices(
    ("C1", 0, "Choice 1"),
    ("C2", 1, "Choice 2")
)

class TestModel(models.Model):
    test_field = models.IntegerField(
        choices = choices, default = choices.C1
    )

drf-spectacular is unable to serialize the schema of this model because the choices are of type extended_choices.helpers.IntChoiceAttribute and not raw integers.

Schema generation fails as follow:

➜ ./manage.py spectacular --file schema.yml
Traceback (most recent call last):
  File "./manage.py", line 22, in <module>
    main()
  File "./manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 60, in handle
    output = renderer.render(schema, renderer_context={})
  File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/renderers.py", line 26, in render
    return yaml.dump(
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/__init__.py", line 290, in dump
    return dump_all([data], stream, Dumper=Dumper, **kwds)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/__init__.py", line 278, in dump_all
    dumper.represent(data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 27, in represent
    node = self.represent_data(data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 199, in represent_list
    return self.represent_sequence('tag:yaml.org,2002:seq', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 92, in represent_sequence
    node_item = self.represent_data(item)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 58, in represent_data
    node = self.yaml_representers[None](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 231, in represent_undefined
    raise RepresenterError("cannot represent an object", data)
yaml.representer.RepresenterError: ('cannot represent an object', 0)

This is the value of data_types in last frame: (<class 'extended_choices.helpers.IntChoiceAttribute'>, <class 'extended_choices.helpers.ChoiceAttributeMixin'>, <class 'int'>, <class 'object'>)

As this is not a custom field, I cannot use OpenApiSerializerFieldExtension.

I can solve that by overriding the serializer field and providing a standard default by casting to int, but it's a little annoying to do that for each ChoiceField.
Django/DRF are able to work that out (that it's in fact an int, above code works out of the box in the admin and in serializers) so maybe drf-spectacular could as well?

To Reproduce
In addition to above snippet, you can use this view:

from .models import TestModel
from rest_framework import viewsets
from rest_framework import serializers

class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = TestModel
        fields = ['id', 'test_field']

class TestViewSet(viewsets.ModelViewSet):
    queryset = TestModel.objects.all()
    serializer_class = TestSerializer

Expected behavior
I want the extended choices object to be properly serialized as an Enum.

@tfranzel
Copy link
Owner

tfranzel commented Jan 11, 2021

i see. strictly speaking we have no issue with this, it is just that the pyyaml lib is a bit picky with types. i'll look into it

tfranzel added a commit that referenced this issue Jan 12, 2021
some libraries create basic sub types on
which pyyaml will choke.
@tfranzel
Copy link
Owner

added some more normalization. this should do it. please check and close

@tfranzel tfranzel added the fix confirmation pending issue has been fixed and confirmation from issue reporter is pending label Jan 12, 2021
@lerela
Copy link
Author

lerela commented Jan 12, 2021

That did the trick. Warm thanks @tfranzel 👍

@lerela lerela closed this as completed Jan 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fix confirmation pending issue has been fixed and confirmation from issue reporter is pending
Projects
None yet
Development

No branches or pull requests

2 participants