Skip to content

Commit 2b29776

Browse files
authored
Merge pull request #1314 from python-gitlab/feat/release-links
feat: add release links API support
2 parents 3381700 + 36d65f0 commit 2b29776

File tree

12 files changed

+329
-83
lines changed

12 files changed

+329
-83
lines changed

docs/api-objects.rst

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ API examples
3737
gl_objects/pipelines_and_jobs
3838
gl_objects/projects
3939
gl_objects/protected_branches
40+
gl_objects/releases
4041
gl_objects/runners
4142
gl_objects/remote_mirrors
4243
gl_objects/repositories

docs/gl_objects/projects.rst

-33
Original file line numberDiff line numberDiff line change
@@ -702,39 +702,6 @@ Delete project push rules::
702702

703703
pr.delete()
704704

705-
Project releases
706-
================
707-
708-
Reference
709-
---------
710-
711-
* v4 API:
712-
713-
+ :class:`gitlab.v4.objects.ProjectRelease`
714-
+ :class:`gitlab.v4.objects.ProjectReleaseManager`
715-
+ :attr:`gitlab.v4.objects.Project.releases`
716-
717-
* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html
718-
719-
Examples
720-
--------
721-
722-
Get a list of releases from a project::
723-
724-
release = project.releases.list()
725-
726-
Get a single release::
727-
728-
release = project.releases.get('v1.2.3')
729-
730-
Create a release for a project tag::
731-
732-
release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'})
733-
734-
Delete a release::
735-
736-
release = p.releases.delete('v1.2.3')
737-
738705
Project protected tags
739706
======================
740707

docs/gl_objects/releases.rst

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
########
2+
Releases
3+
########
4+
5+
Project releases
6+
================
7+
8+
Reference
9+
---------
10+
11+
* v4 API:
12+
13+
+ :class:`gitlab.v4.objects.ProjectRelease`
14+
+ :class:`gitlab.v4.objects.ProjectReleaseManager`
15+
+ :attr:`gitlab.v4.objects.Project.releases`
16+
17+
* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html
18+
19+
Examples
20+
--------
21+
22+
Get a list of releases from a project::
23+
24+
release = project.releases.list()
25+
26+
Get a single release::
27+
28+
release = project.releases.get('v1.2.3')
29+
30+
Create a release for a project tag::
31+
32+
release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'})
33+
34+
Delete a release::
35+
36+
# via its tag name from project attributes
37+
release = project.releases.delete('v1.2.3')
38+
39+
# delete object directly
40+
release.delete()
41+
42+
Project release links
43+
=====================
44+
45+
Reference
46+
---------
47+
48+
* v4 API:
49+
50+
+ :class:`gitlab.v4.objects.ProjectReleaseLink`
51+
+ :class:`gitlab.v4.objects.ProjectReleaseLinkManager`
52+
+ :attr:`gitlab.v4.objects.ProjectRelease.links`
53+
54+
* Gitlab API: https://docs.gitlab.com/ee/api/releases/links.html
55+
56+
Examples
57+
--------
58+
59+
Get a list of releases from a project::
60+
61+
links = release.links.list()
62+
63+
Get a single release link::
64+
65+
link = release.links.get(1)
66+
67+
Create a release link for a release::
68+
69+
link = release.links.create({"url": "https://example.com/asset", "name": "asset"})
70+
71+
Delete a release link::
72+
73+
# via its ID from release attributes
74+
release.links.delete(1)
75+
76+
# delete object directly
77+
link.delete()

gitlab/tests/conftest.py

+10
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ def default_config(tmpdir):
3737
return str(config_path)
3838

3939

40+
@pytest.fixture
41+
def tag_name():
42+
return "v1.0.0"
43+
44+
4045
@pytest.fixture
4146
def group(gl):
4247
return gl.groups.get(1, lazy=True)
@@ -47,6 +52,11 @@ def project(gl):
4752
return gl.projects.get(1, lazy=True)
4853

4954

