From a70a8367ad7ca3f8c8db01a2dd6367d9dba4ac6f Mon Sep 17 00:00:00 2001 From: Nekmo Date: Sat, 11 May 2019 03:15:41 +0200 Subject: [PATCH 01/10] Django Rest Framework autohttp generator. --- sphinxcontrib/autohttp/drf.py | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 sphinxcontrib/autohttp/drf.py diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py new file mode 100644 index 0000000..c48095e --- /dev/null +++ b/sphinxcontrib/autohttp/drf.py @@ -0,0 +1,78 @@ +""" + sphinxcontrib.autohttp.drf + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + The sphinx.ext.autodoc-style HTTP API reference builder (from DRF) + for sphinxcontrib.httpdomain. + + :copyright: Copyright 2013 by Rodrigo Machado + :license: BSD, see LICENSE for details. + +""" + +import inspect +import re +from itertools import chain + +from docutils import nodes +from docutils.parsers.rst import Directive +from docutils.statemachine import ViewList + +from sphinx.util.nodes import nested_parse_with_titles +from sphinxcontrib.autohttp.common import http_directive + + +def get_routes(): + from rest_framework.schemas import SchemaGenerator + generator = SchemaGenerator( + title=None, url=None, description=None, + urlconf=None, patterns=None, + ) + for link in chain(*map(lambda x: x.links.values(), generator.get_schema().data.values())): + yield link + + +def get_schema_type(schema): + return { + 'Integer': 'int', + 'Number': 'int', + 'String': 'str', + 'Enum': 'choice', + }.get(schema.__class__.__name__) + + +class AutoDRFDirective(Directive): + + has_content = True + required_arguments = 0 + option_spec = {} + + def make_rst(self): + for link in get_routes(): + pass + for line in http_directive(link.action, link.url, link.description): + yield line + for field in link.fields: + line = ' :{} '.format({'form': '') + nested_parse_with_titles(self.state, result, node) + return node.children + + +def setup(app): + app.setup_extension('sphinxcontrib.httpdomain') + app.add_directive('autodrf', AutoDRFDirective) From 0d375a8f37d9d0158db211e2d7044de6bbd9b018 Mon Sep 17 00:00:00 2001 From: Nekmo Date: Sat, 11 May 2019 05:10:41 +0200 Subject: [PATCH 02/10] Fixed location parameter for field.location. --- sphinxcontrib/autohttp/drf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index c48095e..e744875 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -53,13 +53,13 @@ def make_rst(self): for line in http_directive(link.action, link.url, link.description): yield line for field in link.fields: - line = ' :{} '.format({'form': ' Date: Sat, 11 May 2019 20:11:59 +0200 Subject: [PATCH 03/10] Support nested schemas in Django Rest Framework. --- sphinxcontrib/autohttp/drf.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index e744875..09aabeb 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -15,21 +15,27 @@ from itertools import chain from docutils import nodes -from docutils.parsers.rst import Directive +from docutils.parsers.rst import Directive, directives from docutils.statemachine import ViewList from sphinx.util.nodes import nested_parse_with_titles from sphinxcontrib.autohttp.common import http_directive -def get_routes(): +def chain_routes(schema): + if schema.data: + yield from chain(*map(lambda x: chain_routes(x), schema.data.values())) + elif schema.links: + yield from schema.links.values() + + +def get_routes(urlconf=None): from rest_framework.schemas import SchemaGenerator generator = SchemaGenerator( title=None, url=None, description=None, - urlconf=None, patterns=None, + urlconf=urlconf, patterns=None, ) - for link in chain(*map(lambda x: x.links.values(), generator.get_schema().data.values())): - yield link + yield from chain_routes(generator.get_schema()) def get_schema_type(schema): @@ -45,11 +51,19 @@ class AutoDRFDirective(Directive): has_content = True required_arguments = 0 - option_spec = {} + option_spec = { + 'urlconf': directives.unchanged, + } + + @property + def urlconf(self): + urlconf = self.options.get('urlconf', None) + if not urlconf: + return None + return urlconf def make_rst(self): - for link in get_routes(): - pass + for link in get_routes(self.urlconf): for line in http_directive(link.action, link.url, link.description): yield line for field in link.fields: @@ -58,7 +72,7 @@ def make_rst(self): if type_: line += '{} '.format(type_) line += '{}: {}'.format(field.name, field.description or field.schema.description) - if type_ == 'choice': + if type_ == 'choice' and len(field.schema.enum) < 30: line += ' **Choices:** {}'.format(', '.join(map(lambda x: '*"{}"*'.format(x), field.schema.enum))) yield line From 8170770c0deb6a56b136d1470bfc6218251ae1f2 Mon Sep 17 00:00:00 2001 From: Nekmo Date: Thu, 8 Aug 2019 23:05:21 +0200 Subject: [PATCH 04/10] Modules option for autodrf. --- sphinxcontrib/autohttp/drf.py | 67 ++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index 09aabeb..d94aa39 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -10,8 +10,6 @@ """ -import inspect -import re from itertools import chain from docutils import nodes @@ -29,15 +27,19 @@ def chain_routes(schema): yield from schema.links.values() -def get_routes(urlconf=None): - from rest_framework.schemas import SchemaGenerator - generator = SchemaGenerator( +def get_routes(urlconf=None, modules=None): + generator = get_schema_generator_class()( title=None, url=None, description=None, - urlconf=urlconf, patterns=None, + urlconf=urlconf, patterns=None, modules=modules, ) yield from chain_routes(generator.get_schema()) +# def get_modules(modules=None): +# from django.utils.module_loading import import_string +# return [import_string(module) for module in map(lambda x: x.strip(), modules.split(','))] + + def get_schema_type(schema): return { 'Integer': 'int', @@ -47,12 +49,51 @@ def get_schema_type(schema): }.get(schema.__class__.__name__) +def issubmodule(cls, modules): + modules = list(map(lambda x: '{}.'.format(x), modules)) + mod = cls.__module__ + print(mod, modules) + return bool(next(filter(lambda x: mod.startswith(x), modules), None)) + + +def get_schema_generator_class(): + from rest_framework.schemas.generators import EndpointEnumerator + from rest_framework.schemas import SchemaGenerator + + class HttpDomainEndpointEnumerator(EndpointEnumerator): + modules = None + + def get_api_endpoints(self, patterns=None, prefix=''): + endpoints = super(HttpDomainEndpointEnumerator, self).get_api_endpoints(patterns, prefix) + endpoints = map(lambda x: x, endpoints) + if self.modules: + endpoints = list(filter(lambda x: issubmodule(x[2].cls, self.modules), endpoints)) + return endpoints + + + class HttpDomainSchemaGenerator(SchemaGenerator): + def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None, modules=None): + super(HttpDomainSchemaGenerator, self).__init__(title, url, description, patterns, urlconf) + self.modules = modules + + @property + def endpoint_inspector_cls(self): + """Create a new class with the modules attribute. There is no possibility to put + the argument when starting the instance + """ + class EndpointEnumerator(HttpDomainEndpointEnumerator): + modules = self.modules + return EndpointEnumerator + return HttpDomainSchemaGenerator + + class AutoDRFDirective(Directive): has_content = True required_arguments = 0 option_spec = { 'urlconf': directives.unchanged, + 'modules': directives.unchanged, } @property @@ -62,8 +103,19 @@ def urlconf(self): return None return urlconf + @property + def modules(self): + return self.options.get('modules', None) or None + def make_rst(self): - for link in get_routes(self.urlconf): + if self.modules: + modules = [module.strip() for module in self.modules.split(',')] + else: + modules = [] + links = get_routes(self.urlconf, modules) + # if self.modules: + # links = + for link in links: for line in http_directive(link.action, link.url, link.description): yield line for field in link.fields: @@ -76,7 +128,6 @@ def make_rst(self): line += ' **Choices:** {}'.format(', '.join(map(lambda x: '*"{}"*'.format(x), field.schema.enum))) yield line - def run(self): node = nodes.section() node.document = self.state.document From a939780fdaea74804f0e2e864fc50e390bd99647 Mon Sep 17 00:00:00 2001 From: Nekmo Date: Thu, 29 Aug 2019 00:01:34 +0200 Subject: [PATCH 05/10] =?UTF-8?q?Documentaci=C3=B3n=20de=20API=20de=20Revi?= =?UTF-8?q?ews.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sphinxcontrib/autohttp/drf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index d94aa39..cfc1a86 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -52,7 +52,6 @@ def get_schema_type(schema): def issubmodule(cls, modules): modules = list(map(lambda x: '{}.'.format(x), modules)) mod = cls.__module__ - print(mod, modules) return bool(next(filter(lambda x: mod.startswith(x), modules), None)) From 3b67d3eeee7b6e918b648820e5aed1a242240acb Mon Sep 17 00:00:00 2001 From: nekmo Date: Mon, 21 Oct 2019 16:01:34 +0200 Subject: [PATCH 06/10] Ignore query params in PUT/PATCH/etc. --- sphinxcontrib/autohttp/drf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index cfc1a86..8572e90 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -118,6 +118,9 @@ def make_rst(self): for line in http_directive(link.action, link.url, link.description): yield line for field in link.fields: + if (link.action != 'get' or '{id}' in link.url) and field.location == 'query': + # Ignore query params in PUT/PATCH/etc. + continue line = ' :{} '.format({'form': ' Date: Mon, 21 Oct 2019 16:57:44 +0200 Subject: [PATCH 07/10] Titles for API docs. --- sphinxcontrib/autohttp/drf.py | 65 ++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index 8572e90..d852775 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -20,6 +20,16 @@ from sphinxcontrib.autohttp.common import http_directive +LEVELS = [ + '#', + '*', + '=', + '-', + '^', + '"', +] + + def chain_routes(schema): if schema.data: yield from chain(*map(lambda x: chain_routes(x), schema.data.values())) @@ -32,7 +42,7 @@ def get_routes(urlconf=None, modules=None): title=None, url=None, description=None, urlconf=urlconf, patterns=None, modules=modules, ) - yield from chain_routes(generator.get_schema()) + return generator.get_schema() # def get_modules(modules=None): @@ -93,6 +103,7 @@ class AutoDRFDirective(Directive): option_spec = { 'urlconf': directives.unchanged, 'modules': directives.unchanged, + 'titles': directives.unchanged, } @property @@ -106,29 +117,49 @@ def urlconf(self): def modules(self): return self.options.get('modules', None) or None + @property + def titles(self): + titles = self.options.get('titles', None) + if titles: + return int(titles.strip()) + def make_rst(self): if self.modules: modules = [module.strip() for module in self.modules.split(',')] else: modules = [] - links = get_routes(self.urlconf, modules) + schema = get_routes(self.urlconf, modules) + yield from self.get_schema_lines(schema, self.titles) # if self.modules: # links = - for link in links: - for line in http_directive(link.action, link.url, link.description): - yield line - for field in link.fields: - if (link.action != 'get' or '{id}' in link.url) and field.location == 'query': - # Ignore query params in PUT/PATCH/etc. - continue - line = ' :{} '.format({'form': ' Date: Mon, 21 Oct 2019 17:12:51 +0200 Subject: [PATCH 08/10] Spaces between titles. --- sphinxcontrib/autohttp/drf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index d852775..d5aceb0 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -137,8 +137,10 @@ def get_schema_lines(self, schema, titles_level): if schema.data: for title, sub_schema in schema.data.items(): if titles_level: + yield '' yield title.title().replace('_', ' ') yield LEVELS[(titles_level + 1) % len(LEVELS)] * len(title) + yield '' yield from self.get_schema_lines(sub_schema, (titles_level + 1) if titles_level else None) # yield from chain(*map(lambda x: chain_routes(x), schema.data.values())) elif schema.links: From ffc566e5e9882a99d8cee70fe559d16efe39882b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Garc=C3=ADa=20Rojas?= Date: Sat, 20 Nov 2021 22:49:33 +0100 Subject: [PATCH 09/10] Fix generating schema with nested routers --- sphinxcontrib/autohttp/drf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index d5aceb0..3a8a33d 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -134,6 +134,11 @@ def make_rst(self): # links = def get_schema_lines(self, schema, titles_level): + # Generate the links of the actual level. + if schema.links: + for link_name, link in schema.links.items(): + yield from self.get_link_lines(link_name, link) + # Visit recursively others levels and generate links if exists. if schema.data: for title, sub_schema in schema.data.items(): if titles_level: @@ -143,9 +148,6 @@ def get_schema_lines(self, schema, titles_level): yield '' yield from self.get_schema_lines(sub_schema, (titles_level + 1) if titles_level else None) # yield from chain(*map(lambda x: chain_routes(x), schema.data.values())) - elif schema.links: - for link_name, link in schema.links.items(): - yield from self.get_link_lines(link_name, link) def get_link_lines(self, link_name, link): for line in http_directive(link.action, link.url, link.description): From a968b685a688a385b458b29db5d67df903339474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Garc=C3=ADa=20Rojas?= Date: Wed, 24 Nov 2021 09:08:38 +0100 Subject: [PATCH 10/10] Add swagger_fake_view to the api endpoints --- sphinxcontrib/autohttp/drf.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sphinxcontrib/autohttp/drf.py b/sphinxcontrib/autohttp/drf.py index 3a8a33d..a79b4b3 100644 --- a/sphinxcontrib/autohttp/drf.py +++ b/sphinxcontrib/autohttp/drf.py @@ -93,6 +93,16 @@ def endpoint_inspector_cls(self): class EndpointEnumerator(HttpDomainEndpointEnumerator): modules = self.modules return EndpointEnumerator + + def _get_paths_and_endpoints(self, request): + """Add ``swagger_fake_view`` to every api view. + """ + paths, view_endpoints = super()._get_paths_and_endpoints(request) + for view_endpoint in view_endpoints: + path, method, view = view_endpoint + setattr(view, 'swagger_fake_view', True) + return paths, view_endpoints + return HttpDomainSchemaGenerator