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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 0 additions & 33 deletions
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

Lines changed: 77 additions & 0 deletions
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

Lines changed: 10 additions & 0 deletions
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

Lines changed: 131 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 2 deletions
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

Lines changed: 36 additions & 0 deletions
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

Lines changed: 0 additions & 13 deletions
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)
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