def get_validation_description(view, method): """ Returns validation description in format: ### Validation: validate method docstring * field1 name * field1 validation docstring * field2 name * field2 validation docstring """ if method not in ('PUT', 'PATCH', 'POST') or not hasattr(view, 'get_serializer'): return '' serializer = view.get_serializer() description = '' if hasattr(serializer, 'validate') and serializer.validate.__doc__ is not None: description += formatting.dedent( smart_text(serializer.validate.__doc__)) for field in serializer.fields.values(): if not hasattr(serializer, 'validate_' + field.field_name): continue field_validation = getattr(serializer, 'validate_' + field.field_name) if field_validation.__doc__ is not None: docstring = formatting.dedent(smart_text( field_validation.__doc__)).replace('\n', '\n\t') field_description = '* %s\n * %s' % (field.field_name, docstring) description += ('\n' + field_description if description else field_description) return '### Validation:\n' + description if description else ''
def get_view_description(view_cls, html=False): """ Given a view class, return a textual description to represent the view. This name is used in the browsable API, and in OPTIONS responses. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting. """ description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if hasattr(getattr(view_cls, 'serializer_class', 'None'), 'Meta'): doc_url = get_doc_url( 'api', '{0}s'.format( view_cls.serializer_class.Meta.model.__name__.lower() ) ) else: doc_url = get_doc_url('api') if html: return ( formatting.markup_description(description) + mark_safe(DOC_TEXT.format(doc_url)) ) return description
def get_view_description(view_cls, html=False): """ Given a view class, return a textual description to represent the view. This name is used in the browsable API, and in OPTIONS responses. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting. """ description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if hasattr(view_cls, 'serializer_class'): doc_url = get_doc_url( 'api' '{0}s'.format( view_cls.serializer_class.Meta.model.__name__.lower() ) ) else: doc_url = get_doc_url('api') description = '\n\n'.join(( description, DOC_TEXT.format(doc_url) )) if html: return formatting.markup_description(description) return description
def get_description(self, path, method, view): """ Determine a link description. This will be based on the method docstring if one exists, or else the class docstring. """ method_name = getattr(view, 'action', method.lower()) method_docstring = getattr(view, method_name, None).__doc__ if method_docstring: # An explicit docstring on the method or action. return formatting.dedent(smart_text(method_docstring)) description = view.get_view_description() lines = [line.strip() for line in description.splitlines()] current_section = '' sections = {'': ''} for line in lines: if header_regex.match(line): current_section, seperator, lead = line.partition(':') sections[current_section] = lead.strip() else: sections[current_section] += line + '\n' header = getattr(view, 'action', method.lower()) if header in sections: return sections[header].strip() if header in self.coerce_method_names: if self.coerce_method_names[header] in sections: return sections[self.coerce_method_names[header]].strip() return sections[''].strip()
def get_description(self, path, method, view): """ Determine a link description. This will be based on the method docstring if one exists, or else the class docstring. """ method_name = getattr(view, 'action', method.lower()) method_docstring = getattr(view, method_name, None).__doc__ if method_docstring: # An explicit docstring on the method or action. return formatting.dedent(smart_text(method_docstring)) description = view.get_view_description() lines = [line.strip() for line in description.splitlines()] current_section = '' sections = {'': ''} for line in lines: if header_regex.match(line): current_section, seperator, lead = line.partition(':') sections[current_section] = lead.strip() else: sections[current_section] += '\n' + line header = getattr(view, 'action', method.lower()) if header in sections: return sections[header].strip() if header in self.coerce_method_names: if self.coerce_method_names[header] in sections: return sections[self.coerce_method_names[header]].strip() return sections[''].strip()
def get_notes(self): """ Returns the body of the docstring trimmed before any parameters are listed. First, get the class docstring and then get the method's. The methods will always inherit the class comments. """ docstring = "" class_docs = get_view_description(self.callback) class_docs = IntrospectorHelper.strip_yaml_from_docstring(class_docs) class_docs = IntrospectorHelper.strip_params_from_docstring(class_docs) method_docs = self.get_docs() if class_docs is not None: docstring += class_docs + " \n" if method_docs is not None: method_docs = formatting.dedent(smart_text(method_docs)) method_docs = IntrospectorHelper.strip_yaml_from_docstring( method_docs ) method_docs = IntrospectorHelper.strip_params_from_docstring( method_docs ) docstring += '\n' + method_docs docstring = docstring.strip() return do_markdown(docstring)
def get_notes(self): """ Returns the body of the docstring trimmed before any parameters are listed. First, get the class docstring and then get the method's. The methods will always inherit the class comments. """ docstring = "" class_docs = self.callback.__doc__ or '' class_docs = smart_text(class_docs) class_docs = IntrospectorHelper.strip_yaml_from_docstring(class_docs) class_docs = IntrospectorHelper.strip_params_from_docstring(class_docs) method_docs = self.get_docs() if class_docs is not None: docstring += class_docs + " \n" if method_docs is not None: method_docs = formatting.dedent(smart_text(method_docs)) method_docs = IntrospectorHelper.strip_yaml_from_docstring( method_docs ) method_docs = IntrospectorHelper.strip_params_from_docstring( method_docs ) docstring += '\n' + method_docs # Markdown is optional if apply_markdown: docstring = apply_markdown(docstring) else: docstring = docstring.replace("\n\n", "<br/>") return docstring
def get_view_summary(view_cls): """Return the viewset's general summary. This will extract the paragraphs between the first line and the first operation description. Example docstring: Some operation description here that will not be included. This text will be included below the tag. This text will also be included. create: First operation in the docstring which will not be included. :param type view_cls: the view class to extra the docstring from. """ try: summary = view_cls.__doc__.split("\n\n", 1)[1].split(":", 1)[0] if "\n\n" in summary: summary = summary.rsplit("\n\n", 1)[0].strip().replace("\r", "") return formatting.dedent(smart_str(summary)) except (AttributeError, IndexError): pass return ""
def get_view_description(view_cls, html=False, instance=None): """ Given a view class, return a textual description to represent the view. This name is used in the browsable API, and in OPTIONS responses. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting. """ # doc can come from the class or from a detail_route or a list_route method documented_object = view_cls if instance: view_method = get_subroute_method(instance) if view_method: documented_object = view_method description = documented_object.__doc__ or '' description = formatting.dedent(smart_text(description)) if hasattr(documented_object, 'filter_class'): default_filter = documented_object.filter_class() filters = default_filter.filters filters_doc = description_tpl.render( Context({ 'filters': filters, 'default_ordering': default_filter._default_ordering_field() })) description += filters_doc if html: return formatting.markup_description(description) return description
def test_docstring_is_not_stripped_by_get_description(): class ExampleDocstringAPIView(APIView): """ === title * item a * item a-a * item a-b * item b - item 1 - item 2 code block begin code code code code block end the end """ def get(self, *args, **kwargs): pass def post(self, request, *args, **kwargs): pass view = ExampleDocstringAPIView() schema = view.schema descr = schema.get_description('example', 'get') # the first and last character are '\n' correctly removed by get_description assert descr == formatting.dedent(ExampleDocstringAPIView.__doc__[1:][:-1])
def get_view_doc(view, html=True): """ Build view documentation. Return in html format. If you want in markdown format, use html=False """ try: description = view.__doc__ or '' description = formatting.dedent(smart_text(description)) # include filters in description filter_fields = get_filter_fields(view) if filter_fields: filter_doc = ['\n\n\n## Filters', ''] for f in filter_fields: filter_doc.append('- `%s`' % f) description += '\n'.join(filter_doc) # replace {api_url} by current base url api_url = "/api" description = description.replace('{api_url}', api_url) if html: description = formatting.markup_description(description) return description except: import traceback traceback.print_exc() raise
def get_notes(self): """ Returns the body of the docstring trimmed before any parameters are listed. First, get the class docstring and then get the method's. The methods will always inherit the class comments. """ docstring = "" class_docs = get_view_description(self.callback) class_docs = IntrospectorHelper.strip_yaml_from_docstring(class_docs) class_docs = IntrospectorHelper.strip_params_from_docstring(class_docs) method_docs = self.get_docs() if class_docs is not None: docstring += class_docs + " \n" if method_docs is not None: method_docs = formatting.dedent(smart_text(method_docs)) method_docs = IntrospectorHelper.strip_yaml_from_docstring( method_docs) method_docs = IntrospectorHelper.strip_params_from_docstring( method_docs) docstring += '\n' + method_docs docstring = docstring.strip() return do_markdown(docstring)
def get_notes(self): """ Returns the body of the docstring trimmed before any parameters are listed. First, get the class docstring and then get the method's. The methods will always inherit the class comments. """ docstring = "" class_docs = self.callback.__doc__ or '' class_docs = smart_text(class_docs) class_docs = IntrospectorHelper.strip_yaml_from_docstring(class_docs) class_docs = IntrospectorHelper.strip_params_from_docstring(class_docs) method_docs = self.get_docs() if class_docs is not None: docstring += class_docs + " \n" if method_docs is not None: method_docs = formatting.dedent(smart_text(method_docs)) method_docs = IntrospectorHelper.strip_yaml_from_docstring( method_docs) method_docs = IntrospectorHelper.strip_params_from_docstring( method_docs) docstring += '\n' + method_docs # Markdown is optional if apply_markdown: docstring = apply_markdown(docstring) else: docstring = docstring.replace("\n\n", "<br/>") return docstring
def amara_get_view_description(view_cls, html=False): description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if not html: return description return mark_safe('<div class="api-description">{}</div>'.format( markup_description(description)))
def _extract_schema_data(self, request): serializer = self.schema_for() model = serializer.opts.model # basename = model._meta.object_name.lower() modelname = model.__name__ description = dedent(model.__doc__) rw_properties = {} ro_properties = {} required = [] for name, field in serializer.get_fields().items(): # FIXME: need to decide format for specifying JSON Schema for # method fields... if isinstance(field, SerializerMethodField): continue if getattr(field, 'required', False): required.append(name) data = {} data['description'] = getattr(field, 'help_text', '') data['type'] = self._schema_type_for(field) if isinstance(field, ChoiceField): # RelatedField also provides choices; don't enumerate those... data['enum'] = [ stored for stored, presentation in field.choices ] if getattr(field, 'max_length', None) is not None: data['maxLength'] = field.max_length if getattr(field, 'min_length', None) is not None: data['minLength'] = field.min_length if isinstance(field, DateTimeField): data['format'] = 'date-time' if isinstance(field, EmailField): data['format'] = 'email' if isinstance(field, PrimaryKeyRelatedField): opts = field.queryset.model._meta rel_basename = '{}-{}'.format(opts.app_label, opts.object_name.lower()) rel_href = reverse('{}-list'.format(rel_basename), request=request) + '{$}/' rel_targetschema = reverse( 'schema-{}-detail'.format(rel_basename), request=request) data['links'] = [{ 'rel': 'full', 'href': rel_href, 'targetSchema': { '$ref': rel_targetschema, } }] if field.read_only: data['readOnly'] = True ro_properties[name] = data else: rw_properties[name] = data return { 'modelname': modelname, 'description': description, 'rw_properties': rw_properties, 'ro_properties': ro_properties, 'required': required, }
def parse_docstring(cls, docstring): docstring = dedent(docstring) match = cls.META_REGEX.search(docstring) if match is not None: meta = yaml.safe_load(match.group(1)) docstring = cls.META_REGEX.sub("", docstring).strip() else: meta = None return docstring, meta
def get_restructuredtext(view_cls, html=False): from docutils import core description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if html: parts = core.publish_parts(source=description, writer_name='html') html = parts['body_pre_docinfo'] + parts['fragment'] return mark_safe(html) return description
def get_view_description(self, html=False): description = self.__doc__ or """ Returns a nested list of objects that would be also be deleted when this object is deleted. """ description = formatting.dedent(description) if html: return formatting.markup_description(description) return description
def _get_yaml_docstring(self, method: str, docstring: str, **many: Dict[str, bool]) -> list: """ Parse Docstring formatted in YAML notation :param method: View method :param docstring: The Docstring from method or from class :param many: Pass <property=True> to append/update existing data, else overwrite :return: List of properties from YAML-formatted Docstring """ method = "get" if method == "list" else method # Invalid properties will be ignored valid_properties = ("summary", "description", "tags", "responses", 200, 201, 202, 204, 400, 401, 403, 404, 500, 502, 503) # Create valid properties many dict (False: overwrite, True: append/update) for valid_property in valid_properties: many.setdefault(valid_property, False) # Same checks got from .get_descriptions() coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES if method in coerce_method_names: method = coerce_method_names[method] docstring_for_yaml = self._yaml_safe_clean(docstring) try: # Load YAML yml = yaml.load(docstring_for_yaml, Loader=yaml.SafeLoader) except ScannerError: # Invalid YAML, let's store the string in description key return [{ "key": "description", "value": "" + "\n".join( line.strip() for line in formatting.dedent(docstring).splitlines()), "append": many["description"] }] result = yml # None YAML, let's return and empty description if yml is None: return [{ "key": "description", "value": "", "append": many["description"] }] # Method doesn't exist in yml if method not in yml: result = {method: yml} # Method property value is str if isinstance(result[method], str): result = {method: {"description": result[method]}} # Return valid property return [{ "key": k, "value": v.strip() if isinstance(v, str) else v, "append": many[k] } for k, v in result[method].items() if k in valid_properties]
def get_view_description(self, obj, attr='__doc__', html=False): """ Get the doc string from either cls or function, parse it and return """ description = getattr(obj, attr, "No description provided by developer") description = formatting.dedent(smart_text(description)) if html: return formatting.markup_description(description) return description
def get_view_description(view_cls, html=False): """ Given a view class, return a textual description to represent the view. This name is used in the browsable API, and in OPTIONS responses. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting. """ description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if html: return formatting.markup_description(description) return description
def get_description(self, view, status_code): try: description = getattr(view, view.action).__doc__ or '' description = formatting.dedent( smart_text(description)).split('---') description = description[0] if isinstance(description, list) else description return formatting.markup_description(description) except (AttributeError, TypeError): return super(ShoutitBrowsableAPIRenderer, self).get_description(view, status_code)
def get_paths(self, top, components, request, public): """ Generate the Swagger Paths for the API from the given endpoints. """ if not top: return None ret=[] # import pdb; pdb.set_trace() for category in top: # print('~~~~~~~~~') endpoints=category['points'] if endpoints is None or len(endpoints)==0: continue prefix = self.get_path(category['prefix']) if prefix.endswith("/"): prefix=prefix[0:-1] assert '{' not in prefix, "base path cannot be templated in swagger 2.0" paths = {} for path, (view_cls, methods) in sorted(endpoints.items()): _path={} #print(path) for method, view in methods: # print(method) # if not self.should_include_endpoint(path, method, view, public): # continue method_lower=method.lower() method_name = getattr(view, 'action', method_lower) method_docstring = getattr(view, method_name, None).__doc__ if method_docstring: method_docstring = self._get_description_section(view, method.lower(), formatting.dedent(smart_text(method_docstring))) else: method_docstring = self._get_description_section(view, getattr(view, 'action', method.lower()), view.get_view_description()) if not method_docstring: method_docstring = "_" (_name, _desc) = self.split_summary_from_description(method_docstring) if _name is None: _name = _desc _path[method_lower]={"name": _name, "description": _desc} path_suffix = path[len(prefix):] if not path_suffix.startswith('/'): path_suffix = '/' + path_suffix paths[path_suffix]=_path if not paths is None: ret.append({"basePath":prefix,"basePathArray":prefix.strip("/").split("/"),"paths":paths}) #print(ret) return ret
def create_yaml_object(self, docstring): """Create YAML object from docstring""" base, yaml_string = self.split_docstring(docstring) if not yaml_string: return None yaml_string = formatting.dedent(yaml_string) try: # Note that it's not `safe_load`. But docstrings shouldn't really # have unsafe values, right?.. return yaml.load(yaml_string) except yaml.YAMLError: # TODO: log?.. return None
def format_docstring(self, view, method, docstring): macros = settings.BROWSABLE_DOCUMENT_MACROS if view: macros['FILTERS'] = get_filters(view) if '%(SERIALIZER)s' in docstring: macros['SERIALIZER'] = get_serializer(view, include_read_only=True) if '%(WRITABLE_SERIALIZER)s' in docstring: macros['WRITABLE_SERIALIZER'] = get_serializer(view, include_read_only=False) string = formatting.dedent(docstring) formatted = string % macros formatted = self.substitute_urls(view, method, formatted) string = smart_text(formatted) return formatting.markup_description(string)
def create_yaml_object(self, docstring): """Create YAML object from docstring""" docstring = trim_docstring(docstring) p = re.compile('^|\n{}'.format(YAMLDocstringParser.SPLITTER)) splitted = p.split(docstring) if len(splitted) < 2: return None yaml_string = splitted[1] yaml_string = formatting.dedent(yaml_string) try: return yaml.load(yaml_string) except yaml.YAMLError as e: return None
def load_obj_from_docstring(self, docstring): """Loads YAML from docstring""" # update one dict by other recursively def recursive_update(obj, other): for key, value in other.iteritems(): if key in obj and key != 'overwrite': # if value is dictionary we need to update it if isinstance(value, dict) and not value.get('overwrite', False): recursive_update(obj[key], other[key]) # if value is a list we need to extend it elif isinstance(value, list): obj[key].extend(value) else: obj[key] = value else: obj[key] = value if not docstring: return {} split_lines = trim_docstring(docstring).split('\n') # Cut YAML from rest of docstring for index, line in enumerate(split_lines): line = line.strip() if line.startswith('---'): cut_from = index break else: return {} yaml_string = formatting.dedent("\n".join(split_lines[cut_from:])) try: yaml_obj = yaml.load(yaml_string) # if is parent view specified, we need to get docs from parent view if 'inherit_docs_from' in yaml_obj: parent_class = self._load_class( yaml_obj['inherit_docs_from'], self.method_introspector.callback) parent_docs = self.method_introspector.get_inherited_docs( parent_class) parent_obj = self.load_obj_from_docstring( docstring=parent_docs) recursive_update(parent_obj, yaml_obj) yaml_obj = parent_obj except yaml.YAMLError as e: self.yaml_error = e return {} return yaml_obj
def get_metadata_content_tuple(self): """Tries to load the frontmatter data and content for the current action type for the operation """ view_docs = formatting.dedent(self.view.__doc__) docs = list(map(lambda x: f'---{x}', view_docs.split('===')[1:])) method = self.method action = getattr(self.view, 'action_map', {}).get(method.lower(), None) if self.view.action_map else None for doc_bloc in docs: metadata, content = frontmatter.parse(doc_bloc) if not metadata: continue action_to_map = metadata.get('action', 'default') if action_to_map == action or (isinstance(action_to_map, list) and action in action_to_map): return metadata, content if action and hasattr(self.view, action): action_func = getattr(self.view, action, None) action_docs = formatting.dedent(action_func.__doc__) if '===' in action_docs: metadata, content = frontmatter.parse(action_docs.replace('===', '---')) return metadata, content return None, action_docs return None, view_docs
def get_view_description(cls, html=False): description = '' if getattr(cls, 'get_description', None): cache_key = cls.__name__ cached_description = cache.get(cache_key) if not cached_description: description = cls.get_description() description = formatting.dedent(smart_text(description)) # Cache for 1 hour (if we update description, it will take 1 hour to show) cache.set(cache_key, description, 60 * 60) else: description = cached_description if html: return formatting.markup_description(description) return description
def get_view_description(view_cls, html=False): from django_markwhat.templatetags.markup import restructuredtext description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if html: rst_doc = getattr(view_cls, 'rst_doc', None) if rst_doc: if isinstance(rst_doc, str): path = os.path.dirname(sys.modules[view_cls.__module__].__file__) path = os.path.join(path, rst_doc) with open(path) as rst: return restructuredtext(rst.read()) return restructuredtext(description) return formatting.markup_description(description) return description
def get_summary(self, path, method): """ Determine a path summary. This will be based on the method docstring if one exists. """ view = self.view method_name = getattr(view, 'action', method.lower()) method_docstring = getattr(view, method_name, None).__doc__ if method_docstring: # An explicit docstring on the method or action. return self._get_summary_section( view, method.lower(), formatting.dedent(smart_str(method_docstring)))
def get_view_description(cls, html=False): description = '' if getattr(cls, 'get_description', None): cache_key = cls.__name__ cached_description = cache.get(cache_key) if not cached_description: description = cls.get_description() description = formatting.dedent(smart_text(description)) # Cache for 1 hour (if we update description, it will take 1 hour to show) cache.set(cache_key, description, 60*60) else: description = cached_description if html: return formatting.markup_description(description) return description
def get_description(self, path, method): """ Determine a link description. This will be based on the method docstring if one exists, or else the class docstring. """ view = self.view method_name = getattr(view, 'action', method.lower()) method_docstring = getattr(view, method_name, None).__doc__ if method_docstring: # An explicit docstring on the method or action. return self._get_description_section(view, method.lower(), formatting.dedent(smart_text(method_docstring))) else: return self._get_description_section(view, getattr(view, 'action', method.lower()), view.get_view_description())
def _get_type_from_docstring(value, default=None): """ Convert docstring into object suitable for inclusion as documentation. It tries to parse the docstring as JSON, falling back on provided default value. """ if value: try: return json.loads(value) except ValueError: return formatting.dedent(str(value)) if default is not None: return default return None
def get_view_description(view, html=False): """ Given a view instance, return a textual description to represent the view. This name is used in the browsable API, and in OPTIONS responses. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting. """ # Description may be set by some Views, such as a ViewSet. description = getattr(view, 'description', None) if description is None: description = view.__class__.__doc__ or '' description = formatting.dedent(smart_str(description)) if html: return formatting.markup_description(description) return description
def get_view_description(view, html=False): """ Given a view class, return a textual description to represent the view. This name is used in the browsable API, and in OPTIONS responses. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting. """ # Description may be set by some Views, such as a ViewSet. description = getattr(view, 'description', None) if description is None: description = view.__class__.__doc__ or '' description = formatting.dedent(smart_text(description)) if html: return formatting.markup_description(description) return description
def get_view_description(view_cls, html=False): """ Alternative django-rest-framework ``VIEW_DESCRIPTION_FUNCTION`` that allows RestructuredText to be used instead of Markdown. This allows docstrings to be used in the DRF-generated HTML views and in Sphinx-generated API views. """ description = view_cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if html: # Get just the HTML parts corresponding to the docstring parts = core.publish_parts(source=description, writer_name='html') html = parts['body_pre_docinfo'] + parts['fragment'] # Mark the output as safe for rendering as-is return mark_safe(html) return description
def load_obj_from_docstring(self, docstring): """Loads YAML from docstring""" # update one dict by other recursively def recursive_update(obj, other): for key, value in other.iteritems(): if key in obj and key != 'overwrite': # if value is dictionary we need to update it if isinstance(value, dict) and not value.get('overwrite', False): recursive_update(obj[key], other[key]) # if value is a list we need to extend it elif isinstance(value, list): obj[key].extend(value) else: obj[key] = value else: obj[key] = value if not docstring: return {} split_lines = trim_docstring(docstring).split('\n') # Cut YAML from rest of docstring for index, line in enumerate(split_lines): line = line.strip() if line.startswith('---'): cut_from = index break else: return {} yaml_string = formatting.dedent("\n".join(split_lines[cut_from:])) try: yaml_obj = yaml.load(yaml_string) # if is parent view specified, we need to get docs from parent view if 'inherit_docs_from' in yaml_obj: parent_class = self._load_class(yaml_obj['inherit_docs_from'], self.method_introspector.callback) parent_docs = self.method_introspector.get_inherited_docs(parent_class) parent_obj = self.load_obj_from_docstring(docstring=parent_docs) recursive_update(parent_obj, yaml_obj) yaml_obj = parent_obj except yaml.YAMLError as e: self.yaml_error = e return {} return yaml_obj
def get_googledocstring(view_cls, html=False): """Parses a docstring containing Google-style docs into HTML. This uses the Napoleon Sphinx extension to parse Google-style argument docstrings into reStructuredText, then convert these into HTML for django-rest-swagger. """ from docutils import core description = view_cls.__doc__ or '' config = Config(napoleon_use_param=False, napoleon_use_rtype=False) description = GoogleDocstring(description, config=config) description = formatting.dedent(smart_text(description)) if html: parts = core.publish_parts(source=description, writer_name='html') html = parts['body_pre_docinfo'] + parts['fragment'] return mark_safe(html) return description
def get_description(self): """ Returns the body of the docstring trimmed before any parameters are listed. First, get the class docstring and then get the method's. The methods will always inherit the class comments. """ class_docs = self._clean_docs(get_view_description(self.callback)) method_docs = self._clean_docs(formatting.dedent(smart_text(self.get_docs()))) if self.yaml_parser.get_param('replace_docs', False): docstring = method_docs else: docstring = "\n\n".join(filter(None, [class_docs, method_docs])) explicit_docs = self.yaml_parser.get_param("docs", None) if explicit_docs is not None: docstring = explicit_docs.format(super=docstring) return docstring.strip()
def load_obj_from_docstring(self, docstring): """Loads YAML from docstring""" split_lines = trim_docstring(docstring).split('\n') # Cut YAML from rest of docstring for index, line in enumerate(split_lines): line = line.strip() if line.startswith('---'): cut_from = index break else: return None yaml_string = "\n".join(split_lines[cut_from:]) yaml_string = formatting.dedent(yaml_string) try: return yaml.load(yaml_string) except yaml.YAMLError as e: self.yaml_error = e return None
def format_docstring(self, view, method, docstring): macros = settings.BROWSABLE_DOCUMENT_MACROS if view: macros['FILTERS'] = get_filters(view) if 'list' == method: ordering_field = get_ordering_field(view, method) if ordering_field: ordering_string = ORDERING_STRING + " %s ." % ordering_field macros['FILTERS'] += ordering_string if '%(SERIALIZER)s' in docstring: macros['SERIALIZER'] = get_serializer(view, include_read_only=True) if '%(WRITABLE_SERIALIZER)s' in docstring: macros['WRITABLE_SERIALIZER'] = get_serializer(view, include_read_only=False) if hasattr(view, 'docstring_macros'): macros.update(view.docstring_macros) string = formatting.dedent(docstring) formatted = string % macros formatted = self.substitute_urls(view, method, formatted) string = smart_text(formatted) return formatting.markup_description(string)
def test_renders_default_schema_with_custom_title_url_and_description(self): expected_out = """info: description: Sample description title: SampleAPI version: '' openapi: 3.0.0 paths: /: get: operationId: list servers: - url: http://api.sample.com/ """ call_command('generateschema', '--title=SampleAPI', '--url=http://api.sample.com', '--description=Sample description', stdout=self.out) self.assertIn(formatting.dedent(expected_out), self.out.getvalue())
def get_view_description(view_cls, html=False): """ Alternative view description function to be used as the DRF ``VIEW_DESCRIPTION_FUNCTION`` so that RestructuredText can be used instead of the DRF-default MarkDown. Except for the RST parts, derived by [email protected] from the DRF default get_view_description function. """ description = view_cls.__doc__ or '' description = formatting.dedent(force_bytes_or_smart_bytes(description)) if html: # from https://wiki.python.org/moin/ReStructuredText -- we use the # third recipe to get just the HTML parts corresponding to the ReST # docstring: parts = core.publish_parts(source=description, writer_name='html') html = parts['body_pre_docinfo']+parts['fragment'] # have to use mark_safe so our HTML will get explicitly marked as # safe and will be correctly rendered return mark_safe(html) return description
def get_description(self, path, method): """ Determine a link description. This will be based on the method docstring if one exists, or else the class docstring. """ view = self.view method_name = getattr(view, 'action', method.lower()) method_docstring = getattr(view, method_name, None).__doc__ if method_docstring: # An explicit docstring on the method or action. return formatting.dedent(smart_text(method_docstring)) description = view.get_view_description() lines = [line for line in description.splitlines()] current_section = '' sections = {'': ''} for line in lines: if header_regex.match(line): current_section, seperator, lead = line.partition(':') sections[current_section] = lead.strip() else: sections[current_section] += '\n' + line # TODO: SCHEMA_COERCE_METHOD_NAMES appears here and in `SchemaGenerator.get_keys` coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES header = getattr(view, 'action', method.lower()) if header in sections: return sections[header].strip() if header in coerce_method_names: if coerce_method_names[header] in sections: return sections[coerce_method_names[header]].strip() return sections[''].strip()
def format_docstring(self, docstring): formatted = docstring % settings.BROWSABLE_DOCUMENT_MACROS string = formatting.dedent(smart_text(formatted)) return formatting.markup_description(string)
def get_content(self, view_cls, html=True, request=None): return dedent(view_cls.__doc__ or "")
def test_dedent_tabs(): result = 'first string\n\nsecond string' assert dedent(" first string\n\n second string") == result assert dedent("first string\n\n second string") == result assert dedent("\tfirst string\n\n\tsecond string") == result assert dedent("first string\n\n\tsecond string") == result
def test_dedent_tabs(): assert dedent("\tfirst string\n\n\tsecond string") == "first string\n\n\tsecond string"
def get_view_description(cls, html=False): description = cls.__doc__ or '' description = formatting.dedent(smart_text(description)) if html: return formatting.markup_description(description) return description