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

Unable to annotate any partialmethod used in SerializerMethodField #451

Closed
sevdog opened this issue Jul 6, 2021 · 4 comments
Closed

Unable to annotate any partialmethod used in SerializerMethodField #451

sevdog opened this issue Jul 6, 2021 · 4 comments
Labels
enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending

Comments

@sevdog
Copy link

sevdog commented Jul 6, 2021

Describe the bug
When a serializer defines any SerializerMethodField as a functools.partialmethod is impossible to specify any extra attribute on this method, thus resulting in a typerror.

Traceback (most recent call last):       
  File "/usr/local/bin/django-admin", line 8, in <module>                                                                                                                                                          
    sys.exit(execute_from_command_line())           
  File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()                                                                                    
  File "/usr/local/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/local/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)                                                                   
  File "/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)                                                               
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 50, in handle
    schema = generator.get_schema(request=None, public=True)                                                                                                                                                       
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/generators.py", line 257, in get_schema
    paths=self.parse(request, public),                                                                   
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/generators.py", line 231, in parse                                                                                                                  
    operation = view.schema.get_operation(                                                                                                                                                                         
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 84, in get_operation
    operation['responses'] = self._get_response_bodies()            
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 1013, in _get_response_bodies
    return {'200': self._get_response_for_code(response_serializers, '200')}                             
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 1058, in _get_response_for_code
    component = self.resolve_serializer(serializer, 'response')            
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 1204, in resolve_serializer
    component.schema = self._map_serializer(serializer, direction)                 
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 705, in _map_serializer
    schema = self._map_basic_serializer(serializer, direction)             
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 769, in _map_basic_serializer
    schema = self._map_serializer_field(field, direction)                          
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 646, in _map_serializer_field
    return append_meta(self._map_response_type_hint(method), meta)                                                                                                                                                 
  File "/usr/local/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 831, in _map_response_type_hint
    hint = get_override(method, 'field') or typing.get_type_hints(method).get('return')                  
  File "/usr/local/lib/python3.8/typing.py", line 1255, in get_type_hints
    raise TypeError('{!r} is not a module, class, method, '
TypeError: functools.partial(<bound method MySerializer._get_generic_ct_field of MySerializer():
    element = SerializerMethodField()       
    other= SerializerMethodField() >, field='other') is not a module, class, method, or function.

To Reproduce

Just define a basic serializer with a partialmethod:

from functools import partialmethod
from rest_framework import serializers
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes


class MySerializer(serializers.Serializer):
    foo = serializers.SerializerMethodField()
    bar = serializers.SerializerMethodField()

    @extend_schema_field(OpenApiTypes.ANY)
    def _private_method(self, field, extra_param):
        # do some real stuff
        print(field, extra_param)

    def _private_method_2(self, field, extra_param):
        # do some real stuff
        print(field, extra_param)
    
    get_foo = partialmethod(_private_method, extra_param='foo')
    get_bar = extend_schema_field(OpenApiTypes.ANY)(partialmethod(_private_method_2, extra_param='bar'))

This will throw the abovementioned error.

Expected behavior
No error should be thrown, if type annotation was setted on base method it should be used.

NOTE: as I have tested using the decorator inline is useless, since it is not possible to add any attrbute on partialmethod objects.

As of now a partialmethod object can be recognized since it has an attribute called _partialmethod where it stores the original partial object which is called by the interpreter.

@tfranzel
Copy link
Owner

tfranzel commented Jul 8, 2021

good point.. already looking into it.

@tfranzel tfranzel added the enhancement New feature or request label Jul 8, 2021
@tfranzel tfranzel added the fix confirmation pending issue has been fixed and confirmation from issue reporter is pending label Jul 15, 2021
@sevdog
Copy link
Author

sevdog commented Jul 23, 2021

@tfranzel I have just tested the current master in my environment and it is working!

If this could also be documented (to help whoever uses this approach) it would be great.

@tfranzel
Copy link
Owner

@sevdog awesome! release is happening very soon.

i thought about documentation but there is really no good place to put it. also i believe people would probably just try out version 1 (get_foo) and assume it would work like you did. i would for now leave it as is and close the issue if there are no objections.

@sevdog
Copy link
Author

sevdog commented Jul 23, 2021

Ok, this is fine.

waiting release 🎁

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request 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