profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/chaosk/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Krzysztof Socha chaosk Sherpany Poznań, Poland https://ecksd.ee

chaosk/brabeion 1

a badges app for Django

chaosk/django-activity-stream 1

Generate generic activity streams from the actions on your site. Users can follow any actor's activities for personalized streams.

chaosk/django-faq 1

A Frequently Asked Question (FAQ) management application built with Django.

chaosk/aldryn-django-cms 0

An opinionated Django CMS setup bundled as an Aldryn Addon

chaosk/aldryn-events 0

An events publishing application for Aldryn and django CMS – part of the Essential Addons.

chaosk/aldryn-faq 0

Add questions and answers for Aldryn and django CMS – part of the Essential Addons.

issue commenttfranzel/drf-spectacular

Are Serializer and BaseSerializer supported?

Hi, drf-spectacular supports both Serializer and ModelSerializer, i.e. subclasses of those and anything included in DRF. having the fields attr is a assumptions that holds true for those.

writing your own serializer based on BaseSerializer apparently breaks some of the assumptions we made. pretty certain that your deviation requires writing a OpenApiSerializerExtension. We did that for example for rest_polymorphic (code). from there you can call back into the AutoSchema and reuse parts of spectacular.

if that is too much trouble you can annotate manually written raw schemas with extend_schema(operation=XXX) but that is a matter of last resort usually.

i don't think writing a preprocessing hook will get you far in this case.

if you need more advice i would need more insight into what your custom serializer baseclass is doing.

rfleschenberg

comment created time in 2 days

issue openedtfranzel/drf-spectacular

Are Serializer and BaseSerializer supported?

Hi,

I am using DRF's serializers.Serializer and serializers.BaseSerializer in a few places. I am running into errors because spectacular tries to access attributes on the serializer that are not present.

https://github.com/tfranzel/drf-spectacular/blob/deb83250f078ed4bce17b12972e098409ac69ec9/drf_spectacular/openapi.py#L763 throws AttributeError because my BaseSerializer subclass does not have fields.

https://github.com/tfranzel/drf-spectacular/blob/deb83250f078ed4bce17b12972e098409ac69ec9/drf_spectacular/openapi.py#L526 throws AttributeError because my Serializer subclass does not have Meta.

  • Is it intended that spectacular only supports ModelSerializer subclasses, or am I maybe missing something here?
  • Is there a good workaround for this?
  • As a last resort solution, is there an API / pattern that allows me to manually define the schema instead of relying on inspection? I checked https://drf-spectacular.readthedocs.io/en/latest/customization.html, but I am not sure what the right approach would be in this case. A custom preprocessing hook maybe?

created time in 2 days

issue commentdivio/django-simple-sso

BadRequest at / b'Signature expired'

One of my developers is getting this all of a sudden running a client locally via Docker and authenticating against a remote site. We can't reproduce the issue ourselves.

felocru

comment created time in 3 days

issue commenttfranzel/drf-spectacular

How to set a custom response body format globally?

hey @passerby223, sry i was unable to get around looking at this yet. apparently you found a solution.

i suspect that this either required subclassing AutoSchema or doing a POSTPROCESSING_HOOK as this is a non-standard behavior.

Yes, I have found a solution, thank you very much!

passerby223

comment created time in 4 days

issue commenttfranzel/drf-spectacular

How to set a custom response body format globally?

hey @passerby223, sry i was unable to get around looking at this yet. apparently you found a solution.

i suspect that this either required subclassing AutoSchema or doing a POSTPROCESSING_HOOK as this is a non-standard behavior.

passerby223

comment created time in 4 days

issue closedtfranzel/drf-spectacular

How to set a custom response body format globally?

Hi, friends.I am using drf-spectacular==0.17.1 in a Django project(Django==3.2.4 & djangorestframework==3.12.4) to generate API documentation. I have a question and want to ask for help

  1. I customized the ModelViewSet. Use a custom response body format(JsonResponse). As follows
