diff --git a/generated/model.py b/generated/model.py index 4659a06e..eff2982f 100644 --- a/generated/model.py +++ b/generated/model.py @@ -132,6 +132,28 @@ class DetectorGroupRequest(BaseModel): name: constr(min_length=1, max_length=100) +class Group(BaseModel): + """ + A group (tenant), e.g. one the authenticated user belongs to. + """ + + id: int + name: constr(max_length=150) + + +class Me(BaseModel): + """ + Authenticated user identity from GET /v1/me (email, username, group, is_superuser). + """ + + email: str = Field(..., description="Email address of the authenticated user.") + username: str = Field(..., description="Username of the authenticated user.") + group: Group = Field(..., description="Group (tenant) the authenticated user belongs to.") + is_superuser: bool = Field( + ..., description="Whether the authenticated user has elevated (superuser) permissions." + ) + + class DetectorModeEnum(str, Enum): """ * `BINARY` - BINARY diff --git a/spec/public-api.yaml b/spec/public-api.yaml index 9e6a17a2..919fcda2 100644 --- a/spec/public-api.yaml +++ b/spec/public-api.yaml @@ -865,7 +865,8 @@ paths: /v1/me: get: operationId: Who am I - description: Retrieve the current user. + description: Retrieve the authenticated user's email, username, group, and superuser + flag. tags: - user security: @@ -875,11 +876,7 @@ paths: content: application/json: schema: - type: object - properties: - username: - type: string - description: The user's username + $ref: '#/components/schemas/Me' description: '' /v1/month-to-date-account-info: get: @@ -1568,6 +1565,19 @@ components: minimal_compatible: type: boolean default: false + Group: + type: object + description: A group (tenant), e.g. one the authenticated user belongs to. + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 150 + required: + - id + - name ImageQuery: type: object description: ImageQuery objects are the answers to natural language questions @@ -1828,6 +1838,29 @@ components: - training_in_progress - type x-internal: true + Me: + type: object + description: Authenticated user identity from GET /v1/me (email, username, group, + is_superuser). + properties: + email: + type: string + description: Email address of the authenticated user. + username: + type: string + description: Username of the authenticated user. + group: + allOf: + - $ref: '#/components/schemas/Group' + description: Group (tenant) the authenticated user belongs to. + is_superuser: + type: boolean + description: Whether the authenticated user has elevated (superuser) permissions. + required: + - email + - group + - is_superuser + - username ModeEnum: type: string enum: diff --git a/src/groundlight/cli.py b/src/groundlight/cli.py index 698ff655..f3fec131 100644 --- a/src/groundlight/cli.py +++ b/src/groundlight/cli.py @@ -190,6 +190,7 @@ def wrapper(*args, **kwargs): _COMMAND_GROUPS: dict[str, str] = { # Account "whoami": "Account", + "me": "Account", "get_month_to_date_usage": "Account", # Detectors "get_detector": "Detectors", diff --git a/src/groundlight/client.py b/src/groundlight/client.py index c988c8f1..822a0b43 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -33,6 +33,7 @@ Detector, DetectorGroup, ImageQuery, + Me, ModeEnum, PaginatedDetectorList, PaginatedImageQueryList, @@ -280,6 +281,23 @@ def _fixup_image_query(iq: ImageQuery) -> ImageQuery: iq.result.label = convert_internal_label_to_display(iq, iq.result.label) return iq + def me(self) -> Me: + """ + Return user identity information for the current API token. + + **Example usage**:: + + gl = Groundlight() + me = gl.me() + print(f"Authenticated as {me.email} in {me.group.name}") + + :return: Me object for the authenticated user + :raises ApiTokenError: If the API token is invalid + :raises GroundlightClientError: If there are connectivity issues with the Groundlight service + """ + obj = self.user_api.who_am_i(_request_timeout=DEFAULT_REQUEST_TIMEOUT) + return Me.model_validate(obj.to_dict()) + def whoami(self) -> str: """ Return the username (email address) associated with the current API token. @@ -297,8 +315,7 @@ def whoami(self) -> str: :raises ApiTokenError: If the API token is invalid :raises GroundlightClientError: If there are connectivity issues with the Groundlight service """ - obj = self.user_api.who_am_i(_request_timeout=DEFAULT_REQUEST_TIMEOUT) - return obj["email"] + return self.me().email def _user_is_privileged(self) -> bool: """ diff --git a/test/unit/test_user.py b/test/unit/test_user.py index 927da25b..04319a66 100644 --- a/test/unit/test_user.py +++ b/test/unit/test_user.py @@ -1,7 +1,21 @@ from groundlight import Groundlight +from model import Group, Me def test_whoami(gl: Groundlight): user = gl.whoami() assert user is not None assert isinstance(user, str) + + +def test_me(gl: Groundlight): + """me() returns structured identity including group from /v1/me.""" + me = gl.me() + assert isinstance(me, Me) + assert me.email + assert me.username + assert isinstance(me.group, Group) + assert me.group.id + assert me.group.name + assert isinstance(me.is_superuser, bool) + assert gl.whoami() == me.email