Skip to content

Commit

Permalink
Add calculation failure phases and cancel method
Browse files Browse the repository at this point in the history
  • Loading branch information
seignovert committed Apr 1, 2019
1 parent a29b27a commit 02b4c9b
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 20 deletions.
29 changes: 26 additions & 3 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,30 @@ def cassini_instrument():
'name': 'CASSINI_CIRS_RAD',
}

@pytest.fixture
def api_queued():
'''Queued API response.'''
return {
"status": "OK",
"message": "The request was successful.",
"calculationId": "0788aba2-d4e5-4028-9ef1-4867ad5385e0",
"result": {
"phase": "QUEUED",
"position": 6
}
}

@pytest.fixture
def api_error():
'''Error API output.'''
'''Error API reponse.'''
return {
"status": "ERROR",
"message": None,
"calculationId": "0788aba2-d4e5-4028-9ef1-4867ad5385e0"
"message": "The request has failed.",
"calculationId": "0788aba2-d4e5-4028-9ef1-4867ad5385e0",
"error": {
"shortDescription": "The provided Calculation ID does not "
"correspond to any requested calculation."
}
}

@pytest.fixture
Expand Down Expand Up @@ -176,6 +193,12 @@ def test_instruments(cassini_kernel_set, cassini_instrument):
assert int(instrument) == cassini_instrument['id']
assert str(instrument) == cassini_instrument['name']


def test_api_read_queued(api_queued):
'''Test queued position output from API.'''
_, phase = API.read(api_queued)
assert phase == 'QUEUED | POSITION: 6'

def test_api_read_error(api_error):
'''Test error output from API.'''
with pytest.raises(APIError):
Expand Down
17 changes: 14 additions & 3 deletions tests/test_calculation_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import pytest

from webgeocalc import Calculation, StateVector
from webgeocalc.errors import (CalculationAlreadySubmitted, CalculationNotCompleted,
CalculationTimeOut, ResultAttributeError)
from webgeocalc.errors import (CalculationAlreadySubmitted, CalculationFailed,
CalculationNotCompleted, CalculationTimeOut,
ResultAttributeError)
from webgeocalc.vars import API_URL

@pytest.fixture
Expand Down Expand Up @@ -266,7 +267,6 @@ def loading_kernels():
}
}


