Repository: python-gitlab/python-gitlab

Issue:
Need support to [...]/personal_access_tokens/self Gitlab API
## Description of the problem, including code/CLI snippet
So far, I'm unable to get scopes information from the 'personal access token' used to authenticate to Gitlab API.
Ofc, it is easy to get the list of the personal access token of the authenticated user, but I see no way to get the link with the one really used to authenticate.

It seems the dedicated Gitlab API entrypoint for that is [...]/personal_access_tokens/self which is not actually supported by python-gitlab.

Context: the idea is to check with efficiency some required scopes/permissions/rights, instead of trying an operation (like download repository tarball) because it could be time consuming although a simple check could be immediate.

## Expected Behavior

Get scopes (and roles ?) information of the 'personal access token' currently used to authenticate to Gitlab API.

## Actual Behavior

Unable to get this information.

## Specifications

  - python-gitlab version: 3.13.0
  - API version you are using (v3/v4): v4
  - Gitlab server version (or gitlab.com): gitlab.com



Discussion:
Hi @bertrand-benoit this is supported by supplying `self` as the ID, so I'd personally say it's not worth implementing a custom method for it. We document it for delete but not get.. sorry about that:

https://python-gitlab.readthedocs.io/en/stable/gl_objects/personal_access_tokens.html?highlight=self#examples

This should do the trick:

```python
token = gl.personal_access_tokens.get("self")
```

Let me know if that works for you.
Oh, maybe we're missing a `GetMixin` there actually! oops.

Resolving patch:
diff --git a/gitlab/v4/objects/personal_access_tokens.py b/gitlab/v4/objects/personal_access_tokens.py
--- a/gitlab/v4/objects/personal_access_tokens.py
+++ b/gitlab/v4/objects/personal_access_tokens.py
@@ -1,5 +1,7 @@
+from typing import Any, cast, Union
+
 from gitlab.base import RESTManager, RESTObject
-from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin
+from gitlab.mixins import CreateMixin, DeleteMixin, ObjectDeleteMixin, RetrieveMixin
 from gitlab.types import ArrayAttribute, RequiredOptional
 
 __all__ = [
@@ -14,11 +16,16 @@ class PersonalAccessToken(ObjectDeleteMixin, RESTObject):
     pass
 
 
-class PersonalAccessTokenManager(DeleteMixin, ListMixin, RESTManager):
+class PersonalAccessTokenManager(DeleteMixin, RetrieveMixin, RESTManager):
     _path = "/personal_access_tokens"
     _obj_cls = PersonalAccessToken
     _list_filters = ("user_id",)
 
+    def get(
+        self, id: Union[str, int], lazy: bool = False, **kwargs: Any
+    ) -> PersonalAccessToken:
+        return cast(PersonalAccessToken, super().get(id=id, lazy=lazy, **kwargs))
+
 
 class UserPersonalAccessToken(RESTObject):
     pass


Test patch:
diff --git a/tests/unit/objects/test_personal_access_tokens.py b/tests/unit/objects/test_personal_access_tokens.py
--- a/tests/unit/objects/test_personal_access_tokens.py
+++ b/tests/unit/objects/test_personal_access_tokens.py
@@ -13,6 +13,7 @@
 
 token_url = "http://localhost/api/v4/personal_access_tokens"
 single_token_url = f"{token_url}/{token_id}"
+self_token_url = f"{token_url}/self"
 user_token_url = f"http://localhost/api/v4/users/{user_id}/personal_access_tokens"
 
 content = {
@@ -41,8 +42,8 @@ def resp_create_user_personal_access_token():
 
 
 @pytest.fixture
-def resp_personal_access_token(no_content):
-    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
+def resp_list_personal_access_tokens():
+    with responses.RequestsMock() as rsps:
         rsps.add(
             method=responses.GET,
             url=token_url,
@@ -50,6 +51,38 @@ def resp_personal_access_token(no_content):
             content_type="application/json",
             status=200,
         )
+        yield rsps
+
+
+@pytest.fixture
+def resp_get_personal_access_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            url=single_token_url,
+            json=content,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
+@pytest.fixture
+def resp_get_personal_access_token_self():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            url=self_token_url,
+            json=content,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
+@pytest.fixture
+def resp_delete_personal_access_token(no_content):
+    with responses.RequestsMock() as rsps:
         rsps.add(
             method=responses.DELETE,
             url=single_token_url,
@@ -69,26 +102,38 @@ def test_create_personal_access_token(gl, resp_create_user_personal_access_token
     assert access_token.name == token_name
 
 
-def test_list_personal_access_tokens(gl, resp_personal_access_token):
+def test_list_personal_access_tokens(gl, resp_list_personal_access_tokens):
     access_tokens = gl.personal_access_tokens.list()
     assert len(access_tokens) == 1
     assert access_tokens[0].revoked is False
     assert access_tokens[0].name == token_name
 
 
-def test_list_personal_access_tokens_filter(gl, resp_personal_access_token):
+def test_list_personal_access_tokens_filter(gl, resp_list_personal_access_tokens):
     access_tokens = gl.personal_access_tokens.list(user_id=user_id)
     assert len(access_tokens) == 1
     assert access_tokens[0].revoked is False
     assert access_tokens[0].user_id == user_id
 
 
-def test_revoke_personal_access_token(gl, resp_personal_access_token):
-    access_token = gl.personal_access_tokens.list(user_id=user_id)[0]
+def test_get_personal_access_token(gl, resp_get_personal_access_token):
+    access_token = gl.personal_access_tokens.get(token_id)
+
+    assert access_token.revoked is False
+    assert access_token.user_id == user_id
+
+
+def test_get_personal_access_token_self(gl, resp_get_personal_access_token_self):
+    access_token = gl.personal_access_tokens.get("self")
+
+    assert access_token.revoked is False
+    assert access_token.user_id == user_id
+
+
+def test_delete_personal_access_token(gl, resp_delete_personal_access_token):
+    access_token = gl.personal_access_tokens.get(token_id, lazy=True)
     access_token.delete()
-    assert resp_personal_access_token.assert_call_count(single_token_url, 1)
 
 
-def test_revoke_personal_access_token_by_id(gl, resp_personal_access_token):
+def test_revoke_personal_access_token_by_id(gl, resp_delete_personal_access_token):
     gl.personal_access_tokens.delete(token_id)
-    assert resp_personal_access_token.assert_call_count(single_token_url, 1)