Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/modelscope_hub/_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (``<owner>/<repo>``), 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
# ==================================================================
Expand Down
42 changes: 42 additions & 0 deletions tests/cli/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading