Source code for argschema.autodoc

from argschema.schemas import ArgSchema
import marshmallow as mm
from argschema.utils import get_description_from_field
from argschema.argschema_parser import ArgSchemaParser
import inspect

FIELD_TYPE_MAP = {v: k for k, v in mm.Schema.TYPE_MAPPING.items()}


[docs]def process_schemas(app, what, name, obj, options, lines): """function designed to process a :mod:`sphinx.ext.autodoc` event as autodoc hook to alter docstring lines of argschema related classes, providing a table of parameters for schemas and links to the default schemas for ArgSchemaParser derived elements use in sphnix conf.py as follows :: from argschema.autodoc import process_schemas def setup(app): app.connect('autodoc-process-docstring',process_schemas) """ if what == "class": # pick out the ArgSchemaParser objects for documenting if issubclass(obj, ArgSchemaParser): # inspect the objects init function to find default schema fas = inspect.getfullargspec(obj.__init__) # find where the schema_type is as a keyword argument try: schema_index = next( i for i, arg in enumerate(fas.args) if arg == "schema_type" ) # use its default value to construct the string version of the classpath to the module def_schema = fas.defaults[schema_index - 1] except StopIteration: def_schema = None def_schema = def_schema or obj.default_schema if def_schema is not None: def_schema_name = def_schema.__module__ + "." + def_schema.__name__ def_schema_name = def_schema_name or "None" # append to the documentation lines.append(".. note::") lines.append(" This class takes a ArgSchema as an input to parse inputs") lines.append( " , with a default schema of type :class:`~{}`".format(def_schema_name) ) lines.append("") if issubclass(obj, ArgSchema): # add a special note to ArgSchema's lines.append( " This schema is designed to be a schema_type for an ArgSchemaParser object" ) lines.append("") if issubclass(obj, mm.Schema): # but document all mm.Schema's schema = obj() if len(schema.declared_fields) > 0: lines.append(".. csv-table:: %s" % obj.__name__) lines.append( ' :header: "key", "description", "default","field_type","json_type"' ) lines.append(" :widths: 30, 80, 30, 30, 30") lines.append("") # #loop over the declared fields for this schema for field_name, field in schema.declared_fields.items(): # we will build up the documentation line for each field # start declaring the key as field_name like a parameter field_line = " %s," % field_name # get the description of this field and add it to documentation description = get_description_from_field(field) description = description or "no description" description = description.replace('"', "'") field_line += '"%s",' % description if field.default is not mm.missing: # add in the default value if there is one default = '"{}",'.format(field.default) field_line += default elif field.required: field_line += "(REQUIRED)," else: field_line += "NA," # add this field line to the documentation try: # get the set of types this field was derived from if isinstance(field, mm.fields.List): # if it's a list we want to do this for its container base_types = inspect.getmro(type(field.inner)) else: base_types = inspect.getmro(type(field)) field_type = type(field) # use these base_types to figure out the raw_json type for this field if isinstance(field, mm.fields.Nested): # if it's a nested field we should specify it as a dict, and link to the documentation # for that nested schema field_type = type(field.schema) # schema_class_name = schema_type.__module__ + "." + schema_type.__name__ if field.many: raw_type = "list" else: raw_type = "dict" else: # otherwise we should be able to look it up in the FIELD_TYPE_MAP try: base_type = next( bt for bt in base_types if bt in FIELD_TYPE_MAP ) raw_type = FIELD_TYPE_MAP[base_type].__name__ # hack in marshmallow for py3 which things Str is type 'bytes' if raw_type == "bytes": raw_type = "str" except: # if its not in the FIELD_TYPE_MAP, we aren't sure what type it is # TODO handle this more elegantly/and/or patch up more use cases raw_type = "?" field_line += ":class:`~{}.{}`,{}".format( field_type.__module__, field_type.__name__, raw_type ) except Exception as e: # in case this fails for some reason, note it as unknown # TODO handle this more elegantly, identify and patch up such cases field_line += "unknown,unknown" lines.append(field_line)
# lines.append(table_line)