Skip to content

Commit

Permalink
Merge pull request #1314 from python-gitlab/feat/release-links
Browse files Browse the repository at this point in the history
feat: add release links API support
  • Loading branch information
max-wittig committed Feb 21, 2021
2 parents 3381700 + 36d65f0 commit 2b29776
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 83 deletions.
1 change: 1 addition & 0 deletions docs/api-objects.rst
Expand Up @@ -37,6 +37,7 @@ API examples
gl_objects/pipelines_and_jobs
gl_objects/projects
gl_objects/protected_branches
gl_objects/releases
gl_objects/runners
gl_objects/remote_mirrors
gl_objects/repositories
Expand Down
33 changes: 0 additions & 33 deletions docs/gl_objects/projects.rst
Expand Up @@ -702,39 +702,6 @@ Delete project push rules::

pr.delete()

Project releases
================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectRelease`
+ :class:`gitlab.v4.objects.ProjectReleaseManager`
+ :attr:`gitlab.v4.objects.Project.releases`

* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html

Examples
--------

Get a list of releases from a project::

release = project.releases.list()

Get a single release::

release = project.releases.get('v1.2.3')

Create a release for a project tag::

release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'})

Delete a release::

release = p.releases.delete('v1.2.3')

Project protected tags
======================

Expand Down
77 changes: 77 additions & 0 deletions docs/gl_objects/releases.rst
@@ -0,0 +1,77 @@
########
Releases
########

Project releases
================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectRelease`
+ :class:`gitlab.v4.objects.ProjectReleaseManager`
+ :attr:`gitlab.v4.objects.Project.releases`

* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html

Examples
--------

Get a list of releases from a project::

release = project.releases.list()

Get a single release::

release = project.releases.get('v1.2.3')

Create a release for a project tag::

release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'})

Delete a release::

# via its tag name from project attributes
release = project.releases.delete('v1.2.3')

# delete object directly
release.delete()

