Skip to content

Commit 0d41da3

Browse files
feat(api): add project templates (#3057)
* feat(api): Added project template classes to templates.py * feat(api): Added project template managers to Project in project.py * docs(merge_requests): Add example of creating mr with description template * test(templates): Added unit tests for templates * docs(templates): added section for project templates
1 parent 735efff commit 0d41da3

File tree

5 files changed

+303
-1
lines changed

5 files changed

+303
-1
lines changed

docs/gl_objects/merge_requests.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Get a single MR::
9595
mr = project.mergerequests.get(mr_iid)
9696

9797
Get MR reviewer details::
98+
9899
mr = project.mergerequests.get(mr_iid)
99100
reviewers = mr.reviewer_details.list()
100101

@@ -105,6 +106,13 @@ Create a MR::
105106
'title': 'merge cool feature',
106107
'labels': ['label1', 'label2']})
107108

109+
# Use a project MR description template
110+
mr_description_template = project.merge_request_templates.get("Default")
111+
mr = project.mergerequests.create({'source_branch': 'cool_feature',
112+
'target_branch': 'main',
113+
'title': 'merge cool feature',
114+
'description': mr_description_template.content})
115+
108116
Update a MR::
109117

110118
mr.description = 'New description'

docs/gl_objects/templates.rst

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Reference
9999
+ :class:`gitlab.v4.objects.DockerfileManager`
100100
+ :attr:`gitlab.Gitlab.gitlabciymls`
101101

102-
* GitLab API: Not documented.
102+
* GitLab API: https://docs.gitlab.com/ce/api/templates/dockerfiles.html
103103

104104
Examples
105105
--------
@@ -112,3 +112,73 @@ Get a Dockerfile template::
112112

113113
dockerfile = gl.dockerfiles.get('Python')
114114
print(dockerfile.content)
115+
116+
Project templates
117+
=========================
118+
119+
These templates are project-specific versions of the templates above, as
120+
well as issue and merge request templates.
121+
122+
Reference
123+
---------
124+
125+
* v4 API:
126+
127+
+ :class:`gitlab.v4.objects.ProjectLicenseTemplate`
128+
+ :class:`gitlab.v4.objects.ProjectLicenseTemplateManager`
129+
+ :attr:`gitlab.v4.objects.Project.license_templates`
130+
+ :class:`gitlab.v4.objects.ProjectGitignoreTemplate`
131+
+ :class:`gitlab.v4.objects.ProjectGitignoreTemplateManager`
132+
+ :attr:`gitlab.v4.objects.Project.gitignore_templates`
133+
+ :class:`gitlab.v4.objects.ProjectGitlabciymlTemplate`
134+
+ :class:`gitlab.v4.objects.ProjectGitlabciymlTemplateManager`
135+
+ :attr:`gitlab.v4.objects.Project.gitlabciyml_templates`
136+
+ :class:`gitlab.v4.objects.ProjectDockerfileTemplate`
137+
+ :class:`gitlab.v4.objects.ProjectDockerfileTemplateManager`
138+
+ :attr:`gitlab.v4.objects.Project.dockerfile_templates`
139+
+ :class:`gitlab.v4.objects.ProjectIssueTemplate`
140+
+ :class:`gitlab.v4.objects.ProjectIssueTemplateManager`
141+
+ :attr:`gitlab.v4.objects.Project.issue_templates`
142+
+ :class:`gitlab.v4.objects.ProjectMergeRequestTemplate`
143+
+ :class:`gitlab.v4.objects.ProjectMergeRequestTemplateManager`
144+
+ :attr:`gitlab.v4.objects.Project.merge_request_templates`
145+
146+
* GitLab API: https://docs.gitlab.com/ce/api/project_templates.html
147+
148+
Examples
149+
--------
150+
151+
List known project templates::
152+
153+
license_templates = project.license_templates.list()
154+
gitignore_templates = project.gitignore_templates.list()
155+
gitlabciyml_templates = project.gitlabciyml_templates.list()
156+
dockerfile_templates = project.dockerfile_templates.list()
157+
issue_templates = project.issue_templates.list()
158+
merge_request_templates = project.merge_request_templates.list()
159+
160+
Get project templates::
161+
162+
license_template = project.license_templates.get('apache-2.0')
163+
gitignore_template = project.gitignore_templates.get('Python')
164+
gitlabciyml_template = project.gitlabciyml_templates.get('Pelican')
165+
dockerfile_template = project.dockerfile_templates.get('Python')
166+
issue_template = project.issue_templates.get('Default')
167+
merge_request_template = project.merge_request_templates.get('Default')
168+
169+
print(license_template.content)
170+
print(gitignore_template.content)
171+
print(gitlabciyml_template.content)
172+
print(dockerfile_template.content)
173+
print(issue_template.content)
174+
print(merge_request_template.content)
175+
176+
Create an issue or merge request using a description template::
177+
178+
issue = project.issues.create({'title': 'I have a bug',
179+
'description': issue_template.content})
180+
mr = project.mergerequests.create({'source_branch': 'cool_feature',
181+
'target_branch': 'main',
182+
'title': 'merge cool feature',
183+
'description': merge_request_template.content})
184+

