← 返回首页
feat(api): add support for package protection rules · python-gitlab/python-gitlab@6b37811 · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

Commit 6b37811

Browse files
authored andcommitted
feat(api): add support for package protection rules
1 parent c378817 commit 6b37811

7 files changed

Lines changed: 214 additions & 1 deletion

File tree

‎docs/api-objects.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ API examples
4949
gl_objects/project_access_tokens
5050
gl_objects/protected_branches
5151
gl_objects/protected_environments
52+
gl_objects/protected_packages
5253
gl_objects/releases
5354
gl_objects/runners
5455
gl_objects/remote_mirrors
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
##################
2+
Protected packages
3+
##################
4+
5+
You can list and manage package protection rules in a project.
6+
7+
References
8+
----------
9+
10+
* v4 API:
11+
12+
+ :class:`gitlab.v4.objects.ProjectPackageProtectionRule`
13+
+ :class:`gitlab.v4.objects.ProjectPackageProtectionRuleManager`
14+
+ :attr:`gitlab.v4.objects.Project.package_protection_rules`
15+
16+
* GitLab API: https://docs.gitlab.com/ee/api/project_packages_protection_rules.html
17+
18+
Examples
19+
--------
20+
21+
List the package protection rules for a project::
22+
23+
package_rules = project.package_protection_rules.list()
24+
25+
Create a package protection rule::
26+
27+
package_rule = project.package_protection_rules.create(
28+
{
29+
'package_name_pattern': 'v*',
30+
'package_type': 'npm',
31+
'minimum_access_level_for_push': 'maintainer'
32+
}
33+
)
34+
35+
Update a package protection rule::
36+
37+
package_rule.minimum_access_level_for_push = 'developer'
38+
package_rule.save()
39+
40+
Delete a package protection rule::
41+
42+
package_rule = project.package_protection_rules.delete(package_rule.id)
43+
# or
44+
package_rule.delete()

‎gitlab/v4/objects/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from .namespaces import *
4747
from .notes import *
4848
from .notification_settings import *
49+
from .package_protection_rules import *
4950
from .packages import *
5051
from .pages import *
5152
from .personal_access_tokens import *
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from gitlab.base import RESTManager, RESTObject
2+
from gitlab.mixins import (
3+
CreateMixin,
4+
DeleteMixin,
5+
ListMixin,
6+
ObjectDeleteMixin,
7+
SaveMixin,
8+
UpdateMethod,
9+
UpdateMixin,
10+
)
11+
from gitlab.types import RequiredOptional
12+
13+
__all__ = [
14+
"ProjectPackageProtectionRule",
15+
"ProjectPackageProtectionRuleManager",
16+
]
17+
18+
19+
class ProjectPackageProtectionRule(ObjectDeleteMixin, SaveMixin, RESTObject):
20+
_repr_attr = "name"
21+
22+
23+
class ProjectPackageProtectionRuleManager(
24+
ListMixin, CreateMixin, DeleteMixin, UpdateMixin, RESTManager
25+
):
26+
_path = "/projects/{project_id}/packages/protection/rules"
27+
_obj_cls = ProjectPackageProtectionRule
28+
_from_parent_attrs = {"project_id": "id"}
29+
_create_attrs = RequiredOptional(
30+
required=(
31+
"package_name_pattern",
32+
"package_type",
33+
"minimum_access_level_for_push",
34+
),
35+
)
36+
_update_method = UpdateMethod.PATCH

‎gitlab/v4/objects/projects.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
from .milestones import ProjectMilestoneManager # noqa: F401
7676
from .notes import ProjectNoteManager # noqa: F401
7777
from .notification_settings import ProjectNotificationSettingsManager # noqa: F401
78+
from .package_protection_rules import ProjectPackageProtectionRuleManager
7879
from .packages import GenericPackageManager, ProjectPackageManager # noqa: F401
7980
from .pages import ProjectPagesDomainManager # noqa: F401
8081
from .pipelines import ( # noqa: F401
@@ -209,6 +210,7 @@ class Project(
209210
notes: ProjectNoteManager
210211
notificationsettings: ProjectNotificationSettingsManager
211212
packages: ProjectPackageManager
213+
package_protection_rules: ProjectPackageProtectionRuleManager
212214
pagesdomains: ProjectPagesDomainManager
213215
pipelines: ProjectPipelineManager
214216
pipelineschedules: ProjectPipelineScheduleManager

‎tests/functional/api/test_packages.py‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
from collections.abc import Iterator
88

9-
from gitlab.v4.objects import GenericPackage
9+
import pytest
10+
11+
from gitlab import Gitlab
12+
from gitlab.v4.objects import GenericPackage, Project, ProjectPackageProtectionRule
1013

1114
package_name = "hello-world"
1215
package_version = "v1.0.0"
@@ -15,6 +18,11 @@
1518
file_content = "package content"
1619

1720

21+
@pytest.fixture(scope="module", autouse=True)
22+
def protected_package_feature(gl: Gitlab):
23+
gl.features.set(name="packages_protected_packages", value=True)
24+
25+
1826
def test_list_project_packages(project):
1927
packages = project.packages.list()
2028
assert isinstance(packages, list)
@@ -148,3 +156,26 @@ def test_stream_generic_package_to_file(tmp_path, project):
148156

149157
with open(path, "r") as f:
150158
assert f.read() == file_content
159+
160+
161+
def test_list_project_protected_packages(project: Project):
162+
rules = project.package_protection_rules.list()
163+
assert isinstance(rules, list)
164+
165+
166+
@pytest.mark.skip(reason="Not released yet")
167+
def test_create_project_protected_packages(project: Project):
168+
protected_package = project.package_protection_rules.create(
169+
{
170+
"package_name_pattern": "v*",
171+
"package_type": "npm",
172+
"minimum_access_level_for_push": "maintainer",
173+
}
174+
)
175+
assert isinstance(protected_package, ProjectPackageProtectionRule)
176+
assert protected_package.package_type == "npm"
177+
178+
protected_package.minimum_access_level_for_push = "owner"
179+
protected_package.save()
180+
181+
protected_package.delete()
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
GitLab API: https://docs.gitlab.com/ee/api/project_packages_protection_rules.html
3+
"""
4+
5+
import pytest
6+
import responses
7+
8+
from gitlab.v4.objects import ProjectPackageProtectionRule
9+
10+
protected_package_content = {
11+
"id": 1,
12+
"project_id": 7,
13+
"package_name_pattern": "v*",
14+
"package_type": "npm",
15+
"minimum_access_level_for_push": "maintainer",
16+
}
17+
18+
19+
@pytest.fixture
20+
def resp_list_protected_packages():
21+
with responses.RequestsMock() as rsps:
22+
rsps.add(
23+
method=responses.GET,
24+
url="http://localhost/api/v4/projects/1/packages/protection/rules",
25+
json=[protected_package_content],
26+
content_type="application/json",
27+
status=200,
28+
)
29+
yield rsps
30+
31+
32+
@pytest.fixture
33+
def resp_create_protected_package():
34+
with responses.RequestsMock() as rsps:
35+
rsps.add(
36+
method=responses.POST,
37+
url="http://localhost/api/v4/projects/1/packages/protection/rules",
38+
json=protected_package_content,
39+
content_type="application/json",
40+
status=201,
41+
)
42+
yield rsps
43+
44+
45+
@pytest.fixture
46+
def resp_update_protected_package():
47+
updated_content = protected_package_content.copy()
48+
updated_content["package_name_pattern"] = "abc*"
49+
50+
with responses.RequestsMock() as rsps:
51+
rsps.add(
52+
method=responses.PATCH,
53+
url="http://localhost/api/v4/projects/1/packages/protection/rules/1",
54+
json=updated_content,
55+
content_type="application/json",
56+
status=200,
57+
)
58+
yield rsps
59+
60+
61+
@pytest.fixture
62+
def resp_delete_protected_package():
63+
with responses.RequestsMock() as rsps:
64+
rsps.add(
65+
method=responses.DELETE,
66+
url="http://localhost/api/v4/projects/1/packages/protection/rules/1",
67+
status=204,
68+
)
69+
yield rsps
70+
71+
72+
def test_list_project_protected_packages(project, resp_list_protected_packages):
73+
protected_package = project.package_protection_rules.list()[0]
74+
assert isinstance(protected_package, ProjectPackageProtectionRule)
75+
assert protected_package.package_type == "npm"
76+
77+
78+
def test_create_project_protected_package(project, resp_create_protected_package):
79+
protected_package = project.package_protection_rules.create(
80+
{
81+
"package_name_pattern": "v*",
82+
"package_type": "npm",
83+
"minimum_access_level_for_push": "maintainer",
84+
}
85+
)
86+
assert isinstance(protected_package, ProjectPackageProtectionRule)
87+
assert protected_package.package_type == "npm"
88+
89+
90+
def test_update_project_protected_package(project, resp_update_protected_package):
91+
updated = project.package_protection_rules.update(
92+
1, {"package_name_pattern": "abc*"}
93+
)
94+
assert updated["package_name_pattern"] == "abc*"
95+
96+
97+
def test_delete_project_protected_package(project, resp_delete_protected_package):
98+
project.package_protection_rules.delete(1)

0 commit comments

Comments
 (0)

Footer

© 2026 GitHub, Inc.