diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f37889c..b883202 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,7 +6,7 @@ * Read this [how-to about writing a PR](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) and this [other how-to about writing a issue](https://wiredcraft.com/blog/how-we-write-our-github-issues/) -* **first ask in chat**: if you find a problem, first ask for [help in the chat](https://gitter.im/HTTP-APIs/Lobby), then consider opening a issue. +* **first ask in chat**: if you find a problem, first ask for [help in the chat](https://hydraecosystem.slack.com/) and then go through the previous issues. If the problem still persists, only then consider opening an issue. * **read history**: before opening a PR be sure that all the tests pass successfully. If any is failing for non-related reasons, annotate the test failure in the PR comment. diff --git a/.gitignore b/.gitignore index 2dbd562..2f9fb61 100644 --- a/.gitignore +++ b/.gitignore @@ -112,7 +112,7 @@ Pipfile.lock .mypy_cache/ # build packages -src +/src # Graphviz generated graph hydra_graph.gv \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 304a573..3a5f47a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ services: - docker before_script: - - docker run -d -p 6379:6379 -it --rm --name redisgraph redislabs/redisgraph:edge + - docker run -d -p 6379:6379 -it --rm --name redisgraph redislabs/redisgraph:2.0-edge python: - "3.5" diff --git a/LICENSE b/LICENSE index cbb5498..7f60175 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2018 HYDRA W3C Group, Github.com/HTTP-APIs contributors +Copyright (c) 2019 Github.com/HTTP-APIs contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ff09d57..14c0803 100644 --- a/README.md +++ b/README.md @@ -4,254 +4,131 @@ For a general introduction to Hydra Ecosystem, see [hydraecosystem.org](http://h `hydra-python-agent` is a smart Hydra client implemented in Python which works with [hydrus](https://github.com/HTTP-APIs/hydrus). Reference implementation is [Heracles.ts](https://github.com/HydraCG/Heracles.ts). Smart clients are generic automated clients that establish resilient connected data networks leveraging knowledge graphs. +## Quick Start +Our Hydra Agent has different interfaces that you can try: + +- [A Web-based GUI](https://github.com/HTTP-APIs/hydra-python-agent-gui/tree/agent-gui-1.0/console-frontend) - which shows how Hydra APIs are connected and how the Smart Agent is generic and can automatically build requests. +- [A Python package](https://github.com/HTTP-APIs/hydra-python-agent/#user-content-agent-package) - so you can use to communicate with a Hydra API in your code/software. +- [Natural-language-like command line tool](#natural-language-like-command-line-tool) - this is still a GET only implementation + ## General characteristics -The client is designed to: -* Cache metadata from the Hydra server it connects to, to allow querying on the client-side; -* Use Redis as a graph-store leveraging `redisgraph` (see [here](https://oss.redislabs.com/redisgraph/)); -* simply, metadata and data are loaded from the server and stored in Redis; +The Agent is *designed* to: +* Provide a seamless Client that can be used to communicate with Hydra APIs +* Cache metadata from the Hydra server it connects to, to allow querying on the client-side +* Maintain a syncrhonization mechanism which makes sure cached resources are consistent * The graph can be queried using OpenCypher. -The starting objective is to create a querying layer that is able to reach data in one or more Hydra srever/s. Leveraging Redis, clients can construct their own representation of the data stored in one or more Hydra servers; querying the data as they need it, and respond complex semantic queries. This will allow any client connected to any server to have access to an "aggregated view" of the connected network (the network of all the servers it connects to). - -## Missing bits at the moment -* For now it is a proof-of-concept, only `GET` functionality -* Soon to develop, a reliable synchronization mechanism to allow strong consistency between server-side data and client-side representation ([#98](https://github.com/HTTP-APIs/hydra-python-agent/issues/98)). -* Allow users to interact with the server using Natural Language which is a processed machine consumable language. **(under development)** +The final goal is to create a Client that can connected to multiple hydrus servers and operate between them while caching information in a graph-based database(Redis). This should enable the client to have an "aggregated view" of the connected network (the network of all the servers it connects to) and make complex sematic queries to it. ## Installation -**NOTE:** You'll need to use python3. +**NOTE:** You'll need to use python3. Using venv(virtual environment) is recommended. -To install only requirements: +Clone and setup a virtual environment: - pip3 install -r requirements.txt - -or, + git clone https://github.com/HTTP-APIs/hydra-python-agent.git + cd hydra-python-agent + python3 -m venv venv + source venv/bin/activate -To install or setup the client environment, you have to run: - - python3 setup.py install +Install dependencies and setup Agent: + pip3 install --upgrade pip + pip3 install -r requirements.txt + python3 setup.py install -To install Redis and other Redis modules: +Setup Redis which is used as caching layer(if permission denied use `sudo`): ./redis_setup.sh -## Quickstart - -### Demo - -To run the demo for hydra-python-agent, you have to follow the instructions: - -* Clone the repo: - - git clone https://github.com/HTTP-APIs/hydra-python-agent.git - -* Change directory and switch to the develop branch: - - cd hydra-python-agent - git checkout -b develop origin/develop - -* Now to install the requirements or setup the environment: - - you should follow the instructions of [installation](#installation). - -After setup the environment. You can query or run the client. - -* To run both the things Redis server and the client. You can run the command: - - docker-compose run client - - - and provide a valid URL(of a hydrus updated server) and then you can query in querying format. - - `>>>url` #here url should be a valid link, for testing you can use http://35.224.198.158:8080/api - `>>>help` # it will provide the querying format - -**Obs.: If failing to connect to localhost** running the Agent via Docker, head to [issue #104](https://github.com/HTTP-APIs/hydra-python-agent/issues/104#issuecomment-497381440). - -#### Code simplification - -To create the graph in Redis memory, use(graph_init.py) : -``` - import redis - from redisgraph import Graph, Node, Edge - redis_con = redis.Redis(host='localhost', port=6379) - self.redis_graph = Graph("apigraph", redis_con) -``` - -For querying, URL should be provided first: - -``` - url = input("url>>>") - - return query(apigraph, url) # apigraph is vocab file provided by url. -``` - -The client takes the query as input, like: - -``` - while True: - print("press exit to quit") - query = input(">>>") - if query == "exit": - break - elif query == "help": - help() # provide querying format - else: - print(facades.user_query(query))# query can be done with facades class -``` - -you can query as following querying formats: - -``` - print("querying format") - print("Get all endpoints:- show endpoints") - print("Get all class_endpoints:- show classEndpoints") - print("Get all collection_endpoints:- show collectionEndpoints") - print("Get all members of collection_endpoint:-", - "show members") - print("Get all properties of objects:-", - "show objects properties") - print("Get all properties of any member:-", - "show object properties ") - print("Get all classes properties:-show class properties") - print("Get data with compare properties:-", - "show and/or ") - print("Get data by using both opeartions(and,or)", - " you should use brackets like:-", - "show model xyz and (name Drone1 or name Drone2)", - "or, show and ( or )") - -``` - -Query test can be done like this: - -``` - check_data = [['p.id', 'p.operations', 'p.properties', 'p.type'], - ['vocab:EntryPoint/Location', - "['POST'", "'PUT'", "'GET']", - "['Location']", 'Location']] - query = "show classEndpoints" - self.assertEqual(data,check_data) #data is data retrieve from the Redis. -``` - -For more detail take a look at [wiki file](https://github.com/HTTP-APIs/http-apis.github.io/blob/master/hydra-agent-redis-graph.md) +**Setup hydrus** +Since this is an API Client, we need an appropriate Hydra Server to query to. To setup a localhost follow the instructions at https://github.com/HTTP-APIs/hydrus#demo. You might want to run `hydrus serve --no-auth` to skip setting up headers. #### Agent package -To use the Agent as a package you can simply do something like: +After installing the Agent and running Redis, [as per instructions above](https://github.com/HTTP-APIs/hydra-python-agent/#user-content-installation), you can do something like: ``` from hydra_agent.agent import Agent -agent = Agent("http://localhost:8080/serverapi") -agent.get("http://localhost:8080/serverapi/DroneCollection/123-123-123-123") +agent = Agent("http://localhost:8080/serverapi/") # <- hydrus Server URL +agent.get("http://localhost:8080/serverapi/DroneCollection/") ``` The agent supports GET, PUT, POST or DELETE: -- GET - used to READ resources or collections -- PUT - used to CREATE new resources in the Server -- POST - used to UPDATE resources in the Server -- DELETE - used to DELETE resources in the Server +- **GET** - used to READ resources or collections +- **PUT** - used to CREATE new resources in the Server +- **POST** - used to UPDATE resources in the Server +- **DELETE** - used to DELETE resources in the Server -To GET a existing resource you should: +**To GET** a existing resource you should: ``` agent.get("http://localhost:8080/serverapi//") agent.get("http://localhost:8080/serverapi//") ``` -To PUT a new resource you should: +**To PUT** a new resource you should: ``` new_resource = {"@type": "Drone", "name": "Drone 1", "model": "Model S", ...} agent.put("http://localhost:8080/serverapi//", new_resource) ``` -To UPDATE a resource you should: +**To UPDATE** a resource you should: ``` existing_resource["name"] = "Updated Name" agent.post("http://localhost:8080/serverapi//", existing_resource) ``` -To DELETE a resource you should: +**To DELETE** a resource you should: ``` agent.delete("http://localhost:8080/serverapi//") ``` More than that, Agent extends Session from https://2.python-requests.org/en/master/api/#request-sessions, so all methods like auth, cookies, headers and so on can also be used. -#### Querying Redis -Reference can be found here: https://oss.redislabs.com/redisgraph/commands/ - -Entity structure: alias:label {filters}. - -Example of MATCH: -(a:actor)-[:act]->(m:movie {title:"straight outta compton"}) - -GRAPH.QUERY apigraph "MATCH (p) RETURN p" - -Internal Hydra Python Agent naming: - -Labels: - -- collection, classes - macro labels -- objects - for members -- object - for resources +### Natural-language-like Command Line Tool +If you've followed the [installation](#installation) instructions you can run: -Aliases: + python hydra_agent/querying_mechanism.py -Alias for collection example: -Collection -DroneCollection +Another alternative to run the CLT is using docker componse. To run **both Redis server and the client**(stop any Redis instance before), you can run the command: + + docker-compose run client -Alias for collection member: - -Dronea9d6f083-79dc-48e2-9e4b-fd5e9fc849ab +To query you should provide a hydrus URL first: -To get all nodes from the Graph: ``` -GRAPH.QUERY apigraph "MATCH (p) RETURN p" + url>>> http://localhost:8080/serverapi/ + ``` -Get all nodes and filter by label: -``` -GRAPH.QUERY apigraph "MATCH (p:collection) RETURN p" -``` +**Obs.: If failing to connect to localhost** running the Agent via Docker, head to [issue #104](https://github.com/HTTP-APIs/hydra-python-agent/issues/104#issuecomment-497381440). -Get all nodes and filter by label: -```' -GRAPH.QUERY apigraph "MATCH (p) WHERE(p.id = '/serverapi/DroneCollection/72b53615-a480-4920-b126-4d1e1e107dc6') RETURN p" -``` +- **Natural Language querying format** -To read all the edges of the graph -``` -GRAPH.QUERY apigraph "MATCH ()-[r]->() RETURN type(r)" -``` +Run help inside the CLT to get the querying format. -To read all the edges connected to a node -``` -GRAPH.QUERY apigraph "MATCH (p)-[r]->() WHERE p.type = 'DroneCollection' RETURN type(r)" -``` + >>>help # it will provide the querying format -Creating Edges between existing Nodes(Ref: https://github.com/RedisGraph/redisgraph-py/issues/16): -*This is not available yet on the oficial doc* -``` -MATCH (f:%s{%s:'%s'}), (t:%s{%s:'%s'}) CREATE (f)-[:in]->(t) -GRAPH.QUERY apigraph "MATCH (s:collection {type:'DroneCollection'} ), (d:objectsDrone {id:'/serverapi/DroneCollection/ea7e438e-a93d-436d-a7e9-994c13d49dc0'} ) CREATE (s)-[:has_Drone]->(d)" -``` +You can query the server with the following format: -To create a node: -``` -GRAPH.QUERY apigraph "CREATE (Droneea7e438e-a93d-436d-a7e9-994c13d49dc0:objectsDrone {@id: '/serverapi/DroneCollection/a9d6f083-79dc-48e2-9e4b-fd5e9fc849ab', @type: 'Drone', model: 'Ultra Model S')" -``` +> Get all endpoints:- **show endpoints** +Get all class_endpoints:- **show classEndpoints** +Get all collection_endpoints:- **show collectionEndpoints** +Get all members of collection_endpoint:- **show < collection_endpoint > members** +Get all properties of objects:- **show objects< endpoint_type > properties** +Get all properties of any member:- **show object< id_of_member > properties ** +Get all classes properties:- **show class< class_endpoint > properties** +Get data with compare properties:- **show < key > < value > and/or < key1 > < value1 >** +Get data by using both opeartions(and,or) you should use brackets like:- **show model xyz and (name Drone1 or name Drone2) or, show < key > < value > and (< key > < value > or < key > < value >)** -To delete a node: -``` -GRAPH.QUERY apigraph "MATCH (p) WHERE (p.id = '/serverapi/DroneCollection/2') DELETE p" +For more detail take a look at [wiki file](https://github.com/HTTP-APIs/http-apis.github.io/blob/master/hydra-agent-redis-graph.md) -``` +### Missing bits at the moment +* For the Web GUI there's a list of [future enhacements here](https://github.com/HTTP-APIs/hydra-python-agent-gui/issues/3). +* For now the Natural language CLT it is a proof-of-concept, only `GET` functionality References ---------- diff --git a/docker-compose.yaml b/docker-compose.yaml index 83cfa71..0b668be 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,6 +14,6 @@ services: environment: - REDIS_HOST=redis_db db: - image: redislabs/redisgraph:edge + image: redislabs/redisgraph:2.0-edge ports: - "6379:6379" diff --git a/hydra_agent/agent.py b/hydra_agent/agent.py index d34a01e..302ba61 100644 --- a/hydra_agent/agent.py +++ b/hydra_agent/agent.py @@ -1,4 +1,6 @@ -import logging, sys +import logging +import sys +import socketio from hydra_agent.redis_core.redis_proxy import RedisProxy from hydra_agent.redis_core.graphutils_operations import GraphOperations from hydra_agent.redis_core.graph_init import InitialGraph @@ -10,25 +12,41 @@ logger = logging.getLogger(__file__) -class Agent(Session): +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) -> None: + + 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() - super().__init__() - jsonld_api_doc = super().get(self.entrypoint_url + '/vocab').json() - self.api_doc = doc_maker.create_doc(jsonld_api_doc) + 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 @@ -140,5 +158,80 @@ def process_embedded(self, embedded_resources: list) -> None: 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 + if __name__ == "__main__": pass diff --git a/hydra_agent/redis_core/README.md b/hydra_agent/redis_core/README.md new file mode 100644 index 0000000..8c198e3 --- /dev/null +++ b/hydra_agent/redis_core/README.md @@ -0,0 +1,151 @@ +#### CLT Code Basic explanation + +To create the graph in Redis memory, use(graph_init.py) : +``` + import redis + from redisgraph import Graph, Node, Edge + redis_con = redis.Redis(host='localhost', port=6379) + self.redis_graph = Graph("apigraph", redis_con) +``` + +For querying, URL should be provided first: + +``` + url = input("url>>>") + + return query(apigraph, url) # apigraph is vocab file provided by url. +``` + +The client takes the query as input, like: + +``` + while True: + print("press exit to quit") + query = input(">>>") + if query == "exit": + break + elif query == "help": + help() # provide querying format + else: + print(facades.user_query(query))# query can be done with facades class +``` + +you can query as following querying formats: + +``` + print("querying format") + print("Get all endpoints:- show endpoints") + print("Get all class_endpoints:- show classEndpoints") + print("Get all collection_endpoints:- show collectionEndpoints") + print("Get all members of collection_endpoint:-", + "show members") + print("Get all properties of objects:-", + "show objects properties") + print("Get all properties of any member:-", + "show object properties ") + print("Get all classes properties:-show class properties") + print("Get data with compare properties:-", + "show and/or ") + print("Get data by using both opeartions(and,or)", + " you should use brackets like:-", + "show model xyz and (name Drone1 or name Drone2)", + "or, show and ( or )") + +``` + +Query test can be done like this: + +``` + check_data = [['p.id', 'p.operations', 'p.properties', 'p.type'], + ['vocab:EntryPoint/Location', + "['POST'", "'PUT'", "'GET']", + "['Location']", 'Location']] + query = "show classEndpoints" + self.assertEqual(data,check_data) #data is data retrieve from the Redis. +``` + +For more detail take a look at [wiki file](https://github.com/HTTP-APIs/http-apis.github.io/blob/master/hydra-agent-redis-graph.md) + +#### Querying Redis - Core Modules + +While developing the Agent, it's necessary to constantly communicate with Redis. Below there are some instructions on how to query Redis/other specific info and formatting we've used. + +Full Redis command reference can be found here: https://oss.redislabs.com/redisgraph/commands/ + +Entity structure: alias:label {filters}. + +Example of MATCH: +(a:actor)-[:act]->(m:movie {title:"straight outta compton"}) + +GRAPH.QUERY apigraph "MATCH (p) RETURN p" + +Internal Hydra Python Agent naming: + +Labels: + +- collection, classes - macro labels +- objects - for members +- object - for resources + +Aliases: + +Alias for collection example: +Collection +DroneCollection + +Alias for collection member: + +Dronea9d6f083-79dc-48e2-9e4b-fd5e9fc849ab + +To get all nodes from the Graph: +``` +GRAPH.QUERY apigraph "MATCH (p) RETURN p" +``` + +Get all nodes and filter by label: +``` +GRAPH.QUERY apigraph "MATCH (p:collection) RETURN p" +``` + +Get all nodes and filter by label: +```' +GRAPH.QUERY apigraph "MATCH (p) WHERE(p.id = '/serverapi/DroneCollection/72b53615-a480-4920-b126-4d1e1e107dc6') RETURN p" +``` + +To read all the edges of the graph +``` +GRAPH.QUERY apigraph "MATCH ()-[r]->() RETURN type(r)" +``` + +To read all the edges connected to a node +``` +GRAPH.QUERY apigraph "MATCH (p)-[r]->() WHERE p.type = 'DroneCollection' RETURN type(r)" +``` + +Creating Edges between existing Nodes(Ref: https://github.com/RedisGraph/redisgraph-py/issues/16): +*This is not available yet on the oficial doc* +``` +MATCH (f:%s{%s:'%s'}), (t:%s{%s:'%s'}) CREATE (f)-[:in]->(t) +GRAPH.QUERY apigraph "MATCH (s:collection {type:'DroneCollection'} ), (d:objectsDrone {id:'/serverapi/DroneCollection/ea7e438e-a93d-436d-a7e9-994c13d49dc0'} ) CREATE (s)-[:has_Drone]->(d)" +``` + +To create a node: +``` +GRAPH.QUERY apigraph "CREATE (Droneea7e438e-a93d-436d-a7e9-994c13d49dc0:objectsDrone {@id: '/serverapi/DroneCollection/a9d6f083-79dc-48e2-9e4b-fd5e9fc849ab', @type: 'Drone', model: 'Ultra Model S')" + +``` + +To delete a node: +``` +GRAPH.QUERY apigraph "MATCH (p) WHERE (p.id = '/serverapi/DroneCollection/2') DELETE p" + +``` + +References +---------- + +[Hydra-Enabled smart client](http://www.hydra-cg.com/) + +[Hydra core vocabulary](http://www.hydra-cg.com/spec/latest/core/) + + diff --git a/hydra_agent/redis_core/graphutils_operations.py b/hydra_agent/redis_core/graphutils_operations.py index de5d920..1782fe1 100644 --- a/hydra_agent/redis_core/graphutils_operations.py +++ b/hydra_agent/redis_core/graphutils_operations.py @@ -94,10 +94,12 @@ def get_processing(self, url: str, resource: dict) -> list: if (self.vocabulary + ":") in str(supported_prop.prop): if resource[supported_prop.title]: new_resource = {} - discovered_url = self.entrypoint_url.replace( - self.api_doc.entrypoint.api, "").rstrip("/") - discovered_url = discovered_url + \ - resource[supported_prop.title] + collection_name = supported_prop.prop.replace( + self.vocabulary + ":", "") + "Collection" + discovered_url = (self.api_doc.entrypoint.url + + self.api_doc.entrypoint.api + "/" + + collection_name + "/" + + resource[supported_prop.title]) new_resource['parent_id'] = resource['@id'] new_resource['parent_type'] = resource['@type'] new_resource['embedded_url'] = discovered_url @@ -131,7 +133,7 @@ def put_processing(self, url: str, new_object: dict) -> None: # Manually add the id that will be on the server for the object added url_list = url.split('/', 3) new_object["@id"] = '/' + url_list[-1] - # Simply call sync_get to add the resource to the collection at Redis + # Simply call self.get_processing to add the resource to the collection at Redis embedded_resources = self.get_processing(url, new_object) return embedded_resources @@ -145,7 +147,7 @@ def post_processing(self, url: str, updated_object: dict) -> None: url_list = url.split('/', 3) updated_object["@id"] = '/' + url_list[-1] - # Simply call sync_get to add the resource to the collection at Redis + # Simply call self.get_processing to add the resource to the collection at Redis self.delete_processing(url) embedded_resources = self.get_processing(url, updated_object) return embedded_resources diff --git a/hydra_agent/tests/test_agent.py b/hydra_agent/tests/test_agent.py index 62ab24c..33d85ad 100644 --- a/hydra_agent/tests/test_agent.py +++ b/hydra_agent/tests/test_agent.py @@ -12,14 +12,16 @@ 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): + 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://localhost:8080/api") self.redis_proxy = RedisProxy() @@ -78,7 +80,7 @@ def test_get_collection(self, put_session_mock, embedded_get_mock): :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": "/api/StateCollection/1", + new_object = {"@type": "Drone", "DroneState": "1", "name": "Smart Drone", "model": "Hydra Drone", "MaxSpeed": "999", "Sensor": "Wind"} @@ -135,7 +137,7 @@ def test_put(self, put_session_mock, embedded_get_mock): :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": "/api/StateCollection/1", + new_object = {"@type": "Drone", "DroneState": "1", "name": "Smart Drone", "model": "Hydra Drone", "MaxSpeed": "999", "Sensor": "Wind"} @@ -176,7 +178,7 @@ def test_post(self, put_session_mock, post_session_mock, :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": "/api/StateCollection/1", + new_object = {"@type": "Drone", "DroneState": "1", "name": "Smart Drone", "model": "Hydra Drone", "MaxSpeed": "999", "Sensor": "Wind"} @@ -246,7 +248,7 @@ def test_edges(self, put_session_mock, embedded_get_mock): :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": "/api/StateCollection/1", + new_object = {"@type": "Drone", "DroneState": "1", "name": "Smart Drone", "model": "Hydra Drone", "MaxSpeed": "999", "Sensor": "Wind"} diff --git a/hydra_agent/tests/test_examples/hydra_doc_sample.py b/hydra_agent/tests/test_examples/hydra_doc_sample.py index 3ea8efc..d113f56 100644 --- a/hydra_agent/tests/test_examples/hydra_doc_sample.py +++ b/hydra_agent/tests/test_examples/hydra_doc_sample.py @@ -27,14 +27,13 @@ }, "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdfs": "http://www.w3.org/2000/01/rdf-schema#", - "readonly": "hydra:readonly", + "readable": "hydra:readable", "required": "hydra:required", "returns": { "@id": "hydra:returns", "@type": "@id" }, "statusCode": "hydra:statusCode", - "statusCodes": "hydra:statusCodes", "subClassOf": { "@id": "rdfs:subClassOf", "@type": "@id" @@ -44,7 +43,10 @@ "supportedProperty": "hydra:supportedProperty", "title": "hydra:title", "vocab": "http://localhost:8080/api/vocab#", - "writeonly": "hydra:writeonly" + "writeable": "hydra:writeable", + "expectsHeader": "hydra:expectsHeader", + "returnsHeader": "hydra:returnsHeader", + "manages": "hydra:manages" }, "@id": "http://localhost:8080/api/vocab", "@type": "ApiDocumentation", @@ -73,6 +75,8 @@ } ], "returns": "vocab:State", + "expectsHeader": [], + "returnsHeader": [], "title": "GetState" } ], @@ -80,50 +84,50 @@ { "@type": "SupportedProperty", "property": "http://auto.schema.org/speed", - "readonly": "false", + "readable": "true", "required": "true", "title": "Speed", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/geo", - "readonly": "false", + "readable": "true", "required": "true", "title": "Position", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/Property", - "readonly": "false", + "readable": "true", "required": "true", "title": "Direction", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/fuelCapacity", - "readonly": "false", + "readable": "true", "required": "true", "title": "Battery", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "https://schema.org/status", - "readonly": "false", + "readable": "true", "required": "true", "title": "SensorStatus", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/identifier", - "readonly": "false", + "readable": "true", "required": "true", "title": "DroneID", - "writeonly": "false" + "writeable": "true" } ], "title": "State" @@ -150,6 +154,8 @@ } ], "returns": "vocab:Command", + "expectsHeader": [], + "returnsHeader": [], "title": "GetCommand" }, { @@ -164,6 +170,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "AddCommand" }, { @@ -178,6 +186,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "DeleteCommand" } ], @@ -185,18 +195,18 @@ { "@type": "SupportedProperty", "property": "http://schema.org/identifier", - "readonly": "false", + "readable": "true", "required": "true", "title": "DroneID", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "vocab:State", - "readonly": "false", + "readable": "true", "required": "true", "title": "State", - "writeonly": "false" + "writeable": "true" } ], "title": "Command" @@ -223,6 +233,8 @@ } ], "returns": "vocab:Message", + "expectsHeader": [], + "returnsHeader": [], "title": "GetMessage" }, { @@ -237,6 +249,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "DeleteMessage" } ], @@ -244,10 +258,10 @@ { "@type": "SupportedProperty", "property": "http://schema.org/Text", - "readonly": "false", + "readable": "true", "required": "true", "title": "MessageString", - "writeonly": "false" + "writeable": "true" } ], "title": "Message" @@ -269,6 +283,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "UpdateArea" }, { @@ -288,6 +304,8 @@ } ], "returns": "vocab:Area", + "expectsHeader": [], + "returnsHeader": [], "title": "GetArea" } ], @@ -295,18 +313,18 @@ { "@type": "SupportedProperty", "property": "http://schema.org/geo", - "readonly": "false", + "readable": "true", "required": "true", "title": "TopLeft", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/geo", - "readonly": "false", + "readable": "true", "required": "true", "title": "BottomRight", - "writeonly": "false" + "writeable": "true" } ], "title": "Area" @@ -333,6 +351,8 @@ } ], "returns": "vocab:Datastream", + "expectsHeader": [], + "returnsHeader": [], "title": "ReadDatastream" }, { @@ -347,6 +367,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "UpdateDatastream" }, { @@ -361,6 +383,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "DeleteDatastream" } ], @@ -368,26 +392,26 @@ { "@type": "SupportedProperty", "property": "http://schema.org/QuantitativeValue", - "readonly": "false", + "readable": "true", "required": "true", "title": "Temperature", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/identifier", - "readonly": "false", + "readable": "true", "required": "true", "title": "DroneID", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/geo", - "readonly": "false", + "readable": "true", "required": "true", "title": "Position", - "writeonly": "false" + "writeable": "true" } ], "title": "Datastream" @@ -409,6 +433,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "SubmitDrone" }, { @@ -423,6 +449,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "CreateDrone" }, { @@ -442,6 +470,8 @@ } ], "returns": "vocab:Drone", + "expectsHeader": [], + "returnsHeader": [], "title": "GetDrone" }, { @@ -461,6 +491,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "DeleteDrone" } ], @@ -468,42 +500,42 @@ { "@type": "SupportedProperty", "property": "vocab:State", - "readonly": "false", + "readable": "true", "required": "true", "title": "DroneState", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/name", - "readonly": "false", + "readable": "true", "required": "true", "title": "name", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/model", - "readonly": "false", + "readable": "true", "required": "true", "title": "model", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://auto.schema.org/speed", - "readonly": "false", + "readable": "true", "required": "true", "title": "MaxSpeed", - "writeonly": "false" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/device", - "readonly": "false", + "readable": "true", "required": "true", "title": "Sensor", - "writeonly": "false" + "writeable": "true" } ], "title": "Drone" @@ -530,6 +562,8 @@ } ], "returns": "vocab:LogEntry", + "expectsHeader": [], + "returnsHeader": [], "title": "GetLog" }, { @@ -544,6 +578,8 @@ } ], "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "title": "AddLog" } ], @@ -551,58 +587,58 @@ { "@type": "SupportedProperty", "property": "http://schema.org/identifier", - "readonly": "true", + "readable": "true", "required": "false", "title": "DroneID", - "writeonly": "true" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/UpdateAction", - "readonly": "false", + "readable": "false", "required": "false", "title": "Update", - "writeonly": "true" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/ReplyAction", - "readonly": "false", + "readable": "false", "required": "false", "title": "Get", - "writeonly": "true" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "http://schema.org/SendAction", - "readonly": "false", + "readable": "false", "required": "false", "title": "Send", - "writeonly": "true" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "vocab:State", - "readonly": "false", + "readable": "false", "required": "false", "title": "State", - "writeonly": "true" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "vocab:Datastream", - "readonly": "false", + "readable": "false", "required": "false", "title": "Data", - "writeonly": "true" + "writeable": "true" }, { "@type": "SupportedProperty", "property": "vocab:Command", - "readonly": "false", + "readable": "false", "required": "false", "title": "Command", - "writeonly": "true" + "writeable": "true" } ], "title": "LogEntry" @@ -624,10 +660,10 @@ { "@type": "SupportedProperty", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "null", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "Collection" @@ -645,6 +681,8 @@ "expects": "null", "method": "GET", "returns": "vocab:CommandCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -654,6 +692,8 @@ "expects": "vocab:Command", "method": "PUT", "returns": "vocab:Command", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Command entity was created successfully.", @@ -668,10 +708,10 @@ "@type": "SupportedProperty", "description": "The command", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "false", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "CommandCollection" @@ -689,6 +729,8 @@ "expects": "null", "method": "GET", "returns": "vocab:StateCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -698,6 +740,8 @@ "expects": "vocab:State", "method": "PUT", "returns": "vocab:State", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the State entity was created successfully.", @@ -712,10 +756,10 @@ "@type": "SupportedProperty", "description": "The state", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "false", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "StateCollection" @@ -733,6 +777,8 @@ "expects": "null", "method": "GET", "returns": "vocab:MessageCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -742,6 +788,8 @@ "expects": "vocab:Message", "method": "PUT", "returns": "vocab:Message", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Message entity was created successfully.", @@ -756,10 +804,10 @@ "@type": "SupportedProperty", "description": "The message", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "false", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "MessageCollection" @@ -777,6 +825,8 @@ "expects": "null", "method": "GET", "returns": "vocab:DroneCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -786,6 +836,8 @@ "expects": "vocab:Drone", "method": "PUT", "returns": "vocab:Drone", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Drone entity was created successfully.", @@ -800,10 +852,10 @@ "@type": "SupportedProperty", "description": "The drone", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "false", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "DroneCollection" @@ -821,6 +873,8 @@ "expects": "null", "method": "GET", "returns": "vocab:LogEntryCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -830,6 +884,8 @@ "expects": "vocab:LogEntry", "method": "PUT", "returns": "vocab:LogEntry", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the LogEntry entity was created successfully.", @@ -844,10 +900,10 @@ "@type": "SupportedProperty", "description": "The logentry", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "false", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "LogEntryCollection" @@ -865,6 +921,8 @@ "expects": "null", "method": "GET", "returns": "vocab:DatastreamCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -874,6 +932,8 @@ "expects": "vocab:Datastream", "method": "PUT", "returns": "vocab:Datastream", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Datastream entity was created successfully.", @@ -888,10 +948,10 @@ "@type": "SupportedProperty", "description": "The datastream", "property": "http://www.w3.org/ns/hydra/core#member", - "readonly": "false", + "readable": "true", "required": "false", "title": "members", - "writeonly": "false" + "writeable": "true" } ], "title": "DatastreamCollection" @@ -908,6 +968,8 @@ "expects": "null", "method": "GET", "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": "vocab:EntryPoint" } ], @@ -931,6 +993,8 @@ "label": "UpdateArea", "method": "POST", "returns": "null", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "Area of interest changed", @@ -947,6 +1011,8 @@ "label": "GetArea", "method": "GET", "returns": "vocab:Area", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "Area of interest not found", @@ -962,9 +1028,9 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" }, { "hydra:description": "The CommandCollection collection", @@ -984,6 +1050,8 @@ "expects": "null", "method": "GET", "returns": "vocab:CommandCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -993,6 +1061,8 @@ "expects": "vocab:Command", "method": "PUT", "returns": "vocab:Command", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Command entity was created successfully.", @@ -1003,9 +1073,9 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" }, { "hydra:description": "The StateCollection collection", @@ -1025,6 +1095,8 @@ "expects": "null", "method": "GET", "returns": "vocab:StateCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -1034,6 +1106,8 @@ "expects": "vocab:State", "method": "PUT", "returns": "vocab:State", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the State entity was created successfully.", @@ -1044,9 +1118,9 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" }, { "hydra:description": "The MessageCollection collection", @@ -1066,6 +1140,8 @@ "expects": "null", "method": "GET", "returns": "vocab:MessageCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -1075,6 +1151,8 @@ "expects": "vocab:Message", "method": "PUT", "returns": "vocab:Message", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Message entity was created successfully.", @@ -1085,9 +1163,9 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" }, { "hydra:description": "The DroneCollection collection", @@ -1107,6 +1185,8 @@ "expects": "null", "method": "GET", "returns": "vocab:DroneCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -1116,6 +1196,8 @@ "expects": "vocab:Drone", "method": "PUT", "returns": "vocab:Drone", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Drone entity was created successfully.", @@ -1126,9 +1208,9 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" }, { "hydra:description": "The LogEntryCollection collection", @@ -1148,6 +1230,8 @@ "expects": "null", "method": "GET", "returns": "vocab:LogEntryCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -1157,6 +1241,8 @@ "expects": "vocab:LogEntry", "method": "PUT", "returns": "vocab:LogEntry", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the LogEntry entity was created successfully.", @@ -1167,9 +1253,9 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" }, { "hydra:description": "The DatastreamCollection collection", @@ -1189,6 +1275,8 @@ "expects": "null", "method": "GET", "returns": "vocab:DatastreamCollection", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [] }, { @@ -1198,6 +1286,8 @@ "expects": "vocab:Datastream", "method": "PUT", "returns": "vocab:Datastream", + "expectsHeader": [], + "returnsHeader": [], "possibleStatus": [ { "title": "If the Datastream entity was created successfully.", @@ -1208,13 +1298,13 @@ } ] }, - "readonly": "true", + "readable": "true", "required": "null", - "writeonly": "false" + "writeable": "false" } ], "title": "EntryPoint" } ], "title": "API Doc for the server side API" -} \ No newline at end of file +} diff --git a/redis_setup.sh b/redis_setup.sh index ebef16c..1472e32 100755 --- a/redis_setup.sh +++ b/redis_setup.sh @@ -1,5 +1,5 @@ -#It will check, if docker is not installed then install it. +# It will check if docker is not installed, if not it will install it. docker -v if [ "$?" = "127" ] then @@ -12,12 +12,13 @@ else fi # after getting the docker-ce, check if `redislabs/redisgraph` docker image is not installed then install ii. -if [ -z "$(docker images -q redislabs/redisgraph:edge)" ] +if [ -z "$(docker images -q redislabs/redisgraph:2.0-edge)" ] then - echo "Docker already have a redislabs/redisgraph:edge image" + echo "Docker already have a redislabs/redisgraph:2.0-edge image" else - sudo docker run -p 6379:6379 -it --rm redislabs/redisgraph:edge + sudo docker run -p 6379:6379 -it --rm redislabs/redisgraph:2.0-edge fi -# command for run the server -# sudo docker run -p 6379:6379 -it --rm redislabs/redisgraph # uncomment this line if you want to run server without using dockerflie. + +# Command to run the Redis directly +# sudo docker run -p 6379:6379 -it --rm redislabs/redisgraph:2.0-edge \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4cdb9f7..4ff5943 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,6 @@ httplib2 redis graphviz requests +python-socketio -e git+https://github.com/RedisGraph/redisgraph-py#egg=redisgraph -e git+https://github.com/HTTP-APIs/hydra-python-core#egg=hydra_python_core diff --git a/setup.py b/setup.py index 665c2f0..e52db47 100644 --- a/setup.py +++ b/setup.py @@ -3,23 +3,30 @@ from setuptools import setup, find_packages -try: # for pip >= 10 +try: + # pip >=20 + from pip._internal.network.session import PipSession from pip._internal.req import parse_requirements - from pip._internal.download import PipSession -except ImportError: # for pip <= 9.0.3 - from pip.req import parse_requirements - from pip.download import PipSession +except ImportError: + try: + # 10.0.0 <= pip <= 19.3.1 + from pip._internal.download import PipSession + from pip._internal.req import parse_requirements + except ImportError: + # pip <= 9.0.3 + from pip.download import PipSession + from pip.req import parse_requirements -install_requires = parse_requirements('requirements.txt', session=PipSession()) +install_requires = parse_requirements('requirements.txt',session=PipSession()) dependencies = [str(package.req) for package in install_requires] setup(name='hydra-python-agent', include_package_data=True, version='0.0.1', description='A Hydra agent using Python and Redis', - author='W3C HYDRA development group', - author_email='public-hydra@w3.org', + author='Hydra Ecosystem', + author_email='collective@hydraecosystem.org', url='https://github.com/HTTP-APIs/python-hydra-agent', python_requires='>=3', install_requires=dependencies,