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

can not use custom DEFAULT_PAGINATOR_INSPECTORS #382

Closed
raphael-leger opened this issue Jun 13, 2019 · 11 comments
Closed

can not use custom DEFAULT_PAGINATOR_INSPECTORS #382

raphael-leger opened this issue Jun 13, 2019 · 11 comments

Comments

@raphael-leger
Copy link

raphael-leger commented Jun 13, 2019

Hi,

I can not set a custom paginator inspector as default using this DEFAULT_PAGINATOR_INSPECTORS option that is in the docs:
https://drf-yasg.readthedocs.io/en/stable/settings.html#default-paginator-inspectors.

Here are the step to reproduce. I have a simple api module in which I created a file swagger.py with the following :

from drf_yasg.inspectors import PaginatorInspector
from drf_yasg import openapi
from collections import OrderedDict


api_info = openapi.Info(
    title="API Title", default_version="v1.0", description="This API contains some cool endpoints."
)


class PageNumberPaginatorInspectorClass(PaginatorInspector):
    def get_paginated_response(self, paginator, response_schema):
        paged_schema = openapi.Schema(
            type=openapi.TYPE_OBJECT,
            properties=OrderedDict((
                ('count', openapi.Schema(type=openapi.TYPE_INTEGER)),
                ('elements', openapi.Schema(type=openapi.TYPE_INTEGER)),
                ('results', response_schema),
            )),
            required=['results']
        )

        return paged_schema

Here is my django config in settings.py for swagger:

SWAGGER_SETTINGS = {
    "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}},
    "DEFAULT_INFO": "api.swagger.api_info",
    "DEFAULT_PAGINATOR_INSPECTORS": [
        'api.swagger.PageNumberPaginatorInspectorClass',
    ]
}

When generating the swagger file with the CLI, it throws the following error:

  [2019-06-13 13:43:01 +0000] [92] [ERROR] Exception in worker process
 Traceback (most recent call last):
   File "/usr/lib/python3.6/site-packages/rest_framework/settings.py", line 184, in import_from_string
     return getattr(module, class_name)
 AttributeError: module 'api.swagger' has no attribute 'PageNumberPaginatorInspectorClass'
 
 During handling of the above exception, another exception occurred:
 
 Traceback (most recent call last):
   File "/usr/lib/python3.6/site-packages/rest_framework/settings.py", line 183, in import_from_string
     module = import_module(module_path)
   File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
     return _bootstrap._gcd_import(name[level:], package, level)
   File "<frozen importlib._bootstrap>", line 994, in _gcd_import
   File "<frozen importlib._bootstrap>", line 971, in _find_and_load
   File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
   File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
   File "<frozen importlib._bootstrap_external>", line 678, in exec_module
   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
   File "/usr/src/app/api/swagger.py", line 1, in <module>
     from drf_yasg.inspectors import PaginatorInspector
   File "/usr/lib/python3.6/site-packages/drf_yasg/inspectors/__init__.py", line 16, in <module>
     ViewInspector.paginator_inspectors = swagger_settings.DEFAULT_PAGINATOR_INSPECTORS
   File "/usr/lib/python3.6/site-packages/drf_yasg/app_settings.py", line 121, in __getattr__
     val = perform_import(val, attr)
   File "/usr/lib/python3.6/site-packages/rest_framework/settings.py", line 172, in perform_import
     return [import_from_string(item, setting_name) for item in val]
   File "/usr/lib/python3.6/site-packages/rest_framework/settings.py", line 172, in <listcomp>
     return [import_from_string(item, setting_name) for item in val]
   File "/usr/lib/python3.6/site-packages/rest_framework/settings.py", line 187, in import_from_string
     raise ImportError(msg)
 ImportError: Could not import 'api.swagger.PageNumberPaginatorInspectorClass' for API setting 'DEFAULT_PAGINATOR_INSPECTORS'. AttributeError: module 'api.swagger' has no attribute 'PageNumberPaginatorInspectorClass'.

Am I doing something wrong or is there a bug here?
How come api.swagger.api_info is correctly imported but api.swagger.PageNumberPaginatorInspectorClass is not?

@raphael-leger raphael-leger changed the title impossible to use a custom DEFAULT_PAGINATOR_INSPECTORS impossible to use custom DEFAULT_PAGINATOR_INSPECTORS Jun 13, 2019
@raphael-leger raphael-leger changed the title impossible to use custom DEFAULT_PAGINATOR_INSPECTORS can not use custom DEFAULT_PAGINATOR_INSPECTORS Jun 13, 2019
@axnsan12
Copy link
Owner

axnsan12 commented Jun 13, 2019

It's probably a circular import issue. Try breaking things up in separate modules.

@raphael-leger
Copy link
Author

raphael-leger commented Jun 13, 2019

Thanks for the reply. Given how simple and little the sample code I provided is and given that it only imports code from the external libs rest-framework and yours, I do not feel like I can break up 'things'. What things?

Perhaps you import django's Paginators in the same file where PaginatorInspector is defined, making it hard to use the paginator inspector while extending a django's paginator (which is probably often the case).

@axnsan12
Copy link
Owner

Looking at the stack trace, it's a cyclic import:

api.swagger -> from drf_yasg.generators import OpenAPISchemaGenerator -> from .inspectors.field import get_basic_type_info, get_queryset_field, get_queryset_from_view -> ViewInspector.paginator_inspectors = swagger_settings.DEFAULT_PAGINATOR_INSPECTORS -> return [import_from_string(item, setting_name) for item in val] (== import api.swagger.PageNumberPaginatorInspectorClass) -> ImportError