gitlab/v4/objects/projects.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@
100100
ProjectIssuesStatisticsManager,
101101
)
102102
from .tags import ProjectProtectedTagManager, ProjectTagManager # noqa: F401
103+
from .templates import ( # noqa: F401
104+
ProjectDockerfileTemplateManager,
105+
ProjectGitignoreTemplateManager,
106+
ProjectGitlabciymlTemplateManager,
107+
ProjectIssueTemplateManager,
108+
ProjectLicenseTemplateManager,
109+
ProjectMergeRequestTemplateManager,
110+
)
103111
from .triggers import ProjectTriggerManager # noqa: F401
104112
from .users import ProjectUserManager # noqa: F401
105113
from .variables import ProjectVariableManager # noqa: F401
@@ -189,27 +197,33 @@ class Project(
189197
customattributes: ProjectCustomAttributeManager
190198
deployments: ProjectDeploymentManager
191199
deploytokens: ProjectDeployTokenManager
200+
dockerfile_templates: ProjectDockerfileTemplateManager
192201
environments: ProjectEnvironmentManager
193202
events: ProjectEventManager
194203
exports: ProjectExportManager
195204
files: ProjectFileManager
196205
forks: "ProjectForkManager"
197206
generic_packages: GenericPackageManager
207+
gitignore_templates: ProjectGitignoreTemplateManager
208+
gitlabciyml_templates: ProjectGitlabciymlTemplateManager
198209
groups: ProjectGroupManager
199210
hooks: ProjectHookManager
200211
imports: ProjectImportManager
201212
integrations: ProjectIntegrationManager
202213
invitations: ProjectInvitationManager
203214
issues: ProjectIssueManager
215+
issue_templates: ProjectIssueTemplateManager
204216
issues_statistics: ProjectIssuesStatisticsManager
205217
iterations: ProjectIterationManager
206218
jobs: ProjectJobManager
207219
job_token_scope: ProjectJobTokenScopeManager
208220
keys: ProjectKeyManager
209221
labels: ProjectLabelManager
222+
license_templates: ProjectLicenseTemplateManager
210223
members: ProjectMemberManager
211224
members_all: ProjectMemberAllManager
212225
mergerequests: ProjectMergeRequestManager
226+
merge_request_templates: ProjectMergeRequestTemplateManager
213227
merge_trains: ProjectMergeTrainManager
214228
milestones: ProjectMilestoneManager
215229
notes: ProjectNoteManager

gitlab/v4/objects/templates.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212
"GitlabciymlManager",
1313
"License",
1414
"LicenseManager",
15+
"ProjectDockerfileTemplate",
16+
"ProjectDockerfileTemplateManager",
17+
"ProjectGitignoreTemplate",
18+
"ProjectGitignoreTemplateManager",
19+
"ProjectGitlabciymlTemplate",
20+
"ProjectGitlabciymlTemplateManager",
21+
"ProjectIssueTemplate",
22+
"ProjectIssueTemplateManager",
23+
"ProjectLicenseTemplate",
24+
"ProjectLicenseTemplateManager",
25+
"ProjectMergeRequestTemplate",
26+
"ProjectMergeRequestTemplateManager",
1527
]
1628

