Skip to content

Commit 117f904

Browse files
authored
Merge pull request #130 from oda-hub/fix_type_key
Refactor class hierarchy of NB2WProduct
2 parents 35b7b93 + 514f3dd commit 117f904

File tree

8 files changed

+129
-41
lines changed

8 files changed

+129
-41
lines changed

dispatcher_plugin_nb2workflow/products.py

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import json
66

77
from cdci_data_analysis.analysis.products import LightCurveProduct, BaseQueryProduct, ImageProduct, SpectrumProduct
8-
from cdci_data_analysis.analysis.parameters import Parameter
8+
from cdci_data_analysis.analysis.parameters import Parameter, subclasses_recursive
9+
from cdci_data_analysis.analysis.exceptions import ProductProcessingError
910
from oda_api.data_products import NumpyDataProduct, ODAAstropyTable, BinaryProduct, PictureProduct
11+
1012
from .util import AstropyTableViewParser, with_hashable_dict
1113
from oda_api.ontology_helper import Ontology
1214
from io import StringIO
@@ -35,34 +37,18 @@ def write(self, file_name=None, overwrite=True, file_dir=None):
3537
file_path = self.file_path.path
3638

3739
self.table_data.write(file_path, overwrite=overwrite, format='ascii.ecsv')
38-
39-
class NB2WProduct:
40-
def __init__(self,
41-
encoded_data,
42-
data_product_type=BaseQueryProduct,
43-
out_dir='./',
44-
name='nb2w',
45-
extra_metadata={}):
4640

47-
# this constructor is only valid for NumpyDataProduct-based products
48-
49-
self.name = name
50-
self.extra_metadata = extra_metadata
51-
metadata = encoded_data.get('meta_data', {})
52-
self.out_dir = out_dir
53-
numpy_data_prod = NumpyDataProduct.decode(encoded_data)
54-
55-
if not numpy_data_prod.name:
56-
numpy_data_prod.name = self.name
5741

58-
self.dispatcher_data_prod = data_product_type(
59-
name=self.name,
60-
data=numpy_data_prod,
61-
meta_data=metadata,
62-
file_dir=out_dir,
63-
file_name = f"{self.name}.fits")
64-
42+
class NB2WProduct:
6543

44+
def __init__(self, *args, **kwargs):
45+
error_msg = "The output"
46+
name = kwargs.get('name', None)
47+
if name is not None:
48+
error_msg += f" with name \"{name}\""
49+
error_msg += " has been wrongly annotated."
50+
raise ProductProcessingError(error_msg)
51+
6652
def write(self):
6753
file_path = self.dispatcher_data_prod.file_path
6854
self.dispatcher_data_prod.write()
@@ -74,10 +60,10 @@ def get_html_draw(self):
7460
@classmethod
7561
def _init_as_list(cls, encoded_data, *args, **kwargs):
7662
encoded_data = cls._dejsonify(encoded_data)
77-
63+
7864
if isinstance(encoded_data, list):
7965
return [cls(elem, *args, **kwargs) for elem in encoded_data]
80-
66+
8167
return [cls(encoded_data, *args, **kwargs)]
8268

