Model Resources

The design of the model resource configuration provides several areas to control the data provided via the request and the responses.

Class Variables

url_prefix = '/'
url_name = None

# for controlling presentation with output fields
serial_fields = None
serial_field_relations = None

# for modifying incoming data/queries with a function
process_get_inputs = None
process_post_inputs = None
process_put_inputs = None
process_patch_inputs = None
process_delete_inputs = None

# for modifying database objects before or after commits
before_commit = {}
after_commit = {}

Set Up Variables

Two variables are used for automatic URL generation with the function get_urls(). It is a convenience function. The url_prefix defaults to root, but can be set to whatever is needed. The url_name is the name of the resource and together they make up the URL.

It only comes in to play when adding resources.

  • url_prefix = ‘/’

  • url_name = None

If the url_name is left as None, a URL can be created from the model class name. For example, if a model class is CustomerOrder and its primary key is id, using a pluralizer, the default URLs will be:

/customer-orders
/customer-orders/<int:id>

Serialization

There are several avenues for controlling the output from model resources. Because the model classes use SQLAlchemy models wrapped with DBBase that have serialization / deserialization as a core process you can:

  • Configure the Model Class serialization: This method would mean that the primary usage of DBBase model classes would be the same within the Flask environment or without as well.

  • Configure the model resource class. This can be done by HTTP method or for all methods.

More detailed informations is available at: https://github.com/sidorof/DBBase

Model Resource Serial Fields

Serial fields are the names of columns and other variables and functions as found in the Model classes. Serial field relations are the serial fields for related models as configured in relationships. For example, the author might be related via the book model.

The default for serial_fields and serial_field_relations looks like the following.

class MyResource(ModelResource):
    model_class = MyModel

    serial_fields = None
    serial_field_relations = None

The default serial fields and serial field relations use what is found from the model class.

Here is an example where both the serial fields and serial field relations are specified. Note that serial_field_relations is a dict with keys for each relationship to be specified.

class MyResource(ModelResource):
    model_class = MyModel

    serial_fields = ["id", "field1", "field2", "field3"]
    serial_field_relations = {
        "RelatedModel1": ["id", "field1", "field2", "field3"],
        "RelatedModel2": ["id", "field1", "field2", "field3"],
    }

Here is an example where the serial fields and relations vary by method. At first, it might seem implausible that this would be ever necessary, but consider a model that is not complete unless it has been processed. The GET variables would return serial fields and relations for that model, but the POST, PUT, and PATCH methods would trigger a job. In that case, the serial fields and relations can be related to that job. Or, the default values for the Job model might be sufficient and that method could set as None. In this example, you would be using an after_commit process to create the job.

class MyResource(ModelResource):
    model_class = MyModel

    serial_fields = {
        "get": ["id", "field1", "field2", "field3"],
        "post": ["uuid", "job_name", "started_at"],
        "put": ["uuid", "job_name", "started_at"],
        "patch": ["uuid", "job_name", "started_at"],

    }

    serial_field_relations = {
        "get": {
            "RelatedModel1": ["id", "field1", "field2", "field3"],
            "RelatedModel2": ["id", "field1", "field2", "field3"],
        }
    }

Model Resource Modifications

Model resources can cover a fair amount of ground for a datacentric API, but when the vanilla version will not fit, there are remedies as a fall-back solution rather than simply writing your own.

Model resources are preset to take functions that can insert your necessary logic into the right point in the processing cycle.

There are three insertion points. The details change, depending upon the HTTP method, but there are more similarities than differences.

  • Process Input Functions: Modify the input data or query and continue processing or close it out early.

  • Adjustments Before or After Commits
    • Before Commit: Just prior to a commit, either a function or class can be inserted. This gives the possibility of a rollback of the session or diversion to another process.

    • After Commit: After a commit, either a function or class can be inserted. This can then be a trigger process or complete substitution of another class for the return trip.

Process Input Functions

Process input functions take the form of a function that can process the input data for the HTTP methods.

The format template for these functions is process_{method}_input.

Since HTTP methods have different requirements for input data, the inputs vary according to the method. And, since these functions are inserted into an on-going process, the arguments and returns for the functions are specific. However, the returns follow a common format of (status, result). The status is a True / False indicating that you would like the method to continue with the possibly altered data. False indicates that the method should end. A failure must have a tuple of a text message and a response status code that will be made into a return result and returned to the front end.

Input Variables

  • kwargs: as passed into the method

  • data: the data gleaned from the request in a dictionary form: This is the data prior to deserializations, so the variable names would be in camel case still.

  • query: the SQLAlchemy query that can be modified: This is the Flask-SQLAlchemy version of query, equivalent to the SQLAlchemy’s session.query(Model), for example session.query(Book). So an additional filter as may be necessary would be done by

query = query.filter_by(my_var='test').

And finally, this is an unexecuted query that the normal program will execute afterwards.

Returns

Since part of the point of these functions is to determine whether to go forward or not, the returns must be in the form {“status” True, “query”: query, “data”: data} where status is either True to continue or {“status”: False, “message”: message, “status_code”: status_code} to exit early. So, if a dictionary is not returned an error will be triggered about the process_input_function itself.

Use the following formats as a guide.

For example, suppose you want to add a process input function for POST.

class MyResource(ModelResource):
    model_class = MyClass

    def process_post_input(self, data):
        # your magic here
        if status:
            return {"status" True, "data": data}

        return {
            "status": False,
            "message": message,
            "status_code": status_code
        }

Adjustments Before or After Commits

Being able to jump in prior to a commit or just after can be very helpful. Possible areas:

  • Triggering another process to run instead of saving, or run directly after saving.

  • A record could be marked as inactive rather than deleted.

  • A separate job could be created and sent to a queue, the job object returned in place of the original record.

  • A process can be run which diverts to an exit of the HTTP method with a message and status code.

The process inputs all had separate names and the input and return variables varied with the HTTP method, while this family of functions are more similar.

These functions must return a status of True to continue to output a data item after adjustments. If a status of False is used, the process will exit the HTTP method with a message and a status code.

Note

By diverting the process to return a message and status code, it is now essentially an RPC.

To make the interface a little cleaner a ModelResource before / after commit is organized as a dict. For example:

MachineLearningModelResource(ModelResource):
    model_class = MachineLearningModel

    after_commit = {
        "post": submit_job
        "put": submit_job
    }

So your submit_job function would be called on POST or PUT, otherwise not.

The format of the before / after functions is similar to the following:

Method

Args

Returns a tuple

status, result, status_code

before_commit after_commit

self

your_function

data item

status_code

True, item, status_code

False, message, status_code

Next: Model Collection Resources