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 are404and403, then the error returned will be400. If both client-side (4xx) and server-side (5xx) errors are encountered, the server will return500.
-
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
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: Trueif the job belongs to this service
-
__eq__(other: topchef.models.interfaces.job_list.JobList) → bool[source]¶ Parameters: other – The other job list against which equality is to be determined Returns: Trueif the equality definition is met, otherwiseFalse
-
__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: KeyErrorif 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.
-
description¶ Returns: A human-readable description of the service
-
has_timed_out¶ Returns: Trueif the server has timed out, otherwiseFalse
-
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 Truewhen they are ready to accept services. Clients SHOULD flip this flag toFalsewhen 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.
Abstract Classes¶
These classes contain some often-repeated business logic.
API Error¶
Contains a model for a TopChef 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 configobject fromtopchef.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
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
-
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
500series 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
-
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
-