8369
@classmethod
@@ -96,7 +82,7 @@ def _prod_list_description_analyser(
9682
onto = None
9783
par_prod_class_dict = {}
9884

99-
mapping = {getattr(x, 'type_key'): x for x in cls.__subclasses__() if hasattr(x, 'type_key')}
85+
mapping = {getattr(x, 'type_key'): x for x in subclasses_recursive(cls) if hasattr(x, 'type_key')}
10086
mapping.update(par_prod_class_dict)
10187

10288
prod_classes_dict = {}
@@ -135,7 +121,7 @@ def _prod_list_description_analyser(
135121
extra_kw.update({'extra_metadata': extra_metadata})
136122

137123
prod_classes_dict[key] = (mapping.get(cls_owl_type, cls), name, extra_kw)
138-
124+
139125
return prod_classes_dict
140126

141127

@@ -152,7 +138,8 @@ def prod_list_factory(cls, output_description_dict, output, out_dir = './', onto
152138
name=val[1],
153139
**val[2]))
154140
except Exception as e:
155-
logger.error('unable to construct %s product: %s from this: %s ', key, e, output[key])
141+
logger.error('unable to construct %s product: %s from %s', key, e, val[0])
142+
raise
156143

157144
return prod_list
158145

@@ -165,8 +152,44 @@ def _dejsonify(encoded_data):
165152
pass
166153
return encoded_data
167154

155+
156+
class _CommentProduct(NB2WProduct):
157+
type_key = 'http://odahub.io/ontology#WorkflowResultComment'
158+
159+
def __init__(self, *args, **kwargs): ...
160+
161+
@classmethod
162+
def _init_as_list(cls, encoded_data, *args, **kwargs):
163+
return []
164+
165+
class NB2WNumpyDataProduct(NB2WProduct):
166+
type_key = 'http://odahub.io/ontology#NumpyDataProduct'
167+
168+
def __init__(self,
169+
encoded_data,
170+
data_product_type=BaseQueryProduct,
171+
out_dir='./',
172+
name='nb2w',
173+
extra_metadata={}):
174+
175+
self.name = name
176+
self.extra_metadata = extra_metadata
177+
metadata = encoded_data.get('meta_data', {})
178+
self.out_dir = out_dir
179+
numpy_data_prod = NumpyDataProduct.decode(encoded_data)
180+
if not numpy_data_prod.name:
181+
numpy_data_prod.name = self.name
182+
183+
self.dispatcher_data_prod = data_product_type(
184+
name=self.name,
185+
data=numpy_data_prod,
186+
meta_data=metadata,
187+
file_dir=out_dir,
188+
file_name=f"{self.name}.fits")
189+
190+
168191
class NB2WParameterProduct(NB2WProduct):
169-
type_key = 'oda:WorkflowParameter'
192+
type_key = 'http://odahub.io/ontology#WorkflowParameter'
170193

171194
ontology_path = None
172195

@@ -248,7 +271,6 @@ def get_html_draw(self):
248271

249272

250273
class NB2WProgressProduct(NB2WProduct):
251-
252274
def __init__(self, progress_html_data, out_dir=None, name='progress'):
253275
self.out_dir = out_dir
254276
self.name = name
@@ -315,7 +337,7 @@ def get_html_draw(self):
315337
return {'image': {'div': '<br><br>'+parser.tabcode,
316338
'script': f"<script>{script_text}</script>"} }
317339

318-
class NB2WLightCurveProduct(NB2WProduct):
340+
class NB2WLightCurveProduct(NB2WNumpyDataProduct):
319341
type_key = 'http://odahub.io/ontology#LightCurve'
320342

321343
def __init__(self,
@@ -366,7 +388,7 @@ def get_html_draw(self, unit_id=None):
366388
)
367389
return im_dic
368390

369-
class NB2WSpectrumProduct(NB2WProduct):
391+
class NB2WSpectrumProduct(NB2WNumpyDataProduct):
370392
type_key = 'http://odahub.io/ontology#Spectrum'
371393

372394
def __init__(self,
@@ -379,15 +401,16 @@ def __init__(self,
379401
out_dir=out_dir, name=name,
380402
extra_metadata=extra_metadata)
381403

382-
class NB2WImageProduct(NB2WProduct):
404+
class NB2WImageProduct(NB2WNumpyDataProduct):
383405
type_key = 'http://odahub.io/ontology#Image'
384406

385407
def __init__(self,
386408
encoded_data,
387409
out_dir='./',
388410
name='image',
389411
extra_metadata={}):
390-
super().__init__(encoded_data,
412+
413+
super().__init__(encoded_data,
391414
data_product_type=ImageProduct,
392415
out_dir=out_dir,
393416
name=name,

dispatcher_plugin_nb2workflow/queries.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
NB2WPictureProduct,
77
NB2WTextProduct,
88
NB2WParameterProduct,
9-
NB2WProgressProduct)
9+
NB2WProgressProduct,
10+
NB2WNumpyDataProduct,
11+
NB2WImageProduct)
1012
from oda_api.ontology_helper import Ontology
1113
import os
1214
from functools import lru_cache
@@ -186,7 +188,7 @@ def process_product_method(self, instrument, prod_list, api=False):
186188
elif isinstance(product, NB2WProgressProduct):
187189
progress_dp_list.append({'name': product.name,
188190
'value': product.progress_data})
189-
else: # NB2WProduct contains NumpyDataProd by default
191+
else:
190192
np_dp_list.append(product.dispatcher_data_prod.data)
191193

192194
extra_meta[product.name] = getattr(product, 'extra_metadata', {})

test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
git+https://github.com/oda-hub/nb2workflow.git@master#egg=nb2workflow[service,rdf]
22
git+https://github.com/oda-hub/oda_api.git@master#egg=oda_api
3-
git+https://github.com/oda-hub/dispatcher-app.git@master#egg=cdci_data_analysis
3+
git+https://github.com/oda-hub/dispatcher-app.git@dedicated-exception-product-processing#egg=cdci_data_analysis

tests/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ def mock_backend(httpserver):
119119
bin_json = json.loads(fd.read())
120120
with open(os.path.join(responses_path, 'image.json'), 'r') as fd:
121121
image_json = json.loads(fd.read())
122+
with open(os.path.join(responses_path, 'data_product.json'), 'r') as fd:
123+
data_product_json = json.loads(fd.read())
122124
# with open(os.path.join(responses_path, 'test_output.html'), 'r') as fd:
123125
# test_output_html = fd.read()
124126

@@ -128,6 +130,8 @@ def mock_backend(httpserver):
128130
httpserver.expect_request(f'/api/v1.0/get/table').respond_with_json(table_json)
129131
httpserver.expect_request(f'/api/v1.0/get/ascii_binary').respond_with_json(bin_json)
130132
httpserver.expect_request(f'/api/v1.0/get/image').respond_with_json(image_json)
133+
httpserver.expect_request(f'/api/v1.0/get/data_product').respond_with_json(data_product_json)
134+
httpserver.expect_request(f'/api/v1.0/get/data_product_no_annotations').respond_with_json(data_product_json)
131135
# httpserver.expect_request(f'/trace/nb2w-ylp5ovnm/lightcurve').respond_with_data(test_output_html)
132136
httpserver.expect_request(f'/trace/nb2w-ylp5ovnm/lightcurve').respond_with_handler(trace_get_func_handler)
133137
httpserver.expect_request(f'/api/v1.0/get/dummy_echo').respond_with_handler(return_request_query_dict)

tests/responses/ascii_binary.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/responses/data_product.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"exceptions": [],
3+
"jobdir": "/tmp/nb2w-aqkvl461",
4+
"output": {
5+
"result": {}
6+
}
7+
}

tests/responses/options.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,34 @@
3838
},
3939
"parameters": {}
4040
},
41+
"data_product": {
42+
"output": {
43+
"result": {
44+
"comment": " http://odahub.io/ontology#DataProduct",
45+
"name": "result",
46+
"owl_type": "http://odahub.io/ontology#DataProduct",
47+
"python_type": {
48+
"type_object": "<class 'str'>"
49+
},
50+
"value": ""
51+
}
52+
},
53+
"parameters": {}
54+
},
55+
"data_product_no_annotations": {
56+
"output": {
57+
"result": {
58+
"comment": "",
59+
"name": "result",
60+
"owl_type": "",
61+
"python_type": {
62+
"type_object": "<class 'str'>"
63+
},
64+
"value": ""
65+
}
66+
},
67+
"parameters": {}
68+
},
4169
"image": {
4270
"output": {
4371
"result": {

tests/test_plugin.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ def test_instrument_products(dispatcher_live_fixture, mock_backend):
151151
if isinstance(elem, dict) and 'prod_dict' in elem.keys():
152152
prod_dict = elem['prod_dict']
153153
assert prod_dict == {'ascii_binary': 'ascii_binary_query',
154+
'data_product': 'data_product_query',
155+
'data_product_no_annotations': 'data_product_no_annotations_query',
154156
'dummy_echo': 'dummy_echo_query',
155157
'image': 'image_query',
156158
'file_download': 'file_download_query',
@@ -289,6 +291,28 @@ def test_image_product(dispatcher_live_fixture, mock_backend):
289291
imdata = jdata['products']['numpy_data_product_list'][0]
290292
oda_ndp = ImageDataProduct.decode(imdata)
291293

294+
@pytest.mark.parametrize("product_type", ["data_product", "data_product_no_annotations"])
295+
def test_data_product_product(dispatcher_live_fixture, mock_backend, product_type):
296+
server = dispatcher_live_fixture
297+
logger.info("constructed server: %s", server)
298+
299+
c = requests.get(server + "/run_analysis",
300+
params = {'instrument': 'example0',
301+
'query_status': 'new',
302+
'query_type': 'Real',
303+
'product_type': product_type,
304+
'api': 'True',
305+
'run_asynch': 'False'})
306+
logger.info("content: %s", c.text)
307+
jdata = c.json()
308+
logger.info(json.dumps(jdata, indent=4, sort_keys=True))
309+
logger.info(jdata)
310+
assert c.status_code == 200
311+
assert jdata['query_status'] == 'failed'
312+
assert jdata['job_status'] == 'failed'
313+
assert jdata['exit_status']['error_message'] == 'The output with name "result" has been wrongly annotated.'
314+
assert jdata['exit_status']['message'] == 'Error during the products post processing'
315+
292316
def test_get_config_dict_from_kg():
293317
from dispatcher_plugin_nb2workflow.exposer import get_config_dict_from_kg
294318

0 commit comments

Comments
 (0)