Models

All resources in the API should be represented by a model class exported by this package. The model classes in this package provide a layer of abstraction between the models stored in the database, and the resources that the user of this API will interact with. This package also provides well-defined interfaces for working with resources, in order to describe a contract for interacting the model classes. The contract in this case refers to the set of properties and methods that an implementation of the contract is guaranteed to implement. Unlike statically-typed languages like Java, Python does not require such a contract to exist, as variable types can be inferred dynamically. Nevertheless, it is recommended to use the contracts defined here when making new implementations of model classes for managing resources. Implementing these interfaces will also get the most use out of the type-hinting syntax introduced in Python 3.4.

Model classes MUST keep track of the changes that they intend to make, but they are not responsible for committing those changes. If the initial state of a model class depends on some underlying representation like a database record, changing the state of the class does not need to be propagated immediately to persistent storage. In that case, propagation of the state SHOULD go to a transaction manager that can then commit the changes. If the state of a model class is modified, the modified value of that attribute must be returned, even if that modified value was not yet committed to storage.

In an effort to separate interface from implementation, each model class defined here MUST expose its type and contract through an abstract base class located in the interfaces directory of this package. Each class imported into the __init__ module of this package MUST be one of these interfaces.

Interfaces

API Error

Describes the interface for a reportable exception that can occur in the API at some point.

exception topchef.models.interfaces.api_error.APIError[source]

Describes the interface for working with an API Exception.

detail
Returns:A detailed message associated with the exception
status_code
Returns:The status code that should ideally be associated with this exception. For instance, if a service is not found, the status code for the error is a 404. If multiple errors are thrown of a particular exception series, then the most general status code will be returned. For instance, if two errors are 404 and 403, then the error returned will be 400. If both client-side (4xx) and server-side (5xx) errors are encountered, the server will return 500.
title
Returns:The title associated with this exception. This should be common across errors of a particular type

API Metadata

Describes an interface for a resource capable of retrieving metadata for the API

class topchef.models.interfaces.api_metadata.APIMetadata[source]

Describes the metadata for the API

documentation_url
Returns:A URL to the API’s documentation. This includes both user documentation such as tutorials, and reference documentation for things like Python API methods
maintainer_email
Returns:An email address for the API maintainer. Users should be able to send emails to this address to inform the maintainer of any bugs in the API.
maintainer_name
Returns:The name of the API’s maintainer
source_code_repository_url
Returns:A URL to the repository where the API source code is located. Users should be able to use resources at this URL in order to inspect the source code
version
Returns:The version of the API being deployed

Job

Defines TopChef jobs

class topchef.models.interfaces.job.Job[source]

Interface for the job

class JobStatus[source]

The possible job statuses

_member_type_

alias of object

Job.__hash__() → int[source]
Returns:The hash of the number
Job.id
Returns:The Job ID
Job.parameter_schema
Returns:The schema used to write down parameters
Job.result_schema
Returns:The schema used to write down valid results
Job.status
Returns:The job status

Job List

class topchef.models.interfaces.job_list.JobList[source]

Describes an interface for manipulating a set of jobs posted to the API. The Job list should be iterating over all the jobs in the list.

__aiter__() → typing.AsyncIterator[topchef.models.interfaces.job.Job][source]
Returns:An iterator that can asynchronously iterate over all the jobs in the set
__contains__(job_id: typing.Union[uuid.UUID, topchef.models.interfaces.job.Job]) → bool[source]

Membership test. This method MUST be able to check whether a Job or JobID is in the container

Parameters:job_id – The job or the Job ID for which membership is to be checked
Returns:True if the job belongs to this service
__delitem__(job_id: uuid.UUID) → None[source]
Parameters:job_id – The ID of the job to delete
__eq__(other: topchef.models.interfaces.job_list.JobList) → bool[source]
Parameters:other – The other job list against which equality is to be determined
Returns:True if the equality definition is met, otherwise False
__getitem__(job_id: uuid.UUID) → topchef.models.interfaces.job.Job[source]

Given a job ID, this method must return a job with an ID corresponding to that job ID, or raise KeyError.

Parameters:job_id – The ID of the job to retrieve, or the value of the job to retrieve
Returns:The job
Raises:KeyError if a job with that ID does not exist
__iter__() → typing.Iterator[topchef.models.interfaces.job.Job][source]
Returns:An iterator that can iterate synchronously through all the jobs in the set
__len__() → int[source]
Returns:The number of jobs in the set
__setitem__(job_id: uuid.UUID, job: topchef.models.interfaces.job.Job) → None[source]
Parameters:
  • job_id – The ID of the job to set
  • job – The new job that is to occupy that ID
Raises:

KeyError if a job with that ID does not exist

Service

Defines the interface for services. A TopChef service is an object that is capable of processing jobs. In practice, there exists a mapping between services and experiment setups.

class topchef.models.interfaces.service.Service[source]