1729

@@ -65,3 +77,95 @@ class LicenseManager(RetrieveMixin, RESTManager):
6577

6678
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> License:
6779
return cast(License, super().get(id=id, lazy=lazy, **kwargs))
80+
81+
82+
class ProjectDockerfileTemplate(RESTObject):
83+
_id_attr = "name"
84+
85+
86+
class ProjectDockerfileTemplateManager(RetrieveMixin, RESTManager):
87+
_path = "/projects/{project_id}/templates/dockerfiles"
88+
_obj_cls = ProjectDockerfileTemplate
89+
_from_parent_attrs = {"project_id": "id"}
90+
91+
def get(
92+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
93+
) -> ProjectDockerfileTemplate:
94+
return cast(ProjectDockerfileTemplate, super().get(id=id, lazy=lazy, **kwargs))
95+
96+
97+
class ProjectGitignoreTemplate(RESTObject):
98+
_id_attr = "name"
99+
100+
101+
class ProjectGitignoreTemplateManager(RetrieveMixin, RESTManager):
102+
_path = "/projects/{project_id}/templates/gitignores"
103+
_obj_cls = ProjectGitignoreTemplate
104+
_from_parent_attrs = {"project_id": "id"}
105+
106+
def get(
107+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
108+
) -> ProjectGitignoreTemplate:
109+
return cast(ProjectGitignoreTemplate, super().get(id=id, lazy=lazy, **kwargs))
110+
111+
112+
class ProjectGitlabciymlTemplate(RESTObject):
113+
_id_attr = "name"
114+
115+
116+
class ProjectGitlabciymlTemplateManager(RetrieveMixin, RESTManager):
117+
_path = "/projects/{project_id}/templates/gitlab_ci_ymls"
118+
_obj_cls = ProjectGitlabciymlTemplate
119+
_from_parent_attrs = {"project_id": "id"}
120+
121+
def get(
122+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
123+
) -> ProjectGitlabciymlTemplate:
124+
return cast(ProjectGitlabciymlTemplate, super().get(id=id, lazy=lazy, **kwargs))
125+
126+
127+
class ProjectLicenseTemplate(RESTObject):
128+
_id_attr = "key"
129+
130+
131+
class ProjectLicenseTemplateManager(RetrieveMixin, RESTManager):
132+
_path = "/projects/{project_id}/templates/licenses"
133+
_obj_cls = ProjectLicenseTemplate
134+
_from_parent_attrs = {"project_id": "id"}
135+
136+
def get(
137+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
138+
) -> ProjectLicenseTemplate:
139+
return cast(ProjectLicenseTemplate, super().get(id=id, lazy=lazy, **kwargs))
140+
141+
142+
class ProjectIssueTemplate(RESTObject):
143+
_id_attr = "name"
144+
145+
146+
class ProjectIssueTemplateManager(RetrieveMixin, RESTManager):
147+
_path = "/projects/{project_id}/templates/issues"
148+
_obj_cls = ProjectIssueTemplate
149+
_from_parent_attrs = {"project_id": "id"}
150+
151+
def get(
152+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
153+
) -> ProjectIssueTemplate:
154+
return cast(ProjectIssueTemplate, super().get(id=id, lazy=lazy, **kwargs))
155+
156+
157+
class ProjectMergeRequestTemplate(RESTObject):
158+
_id_attr = "name"
159+
160+
161+
class ProjectMergeRequestTemplateManager(RetrieveMixin, RESTManager):
162+
_path = "/projects/{project_id}/templates/merge_requests"
163+
_obj_cls = ProjectMergeRequestTemplate
164+
_from_parent_attrs = {"project_id": "id"}
165+
166+
def get(
167+
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
168+
) -> ProjectMergeRequestTemplate:
169+
return cast(
170+
ProjectMergeRequestTemplate, super().get(id=id, lazy=lazy, **kwargs)
171+
)

