chapps.rest.routers.common module

Route factories and other reusable code

Factories for utility functions and API routes are defined in this module along with some FastAPI dependencies.

The route factories perform the repetitive grunt work required to set up the typical ‘create’, ‘read’, ‘update’, ‘delete’ and ‘list’ functions needed for basic object management. In order to avoid extra levels of metaprogramming, the parameter name for the record ID of the main object involved in a factory-generated API call is item_id, since it is clear, brief and generic. Apologies to future subclassors who want an ‘items’ table.

These route factories are used to create all the routes for users, emails, domains, and quotas.

async chapps.rest.routers.common.list_query_params(skip=0, limit=1000, q='%')[source]

FastAPI dependency for list queries

Return type

dict

Parameters
chapps.rest.routers.common.model_name(cls)[source]

Convenience function to get the lowercase name of a model

Return type

str

chapps.rest.routers.common.load_model_with_assoc(cls, assoc, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Create a closure which loads an object along with arbitrary associations

This isn’t meant to create an API route on its own, but it may be used in API routes. It is mainly used in live routes, which are currently all one-offs, not created by factories. In order to return a closure which can work in any context, it does not return a coroutine but a standard synchronous closure.

Parameters
Return type

callable

Returns

a closure as follows:

chapps.rest.routers.common.f(item_id: int, name: Optional[str])
Parameters
  • item_id (int) – if non-zero, the ID of the main record

  • name (Optional[str]) – if item_id is 0, the name of the record to match.

Return type

Tuple[CHAPPSModel, Dict[str, List[CHAPPSModel]], List[str]]

Returns

a tuple containing:

  1. the object loaded by ID or name

  2. that object’s associations in a dict keyed on attribute name (e.g. ‘quota’, ‘domains’)

  3. a list of string remarks, which may have no contents

chapps.rest.routers.common.load_models_with_assoc(cls, *, assoc, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a map of source name => associated object id

Parameters
  • cls (CHAPPSModel) – source model

  • assoc (JoinAssoc) – a join association representing the associated model

  • engine – override the SQLA engine if desired

Return type

callable

Returns

a mapper function which accepts a list of IDs of the source model and returns a list of dicts with <source_model>_name and <assoc_model>_id fields, mapping the source objects onto the IDs of their associated objects of the configured type.

chapps.rest.routers.common.db_wrapper(*, cls, engine=Engine(mysql://chapps:***@localhost:3306/chapps), exception_message='{route_name}:{model}', empty_set_message='Unable to find a matching {model}')[source]

Decorator for database interactions

Parameters
  • cls (CHAPPSModel) – the data model class

  • engine (Engine) – an SQLAlchemy engine, which defaults to the package-wide one declared in dbsession

  • exception_message (str) – a message to include if any untrapped exception occurs; defaults to {route_name}:{model}. Only those two symbols are available for expansion. All arguments are appended.

  • empty_set_message (str) – included if a SELECT results in an empty set; defaults to Unable to find a matching {model} and supports both substitutions that exception_message does

Returns

a decorator closure, which will be called with the function to be decorated as its argument. This is a regular callable decorator.

Return type

callable which wraps and returns a function

The decorator sets up some global symbols for use inside the DB access function:

session

a Session instance created in a context containing the execution of the wrapped coroutine, suitable for performing database interactions, and which will be automatically closed after the coroutine completes

model_name

a string containing the lowercase name of the model

chapps.rest.routers.common.db_interaction(*, cls, engine=Engine(mysql://chapps:***@localhost:3306/chapps), exception_message='{route_name}:{model}', empty_set_message='Unable to find a matching {model}')[source]

Decorator for database interactions

Parameters
  • cls (CHAPPSModel) – the data model class

  • engine (Engine) – an SQLAlchemy engine, which defaults to the package-wide one declared in dbsession

  • exception_message (str) – a message to include if any untrapped exception occurs; defaults to {route_name}:{model}. Only those two symbols are available for expansion. All arguments are appended.

  • empty_set_message (str) – included if a SELECT results in an empty set; defaults to Unable to find a matching {model} and supports both substitutions that exception_message does

Returns

a decorator closure, which will be called with the function to be decorated as its argument. In this case, the function is expected to be a coroutine which is being manufactured for use in the API, and so the decorator closure returned by this routine defines a coroutine to wrap and await its argument, which is ultimately returned and used as the API route.

Return type

callable which wraps and returns a coroutine

The decorator sets up some global symbols for use inside the API route coroutines:

session

a Session instance created in a context containing the execution of the wrapped coroutine, suitable for performing database interactions, and which will be automatically closed after the coroutine completes

model_name

a string containing the lowercase name of the model

chapps.rest.routers.common.get_item_by_id(cls, *, response_model, engine=Engine(mysql://chapps:***@localhost:3306/chapps), assoc=None)[source]

Build a route coroutine to get an item by ID

Parameters
  • cls (CHAPPSModel) – the main data model for the request

  • response_model (CHAPPSResponse) – the response model

  • engine (Engine) – defaults to sql_engine

  • assoc (List[JoinAssoc]) – if included, these associations will be included as optional keys in the response

At present there is no provision for dealing with extremely long association lists. Even if there were 500 elements, the response would not be extremely large.

Note

An alternate closure factory for creating routes which specifically list associations does provide pagination, etc. See list_associated()

The factory produces a coroutine decorated with the db_interaction() decorator, as do all the route factories. Its signature is:

async def get_i(item_id: int) -> response_model

The factory sets the final closure’s name and doc metadata properly to ensure that the automatic documentation is coherent and accurate. All the route factories do this to a greater or lesser extent.

chapps.rest.routers.common.list_items(cls, *, response_model, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a route coroutine to list items

Parameters

The returned closure expects to receive the query parameters as a dict, since that is what the dependency will return. Its signature is

async def list_i(qparams: dict = Depends(list_query_params))

The closure’s name and document metadata are updated to ensure coherence and accuracy of the automatic API documentation.

For an example of using this factory, see Listing Domains.

chapps.rest.routers.common.list_associated(cls, *, assoc, response_model, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a route to list associated items with pagination

Parameters

The returned coroutine will paginate a list of the associated objects, given the ID of a main (source) object to use to select associations. The qparams parameter is a bundle of standard listing query parameters defined by list_query_params() via the fastapi.Depends mechanism.

async def assoc_list(item_id: int, qparams: dict) -> response_model

It returns in the response key of its output a list of the associated object, goverened by the search and window parameters in qparams.

chapps.rest.routers.common.delete_item(cls, *, response_model=<class 'chapps.models.DeleteResp'>, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a route coroutine to delete an item by ID

Parameters

The returned coroutine accepts a list of record IDs for the specified object type and delete them. Its signature is:

async def delete_i(item_ids: List[int]) -> DeleteResp
chapps.rest.routers.common.adjust_associations(cls, *, assoc, assoc_op, params=None, response_model=<class 'chapps.models.TextResp'>, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a route to add or subtract association lists, or set unitary ones

Parameters
  • cls (CHAPPSModel) – a data model class

  • assoc (List[JoinAssoc]) – list of associations to operate on

  • assoc_op (AssocOperation) – operation to perform on the association

  • response_model (CHAPPSResponse) – the response model to send

  • engine (Engine) – defaults to sql_engine

  • params (Optional[dict]) –

The returned coroutine provides logic for a route which adds or subtracts elements to or from those already associated with the main object. Its exact signature is dependent on what associations are listed. After item_id, which is an ID to use to look up the main object, it will expect further arguments named as the association (assoc_name) which are of the specified type (assoc_type).

If only one association is adjusted by the route, there will be just the one list (or scalar) as a body argument, which doesn’t get a label, making the API call very easy to format and looking very clean in the API docs. If more than one are specified, FastAPI will expect a JSON object in the body with keys named as the ID columns and values which are lists of IDs.

It all seems quite complicated when stated this way, but when viewed in the API documentation, it makes much more sense.

For an example of using this factory, see Handling Associations

chapps.rest.routers.common.update_item(cls, *, response_model, assoc=None, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a route to update items.

Parameters
  • cls (CHAPPSModel) – the main data model

  • response_model (CHAPPSResponse) – the response model

  • assoc (JoinAssoc) – the association to list

  • engine (Engine) – defaults to sql_engine

The returned coroutine implements an API route for updating an item by ID, optionally including any associations included when the route coroutine is built. If association data is provided to the route, it will completely replace any existing associations to that type of record with the new list of IDs.

Its signature is determined by the contents of the JoinAssoc passed to it. The factory constructs Parameter elements and uses them to create a correct Signature for the new, decorated closure. It also sets the __doc__ and __name__ metadata so that FastAPI will be able to find all the required data to create an API route with good documentation.

For an example of how to use this factory, see Updating Domains

chapps.rest.routers.common.create_item(cls, *, response_model, params=None, defaults=None, assoc=None, engine=Engine(mysql://chapps:***@localhost:3306/chapps))[source]

Build a route coroutine to create new item records

Parameters
  • cls (CHAPPSModel) – the main data model

  • response_model (CHAPPSResponse) – the response model

  • params (dict) – defaults to dict(name=str); specify to provide additional column names and types, and be sure to include name, as all models currently are expected to have a name column, which is not allowed to be null.

  • assoc (JoinAssoc) – the associations to attach, if any

  • engine (Engine) – defaults to sql_engine

The returned coroutine implements an API route for creating an item, setting all its elements (other than ID) to whatever values are provided. Currently all values must be provided. If desired, associations may also be provided to the factory, and they will be accommodated by the coroutine.

For an example invocation of this factory, see Creating Users