def test_calculation_run(requests_mock, params, response, results):
'''Run generic calculation.'''
calc = Calculation(**params)
Expand Down Expand Up @@ -327,6 +327,17 @@ def test_state_vector_single_time(requests_mock, params_sv, response_sv, results
with pytest.raises(ResultAttributeError):
_ = column.wrong_attr

def test_calculation_cancel(params):
'''Test error if calculation is cancelled.'''
calc = Calculation(**params)
calc.submit()
calc.cancel()

assert calc.phase == 'CANCELLED'

with pytest.raises(CalculationFailed):
calc.run()

def test_calculation_timeout(requests_mock, params, loading_kernels):
'''Test error if response exceed timeout.'''
requests_mock.post(API_URL + '/calculation/new', json=loading_kernels)
Expand Down
36 changes: 34 additions & 2 deletions webgeocalc/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def read(json):
'''
if json['status'] != 'OK':
raise APIError(json['message'])
raise APIError(json['error']['shortDescription'])

keys = json.keys()

Expand All @@ -134,7 +134,11 @@ def read(json):
return [dtype(item) for item in json['items']]

if 'result' in keys:
return json['calculationId'], json['result']['phase']
phase = json['result']['phase']
if phase == 'QUEUED':
phase += f' | POSITION: {json["result"]["position"]}'

return json['calculationId'], phase

if 'columns' in keys and 'rows' in keys:
cols = [ColumnResult(col) for col in json['columns']]
Expand Down Expand Up @@ -313,6 +317,34 @@ def phase_calculation(self, calculation_id):
'''
return self.get(f'/calculation/{calculation_id}')

def cancel_calculation(self, calculation_id):
'''Cancels a previously requested calculation, should this not be completed, or its results..
``GET: /calculation/{id}/cancel``
Calculations that have been already ``DISPATCHED``, previously ``CANCELLED`` or
that have ``EXPIRED`` cannot be cancelled, and requesting it will produce an `error`.
Parameters
----------
calculation_id: str
Calculation id.
Returns
-------
(str, str)
Tuple of the calculation phase:
``(calculation-id, phase)``
See: :py:func:`read`.
Example
-------
>>> API.phase_calculation('0788aba2-d4e5-4028-9ef1-4867ad5385e0') # noqa: E501 # doctest: +SKIP
('0788aba2-d4e5-4028-9ef1-4867ad5385e0', 'CANCELLED')
'''
return self.get(f'/calculation/{calculation_id}/cancel')

def results_calculation(self, calculation_id):
'''Gets the results of a complete calculation.
Expand Down
36 changes: 27 additions & 9 deletions webgeocalc/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

from .api import API
from .errors import (CalculationAlreadySubmitted, CalculationConflictAttr,
CalculationIncompatibleAttr, CalculationInvalidAttr,
CalculationInvalidValue, CalculationNotCompleted,
CalculationRequiredAttr, CalculationTimeOut,
CalculationUndefinedAttr)
CalculationFailed, CalculationIncompatibleAttr,
CalculationInvalidAttr, CalculationInvalidValue,
CalculationNotCompleted, CalculationRequiredAttr,
CalculationTimeOut, CalculationUndefinedAttr)
from .types import KernelSetDetails
from .vars import (ABERRATION_CORRECTION, ANGULAR_UNITS, ANGULAR_VELOCITY_REPRESENTATION,
ANGULAR_VELOCITY_UNITS, AXIS, CALCULATION_TYPE,
COORDINATE_REPRESENTATION, DIRECTION_VECTOR_TYPE, INTERVALS,
ORIENTATION_REPRESENTATION, OUTPUT_TIME_FORMAT, SHAPE,
ANGULAR_VELOCITY_UNITS, AXIS, CALCULATION_FAILED_PHASES,
CALCULATION_TYPE, COORDINATE_REPRESENTATION, DIRECTION_VECTOR_TYPE,
INTERVALS, ORIENTATION_REPRESENTATION, OUTPUT_TIME_FORMAT, SHAPE,
STATE_REPRESENTATION, SUB_POINT_TYPE, TIME_FORMAT, TIME_LOCATION,
TIME_STEP_UNITS, TIME_SYSTEM)

Expand Down Expand Up @@ -229,11 +229,11 @@ def submit(self):
Example
-------
>>> calc.submit() # noqa: E501 # doctest: +SKIP
[Calculation submit] Phase: LOADING_KERNELS (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
[Calculation submit] Phase: QUEUED | POSITION: 6 (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.id # doctest: +SKIP
'8750344d-645d-4e43-b159-c8d88d28aac6'
>>> calc.phase # doctest: +SKIP
'LOADING_KERNELS'
'QUEUED | POSITION: 6'
'''
if self.id is not None:
Expand All @@ -252,13 +252,27 @@ def resubmit(self):
self.id = None
self.submit()

def cancel(self):
'''Cancels calculation if already submitted.'''
if self.id is not None:
_, self.phase = API.cancel_calculation(self.id)

if self.verbose:
print(f'[Calculation cancellation] Phase: {self.phase} (id: {self.id})')

def update(self):
'''Update calculation phase ``phase``.
Example
-------
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: QUEUED | POSITION: 3 (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: STARTING (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: LOADING_KERNELS (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: CALCULATING (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # doctest: +SKIP
[Calculation update] Phase: COMPLETE (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
'''
Expand Down Expand Up @@ -355,6 +369,10 @@ def run(self, timeout=30, sleep=1):

if self.phase == 'COMPLETE':
return self.results

if self.phase in CALCULATION_FAILED_PHASES:
raise CalculationFailed(self.phase)

time.sleep(sleep)

raise CalculationTimeOut(timeout, sleep)
Expand Down
13 changes: 10 additions & 3 deletions webgeocalc/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,17 @@ def __init__(self, calculation_id):
super().__init__(msg)

class CalculationNotCompleted(IOError):
'''This exception is raised when calculation status is not complete.'''
'''This exception is raised when calculation phase is not complete.'''

def __init__(self, status):
msg = f"Calculation is status: '{status}'"
def __init__(self, phase):
msg = f"Calculation phase: '{phase}'"
super().__init__(msg)

class CalculationFailed(IOError):
'''This exception is raised when calculation failed.'''

def __init__(self, phase):
msg = f"Calculation failed phase: '{phase}'"
super().__init__(msg)

class CalculationTimeOut(IOError):
Expand Down
7 changes: 7 additions & 0 deletions webgeocalc/vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@

API_URL = 'https://wgc2.jpl.nasa.gov:8443/webgeocalc/api'

CALCULATION_FAILED_PHASES = [
'FAILED',
'CANCELLED',
'DISPATCHED',
'EXPIRED',
]

CALCULATION_TYPE = [
'STATE_VECTOR',
'ANGULAR_SEPARATION',
Expand Down

0 comments on commit 02b4c9b

Please sign in to comment.