Contains a client written in Python to consume the TopChef API.
The dependencies for this project are given in the requirements.txt
file.
This project relies chiefly on the
requests library for sending HTTP
requests to the API. The six library is
a Python 2 - Python 3 compatibility library. It makes creating
abstract base classes much easier.
Python versions greater than 2.6 are supported.
This project is managed on Waffle IO. The team throughput is shown below
Install the dependencies by running
pip install -r requirements.txt
Then
python setup.py install
After installation, import Client
from the topchef_client
library.
Client
is an abstract base class that must have its run
method
implemented prior to use. The run method takes in a dictionary of parameters
and it must return a dictionary of result values.
The input parameters satisfy the service's job_registration_schema
. The
result dictionary must satisfy the service's job_result_schema
.
Consider a service that takes in a number and adds 1
to it. Let the
argument be constrained between 1
and 10
. The JSON schema to express
this is
{
"type": "integer",
"minimum": 1,
"maximum": 10
}
This matches any integer between 1 and 10.
Considering that we're using JSON, it may make sense to wrap the parameters that we want into a JS object. This would provide a context for our values, making the code we use much more self-documenting. If we want to add parameters later, then we can do that much more easily when the object is wrapped. The JSON schema we'll use for input is therefore
{
"type": "object",
"properties":
{
"value":
{
"type": "integer",
"minimum": 1,
"maximum": 10
}
}
}
from topchef_client import Client
class AddOne(Client):
def run(self, parameters):
result = parameters['value'] + 1
return {'value': result}
The client takes care of validating the input against the input schema, so we
can be confident in extracting the 'value'
key out of the parameter
dictionary without worrying about KeyError
being thrown. However, the onus
is on the run method to return a dictionary that is compliant with the result
schema. Failure to do so will result in a ProcessingError
being thrown.
Service IDs can be obtained from the API
by sending a GET
request to the /services
endpoint. The default
constructor takes in the address of the TopChef API and the service ID.
The service can also be registered as a new service at runtime. The classmethod
Client.register_service``` takes in an address, a service name, description, and the service schemas. The method then
POST``s to the API in
order to register a service with a new service ID.
At runtime, the client launches a polling thread and a processing thread. The
polling thread is responsible for sending a PATCH
request to the API to let
the API know that the client is alive and accepting jobs. The second thread is
a processing thread which checks the /services/<service_id>/queue
for
jobs that have not yet been processed. If it finds one, it executes the job.
Both of these threads are daemonic. If only daemonic threads remain in a Python
program, Python will shut the program down. In our case, instantiating
AddOne
and calling the instance's start
method will start both threads.
After starting, a blocking operation needs to be included somewhere that will
pause the main threads while the client's two threads get to work. An example
of such a method is given in the complete listing below. This code will run
a service.
from topchef_client import Client
import time
class AddOne(Client):
def run(self, parameters):
return {'value': parameters['value'] + 1}
service_address = 'http://localhost'
service_id = '912a5684-7174-11e6-88ce-3c970e7271f5'
if __name__ == '__main__':
service = AddOne(service_address, service_id)
service.start()
while True:
time.sleep(3600)
- Michal Kononenko (@MichalKononenko)
- Thomas Alexander (@whitewhim2718)