Пример #1
0
def get_context(category: str) -> Response:
    """
    Generate the context for a given category.

    :param category: The category of class for which context is required
    :type category: str
    :return: Response with context
    :rtype: Response
    """
    collections, parsed_classes = get_collections_and_parsed_classes()
    # Check for collection
    if category in get_doc().collections:
        # type: Union[Dict[str,Any],Dict[int,str]]
        response = {"@context": collections[category]["context"].generate()}
        return set_response_headers(jsonify(response))
    # Check for non collection class
    elif category in parsed_classes:
        response = {
            "@context":
            get_doc().parsed_classes[category]["context"].generate()
        }
        return set_response_headers(jsonify(response))
    else:
        error = HydraError(code=404,
                           title="NOT FOUND",
                           desc="Context not found")
        return error_response(error)
Пример #2
0
def get_path_from_type(type_: str) -> str:
    _, parsed_classes = get_collections_and_parsed_classes()
    expanded_base_url = DocUrl.doc_url
    for class_name in parsed_classes:
        class_ = parsed_classes[class_name]['class']
        if type_ == class_.id_.split(expanded_base_url)[1]:
            return class_.path
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
def finalize_response(path: str, obj: Dict[str, Any]) -> Dict[str, Any]:
    """
    finalize response objects by removing properties which are not readable and correcting path
    of nested objects.
    :param path: Path of the collection or non-collection class.
    :param obj: object being finalized
    :return: An object not containing any `readable=False` properties and having proper path
             of any nested object's url.
    """
    collections, parsed_classes = get_collections_and_parsed_classes()
    expanded_base_url = DocUrl.doc_url
    if path in collections:
        members = list()
        for member in obj["members"]:
            member_id = member[0]
            member_type = member[1]
            member_path = get_path_from_type(member_type)
            member = {
                "@type":
                "hydra:Link",
                "@id":
                f"{get_host_domain()}/{get_api_name()}/{member_path}/{member_id}",
            }
            members.append(member)
        obj['members'] = members
        return obj
    else:
        # path is of a non-collection class
        supported_properties = get_doc(
        ).parsed_classes[path]["class"].supportedProperty
        expanded_base_url = DocUrl.doc_url
        for prop in supported_properties:
            # Skip not required properties which are not inserted yet.
            if not prop.required and prop.title not in obj:
                continue
            # if prop.read is False:
            #     obj.pop(prop.title, None)
            elif isinstance(prop.prop, HydraLink):
                hydra_link = prop.prop
                range_class = hydra_link.range.split(expanded_base_url)[1]
                nested_path, is_collection = get_nested_class_path(range_class)
                if is_collection:
                    id = obj[prop.title]
                    obj[prop.title] = f"/{get_api_name()}/{nested_path}/{id}"
                else:
                    obj[prop.title] = f"/{get_api_name()}/{nested_path}"
            elif expanded_base_url in prop.prop:
                prop_class = prop.prop.split(expanded_base_url)[1]
                prop_class_path = parsed_classes[prop_class]['class'].path
                id = obj[prop.title]
                class_resp = crud.get(id,
                                      prop_class,
                                      get_api_name(),
                                      get_session(),
                                      path=prop_class_path)
                obj[prop.title] = finalize_response(prop_class_path,
                                                    class_resp)
        return obj
Пример #6
0
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)
Пример #7
0
def item_collection_get_response(path: str) -> Response:
    """
    Handles GET operation on item collection classes.

    :param path: Path for Item Collection
    :type path: str
    :return: Appropriate response for the GET operation.
    :rtype: Response
    """
    search_params = request.args.to_dict()
    collections, parsed_classes = get_collections_and_parsed_classes()
    api_name = get_api_name()
    expanded_base_url = DocUrl.doc_url
    # If endpoint and GET method is supported in the API and class is supported
    if path in parsed_classes:
        abort(405)
    if path in collections:
        collection = collections[path]["collection"]
        class_name = collection.manages["object"].split(expanded_base_url)[1]
        collection_manages_class = parsed_classes[class_name]["class"]
        class_type = collection_manages_class.title
        class_path = collection_manages_class.path
    try:
        # Get collection details from the database
        # create partial function for crud operation
        crud_response = partial(crud.get_collection,
                                api_name,
                                class_type,
                                session=get_session(),
                                path=path,
                                search_params=search_params,
                                collection=False)
        if get_pagination():
            # Get paginated response
            response = crud_response(paginate=True, page_size=get_page_size())
        else:
            # Get whole collection
            response = crud_response(paginate=False)

        response["search"] = add_iri_template(path=class_path,
                                              API_NAME=api_name,
                                              collection_path=path)

        return set_response_headers(jsonify(hydrafy(response, path=path)))

    except (ClassNotFound, PageNotFound, InvalidSearchParameter,
            OffsetOutOfRange) as e:
        error = e.get_HTTP()
        return error_response(error)
Пример #8
0
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)
Пример #9
0
def checkClassOp(path: str, method: str) -> bool:
    """
    Check if the Class supports the operation.
    :param path: Path of the collection or non-collection class.
    :param method: Method name.
    :return: True if the method is defined, false otherwise.
    """
    collections, parsed_classes = get_collections_and_parsed_classes()
    if path in collections:
        supported_operations = get_doc(
        ).collections[path]["collection"].supportedOperation
    else:
        supported_operations = get_doc(
        ).parsed_classes[path]["class"].supportedOperation
    for supportedOp in supported_operations:
        if supportedOp.method == method:
            return True
    return False
Пример #10
0
def get_link_props(path: str, object_) -> Tuple[Dict[str, Any], bool]:
    """
    Get dict of all hydra_link properties of a class.
    :param path: Path of the collection or non-collection class.
    :param object_: Object being inserted/updated.
    :return: Tuple with one elements as Dict with property_title as key and
             instance_id(for collection class) or class_name(for non-collection class) as value,
             second element represents boolean representing validity of the link.
    """
    link_props = {}
    collections, parsed_classes = get_collections_and_parsed_classes()
    expanded_base_url = DocUrl.doc_url
    if path in collections:
        # path is of a collection class
        supported_properties = get_doc(
        ).collections[path]["collection"].supportedProperty
    else:
        # path is of a non-collection class
        supported_properties = get_doc(
        ).parsed_classes[path]["class"].supportedProperty
    for supportedProp in supported_properties:
        if isinstance(supportedProp.prop,
                      HydraLink) and supportedProp.title in object_:
            prop_range = supportedProp.prop.range
            range_class_name = prop_range.split(expanded_base_url)[1]
            for collection_path in get_doc().collections:
                if collection_path in object_[supportedProp.title]:
                    class_title = get_doc(
                    ).collections[collection_path]['collection'].class_.title
                    if range_class_name != class_title:
                        return {}, False
                    link_props[supportedProp.title] = object_[
                        supportedProp.title].split('/')[-1]
                    break
            if supportedProp.title not in link_props:
                for class_path in get_doc().parsed_classes:
                    if class_path in object_[supportedProp.title]:
                        class_title = get_doc(
                        ).parsed_classes[class_path]['class'].title
                        if range_class_name != class_title:
                            return {}, False
                        link_props[supportedProp.title] = class_title
                        break
    return link_props, True
Пример #11
0
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)
Пример #12
0
 def put(self, id_: str, path: str) -> Response:
     """
     Add new object_ optional <id_> parameter using HTTP PUT.
     :param id_ - ID of Item to be updated
     :param path - Path for Item type( Specified in APIDoc @id) to be updated
     """
     id_ = str(id_)
     collections, parsed_classes = get_collections_and_parsed_classes()
     is_collection = False
     if path in parsed_classes:
         class_path = path
     if path in collections:
         item_class = collections[path]["collection"]
         class_path = item_class.path
         is_collection = True
     if checkClassOp(class_path, "PUT"):
         return items_put_check_support(id_, class_path, path,
                                        is_collection)
     abort(405)
Пример #13
0
 def post(self, id_: str, path: str) -> Response:
     """
     Update object of type<path> at ID<id_> with new object_ using HTTP POST.
     :param id_ - ID of Item to be updated
     :param path - Path for Item type( Specified in APIDoc @id)
     """
     id_ = str(id_)
     collections, parsed_classes = get_collections_and_parsed_classes()
     is_collection = False
     if path in parsed_classes:
         class_path = path
     if path in collections:
         item_class = collections[path]["collection"]
         class_path = item_class.path
         is_collection = True
     object_ = json.loads(request.data.decode('utf-8'))
     if checkClassOp(class_path, "POST") and check_writeable_props(
             class_path, object_):
         return items_post_check_support(id_, object_, class_path, path,
                                         is_collection)
     abort(405)