tests/unit/objects/test_templates.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Gitlab API:
3+
https://docs.gitlab.com/ce/api/templates/dockerfiles.html
4+
https://docs.gitlab.com/ce/api/templates/gitignores.html
5+
https://docs.gitlab.com/ce/api/templates/gitlab_ci_ymls.html
6+
https://docs.gitlab.com/ce/api/templates/licenses.html
7+
https://docs.gitlab.com/ce/api/project_templates.html
8+
"""
9+
10+
import pytest
11+
import responses
12+
13+
from gitlab.v4.objects import (
14+
Dockerfile,
15+
Gitignore,
16+
Gitlabciyml,
17+
License,
18+
ProjectDockerfileTemplate,
19+
ProjectGitignoreTemplate,
20+
ProjectGitlabciymlTemplate,
21+
ProjectIssueTemplate,
22+
ProjectLicenseTemplate,
23+
ProjectMergeRequestTemplate,
24+
)
25+
26+
27+
@pytest.mark.parametrize(
28+
"tmpl, tmpl_mgr, tmpl_path",
29+
[
30+
(Dockerfile, "dockerfiles", "dockerfiles"),
31+
(Gitignore, "gitignores", "gitignores"),
32+
(Gitlabciyml, "gitlabciymls", "gitlab_ci_ymls"),
33+
(License, "licenses", "licenses"),
34+
],
35+
ids=[
36+
"dockerfile",
37+
"gitignore",
38+
"gitlabciyml",
39+
"license",
40+
],
41+
)
42+
def test_get_template(gl, tmpl, tmpl_mgr, tmpl_path):
43+
tmpl_id = "sample"
44+
tmpl_content = {"name": tmpl_id, "content": "Sample template content"}
45+
46+
# License templates have 'key' as the id attribute, so ensure
47+
# this is included in the response content
48+
if tmpl == License:
49+
tmpl_id = "smpl"
50+
tmpl_content.update({"key": tmpl_id})
51+
52+
path = f"templates/{tmpl_path}/{tmpl_id}"
53+
with responses.RequestsMock() as rsps:
54+
rsps.add(
55+
method=responses.GET,
56+
url=f"http://localhost/api/v4/{path}",
57+
json=tmpl_content,
58+
)
59+
60+
template = getattr(gl, tmpl_mgr).get(tmpl_id)
61+
62+
assert isinstance(template, tmpl)
63+
assert getattr(template, template._id_attr) == tmpl_id
64+
65+
66+
@pytest.mark.parametrize(
67+
"tmpl, tmpl_mgr, tmpl_path",
68+
[
69+
(ProjectDockerfileTemplate, "dockerfile_templates", "dockerfiles"),
70+
(ProjectGitignoreTemplate, "gitignore_templates", "gitignores"),
71+
(ProjectGitlabciymlTemplate, "gitlabciyml_templates", "gitlab_ci_ymls"),
72+
(ProjectLicenseTemplate, "license_templates", "licenses"),
73+
(ProjectIssueTemplate, "issue_templates", "issues"),
74+
(ProjectMergeRequestTemplate, "merge_request_templates", "merge_requests"),
75+
],
76+
ids=[
77+
"dockerfile",
78+
"gitignore",
79+
"gitlabciyml",
80+
"license",
81+
"issue",
82+
"mergerequest",
83+
],
84+
)
85+
def test_get_project_template(project, tmpl, tmpl_mgr, tmpl_path):
86+
tmpl_id = "sample"
87+
tmpl_content = {"name": tmpl_id, "content": "Sample template content"}
88+
89+
# ProjectLicenseTemplate templates have 'key' as the id attribute, so ensure
90+
# this is included in the response content
91+
if tmpl == ProjectLicenseTemplate:
92+
tmpl_id = "smpl"
93+
tmpl_content.update({"key": tmpl_id})
94+
95+
path = f"projects/{project.id}/templates/{tmpl_path}/{tmpl_id}"
96+
with responses.RequestsMock() as rsps:
97+
rsps.add(
98+
method=responses.GET,
99+
url=f"http://localhost/api/v4/{path}",
100+
json=tmpl_content,
101+
)
102+
103+
template = getattr(project, tmpl_mgr).get(tmpl_id)
104+
105+
assert isinstance(template, tmpl)
106+
assert getattr(template, template._id_attr) == tmpl_id

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy