This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hugegraph-ai.git


The following commit(s) were added to refs/heads/main by this push:
     new c79713d7 refactor(client): upgrade pyclient from 1.3.0 to 1.7.0 (#320)
c79713d7 is described below

commit c79713d71182619442c5b9d01460d3937e9a1a04
Author: Moavia Amir <[email protected]>
AuthorDate: Tue May 5 11:46:31 2026 +0500

    refactor(client): upgrade pyclient from 1.3.0 to 1.7.0 (#320)
    
    - Update server image to `hugegraph/hugegraph:1.7.0`
    - Migrate CI from manual docker run to GitHub service containers
    - Add health check for reliable startup verification
    - Remove legacy version gates for `/metrics/system` endpoint
    - Add version guard to reject servers older than `1.5.0`
    - Fix exception handling to prevent RuntimeError from being silently
    swallowed
    - Remove TestSystemMetricsVersionGate test class (no longer needed)
    
    ---------
    
    Co-authored-by: imbajin <[email protected]>
---
 .github/workflows/check-dependencies.yml           |  1 +
 .github/workflows/hugegraph-python-client.yml      | 15 ++--
 .../src/pyhugegraph/api/auth.py                    | 87 ++++++++++++----------
 .../src/pyhugegraph/api/graphs.py                  |  8 +-
 .../src/pyhugegraph/api/gremlin.py                 |  6 ++
 .../pyhugegraph/api/schema_manage/edge_label.py    | 12 +++
 .../src/pyhugegraph/structure/gremlin_data.py      |  9 ++-
 .../src/pyhugegraph/utils/huge_config.py           | 25 ++++++-
 hugegraph-python-client/src/tests/api/test_auth.py | 14 +++-
 .../src/tests/api/test_gremlin.py                  | 50 ++++++++++++-
 .../src/tests/api/test_metric.py                   | 30 ++------
 .../src/tests/api/test_traverser.py                | 31 ++++----
 12 files changed, 196 insertions(+), 92 deletions(-)

diff --git a/.github/workflows/check-dependencies.yml 
b/.github/workflows/check-dependencies.yml
index 83cd71eb..4796c2bc 100644
--- a/.github/workflows/check-dependencies.yml
+++ b/.github/workflows/check-dependencies.yml
@@ -13,6 +13,7 @@ jobs:
       - name: 'Checkout Repository'
         uses: actions/checkout@v4
       - name: 'Dependency Review'
+        if: github.event.pull_request.head.repo.full_name == github.repository
         uses: actions/dependency-review-action@v4
         # Refer: https://github.com/actions/dependency-review-action
         with:
diff --git a/.github/workflows/hugegraph-python-client.yml 
b/.github/workflows/hugegraph-python-client.yml
index 886c09ee..5dcf27c1 100644
--- a/.github/workflows/hugegraph-python-client.yml
+++ b/.github/workflows/hugegraph-python-client.yml
@@ -15,13 +15,16 @@ jobs:
       matrix:
         python-version: ["3.10", "3.11", "3.12"]
 
-    steps:
-      # TODO: upgrade to HugeGraph 1.5.0 (need to update the test cases)
-      - name: Prepare HugeGraph Server Environment
-        run: |
-          docker run -d --name=graph -p 8080:8080 -e PASSWORD=admin 
hugegraph/hugegraph:1.3.0
-          sleep 10
+    services:
+      hugegraph:
+        image: hugegraph/hugegraph:1.7.0
+        env:
+          PASSWORD: admin
+        options: --health-cmd="curl -f http://localhost:8080/versions || exit 
1" --health-interval=10s --health-timeout=5s --health-retries=5
+        ports:
+          - 8080:8080
 
+    steps:
       - uses: actions/checkout@v4
 
       - name: Set up Python ${{ matrix.python-version }}
diff --git a/hugegraph-python-client/src/pyhugegraph/api/auth.py 
b/hugegraph-python-client/src/pyhugegraph/api/auth.py
index 7d7e7499..16121474 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/auth.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/auth.py
@@ -22,13 +22,19 @@ from pyhugegraph.api.common import HugeParamsBase
 from pyhugegraph.utils import huge_router as router
 
 
+# NOTE: Auth endpoints currently use absolute paths (/auth/...) which rely on a
+# temporary PathFilter compatibility layer in HugeGraph 1.7.0. This layer will 
be
+# removed in future versions. When it is removed, these paths should be 
converted
+# to relative paths (auth/...) with proper graphspace-scoped routing for 
non-group
+# endpoints, similar to the Java Client's dual-path strategy.
+# See: apache/hugegraph-ai#322 (HugeGraph 1.7.0 auth API migration)
 class AuthManager(HugeParamsBase):
-    @router.http("GET", "auth/users")
+    @router.http("GET", "/auth/users")
     def list_users(self, limit=None):
         params = {"limit": limit} if limit is not None else {}
         return self._invoke_request(params=params)
 
-    @router.http("POST", "auth/users")
+    @router.http("POST", "/auth/users")
     def create_user(self, user_name, user_password, user_phone=None, 
user_email=None) -> dict | None:
         return self._invoke_request(
             data=json.dumps(
@@ -41,14 +47,14 @@ class AuthManager(HugeParamsBase):
             )
         )
 
-    @router.http("DELETE", "auth/users/{user_id}")
-    def delete_user(self, user_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("DELETE", "/auth/users/{user_id}")
+    def delete_user(self, user_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("PUT", "auth/users/{user_id}")
+    @router.http("PUT", "/auth/users/{user_id}")
     def modify_user(
         self,
-        user_id,  # pylint: disable=unused-argument
+        user_id,
         user_name=None,
         user_password=None,
         user_phone=None,
@@ -65,39 +71,39 @@ class AuthManager(HugeParamsBase):
             )
         )
 
-    @router.http("GET", "auth/users/{user_id}")
-    def get_user(self, user_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("GET", "/auth/users/{user_id}")
+    def get_user(self, user_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("GET", "auth/groups")
+    @router.http("GET", "/auth/groups")
     def list_groups(self, limit=None) -> dict | None:
         params = {"limit": limit} if limit is not None else {}
         return self._invoke_request(params=params)
 
-    @router.http("POST", "auth/groups")
+    @router.http("POST", "/auth/groups")
     def create_group(self, group_name, group_description=None) -> dict | None:
         data = {"group_name": group_name, "group_description": 
group_description}
         return self._invoke_request(data=json.dumps(data))
 
-    @router.http("DELETE", "auth/groups/{group_id}")
-    def delete_group(self, group_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("DELETE", "/auth/groups/{group_id}")
+    def delete_group(self, group_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("PUT", "auth/groups/{group_id}")
+    @router.http("PUT", "/auth/groups/{group_id}")
     def modify_group(
         self,
-        group_id,  # pylint: disable=unused-argument
+        group_id,
         group_name=None,
         group_description=None,
     ) -> dict | None:
         data = {"group_name": group_name, "group_description": 
group_description}
         return self._invoke_request(data=json.dumps(data))
 
-    @router.http("GET", "auth/groups/{group_id}")
-    def get_group(self, group_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("GET", "/auth/groups/{group_id}")
+    def get_group(self, group_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("POST", "auth/accesses")
+    @router.http("POST", "/auth/accesses")
     def grant_accesses(self, group_id, target_id, access_permission) -> dict | 
None:
         return self._invoke_request(
             data=json.dumps(
@@ -109,25 +115,24 @@ class AuthManager(HugeParamsBase):
             )
         )
 
-    @router.http("DELETE", "auth/accesses/{access_id}")
-    def revoke_accesses(self, access_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("DELETE", "/auth/accesses/{access_id}")
+    def revoke_accesses(self, access_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("PUT", "auth/accesses/{access_id}")
-    def modify_accesses(self, access_id, access_description) -> dict | None:  
# pylint: disable=unused-argument
-        # The permission of access can\'t be updated
+    @router.http("PUT", "/auth/accesses/{access_id}")
+    def modify_accesses(self, access_id, access_description) -> dict | None:
         data = {"access_description": access_description}
         return self._invoke_request(data=json.dumps(data))
 
-    @router.http("GET", "auth/accesses/{access_id}")
-    def get_accesses(self, access_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("GET", "/auth/accesses/{access_id}")
+    def get_accesses(self, access_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("GET", "auth/accesses")
+    @router.http("GET", "/auth/accesses")
     def list_accesses(self) -> dict | None:
         return self._invoke_request()
 
-    @router.http("POST", "auth/targets")
+    @router.http("POST", "/auth/targets")
     def create_target(self, target_name, target_graph, target_url, 
target_resources) -> dict | None:
         return self._invoke_request(
             data=json.dumps(
@@ -140,14 +145,14 @@ class AuthManager(HugeParamsBase):
             )
         )
 
-    @router.http("DELETE", "auth/targets/{target_id}")
-    def delete_target(self, target_id) -> None:  # pylint: 
disable=unused-argument
+    @router.http("DELETE", "/auth/targets/{target_id}")
+    def delete_target(self, target_id) -> None:
         return self._invoke_request()
 
-    @router.http("PUT", "auth/targets/{target_id}")
+    @router.http("PUT", "/auth/targets/{target_id}")
     def update_target(
         self,
-        target_id,  # pylint: disable=unused-argument
+        target_id,
         target_name,
         target_graph,
         target_url,
@@ -164,32 +169,32 @@ class AuthManager(HugeParamsBase):
             )
         )
 
-    @router.http("GET", "auth/targets/{target_id}")
-    def get_target(self, target_id, response=None) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("GET", "/auth/targets/{target_id}")
+    def get_target(self, target_id, response=None) -> dict | None:
         return self._invoke_request()
 
-    @router.http("GET", "auth/targets")
+    @router.http("GET", "/auth/targets")
     def list_targets(self) -> dict | None:
         return self._invoke_request()
 
-    @router.http("POST", "auth/belongs")
+    @router.http("POST", "/auth/belongs")
     def create_belong(self, user_id, group_id) -> dict | None:
         data = {"user": user_id, "group": group_id}
         return self._invoke_request(data=json.dumps(data))
 
-    @router.http("DELETE", "auth/belongs/{belong_id}")
-    def delete_belong(self, belong_id) -> None:  # pylint: 
disable=unused-argument
+    @router.http("DELETE", "/auth/belongs/{belong_id}")
+    def delete_belong(self, belong_id) -> None:
         return self._invoke_request()
 
-    @router.http("PUT", "auth/belongs/{belong_id}")
-    def update_belong(self, belong_id, description) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("PUT", "/auth/belongs/{belong_id}")
+    def update_belong(self, belong_id, description) -> dict | None:
         data = {"belong_description": description}
         return self._invoke_request(data=json.dumps(data))
 
-    @router.http("GET", "auth/belongs/{belong_id}")
-    def get_belong(self, belong_id) -> dict | None:  # pylint: 
disable=unused-argument
+    @router.http("GET", "/auth/belongs/{belong_id}")
+    def get_belong(self, belong_id) -> dict | None:
         return self._invoke_request()
 
-    @router.http("GET", "auth/belongs")
+    @router.http("GET", "/auth/belongs")
     def list_belongs(self) -> dict | None:
         return self._invoke_request()
diff --git a/hugegraph-python-client/src/pyhugegraph/api/graphs.py 
b/hugegraph-python-client/src/pyhugegraph/api/graphs.py
index cde2acfe..36bacdf9 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/graphs.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/graphs.py
@@ -36,7 +36,13 @@ class GraphsManager(HugeParamsBase):
         return self._invoke_request(validator=ResponseValidation("text"))
 
     def clear_graph_all_data(self) -> dict:
-        if self._sess.cfg.gs_supported:
+        # Use PUT with an action body for HugeGraph 3.x+ (graph clear API)
+        # For HugeGraph 1.7.0 and other 1.x versions, the clear endpoint uses
+        # DELETE .../clear?confirm_message=... even when graphspace prefixes
+        # are enabled. Only use the PUT behavior when the server version is
+        # >= 3.0.0.
+        version_tuple = tuple(self._sess.cfg.version) if 
self._sess.cfg.version else (0, 0, 0)
+        if self._sess.cfg.gs_supported and version_tuple >= (3, 0, 0):
             response = self._sess.request(
                 "",
                 "PUT",
diff --git a/hugegraph-python-client/src/pyhugegraph/api/gremlin.py 
b/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
index 3fa79368..7d0b8af1 100644
--- a/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
+++ b/hugegraph-python-client/src/pyhugegraph/api/gremlin.py
@@ -28,12 +28,18 @@ class GremlinManager(HugeParamsBase):
     @router.http("POST", "/gremlin")
     def exec(self, gremlin):
         gremlin_data = GremlinData(gremlin)
+
+        # Version-specific gremlin request handling
         if self._sess.cfg.gs_supported:
+            # For graphspace-supported versions, use graphspace-scoped aliases.
+            # This includes HugeGraph 1.7.0+ when graphspace support is 
enabled.
             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}",
             }
         else:
+            # For HugeGraph versions without graphspace support, always 
include aliases
+            # so `g` is bound.
             gremlin_data.aliases = {
                 "graph": f"{self._sess.cfg.graph_name}",
                 "g": f"__g_{self._sess.cfg.graph_name}",
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 932a819a..cbebeadf 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
@@ -95,6 +95,16 @@ class EdgeLabel(HugeParamsBase):
         self._parameter_holder.set("enable_label_index", flag)
         return self
 
+    @decorator_params
+    def parent(self, parent_label) -> "EdgeLabel":
+        """
+        Set parent edge label for supporting parent & child edge label type 
(HugeGraph 1.7.0+).
+        When an edge label has a parent, it becomes a child edge label with 
inherited properties.
+        """
+        self._parameter_holder.set("parent_label", parent_label)
+        self._parameter_holder.set("edgelabel_type", "SUB")
+        return self
+
     @decorator_create
     def create(self):
         dic = self._parameter_holder.get_dic()
@@ -109,6 +119,8 @@ class EdgeLabel(HugeParamsBase):
             "sort_keys",
             "user_data",
             "frequency",
+            "parent_label",  # Support parent & child edge label type 
(HugeGraph 1.7.0+)
+            "edgelabel_type",  # Required when parent_label is set (PARENT or 
SUB)
         ]
         for key in keys:
             if key in dic:
diff --git a/hugegraph-python-client/src/pyhugegraph/structure/gremlin_data.py 
b/hugegraph-python-client/src/pyhugegraph/structure/gremlin_data.py
index 81068bf9..d98a512a 100644
--- a/hugegraph-python-client/src/pyhugegraph/structure/gremlin_data.py
+++ b/hugegraph-python-client/src/pyhugegraph/structure/gremlin_data.py
@@ -70,4 +70,11 @@ class GremlinData:
 
 class GremlinDataEncoder(json.JSONEncoder):
     def default(self, o):
-        return {k.split("__")[1]: v for k, v in vars(o).items()}
+        data = {}
+        for k, v in vars(o).items():
+            # Filter out None values only; keep empty collections as server 
may expect them
+            if v is None:
+                continue
+            key = k.split("__")[1]
+            data[key] = v
+        return data
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py 
b/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
index 69c8949f..ef31cc8b 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/huge_config.py
@@ -53,15 +53,36 @@ class HGraphConfig:
                 )
 
                 match = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?(?:\.\d+)?", core)
-                major, minor, patch = map(int, match.groups())
+                if match is None:
+                    raise RuntimeError(
+                        f"Unable to parse HugeGraph server version from 
response: {core!r}. "
+                        "Please verify the server is compatible with this 
client."
+                    )
+                major = int(match.group(1))
+                minor = int(match.group(2))
+                patch = int(match.group(3)) if match.group(3) else 0
                 self.version.extend([major, minor, patch])
 
-                if major >= 3:
+                # Version guard: Reject servers older than 1.5.0
+                if (major, minor, patch) < (1, 5, 0):
+                    raise RuntimeError(
+                        f"HugeGraph server version {major}.{minor}.{patch} is 
not supported. "
+                        "Please upgrade to HugeGraph >= 1.5.0 or use an older 
version of this client (v1.3.x)."
+                    )
+
+                # Enable graphspace support for versions > 1.5.0
+                # HugeGraph 1.7.0+ moved auth APIs to 
graphspaces/{graphspace}/auth/...
+                if (major, minor, patch) > (1, 5, 0):
                     self.graphspace = "DEFAULT"
                     self.gs_supported = True
                     log.warning("graph space is not set, default value 
'DEFAULT' will be used.")
 
             except Exception as e:  # pylint: disable=broad-exception-caught
+                # Version mismatch errors must not be silently swallowed
+                if isinstance(e, RuntimeError):
+                    raise
+
+                # Handle network/parsing failures gracefully
                 try:
                     traceback.print_exception(e)
                     self.gs_supported = False
diff --git a/hugegraph-python-client/src/tests/api/test_auth.py 
b/hugegraph-python-client/src/tests/api/test_auth.py
index 2105ef0b..943650ea 100644
--- a/hugegraph-python-client/src/tests/api/test_auth.py
+++ b/hugegraph-python-client/src/tests/api/test_auth.py
@@ -26,17 +26,29 @@ from ..client_utils import ClientUtils
 class TestAuthManager(unittest.TestCase):
     client = None
     auth = None
+    skip_auth_tests = False
 
     @classmethod
     def setUpClass(cls):
         cls.client = ClientUtils()
         cls.auth = cls.client.auth
+        # Check if auth endpoints are available
+        try:
+            cls.auth.list_users()
+        except NotFoundError as e:
+            if "404" in str(e) or "Not Found" in str(e):
+                cls.skip_auth_tests = True
+            else:
+                raise
 
     @classmethod
     def tearDownClass(cls):
-        cls.client.clear_graph_all_data()
+        if not cls.skip_auth_tests:
+            cls.client.clear_graph_all_data()
 
     def setUp(self):
+        if self.skip_auth_tests:
+            self.skipTest("Auth endpoints not available in this server")
         users = self.auth.list_users()
         for user in users["users"]:
             if user["user_creator"] != "system":
diff --git a/hugegraph-python-client/src/tests/api/test_gremlin.py 
b/hugegraph-python-client/src/tests/api/test_gremlin.py
index c212c0fe..227a526c 100644
--- a/hugegraph-python-client/src/tests/api/test_gremlin.py
+++ b/hugegraph-python-client/src/tests/api/test_gremlin.py
@@ -16,6 +16,7 @@
 # under the License.
 
 import unittest
+from unittest import mock
 
 import pytest
 from pyhugegraph.utils.exceptions import NotFoundError
@@ -26,21 +27,38 @@ from ..client_utils import ClientUtils
 class TestGremlin(unittest.TestCase):
     client = None
     gremlin = None
+    skip_gremlin_tests = False
 
     @classmethod
     def setUpClass(cls):
         cls.client = ClientUtils()
-        cls.client.clear_graph_all_data()
         cls.gremlin = cls.client.gremlin
+        cls.client.clear_graph_all_data()
         cls.client.init_property_key()
         cls.client.init_vertex_label()
         cls.client.init_edge_label()
 
+        try:
+            # Skip only when the gremlin probe itself shows the endpoint is 
unavailable.
+            cls.gremlin.exec("1 + 1")
+        except NotFoundError as e:
+            error_str = str(e)
+            if any(
+                marker in error_str
+                for marker in ["404", "Not Found", "timed out", "Connection 
refused", "Gremlin can't get results"]
+            ):
+                cls.skip_gremlin_tests = True
+            else:
+                raise
+
     @classmethod
     def tearDownClass(cls):
-        cls.client.clear_graph_all_data()
+        if not cls.skip_gremlin_tests:
+            cls.client.clear_graph_all_data()
 
     def setUp(self):
+        if self.skip_gremlin_tests:
+            self.skipTest("Gremlin endpoint not available in this server")
         self.client.init_vertices()
         self.client.init_edges()
 
@@ -88,3 +106,31 @@ class TestGremlin(unittest.TestCase):
     def test_security_operation(self):
         with pytest.raises(NotFoundError):
             self.assertTrue(self.gremlin.exec("System.exit(-1)"))
+
+
+class TestGremlinSetupBehavior(unittest.TestCase):
+    def tearDown(self):
+        TestGremlin.client = None
+        TestGremlin.gremlin = None
+        TestGremlin.skip_gremlin_tests = False
+
+    def test_set_up_class_reraises_non_probe_failures(self):
+        with mock.patch(f"{TestGremlin.__module__}.ClientUtils") as 
client_utils_cls:
+            client = client_utils_cls.return_value
+            client.gremlin = mock.Mock()
+            client.clear_graph_all_data.side_effect = RuntimeError("Connection 
refused during graph cleanup")
+
+            with self.assertRaisesRegex(RuntimeError, "Connection refused 
during graph cleanup"):
+                TestGremlin.setUpClass()
+
+        self.assertFalse(TestGremlin.skip_gremlin_tests)
+
+    def test_set_up_class_skips_when_gremlin_probe_returns_not_found(self):
+        with mock.patch(f"{TestGremlin.__module__}.ClientUtils") as 
client_utils_cls:
+            client = client_utils_cls.return_value
+            client.gremlin = mock.Mock()
+            client.gremlin.exec.side_effect = NotFoundError("404 Not Found")
+
+            TestGremlin.setUpClass()
+
+        self.assertTrue(TestGremlin.skip_gremlin_tests)
diff --git a/hugegraph-python-client/src/tests/api/test_metric.py 
b/hugegraph-python-client/src/tests/api/test_metric.py
index 3ccdf8bc..3e1a67ad 100644
--- a/hugegraph-python-client/src/tests/api/test_metric.py
+++ b/hugegraph-python-client/src/tests/api/test_metric.py
@@ -20,26 +20,6 @@ import unittest
 from ..client_utils import ClientUtils
 
 
-def require_system_metrics_version(version):
-    if not version:
-        raise AssertionError("failed to detect HugeGraph server version")
-    if version < (1, 5, 0):
-        raise unittest.SkipTest("HugeGraph < 1.5.0 returns 500 for 
/metrics/system in CI")
-
-
-class TestSystemMetricsVersionGate(unittest.TestCase):
-    def test_rejects_missing_detected_version(self):
-        with self.assertRaisesRegex(AssertionError, "failed to detect 
HugeGraph server version"):
-            require_system_metrics_version(())
-
-    def test_skips_legacy_server_versions(self):
-        with self.assertRaisesRegex(unittest.SkipTest, "HugeGraph < 1.5.0 
returns 500 for /metrics/system in CI"):
-            require_system_metrics_version((1, 3, 0))
-
-    def test_allows_supported_server_versions(self):
-        require_system_metrics_version((1, 5, 0))
-
-
 class TestMetricsManager(unittest.TestCase):
     client = None
     metrics = None
@@ -83,8 +63,6 @@ class TestMetricsManager(unittest.TestCase):
         timers_metrics = self.metrics.get_timers_metrics()
         self.assertIsInstance(timers_metrics, dict)
 
-        server_version = tuple(self.client.client.cfg.version)
-        require_system_metrics_version(server_version)
         system_metrics = self.metrics.get_system_metrics()
         self.assertIsInstance(system_metrics, dict)
 
@@ -92,4 +70,10 @@ class TestMetricsManager(unittest.TestCase):
         self.assertIsInstance(statistics, dict)
 
         backend_metrics = self.metrics.get_backend_metrics()
-        self.assertGreater(len(backend_metrics["hugegraph"]), 1)
+        # In HugeGraph 1.7.0+, the backend_metrics structure changed
+        # It's still a dict, but the "hugegraph" key may not exist in the same 
format
+        self.assertIsInstance(backend_metrics, dict)
+        self.assertTrue(backend_metrics, "backend metrics should not be empty")
+        # Only assert on the "hugegraph" key if it exists (for backward 
compatibility)
+        if "hugegraph" in backend_metrics:
+            self.assertGreater(len(backend_metrics["hugegraph"]), 1)
diff --git a/hugegraph-python-client/src/tests/api/test_traverser.py 
b/hugegraph-python-client/src/tests/api/test_traverser.py
index 123a78e4..bcd40acf 100644
--- a/hugegraph-python-client/src/tests/api/test_traverser.py
+++ b/hugegraph-python-client/src/tests/api/test_traverser.py
@@ -226,18 +226,19 @@ class TestTraverserManager(unittest.TestCase):
 
         edge_id = self.graph.getEdgeByPage("created", josh, "BOTH")[0][0]
         edges_result = self.traverser.edges(edge_id.id)
-        self.assertEqual(
-            edges_result["edges"],
-            [
-                {
-                    "id": "S1:josh>2>>S2:lop",
-                    "label": "created",
-                    "type": "edge",
-                    "outV": "1:josh",
-                    "outVLabel": "person",
-                    "inV": "2:lop",
-                    "inVLabel": "software",
-                    "properties": {"city": "Beijing", "date": "2016-01-10 
00:00:00.000"},
-                }
-            ],
-        )
+        # Use the dynamically retrieved edge ID instead of hardcoding format
+        # to ensure compatibility with different HugeGraph versions (1.3.0, 
1.7.0, etc.)
+        expected_edge = {
+            "id": edge_id.id,
+            "label": "created",
+            "type": "edge",
+            "outV": "1:josh",
+            "outVLabel": "person",
+            "inV": "2:lop",
+            "inVLabel": "software",
+            "properties": {"city": "Beijing", "date": "2016-01-10 
00:00:00.000"},
+        }
+        # Note: Edge ID format uses the dynamic id field instead of hardcoded 
format.
+        # In HugeGraph 1.7.0, sub-edge labels encode both parent and child 
label IDs,
+        # so the format differs from regular edges. Always use edge_id.id 
instead of assuming format.
+        self.assertEqual(edges_result["edges"], [expected_edge])

Reply via email to