chapps.models module

CHAPPS Data Validation Models

This module consists of the Pydantic data models required to represent and also communicate about the various database records which control CHAPPS. There are a few types of models, and perhaps more types to come, so some short discussion of what they are and why they are needed seems reasonable.

Representational Models

There are (currently quite simple) objects which control CHAPPS: User objects are at the root of that tree, and they are linked to one (outbound) Quota object each to control how much email they may send. User objects may also be linked to multiple Domain and Email objects, to represent who they are allowed to appear to be while sending email.

FastAPI uses Pydantic for data validation during API execution. It behooves us to define Pydantic models as representations of the objects, therefore. This could be considered the front-facing identity of the object, which has a back-facing identity represented by its database model, defined in dbmodels.

A metaclass is defined which implements __getattr__, in order to allow the validation model to masquerade as a database model to a certain extent. That routine expects the validation model to define a subclass called Meta with a class attribute called orm_model which refers to the database model for the object. In this way, the validation model (class) is empowered to marshall a set of instances from the database, without a lot of messy dereferencing.

API Response Models

In order to specify to the API constructors and the automatic API documentation generators what the response for a particular API route should look like (contain), more Pydantic models are defined.

All responses contain the CHAPPS version string and UNIX epoch time stamp in them, as well as the response to the query, and possibly optional data regarding an object’s associations. A fair number of response models are defined, and they also fall into a few categories.

Unitary Data Model Responses

When a single object of the primary type is being returned, that object is the value of the response key in the object returned by the API. If the object has associations to objects of other types, the expectation is that a list of those objects will be returned as the value of a key named for the association, as if it were to be accessed via the ORM. Those associated objects are listed without any of their own associations included.

Data Model Listing Responses

These response models are named almost exactly the same as their unitary counterparts, but with their model names pluralized. They will contain a list of objects of the relevant type in their response attributes, without any associations.

Custom Live Responses

Some of the response models are meant to relay information from the live API routes, which deal with the current state of CHAPPS as reflected in Redis. These are each explained in their own documentation.

Basic Datatype Responses

Some operations return a very simple value, such as an integer or string, and so there are some response models to use in such cases.

class chapps.models.AssocOperation[source]

Bases: str, enum.Enum

‘add’, ‘subtract’, or ‘replace’

use ‘replace’ only with unitary associations; logic in JoinAssoc which responds to the replace operation is designed to work only with scalar values.

add = 'add'
subtract = 'subtract'
replace = 'replace'
class chapps.models.SDAStatus[source]

Bases: str, enum.Enum

sender-domain auth status: AUTH, PROH, or NONE

AUTH = 'AUTHORIZED'
PROH = 'PROHIBITED'
NONE = 'NOT CACHED'
class chapps.models.SPFResult[source]

Bases: str, enum.Enum

SPF check results

passing = 'pass'
fail = 'fail'
temperror = 'temperror'
permerror = 'permerror'
softfail = 'softfail'
none_neutral = 'none_neutral'
class chapps.models.PolicyResponse[source]

Bases: object

A wrapper for policy results

static policy_response(passing, decision)[source]

Wrap an action in order to return it as a PolicyResponse

This routine is suitable for use as a decorator

Parameters
  • passing (bool) –

  • decision (str) –

__init__(*, response, passing, decision=None)[source]
Parameters
class chapps.models.CHAPPSMetaModel[source]

Bases: pydantic.main.ModelMetaclass

Metaclass for CHAPPS Pydantic models

We inject an override for __getattr__() in order to attempt to find missing attributes on the ORM class attached via the Meta subclass of each model class. This allows the Pydantic data-model class to serve as a proxy for the ORM class, meaning that we can handle Pydantic models in the API code, and still call ORM methods on them, and cause corresponding ORM objects to be instantiated on demand, etc.

__getattr__(var)[source]

ORM Masquerading

If the requested attribute exists on the orm_model, return it, or else None. Note that while the variable name used assumes the attribute will refer to a callable, it will work on any attribute.

class chapps.models.CHAPPSModel[source]