from rest_framework import status
from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet

from .custom_json_response import JsonResponse


class CustomModelViewSet(viewsets.ModelViewSet):
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_201_CREATED,
                            headers=headers)

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return JsonResponse(data=[], msg='delete resource success', code=20000, status=status.HTTP_204_NO_CONTENT)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)


class MyListRetrieveDestroyModelViewSet(mixins.RetrieveModelMixin,
                                        mixins.ListModelMixin,
                                        mixins.DestroyModelMixin,
                                        GenericViewSet):
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return JsonResponse(data=[], msg='delete resource success', code=20000, status=status.HTTP_204_NO_CONTENT)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)


class MyListRetrieveModelViewSet(mixins.RetrieveModelMixin,
                                 mixins.ListModelMixin,
                                 GenericViewSet):
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JsonResponse(data=serializer.data, msg='success', code=20000, status=status.HTTP_200_OK)
  1. The content of JsonResponse is as follows(see the self.data = {'code': code, 'message': msg, 'data': data})
from rest_framework.response import Response
from rest_framework.serializers import Serializer


class JsonResponse(Response):
    def __init__(self, data=None, code=None, msg=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None, **kwargs):
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)
        self.data = {'code': code, 'message': msg, 'data': data}
        self.data.update(kwargs)
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value
  1. I extends the CustomModelViewSet class in views.py to achieve a unified response body format, as shown below
@extend_schema(tags=['项目管理'])
class ProjectsViewSet(CustomModelViewSet):
    serializer_class = ProjectSerializer
    permission_classes = [permissions.IsAuthenticated, IsObjectCreatorOrModifierInRequestUserGroups]
    filterset_fields = ['project_name', 'project_desc', 'creator', 'modifier']
  1. The actual response body of the interface request is as follows:
{
    "code": 20000,
    "message": "success",
    "data": {
        "id": 1,
        "create_time": "2021-06-14 13:54:26",
        "update_time": "2021-06-14 13:54:26",
        "creator": "anonymous",
        "modifier": "anonymous",
        "project_name": "测试项目1",
        "project_desc": "测试项目description"
    }
}
  1. But viewing the API document in the browser, the format of the response body is not displayed like{"code": 20000, "message": "xxx", "data": "xxx"} image image
  2. So I want to ask, how to set the Schema of the unified response body format ({"code": 20000, "message": "xxx", "data": "xxx"}) globally? thank you very much.

closed time in 4 days

passerby223

issue closedtfranzel/drf-spectacular

Default Response Code for post is 200?

Describe the bug When annotating a generics.createAPIView or generics.ListCreateAPIView, it seems as if the default response code for post responses is 200. Should it be 201? I am using drf spectacular auto schema and generator.

To Reproduce Create a class that derives from generics.ListCreateAPIView and look at the post method response.

Expected behavior The default response should be 201.

closed time in 4 days

li-darren

issue commenttfranzel/drf-spectacular

Default Response Code for post is 200?

closing this issue for now. feel free to comment if anything is missing or not working and we will follow-up.

li-darren

comment created time in 4 days

issue closedtfranzel/drf-spectacular

Support mark field deprecated

See https://spec.openapis.org/oas/v3.1.0#parameter-object

closed time in 4 days

elonzh

issue commenttfranzel/drf-spectacular

Support mark field deprecated

closing this issue for now. feel free to comment if anything is missing or not working and we will follow-up.

elonzh

comment created time in 4 days

issue closedtfranzel/drf-spectacular

`is_patched_serializer` fails with `DictField`

Describe the bug My team has a list endpoint that returns a dictionary values that are generated by methods that aren't serializers. We'd like to document at least that this endpoint returns a list of objects. I tried adding the following extend_schema:

    @extend_schema(
        responses=serializers.ListSerializer(child=serializers.DictField()),
    )
    def get(self, request: Request, *args: Any, **kwargs: Any) -> Response:
        data = # complex code to produce list of dicts here.
        return Response(data, status=200)