55+
@pytest.fixture
56+
def release(project, tag_name):
57+
return project.releases.get(tag_name, lazy=True)
58+
59+
5060
@pytest.fixture
5161
def user(gl):
5262
return gl.users.get(1, lazy=True)

gitlab/tests/objects/test_releases.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
GitLab API:
3+
https://docs.gitlab.com/ee/api/releases/index.html
4+
https://docs.gitlab.com/ee/api/releases/links.html
5+
"""
6+
import re
7+
8+
import pytest
9+
import responses
10+
11+
from gitlab.v4.objects import ProjectReleaseLink
12+
13+
encoded_tag_name = "v1%2E0%2E0"
14+
link_name = "hello-world"
15+
link_url = "https://gitlab.example.com/group/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-amd64"
16+
direct_url = f"https://gitlab.example.com/group/hello/-/releases/{encoded_tag_name}/downloads/hello-world"
17+
new_link_type = "package"
18+
link_content = {
19+
"id": 2,
20+
"name": link_name,
21+
"url": link_url,
22+
"direct_asset_url": direct_url,
23+
"external": False,
24+
"link_type": "other",
25+
}
26+
27+
links_url = re.compile(
28+
rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links"
29+
)
30+
link_id_url = re.compile(
31+
rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links/1"
32+
)
33+
34+
35+
@pytest.fixture
36+
def resp_list_links():
37+
with responses.RequestsMock() as rsps:
38+
rsps.add(
39+
method=responses.GET,
40+
url=links_url,
41+
json=[link_content],
42+
content_type="application/json",
43+
status=200,
44+
)
45+
yield rsps
46+
47+
48+
@pytest.fixture
49+
def resp_get_link():
50+
with responses.RequestsMock() as rsps:
51+
rsps.add(
52+
method=responses.GET,
53+
url=link_id_url,
54+
json=link_content,
55+
content_type="application/json",
56+
status=200,
57+
)
58+
yield rsps
59+
60+
61+
@pytest.fixture
62+
def resp_create_link():
63+
with responses.RequestsMock() as rsps:
64+
rsps.add(
65+
method=responses.POST,
66+
url=links_url,
67+
json=link_content,
68+
content_type="application/json",
69+
status=200,
70+
)
71+
yield rsps
72+
73+
74+
@pytest.fixture
75+
def resp_update_link():
76+
updated_content = dict(link_content)
77+
updated_content["link_type"] = new_link_type
78+
79+
with responses.RequestsMock() as rsps:
80+
rsps.add(
81+
method=responses.PUT,
82+
url=link_id_url,
83+
json=updated_content,
84+
content_type="application/json",
85+
status=200,
86+
)
87+
yield rsps
88+
89+
90+
@pytest.fixture
91+
def resp_delete_link(no_content):
92+
with responses.RequestsMock() as rsps:
93+
rsps.add(
94+
method=responses.DELETE,
95+
url=link_id_url,
96+
json=link_content,
97+
content_type="application/json",
98+
status=204,
99+
)
100+
yield rsps
101+
102+
103+
def test_list_release_links(release, resp_list_links):
104+
links = release.links.list()
105+
assert isinstance(links, list)
106+
assert isinstance(links[0], ProjectReleaseLink)
107+
assert links[0].url == link_url
108+
109+
110+
def test_get_release_link(release, resp_get_link):
111+
link = release.links.get(1)
112+
assert isinstance(link, ProjectReleaseLink)
113+
assert link.url == link_url
114+
115+
116+
def test_create_release_link(release, resp_create_link):
117+
link = release.links.create({"url": link_url, "name": link_name})
118+
assert isinstance(link, ProjectReleaseLink)
119+
assert link.url == link_url
120+
121+
122+
def test_update_release_link(release, resp_update_link):
123+
link = release.links.get(1, lazy=True)
124+
link.link_type = new_link_type
125+
link.save()
126+
assert link.link_type == new_link_type
127+
128+
129+
def test_delete_release_link(release, resp_delete_link):
130+
link = release.links.get(1, lazy=True)
131+
link.delete()

gitlab/v4/objects/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from .pipelines import *
5757
from .projects import *
5858
from .push_rules import *
59+
from .releases import *
5960
from .runners import *
6061
from .services import *
6162
from .settings import *

gitlab/v4/objects/projects.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@
3333
from .pages import ProjectPagesDomainManager
3434
from .pipelines import ProjectPipelineManager, ProjectPipelineScheduleManager
3535
from .push_rules import ProjectPushRulesManager
36+
from .releases import ProjectReleaseManager
3637
from .runners import ProjectRunnerManager
3738
from .services import ProjectServiceManager
3839
from .snippets import ProjectSnippetManager
3940
from .statistics import (
4041
ProjectAdditionalStatisticsManager,
4142
ProjectIssuesStatisticsManager,
4243
)
43-
from .tags import ProjectProtectedTagManager, ProjectReleaseManager, ProjectTagManager
44+
from .tags import ProjectProtectedTagManager, ProjectTagManager
4445
from .triggers import ProjectTriggerManager
4546
from .users import ProjectUserManager
4647
from .variables import ProjectVariableManager
@@ -86,7 +87,7 @@ class GroupProjectManager(ListMixin, RESTManager):
8687
)
8788

8889

89-
class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
90+
class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
9091
_short_print_attr = "path"
9192
_managers = (
9293
("accessrequests", "ProjectAccessRequestManager"),

gitlab/v4/objects/releases.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from gitlab import cli
2+
from gitlab import exceptions as exc
3+
from gitlab.base import * # noqa
4+
from gitlab.mixins import * # noqa
5+
6+
7+
__all__ = [
8+
"ProjectRelease",
9+
"ProjectReleaseManager",
10+
"ProjectReleaseLink",
11+
"ProjectReleaseLinkManager",
12+
]
13+
14+
15+
class ProjectRelease(RESTObject):
16+
_id_attr = "tag_name"
17+
_managers = (("links", "ProjectReleaseLinkManager"),)
18+
19+
20+
class ProjectReleaseManager(NoUpdateMixin, RESTManager):
21+
_path = "/projects/%(project_id)s/releases"
22+
_obj_cls = ProjectRelease
23+
_from_parent_attrs = {"project_id": "id"}
24+
_create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))
25+
26+
27+
class ProjectReleaseLink(RESTObject, ObjectDeleteMixin, SaveMixin):
28+
pass
29+
30+
31+
class ProjectReleaseLinkManager(CRUDMixin, RESTManager):
32+
_path = "/projects/%(project_id)s/releases/%(tag_name)s/assets/links"
33+
_obj_cls = ProjectReleaseLink
34+
_from_parent_attrs = {"project_id": "project_id", "tag_name": "tag_name"}
35+
_create_attrs = (("name", "url"), ("filepath", "link_type"))
36+
_update_attrs = ((), ("name", "url", "filepath", "link_type"))

gitlab/v4/objects/tags.py

-13
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
"ProjectTagManager",
1010
"ProjectProtectedTag",
1111
"ProjectProtectedTagManager",
12-
"ProjectRelease",
13-
"ProjectReleaseManager",
1412
]
1513

1614

@@ -71,14 +69,3 @@ class ProjectProtectedTagManager(NoUpdateMixin, RESTManager):
7169
_obj_cls = ProjectProtectedTag
7270
_from_parent_attrs = {"project_id": "id"}
7371
_create_attrs = (("name",), ("create_access_level",))
74-
75-
76-
class ProjectRelease(RESTObject):
77-
_id_attr = "tag_name"
78-
79-
80-
class ProjectReleaseManager(NoUpdateMixin, RESTManager):
81-
_path = "/projects/%(project_id)s/releases"
82-
_obj_cls = ProjectRelease
83-
_from_parent_attrs = {"project_id": "id"}
84-
_create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))

0 commit comments

Comments
 (0)