Bases: pydantic.main.BaseModel

Base API data model

All models should define a class called Meta and define within it a variable called orm_model as a reference to the ORM model class (generally defined in dbmodels) corresponding to the data model. In this abstract superclass, the ORM model reference is to the parallel abstract ORM model superclass.

Models also define a class called Config, which is used by Pydantic. In it, orm_mode should be set to True. It is also possible to include additional detail for the OpenAPI documentation parser.

All models have these attributes/columns:

id: int

integer auto-incrementing primary identifier

name: str

unique string label

class Meta[source]

Bases: object

Used by CHAPPS

orm_model

The ORM model class corresponding to this data model

alias of sqlalchemy.orm.decl_api.Base

classmethod id_name()[source]
Return type

str

Returns

name of the ID column in a join table

classmethod wrap(orm_instance)[source]

Wrap an ORM instance in its corresponding Pydantic data model

Parameters

orm_instance (cls.Meta.orm_model) – an ORM instance of the appropriate type

Returns

a pydantic model created from an ORM model

classmethod join_assoc(**kwargs)[source]

Create a JoinAssoc with this class as the source

Parameters
  • assoc_name (str) – attribute name of the association

  • assoc_type (type) – usually int or List[int]; should be the type for the API to expect when setting up the route metadata

  • assoc_model (DB_Base) – a reference to the dbmodel class of the associated object

  • assoc_id (str) – label of associated object’s ID column in the join table

  • table (sqlalchemy.schema.Table) – a reference to the join table schema; it will be a constant in this module, generally

Return type

chapps.dbmodels.JoinAssoc

This convenience routine for generating a JoinAssoc provides the source model and ID-column info as it passes on the other arguments.

Return type

JoinAssoc

class chapps.models.User[source]

Bases: chapps.models.CHAPPSModel

User objects represent entities authorized to send email

name: chapps.models.ConstrainedStrValue

user identifiers may be from 5 to 127 chars long

class Config[source]

Bases: object

orm_mode = True
schema_extra = {'example': {'id': 0, 'name': '[user.identifier@]domain.name'}}
class Meta[source]

Bases: object

orm_model

alias of chapps.dbmodels.User

class chapps.models.Quota[source]

Bases: chapps.models.CHAPPSModel

Quota objects represent transmission count limits

name: Optional[chapps.models.ConstrainedStrValue]

quota labels may be up to 31 chars long

quota: Optional[int]

unique integer outbound transmission limit

class Config[source]

Bases: object

orm_mode = True
schema_extra = {'example': {'id': 0, 'name': 'fiftyPerHour', 'quota': 1200}}
class Meta[source]

Bases: object

orm_model

alias of chapps.dbmodels.Quota

class chapps.models.Domain[source]

Bases: chapps.models.CHAPPSModel

Domain objects have a name and ID; the name never contains an @

name: Optional[chapps.models.ConstrainedStrValue]

domain names may be up to 63 chars long

greylist: Optional[bool]

flag indicating whether to greylist all domain’s inbound email

check_spf: Optional[bool]

flag indicating whether to check SPF for domain’s inbound email

class Config[source]

Bases: object

orm_mode = True
schema_extra = {'example': {'check_spf': True, 'greylist': False, 'id': 0, 'name': '[sub.]domain.tld'}}
class Meta[source]

Bases: object

orm_model

alias of chapps.dbmodels.Domain

classmethod domain_validator(val)[source]
class chapps.models.Email[source]

Bases: chapps.models.CHAPPSModel

Email objects have a name and ID; the name always contains an @

name: chapps.models.ConstrainedStrValue

unique string label

class Config[source]

Bases: object

orm_mode = True
schema_extra = {'example': {'id': 0, 'name': 'someone@example.com'}}
class Meta[source]

Bases: object

orm_model

alias of chapps.dbmodels.Email

classmethod email_validator(val)[source]
class chapps.models.CHAPPSResponse[source]

Bases: pydantic.main.BaseModel

Base Pydantic model for API responses

version: str

