Source code for flask_restful_dbbase.doc_utils

# flask_restful_dbbase/utils.py
"""
This module implements utilities.
"""
from dbbase.utils import xlate


[docs]class MetaDoc(object): """ This class provides a scaffolding for holding documentation used when generating meta documents. The goal of a meta document is to provide a standard way to communicate the features of the backend API to frontend users. Resource header: model_class url_prefix base_url requirements Resource methods: single: get/post/put/patch/delete input input_modifier before_commit (if applicable) after_commit (if applicable) responses (if applicable) collection: get query input_modifier Args: resource_class: (obj) : The resource documented requirements: (str) : The input requirements documented methods: (list|dict) : a list or dict of methods that have special requirements. Leaving None would mean the default documentation. """ all_methods = ("get", "post", "put", "patch", "delete")
[docs] def __init__( self, resource_class, requirements=None, methods=None, ): self.resource_class = resource_class self.model_class = resource_class.model_class._class() self.url_prefix = resource_class.url_prefix self.base_url = resource_class.create_url() if methods: if isinstance(methods, dict): self.methods = methods elif isinstance(methods, list): # for convenience self.methods = {} for method in methods: self.methods[method.method] = method else: raise ValueError("methods must be a dictionary") else: self.methods = {} self.table = None
[docs] def to_dict(self, method=None): """ This function returns the settings for the resource. Args: method: (str : None) : choices are get/post/put/patch/delete. Returns: meta_data (dict) : A dict with the resource characteristics. If a method is preferred, the focus will be narrowed to that method. The intent of this function is to show relevant information for someone interacting with an API. """ model_class = self.resource_class.model_class db = model_class.db doc = {} attr_list = [ "model_class", "url_prefix", "base_url", "table", ] # header keys to camel_case for attr in attr_list: value = getattr(self, attr) if value: doc[xlate(attr, camel_case=True)] = value self.add_methods(doc, method=method) doc["table"] = db.doc_table(model_class) return doc
def add_methods(self, doc, method): doc.setdefault("methods", {}) if method is None: for tmp_method in self.all_methods: if hasattr(self.resource_class, tmp_method): if tmp_method in self.methods: method_dict = self.methods[tmp_method].to_dict(self) doc["methods"][tmp_method] = method_dict else: doc["methods"][tmp_method] = MethodDoc( tmp_method ).to_dict(self) else: # check for valid method first if not hasattr(self.resource_class, method): raise ValueError( f"Method '{method}' is not found for this resource" ) doc["methods"][method] = MethodDoc(method).to_dict(self)
[docs]class MethodDoc(object): """ This class holds details about a method. Args: method: (str) : The method name input: (str) : Optional input if necessary, otherwise default input_modifier: (str) : Optional input of process_{method}_inputs before_commit: (str) : Optional before_commit description after_commit: (str) : Optional after_commit description use_default_response: (bool) : A flag to overwrite responses responses: (list) : A list of possible custom responses """
[docs] def __init__( self, method, input=None, input_modifier=None, before_commit=None, after_commit=None, use_default_response=True, responses=None, ): self.method = method self.input = input self.input_modifier = input_modifier self.before_commit = before_commit self.after_commit = after_commit self.use_default_response = True if responses is None: self.responses = [] else: self.responses = responses
def _get_inputs(self, resource_class): """Dispatch to the right input type.""" if self.method in ["get", "delete"]: input_dict = self._get_input_keys(resource_class) else: input_dict = self._get_input_props(resource_class) return input_dict @staticmethod def _get_input_keys(resource_class): "Extract keys for methods" db = resource_class.model_class.db keys = resource_class.get_key_names() if len(keys) > 1: return [ dict([[key, db.doc_column(resource_class.model_class, key)]]) for key in keys ] else: key = keys[0] return {key: db.doc_column(resource_class.model_class, key)} @staticmethod def _get_input_props(resource_class): return resource_class.model_class.filter_columns( column_props=["!readOnly"], to_camel_case=True, ) @staticmethod def _get_url(doc, method, resource_class): if method in ["get", "put", "patch", "delete"]: if resource_class.is_collection(): doc["url"] = resource_class.get_urls()[0] else: doc["url"] = resource_class.get_urls()[1] else: doc["url"] = resource_class.get_urls()[0]
[docs] def to_dict(self, meta_doc): """Convert attributes to a dictionary""" resource_class = meta_doc.resource_class doc = {} self._get_url(doc, self.method, resource_class) doc["requirements"] = self.get_method_decorators(meta_doc) if meta_doc.resource_class.is_collection(): doc["queryString"] = self._get_inputs(resource_class) # hard-coded for the moment doc["jobParams"] = { "orderBy": {"type": "string", "list": True}, "maxPageSize": {"type": "integer"}, "offset": {"type": "integer"}, "debug": {"type": "boolean"}, } else: doc["input"] = self._get_inputs(resource_class) if self.input_modifier is not None: doc["input_modifier"] = self.input_modifier if self.before_commit is not None: doc["before_commit"] = self.before_commit if self.after_commit is not None: doc["after_commit"] = self.after_commit if self.responses: doc["responses"] = self.responses elif self.use_default_response: doc["responses"] = [self.get_default_response(meta_doc)] return doc
[docs] def get_method_decorators(self, meta_doc): """ This function returns a string representation of any method decorators. Args: meta_doc: (obj) : The meta document being processed. Returns: (list : None) : The string list of method decorator names. """ method_decorators = meta_doc.resource_class.method_decorators if isinstance(method_decorators, list): return [item.__name__ for item in method_decorators] if isinstance(method_decorators, dict): if self.method in method_decorators: return [ item.__name__ for item in method_decorators[self.method] ] return None
[docs] def get_default_response(self, meta_doc): """ This function returns the standard response for the method. If something special is done, then the default response would be supressed. """ resource = meta_doc.resource_class method = self.method db = resource.model_class.db outputs = {} if method != "delete": serial_fields = resource._get_serial_fields( method, with_class=True ) if isinstance(serial_fields, dict): # foreign class foreign_class, serial_fields = list(serial_fields.items())[0] # NOTE: serial field relations is unresolved doc = db.doc_table( foreign_class, serial_fields=serial_fields, serial_field_relations=( resource._get_serial_field_relations(method) ), to_camel_case=True, )[foreign_class._class()] else: if serial_fields is None: serial_fields = resource.model_class.get_serial_fields() doc = db.doc_table( resource.model_class, serial_fields=serial_fields, serial_field_relations=( resource._get_serial_field_relations(method) ), to_camel_case=True, )[resource.model_class._class()] outputs["fields"] = doc["properties"] # NOTE: here is where the default sort would go for collections return outputs