Project release links
=====================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectReleaseLink`
+ :class:`gitlab.v4.objects.ProjectReleaseLinkManager`
+ :attr:`gitlab.v4.objects.ProjectRelease.links`

* Gitlab API: https://docs.gitlab.com/ee/api/releases/links.html

Examples
--------

Get a list of releases from a project::

links = release.links.list()

Get a single release link::

link = release.links.get(1)

Create a release link for a release::

link = release.links.create({"url": "https://example.com/asset", "name": "asset"})

Delete a release link::

# via its ID from release attributes
release.links.delete(1)

# delete object directly
link.delete()
10 changes: 10 additions & 0 deletions gitlab/tests/conftest.py
Expand Up @@ -37,6 +37,11 @@ def default_config(tmpdir):
return str(config_path)


@pytest.fixture
def tag_name():
return "v1.0.0"


@pytest.fixture
def group(gl):
return gl.groups.get(1, lazy=True)
Expand All @@ -47,6 +52,11 @@ def project(gl):
return gl.projects.get(1, lazy=True)


@pytest.fixture
def release(project, tag_name):
return project.releases.get(tag_name, lazy=True)


@pytest.fixture
def user(gl):
return gl.users.get(1, lazy=True)
131 changes: 131 additions & 0 deletions gitlab/tests/objects/test_releases.py
@@ -0,0 +1,131 @@
"""
GitLab API:
https://docs.gitlab.com/ee/api/releases/index.html
https://docs.gitlab.com/ee/api/releases/links.html
"""
import re

import pytest
import responses

from gitlab.v4.objects import ProjectReleaseLink

encoded_tag_name = "v1%2E0%2E0"
link_name = "hello-world"
link_url = "https://gitlab.example.com/group/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-amd64"
direct_url = f"https://gitlab.example.com/group/hello/-/releases/{encoded_tag_name}/downloads/hello-world"
new_link_type = "package"
link_content = {
"id": 2,
"name": link_name,
"url": link_url,
"direct_asset_url": direct_url,
"external": False,
"link_type": "other",
}

links_url = re.compile(
rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links"
)
link_id_url = re.compile(
rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links/1"
)


@pytest.fixture
def resp_list_links():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url=links_url,
json=[link_content],
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_get_link():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url=link_id_url,
json=link_content,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_create_link():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.POST,
url=links_url,
json=link_content,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_update_link():
updated_content = dict(link_content)
updated_content["link_type"] = new_link_type

with responses.RequestsMock() as rsps:
rsps.add(
method=responses.PUT,
url=link_id_url,
json=updated_content,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_delete_link(no_content):
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.DELETE,
url=link_id_url,
json=link_content,
content_type="application/json",
status=204,
)
yield rsps


def test_list_release_links(release, resp_list_links):
links = release.links.list()
assert isinstance(links, list)
assert isinstance(links[0], ProjectReleaseLink)
assert links[0].url == link_url


def test_get_release_link(release, resp_get_link):
link = release.links.get(1)
assert isinstance(link, ProjectReleaseLink)
assert link.url == link_url


def test_create_release_link(release, resp_create_link):
link = release.links.create({"url": link_url, "name": link_name})
assert isinstance(link, ProjectReleaseLink)
assert link.url == link_url


def test_update_release_link(release, resp_update_link):
link = release.links.get(1, lazy=True)
link.link_type = new_link_type
link.save()
assert link.link_type == new_link_type


def test_delete_release_link(release, resp_delete_link):
link = release.links.get(1, lazy=True)
link.delete()
1 change: 1 addition & 0 deletions gitlab/v4/objects/__init__.py
Expand Up @@ -56,6 +56,7 @@
from .pipelines import *
from .projects import *
from .push_rules import *
from .releases import *
from .runners import *
from .services import *
from .settings import *
Expand Down
5 changes: 3 additions & 2 deletions gitlab/v4/objects/projects.py
Expand Up @@ -33,14 +33,15 @@
from .pages import ProjectPagesDomainManager
from .pipelines import ProjectPipelineManager, ProjectPipelineScheduleManager
from .push_rules import ProjectPushRulesManager
from .releases import ProjectReleaseManager
from .runners import ProjectRunnerManager
from .services import ProjectServiceManager
from .snippets import ProjectSnippetManager
from .statistics import (
ProjectAdditionalStatisticsManager,
ProjectIssuesStatisticsManager,
)
from .tags import ProjectProtectedTagManager, ProjectReleaseManager, ProjectTagManager
from .tags import ProjectProtectedTagManager, ProjectTagManager
from .triggers import ProjectTriggerManager
from .users import ProjectUserManager
from .variables import ProjectVariableManager
Expand Down Expand Up @@ -86,7 +87,7 @@ class GroupProjectManager(ListMixin, RESTManager):
)


class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
_short_print_attr = "path"
_managers = (
("accessrequests", "ProjectAccessRequestManager"),
Expand Down
36 changes: 36 additions & 0 deletions gitlab/v4/objects/releases.py
@@ -0,0 +1,36 @@
from gitlab import cli
from gitlab import exceptions as exc
from gitlab.base import * # noqa
from gitlab.mixins import * # noqa


__all__ = [
"ProjectRelease",
"ProjectReleaseManager",
"ProjectReleaseLink",
"ProjectReleaseLinkManager",
]


class ProjectRelease(RESTObject):
_id_attr = "tag_name"
_managers = (("links", "ProjectReleaseLinkManager"),)


class ProjectReleaseManager(NoUpdateMixin, RESTManager):
_path = "/projects/%(project_id)s/releases"
_obj_cls = ProjectRelease
_from_parent_attrs = {"project_id": "id"}
_create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))


class ProjectReleaseLink(RESTObject, ObjectDeleteMixin, SaveMixin):
pass


class ProjectReleaseLinkManager(CRUDMixin, RESTManager):
_path = "/projects/%(project_id)s/releases/%(tag_name)s/assets/links"
_obj_cls = ProjectReleaseLink
_from_parent_attrs = {"project_id": "project_id", "tag_name": "tag_name"}
_create_attrs = (("name", "url"), ("filepath", "link_type"))
_update_attrs = ((), ("name", "url", "filepath", "link_type"))
13 changes: 0 additions & 13 deletions gitlab/v4/objects/tags.py
Expand Up @@ -9,8 +9,6 @@
"ProjectTagManager",
"ProjectProtectedTag",
"ProjectProtectedTagManager",
"ProjectRelease",
"ProjectReleaseManager",
]


Expand Down Expand Up @@ -71,14 +69,3 @@ class ProjectProtectedTagManager(NoUpdateMixin, RESTManager):
_obj_cls = ProjectProtectedTag
_from_parent_attrs = {"project_id": "id"}
_create_attrs = (("name",), ("create_access_level",))


class ProjectRelease(RESTObject):
_id_attr = "tag_name"


class ProjectReleaseManager(NoUpdateMixin, RESTManager):
_path = "/projects/%(project_id)s/releases"
_obj_cls = ProjectRelease
_from_parent_attrs = {"project_id": "id"}
_create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))

0 comments on commit 2b29776

Please sign in to comment.