From 644b0c9f7381e952a4320749afa9a2f4af6969a2 Mon Sep 17 00:00:00 2001 From: George-iam Date: Sun, 1 Mar 2026 08:21:25 +0000 Subject: [PATCH] feat: add users profile and nick helpers to Python SDK Expose users contract family methods with tests and quickstart examples so GA parity covers nick lifecycle and profile operations. Made-with: Cursor --- README.md | 19 ++++++ axme_sdk/client.py | 60 +++++++++++++++++++ tests/test_client.py | 136 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) diff --git a/README.md b/README.md index 160f9b9..5e30e9b 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,25 @@ with AxmeClient(config) as client: print(schema["schema"]["schema_hash"]) schema_get = client.get_schema("axme.calendar.schedule.v1") print(schema_get["schema"]["semantic_type"]) + registered = client.register_nick( + {"nick": "@partner.user", "display_name": "Partner User"}, + idempotency_key="nick-register-001", + ) + print(registered["owner_agent"]) + nick_check = client.check_nick("@partner.user") + print(nick_check["available"]) + renamed = client.rename_nick( + {"owner_agent": registered["owner_agent"], "nick": "@partner.new"}, + idempotency_key="nick-rename-001", + ) + print(renamed["public_address"]) + profile = client.get_user_profile(registered["owner_agent"]) + print(profile["updated_at"]) + profile_updated = client.update_user_profile( + {"owner_agent": registered["owner_agent"], "display_name": "Partner User Updated"}, + idempotency_key="profile-update-001", + ) + print(profile_updated["display_name"]) subscription = client.upsert_webhook_subscription( { "callback_url": "https://integrator.example/webhooks/axme", diff --git a/axme_sdk/client.py b/axme_sdk/client.py index ef88988..1c05da1 100644 --- a/axme_sdk/client.py +++ b/axme_sdk/client.py @@ -252,6 +252,66 @@ def upsert_schema( def get_schema(self, semantic_type: str, *, trace_id: str | None = None) -> dict[str, Any]: return self._request_json("GET", f"/v1/schemas/{semantic_type}", trace_id=trace_id, retryable=True) + def register_nick( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/users/register-nick", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def check_nick(self, nick: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", "/v1/users/check-nick", params={"nick": nick}, trace_id=trace_id, retryable=True) + + def rename_nick( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/users/rename-nick", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def get_user_profile(self, owner_agent: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/users/profile", + params={"owner_agent": owner_agent}, + trace_id=trace_id, + retryable=True, + ) + + def update_user_profile( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/users/profile/update", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + def upsert_webhook_subscription( self, payload: dict[str, Any], diff --git a/tests/test_client.py b/tests/test_client.py index c0b13c6..52f6cc1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -526,6 +526,142 @@ def handler(request: httpx.Request) -> httpx.Response: assert client.get_schema(semantic_type)["schema"]["semantic_type"] == semantic_type +def test_register_nick_success() -> None: + payload = {"nick": "@partner.user", "display_name": "Partner User"} + user_id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + owner_agent = f"agent://user/{user_id}" + + def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "POST" + assert request.url.path == "/v1/users/register-nick" + assert request.headers["idempotency-key"] == "nick-register-1" + assert json.loads(request.read().decode("utf-8")) == payload + return httpx.Response( + 200, + json={ + "ok": True, + "user_id": user_id, + "owner_agent": owner_agent, + "nick": "@partner.user", + "public_address": "partner.user@ax", + "display_name": "Partner User", + "phone": None, + "email": None, + "created_at": "2026-02-28T00:00:00Z", + }, + ) + + client = _client(handler) + assert client.register_nick(payload, idempotency_key="nick-register-1")["owner_agent"] == owner_agent + + +def test_check_nick_success() -> None: + nick = "@partner.user" + + def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "GET" + assert request.url.path == "/v1/users/check-nick" + assert request.url.params.get("nick") == nick + return httpx.Response( + 200, + json={ + "ok": True, + "nick": nick, + "normalized_nick": "partner.user", + "public_address": "partner.user@ax", + "available": True, + }, + ) + + client = _client(handler) + assert client.check_nick(nick)["available"] is True + + +def test_rename_nick_success() -> None: + payload = {"owner_agent": "agent://user/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "nick": "@partner.new"} + + def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "POST" + assert request.url.path == "/v1/users/rename-nick" + assert request.headers["idempotency-key"] == "nick-rename-1" + assert json.loads(request.read().decode("utf-8")) == payload + return httpx.Response( + 200, + json={ + "ok": True, + "user_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "owner_agent": payload["owner_agent"], + "nick": "@partner.new", + "public_address": "partner.new@ax", + "display_name": "Partner User", + "phone": None, + "email": None, + "renamed_at": "2026-02-28T00:00:01Z", + }, + ) + + client = _client(handler) + assert client.rename_nick(payload, idempotency_key="nick-rename-1")["nick"] == "@partner.new" + + +def test_get_user_profile_success() -> None: + owner_agent = "agent://user/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + + def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "GET" + assert request.url.path == "/v1/users/profile" + assert request.url.params.get("owner_agent") == owner_agent + return httpx.Response( + 200, + json={ + "ok": True, + "user_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "owner_agent": owner_agent, + "nick": "@partner.new", + "normalized_nick": "partner.new", + "public_address": "partner.new@ax", + "display_name": "Partner User", + "phone": None, + "email": None, + "updated_at": "2026-02-28T00:00:02Z", + }, + ) + + client = _client(handler) + assert client.get_user_profile(owner_agent)["nick"] == "@partner.new" + + +def test_update_user_profile_success() -> None: + payload = { + "owner_agent": "agent://user/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "display_name": "Partner Updated", + } + + def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "POST" + assert request.url.path == "/v1/users/profile/update" + assert request.headers["idempotency-key"] == "profile-update-1" + assert json.loads(request.read().decode("utf-8")) == payload + return httpx.Response( + 200, + json={ + "ok": True, + "user_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "owner_agent": payload["owner_agent"], + "nick": "@partner.new", + "normalized_nick": "partner.new", + "public_address": "partner.new@ax", + "display_name": "Partner Updated", + "phone": None, + "email": None, + "updated_at": "2026-02-28T00:00:03Z", + }, + ) + + client = _client(handler) + assert client.update_user_profile(payload, idempotency_key="profile-update-1")["display_name"] == "Partner Updated" + + @pytest.mark.parametrize( ("status_code", "expected_exception"), [