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

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 🎁

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