# fix: implement graphspace-scoped auth routing in Python client#325
# fix: implement graphspace-scoped auth routing in Python client#325imbajin merged 11 commits intoapache:mainfrom
Conversation
Signed-off-by: Muawiya-contact <contactmuawia@gmail.com>
…achable; fix: normalize auth docstring indentation
|
@imbajin PR #325 is ready for review — implements the dual-path auth routing strategy discussed in this issue. Quick summary of what's in it:
Happy to adjust anything based on your feedback. |
imbajin
left a comment
There was a problem hiding this comment.
Dual-path routing mirrors the Java client correctly and the approach is sound. A few issues to address before merging: a latent setdefault bug in the router that will bite any future method accepting an explicit graphspace kwarg, a fragile str.replace in the fallback, an integration test that silently accepts two response shapes, and a CI workflow change that's out of scope for this PR.
| target_resources = target["target_resources"] | ||
| if isinstance(target_resources, dict): | ||
| target_resources = next(iter(target_resources.values()), []) | ||
| self.assertEqual(target_resources[0]["properties"]["city"], "Shanghai") |
There was a problem hiding this comment.
dict and list shapes silently hides a real API contract divergence. If the server ever returns an unexpected shape due to a bug, the test still passes.
Better: pick the expected shape deterministically (e.g., branch on cls.client.cfg.version or gs_supported), or assert the shape explicitly so a drift surfaces as a test failure, not invisibly. At minimum, add a comment documenting which HugeGraph versions return which shape and link the server-side change — otherwise future maintainers can't tell whether the dict branch is compensating for a server bug or a legitimate format.
| sess = DummySession(cfg) | ||
| auth = AuthManager(sess) | ||
| auth.list_groups() | ||
| assert "/auth/groups" in sess.last or "auth/groups" in sess.last |
There was a problem hiding this comment.
🧹 "/auth/groups" in x or "auth/groups" in x is redundant — the first substring contains the second, so if the first matches the second always does, and if the second fails the first always fails. Just assert "auth/groups" in sess.last once (or use endswith("/auth/groups") if you want to pin the position).
imbajin
left a comment
There was a problem hiding this comment.
Follow-up from cross-checking against apache/hugegraph@1.7.0 source (server @Path annotations, PathFilter whitelist) and apache/hugegraph-toolchain AuthAPI.java: two additional observations about the PR's framing and the fallback branch.
There was a problem hiding this comment.
Final audit of test recovery after this PR's auth fix, but several defensive mechanisms introduced by PR #320 (the 1.7.0 upgrade) are still in place. Splitting them by scope:
In scope of this PR — recommend cleaning up before merge:
-
test_auth.py::setUpClassskip-on-404 probe. This PR's core change makes the 404 branch unreachable against a correctly-configured HugeGraph 1.7.0+ server, which turns the probe into a silent-regression trap: if a future commit regresses the auth paths, tests will skip instead of failing, and CI will stay green with5 skippedinstead of5 failed. Either (a) remove the probe entirely and letlist_users()raise loudly on 404 (preferred — forces regressions to surface), or (b) convert the 404 branch toraise AssertionError("auth path regressed — server returned 404"). The newexcept requests.exceptions.RequestException: skipthat this PR adds falls into the same anti-pattern and should likewise become an explicit error. -
test_auth.py::test_target_operationsdict-or-listtarget_resourcescompat (covered in my earlier inline comment). Same reasoning: once the client pins HugeGraph 1.7.0+, there is one authoritative response shape — assert it and fail on the other, rather than silently accepting both.
Out of scope — file as separate follow-up issues:
-
test_gremlin.pyskip_gremlin_testsprobe (L41-52), introduced by #320. Same dead-defense pattern as (1). Gremlin currently works in CI so the probe never fires, but it would mask a future regression the same way. Belongs in its own cleanup PR. -
test_metric.py::test_metrics_operations(L78-79) — theif "hugegraph" in backend_metrics: ...guard added by #320 effectively dropped the assertion onbackend_metrics' shape for 1.7.0. Should be replaced with a version-gated assertion once 1.7.0's actual shape is documented. -
test_traverser.pydynamic edge-id — the original hardcodedS1:josh>2>>S2:lopwas fragile regardless of server version; usingedge_id.idis the correct fix, not a temporary patch. Leave as-is; no follow-up needed.
Recommend (a) addressing items 1–2 in this PR before merge, and (b) opening two tracking issues for items 3–4 so the 1.7.0 test debt from #320 is fully closed out and the boundary between this PR and the broader upgrade stays clean.
|
@imbajin CI is all green now — the ruff format check on |
removeprefix + concat was redundant (netted to `path` unchanged); drop the dead fallback naming and the stale 'gracefully fall back' comment now that the branch raises ValueError instead.
There was a problem hiding this comment.
LGTM.
Non-blocking follow-up (please handle separately, do not re-open this PR)
File two tracking issues for leftover 1.7.0 test-suite debt from PR #320 (explicitly out of scope for #322/#325, but they share the same dead-defense anti-pattern this PR fixed for auth):
-
test_gremlin.py— theskip_gremlin_testsprobe insetUpClass(L41-52) skips all 6 gremlin integration tests ifgremlin.exec("1 + 1")returns 404/timeout/connection-refused. Gremlin works in current CI so the probe never fires, but a future regression on the gremlin endpoint would be hidden under6 skippedrather than surfaced as6 failed. Same remediation astest_auth.py: drop the probe, or convert the 404 branch into an explicitAssertionError. -
test_metric.py::test_metrics_operations(L78-79) — theif "hugegraph" in backend_metrics: self.assertGreater(...)guard that replaced the original strict assertion effectively no-ops the backend-metrics check on 1.7.0. Pin down the actual 1.7.0 response shape (likely a per-graph map) and restore a version-gated assertion that fails when the shape drifts.
What does this PR do?
Auth endpoints in the Python client were using absolute paths (
/auth/users, etc.) that only worked because of a temporaryPathFiltercompatibility layer in HugeGraph 1.7.0. OncePathFilteris removed, those endpoints would break.This PR implements the same dual-path strategy as the Java client's
AuthAPI.java— graphspace-scoped paths when a graphspace is available, server-level fallback when it isn't.Changes
src/pyhugegraph/utils/huge_router.pyPath resolution moved to runtime — prefers explicit graphspace arg, then
session.cfg.graphspacewhengs_supportedis true, falls back to server-level/auth/...otherwise.src/pyhugegraph/api/auth.pyusers,accesses,belongs,targets→graphspaces/{graphspace}/auth/...groups→/auth/groups(server-level, unchanged)src/tests/api/test_auth_routing.py(new)Unit tests mocking
HGraphSession— asserts correct URL resolution for both graphspace and fallback cases. 9 tests, all passing.src/tests/api/test_auth.pyIntegration tests now skip gracefully when no live server is reachable — local runs won't fail without a HugeGraph instance.
Backward Compatibility
Public method signatures are unchanged. Behavior only differs when a graphspace is configured — requests use the scoped path automatically. No user action required.
Related
Closes #322
Reference:
AuthAPI.java(Java client dual-path strategy)/cc @imbajin