drf-spectacular raised the following exception:

Traceback (most recent call last):
  File "manage.py", line 11, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 375, 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 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.7/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.7/site-packages/drf_spectacular/generators.py", line 230, in get_schema
    paths=self.parse(request, public),
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/generators.py", line 207, in parse
    operation = view.schema.get_operation(path, path_regex, method, self.registry)
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/utils.py", line 222, in get_operation
    return super().get_operation(path, path_regex, method, registry)
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 83, in get_operation
    operation['responses'] = self._get_response_bodies()
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 975, in _get_response_bodies
    return {'200': self._get_response_for_code(response_serializers, '200')}
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 1036, in _get_response_for_code
    paginated_name = f'Paginated{self._get_serializer_name(serializer, "response")}List'
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 1119, in _get_serializer_name
    return self._get_serializer_name(serializer.child, direction)
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 1126, in _get_serializer_name
    if is_patched_serializer(serializer, direction):
  File "/usr/local/lib/python3.7/site-packages/drf_spectacular/plumbing.py", line 101, in is_patched_serializer
    and serializer.partial
AttributeError: 'DictField' object has no attribute 'partial'

Expected behavior

Since this should be a valid serializer (test proving that is below), I'd expect the is_patched_serializer to handle a DictField.

    def test_list_serializer(self):
        from rest_framework.serializers import DictField, ListSerializer

        class MyListSerializer(ListSerializer):
            child = DictField()

        my_list = [{}, {'a': 'b'}]

        serializer = MyListSerializer(my_list)
        assert serializer.data == [{}, {'a': 'b'}]

closed time in 4 days

mblayman

issue commenttfranzel/drf-spectacular

`is_patched_serializer` fails with `DictField`

closing this issue for now. feel free to comment if anything is missing or not working and we will follow-up.

mblayman

comment created time in 4 days

issue commenttfranzel/drf-spectacular

SerializerMethodField defaults seem to be treated incorrectly

@rmelick-vida thanks for taking such a detailed look at this. i gave it a quick glance to provide you with a first opinion. however, i have to have a bit more time to look at it in detail and give you a more definite answer.

what i can tell you already is that the likelihood of them changing such a seasoned API is small unless this is a serious bug, which i'm not yet sure about. also we do call to_representation for a reason. i vaguely remember that we added this to mitigate objects slipping through unprocessed (like datetime objects). so i don't think we should remove it altogether. probably the best course of action is us accounting for those cases and do something smarter there.

just a first opinion which may turn out wrong, but i will schedule some time in the next couple of days for this.

rmelick-vida

comment created time in 4 days

issue commenttfranzel/drf-spectacular

SerializerMethodField defaults seem to be treated incorrectly

I got a test case going (https://github.com/rmelick-vida/drf-troubleshooting), and confirmed that the DRF docs are correct: the value passed into to_representation on a SerializerMethodField is in fact an instance of the object, not an attribute value.

While stepping through the debugger, I saw what was happening. When the get_attribute helper method was called, the second argument (attrs) is an empty list. So, it falls back to that last line where it just does return instance

If you run the example, and start up the server using manage.py, you can see the difference. When calling the api with curl, DRF passes in an object of type User

 curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/

{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "url": "http://127.0.0.1:8000/users/1/",
            "username": "admin",
            "email": "admin@example.com",
            "groups": [],
            "object_types": [
                "<User: admin>"
            ]
        }
    ]
}%  

But, when generating a schema, it is passed the list

