Source code for flask_restful_dbbase.resources.model_resource

# flask_restful_dbbase/resources/model_resource.py
""""
This module implements a starting point for model resources.

"""
import logging
from flask_restful import request
from .dbbase_resource import DBBaseResource
from ..validations import validate_process

logger = logging.getLogger(__name__)


[docs]class ModelResource(DBBaseResource): """ ModelResource Class This model class implements the base class. **Class variables**: model_class: a dbbase.Model url_prefix: the portion of the path leading up to the resource For example: /api/v2 url_name: the url_name defaults to a lower case version of the the model_class name if left as None, but can have an explicit name if necessary. serial_fields: if left as None, it uses the serial list from the model class. However, it can be a list of field names or before_commit: A dict with method keys for placing a function to run just before committing an item to the database. It can also divert the method to end the HTTP method early and return something entirely different than the item being applied. after_commit: A dict with method keys for placing a function to run just afteer committing an item to the database. It can also divert the method to end the HTTP method early and return something entirely different than the item being applied. """ process_get_input = None process_post_input = None process_put_input = None process_patch_input = None process_delete_input = None
[docs] def get(self, **kwargs): """ This function is the HTTP GET method for a resource handling a single item. """ # may be used later # url = request.path FUNC_NAME = "get" # the correct key test - raises error if improper url kdict = self._check_key(kwargs) # for use only with self.process_get_input data = request.args.to_dict(flat=False) query = self.model_class.query if self.process_get_input is not None: output = self.process_get_input(query, data, kwargs) validate_process(output, true_keys=["query", "data"]) if output["status"]: query = output["query"] data = output["data"] else: message = output["message"] status_code = output["status_code"] return message, status_code try: for key_name, key in kdict.items(): query = query.filter( getattr(self.model_class, key_name) == key ) item = query.first() except Exception as err: msg = err.args[0] logger.error(msg) return {"message": msg}, 500 sfields, sfield_relations = self._get_serializations(FUNC_NAME) if item: result = item.to_dict( serial_fields=sfields, serial_field_relations=sfield_relations, ) logger.debug(result) return result, 200 msg = f"{self.model_name} with {kdict} not found" logger.debug(msg) return {"message": msg}, 404
[docs] def post(self): """ This function is the HTTP POST method for a resource handling a single item. """ FUNC_NAME = "post" # may be used later # url = request.path status_code = 201 if request.is_json: try: data = request.json except Exception as err: msg = err return_msg = f"A JSON format problem:{msg}: {request.data}" logger.error(return_msg) return {"message": return_msg}, 400 else: logger.info("JSON format is required") return {"message": "JSON format is required"}, 415 if self.process_post_input is not None: output = self.process_post_input(data) validate_process(output, true_keys=["data"]) if output["status"]: data = output["data"] else: message = output["message"] status_code = output["status_code"] return message, status_code obj_params = self.get_obj_params() try: status, data = self.screen_data( self.model_class.deserialize(data), obj_params ) except Exception as err: msg = f"malformed data: {err.args[0]}" logger(msg) return {"message": msg}, 400 if status is False: logger.info(data) return {"message": data}, 400 key_names = self.get_key_names(formatted=False) item = None status, kdict = self._all_keys_found(key_names, data) if status: # verify it does not already exist query = self.model_class.query for key_name, value in kdict.items(): query = query.filter( getattr(self.model_class, key_name) == value ) try: item = query.first() except Exception as err: msg = err.args[0] logger.error(msg) return {"message": msg}, 400 if item: msg = f"{kdict} for {self.model_name} already exists." logger.info(msg) return ( {"message": msg}, 409, ) non_rel_columns = dict( [ [key, value] for key, value in obj_params.items() if "relationship" not in value ] ) non_rel_data = dict( [ [key, value] for key, value in data.items() if key in non_rel_columns ] ) item = self.model_class(**non_rel_data) rel_columns = dict( [ [key, value] for key, value in obj_params.items() if "relationship" in value ] ) # relationship data by column rel_data = dict( [[key, value] for key, value in data.items() if key in rel_columns] ) for key, value in rel_data.items(): # key such as invoice_items rel_info = rel_columns[key]["relationship"] sub_obj_params = rel_info["fields"] entity = rel_info["entity"] sub_class = self.model_class._decl_class_registry[entity] if rel_info["type"] == "list": if not isinstance(value, list): return ( {"message": f"{key} data must be in a list form"}, 400, ) for subitem in value: # subitem such as invoice item # screen against column # no missing data check, parent id auto filled # NOTE: needs further work sub_status, sub_data = self.screen_data( self.model_class.deserialize(subitem), sub_obj_params, skip_missing_data=True, ) if sub_status is False: # NOTE: look at this further logger.info(sub_data) return {"message": sub_data}, 400 getattr(item, key).append(sub_class(**sub_data)) adjust_before = self.before_commit.get(FUNC_NAME) if adjust_before is not None: status, result, status_code = self._item_adjust( adjust_before, item, status_code ) if status: item = result else: return result, status_code try: item.save() except Exception as err: msg = err.args[0] logger.error(msg) return {"message": msg}, 400 adjust_after = self.after_commit.get(FUNC_NAME) if adjust_after: status, result, status_code = self._item_adjust( adjust_after, item, status_code ) if status: item = result else: return result, status_code ser_fields, rel_ser_fields = self._get_serializations(FUNC_NAME) return ( item.to_dict( serial_fields=ser_fields, serial_field_relations=rel_ser_fields, ), status_code, )
[docs] def put(self, **kwargs): """ This function is the HTTP PUT method for a resource handling a single item. """ url = request.path FUNC_NAME = "put" status_code = 200 try: kdict = self._check_key(kwargs) except Exception as err: msg = err.args[0] logger.info(msg) return {"message": msg}, 400 if request.is_json: try: data = request.json except Exception as err: msg = err return_msg = f"A JSON format problem:{msg}" logger.info(return_msg) return {"message": return_msg}, 400 else: return {"message": "JSON format is required"}, 415 query = self.model_class.query if self.process_put_input is not None: output = self.process_put_input(query, data, kwargs) validate_process(output, true_keys=["query", "data"]) if output["status"]: query = output["query"] data = output["data"] else: message = output["message"] status_code = output["status_code"] return message, status_code status, kdict = self._all_keys_found(list(kdict.keys()), data) if status: for key_name, value in kdict.items(): query = query.filter( getattr(self.model_class, key_name) == value ) try: item = query.first() except Exception as err: msg = err.args[0] logger.error(msg) return {"message": msg}, 400 data = self.model_class.deserialize(data) # use the key(s) from the url data.update(kdict) status, data = self.screen_data( self.model_class.deserialize(data), self.get_obj_params() ) if status is False: logger.info(f"{str(data)}: 400") return {"message": data}, 400 if item is None: item = self.model_class(**data) else: for key, value in data.items(): setattr(item, key, value) adjust_before = self.before_commit.get(FUNC_NAME) if adjust_before: status, result, status_code = self._item_adjust( adjust_before, item, status_code ) if status: item = result else: return result, status_code try: item.save() except Exception as err: self.model_class.db.session.rollback() msg = err.args[0] logger.info(msg) logger.error(f"{url} method {FUNC_NAME}: {msg}") return {"message": msg}, 400 adjust_after = self.after_commit.get(FUNC_NAME) if adjust_after: status, result, status_code = self._item_adjust( adjust_after, item, status_code ) if status: item = result else: return result, status_code ser_fields, rel_ser_fields = self._get_serializations(FUNC_NAME) return ( item.to_dict( serial_fields=ser_fields, serial_field_relations=rel_ser_fields, ), status_code, )
[docs] def patch(self, **kwargs): """ This function is the HTTP PATCH method for a resource handling a single item. """ url = request.path FUNC_NAME = "patch" status_code = 200 try: kdict = self._check_key(kwargs) except Exception as err: msg = err.args[0] return {"message": msg}, 400 if request.is_json: try: data = request.json except Exception as err: msg = err return_msg = f"A JSON format problem:{msg}: {request.data}" return {"message": return_msg}, 400 else: return {"message": "JSON format is required"}, 415 query = self.model_class.query if self.process_patch_input is not None: output = self.process_patch_input(query, data, kwargs) validate_process(output, true_keys=["query", "data"]) if output["status"]: query = output["query"] data = output["data"] else: message = output["message"] status_code = output["status_code"] return message, status_code for key_name, value in kdict.items(): query = query.filter(getattr(self.model_class, key_name) == value) try: item = query.first() except Exception as err: msg = err.args[0] logger.error(msg) return {"message": msg}, 500 data = self.model_class.deserialize(data) data.update(kdict) status, data = self.screen_data( data, self.get_obj_params(), skip_missing_data=True ) if status is False: return {"message": data}, 400 if item is None: item = self.model_class(**data) else: for key, value in data.items(): setattr(item, key, value) adjust_before = self.before_commit.get(FUNC_NAME) if adjust_before is not None: status, result, status_code = self._item_adjust( adjust_before, item, status_code ) if status: item = result else: return result, status_code try: item.save() except Exception as err: msg = err.args[0] self.model_class.db.session.rollback() logger.error(f"{url} method {FUNC_NAME}: {msg}") return ( { "message": "An error occurred updating the " f"{self.model_name}: {msg}." }, 500, ) adjust_after = self.after_commit.get(FUNC_NAME) if adjust_after: status, result, status_code = self._item_adjust( adjust_after, item, status_code ) if status: item = result else: return result, status_code ser_fields, rel_ser_fields = self._get_serializations(FUNC_NAME) return ( item.to_dict( serial_fields=ser_fields, serial_field_relations=rel_ser_fields, ), status_code, )
[docs] def delete(self, **kwargs): """ This function is the HTTP DELETE method for a resource handling a single item. """ url = request.path FUNC_NAME = "delete" status_code = 200 try: kdict = self._check_key(kwargs) except Exception as err: msg = err.args[0] return {"message": msg}, 400 query = self.model_class.query if self.process_delete_input is not None: output = self.process_delete_input(query, kwargs) validate_process(output, true_keys=["query"]) if output["status"]: query = output["query"] else: message = output["message"] status_code = output["status_code"] return {"message": message}, status_code for key_name, value in kdict.items(): query = query.filter(getattr(self.model_class, key_name) == value) try: item = query.first() except Exception as err: msg = err.args[0] logger.error(msg) return {"message": msg}, 500 if item is None: msg = f"{self.model_name} with {kdict} not found" logger.debug(msg) return {"message": msg}, 404 adjust_before = self.before_commit.get(FUNC_NAME) if adjust_before is not None: status, result, status_code = self._item_adjust( adjust_before, item, status_code ) if status: item = result else: return result, status_code try: item.delete() except Exception as err: self.model_class.db.session.rollback() msg = err.args[0] logger.error(f"{url} method {FUNC_NAME}: {msg}") return ( { "message": "An error occurred deleting the " f"{self.model_name}: {msg}." }, 500, ) msg = f"{self.model_name} with {kdict} is deleted" return {"message": msg}, status_code