diff options
author | Max Wittig <max.wittig.ch@gmail.com> | 2021-02-21 13:31:32 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-21 13:31:32 +0100 |
commit | 2b29776a033b9903d055df7c0716805e86d13fa2 (patch) | |
tree | ed8b2c5b5262feb2ac6e0e9f25362e08e338956e | |
parent | 338170029c9c8855a6c44de8f3576e8389338652 (diff) | |
parent | 36d65f03db253d710938c2d827c1124c94a40506 (diff) | |
download | gitlab-2b29776a033b9903d055df7c0716805e86d13fa2.tar.gz |
Merge pull request #1314 from python-gitlab/feat/release-links
feat: add release links API support
-rw-r--r-- | docs/api-objects.rst | 1 | ||||
-rw-r--r-- | docs/gl_objects/projects.rst | 33 | ||||
-rw-r--r-- | docs/gl_objects/releases.rst | 77 | ||||
-rw-r--r-- | gitlab/tests/conftest.py | 10 | ||||
-rw-r--r-- | gitlab/tests/objects/test_releases.py | 131 | ||||
-rw-r--r-- | gitlab/v4/objects/__init__.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/projects.py | 5 | ||||
-rw-r--r-- | gitlab/v4/objects/releases.py | 36 | ||||
-rw-r--r-- | gitlab/v4/objects/tags.py | 13 | ||||
-rw-r--r-- | tools/functional/api/test_projects.py | 36 | ||||
-rw-r--r-- | tools/functional/api/test_releases.py | 36 | ||||
-rw-r--r-- | tools/functional/conftest.py | 33 |
12 files changed, 329 insertions, 83 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 8221f63..5bcbe24 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -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 diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index e483a32..e61bb6a 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -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 ====================== diff --git a/docs/gl_objects/releases.rst b/docs/gl_objects/releases.rst new file mode 100644 index 0000000..3813857 --- /dev/null +++ b/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() diff --git a/gitlab/tests/conftest.py b/gitlab/tests/conftest.py index 98d97ae..fc8312f 100644 --- a/gitlab/tests/conftest.py +++ b/gitlab/tests/conftest.py @@ -38,6 +38,11 @@ def default_config(tmpdir): @pytest.fixture +def tag_name(): + return "v1.0.0" + + +@pytest.fixture def group(gl): return gl.groups.get(1, lazy=True) @@ -48,5 +53,10 @@ def project(gl): @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) diff --git a/gitlab/tests/objects/test_releases.py b/gitlab/tests/objects/test_releases.py new file mode 100644 index 0000000..6c38a7c --- /dev/null +++ b/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() diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py index 9f91f53..8a2ed7c 100644 --- a/gitlab/v4/objects/__init__.py +++ b/gitlab/v4/objects/__init__.py @@ -56,6 +56,7 @@ from .pages import * from .pipelines import * from .projects import * from .push_rules import * +from .releases import * from .runners import * from .services import * from .settings import * diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 722b9ea..320e511 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -33,6 +33,7 @@ from .packages import ProjectPackageManager 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 @@ -40,7 +41,7 @@ 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 @@ -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"), diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py new file mode 100644 index 0000000..d9112e4 --- /dev/null +++ b/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")) diff --git a/gitlab/v4/objects/tags.py b/gitlab/v4/objects/tags.py index c4d60db..1f333c5 100644 --- a/gitlab/v4/objects/tags.py +++ b/gitlab/v4/objects/tags.py @@ -9,8 +9,6 @@ __all__ = [ "ProjectTagManager", "ProjectProtectedTag", "ProjectProtectedTagManager", - "ProjectRelease", - "ProjectReleaseManager", ] @@ -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")) diff --git a/tools/functional/api/test_projects.py b/tools/functional/api/test_projects.py index 945a6ec..404f89d 100644 --- a/tools/functional/api/test_projects.py +++ b/tools/functional/api/test_projects.py @@ -197,32 +197,6 @@ def test_project_protected_branches(project): assert len(project.protectedbranches.list()) == 0 -def test_project_releases(gl): - project = gl.projects.create( - {"name": "release-test-project", "initialize_with_readme": True} - ) - release_name = "Demo Release" - release_tag_name = "v1.2.3" - release_description = "release notes go here" - release = project.releases.create( - { - "name": release_name, - "tag_name": release_tag_name, - "description": release_description, - "ref": "master", - } - ) - assert len(project.releases.list()) == 1 - assert project.releases.get(release_tag_name) - assert release.name == release_name - assert release.tag_name == release_tag_name - assert release.description == release_description - - project.releases.delete(release_tag_name) - assert len(project.releases.list()) == 0 - project.delete() - - def test_project_remote_mirrors(project): mirror_url = "http://gitlab.test/root/mirror.git" @@ -260,15 +234,7 @@ def test_project_stars(project): assert project.star_count == 0 -def test_project_tags(project): - project.files.create( - { - "file_path": "README", - "branch": "master", - "content": "Initial content", - "commit_message": "Initial commit", - } - ) +def test_project_tags(project, project_file): tag = project.tags.create({"tag_name": "v1.0", "ref": "master"}) assert len(project.tags.list()) == 1 diff --git a/tools/functional/api/test_releases.py b/tools/functional/api/test_releases.py new file mode 100644 index 0000000..55f7920 --- /dev/null +++ b/tools/functional/api/test_releases.py @@ -0,0 +1,36 @@ +release_name = "Demo Release" +release_tag_name = "v1.2.3" +release_description = "release notes go here" + +link_data = {"url": "https://example.com", "name": "link_name"} + + +def test_create_project_release(project, project_file): + project.refresh() # Gets us the current default branch + release = project.releases.create( + { + "name": release_name, + "tag_name": release_tag_name, + "description": release_description, + "ref": project.default_branch, + } + ) + + assert len(project.releases.list()) == 1 + assert project.releases.get(release_tag_name) + assert release.name == release_name + assert release.tag_name == release_tag_name + assert release.description == release_description + + +def test_delete_project_release(project, release): + project.releases.delete(release.tag_name) + assert release not in project.releases.list() + + +def test_create_project_release_links(project, release): + link = release.links.create(link_data) + + release = project.releases.get(release.tag_name) + assert release.assets["links"][0]["url"] == link_data["url"] + assert release.assets["links"][0]["name"] == link_data["name"] diff --git a/tools/functional/conftest.py b/tools/functional/conftest.py index 675dba9..a0b14f9 100644 --- a/tools/functional/conftest.py +++ b/tools/functional/conftest.py @@ -197,6 +197,39 @@ def project(gl): @pytest.fixture(scope="module") +def project_file(project): + """File fixture for tests requiring a project with files and branches.""" + project_file = project.files.create( + { + "file_path": "README", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } + ) + + return project_file + + +@pytest.fixture(scope="function") +def release(project, project_file): + _id = uuid.uuid4().hex + name = f"test-release-{_id}" + + project.refresh() # Gets us the current default branch + release = project.releases.create( + { + "name": name, + "tag_name": _id, + "description": "description", + "ref": project.default_branch, + } + ) + + return release + + +@pytest.fixture(scope="module") def user(gl): """User fixture for user API resource tests.""" _id = uuid.uuid4().hex |