diff options
author | Nejc Habjan <hab.nejc@gmail.com> | 2021-12-11 13:18:49 +0100 |
---|---|---|
committer | Nejc Habjan <hab.nejc@gmail.com> | 2021-12-11 15:04:05 +0100 |
commit | 91cd74de9a7f1c65c5a9779e1b48cb26904d60d8 (patch) | |
tree | 976d0ca147415b84bf25c2f2aa3b8ba141d0c29c | |
parent | 2f37ccb5cd8c487662e86aa077f1deabb27fbc9e (diff) | |
download | gitlab-91cd74de9a7f1c65c5a9779e1b48cb26904d60d8.tar.gz |
feat(api): add support for Topics API
-rw-r--r-- | docs/api-objects.rst | 1 | ||||
-rw-r--r-- | docs/gl_objects/topics.rst | 48 | ||||
-rw-r--r-- | gitlab/client.py | 2 | ||||
-rw-r--r-- | gitlab/v4/objects/__init__.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/topics.py | 27 | ||||
-rw-r--r-- | tests/functional/api/test_topics.py | 21 | ||||
-rw-r--r-- | tests/functional/conftest.py | 2 | ||||
-rw-r--r-- | tests/functional/fixtures/.env | 2 | ||||
-rw-r--r-- | tests/unit/objects/test_topics.py | 119 |
9 files changed, 222 insertions, 1 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 9c089fe..984fd4f 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -53,6 +53,7 @@ API examples gl_objects/system_hooks gl_objects/templates gl_objects/todos + gl_objects/topics gl_objects/users gl_objects/variables gl_objects/sidekiq diff --git a/docs/gl_objects/topics.rst b/docs/gl_objects/topics.rst new file mode 100644 index 0000000..0ca46d7 --- /dev/null +++ b/docs/gl_objects/topics.rst @@ -0,0 +1,48 @@ +######## +Topics +######## + +Topics can be used to categorize projects and find similar new projects. + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.Topic` + + :class:`gitlab.v4.objects.TopicManager` + + :attr:`gitlab.Gitlab.topics` + +* GitLab API: https://docs.gitlab.com/ce/api/topics.html + +This endpoint requires admin access for creating, updating and deleting objects. + +Examples +-------- + +List project topics on the GitLab instance:: + + topics = gl.topics.list() + +Get a specific topic by its ID:: + + topic = gl.topics.get(topic_id) + +Create a new topic:: + + topic = gl.topics.create({"name": "my-topic"}) + +Update a topic:: + + topic.description = "My new topic" + topic.save() + + # or + gl.topics.update(topic_id, {"description": "My new topic"}) + +Delete a topic:: + + topic.delete() + + # or + gl.topics.delete(topic_id) diff --git a/gitlab/client.py b/gitlab/client.py index 0dd4a6d..d3fdaab 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -180,6 +180,8 @@ class Gitlab(object): """See :class:`~gitlab.v4.objects.VariableManager`""" self.personal_access_tokens = objects.PersonalAccessTokenManager(self) """See :class:`~gitlab.v4.objects.PersonalAccessTokenManager`""" + self.topics = objects.TopicManager(self) + """See :class:`~gitlab.v4.objects.TopicManager`""" def __enter__(self) -> "Gitlab": return self diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py index b1d6484..0ab3bd4 100644 --- a/gitlab/v4/objects/__init__.py +++ b/gitlab/v4/objects/__init__.py @@ -70,6 +70,7 @@ from .statistics import * from .tags import * from .templates import * from .todos import * +from .topics import * from .triggers import * from .users import * from .variables import * diff --git a/gitlab/v4/objects/topics.py b/gitlab/v4/objects/topics.py new file mode 100644 index 0000000..76208ed --- /dev/null +++ b/gitlab/v4/objects/topics.py @@ -0,0 +1,27 @@ +from typing import Any, cast, Union + +from gitlab import types +from gitlab.base import RequiredOptional, RESTManager, RESTObject +from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin + +__all__ = [ + "Topic", + "TopicManager", +] + + +class Topic(SaveMixin, ObjectDeleteMixin, RESTObject): + pass + + +class TopicManager(CRUDMixin, RESTManager): + _path = "/topics" + _obj_cls = Topic + _create_attrs = RequiredOptional( + required=("name",), optional=("avatar", "description") + ) + _update_attrs = RequiredOptional(optional=("avatar", "description", "name")) + _types = {"avatar": types.ImageAttribute} + + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Topic: + return cast(Topic, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/tests/functional/api/test_topics.py b/tests/functional/api/test_topics.py new file mode 100644 index 0000000..7ad71a5 --- /dev/null +++ b/tests/functional/api/test_topics.py @@ -0,0 +1,21 @@ +""" +GitLab API: +https://docs.gitlab.com/ce/api/topics.html +""" + + +def test_topics(gl): + assert not gl.topics.list() + + topic = gl.topics.create({"name": "my-topic", "description": "My Topic"}) + assert topic.name == "my-topic" + assert gl.topics.list() + + topic.description = "My Updated Topic" + topic.save() + + updated_topic = gl.topics.get(topic.id) + assert updated_topic.description == topic.description + + topic.delete() + assert not gl.topics.list() diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 625cff9..109ee24 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -24,6 +24,8 @@ def reset_gitlab(gl): for deploy_token in group.deploytokens.list(): deploy_token.delete() group.delete() + for topic in gl.topics.list(): + topic.delete() for variable in gl.variables.list(): variable.delete() for user in gl.users.list(): diff --git a/tests/functional/fixtures/.env b/tests/functional/fixtures/.env index 374f7ac..30abd5c 100644 --- a/tests/functional/fixtures/.env +++ b/tests/functional/fixtures/.env @@ -1,2 +1,2 @@ GITLAB_IMAGE=gitlab/gitlab-ce -GITLAB_TAG=14.3.2-ce.0 +GITLAB_TAG=14.5.2-ce.0 diff --git a/tests/unit/objects/test_topics.py b/tests/unit/objects/test_topics.py new file mode 100644 index 0000000..14b2cfd --- /dev/null +++ b/tests/unit/objects/test_topics.py @@ -0,0 +1,119 @@ +""" +GitLab API: +https://docs.gitlab.com/ce/api/topics.html +""" +import pytest +import responses + +from gitlab.v4.objects import Topic + +name = "GitLab" +new_name = "gitlab-test" +topic_content = { + "id": 1, + "name": name, + "description": "GitLab is an open source end-to-end software development platform.", + "total_projects_count": 1000, + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", +} +topics_url = "http://localhost/api/v4/topics" +topic_url = f"{topics_url}/1" + + +@pytest.fixture +def resp_list_topics(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url=topics_url, + json=[topic_content], + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_get_topic(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url=topic_url, + json=topic_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_create_topic(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url=topics_url, + json=topic_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_update_topic(): + updated_content = dict(topic_content) + updated_content["name"] = new_name + + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.PUT, + url=topic_url, + json=updated_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_delete_topic(no_content): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.DELETE, + url=topic_url, + json=no_content, + content_type="application/json", + status=204, + ) + yield rsps + + +def test_list_topics(gl, resp_list_topics): + topics = gl.topics.list() + assert isinstance(topics, list) + assert isinstance(topics[0], Topic) + assert topics[0].name == name + + +def test_get_topic(gl, resp_get_topic): + topic = gl.topics.get(1) + assert isinstance(topic, Topic) + assert topic.name == name + + +def test_create_topic(gl, resp_create_topic): + topic = gl.topics.create({"name": name}) + assert isinstance(topic, Topic) + assert topic.name == name + + +def test_update_topic(gl, resp_update_topic): + topic = gl.topics.get(1, lazy=True) + topic.name = new_name + topic.save() + assert topic.name == new_name + + +def test_delete_topic(gl, resp_delete_topic): + topic = gl.topics.get(1, lazy=True) + topic.delete() |