Exemple #1
0
class CollectionmembersQuery:
    """
    CollectionmembersQuery is used for get members of any collectionendpoint.
    Once it get the data from the server and store it in Redis.
    And after that it can query from Redis memory.
    "fs:endpoints" is using as a faceted index,
    for track which collection endpoint's data is stored in Redis.
    """
    def __init__(self, api_doc, url, graph):
        self.redis_connection = RedisProxy()
        self.handle_data = HandleData()
        self.connection = self.redis_connection.get_connection()
        self._data = self.handle_data
        self.collection = CollectionEndpoints(graph.redis_graph,
                                              graph.class_endpoints)

        self.api_doc = api_doc
        self.url = url

    def data_from_server(self, endpoint):
        """
        Load data from the server for first time.
        :param endpoint: endpoint for getting data from the server.
        :return: get data from the Redis memory.
        """
        self.collection.load_from_server(endpoint, self.api_doc, self.url,
                                         self.connection)

        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph',
            'MATCH(p:collection) WHERE(p.type="{}") RETURN p.members'.format(
                endpoint))
        print(endpoint, " members")
        return self._data.show_data(get_data)

    def get_members(self, query):
        """
        Gets Data from the Redis.
        :param query: query get from the user, Ex: DroneCollection members
        :return: get data from the Redis memory.
        """
        endpoint = query.replace(" members", "")
        if (str.encode("fs:endpoints") in self.connection.keys()
                and str.encode(endpoint)
                in self.connection.smembers("fs:endpoints")):
            get_data = self.connection.execute_command(
                'GRAPH.QUERY', 'apigraph', """MATCH(p:collection)
                   WHERE(p.type='{}')
                   RETURN p.members""".format(endpoint))
            print(endpoint, " members")
            return self._data.show_data(get_data)

        else:
            self.connection.sadd("fs:endpoints", endpoint)
            print(self.connection.smembers("fs:endpoints"))
            return self.data_from_server(endpoint)
 def __init__(self, redis_proxy: RedisProxy, graph_name="apigraph") -> None:
     """Initialize Graph Utils module
     :param redis_proxy: RedisProxy object created from redis_proxy module
     :param graph_name: Graph Key name to be created in Redis
     :return: None
     """
     self.redis_proxy = redis_proxy
     self.redis_connection = redis_proxy.get_connection()
     self.graph_name = graph_name
     self.redis_graph = Graph(graph_name, self.redis_connection)
Exemple #3
0
def check_url_exist(check_url, facades):
    redis_connection = RedisProxy()
    connection = redis_connection.get_connection()
    url = check_url.decode("utf8")
    if (str.encode("fs:url") in connection.keys()
            and check_url in connection.smembers("fs:url")):
        print("url already exist in Redis")
        facades.initialize(False)
    else:
        facades.initialize(True)
        connection.sadd("fs:url", url)
Exemple #4
0
class ClassPropertiesValue:
    """
    ClassPropertiesValue is used for geting the values for properties of class
    And once values get from server and then it stored in Redis.
    """
    def __init__(self, api_doc, url, graph):
        self.redis_connection = RedisProxy()
        self.handle_data = HandleData()
        self.connection = self.redis_connection.get_connection()
        self._data = self.handle_data
        self.clas = ClassEndpoints(graph.redis_graph, graph.class_endpoints)

        self.api_doc = api_doc
        self.url = url

    def data_from_server(self, endpoint):
        """
        Load data from the server for once.
        :param endpoint: endpoint for getting data from the server.
        :return: get data from the Redis memory.
        """
        self.clas.load_from_server(endpoint, self.api_doc, self.url,
                                   self.connection)

        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph', """MATCH(p:classes)
               WHERE(p.type='{}')
               RETURN p.property_value""".format(endpoint))
        return get_data

    def get_property_value(self, query):
        """
        Load data in Redis if data is not in it with help of checklist.
        Checklist have the track on endpoints.
        And access the properties values and show them.
        :param query: get query from the user, Ex:classLocation property_value
        :return: get data from the Redis memory.
        """
        query = query.replace("class", "")
        endpoint = query.replace(" property_value", "")
        if (str.encode("fs:endpoints") in self.connection.keys()
                and str.encode(endpoint)
                in self.connection.smembers("fs:endpoints")):
            get_data = self.connection.execute_command(
                'GRAPH.QUERY', 'apigraph', """MATCH (p:classes)
                   WHERE (p.type = '{}')
                   RETURN p.property_value""".format(endpoint))
        else:
            self.connection.sadd("fs:endpoints", endpoint)
            print(self.connection.smembers("fs:endpoints"))
            get_data = self.data_from_server(endpoint)

        print(endpoint, "property_value")
        return self._data.show_data(get_data)
Exemple #5
0
 def main(self, new_url, api_doc, check_commit):
     redis_connection = RedisProxy()
     redis_con = redis_connection.get_connection()
     self.url = new_url
     self.redis_graph = Graph("apigraph", redis_con)
     print("loading... of graph")
     self.get_endpoints(api_doc, redis_con)
     if check_commit:
         print("commiting")
         self.redis_graph.commit()
         # creating whole the graph in redis
         print("done!!!!")
Exemple #6
0
class EndpointQuery:
    """
    EndpointQuery is used for get the endpoints from the Redis.
    """
    def __init__(self):
        self.redis_connection = RedisProxy()
        self.handle_data = HandleData()
        self.connection = self.redis_connection.get_connection()
        self._data = self.handle_data

    def get_allEndpoints(self, query):
        """
        It will return both type(class and collection) of endpoints.
        :param query: query gets from the user, Ex: endpoints
        :returns: data get from the Redis memory.
        """
        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph',
            "MATCH (p:classes) RETURN p") + self.connection.execute_command(
                'GRAPH.QUERY', 'apigraph', "MATCH (p:collection) RETURN p")
        print("classEndpoints + CollectionEndpoints")

        return self._data.show_data(get_data)

    def get_classEndpoints(self, query):
        """
        It will return all class Endpoints.
        :param query: query get from user, Ex: classEndpoint
        :return: get data from the Redis memory.
        """
        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph', "MATCH (p:classes) RETURN p")

        print("classEndpoints")

        return self._data.show_data(get_data)

    def get_collectionEndpoints(self, query):
        """
        It will returns all collection Endpoints.
        :param query: query get from the user, Ex: collectionEndpoint
        :return: get data from the Redis memory.
        """
        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph', "MATCH (p:collection) RETURN p")

        print("collectoinEndpoints")

        return self._data.show_data(get_data)
 def __init__(self, entrypoint_url: str, api_doc: dict,
              redis_proxy: RedisProxy):
     """Initialize GraphOperations
     :param entrypoint_url: Entrypoint URL for the hydrus server
     :param api_doc: ApiDoc object that contains the documentation
     :param redis_proxy: RedisProxy object created from redis_proxy module
     :return: None
     """
     self.entrypoint_url = entrypoint_url
     self.api_doc = api_doc
     self.redis_proxy = redis_proxy
     self.redis_connection = redis_proxy.get_connection()
     self.vocabulary = 'vocab'
     self.graph_utils = GraphUtils(redis_proxy)
     self.redis_graph = Graph("apigraph", self.redis_connection)
     self.session = Session()
Exemple #8
0
 def __init__(self, entrypoint_url: str, api_doc: HydraDoc,
              redis_proxy: RedisProxy):
     """Initialize GraphOperations
     :param entrypoint_url: Entrypoint URL for the hydrus server
     :param api_doc: ApiDoc object that contains the documentation
     :param redis_proxy: RedisProxy object created from redis_proxy module
     :return: None
     """
     self.entrypoint_url = entrypoint_url
     url_parse = urlparse(entrypoint_url)
     self.entrypoint = url_parse.scheme + "://" + url_parse.netloc
     self.api_name = url_parse.path.rstrip('/')
     self.api_doc = api_doc
     self.redis_proxy = redis_proxy
     self.redis_connection = redis_proxy.get_connection()
     self.complete_vocabulary_url = self.api_doc.doc_url
     self.vocabulary = self.api_doc.doc_name
     self.graph_utils = GraphUtils(redis_proxy)
     self.redis_graph = Graph("apigraph", self.redis_connection)
     self.session = Session()
Exemple #9
0
def query(apidoc, url):
    """
    It uses only for query purpose.
    Querying still user wants or still user enters the exit.
    :param apidoc: Apidocumentation for the given url.
    :param url: url given by user.
    """
    redis_connection = RedisProxy()
    connection = redis_connection.get_connection()
    api_doc = doc_maker.create_doc(apidoc)
    facades = QueryFacades(api_doc, url, False)
    check_url = str.encode(url)
    check_url_exist(check_url, facades)

    while True:
        print("press exit to quit")
        query = input(">>>").strip()
        if query == "exit":
            break
        elif query == "help":
            help()
        else:
            print(facades.user_query(query))
Exemple #10
0
class QueryFacades:
    """
    It is used for call the above defined functions based on given query.
    Using test as a bool which identify that function is using for tests.
    """
    def __init__(self, api_doc, url, test):

        self.endpoint_query = EndpointQuery()
        self.api_doc = api_doc
        self.url = url
        self.properties = PropertiesQuery()
        self.compare = CompareProperties()
        self.test = test
        self.redis_connection = RedisProxy()
        self.connection = self.redis_connection.get_connection()

    def initialize(self, check_commit):
        """
        Initialize is used to initialize the graph for given url.
        """
        print("just initialize")

        self.graph = InitialGraph()
        self.graph.main(self.url, self.api_doc, check_commit)

    def check_fine_query(self, query):
        if query.count(" ") != 1:
            return RequestError("error")

    def user_query(self, query):
        """
        It calls function based on queries type.
        """
        query = query.replace("show ", "")
        if query == "endpoints":
            data = self.endpoint_query.get_allEndpoints(query)
            return data
        elif query == "classEndpoints":
            data = self.endpoint_query.get_classEndpoints(query)
            return data
        elif query == "collectionEndpoints":
            data = self.endpoint_query.get_collectionEndpoints(query)
            return data
        elif "members" in query:
            check_query = self.check_fine_query(query)
            if isinstance(check_query, RequestError):
                logger.info("Error: Incorrect query")
                return None
            else:
                self.members = CollectionmembersQuery(self.api_doc, self.url,
                                                      self.graph)
                if self.test:
                    data = self.members.data_from_server(
                        query.replace(" members", ""))
                    return data
                else:
                    data = self.members.get_members(query)
                    return data
        elif "objects" in query:
            if query[-1] == " ":
                logger.info("Error: incorrect query")
                return None
            check_query = self.check_fine_query(query)
            if isinstance(check_query, RequestError):
                logger.info("Error: Incorrect query")
                return None
            else:
                data = self.properties.get_members_properties(query)
                return data
        elif "object" in query:
            if query[-1] == " ":
                logger.info("Error: incorrect query")
                return None
            check_query = self.check_fine_query(query)
            if isinstance(check_query, RequestError):
                logger.info("Error: Incorrect query")
                return None
            else:
                data = self.properties.get_object_property(query)
                return data
        elif "Collection" in query:
            if query[-1] == " ":
                logger.info("Error: incorrect query")
                return None
            check_query = self.check_fine_query(query)
            if isinstance(check_query, RequestError):
                logger.info("Error: Incorrect query")
                return None
            else:
                data = self.properties.get_collection_properties(query)
                return data
        elif "class" in query and "property_value" in query:
            check_query = self.check_fine_query(query)
            if isinstance(check_query, RequestError):
                logger.info("Error: Incorrect query")
                return None
            else:
                self.class_property = ClassPropertiesValue(
                    self.api_doc, self.url, self.graph)
                data = self.class_property.get_property_value(query)
                return data
        elif "class" in query:
            if query[-1] == " ":
                logger.info("Error: incorrect query")
                return None
            check_query = self.check_fine_query(query)
            if isinstance(check_query, RequestError):
                logger.info("Error: Incorrect query")
                return None
            else:
                data = self.properties.get_classes_properties(query)
                return data
        else:
            if " and " in query or " or " in query:
                if query[-1] == " " or query[-3] == "and" or query[-2] == "or":
                    logger.info("Error: incorrect query")
                    return None
                query_len = len(query.split())
                and_or_count = query.count("and") + query.count("or")
                if query_len != (and_or_count + 2 * (and_or_count + 1)):
                    logger.info("Error: Incorrect query")
                    return None
                data = self.compare.object_property_comparison_list(query)
                return data
            elif query.count(" ") == 1:
                key, value = query.split(" ")
                print("query: ", query)
                search_index = "fs:" + key + ":" + value
                for key in self.connection.keys():
                    if search_index == key.decode("utf8"):
                        data = self.connection.smembers(key)
                        return data
            logger.info(
                "Incorrect query: Use 'help' to know about querying format")
            return None
Exemple #11
0
class CompareProperties:
    """
    CompareProperties is used for extracting endpoints with help of properties
    Like input: name Drone1 and model xyz
    Then output: /api/DroneCollection/2
    With follows objects_property_comparison_list()
    """
    def __init__(self):
        self.redis_connection = RedisProxy()
        self.handle_data = HandleData()
        self.connection = self.redis_connection.get_connection()
        self._data = self.handle_data

    def faceted_key(self, key, value):
        """
        It is simply concatenate the arguments and make faceted key.
        """
        return ("{}".format("fs:" + key + ":" + value))

    def convert_byte_string(self, value_set):
        """
        It converts byte strings to strings.
        """
        new_value_set = set()
        for obj in value_set:
            string = obj.decode('utf-8')
            new_value_set.add(string)
        return new_value_set

    def and_or_query(self, query_list):
        """
        It is a recursive function.
        It takes the arguement as list(query_list)
        which contains the faceted indexes and operation and brackets also.
        List Ex:['fs:model:xyz', 'and', '(', 'fs:name:Drone1', 'or',
                'fs:name:Drone2', ')']
                for query "model xyz and (name Drone1 or name Drone2)"
        :param query_list: get a list of faceted indexes and operations
        :param return: get data from the Redis memory for specific query.
        """
        # check if there is both "and" and "or" with help of bracket.
        if ")" not in query_list:
            # if only one operation "and" or "or".
            if "or" in query_list:
                while query_list.count("or") > 0:
                    query_list.remove("or")
                get_data = self.connection.sunion(*query_list)
                return (get_data)
            else:
                while query_list.count("and") > 0:
                    query_list.remove("and")
                get_data = self.connection.sinter(*query_list)
                return (get_data)
        else:
            # if both the operators are present in query
            for query_element in query_list:
                if query_element == ")":
                    # find index for closed bracket
                    close_index = query_list.index(query_element)
                    break
            for i in range(close_index, -1, -1):
                if query_list[i] == "(":
                    # find index for open bracket
                    open_index = i
                    break
            get_value = self.and_or_query(query_list[open_index +
                                                     1:close_index])
            get_value = self.convert_byte_string(get_value)

            # design random faceted key for store result of partial query.
            faceted_key = "fs:" + \
                ''.join(random.choice(string.ascii_letters + string.digits)
                        for letter in range(8))
            # add data in random faceted key.
            for obj in get_value:
                self.connection.sadd(faceted_key, obj)
            # add new executed partial query value with key in query list.
            query_list.insert(open_index, faceted_key)
            # generate new query after remove executed partial query
            query_list = query_list[0:open_index + 1] + \
                query_list[close_index + 2:len(query_list)]
            return self.and_or_query(query_list)

    def object_property_comparison_list(self, query):
        """
        It takes the argument as a string that can contain many keys and value
        And make a list of all keys and values and identify operator(if there)
        And execute sinter or sunion commands of Redis over faceted keys.
        :param query: get query from the user, Ex: name Drone1
        :return: get data from the Redis memory.
        """

        faceted_list = []
        query_list = []
        while True:
            if query.count(" ") > 1:
                key, value, query = query.split(" ", 2)
                while "(" in key:
                    query_list.append("(")
                    key = key.replace("(", "", 1)

                faceted_list.append(self.faceted_key(key, value))
                query_list.append(
                    self.faceted_key(key.replace("(", ""),
                                     value.replace(")", "")))
                while ")" in value:
                    query_list.append(")")
                    value = value.replace(")", "", 1)
            else:
                key, value = query.split(" ")
                query = ""
                while "(" in key:
                    query_list.append("(")
                    key = key.replace("(", "", 1)

                faceted_list.append(self.faceted_key(key, value))
                query_list.append(
                    self.faceted_key(key.replace("(", ""),
                                     value.replace(")", "")))
                while ")" in value:
                    query_list.append(")")
                    value = value.replace(")", "", 1)
            if len(query) > 0:
                operation, query = query.split(" ", 1)
                query_list.append(operation)

            else:
                break

        get_data = self.and_or_query(query_list)
        return self.show_data(get_data)

    def show_data(self, get_data):
        """It returns the data in readable format."""
        property_list = []
        for string1 in get_data:
            string1 = string1.decode('utf-8')
            property_list.append(string1)


#        print("list   ",property_list)
        return property_list
Exemple #12
0
class PropertiesQuery:
    """
    PropertiesQuery is used for all properties for alltypes of  nodes like:
    classes or collection endpoints, members,object.
    """
    def __init__(self):
        self.redis_connection = RedisProxy()
        self.handle_data = HandleData()
        self.connection = self.redis_connection.get_connection()
        self._data = self.handle_data

    def get_classes_properties(self, query):
        """
        Show the given type of property of given Class endpoint.
        :param query: get query from the user, Ex: classLocation properties
        :return: get data from the Redis memory.
        """
        query = query.replace("class", "")
        endpoint, query = query.split(" ")
        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph',
            'MATCH ( p:classes ) WHERE (p.type="{}") RETURN p.{}'.format(
                endpoint, query))
        print("class", endpoint, query)
        return self._data.show_data(get_data)

    def get_collection_properties(self, query):
        """
        Show the given type of property of given collection endpoint.
        :param query: get query from the user, Ex: DroneCollection properties.
        :return: get data from the Redis memory.
        """
        endpoint, query = query.split(" ")

        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph',
            'MATCH ( p:collection ) WHERE (p.type="{}") RETURN p.{}'.format(
                endpoint, query))

        print("collection", endpoint, query)
        return self._data.show_data(get_data)

    def get_members_properties(self, query):
        """
        Show the given type of property of given member.
        :param query: gete query from the user, Ex: objectsDrone properties
        :return: get data from the Redis memory.
        """
        endpoint, query = query.split(" ")
        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph',
            'MATCH ( p:{} ) RETURN p.id,p.{}'.format(endpoint, query))

        print("member", endpoint, query)
        return self._data.show_data(get_data)

    def get_object_property(self, query):
        """
        Show the given type of property of given object.
        :param query: get query from the user,Ex:object</api/DroneCollection/2
        :return: get data from the Redis memory.
        """
        endpoint, query = query.split(" ")
        endpoint = endpoint.replace("object", "")
        index = endpoint.find("Collection")
        id_ = "object" + endpoint[5:index]
        get_data = self.connection.execute_command(
            'GRAPH.QUERY', 'apigraph',
            'MATCH ( p:{}) WHERE (p.parent_id = "{}") RETURN p.{}'.format(
                id_, endpoint, query))

        print("object", endpoint, query)
        return self._data.show_data(get_data)
Exemple #13
0
class Agent(Session, socketio.ClientNamespace, socketio.Client):
    """Provides a straightforward GET, PUT, POST, DELETE -
    CRUD interface - to query hydrus
    """
    def __init__(self, entrypoint_url: str, namespace: str = '/sync') -> None:
        """Initialize the Agent
        :param entrypoint_url: Entrypoint URL for the hydrus server
        :param namespace: Namespace endpoint to listen for updates
        :return: None
        """
        self.entrypoint_url = entrypoint_url.strip().rstrip('/')
        url_parse = urlparse(entrypoint_url)
        self.entrypoint = url_parse.scheme + "://" + url_parse.netloc
        self.api_name = url_parse.path.rstrip('/')
        self.redis_proxy = RedisProxy()
        self.redis_connection = self.redis_proxy.get_connection()
        Session.__init__(self)
        self.fetch_apidoc()
        self.initialize_graph()
        self.graph_operations = GraphOperations(self.entrypoint_url,
                                                self.api_doc, self.redis_proxy)
        # Declaring Socket Rules and instantiation Synchronization Socket
        socketio.ClientNamespace.__init__(self, namespace)
        socketio.Client.__init__(self, logger=True)
        socketio.Client.register_namespace(self, self)
        socketio.Client.connect(self,
                                self.entrypoint_url,
                                namespaces=namespace)
        self.last_job_id = ""

    def fetch_apidoc(self) -> HydraDoc:
        """Fetches API DOC from Link Header by checking the hydra apiDoc
        relation and passes the obtained JSON-LD to doc_maker module of
        hydra_python_core to return HydraDoc which is used by the agent.
        :return HydraDoc created from doc_maker module
        """
        try:
            res = super().get(self.entrypoint_url)
            api_doc_url = res.links[
                'http://www.w3.org/ns/hydra/core#apiDocumentation']['url']
            jsonld_api_doc = super().get(api_doc_url).json()
            self.api_doc = doc_maker.create_doc(jsonld_api_doc,
                                                self.entrypoint, self.api_name)
            return self.api_doc
        except:
            print("Error parsing your API Documentation")
            raise SyntaxError

    def initialize_graph(self) -> None:
        """Initialize the Graph on Redis based on ApiDoc
        :param entrypoint_url: Entrypoint URL for the hydrus server
        :return: None
        """
        self.graph = InitialGraph()
        self.redis_connection.delete("apigraph")
        self.graph.main(self.entrypoint_url, self.api_doc, True)
        self.redis_connection.sadd("fs:url", self.entrypoint_url)

    def get(self,
            url: str = None,
            resource_type: str = None,
            follow_partial_links: bool = False,
            filters: dict = {},
            cached_limit: int = sys.maxsize) -> Union[dict, list, Paginator]:
        """READ Resource from Server/cached Redis
        :param url: Resource URL to be fetched
        :param resource_type: Resource object type
        :param filters: filters to apply when searching, resources properties
        :param cached_limit : Minimum amount of resources to be fetched
        :param follow_partial_links: If set to True, Paginator can go through pages.
        :return: Dict when one object or a list when multiple targeted objects
        :return: Iterator when param follow_partial_links is set to true
                    Iterator will be returned.
                    Usage:
                    paginator = agent.get('http://*****:*****@type'] in self.api_doc.parsed_classes:
                return response.json()
            else:
                if follow_partial_links:
                    return Paginator(response=response.json())
                else:
                    return response.json()
        else:
            return response.text

    def put(self, url: str, new_object: dict) -> Tuple[dict, str]:
        """CREATE resource in the Server/cache it on Redis
        :param url: Server URL to create the resource at
        :param new_object: Dict containing the object to be created
        :return: Dict with server's response and resource URL
        """
        response = super().put(url, json=new_object)

        if response.status_code == 201:
            url = response.headers['Location']
            # Graph_operations returns the embedded resources if finding any
            full_resource = super().get(url)
            embedded_resources = self.graph_operations.put_processing(
                url, full_resource.json())
            self.process_embedded(embedded_resources)
            return response.json(), url
        else:
            return response.text, ""

    def post(self, url: str, updated_object: dict) -> dict:
        """UPDATE resource in the Server/cache it on Redis
        :param url: Server URL to update the resource at
        :param updated_object: Dict containing the updated object
        :return: Dict with server's response
        """
        response = super().post(url, json=updated_object)

        if response.status_code == 200:
            # Graph_operations returns the embedded resources if finding any
            embedded_resources = \
                self.graph_operations.post_processing(url, updated_object)
            self.process_embedded(embedded_resources)
            return response.json()
        else:
            return response.text

    def delete(self, url: str) -> dict:
        """DELETE resource in the Server/delete it on Redis
        :param url: Resource URL to be deleted
        :return: Dict with server's response
        """
        response = super().delete(url)
        if response.status_code == 200:
            self.graph_operations.delete_processing(url)
            return response.json()
        else:
            return response.text

    def process_embedded(self, embedded_resources: list) -> None:
        """Helper function to process a list of embedded resources
        fetching and linking them to their parent Nodes
        :param embedded_resources: List of dicts containing resources
        """
        # Embedded resources are fetched and then properly linked
        for embedded_resource in embedded_resources:
            self.get(embedded_resource['embedded_url'])
            self.graph_operations.link_resources(
                embedded_resource['parent_id'],
                embedded_resource['parent_type'],
                embedded_resource['embedded_url'],
                embedded_resource['embedded_type'], self.graph)

    # Below are the functions that are responsible to process Socket Events
    def on_connect(self, data: dict = None) -> None:
        """Method executed when the Agent is successfully connected to the Server
        """
        if data:
            self.last_job_id = data['last_job_id']
            logger.info('Socket Connection Established - Synchronization ON')

    def on_disconnect(self):
        """Method executed when the Agent is disconnected
        """
        pass

    def on_update(self, data) -> None:
        """Method executed when the Agent receives an event named 'update'
        This is sent to all clients connected the server under the designed Namespace
        :param data: Dict object with the last inserted row of modification's table
        """
        row = data
        # Checking if the Client is the last job id is up to date with the Server
        if row['last_job_id'] == self.last_job_id:
            # Checking if it's an already cached resource, if not it will ignore
            if self.graph_operations.get_resource(row['resource_url']):
                if row['method'] == 'POST':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                    self.get(row['resource_url'])
                elif row['method'] == 'DELETE':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                if row['method'] == 'PUT':
                    pass
            # Updating the last job id
            self.last_job_id = row['job_id']

        # If last_job_id is not the same, there's more than one outdated modification
        # Therefore the Client will try to get the diff of all modifications after his last job
        else:
            super().emit('get_modification_table_diff',
                         {'agent_job_id': self.last_job_id})

        # Updating the last job id
        self.last_job_id = row['job_id']

    def on_modification_table_diff(self, data) -> None:
        """Event handler for when the client has to updated multiple rows
        :param data: List with all modification rows to be updated
        """
        new_rows = data
        # Syncing procedure for every row received by mod table diff
        for row in new_rows:
            if self.graph_operations.get_resource(row['resource_url']):
                if row['method'] == 'POST':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                    self.get(row['resource_url'])
                elif row['method'] == 'DELETE':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                if row['method'] == 'PUT':
                    pass

        # Checking if the Agent is too outdated and can't be synced
        if not new_rows:
            logger.info('Server Restarting - Automatic Sync not possible')
            self.initialize_graph()
            # Agent should reply with a connect event with the last_job_id
            super().emit('reconnect')
            return None

        # Updating the last job id
        self.last_job_id = new_rows[0]['job_id']

    def on_broadcast_event(self, data):
        """Method executed when the Agent receives a broadcast event
        :param data: Object with the data broadcasted
        """
        pass
Exemple #14
0
class TestAgent(unittest.TestCase):
    """
    TestCase for Agent Class
    """
    @patch('hydra_agent.agent.socketio.Client.connect')
    @patch('hydra_agent.agent.Session.get')
    def setUp(self, get_session_mock, socket_client_mock):
        """Setting up Agent object
        :param get_session_mock: MagicMock object for patching session.get
                                 it's used to Mock Hydrus response to ApiDoc
        """
        # Mocking get for ApiDoc to Server, so hydrus doesn't need to be up
        get_session_mock.return_value.json.return_value = drone_doc
        socket_client_mock.return_value = None

        self.agent = Agent("http://*****:*****@patch('hydra_agent.agent.Session.get')
    def test_get_url(self, get_session_mock):
        """Tests get method from the Agent with URL
        :param get_session_mock: MagicMock object for patching session.get
        """
        state_object = {
            "@id": "/api/StateCollection/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2",
            "@context": "/api/contexts/StateCollection.jsonld"
        }

        get_session_mock.return_value.status_code = 200
        get_session_mock.return_value.json.return_value = state_object
        response = self.agent.get("http://*****:*****@patch('hydra_agent.agent.Session.get')
    def test_get_class_properties(self, get_session_mock):
        """Tests get method from the Agent by class name and properties
        :param get_session_mock: MagicMock object for patching session.get
        """
        state_object = {
            "@id": "/api/StateCollection/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "North",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2",
            "@context": "/api/contexts/StateCollection.jsonld"
        }

        get_session_mock.return_value.status_code = 200
        get_session_mock.return_value.json.return_value = state_object
        response_url = self.agent.get("http://*****:*****@patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.put')
    def test_get_collection(self, put_session_mock, embedded_get_mock):
        """Tests get method from the Agent when fetching collections
        :param put_session_mock: MagicMock object for patching session.put
        :param embedded_get_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        collection_url = "http://*****:*****@context": "/api/contexts/StateCollection.jsonld",
            "@id": "/api/StateCollection/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }

        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.return_value.json.return_value = state_object

        response, new_object_url = self.agent.put(collection_url, new_object)

        simplified_collection = \
            {
                "@context": "/api/contexts/DroneCollection.jsonld",
                "@id": "/api/DroneCollection/",
                "@type": "DroneCollection",
                "members": [
                    {
                        "@id": "/api/DroneCollection/1",
                        "@type": "Drone"
                    }
                ],
            }

        embedded_get_mock.return_value.json.return_value = \
            simplified_collection
        get_collection_url = self.agent.get(collection_url)
        get_collection_resource_type = self.agent.get(resource_type="Drone")
        self.assertEqual(type(get_collection_url), list)
        self.assertEqual(type(get_collection_resource_type), list)
        self.assertEqual(get_collection_resource_type, get_collection_url)

        get_collection_cached = self.agent.get(resource_type="Drone",
                                               cached_limit=1)

        self.assertEqual(get_collection_cached[0]["@id"],
                         get_collection_url[0]["@id"])

    @patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.put')
    def test_put(self, put_session_mock, embedded_get_mock):
        """Tests put method from the Agent
        :param put_session_mock: MagicMock object for patching session.put
        :param embedded_get_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        collection_url = "http://*****:*****@context": "/api/contexts/StateCollection.jsonld",
            "@id": "/api/StateCollection/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }

        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.return_value.json.return_value = state_object

        response, new_object_url = self.agent.put(collection_url, new_object)

        # Assert if object was inserted queried and inserted successfully
        get_new_object_url = self.agent.get(new_object_url)
        self.assertEqual(get_new_object_url, new_object)

        get_new_object_type = self.agent.get(resource_type="Drone",
                                             filters={'name': "Smart Drone"},
                                             cached_limit=1)
        self.assertEqual(get_new_object_url, get_new_object_type[0])

    @patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.post')
    @patch('hydra_agent.agent.Session.put')
    def test_post(self, put_session_mock, post_session_mock,
                  embedded_get_mock):
        """Tests post method from the Agent
        :param put_session_mock: MagicMock object for patching session.put
        :param post_session_mock: MagicMock object for patching session.post
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        collection_url = "http://*****:*****@context": "/api/contexts/StateCollection.jsonld",
            "@id": "/api/StateCollection/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }
        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.return_value.json.return_value = state_object

        response = self.agent.post(new_object_url, new_object)

        # Assert if object was updated successfully as intended
        get_new_object = self.agent.get(new_object_url)
        self.assertEqual(get_new_object, new_object)

    @patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.delete')
    @patch('hydra_agent.agent.Session.put')
    def test_delete(self, put_session_mock, delete_session_mock,
                    get_session_mock):
        """Tests post method from the Agent
        :param put_session_mock: MagicMock object for patching session.put
        :param delete_session_mock: MagicMock object to patch session.delete
        :param get_session_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "/api/StateCollection/1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        collection_url = "http://*****:*****@patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.put')
    def test_edges(self, put_session_mock, embedded_get_mock):
        """Tests to check if all edges are being created properly
        :param put_session_mock: MagicMock object for patching session.put
        :param embedded_get_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        collection_url = "http://*****:*****@context": "/api/contexts/StateCollection.jsonld",
            "@id": "/api/StateCollection/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }

        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.return_value.json.return_value = state_object

        response, new_object_url = self.agent.put(collection_url, new_object)

        # Checking if Drone Collection has an edge to the Drone Resource
        query = "MATCH (p)-[r]->() WHERE p.type = 'DroneCollection' \
            RETURN type(r)"

        query_result = self.redis_graph.query(query)
        self.assertEqual(query_result.result_set[0][0], 'has_Drone')

        # Checking if State Collection has an edge to the State Resource
        query = "MATCH (p)-[r]->() WHERE p.type = 'StateCollection' \
            RETURN type(r)"

        query_result = self.redis_graph.query(query)
        self.assertEqual(query_result.result_set[0][0], 'has_State')

        # Checking if Drone Resource has an edge to the State Resource
        query = "MATCH (p)-[r]->() WHERE p.type = 'Drone' RETURN type(r)"
        query_result = self.redis_graph.query(query)
        self.assertEqual(query_result.result_set[0][0], 'has_State')
Exemple #15
0
class TestAgent(unittest.TestCase):
    """
    TestCase for Agent Class
    """
    @patch('hydra_agent.agent.socketio.Client.connect')
    @patch('hydra_agent.agent.Session.get')
    def setUp(self, get_session_mock, socket_client_mock):
        """Setting up Agent object
        :param get_session_mock: MagicMock object for patching session.get
                                 it's used to Mock Hydrus response to ApiDoc
        """
        # Mocking get for ApiDoc to Server, so hydrus doesn't need to be up
        get_session_mock.return_value.json.return_value = drone_doc
        socket_client_mock.return_value = None
        try:
            self.agent = Agent("http://*****:*****@patch('hydra_agent.agent.Session.get')
    def test_get_url(self, get_session_mock):
        """Tests get method from the Agent with URL
        :param get_session_mock: MagicMock object for patching session.get
        """
        state_object = {
            "@id": "/api/State/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2",
            "@context": "/api/contexts/StateCollection.jsonld"
        }

        get_session_mock.return_value.status_code = 200
        get_session_mock.return_value.json.return_value = state_object
        response = self.agent.get("http://*****:*****@patch('hydra_agent.agent.Session.get')
    def test_get_class_properties(self, get_session_mock):
        """Tests get method from the Agent by class name and properties
        :param get_session_mock: MagicMock object for patching session.get
        """
        state_object = {
            "@id": "/api/State/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "North",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2",
            "@context": "/api/contexts/State.jsonld"
        }

        get_session_mock.return_value.status_code = 200
        get_session_mock.return_value.json.return_value = state_object
        response_url = self.agent.get("http://*****:*****@patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.put')
    def test_get_collection(self, put_session_mock, embedded_get_mock):
        """Tests get method from the Agent when fetching collections
        :param put_session_mock: MagicMock object for patching session.put
        :param embedded_get_mock: MagicMock object for patching session.get
        """
        new_object = {"@type": "DroneCollection", "members": ["1"]}

        collection_url = "http://*****:*****@context": "/api/contexts/DroneCollection.jsonld",
                "@id": "/api/DroneCollection/1",
                "@type": "DroneCollection",
                "members": [
                    {
                        "@id": "/api/Drone/1",
                        "@type": "Drone"
                    }
                ],
                "search": {
                    "@type": "hydra:IriTemplate",
                    "hydra:mapping": [{
                        "@type": "hydra:IriTemplateMapping",
                        "hydra:property": "http://auto.schema.org/speed",
                        "hydra:required": False,
                        "hydra:variable": "DroneState[Speed]"}
                    ],
                    "hydra:template": "/serverapi/Drone(DroneState[Speed])",
                    "hydra:variableRepresentation": "hydra:BasicRepresentation",
                },
                "totalItems": 1,
                "view": {
                    "@id": "/serverapi/DroneCollection?page=1",
                    "@type": "PartialCollectionView",
                    "first": "/serverapi/DroneCollection?page=1",
                    "last": "/serverapi/DroneCollection?page=1",
                    "next": "/serverapi/DroneCollection?page=1"
                }
            }

        embedded_get_mock.return_value.json.return_value = simplified_collection
        embedded_get_mock.return_value.status_code = 200
        response, new_object_url = self.agent.put(collection_url, new_object)
        get_collection_url = self.agent.get(collection_url)
        self.assertEqual(type(get_collection_url), dict)
        # get_collection_cached = self.agent.get(resource_type="Drone",
        #                                        cached_limit=1)
        # self.assertEqual(get_collection_cached[0]["@id"],
        #                  get_collection_url['members'][0]["@id"])

    @patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.put')
    def test_put(self, put_session_mock, embedded_get_mock):
        """Tests put method from the Agent
        :param put_session_mock: MagicMock object for patching session.put
        :param embedded_get_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        class_url = "http://*****:*****@context": "/api/contexts/State.jsonld",
            "@id": "/api/State/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }
        drone_res = {
            "@context": "/api/contexts/Drone.jsonld",
            "@id": "/api/Drone/1",
            "@type": "Drone",
            "DroneState": {
                "@id": "/api/State/1",
                "@type": "State",
                "Battery": "C1WE92",
                "Direction": "Q9VV88",
                "DroneID": "6EBGT5",
                "Position": "A",
                "SensorStatus": "335Y8B",
                "Speed": "IZPSTE"
            },
            "MaxSpeed": "A3GZ37",
            "Sensor": "E7JD5Q",
            "model": "HB14CX",
            "name": "Priaysnhu"
        }
        fake_responses = [Mock(), Mock(), Mock(), Mock()]
        fake_responses[0].json.return_value = drone_res
        fake_responses[0].status_code = 200
        fake_responses[1].json.return_value = state_object
        fake_responses[1].status_code = 200
        fake_responses[2].json.return_value = drone_res
        fake_responses[2].status_code = 200
        fake_responses[3].json.return_value = drone_res
        fake_responses[3].status_code = 200
        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.side_effect = fake_responses
        response, new_object_url = self.agent.put(new_object_url, new_object)

        # Assert if object was inserted queried and inserted successfully
        get_new_object_url = self.agent.get(new_object_url)
        self.assertEqual(get_new_object_url, drone_res)

        get_new_object_type = self.agent.get(new_object_url,
                                             filters={'name': "Smart Drone"})
        self.assertEqual(get_new_object_url, get_new_object_type)

    @patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.post')
    @patch('hydra_agent.agent.Session.put')
    def test_post(self, put_session_mock, post_session_mock,
                  embedded_get_mock):
        """Tests post method from the Agent
        :param put_session_mock: MagicMock object for patching session.put
        :param post_session_mock: MagicMock object for patching session.post
        """
        new_object = {
            "@type": "Drone",
            "DroneState": {
                "@type": "State",
                "Battery": "C1WE92",
                "Direction": "Q9VV88",
                "DroneID": "6EBGT5",
                "Position": "A",
                "SensorStatus": "335Y8B",
                "Speed": "IZPSTE"
            },
            "MaxSpeed": "A3GZ37",
            "Sensor": "E7JD5Q",
            "model": "HB14CX",
            "name": "Priyanshu"
        }

        class_url = "http://*****:*****@context": "/api/contexts/State.jsonld",
            "@id": "/api/State/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }
        drone_res = {
            "@context": "/api/contexts/Drone.jsonld",
            "@id": "/api/Drone/2",
            "@type": "Drone",
            "DroneState": {
                "@id": "/api/State/1",
                "@type": "State",
                "Battery": "C1WE92",
                "Direction": "Q9VV88",
                "DroneID": "6EBGT5",
                "Position": "A",
                "SensorStatus": "335Y8B",
                "Speed": "IZPSTE"
            },
            "MaxSpeed": "A3GZ37",
            "Sensor": "E7JD5Q",
            "model": "HB14CX",
            "name": "Priaysnhu"
        }
        fake_responses = [Mock(), Mock(), Mock()]
        fake_responses[0].json.return_value = drone_res
        fake_responses[0].status_code = 200
        fake_responses[1].json.return_value = state_object
        fake_responses[1].status_code = 200
        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.side_effect = fake_responses

        response, new_object_url = self.agent.put(new_object_url, new_object)

        post_session_mock.return_value.status_code = 200
        post_session_mock.return_value.json.return_value = {"msg": "success"}
        new_object['DroneState']['@id'] = "/api/State/1"
        new_object['name'] = "Updated Name"
        # Mocking an object to be used for a property that has an embedded link
        response = self.agent.post(new_object_url, new_object)
        # Assert if object was updated successfully as intended
        fake_responses[2].json.return_value = new_object
        fake_responses[2].status_code = 200
        get_new_object = self.agent.get(new_object_url)

        self.assertEqual(get_new_object, new_object)

    @patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.delete')
    @patch('hydra_agent.agent.Session.put')
    def test_delete(self, put_session_mock, delete_session_mock,
                    get_session_mock):
        """Tests post method from the Agent
        :param put_session_mock: MagicMock object for patching session.put
        :param delete_session_mock: MagicMock object to patch session.delete
        :param get_session_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": "/api/StateCollection/1",
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        class_url = "http://*****:*****@context": "/api/contexts/State.jsonld",
            "@id": "/api/State/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }
        drone_res = {
            "@context": "/api/contexts/Drone.jsonld",
            "@id": "/api/Drone/1",
            "@type": "Drone",
            "DroneState": {
                "@id": "/api/State/1",
                "@type": "State",
                "Battery": "C1WE92",
                "Direction": "Q9VV88",
                "DroneID": "6EBGT5",
                "Position": "A",
                "SensorStatus": "335Y8B",
                "Speed": "IZPSTE"
            },
            "MaxSpeed": "A3GZ37",
            "Sensor": "E7JD5Q",
            "model": "HB14CX",
            "name": "Priaysnhu"
        }
        fake_responses = [Mock(), Mock(), Mock()]
        fake_responses[0].json.return_value = drone_res
        fake_responses[0].status_code = 200
        fake_responses[1].json.return_value = state_object
        fake_responses[1].status_code = 200
        fake_responses[2].text = {"msg": "resource doesn't exist"}
        # Mocking an object to be used for a property that has an embedded link
        get_session_mock.return_value.status_code = 200
        get_session_mock.side_effect = fake_responses

        response, new_object_url = self.agent.put(new_object_url, new_object)

        delete_session_mock.return_value.status_code = 200
        delete_session_mock.return_value.json.return_value = {"msg": "success"}
        response = self.agent.delete(new_object_url)
        get_new_object = self.agent.get(new_object_url)

        # Assert if nothing different was returned by Redis
        self.assertEqual(get_new_object, {"msg": "resource doesn't exist"})

    def test_basic_iri_templates(self):
        """Tests the URI constructed on the basis of Basic Representation
        """
        simplified_response = {
            "@context": "/serverapi/contexts/DroneCollection.jsonld",
            "@id": "/serverapi/DroneCollection/",
            "@type": "DroneCollection",
            "members": [
                {
                    "@id": "/serverapi/DroneCollection/1",
                    "@type": "Drone"
                },
            ],
            "search": {
                "@type":
                "hydra:IriTemplate",
                "hydra:mapping": [{
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "http://schema.org/name",
                    "hydra:required": False,
                    "hydra:variable": "name"
                }, {
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "pageIndex",
                    "hydra:required": False,
                    "hydra:variable": "pageIndex"
                }, {
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "limit",
                    "hydra:required": False,
                    "hydra:variable": "limit"
                }, {
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "offset",
                    "hydra:required": False,
                    "hydra:variable": "offset"
                }],
                "hydra:template":
                "/serverapi/(pageIndex, limit, offset)",
                "hydra:variableRepresentation":
                "hydra:BasicRepresentation"
            },
            "view": {
                "@id": "/serverapi/DroneCollection?page=1",
                "@type": "PartialCollectionView",
                "first": "/serverapi/DroneCollection?page=1",
                "last": "/serverapi/DroneCollection?page=1",
                "next": "/serverapi/DroneCollection?page=1"
            }
        }
        sample_mapping_object = {
            "name": "Drone1",
            "pageIndex": "1",
            "limit": "10",
            "offset": "1"
        }
        url = urlparse(
            expand_template("http://*****:*****@context": "/serverapi/contexts/DroneCollection.jsonld",
            "@id": "/serverapi/DroneCollection/",
            "@type": "DroneCollection",
            "members": [
                {
                    "@id": "/serverapi/DroneCollection/1",
                    "@type": "Drone"
                },
            ],
            "search": {
                "@type":
                "hydra:IriTemplate",
                "hydra:mapping": [{
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "http://schema.org/name",
                    "hydra:required": False,
                    "hydra:variable": "name"
                }, {
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "pageIndex",
                    "hydra:required": False,
                    "hydra:variable": "pageIndex"
                }, {
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "limit",
                    "hydra:required": False,
                    "hydra:variable": "limit"
                }, {
                    "@type": "hydra:IriTemplateMapping",
                    "hydra:property": "offset",
                    "hydra:required": False,
                    "hydra:variable": "offset"
                }],
                "hydra:template":
                "/serverapi/(pageIndex, limit, offset)",
                "hydra:variableRepresentation":
                "hydra:ExplicitRepresentation"
            },
            "view": {
                "@id": "/serverapi/DroneCollection?page=1",
                "@type": "PartialCollectionView",
                "first": "/serverapi/DroneCollection?page=1",
                "last": "/serverapi/DroneCollection?page=1",
                "next": "/serverapi/DroneCollection?page=1"
            }
        }
        sample_mapping_object = {
            "url_demo": {
                "@id": "http://www.hydra-cg.com/"
            },
            "prop_with_language": {
                "@language": "en",
                "@value": "A simple string"
            },
            "prop_with_type": {
                "@value": "5.5",
                "@type": "http://www.w3.org/2001/XMLSchema#decimal"
            },
            "str_prop": "A simple string"
        }
        url = urlparse(
            expand_template("http://*****:*****@patch('hydra_agent.agent.Session.get')
    @patch('hydra_agent.agent.Session.put')
    def test_edges(self, put_session_mock, embedded_get_mock):
        """Tests to check if all edges are being created properly
        :param put_session_mock: MagicMock object for patching session.put
        :param embedded_get_mock: MagicMock object for patching session.get
        """
        new_object = {
            "@type": "Drone",
            "DroneState": {
                "@type": "State",
                "Battery": "C1WE92",
                "Direction": "Q9VV88",
                "DroneID": "6EBGT5",
                "Position": "A",
                "SensorStatus": "335Y8B",
                "Speed": "IZPSTE"
            },
            "name": "Smart Drone",
            "model": "Hydra Drone",
            "MaxSpeed": "999",
            "Sensor": "Wind"
        }

        class_url = "http://*****:*****@context": "/api/contexts/State.jsonld",
            "@id": "/api/State/1",
            "@type": "State",
            "Battery": "sensor Ex",
            "Direction": "speed Ex",
            "DroneID": "sensor Ex",
            "Position": "model Ex",
            "SensorStatus": "sensor Ex",
            "Speed": "2"
        }
        drone_res = {
            "@context": "/api/contexts/Drone.jsonld",
            "@id": "/api/Drone/1",
            "@type": "Drone",
            "DroneState": {
                "@id": "/api/State/1",
                "@type": "State",
                "Battery": "C1WE92",
                "Direction": "Q9VV88",
                "DroneID": "6EBGT5",
                "Position": "A",
                "SensorStatus": "335Y8B",
                "Speed": "IZPSTE"
            },
            "MaxSpeed": "A3GZ37",
            "Sensor": "E7JD5Q",
            "model": "HB14CX",
            "name": "Priaysnhu"
        }
        fake_responses = [Mock(), Mock()]
        fake_responses[0].json.return_value = drone_res
        fake_responses[0].status_code = 200
        fake_responses[1].json.return_value = state_object
        fake_responses[1].status_code = 200
        # Mocking an object to be used for a property that has an embedded link
        embedded_get_mock.return_value.status_code = 200
        embedded_get_mock.side_effect = fake_responses
        response, new_object_url = self.agent.put(class_url, new_object)
        # Checking if Drone Class has an edge to the Drone Resource
        query = "MATCH (p)-[r]->() WHERE p.type = 'Drone' \
            RETURN type(r)"

        query_result = self.redis_graph.query(query)
        self.assertEqual(query_result.result_set[0][0], 'has_Drone')

        # Checking if State  has an edge to the State Resource
        query = "MATCH (p)-[r]->() WHERE p.type = 'State' \
            RETURN type(r)"

        query_result = self.redis_graph.query(query)
        self.assertEqual(query_result.result_set[0][0], 'has_State')

        # Checking if Drone Resource has an edge to the State Resource
        query = "MATCH (p)-[r]->() WHERE p.type = 'Drone' RETURN type(r)"
        query_result = self.redis_graph.query(query)
        self.assertEqual(query_result.result_set[1][0], 'has_State')
Exemple #16
0
class Agent(Session, socketio.ClientNamespace, socketio.Client):
    """Provides a straightforward GET, PUT, POST, DELETE -
    CRUD interface - to query hydrus
    """
    def __init__(self, entrypoint_url: str, namespace: str = '/sync') -> None:
        """Initialize the Agent
        :param entrypoint_url: Entrypoint URL for the hydrus server
        :param namespace: Namespace endpoint to listen for updates
        :return: None
        """
        self.entrypoint_url = entrypoint_url.strip().rstrip('/')
        self.redis_proxy = RedisProxy()
        self.redis_connection = self.redis_proxy.get_connection()
        Session.__init__(self)
        self.fetch_apidoc()
        self.initialize_graph()
        self.graph_operations = GraphOperations(self.entrypoint_url,
                                                self.api_doc, self.redis_proxy)
        # Declaring Socket Rules and instaciating Synchronization Socket
        socketio.ClientNamespace.__init__(self, namespace)
        socketio.Client.__init__(self, logger=True)
        socketio.Client.register_namespace(self, self)
        socketio.Client.connect(self,
                                self.entrypoint_url,
                                namespaces=namespace)
        self.last_job_id = ""

    def fetch_apidoc(self) -> dict:
        if hasattr(self, 'api_doc'):
            return self.api_doc
        else:
            jsonld_api_doc = super().get(self.entrypoint_url + '/vocab').json()
            self.api_doc = doc_maker.create_doc(jsonld_api_doc)
            return self.api_doc

    def initialize_graph(self) -> None:
        """Initialize the Graph on Redis based on ApiDoc
        :param entrypoint_url: Entrypoint URL for the hydrus server
        :return: None
        """
        self.graph = InitialGraph()
        self.redis_connection.delete("apigraph")
        self.graph.main(self.entrypoint_url, self.api_doc, True)
        self.redis_connection.sadd("fs:url", self.entrypoint_url)

    def get(self,
            url: str = None,
            resource_type: str = None,
            filters: dict = {},
            cached_limit: int = sys.maxsize) -> Union[dict, list]:
        """READ Resource from Server/cached Redis
        :param url: Resource URL to be fetched
        :param resource_type: Resource object type
        :param filters: filters to apply when searching, resources properties
        :param cached_limit : Minimum amount of resources to be fetched
        :return: Dict when one object or a list when multiple targerted objects
        """
        redis_response = self.graph_operations.get_resource(
            url, resource_type, filters)
        if redis_response:
            if type(redis_response) is dict:
                return redis_response
            elif len(redis_response) >= cached_limit:
                return redis_response

        # If querying with resource type build url
        # This can be more stable when adding Manages Block
        # More on: https://www.hydra-cg.com/spec/latest/core/#manages-block
        if resource_type:
            url = self.entrypoint_url + "/" + resource_type + "Collection"
            response = super().get(url, params=filters)
        else:
            response = super().get(url)

        if response.status_code == 200:
            # Graph_operations returns the embedded resources if finding any
            embedded_resources = \
                self.graph_operations.get_processing(url, response.json())
            self.process_embedded(embedded_resources)
            if response.json()['@type'] in self.api_doc.parsed_classes:
                return response.json()
            else:
                return response.json()['members']
        else:
            return response.text

    def put(self, url: str, new_object: dict) -> Tuple[dict, str]:
        """CREATE resource in the Server/cache it on Redis
        :param url: Server URL to create the resource at
        :param new_object: Dict containing the object to be created
        :return: Dict with server's response and resource URL
        """
        response = super().put(url, json=new_object)

        if response.status_code == 201:
            url = response.headers['Location']
            # Graph_operations returns the embedded resources if finding any
            embedded_resources = \
                self.graph_operations.put_processing(url, new_object)
            self.process_embedded(embedded_resources)
            return response.json(), url
        else:
            return response.text, ""

    def post(self, url: str, updated_object: dict) -> dict:
        """UPDATE resource in the Server/cache it on Redis
        :param url: Server URL to update the resource at
        :param updated_object: Dict containing the updated object
        :return: Dict with server's response
        """
        response = super().post(url, json=updated_object)

        if response.status_code == 200:
            # Graph_operations returns the embedded resources if finding any
            embedded_resources = \
                self.graph_operations.post_processing(url, updated_object)
            self.process_embedded(embedded_resources)
            return response.json()
        else:
            return response.text

    def delete(self, url: str) -> dict:
        """DELETE resource in the Server/delete it on Redis
        :param url: Resource URL to be deleted
        :return: Dict with server's response
        """
        response = super().delete(url)

        if response.status_code == 200:
            self.graph_operations.delete_processing(url)
            return response.json()
        else:
            return response.text

    def process_embedded(self, embedded_resources: list) -> None:
        """Helper function to process a list of embedded resources
        fetching and linking them to their parent Nodes
        :param embedded_resources: List of dicts containing resources
        """
        # Embedded resources are fetched and then properly linked
        for embedded_resource in embedded_resources:
            self.get(embedded_resource['embedded_url'])
            self.graph_operations.link_resources(
                embedded_resource['parent_id'],
                embedded_resource['parent_type'],
                embedded_resource['embedded_url'])

    # Below are the functions that are responsible to process Socket Events
    def on_connect(self, data: dict = None) -> None:
        """Method executed when the Agent is successfully connected to the Server
        """
        if data:
            self.last_job_id = data['last_job_id']
            logger.info('Socket Connection Established - Synchronization ON')

    def on_disconnect(self):
        """Method executed when the Agent is disconnected
        """
        pass

    def on_update(self, data) -> None:
        """Method executed when the Agent receives an event named 'update'
        This is sent to all clients connected the server under the designed Namespace
        :param data: Dict object with the last inserted row of modification's table
        """
        row = data
        # Checking if the Client is the last job id is up to date with the Server
        if row['last_job_id'] == self.last_job_id:
            # Checking if it's an already cached resource, if not it will ignore
            if self.graph_operations.get_resource(row['resource_url']):
                if row['method'] == 'POST':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                    self.get(row['resource_url'])
                elif row['method'] == 'DELETE':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                if row['method'] == 'PUT':
                    pass
            # Updating the last job id
            self.last_job_id = row['job_id']

        # If last_job_id is not the same, there's more than one outdated modification
        # Therefore the Client will try to get the diff of all modifications after his last job
        else:
            super().emit('get_modification_table_diff',
                         {'agent_job_id': self.last_job_id})

        # Updating the last job id
        self.last_job_id = row['job_id']

    def on_modification_table_diff(self, data) -> None:
        """Event handler for when the client has to updated multiple rows
        :param data: List with all modification rows to be updated
        """
        new_rows = data
        # Syncing procedure for every row received by mod table diff
        for row in new_rows:
            if self.graph_operations.get_resource(row['resource_url']):
                if row['method'] == 'POST':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                    self.get(row['resource_url'])
                elif row['method'] == 'DELETE':
                    self.graph_operations.delete_processing(
                        row['resource_url'])
                if row['method'] == 'PUT':
                    pass

        # Checking if the Agent is too outdated and can't be synced
        if not new_rows:
            logger.info('Server Restarting - Automatic Sync not possible')
            self.initialize_graph()
            # Agent should reply with a connect event with the last_job_id
            super().emit('reconnect')
            return None

        # Updating the last job id
        self.last_job_id = new_rows[0]['job_id']

    def on_broadcast_event(self, data):
        """Method executed when the Agent receives a broadcast event
        :param data: Object with the data broadcasted
        """
        pass