Source code for stochss_compute.core.messages
'''
stochss_compute.core.messages
'''
# StochSS-Compute is a tool for running and caching GillesPy2 simulations remotely.
# Copyright (C) 2019-2023 GillesPy2 and StochSS developers.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from abc import ABC, abstractmethod
from enum import Enum
from hashlib import md5
from gillespy2 import Model, Results
from tornado.escape import json_encode, json_decode
[docs]class SimStatus(Enum):
'''
Status describing a remote simulation.
'''
PENDING = 'The simulation is pending.'
RUNNING = 'The simulation is still running.'
READY = 'Simulation is done and results exist in the cache.'
ERROR = 'The Simulation has encountered an error.'
DOES_NOT_EXIST = 'There is no evidence of this simulation either running or on disk.'
[docs] @staticmethod
def from_str(name):
'''
Convert str to Enum.
'''
if name == 'PENDING':
return SimStatus.PENDING
if name == 'RUNNING':
return SimStatus.RUNNING
if name == 'READY':
return SimStatus.READY
if name == 'ERROR':
return SimStatus.ERROR
if name == 'DOES_NOT_EXIST':
return SimStatus.DOES_NOT_EXIST
[docs]class Request(ABC):
'''
Base class.
'''
[docs] @abstractmethod
def encode(self):
'''
Encode self for http.
'''
[docs] @staticmethod
@abstractmethod
def parse(raw_request):
'''
Parse http for python.
'''
[docs]class Response(ABC):
'''
Base class.
'''
[docs] @abstractmethod
def encode(self):
'''
Encode self for http.
'''
[docs] @staticmethod
@abstractmethod
def parse(raw_response):
'''
Parse http for python.
'''
[docs]class SimulationRunRequest(Request):
'''
A simulation request.
:param model: A model to run.
:type model: gillespy2.Model
:param kwargs: kwargs for the model.run() call.
:type kwargs: dict[str, Any]
'''
def __init__(self, model, **kwargs):
self.model = model
self.kwargs = kwargs
[docs] def encode(self):
'''
JSON-encode model and then encode self to dict.
'''
return {'model': self.model.to_json(),
'kwargs': self.kwargs}
[docs] @staticmethod
def parse(raw_request):
'''
Parse HTTP request.
:param raw_request: The request.
:type raw_request: dict[str, str]
:returns: The decoded object.
:rtype: SimulationRunRequest
'''
request_dict = json_decode(raw_request)
model = Model.from_json(request_dict['model'])
kwargs = request_dict['kwargs']
return SimulationRunRequest(model, **kwargs)
[docs] def hash(self):
'''
Generate a unique hash of this simulation request.
Does not include number_of_trajectories in this calculation.
:returns: md5 hex digest.
:rtype: str
'''
anon_model_string = self.model.to_anon().to_json(encode_private=False)
popped_kwargs = {kw:self.kwargs[kw] for kw in self.kwargs if kw!='number_of_trajectories'}
kwargs_string = json_encode(popped_kwargs)
request_string = f'{anon_model_string}{kwargs_string}'
_hash = md5(str.encode(request_string)).hexdigest()
return _hash
[docs]class SimulationRunResponse(Response):
'''
A response from the server regarding a SimulationRunRequest.
:param status: The status of the simulation.
:type status: SimStatus
:param error_message: Possible error message.
:type error_message: str | None
:param results_id: Hash of the simulation request. Identifies the results.
:type results_id: str | None
:param results: JSON-Encoded gillespy2.Results
:type results: str | None
'''
def __init__(self, status, error_message = None, results_id = None, results = None, task_id = None):
self.status = status
self.error_message = error_message
self.results_id = results_id
self.results = results
self.task_id = task_id
[docs] def encode(self):
'''
Encode self to dict.
'''
return {'status': self.status.name,
'error_message': self.error_message or '',
'results_id': self.results_id or '',
'results': self.results or '',
'task_id': self.task_id or '',}
[docs] @staticmethod
def parse(raw_response):
'''
Parse HTTP response.
:param raw_response: The response.
:type raw_response: dict[str, str]
:returns: The decoded object.
:rtype: SimulationRunResponse
'''
response_dict = json_decode(raw_response)
status = SimStatus.from_str(response_dict['status'])
results_id = response_dict['results_id']
error_message = response_dict['error_message']
task_id = response_dict['task_id']
if response_dict['results'] != '':
results = Results.from_json(response_dict['results'])
else:
results = None
return SimulationRunResponse(status, error_message, results_id, results, task_id)
[docs]class StatusRequest(Request):
'''
A request for simulation status.
:param results_id: Hash of the SimulationRunRequest
:type results_id: str
'''
def __init__(self, results_id):
self.results_id = results_id
[docs] def encode(self):
'''
:returns: self.__dict__
:rtype: dict
'''
return self.__dict__
[docs] @staticmethod
def parse(raw_request):
'''
Parse HTTP request.
:param raw_request: The request.
:type raw_request: dict[str, str]
:returns: The decoded object.
:rtype: StatusRequest
'''
request_dict = json_decode(raw_request)
return StatusRequest(request_dict['results_id'])
[docs]class StatusResponse(Response):
'''
A response from the server about simulation status.
:param status: Status of the simulation
:type status: SimStatus
:param message: Possible error message or otherwise
:type message: str
'''
def __init__(self, status, message = None):
self.status = status
self.message = message
[docs] def encode(self):
'''
Encodes self.
:returns: self as dict
:rtype: dict[str, str]
'''
return {'status': self.status.name,
'message': self.message or ''}
[docs] @staticmethod
def parse(raw_response):
'''
Parse HTTP response.
:param raw_response: The response.
:type raw_response: dict[str, str]
:returns: The decoded object.
:rtype: StatusResponse
'''
response_dict = json_decode(raw_response)
status = SimStatus.from_str(response_dict['status'])
message = response_dict['message']
if not message:
return StatusResponse(status)
else:
return StatusResponse(status, message)
[docs]class ResultsRequest(Request):
'''
Request results from the server.
:param results_id: Hash of the SimulationRunRequest
:type results_id: str
'''
def __init__(self, results_id):
self.results_id = results_id
[docs] def encode(self):
'''
:returns: self.__dict__
:rtype: dict
'''
return self.__dict__
[docs] @staticmethod
def parse(raw_request):
'''
Parse HTTP request.
:param raw_request: The request.
:type raw_request: dict[str, str]
:returns: The decoded object.
:rtype: ResultsRequest
'''
request_dict = json_decode(raw_request)
return ResultsRequest(request_dict['results_id'])
[docs]class ResultsResponse(Response):
'''
A response from the server about the Results.
:param results: The requested Results from the cache. (JSON)
:type results: str
'''
def __init__(self, results = None):
self.results = results
[docs] def encode(self):
'''
:returns: self.__dict__
:rtype: dict
'''
return {'results': self.results or ''}
[docs] @staticmethod
def parse(raw_response):
'''
Parse HTTP response.
:param raw_response: The response.
:type raw_response: dict[str, str]
:returns: The decoded object.
:rtype: ResultsResponse
'''
response_dict = json_decode(raw_response)
if response_dict['results'] != '':
results = Results.from_json(response_dict['results'])
else:
results = None
return ResultsResponse(results)
[docs]class SourceIpRequest(Request):
'''
Restrict server access.
:param cloud_key: Random key generated locally during launch.
:type cloud_key: str
'''
def __init__(self, cloud_key):
self.cloud_key = cloud_key
[docs] def encode(self):
'''
:returns: self.__dict__
:rtype: dict
'''
return self.__dict__
[docs] @staticmethod
def parse(raw_request):
'''
Parse HTTP request.
:param raw_request: The request.
:type raw_request: dict[str, str]
:returns: The decoded object.
:rtype: SourceIpRequest
'''
request_dict = json_decode(raw_request)
return SourceIpRequest(request_dict['cloud_key'])
[docs]class SourceIpResponse(Response):
'''
Response from server containing IP address of the source.
:param source_ip: IP address of the client.
:type source_ip: str
'''
def __init__(self, source_ip):
self.source_ip = source_ip
[docs] def encode(self):
'''
:returns: self.__dict__
:rtype: dict
'''
return self.__dict__
[docs] @staticmethod
def parse(raw_response):
'''
Parses a http response and returns a python object.
:param raw_response: A raw http SourceIpResponse from the server.
:type raw_response: str
:returns: The decoded object.
:rtype: SourceIpResponse
'''
response_dict = json_decode(raw_response)
return SourceIpResponse(response_dict['source_ip'])