The CHAPPS version as a string

timestamp: float

When this response was generated

response: object

Whatever piece of data was requested

class Config[source]

Bases: object

schema_extra = {'timestamp': 1668194499.7116046, 'version': 'CHAPPS v0.5.10'}
classmethod send(response=None, **kwargs)[source]

Utility function for encapsulating responses in a standard body

class chapps.models.UserResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a single User record

response: chapps.models.User

The response field contains a User record

domains: Optional[List[chapps.models.Domain]]

A list of associated Domain records may be included

emails: Optional[List[chapps.models.Email]]

A list of associated Email records may be included

quota: Optional[chapps.models.Quota]

The Quota record associated with the User may be included

class chapps.models.UsersResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a list of User records

response: List[chapps.models.User]

A list of User objects

class chapps.models.DomainResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a single Domain record

response: chapps.models.Domain

A Domain object

users: Optional[List[chapps.models.User]]

A list of User objects associated to the Domain may be included

class chapps.models.DomainsResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a list of Domain records

response: List[chapps.models.Domain]

A list of Domain objects

class chapps.models.EmailResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a single Email record

response: chapps.models.Email

An Email object

users: Optional[List[chapps.models.User]]

A list of associated User objects may be included

class chapps.models.EmailsResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a list of Email records

response: List[chapps.models.Email]

A list of Email objects

class chapps.models.QuotaResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a single Quota record

response: chapps.models.Quota

A Quota object

class chapps.models.QuotasResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a list of Quota records

response: List[chapps.models.Quota]

A list of Quota objects

class chapps.models.IntResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with an integer

response: int

An integer

class chapps.models.FloatResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a float

response: float

A floating-point number

class chapps.models.TextResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responding with a string

response: str

A string

class chapps.models.TimeResp[source]

Bases: chapps.models.FloatResp

Data model for responding with a UNIX epoch time value

response: float

UNIX epoch time (UTC)

class chapps.models.InstanceTimesResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for returning a list of instances and timestamps

response: List[Tuple[str, float]]

A list of (instance, timestamp) tuples

class Config[source]

Bases: object

schema_extra = {'example': {'response': [['instance001', 1668182499.7210803], ['instance002', 1668188499.7210813]], 'timestamp': 1668194499.721082, 'version': 'CHAPPS v0.5.10'}}
class chapps.models.LiveQuotaResp[source]

Bases: chapps.models.CHAPPSResponse

Data model for responses from the Live API

response: int

An integer

remarks: List[str]

A list of string remarks may be included

class chapps.models.SourceUserMapResp[source]

Bases: chapps.models.CHAPPSResponse

A source-user map is a dict-of-dicts:
  • top level key is domain or email name

  • second key is user

  • value is SDAStatus

response: Dict[str, Dict[str, chapps.models.SDAStatus]]

A map of auth-subject to dicts of username mapping to status

class chapps.models.BulkQuotaResp[source]

Bases: chapps.models.CHAPPSResponse

Maps User name onto Quota id

With descriptive labels and optional remarks (from the live API)

response: List[Dict[str, Optional[Union[str, int]]]]

Whatever piece of data was requested

remarks: List[str]
class chapps.models.BulkDomainsResp[source]

Bases: chapps.models.CHAPPSResponse

Maps User name onto lists of Domain id

With descriptive labels and optional remarks (from the live API)

response: List[Dict[str, Optional[Union[str, List[int]]]]]

Whatever piece of data was requested

remarks: List[str]
class chapps.models.BulkEmailsResp[source]

Bases: chapps.models.BulkDomainsResp

Maps User name onto lists of Email id

With descriptive labels and optional remarks (from the live API)

response: List[Dict[str, Optional[Union[str, List[int]]]]]

Whatever piece of data was requested

remarks: List[str]
version: str

The CHAPPS version as a string

timestamp: float

When this response was generated

class chapps.models.DeleteResp[source]

Bases: chapps.models.TextResp

Data model for responding to deletion requests

response: str

The string ‘deleted’