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"