Skip to content

Commit

Permalink
Merge pull request #1160 from python-gitlab/feat/packages-api
Browse files Browse the repository at this point in the history
Feat: Add support for packages API
  • Loading branch information
max-wittig committed Aug 29, 2020
2 parents a038e95 + a47dfcd commit 0f42e32
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/api-objects.rst
Expand Up @@ -32,6 +32,7 @@ API examples
gl_objects/milestones
gl_objects/namespaces
gl_objects/notes
gl_objects/packages
gl_objects/pagesdomains
gl_objects/pipelines_and_jobs
gl_objects/projects
Expand Down
24 changes: 24 additions & 0 deletions docs/cli.rst
Expand Up @@ -235,6 +235,30 @@ List deploy tokens for a group:
$ gitlab -v group-deploy-token list --group-id 3
List packages for a project:

.. code-block:: console
$ gitlab -v project-package list --project-id 3
List packages for a group:

.. code-block:: console
$ gitlab -v group-package list --group-id 3
Get a specific project package by id:

.. code-block:: console
$ gitlab -v project-package get --id 1 --project-id 3
Delete a specific project package by id:

.. code-block:: console
$ gitlab -v project-package delete --id 1 --project-id 3
Get a list of snippets for this project:

.. code-block:: console
Expand Down
68 changes: 68 additions & 0 deletions docs/gl_objects/packages.rst
@@ -0,0 +1,68 @@
#######
Packages
#######

Packages allow you to utilize GitLab as a private repository for a variety
of common package managers.

Project Packages
=====================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectPackage`
+ :class:`gitlab.v4.objects.ProjectPackageManager`
+ :attr:`gitlab.v4.objects.Project.packages`

* GitLab API: https://docs.gitlab.com/ee/api/packages.html#within-a-project

Examples
--------

List the packages in a project::

packages = project.packages.list()

Filter the results by ``package_type`` or ``package_name`` ::

packages = project.packages.list(package_type='pypi')

Get a specific package of a project by id::

package = project.packages.get(1)

Delete a package from a project::

package.delete()
# or
project.packages.delete(package.id)


Group Packages
===================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.GroupPackage`
+ :class:`gitlab.v4.objects.GroupPackageManager`
+ :attr:`gitlab.v4.objects.Group.packages`

* GitLab API: https://docs.gitlab.com/ee/api/packages.html#within-a-group

Examples
--------

List the packages in a group::

packages = group.packages.list()

Filter the results by ``package_type`` or ``package_name`` ::

packages = group.packages.list(package_type='pypi')

5 changes: 5 additions & 0 deletions gitlab/tests/objects/conftest.py
Expand Up @@ -21,6 +21,11 @@ def created_content():
return {"message": "201 Created"}


@pytest.fixture
def no_content():
return {"message": "204 No Content"}


@pytest.fixture
def resp_export(accepted_content, binary_content):
"""Common fixture for group and project exports."""
Expand Down
8 changes: 7 additions & 1 deletion gitlab/tests/objects/test_commits.py
Expand Up @@ -88,7 +88,13 @@ def test_create_commit(project, resp_create_commit):
data = {
"branch": "master",
"commit_message": "Commit message",
"actions": [{"action": "create", "file_path": "README", "content": "",}],
"actions": [
{
"action": "create",
"file_path": "README",
"content": "",
}
],
}
commit = project.commits.create(data)
assert commit.short_id == "ed899a2f"
Expand Down
119 changes: 119 additions & 0 deletions gitlab/tests/objects/test_packages.py
@@ -0,0 +1,119 @@
"""
GitLab API: https://docs.gitlab.com/ce/api/packages.html
"""
import re

import pytest
import responses

from gitlab.v4.objects import GroupPackage, ProjectPackage


package_content = {
"id": 1,
"name": "com/mycompany/my-app",
"version": "1.0-SNAPSHOT",
"package_type": "maven",
"_links": {
"web_path": "/namespace1/project1/-/packages/1",
"delete_api_path": "/namespace1/project1/-/packages/1",
},
"created_at": "2019-11-27T03:37:38.711Z",
"pipeline": {
"id": 123,
"status": "pending",
"ref": "new-pipeline",
"sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
"web_url": "https://example.com/foo/bar/pipelines/47",
"created_at": "2016-08-11T11:28:34.085Z",
"updated_at": "2016-08-11T11:32:35.169Z",
"user": {
"name": "Administrator",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
},
},
"versions": [
{
"id": 2,
"version": "2.0-SNAPSHOT",
"created_at": "2020-04-28T04:42:11.573Z",
"pipeline": {
"id": 234,
"status": "pending",
"ref": "new-pipeline",
"sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
"web_url": "https://example.com/foo/bar/pipelines/58",
"created_at": "2016-08-11T11:28:34.085Z",
"updated_at": "2016-08-11T11:32:35.169Z",
"user": {
"name": "Administrator",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
},
},
}
],
}


@pytest.fixture
def resp_list_packages():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url=re.compile(r"http://localhost/api/v4/(groups|projects)/1/packages"),
json=[package_content],
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_get_package():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/projects/1/packages/1",
json=package_content,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_delete_package(no_content):
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.DELETE,
url="http://localhost/api/v4/projects/1/packages/1",
json=no_content,
content_type="application/json",
status=204,
)
yield rsps


def test_list_project_packages(project, resp_list_packages):
packages = project.packages.list()
assert isinstance(packages, list)
assert isinstance(packages[0], ProjectPackage)
assert packages[0].version == "1.0-SNAPSHOT"


def test_list_group_packages(group, resp_list_packages):
packages = group.packages.list()
assert isinstance(packages, list)
assert isinstance(packages[0], GroupPackage)
assert packages[0].version == "1.0-SNAPSHOT"


def test_get_project_package(project, resp_get_package):
package = project.packages.get(1)
assert isinstance(package, ProjectPackage)
assert package.version == "1.0-SNAPSHOT"


def test_delete_project_package(project, resp_delete_package):
package = project.packages.get(1, lazy=True)
package.delete()
12 changes: 9 additions & 3 deletions gitlab/tests/objects/test_runners.py
Expand Up @@ -167,7 +167,9 @@ def resp_runner_delete():
status=200,
)
rsps.add(
method=responses.DELETE, url=pattern, status=204,
method=responses.DELETE,
url=pattern,
status=204,
)
yield rsps

Expand All @@ -177,7 +179,9 @@ def resp_runner_disable():
with responses.RequestsMock() as rsps:
pattern = re.compile(r".*?/(groups|projects)/1/runners/6")
rsps.add(
method=responses.DELETE, url=pattern, status=204,
method=responses.DELETE,
url=pattern,
status=204,
)
yield rsps

Expand All @@ -187,7 +191,9 @@ def resp_runner_verify():
with responses.RequestsMock() as rsps:
pattern = re.compile(r".*?/runners/verify")
rsps.add(
method=responses.POST, url=pattern, status=200,
method=responses.POST,
url=pattern,
status=200,
)
yield rsps

Expand Down
61 changes: 56 additions & 5 deletions gitlab/v4/objects.py
Expand Up @@ -711,8 +711,14 @@ class ProjectDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager
_from_parent_attrs = {"project_id": "id"}
_obj_cls = ProjectDeployToken
_create_attrs = (
("name", "scopes",),
("expires_at", "username",),
(
"name",
"scopes",
),
(
"expires_at",
"username",
),
)


Expand All @@ -725,8 +731,14 @@ class GroupDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
_from_parent_attrs = {"group_id": "id"}
_obj_cls = GroupDeployToken
_create_attrs = (
("name", "scopes",),
("expires_at", "username",),
(
"name",
"scopes",
),
(
"expires_at",
"username",
),
)


Expand Down Expand Up @@ -1279,6 +1291,23 @@ class GroupNotificationSettingsManager(NotificationSettingsManager):
_from_parent_attrs = {"group_id": "id"}


class GroupPackage(RESTObject):
pass


class GroupPackageManager(ListMixin, RESTManager):
_path = "/groups/%(group_id)s/packages"
_obj_cls = GroupPackage
_from_parent_attrs = {"group_id": "id"}
_list_filters = (
"exclude_subgroups",
"order_by",
"sort",
"package_type",
"package_name",
)


class GroupProject(RESTObject):
pass

Expand Down Expand Up @@ -1365,6 +1394,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
("mergerequests", "GroupMergeRequestManager"),
("milestones", "GroupMilestoneManager"),
("notificationsettings", "GroupNotificationSettingsManager"),
("packages", "GroupPackageManager"),
("projects", "GroupProjectManager"),
("runners", "GroupRunnerManager"),
("subgroups", "GroupSubgroupManager"),
Expand Down Expand Up @@ -2840,6 +2870,22 @@ class ProjectNotificationSettingsManager(NotificationSettingsManager):
_from_parent_attrs = {"project_id": "id"}


class ProjectPackage(ObjectDeleteMixin, RESTObject):
pass


class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager):
_path = "/projects/%(project_id)s/packages"
_obj_cls = ProjectPackage
_from_parent_attrs = {"project_id": "id"}
_list_filters = (
"order_by",
"sort",
"package_type",
"package_name",
)


class ProjectPagesDomain(SaveMixin, ObjectDeleteMixin, RESTObject):
_id_attr = "domain"

Expand Down Expand Up @@ -4181,7 +4227,11 @@ class ProjectServiceManager(GetMixin, UpdateMixin, DeleteMixin, ListMixin, RESTM
),
),
"jira": (
("url", "username", "password",),
(
"url",
"username",
"password",
),
(
"api_url",
"active",
Expand Down Expand Up @@ -4532,6 +4582,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
("milestones", "ProjectMilestoneManager"),
("notes", "ProjectNoteManager"),
("notificationsettings", "ProjectNotificationSettingsManager"),
("packages", "ProjectPackageManager"),
("pagesdomains", "ProjectPagesDomainManager"),
("pipelines", "ProjectPipelineManager"),
("protectedbranches", "ProjectProtectedBranchManager"),
Expand Down

0 comments on commit 0f42e32

Please sign in to comment.