The interface for the service, and the collection of jobs that the service manages. Describes the immutable and mutable properties of the service.

Note

After creating a schema, there is no way to change the job parameter
or result schema. This is here to prevent rendering jobs that have already completed invalid, as they will no longer match the schema.
check_in() → None[source]

Reset the timeout

description
Returns:A human-readable description of the service
has_timed_out
Returns:True if the server has timed out, otherwise False
id
Returns:The ID of the service. This is an object that can uniquely identify the service
is_service_available
Returns:A user-settable flag to indicate whether the service is available or not. Clients consuming a service SHOULD flip this flag to True when they are ready to accept services. Clients SHOULD flip this flag to False when they are shutting down. Clients posting jobs to this service MAY use this flag to check whether the service is available. Jobs posted to an unavailable service will still be pushed to the service’s queue
job_registration_schema
Returns:A JSON schema representing the set of all valid JSON objects that can serve as parameters for this service’s jobs
job_result_schema
Returns:A JSON schema representing the set of all valid JSON objects that can serve as results for this service’s jobs
jobs
Returns:The list of all jobs that belong to this service
name
Returns:A human-readable name for the service
classmethod new(name: str, description: str, registration_schema: typing.Dict[str, typing.Union[str, int, float, NoneType, typing.List[typing.JSON_TYPE], typing.JSON_TYPE]], result_schema: typing.Dict[str, typing.Union[str, int, float, NoneType, typing.List[typing.JSON_TYPE], typing.JSON_TYPE]], database_session: sqlalchemy.orm.session.Session) → topchef.models.interfaces.service.Service[source]

Create a new service and save the details of this service to some persistent storage.

Parameters:
  • name – The name of the newly-created service
  • description – A human-readable description describing what the service does.
  • registration_schema – A JSON schema describing the set of all JSON objects that are allowed as parameters for this server’s jobs
  • result_schema – A JSON schema describing the set of all JSON objects that are allowed as results for this server’s jobs
  • database_session – The SQLAlchemy session to which the database model is to be written
Returns:

The newly-created service

new_job(parameters: typing.Dict[str, typing.Union[str, int, float, NoneType, typing.List[typing.JSON_TYPE], typing.JSON_TYPE]], database_job_constructor: typing.Callable[[topchef.database.models.service.Service, typing.Dict[str, typing.Union[str, int, float, NoneType, typing.List[typing.JSON_TYPE], typing.JSON_TYPE]]], topchef.models.interfaces.job.Job] = <bound method Job.new of <class 'topchef.database.models.job.Job'>>) → topchef.models.interfaces.job.Job[source]

Using the

Parameters:
  • parameters – The job parameters
  • database_job_constructor – The type to be used to create a new job
Returns:

timeout
Returns:The length of time that must pass between checkins before the service is considered to have timed out

Service List

Contains an interface for getting services

class topchef.models.interfaces.service_list.ServiceList[source]

Describes an interface for manipulating the set of all services that have been posted to the API.

__getitem__(service_id: uuid.UUID) → topchef.models.interfaces.service.Service[source]
Parameters:service_id – The ID of the service to retrieve
Returns:The service
Raises:KeyError if a service with this ID does not exist
__setitem__(service_id: uuid.UUID, service: topchef.models.interfaces.service.Service) → None[source]
Parameters:
  • service_id
  • service
Returns:

Abstract Classes

These classes contain some often-repeated business logic.

API Error

Contains a model for a TopChef API exception

exception topchef.models.abstract_classes.api_error.APIError[source]

Base class for an API exception

Job List From Query

When working on the models, I found that getting the job list for a particular service, and getting all the jobs on the API required very similar code. The only difference between these two types is the “base” SQL query on which they operate. In order to get all the jobs on the API, I would have to run

SELECT * FROM jobs

Whereas to get all the jobs for a particular service (let’s say with ID = 1), I would have to run

SELECT * FROM jobs
INNER JOIN services ON jobs.service_id == service.service_id
WHERE service_id = 1

The former query would map to SQLAlchemy as

session.query(Job).all()

and the latter would map to

session.query(Job).filter_by(service=Service).all()

Therefore, everything before

Database Model Implementations

API Metadata

Provides a model that pulls the application metadata from The CONFIG object defined in topchef.config.

class topchef.models.api_metadata.APIMetadata(app_configuration: topchef.config.Config = <topchef.config.Config object>) → None[source]

Model responsible for pulling metadata from the config file

__init__(app_configuration: topchef.config.Config = <topchef.config.Config object>) → None[source]
Parameters:app_configuration – The configuration object from which metadata is to be pulled. By default, this is the config object from topchef.config.
documentation_url
Returns:A URL where documentation for the API can be obtained. This documentation MUST contain a description of the available API endpoints, the methods that can be performed on them, and what errors may arise from using the API
maintainer_email
Returns:The email-address of the maintainer of this API
maintainer_name
Returns:A name that can be used to look up the human responsible for maintaining this API
source_code_repository_url
Returns:The URL to the repository where the source code for the API is hosted
version
Returns:The version of the API

