Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # 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
# for free to join this conversation on GitHub. Already have an account? # 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