Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-gitlab for 
openSUSE:Factory checked in at 2026-05-04 12:54:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-gitlab (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-gitlab.new.30200 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-gitlab"

Mon May  4 12:54:22 2026 rev:32 rq:1350546 version:8.3.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-gitlab/python-python-gitlab.changes    
    2026-03-30 18:34:52.280018471 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-python-gitlab.new.30200/python-python-gitlab.changes
     2026-05-04 12:57:54.808336041 +0200
@@ -1,0 +2,13 @@
+Thu Apr 30 12:35:18 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- update to 8.3.0:
+  * Bug Fixes
+    - api: Update CI/CD variable create and update attributes
+      (849301b)
+    - mixins: Register service account token classes in RotateMixin
+      CLI actions (2cf430b)
+  * Features
+    - api: Add full service account support at instance, group, and
+      project level (feb6ced)
+
+-------------------------------------------------------------------

Old:
----
  python_gitlab-8.2.0.tar.gz

New:
----
  python_gitlab-8.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-python-gitlab.spec ++++++
--- /var/tmp/diff_new_pack.ZJLJWV/_old  2026-05-04 12:57:55.356358595 +0200
+++ /var/tmp/diff_new_pack.ZJLJWV/_new  2026-05-04 12:57:55.356358595 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-python-gitlab
-Version:        8.2.0
+Version:        8.3.0
 Release:        0
 Summary:        Python module for interacting with the GitLab API
 License:        LGPL-3.0-only
@@ -35,7 +35,7 @@
 # relax constraint, as Tumbleweed already has 3.x
 BuildRequires:  %{python_module argcomplete >= 1.10.0 with %python-argcomplete 
< 4 }
 BuildRequires:  %{python_module gql >= 3.5.0 with %python-gql < 5 }
-BuildRequires:  %{python_module requests >= 2.32.0}
+BuildRequires:  %{python_module requests >= 2.33.1}
 BuildRequires:  %{python_module requests-toolbelt >= 1.0.0}
 # /SECTION
 # SECTION test requirements
@@ -47,7 +47,7 @@
 # /SECTION
 # SECTION runtime requirements
 Requires:       python-PyYAML >= 6.0.1
-Requires:       python-requests >= 2.32.3
+Requires:       python-requests >= 2.33.1
 Requires:       python-requests-toolbelt >= 1.0.0
 # relax constraint, as Tumbleweed already has 3.x
 Requires:       (python-argcomplete >= 1.10.0 with python-argcomplete < 4)

++++++ python_gitlab-8.2.0.tar.gz -> python_gitlab-8.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/CHANGELOG.md 
new/python_gitlab-8.3.0/CHANGELOG.md
--- old/python_gitlab-8.2.0/CHANGELOG.md        2026-03-28 02:49:39.000000000 
+0100
+++ new/python_gitlab-8.3.0/CHANGELOG.md        2026-04-28 04:32:01.000000000 
+0200
@@ -2,6 +2,22 @@
 
 All versions below are listed in reverse chronological order.
 
+## v8.3.0 (2026-04-28)
+
+### Bug Fixes
+
+- **api**: Update CI/CD variable create and update attributes
+  
([`849301b`](https://github.com/python-gitlab/python-gitlab/commit/849301b830b0d4f76ab85ffa3f39b22a6e628df9))
+
+- **mixins**: Register service account token classes in RotateMixin CLI actions
+  
([`2cf430b`](https://github.com/python-gitlab/python-gitlab/commit/2cf430b9c55c88f1d43fd31122a022dda0a88700))
+
+### Features
+
+- **api**: Add full service account support at instance, group, and project 
level
+  
([`feb6ced`](https://github.com/python-gitlab/python-gitlab/commit/feb6cedeffa75ef386ef13a3226bc07bcb396f61))
+
+
 ## v8.2.0 (2026-03-28)
 
 ### Documentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/PKG-INFO 
new/python_gitlab-8.3.0/PKG-INFO
--- old/python_gitlab-8.2.0/PKG-INFO    2026-03-28 02:49:46.099087200 +0100
+++ new/python_gitlab-8.3.0/PKG-INFO    2026-04-28 04:32:08.061105300 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: python-gitlab
-Version: 8.2.0
+Version: 8.3.0
 Summary: The python wrapper for the GitLab REST and GraphQL APIs.
 Author-email: Gauvain Pocentek <[email protected]>
 Maintainer-email: John Villalovos <[email protected]>, Max Wittig 
<[email protected]>, Nejc Habjan <[email protected]>, Roger Meier 
<[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/docs/api-objects.rst 
new/python_gitlab-8.3.0/docs/api-objects.rst
--- old/python_gitlab-8.2.0/docs/api-objects.rst        2026-03-28 
02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/docs/api-objects.rst        2026-04-28 
04:31:51.000000000 +0200
@@ -64,6 +64,7 @@
    gl_objects/resource_groups
    gl_objects/search
    gl_objects/secure_files
+   gl_objects/service_accounts
    gl_objects/settings
    gl_objects/snippets
    gl_objects/statistics
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.2.0/docs/gl_objects/service_accounts.rst 
new/python_gitlab-8.3.0/docs/gl_objects/service_accounts.rst
--- old/python_gitlab-8.2.0/docs/gl_objects/service_accounts.rst        
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.3.0/docs/gl_objects/service_accounts.rst        
2026-04-28 04:31:51.000000000 +0200
@@ -0,0 +1,153 @@
+################
+Service Accounts
+################
+
+References
+----------
+
+* v4 API:
+
+  + :class:`gitlab.v4.objects.ServiceAccount`
+  + :class:`gitlab.v4.objects.ServiceAccountManager`
+  + :class:`gitlab.v4.objects.GroupServiceAccount`
+  + :class:`gitlab.v4.objects.GroupServiceAccountManager`
+  + :class:`gitlab.v4.objects.GroupServiceAccountAccessToken`
+  + :class:`gitlab.v4.objects.GroupServiceAccountAccessTokenManager`
+  + :class:`gitlab.v4.objects.ProjectServiceAccount`
+  + :class:`gitlab.v4.objects.ProjectServiceAccountManager`
+  + :class:`gitlab.v4.objects.ProjectServiceAccountAccessToken`
+  + :class:`gitlab.v4.objects.ProjectServiceAccountAccessTokenManager`
+
+* GitLab API: https://docs.gitlab.com/api/service_accounts/
+
+Instance service accounts
+-------------------------
+
+List instance service accounts::
+
+    accounts = gl.service_accounts.list()
+
+Create an instance service account::
+
+    sa = gl.service_accounts.create({})
+    # with optional attributes
+    sa = gl.service_accounts.create({"name": "my-bot", "username": "my-bot", 
"email": "[email protected]"})
+
+Update an instance service account::
+
+    gl.service_accounts.update(sa.id, {"name": "renamed-bot"})
+    # or via the object
+    sa.name = "renamed-bot"
+    sa.save()
+
+Group service accounts
+----------------------
+
+List group service accounts::
+
+    accounts = group.service_accounts.list()
+
+Create a group service account::
+
+    sa = group.service_accounts.create({})
+    # with optional attributes
+    sa = group.service_accounts.create({"name": "ci-bot", "username": 
"ci-bot"})
+
+Update a group service account::
+
+    group.service_accounts.update(sa.id, {"name": "renamed-bot"})
+    # or via the object
+    sa.name = "renamed-bot"
+    sa.save()
+
+Delete a group service account::
+
+    group.service_accounts.delete(sa.id)
+    # or via the object
+    sa.delete()
+
+Group service account personal access tokens
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+List tokens for a group service account::
+
+    tokens = sa.access_tokens.list()
+
+Create a token for a group service account::
+
+    token = sa.access_tokens.create({
+        "name": "ci-token",
+        "scopes": ["api"],
+        "expires_at": "2026-01-01",
+    })
+    print(token.token)
+
+Rotate a token::
+
+    token.rotate()
+    print(token.token)
+    # or directly using a token ID
+    new_token = sa.access_tokens.rotate(token.id)
+    print(new_token["token"])
+
+Revoke a token::
+
+    sa.access_tokens.delete(token.id)
+    # or via the object
+    token.delete()
+
+Project service accounts
+------------------------
+
+List project service accounts::
+
+    accounts = project.service_accounts.list()
+
+Create a project service account::
+
+    sa = project.service_accounts.create({})
+    # with optional attributes
+    sa = project.service_accounts.create({"name": "ci-bot", "username": 
"ci-bot"})
+
+Update a project service account::
+
+    project.service_accounts.update(sa.id, {"name": "renamed-bot"})
+    # or via the object
+    sa.name = "renamed-bot"
+    sa.save()
+
+Delete a project service account::
+
+    project.service_accounts.delete(sa.id)
+    # or via the object
+    sa.delete()
+
+Project service account personal access tokens
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+List tokens for a project service account::
+
+    tokens = sa.access_tokens.list()
+
+Create a token for a project service account::
+
+    token = sa.access_tokens.create({
+        "name": "ci-token",
+        "scopes": ["read_repository"],
+        "expires_at": "2026-01-01",
+    })
+    print(token.token)
+
+Rotate a token::
+
+    token.rotate()
+    print(token.token)
+    # or directly using a token ID
+    new_token = sa.access_tokens.rotate(token.id)
+    print(new_token["token"])
+
+Revoke a token::
+
+    sa.access_tokens.delete(token.id)
+    # or via the object
+    token.delete()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/gitlab/_version.py 
new/python_gitlab-8.3.0/gitlab/_version.py
--- old/python_gitlab-8.2.0/gitlab/_version.py  2026-03-28 02:49:39.000000000 
+0100
+++ new/python_gitlab-8.3.0/gitlab/_version.py  2026-04-28 04:32:01.000000000 
+0200
@@ -3,4 +3,4 @@
 __email__ = "[email protected]"
 __license__ = "LGPL3"
 __title__ = "python-gitlab"
-__version__ = "8.2.0"
+__version__ = "8.3.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/gitlab/client.py 
new/python_gitlab-8.3.0/gitlab/client.py
--- old/python_gitlab-8.2.0/gitlab/client.py    2026-03-28 02:49:29.000000000 
+0100
+++ new/python_gitlab-8.3.0/gitlab/client.py    2026-04-28 04:31:51.000000000 
+0200
@@ -211,6 +211,8 @@
         """See :class:`~gitlab.v4.objects.PersonalAccessTokenManager`"""
         self.topics = objects.TopicManager(self)
         """See :class:`~gitlab.v4.objects.TopicManager`"""
+        self.service_accounts = objects.ServiceAccountManager(self)
+        """See :class:`~gitlab.v4.objects.ServiceAccountManager`"""
         self.statistics = objects.ApplicationStatisticsManager(self)
         """See :class:`~gitlab.v4.objects.ApplicationStatisticsManager`"""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/gitlab/mixins.py 
new/python_gitlab-8.3.0/gitlab/mixins.py
--- old/python_gitlab-8.2.0/gitlab/mixins.py    2026-03-28 02:49:29.000000000 
+0100
+++ new/python_gitlab-8.3.0/gitlab/mixins.py    2026-04-28 04:31:51.000000000 
+0200
@@ -619,6 +619,8 @@
             "PersonalAccessTokenManager",
             "GroupAccessTokenManager",
             "ProjectAccessTokenManager",
+            "GroupServiceAccountAccessTokenManager",
+            "ProjectServiceAccountAccessTokenManager",
         ),
         optional=("expires_at",),
     )
@@ -656,7 +658,13 @@
     manager: base.RESTManager[Any]
 
     @cli.register_custom_action(
-        cls_names=("PersonalAccessToken", "GroupAccessToken", 
"ProjectAccessToken"),
+        cls_names=(
+            "PersonalAccessToken",
+            "GroupAccessToken",
+            "ProjectAccessToken",
+            "GroupServiceAccountAccessToken",
+            "ProjectServiceAccountAccessToken",
+        ),
         optional=("expires_at",),
     )
     @exc.on_http_error(exc.GitlabRotateError)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/gitlab/v4/objects/projects.py 
new/python_gitlab-8.3.0/gitlab/v4/objects/projects.py
--- old/python_gitlab-8.2.0/gitlab/v4/objects/projects.py       2026-03-28 
02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/gitlab/v4/objects/projects.py       2026-04-28 
04:31:51.000000000 +0200
@@ -91,6 +91,7 @@
 from .resource_groups import ProjectResourceGroupManager
 from .runners import ProjectRunnerManager  # noqa: F401
 from .secure_files import ProjectSecureFileManager  # noqa: F401
+from .service_accounts import ProjectServiceAccountManager  # noqa: F401
 from .snippets import ProjectSnippetManager  # noqa: F401
 from .statistics import (  # noqa: F401
     ProjectAdditionalStatisticsManager,
@@ -251,6 +252,7 @@
     repositories: ProjectRegistryRepositoryManager
     runners: ProjectRunnerManager
     secure_files: ProjectSecureFileManager
+    service_accounts: ProjectServiceAccountManager
     services: ProjectServiceManager
     snippets: ProjectSnippetManager
     external_status_checks: ProjectExternalStatusCheckManager
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.2.0/gitlab/v4/objects/service_accounts.py 
new/python_gitlab-8.3.0/gitlab/v4/objects/service_accounts.py
--- old/python_gitlab-8.2.0/gitlab/v4/objects/service_accounts.py       
2026-03-28 02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/gitlab/v4/objects/service_accounts.py       
2026-04-28 04:31:51.000000000 +0200
@@ -1,20 +1,155 @@
+"""
+GitLab API: https://docs.gitlab.com/api/service_accounts/
+"""
+
 from gitlab.base import RESTObject
-from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, 
ObjectDeleteMixin
-from gitlab.types import RequiredOptional
+from gitlab.mixins import (
+    CreateMixin,
+    DeleteMixin,
+    ListMixin,
+    ObjectDeleteMixin,
+    ObjectRotateMixin,
+    RotateMixin,
+    SaveMixin,
+    UpdateMethod,
+    UpdateMixin,
+)
+from gitlab.types import ArrayAttribute, RequiredOptional
+
+__all__ = [
+    "ServiceAccount",
+    "ServiceAccountManager",
+    "GroupServiceAccount",
+    "GroupServiceAccountManager",
+    "GroupServiceAccountAccessToken",
+    "GroupServiceAccountAccessTokenManager",
+    "ProjectServiceAccount",
+    "ProjectServiceAccountManager",
+    "ProjectServiceAccountAccessToken",
+    "ProjectServiceAccountAccessTokenManager",
+]
+
+_SA_ACCOUNT_ATTRS = RequiredOptional(optional=("name", "username", "email"))
+
+_SA_TOKEN_CREATE_ATTRS = RequiredOptional(
+    required=("name", "scopes"), optional=("description", "expires_at")
+)
+
+_SA_TOKEN_LIST_FILTERS = (
+    "created_after",
+    "created_before",
+    "expires_after",
+    "expires_before",
+    "last_used_after",
+    "last_used_before",
+    "revoked",
+    "search",
+    "sort",
+    "state",
+)
 
-__all__ = ["GroupServiceAccount", "GroupServiceAccountManager"]
 
+# ---------------------------------------------------------------------------
+# Instance-level service accounts
+# ---------------------------------------------------------------------------
 
-class GroupServiceAccount(ObjectDeleteMixin, RESTObject):
+
+class ServiceAccount(SaveMixin, RESTObject):
     pass
 
 
+class ServiceAccountManager(
+    CreateMixin[ServiceAccount], ListMixin[ServiceAccount], 
UpdateMixin[ServiceAccount]
+):
+    _path = "/service_accounts"
+    _obj_cls = ServiceAccount
+    _create_attrs = _SA_ACCOUNT_ATTRS
+    _update_attrs = _SA_ACCOUNT_ATTRS
+    _update_method = UpdateMethod.PATCH
+    _list_filters = ("order_by", "sort")
+
+
+# ---------------------------------------------------------------------------
+# Group-level service accounts
+# ---------------------------------------------------------------------------
+
+
+class GroupServiceAccountAccessToken(ObjectDeleteMixin, ObjectRotateMixin, 
RESTObject):
+    pass
+
+
+class GroupServiceAccountAccessTokenManager(
+    CreateMixin[GroupServiceAccountAccessToken],
+    DeleteMixin[GroupServiceAccountAccessToken],
+    ListMixin[GroupServiceAccountAccessToken],
+    RotateMixin[GroupServiceAccountAccessToken],
+):
+    _path = 
"/groups/{group_id}/service_accounts/{user_id}/personal_access_tokens"
+    _obj_cls = GroupServiceAccountAccessToken
+    _from_parent_attrs = {"group_id": "group_id", "user_id": "id"}
+    _create_attrs = _SA_TOKEN_CREATE_ATTRS
+    _types = {"scopes": ArrayAttribute}
+    _list_filters = _SA_TOKEN_LIST_FILTERS
+
+
+class GroupServiceAccount(SaveMixin, ObjectDeleteMixin, RESTObject):
+    access_tokens: GroupServiceAccountAccessTokenManager
+
+
 class GroupServiceAccountManager(
     CreateMixin[GroupServiceAccount],
     DeleteMixin[GroupServiceAccount],
     ListMixin[GroupServiceAccount],
+    UpdateMixin[GroupServiceAccount],
 ):
     _path = "/groups/{group_id}/service_accounts"
     _obj_cls = GroupServiceAccount
     _from_parent_attrs = {"group_id": "id"}
-    _create_attrs = RequiredOptional(optional=("name", "username"))
+    _create_attrs = _SA_ACCOUNT_ATTRS
+    _update_attrs = _SA_ACCOUNT_ATTRS
+    _update_method = UpdateMethod.PATCH
+    _list_filters = ("order_by", "sort")
+
+
+# ---------------------------------------------------------------------------
+# Project-level service accounts
+# ---------------------------------------------------------------------------
+
+
+class ProjectServiceAccountAccessToken(
+    ObjectDeleteMixin, ObjectRotateMixin, RESTObject
+):
+    pass
+
+
+class ProjectServiceAccountAccessTokenManager(
+    CreateMixin[ProjectServiceAccountAccessToken],
+    DeleteMixin[ProjectServiceAccountAccessToken],
+    ListMixin[ProjectServiceAccountAccessToken],
+    RotateMixin[ProjectServiceAccountAccessToken],
+):
+    _path = 
"/projects/{project_id}/service_accounts/{user_id}/personal_access_tokens"
+    _obj_cls = ProjectServiceAccountAccessToken
+    _from_parent_attrs = {"project_id": "project_id", "user_id": "id"}
+    _create_attrs = _SA_TOKEN_CREATE_ATTRS
+    _types = {"scopes": ArrayAttribute}
+    _list_filters = _SA_TOKEN_LIST_FILTERS
+
+
+class ProjectServiceAccount(SaveMixin, ObjectDeleteMixin, RESTObject):
+    access_tokens: ProjectServiceAccountAccessTokenManager
+
+
+class ProjectServiceAccountManager(
+    CreateMixin[ProjectServiceAccount],
+    DeleteMixin[ProjectServiceAccount],
+    ListMixin[ProjectServiceAccount],
+    UpdateMixin[ProjectServiceAccount],
+):
+    _path = "/projects/{project_id}/service_accounts"
+    _obj_cls = ProjectServiceAccount
+    _from_parent_attrs = {"project_id": "id"}
+    _create_attrs = _SA_ACCOUNT_ATTRS
+    _update_attrs = _SA_ACCOUNT_ATTRS
+    _update_method = UpdateMethod.PATCH
+    _list_filters = ("order_by", "sort")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/gitlab/v4/objects/variables.py 
new/python_gitlab-8.3.0/gitlab/v4/objects/variables.py
--- old/python_gitlab-8.2.0/gitlab/v4/objects/variables.py      2026-03-28 
02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/gitlab/v4/objects/variables.py      2026-04-28 
04:31:51.000000000 +0200
@@ -27,10 +27,19 @@
     _path = "/admin/ci/variables"
     _obj_cls = Variable
     _create_attrs = RequiredOptional(
-        required=("key", "value"), optional=("protected", "variable_type", 
"masked")
+        required=("key", "value"),
+        optional=("description", "masked", "protected", "raw", 
"variable_type"),
     )
     _update_attrs = RequiredOptional(
-        required=("key", "value"), optional=("protected", "variable_type", 
"masked")
+        required=("key",),
+        optional=(
+            "description",
+            "masked",
+            "protected",
+            "raw",
+            "value",
+            "variable_type",
+        ),
     )
 
 
@@ -43,10 +52,28 @@
     _obj_cls = GroupVariable
     _from_parent_attrs = {"group_id": "id"}
     _create_attrs = RequiredOptional(
-        required=("key", "value"), optional=("protected", "variable_type", 
"masked")
+        required=("key", "value"),
+        optional=(
+            "description",
+            "environment_scope",
+            "masked",
+            "masked_and_hidden",
+            "protected",
+            "raw",
+            "variable_type",
+        ),
     )
     _update_attrs = RequiredOptional(
-        required=("key", "value"), optional=("protected", "variable_type", 
"masked")
+        required=("key",),
+        optional=(
+            "description",
+            "environment_scope",
+            "masked",
+            "protected",
+            "raw",
+            "value",
+            "variable_type",
+        ),
     )
 
 
@@ -60,9 +87,25 @@
     _from_parent_attrs = {"project_id": "id"}
     _create_attrs = RequiredOptional(
         required=("key", "value"),
-        optional=("protected", "variable_type", "masked", "environment_scope"),
+        optional=(
+            "description",
+            "environment_scope",
+            "masked",
+            "masked_and_hidden",
+            "protected",
+            "raw",
+            "variable_type",
+        ),
     )
     _update_attrs = RequiredOptional(
-        required=("key", "value"),
-        optional=("protected", "variable_type", "masked", "environment_scope"),
+        required=("key",),
+        optional=(
+            "description",
+            "environment_scope",
+            "masked",
+            "protected",
+            "raw",
+            "value",
+            "variable_type",
+        ),
     )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/python_gitlab.egg-info/PKG-INFO 
new/python_gitlab-8.3.0/python_gitlab.egg-info/PKG-INFO
--- old/python_gitlab-8.2.0/python_gitlab.egg-info/PKG-INFO     2026-03-28 
02:49:45.000000000 +0100
+++ new/python_gitlab-8.3.0/python_gitlab.egg-info/PKG-INFO     2026-04-28 
04:32:07.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: python-gitlab
-Version: 8.2.0
+Version: 8.3.0
 Summary: The python wrapper for the GitLab REST and GraphQL APIs.
 Author-email: Gauvain Pocentek <[email protected]>
 Maintainer-email: John Villalovos <[email protected]>, Max Wittig 
<[email protected]>, Nejc Habjan <[email protected]>, Roger Meier 
<[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.2.0/python_gitlab.egg-info/SOURCES.txt 
new/python_gitlab-8.3.0/python_gitlab.egg-info/SOURCES.txt
--- old/python_gitlab-8.2.0/python_gitlab.egg-info/SOURCES.txt  2026-03-28 
02:49:46.000000000 +0100
+++ new/python_gitlab-8.3.0/python_gitlab.egg-info/SOURCES.txt  2026-04-28 
04:32:07.000000000 +0200
@@ -93,6 +93,7 @@
 docs/gl_objects/runners.rst
 docs/gl_objects/search.rst
 docs/gl_objects/secure_files.rst
+docs/gl_objects/service_accounts.rst
 docs/gl_objects/settings.rst
 docs/gl_objects/sidekiq.rst
 docs/gl_objects/snippets.rst
@@ -359,6 +360,7 @@
 tests/unit/objects/test_resource_state_events.py
 tests/unit/objects/test_runners.py
 tests/unit/objects/test_secure_files.py
+tests/unit/objects/test_service_accounts.py
 tests/unit/objects/test_services.py
 tests/unit/objects/test_snippets.py
 tests/unit/objects/test_statistics.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/requirements-lint.txt 
new/python_gitlab-8.3.0/requirements-lint.txt
--- old/python_gitlab-8.2.0/requirements-lint.txt       2026-03-28 
02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/requirements-lint.txt       2026-04-28 
04:31:51.000000000 +0200
@@ -1,14 +1,14 @@
 -r requirements.txt
 argcomplete==2.0.0
 black==26.3.1
-commitizen==4.13.9
+commitizen==4.13.10
 flake8==7.3.0
 isort==8.0.1
-mypy==1.19.1
+mypy==1.20.2
 pylint==4.0.5
-pytest==9.0.2
+pytest==9.0.3
 responses==0.26.0
-respx==0.22.0
-types-PyYAML==6.0.12.20250915
-types-requests==2.32.4.20260107
-types-setuptools==82.0.0.20260210
+respx==0.23.1
+types-PyYAML==6.0.12.20260408
+types-requests==2.33.0.20260408
+types-setuptools==82.0.0.20260408
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/requirements-precommit.txt 
new/python_gitlab-8.3.0/requirements-precommit.txt
--- old/python_gitlab-8.2.0/requirements-precommit.txt  2026-03-28 
02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/requirements-precommit.txt  2026-04-28 
04:31:51.000000000 +0200
@@ -1 +1 @@
-pre-commit==4.5.1
+pre-commit==4.6.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/requirements-test.txt 
new/python_gitlab-8.3.0/requirements-test.txt
--- old/python_gitlab-8.2.0/requirements-test.txt       2026-03-28 
02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/requirements-test.txt       2026-04-28 
04:31:51.000000000 +0200
@@ -1,13 +1,13 @@
 -r requirements.txt
-anyio==4.12.1
-build==1.4.0
+anyio==4.13.0
+build==1.4.4
 coverage==7.13.5
 pytest-console-scripts==1.4.1
 pytest-cov==7.1.0
 pytest-github-actions-annotate-failures==0.4.0
-pytest==9.0.2
+pytest==9.0.3
 PyYaml==6.0.3
 responses==0.26.0
-respx==0.22.0
+respx==0.23.1
 trio==0.33.0
-wheel==0.46.3
+wheel==0.47.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.2.0/requirements.txt 
new/python_gitlab-8.3.0/requirements.txt
--- old/python_gitlab-8.2.0/requirements.txt    2026-03-28 02:49:29.000000000 
+0100
+++ new/python_gitlab-8.3.0/requirements.txt    2026-04-28 04:31:51.000000000 
+0200
@@ -1,4 +1,4 @@
 gql==4.0.0
 httpx==0.28.1
-requests==2.33.0
+requests==2.33.1
 requests-toolbelt==1.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.2.0/tests/functional/api/test_variables.py 
new/python_gitlab-8.3.0/tests/functional/api/test_variables.py
--- old/python_gitlab-8.2.0/tests/functional/api/test_variables.py      
2026-03-28 02:49:29.000000000 +0100
+++ new/python_gitlab-8.3.0/tests/functional/api/test_variables.py      
2026-04-28 04:31:51.000000000 +0200
@@ -43,3 +43,39 @@
     assert variable.value == "new_value1"
 
     variable.delete()
+
+
+def test_hidden_group_variables(group):
+    variable = group.variables.create(
+        {"key": "key1", "value": "secret_value", "masked_and_hidden": True}
+    )
+
+    variable = group.variables.get(variable.key)
+    assert variable.value is None
+    assert variable.description is None
+    assert variable in group.variables.list()
+
+    variable.description = "new_description"
+    variable.save()
+    variable = group.variables.get(variable.key)
+    assert variable.description == "new_description"
+
+    variable.delete()
+
+
+def test_hidden_project_variables(project):
+    variable = project.variables.create(
+        {"key": "key1", "value": "secret_value", "masked_and_hidden": True}
+    )
+
+    variable = project.variables.get(variable.key)
+    assert variable.value is None
+    assert variable.description is None
+    assert variable in project.variables.list()
+
+    variable.description = "new_description"
+    variable.save()
+    variable = project.variables.get(variable.key)
+    assert variable.description == "new_description"
+
+    variable.delete()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.2.0/tests/unit/objects/test_service_accounts.py 
new/python_gitlab-8.3.0/tests/unit/objects/test_service_accounts.py
--- old/python_gitlab-8.2.0/tests/unit/objects/test_service_accounts.py 
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.3.0/tests/unit/objects/test_service_accounts.py 
2026-04-28 04:31:51.000000000 +0200
@@ -0,0 +1,592 @@
+"""
+GitLab API: https://docs.gitlab.com/api/service_accounts/
+"""
+
+import pytest
+import responses
+
+from gitlab.v4.objects import (
+    GroupServiceAccount,
+    GroupServiceAccountAccessToken,
+    ProjectServiceAccount,
+    ProjectServiceAccountAccessToken,
+    ServiceAccount,
+)
+
+# ---------------------------------------------------------------------------
+# Fixtures – instance-level service accounts
+# ---------------------------------------------------------------------------
+
+instance_sa_content = {
+    "id": 57,
+    "username": "service_account_abc123",
+    "name": "Service account user",
+    "email": "[email protected]",
+}
+
+
[email protected]
+def resp_list_service_accounts():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            url="http://localhost/api/v4/service_accounts";,
+            json=[instance_sa_content],
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_create_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            url="http://localhost/api/v4/service_accounts";,
+            json=instance_sa_content,
+            content_type="application/json",
+            status=201,
+        )
+        yield rsps
+
+
[email protected]
+def resp_update_service_account():
+    updated = {**instance_sa_content, "name": "Renamed account"}
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.PATCH,
+            
url=f"http://localhost/api/v4/service_accounts/{instance_sa_content['id']}",
+            json=updated,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_create_and_save_service_account():
+    updated = {**instance_sa_content, "name": "Renamed account"}
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            url="http://localhost/api/v4/service_accounts";,
+            json=instance_sa_content,
+            content_type="application/json",
+            status=201,
+        )
+        rsps.add(
+            method=responses.PATCH,
+            
url=f"http://localhost/api/v4/service_accounts/{instance_sa_content['id']}",
+            json=updated,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
+# ---------------------------------------------------------------------------
+# Fixtures – group service accounts
+# ---------------------------------------------------------------------------
+
+group_sa_content = {
+    "id": 42,
+    "username": "group-service-account",
+    "name": "Group Service Account",
+    "email": "[email protected]",
+}
+
+group_sa_updated = {**group_sa_content, "name": "Renamed Group SA"}
+
+sa_token_content = {
+    "id": 1,
+    "name": "my-token",
+    "scopes": ["api", "read_api"],
+    "user_id": 42,
+    "revoked": False,
+    "active": True,
+    "expires_at": "2025-12-31",
+    "token": "glpat-secret",
+}
+
+sa_token_rotated = {**sa_token_content, "token": "glpat-rotated"}
+
+
[email protected]
+def resp_list_group_service_accounts():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            url="http://localhost/api/v4/groups/1/service_accounts";,
+            json=[group_sa_content],
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_create_group_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            url="http://localhost/api/v4/groups/1/service_accounts";,
+            json=group_sa_content,
+            content_type="application/json",
+            status=201,
+        )
+        yield rsps
+
+
[email protected]
+def resp_update_group_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.PATCH,
+            url="http://localhost/api/v4/groups/1/service_accounts/42";,
+            json=group_sa_updated,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_delete_group_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.DELETE,
+            url="http://localhost/api/v4/groups/1/service_accounts/42";,
+            status=204,
+        )
+        yield rsps
+
+
[email protected]
+def resp_list_group_sa_tokens():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            
url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens";,
+            json=[sa_token_content],
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_create_group_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            
url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens";,
+            json=sa_token_content,
+            content_type="application/json",
+            status=201,
+        )
+        yield rsps
+
+
[email protected]
+def resp_delete_group_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.DELETE,
+            
url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens/1";,
+            status=204,
+        )
+        yield rsps
+
+
[email protected]
+def resp_list_and_delete_group_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            
url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens";,
+            json=[sa_token_content],
+            content_type="application/json",
+            status=200,
+        )
+        rsps.add(
+            method=responses.DELETE,
+            
url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens/1";,
+            status=204,
+        )
+        yield rsps
+
+
[email protected]
+def resp_rotate_group_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            
url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens/1/rotate";,
+            json=sa_token_rotated,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
+# ---------------------------------------------------------------------------
+# Helper – lazy service account under group 1 with id 42
+# ---------------------------------------------------------------------------
+
+
[email protected]
+def group_service_account(gl):
+    manager = gl.groups.get(1, lazy=True).service_accounts
+    return GroupServiceAccount(manager, group_sa_content)
+
+
+# ---------------------------------------------------------------------------
+# Tests – instance-level service accounts
+# ---------------------------------------------------------------------------
+
+
+def test_list_service_accounts(gl, resp_list_service_accounts):
+    accounts = gl.service_accounts.list()
+    assert len(accounts) == 1
+    assert isinstance(accounts[0], ServiceAccount)
+    assert accounts[0].id == 57
+    assert accounts[0].username == "service_account_abc123"
+
+
+def test_create_service_account_with_defaults(gl, resp_create_service_account):
+    sa = gl.service_accounts.create({})
+    assert isinstance(sa, ServiceAccount)
+    assert sa.id == 57
+    assert sa.name == "Service account user"
+
+
+def test_create_service_account_with_attrs(gl, resp_create_service_account):
+    sa = gl.service_accounts.create(
+        {"name": "Service account user", "username": "service_account_abc123"}
+    )
+    assert isinstance(sa, ServiceAccount)
+    assert sa.username == "service_account_abc123"
+
+
+def test_update_service_account(gl, resp_update_service_account):
+    updated = gl.service_accounts.update(57, {"name": "Renamed account"})
+    assert updated["name"] == "Renamed account"
+
+
+def test_save_service_account(gl, resp_create_and_save_service_account):
+    sa = gl.service_accounts.create({})
+    sa.name = "Renamed account"
+    sa.save()
+
+
+# ---------------------------------------------------------------------------
+# Tests – group service accounts
+# ---------------------------------------------------------------------------
+
+
+def test_list_group_service_accounts(gl, resp_list_group_service_accounts):
+    accounts = gl.groups.get(1, lazy=True).service_accounts.list()
+    assert len(accounts) == 1
+    assert isinstance(accounts[0], GroupServiceAccount)
+    assert accounts[0].id == 42
+
+
+def test_create_group_service_account(gl, resp_create_group_service_account):
+    sa = gl.groups.get(1, lazy=True).service_accounts.create(
+        {"name": "Group Service Account", "username": "group-service-account"}
+    )
+    assert isinstance(sa, GroupServiceAccount)
+    assert sa.id == 42
+    assert sa.username == "group-service-account"
+
+
+def test_update_group_service_account(gl, resp_update_group_service_account):
+    updated = gl.groups.get(1, lazy=True).service_accounts.update(
+        42, {"name": "Renamed Group SA"}
+    )
+    assert updated["name"] == "Renamed Group SA"
+
+
+def test_save_group_service_account(
+    group_service_account, resp_update_group_service_account
+):
+    group_service_account.name = "Renamed Group SA"
+    group_service_account.save()
+
+
+def test_delete_group_service_account(gl, resp_delete_group_service_account):
+    gl.groups.get(1, lazy=True).service_accounts.delete(42)
+
+
+def test_delete_group_service_account_via_object(
+    group_service_account, resp_delete_group_service_account
+):
+    group_service_account.delete()
+
+
+# ---------------------------------------------------------------------------
+# Tests – group service account personal access tokens
+# ---------------------------------------------------------------------------
+
+
+def test_list_group_sa_tokens(group_service_account, 
resp_list_group_sa_tokens):
+    tokens = group_service_account.access_tokens.list()
+    assert len(tokens) == 1
+    assert isinstance(tokens[0], GroupServiceAccountAccessToken)
+    assert tokens[0].name == "my-token"
+    assert tokens[0].scopes == ["api", "read_api"]
+
+
+def test_create_group_sa_token(group_service_account, 
resp_create_group_sa_token):
+    token = group_service_account.access_tokens.create(
+        {"name": "my-token", "scopes": ["api", "read_api"]}
+    )
+    assert isinstance(token, GroupServiceAccountAccessToken)
+    assert token.id == 1
+    assert token.token == "glpat-secret"
+
+
+def test_delete_group_sa_token(group_service_account, 
resp_delete_group_sa_token):
+    group_service_account.access_tokens.delete(1)
+
+
+def test_delete_group_sa_token_via_object(
+    group_service_account, resp_list_and_delete_group_sa_token
+):
+    token = group_service_account.access_tokens.list()[0]
+    token.delete()
+
+
+def test_rotate_group_sa_token(group_service_account, 
resp_rotate_group_sa_token):
+    token = GroupServiceAccountAccessToken(
+        group_service_account.access_tokens, sa_token_content
+    )
+    token.rotate()
+    assert token.token == "glpat-rotated"
+
+
+def test_rotate_group_sa_token_via_manager(
+    group_service_account, resp_rotate_group_sa_token
+):
+    result = group_service_account.access_tokens.rotate(1)
+    assert result["token"] == "glpat-rotated"
+
+
+# ---------------------------------------------------------------------------
+# Fixtures – project service accounts
+# ---------------------------------------------------------------------------
+
+proj_sa_content = {
+    "id": 99,
+    "username": "project-service-account",
+    "name": "Project Service Account",
+    "email": "[email protected]",
+}
+
+proj_sa_updated = {**proj_sa_content, "name": "Renamed Project SA"}
+
+proj_sa_token_content = {
+    "id": 2,
+    "name": "proj-token",
+    "scopes": ["read_api"],
+    "user_id": 99,
+    "revoked": False,
+    "active": True,
+    "expires_at": "2025-12-31",
+    "token": "glpat-proj-secret",
+}
+
+proj_sa_token_rotated = {**proj_sa_token_content, "token": 
"glpat-proj-rotated"}
+
+
[email protected]
+def resp_list_project_service_accounts():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            url="http://localhost/api/v4/projects/1/service_accounts";,
+            json=[proj_sa_content],
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_create_project_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            url="http://localhost/api/v4/projects/1/service_accounts";,
+            json=proj_sa_content,
+            content_type="application/json",
+            status=201,
+        )
+        yield rsps
+
+
[email protected]
+def resp_update_project_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.PATCH,
+            url="http://localhost/api/v4/projects/1/service_accounts/99";,
+            json=proj_sa_updated,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_delete_project_service_account():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.DELETE,
+            url="http://localhost/api/v4/projects/1/service_accounts/99";,
+            status=204,
+        )
+        yield rsps
+
+
[email protected]
+def resp_list_project_sa_tokens():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            
url="http://localhost/api/v4/projects/1/service_accounts/99/personal_access_tokens";,
+            json=[proj_sa_token_content],
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def resp_create_project_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            
url="http://localhost/api/v4/projects/1/service_accounts/99/personal_access_tokens";,
+            json=proj_sa_token_content,
+            content_type="application/json",
+            status=201,
+        )
+        yield rsps
+
+
[email protected]
+def resp_delete_project_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.DELETE,
+            
url="http://localhost/api/v4/projects/1/service_accounts/99/personal_access_tokens/2";,
+            status=204,
+        )
+        yield rsps
+
+
[email protected]
+def resp_rotate_project_sa_token():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            
url="http://localhost/api/v4/projects/1/service_accounts/99/personal_access_tokens/2/rotate";,
+            json=proj_sa_token_rotated,
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
[email protected]
+def project_service_account(gl):
+    manager = gl.projects.get(1, lazy=True).service_accounts
+    return ProjectServiceAccount(manager, proj_sa_content)
+
+
+# ---------------------------------------------------------------------------
+# Tests – project service accounts
+# ---------------------------------------------------------------------------
+
+
+def test_list_project_service_accounts(gl, resp_list_project_service_accounts):
+    accounts = gl.projects.get(1, lazy=True).service_accounts.list()
+    assert len(accounts) == 1
+    assert isinstance(accounts[0], ProjectServiceAccount)
+    assert accounts[0].id == 99
+
+
+def test_create_project_service_account(gl, 
resp_create_project_service_account):
+    sa = gl.projects.get(1, lazy=True).service_accounts.create(
+        {"name": "Project Service Account"}
+    )
+    assert isinstance(sa, ProjectServiceAccount)
+    assert sa.id == 99
+    assert sa.username == "project-service-account"
+
+
+def test_update_project_service_account(gl, 
resp_update_project_service_account):
+    updated = gl.projects.get(1, lazy=True).service_accounts.update(
+        99, {"name": "Renamed Project SA"}
+    )
+    assert updated["name"] == "Renamed Project SA"
+
+
+def test_save_project_service_account(
+    project_service_account, resp_update_project_service_account
+):
+    project_service_account.name = "Renamed Project SA"
+    project_service_account.save()
+
+
+def test_delete_project_service_account(gl, 
resp_delete_project_service_account):
+    gl.projects.get(1, lazy=True).service_accounts.delete(99)
+
+
+def test_delete_project_service_account_via_object(
+    project_service_account, resp_delete_project_service_account
+):
+    project_service_account.delete()
+
+
+# ---------------------------------------------------------------------------
+# Tests – project service account personal access tokens
+# ---------------------------------------------------------------------------
+
+
+def test_list_project_sa_tokens(project_service_account, 
resp_list_project_sa_tokens):
+    tokens = project_service_account.access_tokens.list()
+    assert len(tokens) == 1
+    assert isinstance(tokens[0], ProjectServiceAccountAccessToken)
+    assert tokens[0].name == "proj-token"
+
+
+def test_create_project_sa_token(project_service_account, 
resp_create_project_sa_token):
+    token = project_service_account.access_tokens.create(
+        {"name": "proj-token", "scopes": ["read_api"]}
+    )
+    assert isinstance(token, ProjectServiceAccountAccessToken)
+    assert token.id == 2
+    assert token.token == "glpat-proj-secret"
+
+
+def test_delete_project_sa_token(project_service_account, 
resp_delete_project_sa_token):
+    project_service_account.access_tokens.delete(2)
+
+
+def test_rotate_project_sa_token(project_service_account, 
resp_rotate_project_sa_token):
+    token = ProjectServiceAccountAccessToken(
+        project_service_account.access_tokens, proj_sa_token_content
+    )
+    token.rotate()
+    assert token.token == "glpat-proj-rotated"
+
+
+def test_rotate_project_sa_token_via_manager(
+    project_service_account, resp_rotate_project_sa_token
+):
+    result = project_service_account.access_tokens.rotate(2)
+    assert result["token"] == "glpat-proj-rotated"

Reply via email to