This is an automated email from the ASF dual-hosted git repository.
jin pushed a commit to branch py-client-new
in repository https://gitbox.apache.org/repos/asf/incubator-hugegraph-ai.git
The following commit(s) were added to refs/heads/py-client-new by this push:
new 752bd5f feat(client): Implement new api & optimize code structure
(#63)
752bd5f is described below
commit 752bd5f1f7666ae6ec83122680aed26f9e4112ef
Author: PeiChaoXu <[email protected]>
AuthorDate: Tue Aug 13 20:49:27 2024 +0800
feat(client): Implement new api & optimize code structure (#63)
- Enhanced Naming Convention Support: Introduced support for
underscore-named function calls in addition to existing camelCase naming
convention. This allows for greater flexibility and compatibility with
different coding styles and external libraries.
- Expanded index_label Types: Augmented the index_label functionality by
adding support for shard and unique types. These additional types provide
enhanced indexing capabilities that enable more sophisticated data organization
and retrieval scenarios, improving overall performance and scalability.
- New Rank API Implementation: Implemented a new rank API to support
ranking and sorting of data based on specified criteria. This feature enables
applications to easily incorporate ranking logic into their workflows,
enhancing user experience by presenting the most relevant or prioritized
information first.
- Optimized HTTP Invocation and Response Handling: Refactored HTTP client
invocations to utilize a more streamlined approach. Introduced
ResponseValidation for managing response checks, effectively reducing the need
for explicit check_if_success calls in multiple locations. This optimization
not only simplifies code but also enhances its maintainability and readability,
making it easier to identify and resolve potential issues related to HTTP
response handling.
TODO:
- handle some py-lint problems before Merge to Master/Main
---------
Co-authored-by: imbajin <[email protected]>
Co-authored-by: blank <[email protected]>
---
hugegraph-python-client/README.md | 39 +--
.../src/pyhugegraph/api/auth.py | 277 +++++++++------------
.../src/pyhugegraph/api/common.py | 37 ++-
.../src/pyhugegraph/api/graph.py | 196 +++++----------
.../src/pyhugegraph/api/graphs.py | 48 ++--
.../src/pyhugegraph/api/gremlin.py | 30 ++-
.../src/pyhugegraph/api/metric.py | 65 ++---
.../src/pyhugegraph/api/rank.py | 65 +++++
.../src/pyhugegraph/api/rebuild.py | 87 +++++++
.../src/pyhugegraph/api/schema.py | 121 ++++-----
.../pyhugegraph/api/schema_manage/edge_label.py | 43 ++--
.../pyhugegraph/api/schema_manage/index_label.py | 39 +--
.../pyhugegraph/api/schema_manage/property_key.py | 53 ++--
.../pyhugegraph/api/schema_manage/vertex_label.py | 49 ++--
.../src/pyhugegraph/api/services.py | 135 ++++++++++
.../src/pyhugegraph/api/task.py | 24 +-
.../src/pyhugegraph/api/traverser.py | 245 ++++++++----------
.../src/pyhugegraph/api/variable.py | 26 +-
.../src/pyhugegraph/api/version.py | 7 +-
hugegraph-python-client/src/pyhugegraph/client.py | 16 +-
.../src/pyhugegraph/structure/rank_data.py | 96 +++++++
.../src/pyhugegraph/structure/services_data.py | 62 +++++
.../src/pyhugegraph/utils/huge_config.py | 15 +-
.../src/pyhugegraph/utils/huge_decorator.py | 1 -
.../src/pyhugegraph/utils/huge_requests.py | 78 ++++--
.../src/pyhugegraph/utils/huge_router.py | 97 ++++++--
.../src/pyhugegraph/utils/log.py | 8 +-
.../src/pyhugegraph/utils/util.py | 68 +++++
style/pylint.conf | 3 +-
29 files changed, 1206 insertions(+), 824 deletions(-)
diff --git a/hugegraph-python-client/README.md
b/hugegraph-python-client/README.md
index ba59075..8f478a6 100644
--- a/hugegraph-python-client/README.md
+++ b/hugegraph-python-client/README.md
@@ -10,36 +10,43 @@ pip3 install hugegraph-python
### Install from source
-release soon
+```bash
+cd /path/to/hugegraph-python-client
+
+# Normal install
+pip install .
+
+# (Optional) install the devel version
+pip install -e .
+```
## Examples
```python
from pyhugegraph.client import PyHugeClient
-client = PyHugeClient("127.0.0.1", "8080", user="admin", pwd="admin",
graph="hugegraph")
-
-"""system"""
-print(client.get_graphinfo())
-print(client.get_all_graphs())
-print(client.get_version())
-print(client.get_graph_config())
+# For HugeGraph API version ≥ v3: (Or enable graphspace function)
+# - The 'graphspace' parameter becomes relevant if graphspaces are
enabled.(default name is 'DEFAULT')
+# - Otherwise, the graphspace parameter is optional and can be ignored.
+client = PyHugeClient("127.0.0.1", "8080", user="admin", pwd="admin",
graph="hugegraph", graphspace="DEFAULT")
-"""schema"""
+""""
+Note:
+Could refer to the official REST-API doc of your HugeGraph version for
accurate details.
+If some API is not as expected, please submit a issue or contact us.
+"""
schema = client.schema()
schema.propertyKey("name").asText().ifNotExist().create()
schema.propertyKey("birthDate").asText().ifNotExist().create()
-schema.vertexLabel("Person").properties("name",
"birthDate").usePrimaryKeyId().primaryKeys(
- "name").ifNotExist().create()
-schema.vertexLabel("Movie").properties("name").usePrimaryKeyId().primaryKeys(
- "name").ifNotExist().create()
+schema.vertexLabel("Person").properties("name",
"birthDate").usePrimaryKeyId().primaryKeys("name").ifNotExist().create()
+schema.vertexLabel("Movie").properties("name").usePrimaryKeyId().primaryKeys("name").ifNotExist().create()
schema.edgeLabel("ActedIn").sourceLabel("Person").targetLabel("Movie").ifNotExist().create()
print(schema.getVertexLabels())
print(schema.getEdgeLabels())
print(schema.getRelations())
-"""graph"""
+"""Init Graph"""
g = client.graph()
g.addVertex("Person", {"name": "Al Pacino", "birthDate": "1940-04-25"})
g.addVertex("Person", {"name": "Robert De Niro", "birthDate": "1943-08-17"})
@@ -57,8 +64,8 @@ res = g.getVertexById("12:Al Pacino").label
print(res)
g.close()
-"""gremlin"""
+"""Execute Gremlin Query"""
g = client.gremlin()
-res = g.exec("g.V().limit(10)")
+res = g.exec("g.V().limit(5)")
print(res)
```
diff --git a/hugegraph-python-client/src/pyhugegraph/api/auth.py
b/hugegraph-python-client/src/pyhugegraph/api/auth.py
index 09c80b0..90b3e98 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/auth.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/auth.py
@@ -18,10 +18,9 @@
import json
+from typing import Optional, Dict
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
class AuthManager(HugeParamsBase):
@@ -29,216 +28,188 @@ class AuthManager(HugeParamsBase):
@router.http("GET", "auth/users")
def list_users(self, limit=None):
params = {"limit": limit} if limit is not None else {}
- response = self._invoke_request(params=params)
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return []
+ return self._invoke_request(params=params)
@router.http("POST", "auth/users")
- def create_user(self, user_name, user_password, user_phone=None,
user_email=None):
- data = {
- "user_name": user_name,
- "user_password": user_password,
- "user_phone": user_phone,
- "user_email": user_email,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def create_user(
+ self, user_name, user_password, user_phone=None, user_email=None
+ ) -> Optional[Dict]:
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "user_name": user_name,
+ "user_password": user_password,
+ "user_phone": user_phone,
+ "user_email": user_email,
+ }
+ )
+ )
@router.http("DELETE", "auth/users/{user_id}")
- def delete_user(self, user_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- if response.status_code != 204:
- return response.json()
- return {}
+ def delete_user(self, user_id) -> Optional[Dict]: # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http("PUT", "auth/users/{user_id}")
def modify_user(
self,
- user_id,
+ user_id, # pylint: disable=unused-argument
user_name=None,
user_password=None,
user_phone=None,
user_email=None,
- ):
- data = {
- "user_name": user_name,
- "user_password": user_password,
- "user_phone": user_phone,
- "user_email": user_email,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ ) -> Optional[Dict]:
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "user_name": user_name,
+ "user_password": user_password,
+ "user_phone": user_phone,
+ "user_email": user_email,
+ }
+ )
+ )
@router.http("GET", "auth/users/{user_id}")
- def get_user(self, user_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_user(self, user_id) -> Optional[Dict]: # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http("GET", "auth/groups")
- def list_groups(self, limit=None):
+ def list_groups(self, limit=None) -> Optional[Dict]:
params = {"limit": limit} if limit is not None else {}
- response = self._invoke_request(params=params)
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return []
+ return self._invoke_request(params=params)
@router.http("POST", "auth/groups")
- def create_group(self, group_name, group_description=None):
+ def create_group(self, group_name, group_description=None) ->
Optional[Dict]:
data = {"group_name": group_name, "group_description":
group_description}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(data=json.dumps(data))
@router.http("DELETE", "auth/groups/{group_id}")
- def delete_group(self, group_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- if response.status_code != 204:
- return response.json()
- return {}
+ def delete_group(
+ self, group_id # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("PUT", "auth/groups/{group_id}")
- def modify_group(self, group_id, group_name=None, group_description=None):
+ def modify_group(
+ self,
+ group_id, # pylint: disable=unused-argument
+ group_name=None,
+ group_description=None,
+ ) -> Optional[Dict]:
data = {"group_name": group_name, "group_description":
group_description}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(data=json.dumps(data))
@router.http("GET", "auth/groups/{group_id}")
- def get_group(self, group_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_group(self, group_id) -> Optional[Dict]: # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http("POST", "auth/accesses")
- def grant_accesses(self, group_id, target_id, access_permission):
- data = {
- "group": group_id,
- "target": target_id,
- "access_permission": access_permission,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def grant_accesses(self, group_id, target_id, access_permission) ->
Optional[Dict]:
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "group": group_id,
+ "target": target_id,
+ "access_permission": access_permission,
+ }
+ )
+ )
@router.http("DELETE", "auth/accesses/{access_id}")
- def revoke_accesses(self, access_id):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
+ def revoke_accesses(
+ self, access_id # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("PUT", "auth/accesses/{access_id}")
- def modify_accesses(self, access_id, access_description):
+ def modify_accesses(
+ self, access_id, access_description # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
# The permission of access can\'t be updated
data = {"access_description": access_description}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(data=json.dumps(data))
@router.http("GET", "auth/accesses/{access_id}")
- def get_accesses(self, access_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_accesses(
+ self, access_id # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("GET", "auth/accesses")
- def list_accesses(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def list_accesses(self) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("POST", "auth/targets")
- def create_target(self, target_name, target_graph, target_url,
target_resources):
- data = {
- "target_name": target_name,
- "target_graph": target_graph,
- "target_url": target_url,
- "target_resources": target_resources,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def create_target(
+ self, target_name, target_graph, target_url, target_resources
+ ) -> Optional[Dict]:
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "target_name": target_name,
+ "target_graph": target_graph,
+ "target_url": target_url,
+ "target_resources": target_resources,
+ }
+ )
+ )
@router.http("DELETE", "auth/targets/{target_id}")
- def delete_target(self, target_id):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
+ def delete_target(self, target_id) -> None: # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http("PUT", "auth/targets/{target_id}")
def update_target(
- self, target_id, target_name, target_graph, target_url,
target_resources
- ):
- data = {
- "target_name": target_name,
- "target_graph": target_graph,
- "target_url": target_url,
- "target_resources": target_resources,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ self,
+ target_id, # pylint: disable=unused-argument
+ target_name,
+ target_graph,
+ target_url,
+ target_resources,
+ ) -> Optional[Dict]:
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "target_name": target_name,
+ "target_graph": target_graph,
+ "target_url": target_url,
+ "target_resources": target_resources,
+ }
+ )
+ )
@router.http("GET", "auth/targets/{target_id}")
- def get_target(self, target_id, response=None):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_target(
+ self, target_id, response=None # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("GET", "auth/targets")
- def list_targets(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def list_targets(self) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("POST", "auth/belongs")
- def create_belong(self, user_id, group_id):
+ def create_belong(self, user_id, group_id) -> Optional[Dict]:
data = {"user": user_id, "group": group_id}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(data=json.dumps(data))
@router.http("DELETE", "auth/belongs/{belong_id}")
- def delete_belong(self, belong_id):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
+ def delete_belong(self, belong_id) -> None: # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http("PUT", "auth/belongs/{belong_id}")
- def update_belong(self, belong_id, description):
+ def update_belong(
+ self, belong_id, description # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
data = {"belong_description": description}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(data=json.dumps(data))
@router.http("GET", "auth/belongs/{belong_id}")
- def get_belong(self, belong_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_belong(
+ self, belong_id # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("GET", "auth/belongs")
- def list_belongs(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def list_belongs(self) -> Optional[Dict]:
+ return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/api/common.py
b/hugegraph-python-client/src/pyhugegraph/api/common.py
index 731d4d2..631a277 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/common.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/common.py
@@ -16,8 +16,11 @@
# under the License.
+import re
+
from abc import ABC
-from pyhugegraph.utils.huge_router import HGraphRouter
+from pyhugegraph.utils.log import log
+from pyhugegraph.utils.huge_router import RouterMixin
from pyhugegraph.utils.huge_requests import HGraphSession
@@ -49,12 +52,24 @@ class HGraphContext(ABC):
def close(self):
self._sess.close()
+ @property
+ def session(self):
+ """
+ Get session.
+
+ Returns:
+ -------
+ HGraphSession: session
+ """
+ return self._sess
+
# todo: rename -> HGraphModule | HGraphRouterable | HGraphModel
-class HugeParamsBase(HGraphContext, HGraphRouter):
+class HugeParamsBase(HGraphContext, RouterMixin):
def __init__(self, sess: HGraphSession) -> None:
super().__init__(sess)
self._parameter_holder = None
+ self.__camel_to_snake_case()
def add_parameter(self, key, value):
self._parameter_holder.set(key, value)
@@ -67,3 +82,21 @@ class HugeParamsBase(HGraphContext, HGraphRouter):
def clean_parameter_holder(self):
self._parameter_holder = None
+
+ def __camel_to_snake_case(self):
+ camel_case_pattern = re.compile(r"^[a-z]+([A-Z][a-z]*)+$")
+ attributes = dir(self)
+ for attr in attributes:
+ if attr.startswith("__"):
+ continue
+ if not callable(getattr(self, attr)):
+ continue
+ if camel_case_pattern.match(attr):
+ s = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", attr)
+ snake = re.sub("([a-z0-9])([A-Z])", r"\1_\2", s).lower()
+ setattr(self, snake, getattr(self, attr))
+ log.debug( # pylint: disable=logging-fstring-interpolation
+ f"The method {self.__class__.__name__}.{attr} "
+ f"is deprecated and will be removed in future versions. "
+ f"Please update your code to use the new method name
{self.__class__.__name__}.{snake} instead."
+ )
diff --git a/hugegraph-python-client/src/pyhugegraph/api/graph.py
b/hugegraph-python-client/src/pyhugegraph/api/graph.py
index 708d63c..f410404 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/graph.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/graph.py
@@ -17,21 +17,12 @@
import json
+from typing import Optional, List
from pyhugegraph.api.common import HugeParamsBase
from pyhugegraph.structure.vertex_data import VertexData
from pyhugegraph.structure.edge_data import EdgeData
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.exceptions import (
- NotFoundError,
- CreateError,
- RemoveError,
- UpdateError,
-)
-from pyhugegraph.utils.util import (
- create_exception,
- check_if_authorized,
- check_if_success,
-)
+from pyhugegraph.utils.exceptions import NotFoundError
class GraphManager(HugeParamsBase):
@@ -43,12 +34,8 @@ class GraphManager(HugeParamsBase):
data["id"] = id
data["label"] = label
data["properties"] = properties
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(
- response, CreateError(f"create vertex failed:
{str(response.content)}")
- ):
- res = VertexData(json.loads(response.content))
- return res
+ if response := self._invoke_request(data=json.dumps(data)):
+ return VertexData(response)
return None
@router.http("POST", "graph/vertices/batch")
@@ -56,46 +43,28 @@ class GraphManager(HugeParamsBase):
data = []
for item in input_data:
data.append({"label": item[0], "properties": item[1]})
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(
- response, CreateError(f"create vertexes failed:
{str(response.content)}")
- ):
- res = []
- for item in json.loads(response.content):
- res.append(VertexData({"id": item}))
- return res
+ if response := self._invoke_request(data=json.dumps(data)):
+ return [VertexData({"id": item}) for item in response]
return None
@router.http("PUT", 'graph/vertices/"{vertex_id}"?action=append')
- def appendVertex(self, vertex_id, properties):
+ def appendVertex(self, vertex_id, properties): # pylint:
disable=unused-argument
data = {"properties": properties}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(
- response, UpdateError(f"append vertex failed:
{str(response.content)}")
- ):
- res = VertexData(json.loads(response.content))
- return res
+ if response := self._invoke_request(data=json.dumps(data)):
+ return VertexData(response)
return None
@router.http("PUT", 'graph/vertices/"{vertex_id}"?action=eliminate')
- def eliminateVertex(self, vertex_id, properties):
+ def eliminateVertex(self, vertex_id, properties): # pylint:
disable=unused-argument
data = {"properties": properties}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(
- response, UpdateError(f"eliminate vertex failed:
{str(response.content)}")
- ):
- res = VertexData(json.loads(response.content))
- return res
+ if response := self._invoke_request(data=json.dumps(data)):
+ return VertexData(response)
return None
@router.http("GET", 'graph/vertices/"{vertex_id}"')
- def getVertexById(self, vertex_id):
- response = self._invoke_request()
- if check_if_success(
- response, NotFoundError(f"Vertex not found:
{str(response.content)}")
- ):
- res = VertexData(json.loads(response.content))
- return res
+ def getVertexById(self, vertex_id): # pylint: disable=unused-argument
+ if response := self._invoke_request():
+ return VertexData(response)
return None
def getVertexByPage(self, label, limit, page=None, properties=None):
@@ -110,16 +79,11 @@ class GraphManager(HugeParamsBase):
para += "&page"
para = para + "&limit=" + str(limit)
path = path + para[1:]
- response = self._sess.request(path)
- if check_if_success(
- response, NotFoundError(f"Vertex not found:
{str(response.content)}")
- ):
- res = []
- for item in json.loads(response.content)["vertices"]:
- res.append(VertexData(item))
- next_page = json.loads(response.content)["page"]
+ if response := self._sess.request(path):
+ res = [VertexData(item) for item in response["vertices"]]
+ next_page = response["page"]
return res, next_page
- return None
+ return None, None
def getVertexByCondition(self, label="", limit=0, page=None,
properties=None):
path = "graph/vertices?"
@@ -135,43 +99,28 @@ class GraphManager(HugeParamsBase):
else:
para += "&page"
path = path + para[1:]
- response = self._sess.request(path)
- if check_if_success(
- response, NotFoundError(f"Vertex not found:
{str(response.content)}")
- ):
- res = []
- for item in json.loads(response.content)["vertices"]:
- res.append(VertexData(item))
- return res
+ if response := self._sess.request(path):
+ return [VertexData(item) for item in response["vertices"]]
return None
@router.http("DELETE", 'graph/vertices/"{vertex_id}"')
- def removeVertexById(self, vertex_id):
- response = self._invoke_request()
- if check_if_success(
- response, RemoveError(f"remove vertex failed:
{str(response.content)}")
- ):
- return response.content
- return None
+ def removeVertexById(self, vertex_id): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http("POST", "graph/edges")
- def addEdge(self, edge_label, out_id, in_id, properties):
+ def addEdge(self, edge_label, out_id, in_id, properties) ->
Optional[EdgeData]:
data = {
"label": edge_label,
"outV": out_id,
"inV": in_id,
"properties": properties,
}
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(
- response, CreateError(f"created edge failed:
{str(response.content)}")
- ):
- res = EdgeData(json.loads(response.content))
- return res
+ if response := self._invoke_request(data=json.dumps(data)):
+ return EdgeData(response)
return None
@router.http("POST", "graph/edges/batch")
- def addEdges(self, input_data):
+ def addEdges(self, input_data) -> Optional[List[EdgeData]]:
data = []
for item in input_data:
data.append(
@@ -184,44 +133,36 @@ class GraphManager(HugeParamsBase):
"properties": item[5],
}
)
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(
- response, CreateError(f"created edges failed:
{str(response.content)}")
- ):
- res = []
- for item in json.loads(response.content):
- res.append(EdgeData({"id": item}))
- return res
+ if response := self._invoke_request(data=json.dumps(data)):
+ return [EdgeData({"id": item}) for item in response]
return None
@router.http("PUT", "graph/edges/{edge_id}?action=append")
- def appendEdge(self, edge_id, properties):
- response = self._invoke_request(data=json.dumps({"properties":
properties}))
- if check_if_success(
- response, UpdateError(f"append edge failed:
{str(response.content)}")
+ def appendEdge(
+ self, edge_id, properties # pylint: disable=unused-argument
+ ) -> Optional[EdgeData]:
+ if response := self._invoke_request(
+ data=json.dumps({"properties": properties})
):
- res = EdgeData(json.loads(response.content))
- return res
+ return EdgeData(response)
return None
@router.http("PUT", "graph/edges/{edge_id}?action=eliminate")
- def eliminateEdge(self, edge_id, properties):
- response = self._invoke_request(data=json.dumps({"properties":
properties}))
- if check_if_success(
- response, UpdateError(f"eliminate edge failed:
{str(response.content)}")
+ def eliminateEdge(
+ self, edge_id, properties # pylint: disable=unused-argument
+ ) -> Optional[EdgeData]:
+ if response := self._invoke_request(
+ data=json.dumps({"properties": properties})
):
- res = EdgeData(json.loads(response.content))
- return res
+ return EdgeData(response)
return None
@router.http("GET", "graph/edges/{edge_id}")
- def getEdgeById(self, edge_id):
- response = self._invoke_request()
- if check_if_success(
- response, NotFoundError(f"not found edge: {str(response.content)}")
- ):
- res = EdgeData(json.loads(response.content))
- return res
+ def getEdgeById(
+ self, edge_id # pylint: disable=unused-argument
+ ) -> Optional[EdgeData]:
+ if response := self._invoke_request():
+ return EdgeData(response)
return None
def getEdgeByPage(
@@ -233,7 +174,7 @@ class GraphManager(HugeParamsBase):
page=None,
properties=None,
):
- path = f"graph/edges?"
+ path = "graph/edges?"
para = ""
if vertex_id:
if direction:
@@ -251,53 +192,32 @@ class GraphManager(HugeParamsBase):
if limit > 0:
para = para + "&limit=" + str(limit)
path = path + para[1:]
- response = self._sess.request(path)
- if check_if_success(
- response, NotFoundError(f"not found edges:
{str(response.content)}")
- ):
- res = []
- for item in json.loads(response.content)["edges"]:
- res.append(EdgeData(item))
- return res, json.loads(response.content)["page"]
- return None
+ if response := self._sess.request(path):
+ return [EdgeData(item) for item in response["edges"]],
response["page"]
+ return None, None
@router.http("DELETE", "graph/edges/{edge_id}")
- def removeEdgeById(self, edge_id):
- response = self._invoke_request()
- if check_if_success(
- response, RemoveError(f"remove edge failed:
{str(response.content)}")
- ):
- return response.content
- return None
+ def removeEdgeById(self, edge_id) -> dict: # pylint:
disable=unused-argument
+ return self._invoke_request()
- def getVerticesById(self, vertex_ids):
+ def getVerticesById(self, vertex_ids) -> Optional[List[VertexData]]:
if not vertex_ids:
return []
path = "traversers/vertices?"
for vertex_id in vertex_ids:
path += f'ids="{vertex_id}"&'
path = path.rstrip("&")
- response = self._sess.request(path)
- if response.status_code == 200 and check_if_authorized(response):
- res = []
- for item in json.loads(response.content)["vertices"]:
- res.append(VertexData(item))
- return res
- create_exception(response.content)
+ if response := self._sess.request(path):
+ return [VertexData(item) for item in response["vertices"]]
return None
- def getEdgesById(self, edge_ids):
+ def getEdgesById(self, edge_ids) -> Optional[List[EdgeData]]:
if not edge_ids:
return []
path = "traversers/edges?"
for vertex_id in edge_ids:
path += f"ids={vertex_id}&"
path = path.rstrip("&")
- response = self._sess.request(path)
- if response.status_code == 200 and check_if_authorized(response):
- res = []
- for item in json.loads(response.content)["edges"]:
- res.append(EdgeData(item))
- return res
- create_exception(response.content)
+ if response := self._sess.request(path):
+ return [EdgeData(item) for item in response["edges"]]
return None
diff --git a/hugegraph-python-client/src/pyhugegraph/api/graphs.py
b/hugegraph-python-client/src/pyhugegraph/api/graphs.py
index c7f4e30..dbcf477 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/graphs.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/graphs.py
@@ -19,49 +19,39 @@ import json
from pyhugegraph.api.common import HugeParamsBase
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.exceptions import NotFoundError
-from pyhugegraph.utils.util import check_if_success
+from pyhugegraph.utils.util import ResponseValidation
class GraphsManager(HugeParamsBase):
@router.http("GET", "/graphs")
- def get_all_graphs(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return str(response.content)
- return ""
+ def get_all_graphs(self) -> dict:
+ return self._invoke_request(validator=ResponseValidation("text"))
@router.http("GET", "/versions")
- def get_version(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return str(response.content)
- return ""
+ def get_version(self) -> dict:
+ return self._invoke_request(validator=ResponseValidation("text"))
@router.http("GET", "")
- def get_graph_info(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return str(response.content)
- return ""
+ def get_graph_info(self) -> dict:
+ return self._invoke_request(validator=ResponseValidation("text"))
- def clear_graph_all_data(self):
- if self._sess._cfg.gs_supported:
+ def clear_graph_all_data(self) -> dict:
+ if self._sess.cfg.gs_supported:
response = self._sess.request(
- "", "PUT", data=json.dumps({"action": "clear", "clear_schema":
True})
+ "",
+ "PUT",
+ validator=ResponseValidation("text"),
+ data=json.dumps({"action": "clear", "clear_schema": True}),
)
else:
response = self._sess.request(
- "clear?confirm_message=I%27m+sure+to+delete+all+data", "DELETE"
+ "clear?confirm_message=I%27m+sure+to+delete+all+data",
+ "DELETE",
+ validator=ResponseValidation("text"),
)
- if check_if_success(response, NotFoundError(response.content)):
- return str(response.content)
- return ""
+ return response
@router.http("GET", "conf")
- def get_graph_config(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return str(response.content)
- return ""
+ def get_graph_config(self) -> dict:
+ return self._invoke_request(validator=ResponseValidation("text"))
diff --git a/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
b/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
index 3095642..e02e7fb 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
@@ -15,14 +15,13 @@
# specific language governing permissions and limitations
# under the License.
-import json
from pyhugegraph.api.common import HugeParamsBase
+from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.structure.gremlin_data import GremlinData
from pyhugegraph.structure.response_data import ResponseData
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
+from pyhugegraph.utils.log import log
class GremlinManager(HugeParamsBase):
@@ -30,18 +29,23 @@ class GremlinManager(HugeParamsBase):
@router.http("POST", "/gremlin")
def exec(self, gremlin):
gremlin_data = GremlinData(gremlin)
- if self._sess._cfg.gs_supported:
+ if self._sess.cfg.gs_supported:
gremlin_data.aliases = {
- "graph":
f"{self._sess._cfg.graphspace}-{self._sess._cfg.graph_name}",
- "g":
f"__g_{self._sess._cfg.graphspace}-{self._sess._cfg.graph_name}",
+ "graph":
f"{self._sess.cfg.graphspace}-{self._sess.cfg.graph_name}",
+ "g":
f"__g_{self._sess.cfg.graphspace}-{self._sess.cfg.graph_name}",
}
else:
gremlin_data.aliases = {
- "graph": f"{self._sess._cfg.graph_name}",
- "g": f"__g_{self._sess._cfg.graph_name}",
+ "graph": f"{self._sess.cfg.graph_name}",
+ "g": f"__g_{self._sess.cfg.graph_name}",
}
- response = self._invoke_request(data=gremlin_data.to_json())
- error = NotFoundError(f"Gremlin can't get results:
{str(response.content)}")
- if check_if_success(response, error):
- return ResponseData(json.loads(response.content)).result
- return ""
+
+ try:
+ if response := self._invoke_request(data=gremlin_data.to_json()):
+ return ResponseData(response).result
+ log.error( # pylint: disable=logging-fstring-interpolation
+ f"Gremlin can't get results: {str(response)}"
+ )
+ return None
+ except Exception as e:
+ raise NotFoundError(f"Gremlin can't get results: {e}") from e
diff --git a/hugegraph-python-client/src/pyhugegraph/api/metric.py
b/hugegraph-python-client/src/pyhugegraph/api/metric.py
index 9d27d52..27fd715 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/metric.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/metric.py
@@ -16,72 +16,43 @@
# under the License.
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
class MetricsManager(HugeParamsBase):
@router.http("GET", "/metrics/?type=json")
- def get_all_basic_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_all_basic_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/gauges")
- def get_gauges_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_gauges_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/counters")
- def get_counters_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_counters_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/gauges")
- def get_histograms_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_histograms_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/meters")
- def get_meters_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_meters_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/timers")
- def get_timers_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_timers_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/statistics/?type=json")
- def get_statistics_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_statistics_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/system")
- def get_system_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_system_metrics(self) -> dict:
+ return self._invoke_request()
@router.http("GET", "/metrics/backend")
- def get_backend_metrics(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get_backend_metrics(self) -> dict:
+ return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/api/rank.py
b/hugegraph-python-client/src/pyhugegraph/api/rank.py
new file mode 100644
index 0000000..ba7bb0b
--- /dev/null
+++ b/hugegraph-python-client/src/pyhugegraph/api/rank.py
@@ -0,0 +1,65 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+from pyhugegraph.utils import huge_router as router
+from pyhugegraph.structure.rank_data import (
+ PersonalRankParameters,
+ NeighborRankParameters,
+)
+from pyhugegraph.api.common import HugeParamsBase
+
+
+class RankManager(HugeParamsBase):
+ """
+ This class provides methods to interact with the rank APIs in
HugeGraphServer.
+ It allows for personalized recommendations based on graph traversal and
ranking algorithms.
+
+ Methods:
+ personal_rank(source, label, alpha=0.85, max_degree=10000, max_depth=5,
+ limit=100, sorted=True, with_label="BOTH_LABEL"):
+ Computes the Personal Rank for a given source vertex and edge
label.
+
+ neighbor_rank(source, steps, alpha=0.85, capacity=10000000):
+ Computes the Neighbor Rank for a given source vertex and defined
steps.
+ """
+
+ @router.http("POST", "traversers/personalrank")
+ def personal_rank(self, body_params: PersonalRankParameters):
+ """
+ Computes the Personal Rank for a given source vertex and edge label.
+
+ Args:
+ body_params (PersonalRankParameters): BodyParams defines the body
parameters for the rank API requests.
+
+ Returns:
+ dict: A dictionary containing the ranked list of vertices and
their corresponding rank values.
+ """
+ return self._invoke_request(data=body_params.dumps())
+
+ @router.http("POST", "traversers/neighborrank")
+ def neighbor_rank(self, body_params: NeighborRankParameters):
+ """
+ Computes the Neighbor Rank for a given source vertex and defined steps.
+
+ Args:
+ body_params (NeighborRankParameters): BodyParams defines the body
parameters for the rank API requests.
+
+ Returns:
+ dict: A dictionary containing the probability of reaching other
vertices from the source vertex.
+ """
+ return self._invoke_request(data=body_params.dumps())
diff --git a/hugegraph-python-client/src/pyhugegraph/api/rebuild.py
b/hugegraph-python-client/src/pyhugegraph/api/rebuild.py
new file mode 100644
index 0000000..e0b4d64
--- /dev/null
+++ b/hugegraph-python-client/src/pyhugegraph/api/rebuild.py
@@ -0,0 +1,87 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+from pyhugegraph.utils import huge_router as router
+from pyhugegraph.api.common import HugeParamsBase
+
+
+class RebuildManager(HugeParamsBase):
+ """
+ Manages the rebuilding of index, vertex, and edge labels via HTTP
endpoints.
+
+ Methods:
+ rebuild_indexlabels(indexlabel: str):
+ Rebuilds the specified IndexLabel. Returns a dictionary with the
task ID.
+
+ rebuild_vertexlabels(vertexlabel: str):
+ Rebuilds the specified VertexLabel. Returns a dictionary with the
task ID.
+
+ rebuild_edgelabels(edgelabel: str):
+ Rebuilds the specified EdgeLabel. Returns a dictionary with the
task ID.
+ """
+
+ @router.http("PUT", "jobs/rebuild/indexlabels/{indexlabel}")
+ def rebuild_indexlabels(self, indexlabel: str): # pylint:
disable=unused-argument
+ """
+ Rebuild IndexLabel.
+
+ Args:
+ indexlabel (str): Name of the indexlabel.
+
+ Returns:
+ dict: A dictionary containing the response from the HTTP request.
+ The structure of the response is as follows:
+ response = {
+ "task_id": 1 # Unique identifier for the task.
+ }
+ """
+ return self._invoke_request()
+
+ @router.http("PUT", "jobs/rebuild/vertexlabels/{vertexlabel}")
+ def rebuild_vertexlabels(self, vertexlabel: str): # pylint:
disable=unused-argument
+ """
+ Rebuild VertexLabel.
+
+ Args:
+ vertexlabel (str): Name of the vertexlabel.
+
+ Returns:
+ dict: A dictionary containing the response from the HTTP request.
+ The structure of the response is as follows:
+ response = {
+ "task_id": 1 # Unique identifier for the task.
+ }
+ """
+ return self._invoke_request()
+
+ @router.http("PUT", "jobs/rebuild/edgelabels/{edgelabel}")
+ def rebuild_edgelabels(self, edgelabel: str): # pylint:
disable=unused-argument
+ """
+ Rebuild EdgeLabel.
+
+ Args:
+ edgelabel (str): Name of the edgelabel.
+
+ Returns:
+ dict: A dictionary containing the response from the HTTP request.
+ The structure of the response is as follows:
+ response = {
+ "task_id": 1 # Unique identifier for the task.
+ }
+ """
+ return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/api/schema.py
b/hugegraph-python-client/src/pyhugegraph/api/schema.py
index ab92bc8..fdebc9d 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/schema.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/schema.py
@@ -16,9 +16,7 @@
# under the License.
-import json
-
-from typing import Optional, Dict
+from typing import Optional, Dict, List
from pyhugegraph.api.common import HugeParamsBase
from pyhugegraph.api.schema_manage.edge_label import EdgeLabel
from pyhugegraph.api.schema_manage.index_label import IndexLabel
@@ -28,9 +26,8 @@ from pyhugegraph.structure.edge_label_data import
EdgeLabelData
from pyhugegraph.structure.index_label_data import IndexLabelData
from pyhugegraph.structure.property_key_data import PropertyKeyData
from pyhugegraph.structure.vertex_label_data import VertexLabelData
-from pyhugegraph.utils.exceptions import NotFoundError
-from pyhugegraph.utils.util import check_if_success
from pyhugegraph.utils import huge_router as router
+from pyhugegraph.utils.log import log
class SchemaManager(HugeParamsBase):
@@ -66,93 +63,73 @@ class SchemaManager(HugeParamsBase):
index_label.add_parameter("name", name)
return index_label
- @router.http("GET", "schema?format={format}")
- def getSchema(self, format: str = "json") -> Optional[Dict]:
- response = self._invoke_request()
- error = NotFoundError(f"schema not found: {str(response.content)}")
- if check_if_success(response, error):
- schema = json.loads(response.content)
- return schema
- return None
+ @router.http("GET", "schema?format={_format}")
+ def getSchema(
+ self, _format: str = "json" # pylint: disable=unused-argument
+ ) -> Optional[Dict]:
+ return self._invoke_request()
@router.http("GET", "schema/propertykeys/{property_name}")
- def getPropertyKey(self, property_name):
- response = self._invoke_request()
- error = NotFoundError(f"PropertyKey not found:
{str(response.content)}")
- if check_if_success(response, error):
- property_keys_data = PropertyKeyData(json.loads(response.content))
- return property_keys_data
+ def getPropertyKey(
+ self, property_name # pylint: disable=unused-argument
+ ) -> Optional[PropertyKeyData]:
+ if response := self._invoke_request():
+ return PropertyKeyData(response)
return None
@router.http("GET", "schema/propertykeys")
- def getPropertyKeys(self):
- response = self._invoke_request()
- res = []
- if check_if_success(response):
- for item in json.loads(response.content)["propertykeys"]:
- res.append(PropertyKeyData(item))
- return res
+ def getPropertyKeys(self) -> Optional[List[PropertyKeyData]]:
+ if response := self._invoke_request():
+ return [PropertyKeyData(item) for item in response["propertykeys"]]
return None
@router.http("GET", "schema/vertexlabels/{name}")
- def getVertexLabel(self, name):
- response = self._invoke_request()
- error = NotFoundError(f"VertexLabel not found:
{str(response.content)}")
- if check_if_success(response, error):
- res = VertexLabelData(json.loads(response.content))
- return res
+ def getVertexLabel(
+ self, name # pylint: disable=unused-argument
+ ) -> Optional[VertexLabelData]:
+ if response := self._invoke_request():
+ return VertexLabelData(response)
+ log.error("VertexLabel not found: %s", str(response))
return None
@router.http("GET", "schema/vertexlabels")
- def getVertexLabels(self):
- response = self._invoke_request()
- res = []
- if check_if_success(response):
- for item in json.loads(response.content)["vertexlabels"]:
- res.append(VertexLabelData(item))
- return res
+ def getVertexLabels(self) -> Optional[List[VertexLabelData]]:
+ if response := self._invoke_request():
+ return [VertexLabelData(item) for item in response["vertexlabels"]]
+ return None
@router.http("GET", "schema/edgelabels/{label_name}")
- def getEdgeLabel(self, label_name: str):
- response = self._invoke_request()
- error = NotFoundError(f"EdgeLabel not found: {str(response.content)}")
- if check_if_success(response, error):
- res = EdgeLabelData(json.loads(response.content))
- return res
+ def getEdgeLabel(
+ self, label_name: str # pylint: disable=unused-argument
+ ) -> Optional[EdgeLabelData]:
+ if response := self._invoke_request():
+ return EdgeLabelData(response)
+ log.error("EdgeLabel not found: %s", str(response))
return None
@router.http("GET", "schema/edgelabels")
- def getEdgeLabels(self):
- response = self._invoke_request()
- res = []
- if check_if_success(response):
- for item in json.loads(response.content)["edgelabels"]:
- res.append(EdgeLabelData(item))
- return res
+ def getEdgeLabels(self) -> Optional[List[EdgeLabelData]]:
+ if response := self._invoke_request():
+ return [EdgeLabelData(item) for item in response["edgelabels"]]
+ return None
@router.http("GET", "schema/edgelabels")
- def getRelations(self):
- response = self._invoke_request()
- res = []
- if check_if_success(response):
- for item in json.loads(response.content)["edgelabels"]:
- res.append(EdgeLabelData(item).relations())
- return res
+ def getRelations(self) -> Optional[List[str]]:
+ if response := self._invoke_request():
+ return [EdgeLabelData(item).relations() for item in
response["edgelabels"]]
+ return None
@router.http("GET", "schema/indexlabels/{name}")
- def getIndexLabel(self, name):
- response = self._invoke_request()
- error = NotFoundError(f"EdgeLabel not found: {str(response.content)}")
- if check_if_success(response, error):
- res = IndexLabelData(json.loads(response.content))
- return res
+ def getIndexLabel(
+ self, name # pylint: disable=unused-argument
+ ) -> Optional[IndexLabelData]:
+ if response := self._invoke_request():
+ return IndexLabelData(response)
+ log.error("IndexLabel not found: %s", str(response))
return None
@router.http("GET", "schema/indexlabels")
- def getIndexLabels(self):
- response = self._invoke_request()
- res = []
- if check_if_success(response):
- for item in json.loads(response.content)["indexlabels"]:
- res.append(IndexLabelData(item))
- return res
+ def getIndexLabels(self) -> Optional[List[IndexLabelData]]:
+ if response := self._invoke_request():
+ return [IndexLabelData(item) for item in response["indexlabels"]]
+ return None
diff --git
a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/edge_label.py
b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/edge_label.py
index ee6b8c1..37b8564 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/edge_label.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/edge_label.py
@@ -18,9 +18,9 @@
import json
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import CreateError, UpdateError, RemoveError
+from pyhugegraph.utils.util import ResponseValidation
from pyhugegraph.utils.huge_decorator import decorator_params, decorator_create
-from pyhugegraph.utils.util import check_if_success, check_if_authorized
+from pyhugegraph.utils.log import log
class EdgeLabel(HugeParamsBase):
@@ -81,8 +81,7 @@ class EdgeLabel(HugeParamsBase):
@decorator_params
def ifNotExist(self) -> "EdgeLabel":
path = f'schema/edgelabels/{self._parameter_holder.get_value("name")}'
- response = self._sess.request(path)
- if response.status_code == 200 and check_if_authorized(response):
+ if _ := self._sess.request(path,
validator=ResponseValidation(strict=False)):
self._parameter_holder.set("not_exist", False)
return self
@@ -105,25 +104,19 @@ class EdgeLabel(HugeParamsBase):
if key in dic:
data[key] = dic[key]
path = "schema/edgelabels"
- response = self._sess.request(path, "POST", data=json.dumps(data))
self.clean_parameter_holder()
- error = CreateError(
- f'CreateError: "create EdgeLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'create EdgeLabel success, Detail:
"{str(response.content)}"'
+ if response := self._sess.request(path, "POST", data=json.dumps(data)):
+ return f'create EdgeLabel success, Detail: "{str(response)}"'
+ log.error(f'create EdgeLabel failed, Detail: "{str(response)}"')
return None
@decorator_params
def remove(self):
path = f'schema/edgelabels/{self._parameter_holder.get_value("name")}'
- response = self._sess.request(path, "DELETE")
self.clean_parameter_holder()
- error = RemoveError(
- f'RemoveError: "remove EdgeLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'remove EdgeLabel success, Detail:
"{str(response.content)}"'
+ if response := self._sess.request(path, "DELETE"):
+ return f'remove EdgeLabel success, Detail: "{str(response)}"'
+ log.error(f'remove EdgeLabel failed, Detail: "{str(response)}"')
return None
@decorator_params
@@ -136,13 +129,10 @@ class EdgeLabel(HugeParamsBase):
data[key] = dic[key]
path = f'schema/edgelabels/{data["name"]}?action=append'
- response = self._sess.request(path, "PUT", data=json.dumps(data))
self.clean_parameter_holder()
- error = UpdateError(
- f'UpdateError: "append EdgeLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'append EdgeLabel success, Detail:
"{str(response.content)}"'
+ if response := self._sess.request(path, "PUT", data=json.dumps(data)):
+ return f'append EdgeLabel success, Detail: "{str(response)}"'
+ log.error(f'append EdgeLabel failed, Detail: "{str(response)}"')
return None
@decorator_params
@@ -155,11 +145,8 @@ class EdgeLabel(HugeParamsBase):
)
path = f"schema/edgelabels/{name}?action=eliminate"
data = {"name": name, "user_data": user_data}
- response = self._sess.request(path, "PUT", data=json.dumps(data))
self.clean_parameter_holder()
- error = UpdateError(
- f'UpdateError: "eliminate EdgeLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'eliminate EdgeLabel success, Detail:
"{str(response.content)}"'
+ if response := self._sess.request(path, "PUT", data=json.dumps(data)):
+ return f'eliminate EdgeLabel success, Detail: "{str(response)}"'
+ log.error(f'eliminate EdgeLabel failed, Detail: "{str(response)}"')
return None
diff --git
a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/index_label.py
b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/index_label.py
index 09ee8aa..1437177 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/index_label.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/index_label.py
@@ -19,9 +19,9 @@ import json
from pyhugegraph.api.common import HugeParamsBase
+from pyhugegraph.utils.util import ResponseValidation
from pyhugegraph.utils.huge_decorator import decorator_params, decorator_create
-from pyhugegraph.utils.exceptions import CreateError, RemoveError
-from pyhugegraph.utils.util import check_if_authorized, check_if_success
+from pyhugegraph.utils.log import log
class IndexLabel(HugeParamsBase):
@@ -58,15 +58,24 @@ class IndexLabel(HugeParamsBase):
return self
@decorator_params
- def Search(self) -> "IndexLabel":
+ def search(self) -> "IndexLabel":
self._parameter_holder.set("index_type", "SEARCH")
return self
+ @decorator_params
+ def shard(self) -> "IndexLabel":
+ self._parameter_holder.set("index_type", "SHARD")
+ return self
+
+ @decorator_params
+ def unique(self) -> "IndexLabel":
+ self._parameter_holder.set("index_type", "UNIQUE")
+ return self
+
@decorator_params
def ifNotExist(self) -> "IndexLabel":
path = f'schema/indexlabels/{self._parameter_holder.get_value("name")}'
- response = self._sess.request(path)
- if response.status_code == 200 and check_if_authorized(response):
+ if _ := self._sess.request(path,
validator=ResponseValidation(strict=False)):
self._parameter_holder.set("not_exist", False)
return self
@@ -79,25 +88,19 @@ class IndexLabel(HugeParamsBase):
data["base_value"] = dic["base_value"]
data["index_type"] = dic["index_type"]
data["fields"] = list(dic["fields"])
- path = f"schema/indexlabels"
- response = self._sess.request(path, "POST", data=json.dumps(data))
+ path = "schema/indexlabels"
self.clean_parameter_holder()
- error = CreateError(
- f'CreateError: "create IndexLabel failed", Detail
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'create IndexLabel success, Deatil:
"{str(response.content)}"'
+ if response := self._sess.request(path, "POST", data=json.dumps(data)):
+ return f'create IndexLabel success, Detail: "{str(response)}"'
+ log.error(f'create IndexLabel failed, Detail: "{str(response)}"')
return None
@decorator_params
def remove(self):
name = self._parameter_holder.get_value("name")
path = f"schema/indexlabels/{name}"
- response = self._sess.request(path, "DELETE")
self.clean_parameter_holder()
- error = RemoveError(
- f'RemoveError: "remove IndexLabel failed", Detail
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'remove IndexLabel success, Deatil:
"{str(response.content)}"'
+ if response := self._sess.request(path, "DELETE"):
+ return f'remove IndexLabel success, Detail: "{str(response)}"'
+ log.error(f'remove IndexLabel failed, Detail: "{str(response)}"')
return None
diff --git
a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/property_key.py
b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/property_key.py
index e6a1765..38f5bd9 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/property_key.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/property_key.py
@@ -19,9 +19,9 @@ import json
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import CreateError, UpdateError, RemoveError
+from pyhugegraph.utils.util import ResponseValidation
from pyhugegraph.utils.huge_decorator import decorator_params, decorator_create
-from pyhugegraph.utils.util import check_if_success, check_if_authorized
+from pyhugegraph.utils.log import log
class PropertyKey(HugeParamsBase):
@@ -100,8 +100,7 @@ class PropertyKey(HugeParamsBase):
def ifNotExist(self) -> "PropertyKey":
path =
f'schema/propertykeys/{self._parameter_holder.get_value("name")}'
- response = self._sess.request(path)
- if response.status_code == 200 and check_if_authorized(response):
+ if _ := self._sess.request(path,
validator=ResponseValidation(strict=False)):
self._parameter_holder.set("not_exist", False)
return self
@@ -114,16 +113,10 @@ class PropertyKey(HugeParamsBase):
if "cardinality" in dic:
property_keys["cardinality"] = dic["cardinality"]
path = "schema/propertykeys"
- response = self._sess.request(path, "POST",
data=json.dumps(property_keys))
self.clean_parameter_holder()
- if check_if_success(
- response,
- CreateError(
- f'CreateError: "create PropertyKey failed", Detail:
{str(response.content)}'
- ),
- ):
- return f"create PropertyKey success, Detail:
{str(response.content)}"
- return f"create PropertyKey failed, Detail: {str(response.content)}"
+ if response := self._sess.request(path, "POST",
data=json.dumps(property_keys)):
+ return f"create PropertyKey success, Detail: {str(response)}"
+ log.error("create PropertyKey failed, Detail: %s", str(response))
@decorator_params
def append(self):
@@ -134,16 +127,10 @@ class PropertyKey(HugeParamsBase):
data = {"name": property_name, "user_data": user_data}
path = f"schema/propertykeys/{property_name}/?action=append"
- response = self._sess.request(path, "PUT", data=json.dumps(data))
self.clean_parameter_holder()
- if check_if_success(
- response,
- UpdateError(
- f'UpdateError: "append PropertyKey failed", Detail:
{str(response.content)}'
- ),
- ):
- return f"append PropertyKey success, Detail:
{str(response.content)}"
- return f"append PropertyKey failed, Detail: {str(response.content)}"
+ if response := self._sess.request(path, "PUT", data=json.dumps(data)):
+ return f"append PropertyKey success, Detail: {str(response)}"
+ log.error("append PropertyKey failed, Detail: %s", str(response))
@decorator_params
def eliminate(self):
@@ -154,26 +141,16 @@ class PropertyKey(HugeParamsBase):
data = {"name": property_name, "user_data": user_data}
path = f"schema/propertykeys/{property_name}/?action=eliminate"
- response = self._sess.request(path, "PUT", data=json.dumps(data))
self.clean_parameter_holder()
- error = UpdateError(
- f'UpdateError: "eliminate PropertyKey failed", Detail:
{str(response.content)}'
- )
- if check_if_success(response, error):
- return f"eliminate PropertyKey success, Detail:
{str(response.content)}"
- return f"eliminate PropertyKey failed, Detail: {str(response.content)}"
+ if response := self._sess.request(path, "PUT", data=json.dumps(data)):
+ return f"eliminate PropertyKey success, Detail: {str(response)}"
+ log.error("eliminate PropertyKey failed, Detail: %s", str(response))
@decorator_params
def remove(self):
dic = self._parameter_holder.get_dic()
path = f'schema/propertykeys/{dic["name"]}'
- response = self._sess.request(path, "DELETE")
self.clean_parameter_holder()
- if check_if_success(
- response,
- RemoveError(
- f'RemoveError: "delete PropertyKey failed", Detail:
{str(response.content)}'
- ),
- ):
- return f'delete PropertyKey success, Detail: {dic["name"]}'
- return f"delete PropertyKey failed, Detail: {str(response.content)}"
+ if response := self._sess.request(path, "DELETE"):
+ return f"delete PropertyKey success, Detail: {str(response)}"
+ log.error("delete PropertyKey failed, Detail: %s", str(response))
diff --git
a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/vertex_label.py
b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/vertex_label.py
index a9b49e8..610d214 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/schema_manage/vertex_label.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/schema_manage/vertex_label.py
@@ -18,9 +18,9 @@
import json
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import CreateError, UpdateError, RemoveError
+from pyhugegraph.utils.util import ResponseValidation
from pyhugegraph.utils.huge_decorator import decorator_params, decorator_create
-from pyhugegraph.utils.util import check_if_success, check_if_authorized
+from pyhugegraph.utils.log import log
class VertexLabel(HugeParamsBase):
@@ -78,8 +78,7 @@ class VertexLabel(HugeParamsBase):
def ifNotExist(self) -> "VertexLabel":
path =
f'schema/vertexlabels/{self._parameter_holder.get_value("name")}'
- response = self._sess.request(path)
- if response.status_code == 200 and check_if_authorized(response):
+ if _ := self._sess.request(path,
validator=ResponseValidation(strict=False)):
self._parameter_holder.set("not_exist", False)
return self
@@ -100,15 +99,11 @@ class VertexLabel(HugeParamsBase):
for key in key_list:
if key in dic:
data[key] = dic[key]
- path = f"schema/vertexlabels"
- response = self._sess.request(path, "POST", data=json.dumps(data))
+ path = "schema/vertexlabels"
self.clean_parameter_holder()
- error = CreateError(
- f'CreateError: "create VertexLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'create VertexLabel success, Detail:
"{str(response.content)}"'
- return f'create VertexLabel failed, Detail: "{str(response.content)}"'
+ if response := self._sess.request(path, "POST", data=json.dumps(data)):
+ return f'create VertexLabel success, Detail: "{str(response)}"'
+ log.error("create VertexLabel failed, Detail: %s", str(response))
@decorator_params
def append(self):
@@ -123,27 +118,19 @@ class VertexLabel(HugeParamsBase):
"nullable_keys": nullable_keys,
"user_data": user_data,
}
- response = self._sess.request(path, "PUT", data=json.dumps(data))
self.clean_parameter_holder()
- error = UpdateError(
- f'UpdateError: "append VertexLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'append VertexLabel success, Detail:
"{str(response.content)}"'
- return f'append VertexLabel failed, Detail: "{str(response.content)}"'
+ if response := self._sess.request(path, "PUT", data=json.dumps(data)):
+ return f'append VertexLabel success, Detail: "{str(response)}"'
+ log.error("append VertexLabel failed, Detail: %s", str(response))
@decorator_params
def remove(self):
name = self._parameter_holder.get_value("name")
path = f"schema/vertexlabels/{name}"
- response = self._sess.request(path, "DELETE")
self.clean_parameter_holder()
- error = RemoveError(
- f'RemoveError: "remove VertexLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'remove VertexLabel success, Detail:
"{str(response.content)}"'
- return f'remove VertexLabel failed, Detail: "{str(response.content)}"'
+ if response := self._sess.request(path, "DELETE"):
+ return f'remove VertexLabel success, Detail: "{str(response)}"'
+ log.error("remove VertexLabel failed, Detail: %s", str(response))
@decorator_params
def eliminate(self):
@@ -156,10 +143,6 @@ class VertexLabel(HugeParamsBase):
"name": self._parameter_holder.get_value("name"),
"user_data": user_data,
}
- response = self._sess.request(path, "PUT", data=json.dumps(data))
- error = UpdateError(
- f'UpdateError: "eliminate VertexLabel failed", Detail:
"{str(response.content)}"'
- )
- if check_if_success(response, error):
- return f'eliminate VertexLabel success, Detail:
"{str(response.content)}"'
- return f'eliminate VertexLabel failed, Detail:
"{str(response.content)}"'
+ if response := self._sess.request(path, "PUT", data=json.dumps(data)):
+ return f'eliminate VertexLabel success, Detail: "{str(response)}"'
+ log.error("eliminate VertexLabel failed, Detail: %s", str(response))
diff --git a/hugegraph-python-client/src/pyhugegraph/api/services.py
b/hugegraph-python-client/src/pyhugegraph/api/services.py
new file mode 100644
index 0000000..0c2c0a7
--- /dev/null
+++ b/hugegraph-python-client/src/pyhugegraph/api/services.py
@@ -0,0 +1,135 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+from pyhugegraph.utils import huge_router as router
+from pyhugegraph.api.common import HugeParamsBase
+from pyhugegraph.structure.services_data import ServiceCreateParameters
+
+
+class ServicesManager(HugeParamsBase):
+ """
+ Manages services within HugeGraph via HTTP endpoints.
+
+ This class acts as an interface to manage different types of services such
as online
+ transaction processing (OLTP) and online analytical processing (OLAP)
+ in HugeGraph. It currently supports dynamic creation and management of
services,
+ with a focus on OLTP services for now.
+
+ Methods:
+ create_service(graphspace: str, service_create_parameters:
ServiceCreateParameters):
+ Creates a new service within a specified graph space using the
provided parameters.
+ Returns a dictionary with the details of the created service.
+
+ list_services(graphspace: str):
+ Lists all services available within a specified graph space.
+ Returns a dictionary containing a list of service names.
+
+ get_service(graphspace: str, service: str):
+ Retrieves detailed information about a specific service within a
graph space.
+ Returns a dictionary with the service details.
+
+ delete_service(graphspace: str, service: str):
+ Deletes a specific service within a graph space after confirmation.
+ No return value expected; the operation's success is indicated by
an HTTP 204 status code.
+ """
+
+ @router.http("POST", "/graphspaces/{graphspace}/services")
+ def create_services(
+ self,
+ graphspace: str, # pylint: disable=unused-argument
+ body_params: ServiceCreateParameters,
+ ):
+ """
+ Create HugeGraph Servers.
+
+ Args:
+ service_create (ServiceCreate): The name of the service.
+
+ Returns:
+ dict: A dictionary containing the response from the HTTP request.
+ """
+ return self._invoke_request(data=body_params.dumps())
+
+ @router.http("GET", "/graphspaces/${graphspace}/services")
+ def list_services(self, graphspace: str): # pylint:
disable=unused-argument
+ """
+ List all services in a specified graph space..
+
+ Args:
+ graphspace (str): The name of the graph space to list services
from.
+
+
+ Response:
+ A list of service names in the specified graph space.
+
+ Returns:
+ dict: A dictionary containing the list of service names.
+ Example:
+ {
+ "services": ["service1", "service2"]
+ }
+ """
+ return self._invoke_request()
+
+ @router.http("GET", "/graphspaces/{graphspace}/services/{service}")
+ def get_service(
+ self, graphspace: str, service: str # pylint: disable=unused-argument
+ ):
+ """
+ Retrieve the details of a specific service.
+
+ Args:
+ graphspace (str): The name of the graph space where the service is
located.
+ service (str): The name of the service to retrieve details for.
+
+ Response:
+ A dictionary containing the details of the specified service.
+
+ Returns:
+ dict: A dictionary with the service details.
+ Example:
+ {
+ "name": "service1",
+ "description": "This is a description of service1.",
+ "type": "OLTP",
+ // ... other service details
+ }
+ """
+ return self._invoke_request()
+
+ def delete_service(
+ self, graphspace: str, service: str # pylint: disable=unused-argument
+ ):
+ """
+ Delete a specific service within a graph space.
+
+ Args:
+ graphspace (str): The name of the graph space where the service is
located.
+ service (str): The name of the service to be deleted.
+
+ Response:
+ 204
+
+ Returns:
+ None
+ """
+ return self._sess.request(
+ f"/graphspaces/{graphspace}/services/{service}"
+ f"?confirm_message=I'm sure to delete the service",
+ "DELETE",
+ )
diff --git a/hugegraph-python-client/src/pyhugegraph/api/task.py
b/hugegraph-python-client/src/pyhugegraph/api/task.py
index c31eb8d..0668eb0 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/task.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/task.py
@@ -16,9 +16,7 @@
# under the License.
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
class TaskManager(HugeParamsBase):
@@ -30,24 +28,16 @@ class TaskManager(HugeParamsBase):
params["status"] = status
if limit is not None:
params["limit"] = limit
- response = self._invoke_request(params=params)
- check_if_success(response, NotFoundError(response.content))
- return response.json()
+ return self._invoke_request(params=params)
@router.http("GET", "tasks/{task_id}")
- def get_task(self, task_id):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
- return response.json()
+ def get_task(self, task_id): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http("DELETE", "tasks/{task_id}")
- def delete_task(self, task_id):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
- return response.status_code
+ def delete_task(self, task_id): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http("PUT", "tasks/{task_id}?action=cancel")
- def cancel_task(self, task_id):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
- return response.json()
+ def cancel_task(self, task_id): # pylint: disable=unused-argument
+ return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/api/traverser.py
b/hugegraph-python-client/src/pyhugegraph/api/traverser.py
index 165178b..f9dbbb2 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/traverser.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/traverser.py
@@ -17,86 +17,71 @@
import json
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
class TraverserManager(HugeParamsBase):
@router.http("GET",
'traversers/kout?source="{source_id}"&max_depth={max_depth}')
- def k_out(self, source_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def k_out(self, source_id, max_depth): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http(
"GET",
'traversers/kneighbor?source="{source_id}"&max_depth={max_depth}'
)
- def k_neighbor(self, source_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def k_neighbor(self, source_id, max_depth): # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http(
"GET",
'traversers/sameneighbors?vertex="{vertex_id}"&other="{other_id}"'
)
- def same_neighbors(self, vertex_id, other_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def same_neighbors(self, vertex_id, other_id): # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http(
"GET",
'traversers/jaccardsimilarity?vertex="{vertex_id}"&other="{other_id}"'
)
- def jaccard_similarity(self, vertex_id, other_id):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def jaccard_similarity(
+ self, vertex_id, other_id # pylint: disable=unused-argument
+ ):
+ return self._invoke_request()
@router.http(
"GET",
'traversers/shortestpath?source="{source_id}"&target="{target_id}"&max_depth={max_depth}',
)
- def shortest_path(self, source_id, target_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def shortest_path(
+ self, source_id, target_id, max_depth # pylint:
disable=unused-argument
+ ):
+ return self._invoke_request()
@router.http(
"GET",
'traversers/allshortestpaths?source="{source_id}"&target="{target_id}"&max_depth={max_depth}',
)
- def all_shortest_paths(self, source_id, target_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def all_shortest_paths(
+ self, source_id, target_id, max_depth # pylint:
disable=unused-argument
+ ):
+ return self._invoke_request()
@router.http(
"GET",
-
'traversers/weightedshortestpath?source="{source_id}"&target="{target_id}"&weight={weight}&max_depth={max_depth}',
+
'traversers/weightedshortestpath?source="{source_id}"&target="{target_id}"'
+ "&weight={weight}&max_depth={max_depth}",
)
- def weighted_shortest_path(self, source_id, target_id, weight, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def weighted_shortest_path(
+ self, source_id, target_id, weight, max_depth # pylint:
disable=unused-argument
+ ):
+ return self._invoke_request()
@router.http(
"GET",
'traversers/singlesourceshortestpath?source="{source_id}"&max_depth={max_depth}',
)
- def single_source_shortest_path(self, source_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def single_source_shortest_path(
+ self, source_id, max_depth # pylint: disable=unused-argument
+ ):
+ return self._invoke_request()
@router.http("POST", "traversers/multinodeshortestpath")
def multi_node_shortest_path(
@@ -110,71 +95,67 @@ class TraverserManager(HugeParamsBase):
):
if properties is None:
properties = {}
- data = {
- "vertices": {"ids": vertices},
- "step": {"direction": direction, "properties": properties},
- "max_depth": max_depth,
- "capacity": capacity,
- "with_vertex": with_vertex,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "vertices": {"ids": vertices},
+ "step": {"direction": direction, "properties": properties},
+ "max_depth": max_depth,
+ "capacity": capacity,
+ "with_vertex": with_vertex,
+ }
+ )
+ )
@router.http(
"GET",
'traversers/paths?source="{source_id}"&target="{target_id}"&max_depth={max_depth}',
)
- def paths(self, source_id, target_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def paths(self, source_id, target_id, max_depth): # pylint:
disable=unused-argument
+ return self._invoke_request()
@router.http("POST", "traversers/customizedpaths")
def customized_paths(
self, sources, steps, sort_by="INCR", with_vertex=True, capacity=-1,
limit=-1
):
- data = {
- "sources": sources,
- "steps": steps,
- "sort_by": sort_by,
- "with_vertex": with_vertex,
- "capacity": capacity,
- "limit": limit,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "sources": sources,
+ "steps": steps,
+ "sort_by": sort_by,
+ "with_vertex": with_vertex,
+ "capacity": capacity,
+ "limit": limit,
+ }
+ )
+ )
@router.http("POST", "traversers/templatepaths")
def template_paths(
self, sources, targets, steps, capacity=10000, limit=10,
with_vertex=True
):
- data = {
- "sources": sources,
- "targets": targets,
- "steps": steps,
- "capacity": capacity,
- "limit": limit,
- "with_vertex": with_vertex,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "sources": sources,
+ "targets": targets,
+ "steps": steps,
+ "capacity": capacity,
+ "limit": limit,
+ "with_vertex": with_vertex,
+ }
+ )
+ )
@router.http(
"GET",
'traversers/crosspoints?source="{source_id}"&target="{target_id}"&max_depth={max_depth}',
)
- def crosspoints(self, source_id, target_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def crosspoints(
+ self, source_id, target_id, max_depth # pylint:
disable=unused-argument
+ ):
+ return self._invoke_request()
@router.http("POST", "traversers/customizedcrosspoints")
def customized_crosspoints(
@@ -186,32 +167,26 @@ class TraverserManager(HugeParamsBase):
capacity=-1,
limit=-1,
):
- data = {
- "sources": sources,
- "path_patterns": path_patterns,
- "with_path": with_path,
- "with_vertex": with_vertex,
- "capacity": capacity,
- "limit": limit,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "sources": sources,
+ "path_patterns": path_patterns,
+ "with_path": with_path,
+ "with_vertex": with_vertex,
+ "capacity": capacity,
+ "limit": limit,
+ }
+ )
+ )
@router.http("GET",
'traversers/rings?source="{source_id}"&max_depth={max_depth}')
- def rings(self, source_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def rings(self, source_id, max_depth): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http("GET",
'traversers/rays?source="{source_id}"&max_depth={max_depth}')
- def rays(self, source_id, max_depth):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def rays(self, source_id, max_depth): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http("POST", "traversers/fusiformsimilarity")
def fusiform_similarity(
@@ -231,39 +206,33 @@ class TraverserManager(HugeParamsBase):
with_intermediary=False,
with_vertex=True,
):
- data = {
- "sources": sources,
- "label": label,
- "direction": direction,
- "min_neighbors": min_neighbors,
- "alpha": alpha,
- "min_similars": min_similars,
- "top": top,
- "group_property": group_property,
- "min_groups": min_groups,
- "max_degree": max_degree,
- "capacity": capacity,
- "limit": limit,
- "with_intermediary": with_intermediary,
- "with_vertex": with_vertex,
- }
- response = self._invoke_request(data=json.dumps(data))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(
+ data=json.dumps(
+ {
+ "sources": sources,
+ "label": label,
+ "direction": direction,
+ "min_neighbors": min_neighbors,
+ "alpha": alpha,
+ "min_similars": min_similars,
+ "top": top,
+ "group_property": group_property,
+ "min_groups": min_groups,
+ "max_degree": max_degree,
+ "capacity": capacity,
+ "limit": limit,
+ "with_intermediary": with_intermediary,
+ "with_vertex": with_vertex,
+ }
+ )
+ )
@router.http("GET", "traversers/vertices")
def vertices(self, ids):
params = {"ids": f'"{ids}"'}
- response = self._invoke_request(params=params)
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(params=params)
@router.http("GET", "traversers/edges")
def edges(self, ids):
params = {"ids": ids}
- response = self._invoke_request(params=params)
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request(params=params)
diff --git a/hugegraph-python-client/src/pyhugegraph/api/variable.py
b/hugegraph-python-client/src/pyhugegraph/api/variable.py
index 4c060b9..c5fe840 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/variable.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/variable.py
@@ -18,35 +18,23 @@
import json
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
class VariableManager(HugeParamsBase):
@router.http("PUT", "variables/{key}")
- def set(self, key, value):
- response = self._invoke_request(data=json.dumps({"data": value}))
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def set(self, key, value): # pylint: disable=unused-argument
+ return self._invoke_request(data=json.dumps({"data": value}))
@router.http("GET", "variables/{key}")
- def get(self, key):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ def get(self, key): # pylint: disable=unused-argument
+ return self._invoke_request()
@router.http("GET", "variables")
def all(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request()
@router.http("DELETE", "variables/{key}")
- def remove(self, key):
- response = self._invoke_request()
- check_if_success(response, NotFoundError(response.content))
+ def remove(self, key): # pylint: disable=unused-argument
+ return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/api/version.py
b/hugegraph-python-client/src/pyhugegraph/api/version.py
index 23b5baf..3635c77 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/version.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/version.py
@@ -16,16 +16,11 @@
# under the License.
from pyhugegraph.api.common import HugeParamsBase
-from pyhugegraph.utils.exceptions import NotFoundError
from pyhugegraph.utils import huge_router as router
-from pyhugegraph.utils.util import check_if_success
class VersionManager(HugeParamsBase):
@router.http("GET", "/versions")
def version(self):
- response = self._invoke_request()
- if check_if_success(response, NotFoundError(response.content)):
- return response.json()
- return {}
+ return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/client.py
b/hugegraph-python-client/src/pyhugegraph/client.py
index fd776ff..b740e89 100644
--- a/hugegraph-python-client/src/pyhugegraph/client.py
+++ b/hugegraph-python-client/src/pyhugegraph/client.py
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
-from typing import Optional
+from typing import Any, Callable, Optional, TypeVar
from pyhugegraph.api.auth import AuthManager
from pyhugegraph.api.graph import GraphManager
@@ -30,13 +30,15 @@ from pyhugegraph.api.version import VersionManager
from pyhugegraph.utils.huge_config import HGraphConfig
from pyhugegraph.utils.huge_requests import HGraphSession
+T = TypeVar("T")
-def manager_builder(fn):
+
+def manager_builder(fn: Callable[[Any, "HGraphSession"], T]) ->
Callable[[Any], T]:
attr_name = "_lazy_" + fn.__name__
- def wrapper(self: "PyHugeClient"):
+ def wrapper(self: "PyHugeClient") -> T:
if not hasattr(self, attr_name):
- session = HGraphSession(self._cfg)
+ session = HGraphSession(self.cfg)
setattr(self, attr_name, fn(self)(session))
return getattr(self, attr_name)
@@ -51,10 +53,10 @@ class PyHugeClient:
graph: str,
user: str,
pwd: str,
+ graphspace: Optional[str] = None,
timeout: int = 10,
- gs: Optional[str] = None,
):
- self._cfg = HGraphConfig(ip, port, user, pwd, graph, gs, timeout)
+ self.cfg = HGraphConfig(ip, port, user, pwd, graph, graphspace,
timeout)
@manager_builder
def schema(self) -> "SchemaManager":
@@ -97,4 +99,4 @@ class PyHugeClient:
return VersionManager
def __repr__(self) -> str:
- return f"{self._cfg}"
+ return f"{self.cfg}"
diff --git a/hugegraph-python-client/src/pyhugegraph/structure/rank_data.py
b/hugegraph-python-client/src/pyhugegraph/structure/rank_data.py
new file mode 100644
index 0000000..e4adc32
--- /dev/null
+++ b/hugegraph-python-client/src/pyhugegraph/structure/rank_data.py
@@ -0,0 +1,96 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from typing import List, Union
+from dataclasses import asdict, dataclass, field
+
+
+@dataclass
+class NeighborRankStep:
+ """
+ Steps object defines the traversal path rules from the starting vertex.
+ """
+
+ direction: str = "BOTH"
+ labels: List[str] = field(default_factory=list)
+ max_degree: int = 10000
+ top: int = 100
+
+ def dumps(self):
+ return json.dumps(asdict(self))
+
+
+@dataclass
+class NeighborRankParameters:
+ """
+ BodyParams defines the body parameters for the rank API requests.
+ """
+
+ source: Union[str, int]
+ label: str
+ alpha: float = 0.85
+ capacity: int = 10000000
+ steps: List[NeighborRankStep] = field(default_factory=list)
+
+ def dumps(self):
+ return json.dumps(asdict(self))
+
+
+@dataclass
+class PersonalRankParameters:
+ """
+ Data class that represents the body parameters for a rank API request.
+
+ Attributes:
+ - source (Union[str, int]): The ID of the source vertex. This is a
required field with no default value.
+ - label (str): The label of the edge type that starts the traversal.
+ This is a required field with no default value.
+ - alpha (float): The probability of moving to a neighboring vertex in
each iteration,
+ similar to the alpha in PageRank. Optional with a
default value of 0.85.
+ The value should be in the range (0, 1].
+ - max_degree (int): The maximum number of adjacent edges a single vertex
can traverse
+ during the query process. Optional with a default
value of 10000.
+ The value should be greater than 0.
+ - max_depth (int): The maximum number of iterations for the traversal.
Optional with a default value of 5.
+ The value should be within the range [2, 50].
+ - limit (int): The maximum number of vertices to return in the results.
Optional with a default value of 100.
+ The value should be greater than 0.
+ - max_diff (float): The precision difference for early convergence (to
be implemented later).
+ Optional with a default value of 0.0001. The value
should be in the range (0, 1).
+ - sorted (bool): Indicates whether the results should be sorted based on
rank.
+ If true, the results are sorted in descending order of
rank;
+ if false, they are not sorted. Optional with a default
value of True.
+ - with_label (str): Determines which results to keep in the final output.
+ Optional with a default value of "BOTH_LABEL". The
options are "SAME_LABEL" to keep only
+ vertices of the same category as the source vertex,
"OTHER_LABEL" to keep only vertices
+ of a different category (the other end of a
bipartite graph), and "BOTH_LABEL" to keep both.
+ """
+
+ source: Union[str, int]
+ label: str
+ alpha: float = 0.85
+ max_degree: int = 10000
+ max_depth: int = 5
+ limit: int = 100
+ max_diff: float = 0.0001
+ sorted: bool = True
+ with_label: str = "BOTH_LABEL"
+
+ def dumps(self):
+ return json.dumps(asdict(self))
diff --git a/hugegraph-python-client/src/pyhugegraph/structure/services_data.py
b/hugegraph-python-client/src/pyhugegraph/structure/services_data.py
new file mode 100644
index 0000000..07b7a23
--- /dev/null
+++ b/hugegraph-python-client/src/pyhugegraph/structure/services_data.py
@@ -0,0 +1,62 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from typing import List, Optional
+from dataclasses import asdict, dataclass, field
+
+
+@dataclass
+class ServiceCreateParameters:
+ """
+ Data class representing the request body for HugeGraph services.
+
+ Attributes:
+ - name (str): The name of the service. It must consist of lowercase
letters, numbers, and underscores.
+ The first character must be a lowercase letter and the
length must not exceed 48.
+ - description (str): A description of the service.
+ - type (str): The type of service. Currently, only 'OLTP' is allowed.
Default is 'OLTP'.
+ - count (int): The number of HugeGraphServer instances. Must be greater
than 0. Default is 1.
+ - cpu_limit (int): The number of CPU cores per HugeGraphServer instance.
Must be greater than 0. Default is 1.
+ - memory_limit (int): The memory size per HugeGraphServer instance in
GB. Must be greater than 0. Default is 4.
+ - storage_limit (int): The disk size for HStore in GB. Must be greater
than 0. Default is 100.
+ - route_type (str): Required when deployment_type is 'K8S'.
+ Accepted values are 'ClusterIP', 'LoadBalancer',
'NodePort'.
+ - port (int): Required when deployment_type is 'K8S'. Must be greater
than 0.
+ Default is None and invalid for other deployment types.
+ - urls (List[str]): Required when deployment_type is 'MANUAL'.
+ Should not be provided for other deployment types.
+ - deployment_type (str): The deployment type of the service.
+ 'K8S' indicates service deployment through a
Kubernetes cluster,
+ 'MANUAL' indicates manual service deployment.
Default is an empty string.
+ """
+
+ name: str
+ description: str
+ type: str = "OLTP"
+ count: int = 1
+ cpu_limit: int = 1
+ memory_limit: int = 4
+ storage_limit: int = 100
+ route_type: Optional[str] = None
+ port: Optional[int] = None
+ urls: List[str] = field(default_factory=list)
+ deployment_type: Optional[str] = None
+
+ def dumps(self):
+ return json.dumps(asdict(self))
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
b/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
index f7e00d9..fc5e1c7 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
@@ -21,6 +21,7 @@ import traceback
from dataclasses import dataclass, field
from typing import Optional, List
+from pyhugegraph.utils.log import log
@dataclass
@@ -46,22 +47,24 @@ class HGraphConfig:
f"http://{self.ip}:{self.port}/versions", timeout=1
)
core = response.json()["versions"]["core"]
- print(f"Retrieved API version information from the server:
{core}.")
+ log.info( # pylint: disable=logging-fstring-interpolation
+ f"Retrieved API version information from the server:
{core}."
+ )
- match = re.search("(\d+)\.(\d+)(?:\.(\d+))?(?:\.\d+)?", core)
+ match = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?(?:\.\d+)?", core)
major, minor, patch = map(int, match.groups())
self.version.extend([major, minor, patch])
if major >= 3:
self.graphspace = "DEFAULT"
self.gs_supported = True
- print(
- f"graph space is not set, default value 'DEFAULT' will
be used."
+ log.warning(
+ "graph space is not set, default value 'DEFAULT' will
be used."
)
- except Exception as e:
+ except Exception as e: # pylint: disable=broad-exception-caught
traceback.print_exception(e)
self.gs_supported = False
- print(
+ log.warning(
"Failed to retrieve API version information from the
server, reverting to default v1."
)
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/huge_decorator.py
b/hugegraph-python-client/src/pyhugegraph/utils/huge_decorator.py
index ecc94bf..a6dbe89 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/huge_decorator.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/huge_decorator.py
@@ -17,7 +17,6 @@
from decorator import decorator
-
from pyhugegraph.utils.exceptions import NotAuthorizedError
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/huge_requests.py
b/hugegraph-python-client/src/pyhugegraph/utils/huge_requests.py
index 83ecece..e983fcb 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/huge_requests.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/huge_requests.py
@@ -15,14 +15,17 @@
# specific language governing permissions and limitations
# under the License.
-import requests
-from requests.adapters import HTTPAdapter
+
from urllib3.util.retry import Retry
from urllib.parse import urljoin
-
from typing import Any, Optional
+from requests.adapters import HTTPAdapter
from pyhugegraph.utils.constants import Constants
from pyhugegraph.utils.huge_config import HGraphConfig
+from pyhugegraph.utils.util import ResponseValidation
+from pyhugegraph.utils.log import log
+
+import requests
class HGraphSession:
@@ -66,11 +69,25 @@ class HGraphSession:
self._session.mount("http://", adapter)
self._session.mount("https://", adapter)
self._session.keep_alive = False
- # logger.debug(
- # "Session configured with retries=%s and backoff_factor=%s",
- # self.retries,
- # self.backoff_factor,
- # )
+ log.debug(
+ "Session configured with retries=%s and backoff_factor=%s",
+ self._retries,
+ self._backoff_factor,
+ )
+
+ @property
+ def cfg(self):
+ """
+ Get the configuration information of the current instance.
+
+ Args:
+ None.
+
+ Returns:
+ -------
+ HGraphConfig: The configuration information of the current
instance.
+ """
+ return self._cfg
def resolve(self, path: str):
"""
@@ -80,7 +97,8 @@ class HGraphSession:
:return: The fully resolved URL as a string.
When path is "/some/things":
- - Since path starts with "/", it is considered an absolute path, and
urljoin will replace the path part of the base URL.
+ - Since path starts with "/", it is considered an absolute path,
+ and urljoin will replace the path part of the base URL.
- Assuming the base URL is
"http://127.0.0.1:8000/graphspaces/default/graphs/test_graph/"
- The result will be "http://127.0.0.1:8000/some/things"
@@ -101,18 +119,34 @@ class HGraphSession:
return urljoin(url, path).strip("/")
def close(self):
+ """
+ closes the session.
+
+ Args:
+ None
+
+ Returns:
+ None
+
+ """
self._session.close()
- def request(self, path: str, method: str = "GET", **kwargs: Any):
- try:
- # print(method, self.resolve(path))
- response = getattr(self._session, method.lower())(
- self.resolve(path),
- auth=self._auth,
- headers=self._headers,
- timeout=self._timeout,
- **kwargs,
- )
- return response
- except requests.RequestException as e:
- raise
+ def request(
+ self,
+ path: str,
+ method: str = "GET",
+ validator=ResponseValidation(),
+ **kwargs: Any,
+ ) -> dict:
+ url = self.resolve(path)
+ response: requests.Response = getattr(self._session, method.lower())(
+ url,
+ auth=self._auth,
+ headers=self._headers,
+ timeout=self._timeout,
+ **kwargs,
+ )
+ log.debug( # pylint: disable=logging-fstring-interpolation
+ f"Request: {method} {url} validator={validator} kwargs={kwargs}
{response}"
+ )
+ return validator(response, method=method, path=path)
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/huge_router.py
b/hugegraph-python-client/src/pyhugegraph/utils/huge_router.py
index 08f9311..0db81ed 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/huge_router.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/huge_router.py
@@ -16,19 +16,21 @@
# under the License.
import re
-import json
import inspect
import functools
import threading
-from abc import ABC
-from typing import Any, Callable, Dict, TYPE_CHECKING
+from dataclasses import dataclass
+from typing import Any, Callable, Dict, Optional, TYPE_CHECKING
+from pyhugegraph.utils.log import log
+from pyhugegraph.utils.util import ResponseValidation
+
if TYPE_CHECKING:
from pyhugegraph.api.common import HGraphContext
-class SingletonBase(type):
+class SingletonMeta(type):
_instances = {}
_lock = threading.Lock()
@@ -44,18 +46,50 @@ class SingletonBase(type):
return cls._instances[cls]
-class HGraphRouterManager(metaclass=SingletonBase):
+@dataclass
+class Route:
+ method: str
+ path: str
+ request_func: Optional[Callable] = None
+
+
+class RouterRegistry(metaclass=SingletonMeta):
def __init__(self):
- self._routers = {}
+ self._routers: Dict[str, Route] = {}
- def register(self, key, path):
- self._routers.update({key: path})
+ def register(self, key: str, route: Route):
+ self._routers[key] = route
- def get_routers(self):
+ @property
+ def routers(self):
return self._routers
def __repr__(self) -> str:
- return json.dumps(self._routers, indent=4)
+ return str(self._routers)
+
+
+def register(method: str, path: str) -> Callable:
+
+ def decorator(func: Callable) -> Callable:
+ RouterRegistry().register(
+ func.__qualname__,
+ Route(method, path),
+ )
+
+ @functools.wraps(func)
+ def wrapper(self: "HGraphContext", *args: Any, **kwargs: Any) -> Any:
+ route = RouterRegistry().routers.get(func.__qualname__)
+
+ if route.request_func is None:
+ route.request_func = functools.partial(
+ self.session.request, method=method
+ )
+
+ return func(self, *args, **kwargs)
+
+ return wrapper
+
+ return decorator
def http(method: str, path: str) -> Callable:
@@ -72,7 +106,7 @@ def http(method: str, path: str) -> Callable:
def decorator(func: Callable) -> Callable:
"""Decorator function that modifies the original function."""
- HGraphRouterManager().register(func.__qualname__, (method, path))
+ RouterRegistry().register(func.__qualname__, Route(method, path))
@functools.wraps(func)
def wrapper(self: "HGraphContext", *args: Any, **kwargs: Any) -> Any:
@@ -99,10 +133,10 @@ def http(method: str, path: str) -> Callable:
else:
formatted_path = path
- # todo: If possible, reduce unnecessary multiple creations.
- cache_key = (func.__qualname__, method, formatted_path)
# Use functools.partial to create a partial function for making
requests
- make_request = functools.partial(self._sess.request,
formatted_path, method)
+ make_request = functools.partial(
+ self.session.request, formatted_path, method
+ )
# Store the partial function on the instance
setattr(self, f"_{func.__name__}_request", make_request)
@@ -113,9 +147,35 @@ def http(method: str, path: str) -> Callable:
return decorator
-class HGraphRouter(ABC):
+class RouterMixin:
+
+ def _invoke_request_registered(
+ self, placeholders: dict = None, validator=ResponseValidation(),
**kwargs: Any
+ ):
+ """
+ Make an HTTP request using the stored partial request function.
+ Args:
+ **kwargs (Any): Keyword arguments to be passed to the request
function.
+ Returns:
+ Any: The response from the HTTP request.
+ """
+ frame = inspect.currentframe().f_back
+ fname = frame.f_code.co_name
+ route =
RouterRegistry().routers.get(f"{self.__class__.__name__}.{fname}")
+
+ if re.search(r"{\w+}", route.path):
+ assert placeholders is not None, "Placeholders must be provided"
+ formatted_path = route.path.format(**placeholders)
+ else:
+ formatted_path = route.path
+
+ log.debug( # pylint: disable=logging-fstring-interpolation
+ f"Invoke request registered with router: {route.method}: "
+ f"{self.__class__.__name__}.{fname}: {formatted_path}"
+ )
+ return route.request_func(formatted_path, validator=validator,
**kwargs)
- def _invoke_request(self, **kwargs: Any):
+ def _invoke_request(self, validator=ResponseValidation(), **kwargs: Any):
"""
Make an HTTP request using the stored partial request function.
@@ -127,4 +187,7 @@ class HGraphRouter(ABC):
"""
frame = inspect.currentframe().f_back
fname = frame.f_code.co_name
- return getattr(self, f"_{fname}_request")(**kwargs)
+ log.debug( # pylint: disable=logging-fstring-interpolation
+ f"Invoke request: {str(self.__class__.__name__)}.{fname}"
+ )
+ return getattr(self, f"_{fname}_request")(validator=validator,
**kwargs)
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/log.py
b/hugegraph-python-client/src/pyhugegraph/utils/log.py
index 18dac65..2f5153f 100755
--- a/hugegraph-python-client/src/pyhugegraph/utils/log.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/log.py
@@ -31,10 +31,12 @@ def init_log(log_file="logs/output.log"):
# Create a logger
log = logging.getLogger(__name__)
- log.setLevel(logging.DEBUG)
+ log.setLevel(logging.INFO)
# Create a handler for writing to log file
- file_handler = TimedRotatingFileHandler(log_file, when='midnight',
interval=1, backupCount=3, encoding='utf-8')
+ file_handler = TimedRotatingFileHandler(
+ log_file, when="midnight", interval=1, backupCount=3, encoding="utf-8"
+ )
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(LOG_FORMAT,
datefmt=DATE_FORMAT))
log.addHandler(file_handler)
@@ -46,7 +48,7 @@ def init_log(log_file="logs/output.log"):
"INFO": "\033[0;32m", # Green
"WARNING": "\033[0;33m", # Yellow
"ERROR": "\033[0;31m", # Red
- "CRITICAL": "\033[0;41m" # Red background
+ "CRITICAL": "\033[0;41m", # Red background
}
def emit(self, record):
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/util.py
b/hugegraph-python-client/src/pyhugegraph/utils/util.py
index 5ca36b3..256b672 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/util.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/util.py
@@ -16,12 +16,15 @@
# under the License.
import json
+import traceback
+import requests
from pyhugegraph.utils.exceptions import (
ServiceUnavailableException,
NotAuthorizedError,
NotFoundError,
)
+from pyhugegraph.utils.log import log
def create_exception(response_content):
@@ -60,3 +63,68 @@ def check_if_success(response, error=None):
)
raise error
return True
+
+
+class ResponseValidation:
+ def __init__(self, content_type: str = "json", strict: bool = True) ->
None:
+ super().__init__()
+ self._content_type = content_type
+ self._strict = strict
+
+ def __call__(self, response: requests.Response, method: str, path: str):
+ """
+ Validate the HTTP response according to the provided content type and
strictness.
+
+ :param response: HTTP response object
+ :param method: HTTP method used (e.g., 'GET', 'POST')
+ :param path: URL path of the request
+ :return: Parsed response content or empty dict if none applicable
+ """
+ result = {}
+
+ try:
+ response.raise_for_status()
+
+ if response.status_code == 204:
+ log.debug("No content returned (204) for %s: %s", method, path)
+ else:
+ if self._content_type == "raw":
+ result = response
+ elif self._content_type == "json":
+ result = response.json()
+ elif self._content_type == "text":
+ result = response.text
+ else:
+ raise ValueError(f"Unknown content type:
{self._content_type}")
+
+ except requests.exceptions.HTTPError as e:
+ if not self._strict and response.status_code == 404:
+ log.info( # pylint: disable=logging-fstring-interpolation
+ f"Resource {path} not found (404)"
+ )
+ else:
+ try:
+ details = response.json().get(
+ "exception", "key 'exception' not found"
+ )
+ except (ValueError, KeyError):
+ details = "key 'exception' not found"
+
+ log.error( # pylint: disable=logging-fstring-interpolation
+ f"{method}: {e}; Server Exception: {details}"
+ )
+
+ if response.status_code == 404:
+ raise NotFoundError(response.content) from e
+
+ raise e
+
+ except Exception: # pylint: disable=broad-exception-caught
+ log.error( # pylint: disable=logging-fstring-interpolation
+ f"Unhandled exception occurred: {traceback.format_exc()}"
+ )
+
+ return result
+
+ def __repr__(self) -> str:
+ return f"ResponseValidation(content_type={self._content_type},
strict={self._strict})"
diff --git a/style/pylint.conf b/style/pylint.conf
index 80ebc61..ed69982 100644
--- a/style/pylint.conf
+++ b/style/pylint.conf
@@ -447,7 +447,8 @@ disable=raw-checker-failed,
R0902, # Too many instance attributes (11/7)
R1725, # Consider using Python 3 style super() without arguments
(super-with-arguments)
W0622, # Redefining built-in 'id' (redefined-builtin)
- R0904 # Too many public methods (27/20) (too-many-public-methods)
+ R0904, # Too many public methods (27/20) (too-many-public-methods)
+ E1120 # TODO: unbound-method-call-no-value-for-parameter
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option