diff --git a/src/modelscope_hub/_openapi.py b/src/modelscope_hub/_openapi.py index 0306560..b49e248 100644 --- a/src/modelscope_hub/_openapi.py +++ b/src/modelscope_hub/_openapi.py @@ -388,6 +388,28 @@ def get_current_user(self) -> JSON: """``GET /users/me`` — fetch the authenticated user profile.""" return self._request("GET", "/users/me") + def get_current_username(self) -> str: + """The authenticated account handle, or ``""`` when unresolvable. + + Callers need the handle to build repo paths (``/``), and + the field it arrives in has changed: the endpoint now answers with + OIDC-style claims (``preferred_username`` / ``name``) where it used to + return ModelScope's own ``Username``. Reading only the old key yielded + an empty owner, which then produced confusing downstream failures + (``path is required`` on create, then a ``//`` URL 404 on commit). + + Keys are tried in handle-before-display-name order so a server that + populates both still gives the login handle rather than a full name. + """ + data = self.get_current_user() + if not isinstance(data, dict): + return "" + for key in ("Username", "username", "preferred_username", "name"): + value = data.get(key) + if value: + return str(value) + return "" + # ================================================================== # Models # ================================================================== diff --git a/tests/cli/test_openapi.py b/tests/cli/test_openapi.py index 1625da0..e81e265 100644 --- a/tests/cli/test_openapi.py +++ b/tests/cli/test_openapi.py @@ -379,3 +379,45 @@ def test_foreign_host_absolute_url_strips_auth_and_cookies(self, client): assert call_kwargs["cookies"] == {} # Caller-supplied headers (not credentials) must still be sent. assert call_kwargs["headers"]["Content-Type"] == "application/octet-stream" + + +class TestCurrentUsernameFieldCompat: + """``/users/me`` moved from ``Username`` to OIDC claims; both must work. + + Reading only the old key silently produced an empty owner, which surfaced + much later as ``path is required`` on repo creation and a ``//`` URL 404 on + commit -- so each accepted shape is pinned here. + """ + + @pytest.mark.parametrize( + ("payload", "expected"), + [ + # Shape observed on the server today: OIDC claims, handle in ``name`` + # while ``preferred_username`` comes back empty. + ( + { + "name": "tastelikefeet", + "preferred_username": "", + "description": "", + "avatar": "", + "email": "user@example.com", + }, + "tastelikefeet", + ), + # Legacy ModelScope shapes still deployed elsewhere. + ({"Username": "alice", "Email": "a@b.c"}, "alice"), + ({"username": "bob"}, "bob"), + # A server populating both must yield the login handle, not the + # human-readable display name. + ({"preferred_username": "carol", "name": "Carol Smith"}, "carol"), + ({"Username": "dave", "name": "Dave X"}, "dave"), + # Unresolvable responses degrade to "" so callers can report it. + ({}, ""), + ({"name": ""}, ""), + (None, ""), + ("not-a-dict", ""), + ], + ) + def test_username_resolved_from_any_known_field(self, client, payload, expected): + with patch.object(OpenAPIClient, "get_current_user", return_value=payload): + assert client.get_current_username() == expected