def items_put_check_support(id_, class_path, path, is_collection): """Check if class_type supports PUT operation""" object_ = json.loads(request.data.decode('utf-8')) collections, parsed_classes = get_collections_and_parsed_classes() if path in parsed_classes: class_path = path obj_type = getType(path, "PUT") elif path in collections: collection = collections[path]["collection"] class_path = collection.path obj_type = collection.name link_props, link_type_check = get_link_props(class_path, object_) # Load new object and type if (validate_object(object_, obj_type, class_path) and link_type_check): if is_collection: object_ = parse_collection_members(object_) try: # Add the object with given ID object_id = crud.insert(object_=object_, id_=id_, session=get_session(), collection=is_collection) headers_ = [{"Location": f"{get_hydrus_server_url()}" f"{get_api_name()}/{path}/{object_id}"}] status_description = f"Object with ID {object_id} successfully added" status = HydraStatus(code=201, title="Object successfully added.", desc=status_description) return set_response_headers( jsonify(status.generate()), headers=headers_, status_code=status.code) except (ClassNotFound, InstanceExists, PropertyNotFound) as e: error = e.get_HTTP() return error_response(error) else: error = HydraError(code=400, title="Data is not valid") return error_response(error)
def items_delete_check_support(id_, class_type, path, is_collection): """Check if class_type supports PUT operation""" try: # Delete the Item with ID == id_ # for colletions, id_ is corresponding to their collection_id and not the id_ # primary key crud.delete(id_, class_type, session=get_session(), collection=is_collection) method = "DELETE" resource_url = f"{get_hydrus_server_url()}{get_api_name()}/{path}/{id_}" last_job_id = crud.get_last_modification_job_id(session=get_session()) new_job_id = crud.insert_modification_record(method, resource_url, session=get_session()) send_sync_update(socketio=socketio, new_job_id=new_job_id, last_job_id=last_job_id, method=method, resource_url=resource_url) status_description = f"Object with ID {id_} successfully deleted" status = HydraStatus(code=200, title="Object successfully deleted.", desc=status_description) return set_response_headers(jsonify(status.generate())) except (ClassNotFound, InstanceNotFound) as e: error = e.get_HTTP() return error_response(error)
def items_post_check_support(id_, object_, class_path, path, is_collection): """Check if class_type supports POST operation""" collections, parsed_classes = get_collections_and_parsed_classes() if path in parsed_classes: class_path = path obj_type = getType(path, "PUT") elif path in collections: collection = collections[path]["collection"] class_path = collection.path obj_type = collection.name link_props, link_type_check = get_link_props(class_path, object_) # Load new object and type if (validate_object(object_, obj_type, class_path) and link_type_check): if is_collection: object_ = parse_collection_members(object_) try: # Update the right ID if the object is valid and matches # type of Item object_id = crud.update(object_=object_, id_=id_, type_=object_["@type"], session=get_session(), api_name=get_api_name(), collection=is_collection) method = "POST" resource_url = f"{get_hydrus_server_url()}{get_api_name()}/{path}/{object_id}" last_job_id = crud.get_last_modification_job_id( session=get_session()) new_job_id = crud.insert_modification_record(method, resource_url, session=get_session()) send_sync_update(socketio=socketio, new_job_id=new_job_id, last_job_id=last_job_id, method=method, resource_url=resource_url) headers_ = [{"Location": resource_url}] status_description = (f"Object with ID {object_id} successfully " "updated") status = HydraStatus(code=200, title="Object updated", desc=status_description) return set_response_headers(jsonify(status.generate()), headers=headers_) except (ClassNotFound, InstanceNotFound, InstanceExists, PropertyNotFound) as e: error = e.get_HTTP() return error_response(error) else: error = HydraError(code=400, title="Data is not valid") return error_response(error)
def item_collection_put_response(path: str) -> Response: """ Handles PUT operation on item collection classes. :param path: Path for Item Collection :type path: str :return: Appropriate response for the PUT operation. :rtype: Response """ object_ = json.loads(request.data.decode('utf-8')) collections, parsed_classes = get_collections_and_parsed_classes() is_collection = False if path in parsed_classes: class_path = path is_collection = False obj_type = getType(path, "PUT") elif path in collections: collection = collections[path]["collection"] class_path = collection.path obj_type = collection.name is_collection = True if validate_object(object_, obj_type, class_path): # If Item in request's JSON is a valid object ie. @type is a key in object_ # and the right Item type is being added to the collection if is_collection: object_ = parse_collection_members(object_) try: # Insert object and return location in Header object_id = crud.insert(object_=object_, session=get_session(), collection=is_collection) headers_ = [{ "Location": f"{get_hydrus_server_url()}{get_api_name()}/{path}/{object_id}" }] status_description = f"Object with ID {object_id} successfully added" status = HydraStatus(code=201, title="Object successfully added", desc=status_description) return set_response_headers(jsonify(status.generate()), headers=headers_, status_code=status.code) except (ClassNotFound, InstanceExists, PropertyNotFound, PropertyNotGiven) as e: error = e.get_HTTP() return error_response(error) else: error = HydraError(code=400, title="Data is not valid") return error_response(error)
def create_status(possible_status: List[Any]) -> List[HydraStatus]: """ Creates instance of HydraStatus from expanded API doc :param possible_status: possible status from the expanded API doc :return: List of instances of HydraStatus """ status_list = [] for status in possible_status: status_id = None status_title = "The default title for status" status_desc = "The default description of status" if hydra['description'] in status: status_desc = status[hydra['description']][0]['@value'] status_code = status[hydra['statusCode']][0]['@value'] if '@id' in status: status_id = status['@id'] if hydra['title'] in status: status_title = status[hydra['title']][0]['@value'] status_ = HydraStatus(status_code, status_id, status_title, status_desc) status_list.append(status_) return status_list
def items_delete_response(path: str, int_list="") -> Response: """ Handles DELETE operation to insert multiple items. :param path: Path for Item Collection :type path: str :param int_list: Optional String containing ',' separated ID's :type int_list: List :return: Appropriate response for the DELETE operation on multiple items. :rtype: Response """ _, parsed_classes = get_collections_and_parsed_classes() if path in parsed_classes: class_type = getType(path, "DELETE") if checkClassOp(class_type, "DELETE"): # Check if class_type supports PUT operation try: # Delete the Item with ID == id_ crud.delete_multiple(int_list, class_type, session=get_session()) method = "DELETE" path_url = f"{get_hydrus_server_url()}{get_api_name()}/{path}" last_job_id = crud.get_last_modification_job_id(session=get_session()) id_list = int_list.split(',') for item in id_list: resource_url = path_url + item new_job_id = crud.insert_modification_record(method, resource_url, session=get_session()) send_sync_update(socketio=socketio, new_job_id=new_job_id, last_job_id=last_job_id, method=method, resource_url=resource_url) last_job_id = new_job_id status_description = f"Objects with ID {id_list} successfully deleted" status = HydraStatus(code=200, title="Objects successfully deleted", desc=status_description) return set_response_headers(jsonify(status.generate())) except (ClassNotFound, InstanceNotFound) as e: error = e.get_HTTP() return error_response(error) abort(405)
def create_status(possible_status: Dict[str, Any]) -> HydraStatus: """Create a HydraStatus object from the possibleStatus.""" # Syntax checks doc_keys = {"title": False, "statusCode": False, "description": True} result = {} for k, literal in doc_keys.items(): result[k] = input_key_check(possible_status, k, "possible_status", literal) # Create the HydraStatus object status = HydraStatus(result["statusCode"], result["title"], result["description"]) return status
def items_put_response(path: str, int_list="") -> Response: """ Handles PUT operation to insert multiple items. :param path: Path for Item Collection :type path: str :param int_list: Optional String containing ',' separated ID's :type int_list: List :return: Appropriate response for the PUT operation on multiple items. :rtype: Response """ object_ = json.loads(request.data.decode('utf-8')) object_ = object_["data"] _, parsed_classes = get_collections_and_parsed_classes() if path in parsed_classes: class_path = path obj_type = getType(path, "PUT") incomplete_objects = [] for obj in object_: if not check_required_props(class_path, obj): incomplete_objects.append(obj) object_.remove(obj) link_props_list, link_type_check = get_link_props_for_multiple_objects(class_path, object_) if validObjectList(object_) and link_type_check: type_result = type_match(object_, obj_type) # If Item in request's JSON is a valid object # ie. @type is one of the keys in object_ if type_result: # If the right Item type is being added to the # collection try: # Insert object and return location in Header object_id = crud.insert_multiple( objects_=object_, session=get_session(), id_=int_list) headers_ = [{"Location": f"{get_hydrus_server_url()}" f"{get_api_name()}/{path}/{object_id}"}] if len(incomplete_objects) > 0: status = HydraStatus(code=202, title="Object(s) missing required property") response = status.generate() response["objects"] = incomplete_objects return set_response_headers( jsonify(response), headers=headers_, status_code=status.code) else: status_description = f"Objects with ID {object_id} successfully added" status = HydraStatus(code=201, title="Objects successfully added", desc=status_description) return set_response_headers( jsonify(status.generate()), headers=headers_, status_code=status.code) except (ClassNotFound, InstanceExists, PropertyNotFound) as e: error = e.get_HTTP() return error_response(error) error = HydraError(code=400, title="Data is not valid") return error_response(error)
read=False, write=True) # NOTE: Properties that are required=True must be added during class object creation # Properties that are read=True are read only # Properties that are write=True are writable # Create operations for the class op_name = "UpdateClass" # The name of the operation op_method = "POST" # The method of the Operation [GET, POST, PUT, DELETE] # URI of the object that is expected for the operation op_expects = class_.id_ op_returns = None # URI of the object that is returned by the operation op_returns_header = ["Content-Type", "Content-Length"] op_expects_header = [] # List of statusCode for the operation op_status = [HydraStatus(code=200, desc="dummyClass updated.")] op1 = HydraClassOp(op_name, op_method, op_expects, op_returns, op_expects_header, op_returns_header, op_status) # Same way add DELETE, PUT and GET operations op2_status = [HydraStatus(code=200, desc="dummyClass deleted.")] op2 = HydraClassOp("DeleteClass", "DELETE", None, None, [], [], op2_status) op3_status = [HydraStatus(code=201, desc="dummyClass successfully added.")] op3 = HydraClassOp("AddClass", "PUT", class_.id_, None, [], [], op3_status) op4_status = [HydraStatus(code=200, desc="dummyClass returned.")] op4 = HydraClassOp("GetClass", "GET", None, class_.id_, [], [], op4_status) # Operations for non collection class class_2_op1_status = [HydraStatus(code=200, desc="singleClass changed.")] class_2_op1 = HydraClassOp("UpdateClass", "POST", class_2.id_, None, [], [],
def doc_gen(API: str, BASE_URL: str) -> HydraDoc: """Generate API Doc for server.""" # Main API Doc api_doc = HydraDoc(API, "API Doc for the server side API", "API Documentation for the server side system", API, BASE_URL, "vocab") # State Class state = HydraClass("State", "Class for drone state objects", endpoint=True) # Properties state.add_supported_prop( HydraClassProp("http://auto.schema.org/speed", "Speed", False, False, True)) state.add_supported_prop( HydraClassProp("http://schema.org/geo", "Position", False, False, True)) state.add_supported_prop( HydraClassProp("http://schema.org/Property", "Direction", False, False, True)) state.add_supported_prop( HydraClassProp("http://schema.org/fuelCapacity", "Battery", False, False, True)) state.add_supported_prop( HydraClassProp("https://schema.org/status", "SensorStatus", False, False, True)) state.add_supported_prop( HydraClassProp("http://schema.org/identifier", "DroneID", False, False, True)) # Operations state.add_supported_op( HydraClassOp("GetState", "GET", None, state.id_, possible_status=[ HydraStatus(code=404, desc="State not found"), HydraStatus(code=200, desc="State Returned") ])) state_collection_manages = {"property": "rdfs:type", "object": state.id_} state_collection = HydraCollection( collection_name="StateCollection", collection_description="A collection of states", manages=state_collection_manages) # Drone Class drone = HydraClass("Drone", "Class for a drone", endpoint=True) # Properties drone.add_supported_prop( HydraClassProp(state.id_, "DroneState", False, False, True)) drone.add_supported_prop( HydraClassProp("http://schema.org/name", "name", False, False, True)) drone.add_supported_prop( HydraClassProp("http://schema.org/model", "model", False, False, True)) drone.add_supported_prop( HydraClassProp("http://auto.schema.org/speed", "MaxSpeed", False, False, True)) drone.add_supported_prop( HydraClassProp("http://schema.org/device", "Sensor", False, False, True)) # Operations # Drones will submit their state to the server at certain intervals or # when some event happens drone.add_supported_op( HydraClassOp( "SubmitDrone", "POST", drone.id_, None, possible_status=[HydraStatus(code=200, desc="Drone updated")])) drone.add_supported_op( HydraClassOp( "CreateDrone", "PUT", drone.id_, None, possible_status=[HydraStatus(code=200, desc="Drone added")])) drone.add_supported_op( HydraClassOp("GetDrone", "GET", None, drone.id_, possible_status=[ HydraStatus(code=404, desc="Drone not found"), HydraStatus(code=200, desc="Drone Returned") ])) drone_collection_manages = {"property": "rdfs:type", "object": drone.id_} drone_collection = HydraCollection( collection_name="DroneCollection", collection_description="A collection of drones", manages=drone_collection_manages) # NOTE: Commands are stored in a collection. You may GET a command or you # may DELETE it, there is not UPDATE. command = HydraClass("Command", "Class for drone commands", endpoint=True) command.add_supported_prop( HydraClassProp("http://schema.org/identifier", "DroneID", False, False, True)) command.add_supported_prop( HydraClassProp(state.id_, "State", False, False, True)) # Used by mechanics to get newly added commands command.add_supported_op( HydraClassOp("GetCommand", "GET", None, command.id_, possible_status=[ HydraStatus(code=404, desc="Command not found"), HydraStatus(code=200, desc="Command Returned") ])) # Used by server to add new commands command.add_supported_op( HydraClassOp( "AddCommand", "PUT", command.id_, None, possible_status=[HydraStatus(code=201, desc="Command added")])) command.add_supported_op( HydraClassOp( "DeleteCommand", "DELETE", None, None, possible_status=[HydraStatus(code=201, desc="Command deleted")])) command_collection_manages = { "property": "rdfs:type", "object": command.id_ } command_collection = HydraCollection( collection_name="CommandCollection", collection_description="A collection of commands", manages=command_collection_manages) # Data is stored as a collection. Each data object can be read. # New data added to the collection datastream = HydraClass("Datastream", "Class for a datastream entry", endpoint=True) datastream.add_supported_prop( HydraClassProp("http://schema.org/QuantitativeValue", "Temperature", False, False, True)) datastream.add_supported_prop( HydraClassProp("http://schema.org/identifier", "DroneID", False, False, True)) datastream.add_supported_prop( HydraClassProp("http://schema.org/geo", "Position", False, False, True)) datastream.add_supported_op( HydraClassOp("ReadDatastream", "GET", None, datastream.id_, possible_status=[ HydraStatus(code=404, desc="Data not found"), HydraStatus(code=200, desc="Data returned") ])) datastream.add_supported_op( HydraClassOp( "UpdateDatastream", "POST", datastream.id_, None, possible_status=[HydraStatus(code=200, desc="Data updated")])) datastream.add_supported_op( HydraClassOp( "DeleteDatastream", "DELETE", None, None, possible_status=[HydraStatus(code=200, desc="Data deleted")])) datastream_collection_manages = { "property": "rdfs:type", "object": datastream.id_ } datastream_collection = HydraCollection( collection_name="DatastreamCollection", collection_description="A collection of datastream", manages=datastream_collection_manages) # Logs to be accessed mostly by the GUI. Mechanics should add logs for # every event. log = HydraClass("LogEntry", "Class for a log entry", endpoint=True) # Subject log.add_supported_prop( HydraClassProp("http://schema.org/identifier", "DroneID", True, True, False)) # Predicate log.add_supported_prop( HydraClassProp("http://schema.org/UpdateAction", "Update", False, True, False)) log.add_supported_prop( HydraClassProp("http://schema.org/ReplyAction", "Get", False, True, False)) log.add_supported_prop( HydraClassProp("http://schema.org/SendAction", "Send", False, True, False)) # Objects log.add_supported_prop( HydraClassProp(state.id_, "State", False, True, False)) log.add_supported_prop( HydraClassProp(datastream.id_, "Data", False, True, False)) log.add_supported_prop( HydraClassProp(command.id_, "Command", False, True, False)) # GUI will get a certain log entry. log.add_supported_op( HydraClassOp("GetLog", "GET", None, log.id_, possible_status=[ HydraStatus(code=404, desc="Log entry not found"), HydraStatus(code=200, desc="Log entry returned") ])) log.add_supported_op( HydraClassOp( "AddLog", "PUT", log.id_, None, possible_status=[HydraStatus(code=201, desc="Log entry created")])) log_collection_manages = {"property": "rdfs:type", "object": log.id_} log_collection = HydraCollection( collection_name="LogEntryCollection", collection_description="A collection of logs", manages=log_collection_manages) # Single object representing the area of interest. No collections. area = HydraClass("Area", "Class for Area of Interest of the server", endpoint=True) # Using two positions to have a bounding box area.add_supported_prop( HydraClassProp("http://schema.org/geo", "TopLeft", False, False, True)) area.add_supported_prop( HydraClassProp("http://schema.org/geo", "BottomRight", False, False, True)) # Allowing updation of the area of interest area.add_supported_op( HydraClassOp("UpdateArea", "POST", area.id_, None, possible_status=[ HydraStatus(code=200, desc="Area of interest changed") ])) area.add_supported_op( HydraClassOp("GetArea", "GET", None, area.id_, possible_status=[ HydraStatus(code=200, desc="Area of interest not found"), HydraStatus(code=200, desc="Area of interest returned") ])) message = HydraClass("Message", "Class for messages received by the GUI interface", endpoint=True) message.add_supported_prop( HydraClassProp("http://schema.org/Text", "MessageString", False, False, True)) message.add_supported_op( HydraClassOp("GetMessage", "GET", None, message.id_, possible_status=[ HydraStatus(code=200, desc="Message not found"), HydraStatus(code=200, desc="Message returned") ])) message.add_supported_op( HydraClassOp( "DeleteMessage", "DELETE", None, None, possible_status=[HydraStatus(code=200, desc="Message deleted")])) message_collection_manages = { "property": "rdfs:type", "object": message.id_ } message_collection = HydraCollection( collection_name="MessageCollection", collection_description="A collection of messages", manages=message_collection_manages) api_doc.add_supported_class(drone) api_doc.add_supported_collection(drone_collection) api_doc.add_supported_class(state) api_doc.add_supported_collection(state_collection) api_doc.add_supported_class(datastream) api_doc.add_supported_collection(datastream_collection) api_doc.add_supported_class(log) api_doc.add_supported_collection(log_collection) api_doc.add_supported_class(area) api_doc.add_supported_class(command) api_doc.add_supported_collection(command_collection) api_doc.add_supported_class(message) api_doc.add_supported_collection(message_collection) api_doc.add_baseResource() api_doc.add_baseCollection() api_doc.gen_EntryPoint() return api_doc