Пример #14
0
def get_nested_class_path(class_type: str) -> Tuple[str, bool]:
    """
    Get the path of class
    :param class_type: class name whose path is needed
    :return: Tuple, where the first element is the path string and
             the second element is a boolean, True if the class is a collection class
             False otherwise.
    """
    expanded_base_url = DocUrl.doc_url
    collections, parsed_classes = get_collections_and_parsed_classes()
    for path in collections:
        collection = collections[path]["collection"]
        class_name = collection.manages["object"].split(expanded_base_url)[1]
        collection_manages_class = parsed_classes[class_name]["class"]
        collection_manages_class_type = collection_manages_class.title
        collection_manages_class_path = collection_manages_class.path
        if collection_manages_class_type == class_type:
            return collection_manages_class_path, True
    for class_path in parsed_classes:
        if class_type == parsed_classes[class_path]["class"].title:
            return class_path, False
Пример #15
0
def check_required_props(path: str, obj: Dict[str, Any]) -> bool:
    """
    Check if the object contains all required properties.
    :param path: Path of the collection or non-collection class.
    :param obj: object under check
    :return: True if the object contains all required properties
             False otherwise.
    """
    collections, parsed_classes = get_collections_and_parsed_classes()
    if path in collections:
        # path is of a collection class
        supported_properties = get_doc(
        ).collections[path]["collection"].supportedProperty
    else:
        # path is of a non-collection class
        supported_properties = get_doc(
        ).parsed_classes[path]["class"].supportedProperty
    for prop in supported_properties:
        if prop.required:
            if prop.title not in obj:
                return False
    return True
Пример #16
0
 def delete(self, id_: str, path: str) -> Response:
     """
     Delete object with id=id_ from database.
     :param id_ - ID of Item to be deleted
     :param path - Path for Item type( Specified in APIDoc @id) to be deleted
     """
     id_ = str(id_)
     collections, parsed_classes = get_collections_and_parsed_classes()
     is_collection = False
     if path in parsed_classes:
         class_path = path
         class_type = parsed_classes[path]['class'].title
     if path in collections:
         item_class = collections[path]["collection"]
         class_type = item_class.name
         # Get path of the collection-class
         class_path = item_class.path
         is_collection = True
     if checkClassOp(class_path, "DELETE"):
         return items_delete_check_support(id_, class_type, path,
                                           is_collection)
     abort(405)
Пример #17
0
def check_writeable_props(path: str, obj: Dict[str, Any]) -> bool:
    """
    Check that the object only contains writeable fields(properties).
    :param path: Path of the collection or non-collection class.
    :param obj: object under check
    :return: True if the object only contains writeable properties
             False otherwise.
    """
    collections, parsed_classes = get_collections_and_parsed_classes()
    if path in collections:
        # path is of a collection class
        supported_properties = get_doc(
        ).collections[path]["collection"].supportedProperty
    else:
        # path is of a non-collection class
        supported_properties = get_doc(
        ).parsed_classes[path]["class"].supportedProperty
    for prop in supported_properties:
        if prop.write is False:
            if prop.title in obj:
                return False
    return True
Пример #18
0
 def get(self, id_: str, path: str) -> Response:
     """
     GET object with id = id_ from the database.
     :param id_ : Item ID
     :param path : Path for Item ( Specified in APIDoc @id)
     :return : object with id=id_
     """
     id_ = str(id_)
     collections, parsed_classes = get_collections_and_parsed_classes()
     is_collection = False
     if path in parsed_classes:
         class_path = path
         class_type = parsed_classes[path]['class'].title
     if path in collections:
         item_class = collections[path]["collection"]
         class_type = item_class.name
         # Get path of the collection-class
         class_path = item_class.path
         is_collection = True
     if checkClassOp(class_path, "GET"):
         return items_get_check_support(id_, class_type, class_path, path,
                                        is_collection)
     abort(405)