Job

Implements a job that maps to the

class topchef.models.job.Job(database_job: topchef.database.models.job.Job)[source]

Contains an implementation of the job interface

__init__(database_job: topchef.database.models.job.Job)[source]
Parameters:database_job – The database model for the job
id
Returns:The job ID

Job List

class topchef.models.job_list.JobList(database_session: sqlalchemy.orm.session.Session)[source]

Implements the interface to get all the jobs in the job list

Service

Contains an implementation of the Service interface that pulls all the required data from a SQLAlchemy model class.

class topchef.models.service.Service(database_service: topchef.database.models.service.Service, session_getter_for_model: typing.Callable[[sqlalchemy.ext.declarative.api.Base], sqlalchemy.orm.session.Session] = <bound method _SessionClassMethods.object_session of <class 'sqlalchemy.orm.session.Session'>>) → None[source]

Provides a model class that gets all its data from an underlying database service already in the API database

description
Returns:A human-readable description for the service
has_timed_out
Returns:Whether the service has timed out or not
id
Returns:The service ID
is_service_available
Returns:A flag that indicates whether the service is ready to accept jobs
job_registration_schema
Returns:The JSON schema that must be satisfied in order to create a new job
job_result_schema
Returns:The JSON schema that must be satisfied in order to post a result to the job
name
Returns:The service name

Service List

class topchef.models.service_list.ServiceList(session: sqlalchemy.orm.session.Session) → None[source]

Implements a means of getting services from a relational DB back end

__init__(session: sqlalchemy.orm.session.Session) → None[source]
Parameters:session – The database session to use for getting services

Errors

Contains implementations of all API exceptions that can be thrown by the API and exposed to the user. Each class in this folder is an instance of APIException. Since APIException is an instance of Exception, these errors can be thrown as well. Each exception has an HTTP status code associated with it, a title, and a message. The HTTP endpoints can use these attributes to prepare error reports for the API consumer.

Deserialization Error

Describes a reportable exception that is thrown if Marshmallow or jsonschema is unable to deserialize an object provided into the API. This is a client-side error.

exception topchef.models.errors.deserialization_error.DeserializationError(source: str, validator_error_message: str) → None[source]

Describes the exception

__init__(source: str, validator_error_message: str) → None[source]
Parameters:
  • source – The source from where the error came
  • validator_error_message – The error message reported by the validator
detail
Returns:The error message reported by the serializer
status_code
Returns:Since this error is thrown when the user inputs invalid data, the status code for this error is 400 BAD REQUEST
title
Returns:The title of the error

Method Not Allowed Error

Contains the exception to be thrown if a method is not allowed

exception topchef.models.errors.method_not_allowed_error.MethodNotAllowedError(offending_method: str, allowed_methods: typing.Iterable[str])[source]

Describes an exception thrown if an HTTP method is called on an endpoint that does not have this method defined

__init__(offending_method: str, allowed_methods: typing.Iterable[str])[source]
Parameters:
  • offending_method – The method that resulted in the error being thrown
  • allowed_methods – The allowed methods
detail
Returns:A detailed message indicating why this error was thrown
status_code
Returns:The standard HTTP status code for a method not being allowed
title
Returns:The exception title

Not UUID Error

Serialization Error

Describes an error to be thrown if the server is provided with some invalid data from the API

exception topchef.models.errors.serialization_error.SerializationError(marshmallow_error: str) → None[source]

Describes an error thrown if the server receives an object that it cannot serialize with a particular serializer. This is a 500 series error. If this error is encountered in production, the API maintainer should hang their head in shame.

__init__(marshmallow_error: str) → None[source]
Parameters:marshmallow_error – The error message thrown by marshmallow
detail
Returns:A detailed error message
status_code
Returns:The status code for this error
title
Returns:The title of this exception

Service Not Found Error

Contains an exception thrown if a service is not found

exception topchef.models.errors.service_not_found_error.ServiceNotFoundError[source]

Thrown if a service with a particular ID is not found

detail
Returns:A detailed error message explaining why this error was thrown
status_code
Returns:The HTTP status code that should be thrown if a service is not found
title
Returns:The title of the exception
exception topchef.models.errors.service_not_found_error.ServiceWithUUIDNotFound(offending_uuid: uuid.UUID, *args) → None[source]

Thrown if a service with a particular UUID is not found

detail
Returns:A detailed error message explaining why this error was thrown

SQLAlchemy Error

Contains an exception to be thrown if the SQL library acts up

exception topchef.models.errors.sqlalchemy_error.SQLAlchemyError(underlying_exception: sqlalchemy.exc.SQLAlchemyError)[source]

Base class for an HTTP exception thrown as a result of a SQLAlchemy error

detail
Returns:A message indicating why the API failed
status_code
Returns:
title
Returns:The title of the error