(env) ➜  drf-quickstart git:(master) ✗ ./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 "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/management/commands/spectacular.py", line 50, in handle
    schema = generator.get_schema(request=None, public=True)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/generators.py", line 257, in get_schema
    paths=self.parse(request, public),
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/generators.py", line 232, in parse
    path, path_regex, path_prefix, method, self.registry
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 84, in get_operation
    operation['responses'] = self._get_response_bodies()
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 1013, in _get_response_bodies
    return {'200': self._get_response_for_code(response_serializers, '200')}
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 1058, in _get_response_for_code
    component = self.resolve_serializer(serializer, 'response')
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 1204, in resolve_serializer
    component.schema = self._map_serializer(serializer, direction)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 705, in _map_serializer
    schema = self._map_basic_serializer(serializer, direction)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 769, in _map_basic_serializer
    schema = self._map_serializer_field(field, direction)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 452, in _map_serializer_field
    meta = self._get_serializer_field_meta(field)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/drf_spectacular/openapi.py", line 748, in _get_serializer_field_meta
    default = field.to_representation(field.default)
  File "/Users/russell/src/other/drf-quickstart/env/lib/python3.7/site-packages/rest_framework/fields.py", line 1882, in to_representation
    return method(value)
  File "/Users/russell/src/other/drf-quickstart/tutorial/quickstart/serializers.py", line 16, in get_object_types
    raise Exception("Unexpected type passed <%s: %s>" % (obj.__class__.__name__, obj))
Exception: Unexpected type passed <list: []>

What do you think, should we open a ticket / issue within the DRF project to ask them why they're inconsistent in the parameters that to_representation expects? It seems like it's almost part of their public API, since they say you need to override it if you define a Custom Field. Or should drf-spectacular change how it handles defaults (maybe skip calling to_representation and just return the value directly, perhaps only for weird fields like ModelField and SerializerMethodField)?

rmelick-vida

comment created time in 4 days

issue commenttfranzel/drf-spectacular

In a OpenApiSerializerFieldExtension, how to extend from the default output?

so i took your comment into consideration, but decided against having both. i only implemented bypass_extensions and added this to the documentation. in any case, you should now be able to achieve what you wanted.

the reason is that in nearly half of the cases this will not work anyway due warnings in the regular codepath. also get_default_output would be the only convenience method in all extensions. it would also only contain that one line that is documented in the docstring. so basically fort consistency and brevity i decided against it.

foucdeg

comment created time in 4 days

issue commenttfranzel/drf-spectacular

SerializerMethodField defaults seem to be treated incorrectly

I spent some more time looking into this today. From my reading of the DRF code, it looks like both their docs and our code are incorrect (which feels kind of unlikely). Here's what I see

During a call to Serializer.to_representation, get_attribute is called on the field, and then that value is passed into to_representation source

def to_representation(self, instance):
        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue

            check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
            if check_for_none is None:
                ret[field.field_name] = None
            else:
                ret[field.field_name] = field.to_representation(attribute)

        return ret

For a SerializerMethodField, the get_attribute function is inherited from Field source

        def get_attribute(self, instance):
        """
        Given the *outgoing* object instance, return the primitive value
        that should be used for this field.
        """
        try:
            return get_attribute(instance, self.source_attrs)
        except BuiltinSignatureError as exc:

and the get_attribute helper method will use the built in getattr to look up attribute from the object source

def get_attribute(instance, attrs):
    """
    Similar to Python's built in `getattr(instance, attr)`,
    but takes a list of nested attributes, instead of a single attribute.
    Also accepts either attribute lookup on objects or dictionary lookups.
    """
    for attr in attrs:
        try:
            if isinstance(instance, Mapping):
                instance = instance[attr]
            else:
                instance = getattr(instance, attr)
        except ObjectDoesNotExist:
            return None
        if is_simple_callable(instance):
            try:
                instance = instance()
            except (AttributeError, KeyError) as exc:
                # If we raised an Attribute or KeyError here it'd get treated
                # as an omitted field in `Field.get_attribute()`. Instead we
                # raise a ValueError to ensure the exception is not masked.
                raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))

    return instance

To me, this seems to show that what is passed in to to_representation should not be the entire object (like their docs describe), but instead should be just the value for that field (like drf-spectacular is assuming). I'll have to take some time to test this, and perhaps open a discussion in DRF to get some clarification from their side.

While investigating this, I happened to see that ModelField acts differently than other fields though. It assumes that an instance of the entire object is passed in to to_representation, not just the value for that field. It seems they handle it by returning the entire object from get_attribute though source.

    def get_attribute(self, obj):
        # We pass the object instance onto `to_representation`,
        # not just the field attribute.
        return obj

    def to_representation(self, obj):
        value = self.model_field.value_from_object(obj)
        if is_protected_type(value):
            return value
        return self.model_field.value_to_string(obj)

I wonder if drf-spectacular would also hit an error if someone specified a default on a ModelField. I do think that it's valid to set defaults on fields for both inbound and outbound. I definitely see the point that in our code, it's redundant though, since we can just handle the None case inside of our method.

rmelick-vida

comment created time in 4 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

awesome! fyi, i just released 0.17.2

blueyed

comment created time in 4 days

created tagtfranzel/drf-spectacular

tag0.17.2

Sane and flexible OpenAPI 3 schema generation for Django REST framework.

created time in 4 days

push eventtfranzel/drf-spectacular

T. Franzel

commit sha deb83250f078ed4bce17b12972e098409ac69ec9

version bump

view details

push time in 4 days

push eventtfranzel/drf-spectacular

T. Franzel

commit sha a33d55dca3a7afd38871e68a1c9a7c5ef3ec7ff9

prevent endless loop in extensions when augmenting schema #426

view details

push time in 4 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

@tfranzel master works for me also. Thanks for fixing it so fast!

blueyed

comment created time in 4 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

@tfranzel looks good for me after changes!

blueyed

comment created time in 5 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

@jairhenrique @blueyed i fixed the second import error (hopefully). could you test if the current master works for you both? if yes we can do a hotfix release. thanks a lot.

blueyed

comment created time in 5 days

PR merged tfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

The typing hints added in 9819424 caused a regression with version 0.17.1, where importing OpenApiAuthenticationExtension from drf_spectacular.extensions might cause a circular import error:

ImportError: Could not import 'project.app.authentication.MyAuthentication' for API setting 'DEFAULT_AUTHENTICATION_CLASSES'. ImportError: cannot import name 'OpenApiAuthenticationExtension' from partially initialized module 'drf_spectacular.extensions' (most likely due to a circular import) (…/.venv/lib/python3.9/site-packages/drf_spectacular/extensions.py).

+3 -3

8 comments

1 changed file

blueyed

pr closed time in 5 days

push eventtfranzel/drf-spectacular

Daniel Hahler

commit sha b744034e07b70551966088f46d9e906d0d2b3ef4

fix: avoid circular import of/via rest_framework's APIView The typing hints added in 9819424 caused a regression with version 0.17.1, where importing `OpenApiAuthenticationExtension` from `drf_spectacular.extensions` might cause a circular import error: > ImportError: Could not import > 'project.app.authentication.MyAuthentication' for API setting > 'DEFAULT_AUTHENTICATION_CLASSES'. ImportError: cannot import name > 'OpenApiAuthenticationExtension' from partially initialized module > 'drf_spectacular.extensions' (most likely due to a circular import) > (…/.venv/lib/python3.9/site-packages/drf_spectacular/extensions.py).

view details

T. Franzel

commit sha 475e4110a7f13b59429ba658eb340b95d8b46b3b

bugfix secondary import cycle (generics.APIView) #430

view details

push time in 5 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

i dug into this further. i changed 2 imports in this release. the other one looked equally innocent: https://github.com/tfranzel/drf-spectacular/commit/1ec133aeeeabcc457bad9b1ecd837f7d1670c5f0#diff-d727722caf6fe5f5507940c9efbcbe843fcca1dec9ac64402f074b08dd1c294fR29

@jairhenrique i was able to reproduce your exact error. however this PR was not enough. the other import failed afterwards. can you confirm this?

@blueyed is your case completely fixed by this PR?

blueyed

comment created time in 5 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

@tfranzel yes:

from typing import Any, Dict

from django.contrib.auth.models import User
from drf_spectacular.extensions import OpenApiAuthenticationExtension
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import (
    AuthenticationFailed,
    InvalidToken,
)
from rest_framework_simplejwt.settings import api_settings


class MyAuthentication(JWTAuthentication):
    def get_user(self, validated_token: Dict[str, Any]) -> User:
        """
        Attempts to find and return a user using the given validated token.
        """
        try:
            user_id = validated_token[api_settings.USER_ID_CLAIM]
        except KeyError:
            raise InvalidToken(
                "Token contained no recognizable user identification"
            )

        try:
            user = User.objects.get(
                **{api_settings.USER_ID_FIELD: user_id},
                clientprofile__user_id=user_id,
            )
        except User.DoesNotExist:
            raise AuthenticationFailed("User not found", code="invalid_user")

        if not user.is_active:
            raise AuthenticationFailed("User is inactive", code="invalid_user")

        return user


class OpenApiMyAuthentication(  # type:ignore
    OpenApiAuthenticationExtension
):
    target_class = "app.helpers.api.auth.MyAuthentication"
    name = "jwtAuth"
    matches_subclass = True

    def get_security_definition(self, auto_schema: Any) -> Dict[str, str]:
        return {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "Bearer",
        }
blueyed

comment created time in 5 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

@jairhenrique very strange. these errors are the worst. is your custom auth class in the same python file as the AuthExtensions?

blueyed

comment created time in 5 days

pull request commenttfranzel/drf-spectacular

fix: avoid circular import of/via rest_framework's APIView

@tfranzel I've get this error when try to load my custom auth class.

Traceback (most recent call last):
  File "/home/vsts/work/1/s/src/manage.py", line 21, in <module>
    main()
  File "/home/vsts/work/1/s/src/manage.py", line 17, in main
    return check_method()
  File "python3.9/site-packages/django/urls/resolvers.py", line 412, in check
    for pattern in self.url_patterns:
  File "python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "python3.9/site-packages/django/urls/resolvers.py", line 598, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "python3.9/site-packages/django/urls/resolvers.py", line 591, in urlconf_module
    return import_module(self.urlconf_name)
  File "python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 855, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/home/vsts/work/1/s/src/madmax/urls.py", line 19, in <module>
    from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
  File "python3.9/site-packages/drf_spectacular/views.py", line 12, in <module>
    from rest_framework.views import APIView
  File "python3.9/site-packages/rest_framework/views.py", line 17, in <module>
    from rest_framework.schemas import DefaultSchema
  File "python3.9/site-packages/rest_framework/schemas/__init__.py", line 33, in <module>
    authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
  File "python3.9/site-packages/rest_framework/settings.py", line 225, in __getattr__
    val = perform_import(val, attr)
  File "python3.9/site-packages/rest_framework/settings.py", line 168, in perform_import
    return [import_from_string(item, setting_name) for item in val]
  File "python3.9/site-packages/rest_framework/settings.py", line 168, in <listcomp>
    return [import_from_string(item, setting_name) for item in val]
  File "python3.9/site-packages/rest_framework/settings.py", line 180, in import_from_string
    raise ImportError(msg)
ImportError: Could not import 'path.to.my.custom.api.auth.CustomAuthentication' for API setting 'DEFAULT_AUTHENTICATION_CLASSES'. ImportError: cannot import name 'APIView' from partially initialized module 'rest_framework.views' (most likely due to a circular import) (python3.9/site-packages/rest_framework/views.py).
blueyed

comment created time in 5 days