Which suggets that the file contains more than you've shown, i.e. an OpenAPISchemaGenerator subclass also.

You could argue that this is a bug in drf-yasg and that imports from settings should be made lazily, but simply moving PageNumberPaginatorInspectorClass to a file which does not import drf_yasg.generators will fix your problem.

axnsan12 added a commit that referenced this issue Jun 13, 2019
@raphael-leger
Copy link
Author

raphael-leger commented Jun 13, 2019

Oops, you are right about the stack trace, I got it mixed up when pasting it here.

I updated and simplified the example and stack trace in the original post to match the given example, where there is nothing else in the file than what is shown.

The stack trace seems to say that the import error happens when importing the paginator inspector:
from drf_yasg.inspectors import PaginatorInspector

@axnsan12
Copy link
Owner

Well then, that's strange. Using acc204e I can't reproduce the error.

@raphael-leger
Copy link
Author

Ok I found out the issue. Thank you very much for your help and reactivity.

It seems that drf-yasg can not import two different defaults from the same file.

cf. in the given settings example, DEFAULT_INFO and DEFAULT_PAGINATOR_INSPECTORS both read from the file swagger.py (api.swagger.xxxxxxx)

When I move api_info into its own file info.py and the inspector in its own file inspector.py with the following settings everything works:

SWAGGER_SETTINGS = {
    "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}},
    "DEFAULT_INFO": "api.info.api_info",
    "DEFAULT_PAGINATOR_INSPECTORS": [
        'api.inspector.PageNumberPaginatorInspectorClass',
    ]
}

@samwho
Copy link

samwho commented Mar 16, 2021

I'm having this problem and can't for the life of me break the circular dependency.

I have a file in content/inspectors.py that contains only this:

from drf_yasg.inspectors.base import FilterInspector, NotHandled, openapi


class URLParamFilterInspector(FilterInspector):
    def get_filter_parameters(self, filter):
        if not filter.__name__ == "URLParamFilter":
            return NotHandled

        return [
            openapi.Parameter(
                filter.field_name, openapi.IN_QUERY, type=openapi.TYPE_STRING
            )
        ]

And in settings/base.py:

SWAGGER_SETTINGS = {
    "DEFAULT_INFO": {
        "title": "API",
    },
    "SECURITY_DEFINITIONS": {
        "JWT": {"type": "apiKey", "name": "Authorization", "in": "header"},
    },
    "DEFAULT_MODEL_RENDERING": ["example", "model"],
    "DEFAULT_FILTER_INSPECTORS": ["content.inspectors.URLParamFilterInspector"],
}

I fire up ./manage.py shell and try to run from content.inspectors import URLParamFilterInspector and get this error:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/usr/src/app/content/inspectors.py", line 1, in <module>
    from drf_yasg.inspectors.base import FilterInspector, NotHandled, openapi
  File "/usr/local/lib/python3.9/site-packages/drf_yasg/inspectors/__init__.py", line 15, in <module>
    ViewInspector.filter_inspectors = swagger_settings.DEFAULT_FILTER_INSPECTORS
  File "/usr/local/lib/python3.9/site-packages/drf_yasg/app_settings.py", line 122, in __getattr__
    val = perform_import(val, attr)
  File "/usr/local/lib/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 "/usr/local/lib/python3.9/site-packages/rest_framework/settings.py", line 168, in <listcomp>
    return [import_from_string(item, setting_name) for item in val]
  File "/usr/local/lib/python3.9/site-packages/rest_framework/settings.py", line 180, in import_from_string
    raise ImportError(msg)
ImportError: Could not import 'content.inspectors.URLParamFilterInspector' for API setting 'DEFAULT_FILTER_INSPECTORS'. ImportError: Module "content.inspectors" does not define a "URLParamFilterInspector" attribute/class.

I also get this error when attempting to start the development server normally, using ./manage.py runserver.

I'm not sure how I'm meant to subclass FilterInspector, it appears that there's an inescapable circular dependency caused by the line ViewInspector.filter_inspectors = swagger_settings.DEFAULT_FILTER_INSPECTORS. It will always try and load the filter inspectors before I can define one.

@samwho
Copy link

samwho commented Mar 16, 2021

If I try and cheese it like so:

from drf_yasg import openapi


class URLParamFilterInspector:
    def get_filter_parameters(self, filter):
        if not filter.__name__ == "URLParamFilter":
            return None

        return openapi.Parameter(
            filter.field_name, openapi.IN_QUERY, type=openapi.TYPE_STRING
        )

I get the error:

AssertionError: inspectors must subclass BaseInspector

@samwho
Copy link

samwho commented Mar 16, 2021

Added more cheese but it didn't work:

def URLParamFilterInspector():
    from drf_yasg import openapi
    from drf_yasg.inspectors import FilterInspector, NotHandled

    class _URLParamFilterInspector(FilterInspector):
        def get_filter_parameters(self, filter):
            if not filter.__name__ == "URLParamFilter":
                return NotHandled

            return openapi.Parameter(
                filter.field_name, openapi.IN_QUERY, type=openapi.TYPE_STRING
            )

    return _URLParamFilterInspector

Error:

AssertionError: inspector must be a class, not an object

You've made it difficult to trick your project into doing what I want to do 😄

@FreelanceDev217
Copy link

If anybody sees the same error, check if you imported the custom inspector in any other views. It caused circular import for me and removing all and just setting the DEFAULT_FILTER_INSPECTORS in Django settings, the errors have gone.

@raphael-leger
Copy link
Author

Issue workaround resolution here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants