Skip to content

Commit 3be18a0

Browse files
committed
enable attributes to be included with POST req
1 parent fb17e10 commit 3be18a0

12 files changed

Lines changed: 429 additions & 265 deletions

hsds/attr_sn.py

Lines changed: 12 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818
from aiohttp.web import StreamResponse
1919
from json import JSONDecodeError
2020

21-
from h5json.hdf5dtype import validateTypeItem, getBaseTypeJson
2221
from h5json.hdf5dtype import createDataType, getItemSize
23-
from h5json.array_util import jsonToArray, getNumElements, bytesArrayToList
22+
from h5json.array_util import jsonToArray, getNumElements
2423
from h5json.array_util import bytesToArray, arrayToBytes, decodeData, encodeData
25-
from h5json.objid import isValidUuid, getRootObjId
24+
from h5json.objid import isValidUuid
2625

2726
from .util.httpUtil import getAcceptType, jsonResponse, getHref, getBooleanParam
2827
from .util.globparser import globmatch
@@ -32,8 +31,8 @@
3231
from .util.attrUtil import validateAttributeName, getRequestCollectionName
3332
from .util.dsetUtil import getShapeDims
3433

35-
from .servicenode_lib import getDomainJson, getObjectJson, validateAction
36-
from .servicenode_lib import getAttributes, putAttributes, deleteAttributes
34+
from .servicenode_lib import getDomainJson, getAttributeFromRequest, getAttributesFromRequest
35+
from .servicenode_lib import getAttributes, putAttributes, deleteAttributes, validateAction
3736
from .domain_crawl import DomainCrawler
3837
from . import hsds_logger as log
3938
from . import config
@@ -296,244 +295,6 @@ async def GET_Attribute(request):
296295
return resp
297296

298297

299-
async def _getTypeFromRequest(app, body, obj_id=None, bucket=None):
300-
""" return a type json from the request body """
301-
if "type" not in body:
302-
msg = "PUT attribute with no type in body"
303-
log.warn(msg)
304-
raise HTTPBadRequest(reason=msg)
305-
datatype = body["type"]
306-
307-
if isinstance(datatype, str) and datatype.startswith("t-"):
308-
# Committed type - fetch type json from DN
309-
ctype_id = datatype
310-
log.debug(f"got ctypeid: {ctype_id}")
311-
ctype_json = await getObjectJson(app, ctype_id, bucket=bucket)
312-
log.debug(f"ctype {ctype_id}: {ctype_json}")
313-
root_id = getRootObjId(obj_id)
314-
if ctype_json["root"] != root_id:
315-
msg = "Referenced committed datatype must belong in same domain"
316-
log.warn(msg)
317-
raise HTTPBadRequest(reason=msg)
318-
datatype = ctype_json["type"]
319-
# add the ctype_id to the type
320-
datatype["id"] = ctype_id
321-
elif isinstance(datatype, str):
322-
try:
323-
# convert predefined type string (e.g. "H5T_STD_I32LE") to
324-
# corresponding json representation
325-
datatype = getBaseTypeJson(datatype)
326-
except TypeError:
327-
msg = "PUT attribute with invalid predefined type"
328-
log.warn(msg)
329-
raise HTTPBadRequest(reason=msg)
330-
331-
try:
332-
validateTypeItem(datatype)
333-
except KeyError as ke:
334-
msg = f"KeyError creating type: {ke}"
335-
log.warn(msg)
336-
raise HTTPBadRequest(reason=msg)
337-
except TypeError as te:
338-
msg = f"TypeError creating type: {te}"
339-
log.warn(msg)
340-
raise HTTPBadRequest(reason=msg)
341-
except ValueError as ve:
342-
msg = f"ValueError creating type: {ve}"
343-
log.warn(msg)
344-
raise HTTPBadRequest(reason=msg)
345-
346-
return datatype
347-
348-
349-
def _getShapeFromRequest(body):
350-
""" get shape json from request body """
351-
shape_json = {}
352-
if "shape" in body:
353-
shape_body = body["shape"]
354-
shape_class = None
355-
if isinstance(shape_body, dict) and "class" in shape_body:
356-
shape_class = shape_body["class"]
357-
elif isinstance(shape_body, str):
358-
shape_class = shape_body
359-
if shape_class:
360-
if shape_class == "H5S_NULL":
361-
shape_json["class"] = "H5S_NULL"
362-
if isinstance(shape_body, dict) and "dims" in shape_body:
363-
msg = "can't include dims with null shape"
364-
log.warn(msg)
365-
raise HTTPBadRequest(reason=msg)
366-
if isinstance(shape_body, dict) and "value" in body:
367-
msg = "can't have H5S_NULL shape with value"
368-
log.warn(msg)
369-
raise HTTPBadRequest(reason=msg)
370-
elif shape_class == "H5S_SCALAR":
371-
shape_json["class"] = "H5S_SCALAR"
372-
dims = getShapeDims(shape_body)
373-
if len(dims) != 1 or dims[0] != 1:
374-
msg = "dimensions aren't valid for scalar attribute"
375-
log.warn(msg)
376-
raise HTTPBadRequest(reason=msg)
377-
elif shape_class == "H5S_SIMPLE":
378-
shape_json["class"] = "H5S_SIMPLE"
379-
dims = getShapeDims(shape_body)
380-
shape_json["dims"] = dims
381-
else:
382-
msg = f"Unknown shape class: {shape_class}"
383-
log.warn(msg)
384-
raise HTTPBadRequest(reason=msg)
385-
else:
386-
# no class, interpet shape value as dimensions and
387-
# use H5S_SIMPLE as class
388-
if isinstance(shape_body, list) and len(shape_body) == 0:
389-
shape_json["class"] = "H5S_SCALAR"
390-
else:
391-
shape_json["class"] = "H5S_SIMPLE"
392-
dims = getShapeDims(shape_body)
393-
shape_json["dims"] = dims
394-
else:
395-
shape_json["class"] = "H5S_SCALAR"
396-
397-
return shape_json
398-
399-
400-
def _getValueFromRequest(body, data_type, data_shape):
401-
""" Get attribute value from request json """
402-
dims = getShapeDims(data_shape)
403-
if "value" in body:
404-
if dims is None:
405-
msg = "Bad Request: data can not be included with H5S_NULL space"
406-
log.warn(msg)
407-
raise HTTPBadRequest(reason=msg)
408-
value = body["value"]
409-
# validate that the value agrees with type/shape
410-
arr_dtype = createDataType(data_type) # np datatype
411-
if len(dims) == 0:
412-
np_dims = [1, ]
413-
else:
414-
np_dims = dims
415-
416-
if body.get("encoding"):
417-
item_size = getItemSize(data_type)
418-
if item_size == "H5T_VARIABLE":
419-
msg = "base64 encoding is not support for variable length attributes"
420-
log.warn(msg)
421-
raise HTTPBadRequest(reason=msg)
422-
try:
423-
data = decodeData(value)
424-
except ValueError:
425-
msg = "unable to decode data"
426-
log.warn(msg)
427-
raise HTTPBadRequest(reason=msg)
428-
429-
expected_numbytes = arr_dtype.itemsize * np.prod(dims)
430-
if len(data) != expected_numbytes:
431-
msg = f"expected: {expected_numbytes} but got: {len(data)}"
432-
log.warn(msg)
433-
raise HTTPBadRequest(reason=msg)
434-
435-
# check to see if this works with our shape and type
436-
try:
437-
arr = bytesToArray(data, arr_dtype, np_dims)
438-
except ValueError as e:
439-
log.debug(f"data: {data}")
440-
log.debug(f"type: {arr_dtype}")
441-
log.debug(f"np_dims: {np_dims}")
442-
msg = f"Bad Request: encoded input data doesn't match shape and type: {e}"
443-
log.warn(msg)
444-
raise HTTPBadRequest(reason=msg)
445-
446-
value_json = None
447-
# now try converting to JSON
448-
list_data = arr.tolist()
449-
try:
450-
value_json = bytesArrayToList(list_data)
451-
except ValueError as err:
452-
msg = f"Cannot decode bytes to list: {err}, will store as encoded bytes"
453-
log.warn(msg)
454-
if value_json:
455-
log.debug("will store base64 input as json")
456-
if data_shape["class"] == "H5S_SCALAR":
457-
# just use the scalar value
458-
value = value_json[0]
459-
else:
460-
value = value_json # return this
461-
else:
462-
value = data # return bytes to signal that this needs to be encoded
463-
else:
464-
# verify that the input data matches the array shape and type
465-
try:
466-
jsonToArray(np_dims, arr_dtype, value)
467-
except ValueError as e:
468-
msg = f"Bad Request: input data doesn't match selection: {e}"
469-
log.warn(msg)
470-
raise HTTPBadRequest(reason=msg)
471-
else:
472-
value = None
473-
474-
return value
475-
476-
477-
async def _getAttributeFromRequest(app, req_json, obj_id=None, bucket=None):
478-
""" return attribute from given request json """
479-
attr_item = {}
480-
attr_type = await _getTypeFromRequest(app, req_json, obj_id=obj_id, bucket=bucket)
481-
attr_shape = _getShapeFromRequest(req_json)
482-
attr_item = {"type": attr_type, "shape": attr_shape}
483-
attr_value = _getValueFromRequest(req_json, attr_type, attr_shape)
484-
if attr_value is not None:
485-
if isinstance(attr_value, bytes):
486-
attr_value = encodeData(attr_value) # store as base64
487-
attr_item["encoding"] = "base64"
488-
else:
489-
# just store the JSON dict or primitive value
490-
attr_item["value"] = attr_value
491-
else:
492-
attr_item["value"] = None
493-
494-
return attr_item
495-
496-
497-
async def _getAttributesFromRequest(request, req_json, obj_id=None, bucket=None):
498-
""" read the given JSON dictinary and return dict of attribute json """
499-
500-
app = request.app
501-
attr_items = {}
502-
kwargs = {"obj_id": obj_id}
503-
if bucket:
504-
kwargs["bucket"] = bucket
505-
if "attributes" in req_json:
506-
attributes = req_json["attributes"]
507-
if not isinstance(attributes, dict):
508-
msg = f"expected list for attributes but got: {type(attributes)}"
509-
log.warn(msg)
510-
raise HTTPBadRequest(reason=msg)
511-
# read each attr_item and canonicalize the shape, type, verify value
512-
for attr_name in attributes:
513-
attr_json = attributes[attr_name]
514-
attr_item = await _getAttributeFromRequest(app, attr_json, **kwargs)
515-
attr_items[attr_name] = attr_item
516-
517-
elif "type" in req_json:
518-
# single attribute create - fake an item list
519-
attr_item = await _getAttributeFromRequest(app, req_json, **kwargs)
520-
if "name" in req_json:
521-
attr_name = req_json["name"]
522-
else:
523-
attr_name = request.match_info.get("name")
524-
validateAttributeName(attr_name)
525-
if not attr_name:
526-
msg = "Missing attribute name"
527-
log.warn(msg)
528-
raise HTTPBadRequest(reason=msg)
529-
530-
attr_items[attr_name] = attr_item
531-
else:
532-
log.debug(f"_getAttributes from request - no attribute defined in {req_json}")
533-
534-
return attr_items
535-
536-
537298
async def PUT_Attribute(request):
538299
"""HTTP method to create a new attribute"""
539300
log.request(request)
@@ -556,7 +317,7 @@ async def PUT_Attribute(request):
556317
log.debug(f"Attribute name: [{attr_name}]")
557318
validateAttributeName(attr_name)
558319

559-
log.info(f"PUT Attributes id: {req_obj_id} name: {attr_name}")
320+
log.info(f"PUT Attribute id: {req_obj_id} name: {attr_name}")
560321
username, pswd = getUserPasswordFromRequest(request)
561322
# write actions need auth
562323
await validateUserPassword(app, username, pswd)
@@ -588,7 +349,7 @@ async def PUT_Attribute(request):
588349

589350
# get attribute from request body
590351
kwargs = {"bucket": bucket, "obj_id": req_obj_id}
591-
attr_body = await _getAttributeFromRequest(app, body, **kwargs)
352+
attr_body = await getAttributeFromRequest(app, body, **kwargs)
592353

593354
# write attribute to DN
594355
attr_json = {attr_name: attr_body}
@@ -625,7 +386,7 @@ async def PUT_Attributes(request):
625386
await validateUserPassword(app, username, pswd)
626387

627388
if not request.has_body:
628-
msg = "PUT Attribute with no body"
389+
msg = "PUT Attributes with no body"
629390
log.warn(msg)
630391
raise HTTPBadRequest(reason=msg)
631392
try:
@@ -655,10 +416,10 @@ async def PUT_Attributes(request):
655416
if not req_obj_id:
656417
req_obj_id = domain_json["root"]
657418
kwargs = {"obj_id": req_obj_id, "bucket": bucket}
658-
attr_items = await _getAttributesFromRequest(request, body, **kwargs)
419+
attr_items = await getAttributesFromRequest(app, body, **kwargs)
659420

660421
if attr_items:
661-
log.debug(f"PUT Attribute {len(attr_items)} attibutes to add")
422+
log.debug(f"PUT Attribute {len(attr_items)} attributes to add")
662423
else:
663424
log.debug("no attributes defined yet")
664425

@@ -667,6 +428,7 @@ async def PUT_Attributes(request):
667428
obj_ids = {}
668429
if "obj_ids" in body:
669430
body_ids = body["obj_ids"]
431+
670432
if isinstance(body_ids, list):
671433
# multi cast the attributes - each attribute in attr-items
672434
# will be written to each of the objects identified by obj_id
@@ -686,7 +448,7 @@ async def PUT_Attributes(request):
686448
msg += f"{len(obj_ids)} objects"
687449
log.info(msg)
688450
elif isinstance(body_ids, dict):
689-
# each value is body_ids is a set of attriutes to write to the object
451+
# each value is body_ids is a set of attributes to write to the object
690452
# unlike the above case, different attributes can be written to
691453
# different objects
692454
if attr_items:
@@ -702,7 +464,7 @@ async def PUT_Attributes(request):
702464
id_json = body_ids[obj_id]
703465

704466
kwargs = {"obj_id": obj_id, "bucket": bucket}
705-
obj_items = await _getAttributesFromRequest(request, id_json, **kwargs)
467+
obj_items = await getAttributesFromRequest(app, id_json, **kwargs)
706468
if obj_items:
707469
obj_ids[obj_id] = obj_items
708470

hsds/ctype_dn.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,25 @@ async def POST_Datatype(request):
122122
raise HTTPInternalServerError()
123123
type_json = body["type"]
124124

125+
if "attributes" in body:
126+
# initialize attributes
127+
attrs = body["attributes"]
128+
log.debug(f"POST datatype with attributes: {attrs}")
129+
else:
130+
attrs = {}
131+
125132
# ok - all set, create committed type obj
126133
now = getNow(app)
127134

128-
log.info(f"POST_datatype, typejson: {type_json}")
135+
log.info(f"POST_datatype, type_json: {type_json}")
129136

130137
ctype_json = {
131138
"id": ctype_id,
132139
"root": root_id,
133140
"created": now,
134141
"lastModified": now,
135142
"type": type_json,
136-
"attributes": {},
143+
"attributes": attrs,
137144
}
138145

139146
kwargs = {"bucket": bucket, "notify": True, "flush": True}
@@ -145,7 +152,7 @@ async def POST_Datatype(request):
145152
resp_json["created"] = ctype_json["created"]
146153
resp_json["lastModified"] = ctype_json["lastModified"]
147154
resp_json["type"] = type_json
148-
resp_json["attributeCount"] = 0
155+
resp_json["attributeCount"] = len(attrs)
149156
resp = json_response(resp_json, status=201)
150157

151158
log.response(request, resp=resp)

hsds/ctype_sn.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,16 @@ async def POST_Datatype(request):
213213
link_title = None
214214
obj_id = None
215215
h5path = None
216+
attrs = None
217+
216218
if "id" in body:
217219
obj_id = body["id"]
218220
log.debug(f"POST datatype using client id: {obj_id}")
219221

222+
if "attributes" in body:
223+
attrs = body["attributes"]
224+
log.debug(f"POST datatype attributes: {attrs}")
225+
220226
if "link" in body:
221227
if "h5path" in body:
222228
msg = "link can't be used with h5path"
@@ -251,6 +257,8 @@ async def POST_Datatype(request):
251257
kwargs = {"bucket": bucket, "obj_type": datatype}
252258
if obj_id:
253259
kwargs["obj_id"] = obj_id
260+
if attrs:
261+
kwargs["attrs"] = attrs
254262

255263
# TBD: creation props for datatype obj?
256264
if parent_id:

0 commit comments

Comments
 (0)