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?
It's probably a circular import issue. Try breaking things up in separate modules.
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).
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.
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
Well then, that's strange. Using https://github.com/axnsan12/drf-yasg/commit/acc204e4ea6f16f8541eed1e940e9b7e4ebe6ea6 I can't reproduce the error.
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',
]
}
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.
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
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 馃槃
Most helpful comment
Ok I found out the issue. Thank you very much for your help and reactivity.
It seems that
drf-yasgcan not import two different defaults from the same file.cf. in the given settings example,
DEFAULT_INFOandDEFAULT_PAGINATOR_INSPECTORSboth read from the fileswagger.py(api.swagger.xxxxxxx)When I move
api_infointo its own fileinfo.pyand the inspector in its own fileinspector.pywith the following settings everything works: