From 5650db9fd2ddcfef9da4ec972fefd82a7bfdef03 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Tue, 21 Jan 2020 22:01:40 +0100 Subject: [PATCH 1/2] refactor: structure python objects in a reasonable way --- gitlab/__init__.py | 7 +- gitlab/mixins.py | 6 +- gitlab/v4/__init__.py | 1 + gitlab/v4/objects/__init__.py | 20 + gitlab/v4/objects/application.py | 137 ++++ gitlab/v4/objects/broadcast_message.py | 17 + gitlab/v4/objects/current_user.py | 62 ++ gitlab/v4/objects/deploy_key.py | 14 + gitlab/v4/objects/ee.py | 138 ++++ gitlab/v4/objects/event.py | 16 + gitlab/v4/objects/feature.py | 37 ++ gitlab/v4/objects/group.py | 635 +++++++++++++++++++ gitlab/v4/objects/hook.py | 9 + gitlab/v4/objects/issue.py | 33 + gitlab/v4/objects/mergerequest.py | 35 + gitlab/v4/objects/namespace.py | 15 + gitlab/v4/objects/notification_settings.py | 32 + gitlab/v4/{objects.py => objects/project.py} | 399 +++--------- gitlab/v4/objects/runner.py | 95 +++ gitlab/v4/objects/sidekiq.py | 81 +++ gitlab/v4/objects/snippet.py | 59 ++ gitlab/v4/objects/template.py | 43 ++ gitlab/v4/objects/todo.py | 46 ++ gitlab/v4/objects/user.py | 318 ++++++++++ 24 files changed, 1936 insertions(+), 319 deletions(-) create mode 100644 gitlab/v4/objects/__init__.py create mode 100644 gitlab/v4/objects/application.py create mode 100644 gitlab/v4/objects/broadcast_message.py create mode 100644 gitlab/v4/objects/current_user.py create mode 100644 gitlab/v4/objects/deploy_key.py create mode 100644 gitlab/v4/objects/ee.py create mode 100644 gitlab/v4/objects/event.py create mode 100644 gitlab/v4/objects/feature.py create mode 100644 gitlab/v4/objects/group.py create mode 100644 gitlab/v4/objects/hook.py create mode 100644 gitlab/v4/objects/issue.py create mode 100644 gitlab/v4/objects/mergerequest.py create mode 100644 gitlab/v4/objects/namespace.py create mode 100644 gitlab/v4/objects/notification_settings.py rename gitlab/v4/{objects.py => objects/project.py} (95%) create mode 100644 gitlab/v4/objects/runner.py create mode 100644 gitlab/v4/objects/sidekiq.py create mode 100644 gitlab/v4/objects/snippet.py create mode 100644 gitlab/v4/objects/template.py create mode 100644 gitlab/v4/objects/todo.py create mode 100644 gitlab/v4/objects/user.py diff --git a/gitlab/__init__.py b/gitlab/__init__.py index b37702319..0fdfc7186 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -18,16 +18,12 @@ from __future__ import print_function from __future__ import absolute_import +from gitlab.v4 import objects import importlib import time import warnings - import requests - import gitlab.config -from gitlab.const import * # noqa -from gitlab.exceptions import * # noqa -from gitlab import utils # noqa __title__ = "python-gitlab" __version__ = "2.0.0" @@ -110,7 +106,6 @@ def __init__( self.per_page = per_page - objects = importlib.import_module("gitlab.v%s.objects" % self._api_version) self._objects = objects self.broadcastmessages = objects.BroadcastMessageManager(self) diff --git a/gitlab/mixins.py b/gitlab/mixins.py index c812d66b7..1a7fdbae2 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -16,11 +16,9 @@ # along with this program. If not, see . import gitlab -from gitlab import base -from gitlab import cli +from gitlab import base, cli, utils from gitlab import exceptions as exc from gitlab import types as g_types -from gitlab import utils class GetMixin(object): @@ -415,7 +413,7 @@ class AccessRequestMixin(object): ("ProjectAccessRequest", "GroupAccessRequest"), tuple(), ("access_level",) ) @exc.on_http_error(exc.GitlabUpdateError) - def approve(self, access_level=gitlab.DEVELOPER_ACCESS, **kwargs): + def approve(self, access_level=gitlab.const.DEVELOPER_ACCESS, **kwargs): """Approve an access request. Args: diff --git a/gitlab/v4/__init__.py b/gitlab/v4/__init__.py index e69de29bb..e93ca6783 100644 --- a/gitlab/v4/__init__.py +++ b/gitlab/v4/__init__.py @@ -0,0 +1 @@ +from gitlab.const import * # noqa diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py new file mode 100644 index 000000000..9595c928e --- /dev/null +++ b/gitlab/v4/objects/__init__.py @@ -0,0 +1,20 @@ +from gitlab.v4.objects.application import * # noqa +from gitlab.v4.objects.broadcast_message import * # noqa +from gitlab.v4.objects.current_user import * # noqa +from gitlab.v4.objects.deploy_key import * # noqa +from gitlab.v4.objects.ee import * # noqa +from gitlab.v4.objects.event import * # noqa +from gitlab.v4.objects.feature import * # noqa +from gitlab.v4.objects.group import * # noqa +from gitlab.v4.objects.hook import * # noqa +from gitlab.v4.objects.issue import * # noqa +from gitlab.v4.objects.mergerequest import * # noqa +from gitlab.v4.objects.namespace import * # noqa +from gitlab.v4.objects.notification_settings import * # noqa +from gitlab.v4.objects.project import * # noqa +from gitlab.v4.objects.runner import * # noqa +from gitlab.v4.objects.sidekiq import * # noqa +from gitlab.v4.objects.snippet import * # noqa +from gitlab.v4.objects.template import * # noqa +from gitlab.v4.objects.todo import * # noqa +from gitlab.v4.objects.user import * # noqa diff --git a/gitlab/v4/objects/application.py b/gitlab/v4/objects/application.py new file mode 100644 index 000000000..0b51df65e --- /dev/null +++ b/gitlab/v4/objects/application.py @@ -0,0 +1,137 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class ApplicationAppearance(SaveMixin, RESTObject): + _id_attr = None + + +class ApplicationAppearanceManager(GetWithoutIdMixin, UpdateMixin, RESTManager): + _path = "/application/appearance" + _obj_cls = ApplicationAppearance + _update_attrs = ( + tuple(), + ( + "title", + "description", + "logo", + "header_logo", + "favicon", + "new_project_guidelines", + "header_message", + "footer_message", + "message_background_color", + "message_font_color", + "email_header_and_footer_enabled", + ), + ) + + @exc.on_http_error(exc.GitlabUpdateError) + def update(self, id=None, new_data=None, **kwargs): + """Update an object on the server. + + Args: + id: ID of the object to update (can be None if not required) + new_data: the update data for the object + **kwargs: Extra options to send to the server (e.g. sudo) + + Returns: + dict: The new object data (*not* a RESTObject) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabUpdateError: If the server cannot perform the request + """ + new_data = new_data or {} + data = new_data.copy() + super(ApplicationAppearanceManager, self).update(id, data, **kwargs) + + +class ApplicationSettings(SaveMixin, RESTObject): + _id_attr = None + + +class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): + _path = "/application/settings" + _obj_cls = ApplicationSettings + _update_attrs = ( + tuple(), + ( + "id", + "default_projects_limit", + "signup_enabled", + "password_authentication_enabled_for_web", + "gravatar_enabled", + "sign_in_text", + "created_at", + "updated_at", + "home_page_url", + "default_branch_protection", + "restricted_visibility_levels", + "max_attachment_size", + "session_expire_delay", + "default_project_visibility", + "default_snippet_visibility", + "default_group_visibility", + "outbound_local_requests_whitelist", + "domain_whitelist", + "domain_blacklist_enabled", + "domain_blacklist", + "external_authorization_service_enabled", + "external_authorization_service_url", + "external_authorization_service_default_label", + "external_authorization_service_timeout", + "user_oauth_applications", + "after_sign_out_path", + "container_registry_token_expire_delay", + "repository_storages", + "plantuml_enabled", + "plantuml_url", + "terminal_max_session_time", + "polling_interval_multiplier", + "rsa_key_restriction", + "dsa_key_restriction", + "ecdsa_key_restriction", + "ed25519_key_restriction", + "first_day_of_week", + "enforce_terms", + "terms", + "performance_bar_allowed_group_id", + "instance_statistics_visibility_private", + "user_show_add_ssh_key_message", + "file_template_project_id", + "local_markdown_version", + "asset_proxy_enabled", + "asset_proxy_url", + "asset_proxy_whitelist", + "geo_node_allowed_ips", + "allow_local_requests_from_hooks_and_services", + "allow_local_requests_from_web_hooks_and_services", + "allow_local_requests_from_system_hooks", + ), + ) + + @exc.on_http_error(exc.GitlabUpdateError) + def update(self, id=None, new_data=None, **kwargs): + """Update an object on the server. + + Args: + id: ID of the object to update (can be None if not required) + new_data: the update data for the object + **kwargs: Extra options to send to the server (e.g. sudo) + + Returns: + dict: The new object data (*not* a RESTObject) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabUpdateError: If the server cannot perform the request + """ + new_data = new_data or {} + data = new_data.copy() + if "domain_whitelist" in data and data["domain_whitelist"] is None: + data.pop("domain_whitelist") + super(ApplicationSettingsManager, self).update(id, data, **kwargs) diff --git a/gitlab/v4/objects/broadcast_message.py b/gitlab/v4/objects/broadcast_message.py new file mode 100644 index 000000000..f8e39f390 --- /dev/null +++ b/gitlab/v4/objects/broadcast_message.py @@ -0,0 +1,17 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class BroadcastMessage(SaveMixin, ObjectDeleteMixin, RESTObject): + pass + + +class BroadcastMessageManager(CRUDMixin, RESTManager): + _path = "/broadcast_messages" + _obj_cls = BroadcastMessage + + _create_attrs = (("message",), ("starts_at", "ends_at", "color", "font")) + _update_attrs = (tuple(), ("message", "starts_at", "ends_at", "color", "font")) diff --git a/gitlab/v4/objects/current_user.py b/gitlab/v4/objects/current_user.py new file mode 100644 index 000000000..40398e2ac --- /dev/null +++ b/gitlab/v4/objects/current_user.py @@ -0,0 +1,62 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class CurrentUserEmail(ObjectDeleteMixin, RESTObject): + _short_print_attr = "email" + + +class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/user/emails" + _obj_cls = CurrentUserEmail + _create_attrs = (("email",), tuple()) + + +class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): + pass + + +class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/user/gpg_keys" + _obj_cls = CurrentUserGPGKey + _create_attrs = (("key",), tuple()) + + +class CurrentUserKey(ObjectDeleteMixin, RESTObject): + _short_print_attr = "title" + + +class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/user/keys" + _obj_cls = CurrentUserKey + _create_attrs = (("title", "key"), tuple()) + + +class CurrentUserStatus(SaveMixin, RESTObject): + _id_attr = None + _short_print_attr = "message" + + +class CurrentUserStatusManager(GetWithoutIdMixin, UpdateMixin, RESTManager): + _path = "/user/status" + _obj_cls = CurrentUserStatus + _update_attrs = (tuple(), ("emoji", "message")) + + +class CurrentUser(RESTObject): + _id_attr = None + _short_print_attr = "username" + _managers = ( + ("status", "CurrentUserStatusManager"), + ("emails", "CurrentUserEmailManager"), + ("gpgkeys", "CurrentUserGPGKeyManager"), + ("keys", "CurrentUserKeyManager"), + ) + + +class CurrentUserManager(GetWithoutIdMixin, RESTManager): + _path = "/user" + _obj_cls = CurrentUser diff --git a/gitlab/v4/objects/deploy_key.py b/gitlab/v4/objects/deploy_key.py new file mode 100644 index 000000000..dddfb6dd4 --- /dev/null +++ b/gitlab/v4/objects/deploy_key.py @@ -0,0 +1,14 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class DeployKey(RESTObject): + pass + + +class DeployKeyManager(ListMixin, RESTManager): + _path = "/deploy_keys" + _obj_cls = DeployKey diff --git a/gitlab/v4/objects/ee.py b/gitlab/v4/objects/ee.py new file mode 100644 index 000000000..46d61e013 --- /dev/null +++ b/gitlab/v4/objects/ee.py @@ -0,0 +1,138 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class AuditEvent(RESTObject): + _id_attr = "id" + + +class AuditEventManager(ListMixin, RESTManager): + _path = "/audit_events" + _obj_cls = AuditEvent + _list_filters = ("created_after", "created_before", "entity_type", "entity_id") + + +class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject): + @cli.register_custom_action("GeoNode") + @exc.on_http_error(exc.GitlabRepairError) + def repair(self, **kwargs): + """Repair the OAuth authentication of the geo node. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabRepairError: If the server failed to perform the request + """ + path = "/geo_nodes/%s/repair" % self.get_id() + server_data = self.manager.gitlab.http_post(path, **kwargs) + self._update_attrs(server_data) + + @cli.register_custom_action("GeoNode") + @exc.on_http_error(exc.GitlabGetError) + def status(self, **kwargs): + """Get the status of the geo node. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the server failed to perform the request + + Returns: + dict: The status of the geo node + """ + path = "/geo_nodes/%s/status" % self.get_id() + return self.manager.gitlab.http_get(path, **kwargs) + + +class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): + _path = "/geo_nodes" + _obj_cls = GeoNode + _update_attrs = ( + tuple(), + ("enabled", "url", "files_max_capacity", "repos_max_capacity"), + ) + + @cli.register_custom_action("GeoNodeManager") + @exc.on_http_error(exc.GitlabGetError) + def status(self, **kwargs): + """Get the status of all the geo nodes. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the server failed to perform the request + + Returns: + list: The status of all the geo nodes + """ + return self.gitlab.http_list("/geo_nodes/status", **kwargs) + + @cli.register_custom_action("GeoNodeManager") + @exc.on_http_error(exc.GitlabGetError) + def current_failures(self, **kwargs): + """Get the list of failures on the current geo node. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the server failed to perform the request + + Returns: + list: The list of failures + """ + return self.gitlab.http_list("/geo_nodes/current/failures", **kwargs) + + +class LDAPGroup(RESTObject): + _id_attr = None + + +class LDAPGroupManager(RESTManager): + _path = "/ldap/groups" + _obj_cls = LDAPGroup + _list_filters = ("search", "provider") + + @exc.on_http_error(exc.GitlabListError) + def list(self, **kwargs): + """Retrieve a list of objects. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Returns: + list: The list of objects, or a generator if `as_list` is False + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the server cannot perform the request + """ + data = kwargs.copy() + if self.gitlab.per_page: + data.setdefault("per_page", self.gitlab.per_page) + + if "provider" in data: + path = "/ldap/%s/groups" % data["provider"] + else: + path = self._path + + obj = self.gitlab.http_list(path, **data) + if isinstance(obj, list): + return [self._obj_cls(self, item) for item in obj] + else: + return base.RESTObjectList(self, self._obj_cls, obj) diff --git a/gitlab/v4/objects/event.py b/gitlab/v4/objects/event.py new file mode 100644 index 000000000..add25812b --- /dev/null +++ b/gitlab/v4/objects/event.py @@ -0,0 +1,16 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Event(RESTObject): + _id_attr = None + _short_print_attr = "target_title" + + +class EventManager(ListMixin, RESTManager): + _path = "/events" + _obj_cls = Event + _list_filters = ("action", "target_type", "before", "after", "sort") diff --git a/gitlab/v4/objects/feature.py b/gitlab/v4/objects/feature.py new file mode 100644 index 000000000..3cc753bfe --- /dev/null +++ b/gitlab/v4/objects/feature.py @@ -0,0 +1,37 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Feature(ObjectDeleteMixin, RESTObject): + _id_attr = "name" + + +class FeatureManager(ListMixin, DeleteMixin, RESTManager): + _path = "/features/" + _obj_cls = Feature + + @exc.on_http_error(exc.GitlabSetError) + def set(self, name, value, feature_group=None, user=None, **kwargs): + """Create or update the object. + + Args: + name (str): The value to set for the object + value (bool/int): The value to set for the object + feature_group (str): A feature group name + user (str): A GitLab username + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabSetError: If an error occured + + Returns: + obj: The created/updated attribute + """ + path = "%s/%s" % (self.path, name.replace("/", "%2F")) + data = {"value": value, "feature_group": feature_group, "user": user} + server_data = self.gitlab.http_post(path, post_data=data, **kwargs) + return self._obj_cls(self, server_data) diff --git a/gitlab/v4/objects/group.py b/gitlab/v4/objects/group.py new file mode 100644 index 000000000..9be2b3f1a --- /dev/null +++ b/gitlab/v4/objects/group.py @@ -0,0 +1,635 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab.v4 import objects +from gitlab import utils + + +class GroupAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): + pass + + +class GroupAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/groups/%(group_id)s/access_requests" + _obj_cls = GroupAccessRequest + _from_parent_attrs = {"group_id": "id"} + + +class GroupBadge(SaveMixin, ObjectDeleteMixin, RESTObject): + pass + + +class GroupBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/badges" + _obj_cls = GroupBadge + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("link_url", "image_url"), tuple()) + _update_attrs = (tuple(), ("link_url", "image_url")) + + +class GroupBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): + pass + + +class GroupBoardListManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/boards/%(board_id)s/lists" + _obj_cls = GroupBoardList + _from_parent_attrs = {"group_id": "group_id", "board_id": "id"} + _create_attrs = (("label_id",), tuple()) + _update_attrs = (("position",), tuple()) + + +class GroupBoard(SaveMixin, ObjectDeleteMixin, RESTObject): + _managers = (("lists", "GroupBoardListManager"),) + + +class GroupBoardManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/boards" + _obj_cls = GroupBoard + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("name",), tuple()) + + +class GroupCluster(SaveMixin, ObjectDeleteMixin, RESTObject): + pass + + +class GroupClusterManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/clusters" + _obj_cls = GroupCluster + _from_parent_attrs = {"group_id": "id"} + _create_attrs = ( + ("name", "platform_kubernetes_attributes"), + ("domain", "enabled", "managed", "environment_scope"), + ) + _update_attrs = ( + tuple(), + ( + "name", + "domain", + "management_project_id", + "platform_kubernetes_attributes", + "environment_scope", + ), + ) + + @exc.on_http_error(exc.GitlabStopError) + def create(self, data, **kwargs): + """Create a new object. + + Args: + data (dict): Parameters to send to the server to create the + resource + **kwargs: Extra options to send to the server (e.g. sudo or + 'ref_name', 'stage', 'name', 'all') + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabCreateError: If the server cannot perform the request + + Returns: + RESTObject: A new instance of the manage object class build with + the data sent by the server + """ + path = "%s/user" % (self.path) + return CreateMixin.create(self, data, path=path, **kwargs) + + +class GroupCustomAttribute(ObjectDeleteMixin, RESTObject): + _id_attr = "key" + + +class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): + _path = "/groups/%(group_id)s/custom_attributes" + _obj_cls = GroupCustomAttribute + _from_parent_attrs = {"group_id": "id"} + + +class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject): + _id_attr = "epic_issue_id" + + def save(self, **kwargs): + """Save the changes made to the object to the server. + + The object is updated to match what the server returns. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raise: + GitlabAuthenticationError: If authentication is not correct + GitlabUpdateError: If the server cannot perform the request + """ + updated_data = self._get_updated_data() + # Nothing to update. Server fails if sent an empty dict. + if not updated_data: + return + + # call the manager + obj_id = self.get_id() + self.manager.update(obj_id, updated_data, **kwargs) + + +class GroupEpicIssueManager( + ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = "/groups/%(group_id)s/epics/%(epic_iid)s/issues" + _obj_cls = GroupEpicIssue + _from_parent_attrs = {"group_id": "group_id", "epic_iid": "iid"} + _create_attrs = (("issue_id",), tuple()) + _update_attrs = (tuple(), ("move_before_id", "move_after_id")) + + @exc.on_http_error(exc.GitlabCreateError) + def create(self, data, **kwargs): + """Create a new object. + + Args: + data (dict): Parameters to send to the server to create the + resource + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabCreateError: If the server cannot perform the request + + Returns: + RESTObject: A new instance of the manage object class build with + the data sent by the server + """ + CreateMixin._check_missing_create_attrs(self, data) + path = "%s/%s" % (self.path, data.pop("issue_id")) + server_data = self.gitlab.http_post(path, **kwargs) + # The epic_issue_id attribute doesn't exist when creating the resource, + # but is used everywhere elese. Let's create it to be consistent client + # side + server_data["epic_issue_id"] = server_data["id"] + return self._obj_cls(self, server_data) + + +class GroupEpicResourceLabelEvent(RESTObject): + pass + + +class GroupEpicResourceLabelEventManager(RetrieveMixin, RESTManager): + _path = "/groups/%(group_id)s/epics/%(epic_id)s/resource_label_events" + _obj_cls = GroupEpicResourceLabelEvent + _from_parent_attrs = {"group_id": "group_id", "epic_id": "id"} + + +class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): + _id_attr = "iid" + _managers = ( + ("issues", "GroupEpicIssueManager"), + ("resourcelabelevents", "GroupEpicResourceLabelEventManager"), + ) + + +class GroupEpicManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/epics" + _obj_cls = GroupEpic + _from_parent_attrs = {"group_id": "id"} + _list_filters = ("author_id", "labels", "order_by", "sort", "search") + _create_attrs = (("title",), ("labels", "description", "start_date", "end_date")) + _update_attrs = ( + tuple(), + ("title", "labels", "description", "start_date", "end_date"), + ) + _types = {"labels": types.ListAttribute} + + +class GroupIssue(RESTObject): + pass + + +class GroupIssueManager(ListMixin, RESTManager): + _path = "/groups/%(group_id)s/issues" + _obj_cls = GroupIssue + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "state", + "labels", + "milestone", + "order_by", + "sort", + "iids", + "author_id", + "assignee_id", + "my_reaction_emoji", + "search", + "created_after", + "created_before", + "updated_after", + "updated_before", + ) + _types = {"labels": types.ListAttribute} + + +class GroupLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = "name" + + # Update without ID, but we need an ID to get from list. + @exc.on_http_error(exc.GitlabUpdateError) + def save(self, **kwargs): + """Saves the changes made to the object to the server. + + The object is updated to match what the server returns. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct. + GitlabUpdateError: If the server cannot perform the request. + """ + updated_data = self._get_updated_data() + + # call the manager + server_data = self.manager.update(None, updated_data, **kwargs) + self._update_attrs(server_data) + + +class GroupLabelManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager): + _path = "/groups/%(group_id)s/labels" + _obj_cls = GroupLabel + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("name", "color"), ("description", "priority")) + _update_attrs = (("name",), ("new_name", "color", "description", "priority")) + + # Update without ID. + def update(self, name, new_data=None, **kwargs): + """Update a Label on the server. + + Args: + name: The name of the label + **kwargs: Extra options to send to the server (e.g. sudo) + """ + new_data = new_data or {} + if name: + new_data["name"] = name + return super().update(id=None, new_data=new_data, **kwargs) + + # Delete without ID. + @exc.on_http_error(exc.GitlabDeleteError) + def delete(self, name, **kwargs): + """Delete a Label on the server. + + Args: + name: The name of the label + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabDeleteError: If the server cannot perform the request + """ + self.gitlab.http_delete(self.path, query_data={"name": name}, **kwargs) + + +class GroupMember(SaveMixin, ObjectDeleteMixin, RESTObject): + _short_print_attr = "username" + + +class GroupMemberManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/members" + _obj_cls = GroupMember + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("access_level", "user_id"), ("expires_at",)) + _update_attrs = (("access_level",), ("expires_at",)) + + @cli.register_custom_action("GroupMemberManager") + @exc.on_http_error(exc.GitlabListError) + def all(self, **kwargs): + """List all the members, included inherited ones. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: The list of members + """ + + path = "%s/all" % self.path + obj = self.gitlab.http_list(path, **kwargs) + return [self._obj_cls(self, item) for item in obj] + + +class GroupMergeRequest(RESTObject): + pass + + +class GroupMergeRequestManager(ListMixin, RESTManager): + _path = "/groups/%(group_id)s/merge_requests" + _obj_cls = GroupMergeRequest + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "state", + "order_by", + "sort", + "milestone", + "view", + "labels", + "created_after", + "created_before", + "updated_after", + "updated_before", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "source_branch", + "target_branch", + "search", + ) + _types = {"labels": types.ListAttribute} + + +class GroupMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): + _short_print_attr = "title" + + @cli.register_custom_action("GroupMilestone") + @exc.on_http_error(exc.GitlabListError) + def issues(self, **kwargs): + """List issues related to this milestone. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: The list of issues + """ + + path = "%s/%s/issues" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = GroupIssueManager(self.manager.gitlab, parent=self.manager._parent) + # FIXME(gpocentek): the computed manager path is not correct + return RESTObjectList(manager, GroupIssue, data_list) + + @cli.register_custom_action("GroupMilestone") + @exc.on_http_error(exc.GitlabListError) + def merge_requests(self, **kwargs): + """List the merge requests related to this milestone. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: The list of merge requests + """ + path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = GroupIssueManager(self.manager.gitlab, parent=self.manager._parent) + # FIXME(gpocentek): the computed manager path is not correct + return RESTObjectList(manager, GroupMergeRequest, data_list) + + +class GroupMilestoneManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/milestones" + _obj_cls = GroupMilestone + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("title",), ("description", "due_date", "start_date")) + _update_attrs = ( + tuple(), + ("title", "description", "due_date", "start_date", "state_event"), + ) + _list_filters = ("iids", "state", "search") + + +class GroupNotificationSettings(objects.NotificationSettings): + pass + + +class GroupNotificationSettingsManager(objects.NotificationSettingsManager): + _path = "/groups/%(group_id)s/notification_settings" + _obj_cls = GroupNotificationSettings + _from_parent_attrs = {"group_id": "id"} + + +class GroupProject(RESTObject): + pass + + +class GroupProjectManager(ListMixin, RESTManager): + _path = "/groups/%(group_id)s/projects" + _obj_cls = GroupProject + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "archived", + "visibility", + "order_by", + "sort", + "search", + "ci_enabled_first", + "simple", + "owned", + "starred", + "with_custom_attributes", + "include_subgroups", + ) + + +class GroupSubgroup(RESTObject): + pass + + +class GroupSubgroupManager(ListMixin, RESTManager): + _path = "/groups/%(group_id)s/subgroups" + _obj_cls = GroupSubgroup + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "skip_groups", + "all_available", + "search", + "order_by", + "sort", + "statistics", + "owned", + "with_custom_attributes", + ) + + +class GroupVariable(SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = "key" + + +class GroupVariableManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/variables" + _obj_cls = GroupVariable + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("key", "value"), ("protected", "variable_type")) + _update_attrs = (("key", "value"), ("protected", "variable_type")) + + +class Group(SaveMixin, ObjectDeleteMixin, RESTObject): + _short_print_attr = "name" + _managers = ( + ("accessrequests", "GroupAccessRequestManager"), + ("badges", "GroupBadgeManager"), + ("boards", "GroupBoardManager"), + ("customattributes", "GroupCustomAttributeManager"), + ("epics", "GroupEpicManager"), + ("issues", "GroupIssueManager"), + ("labels", "GroupLabelManager"), + ("members", "GroupMemberManager"), + ("mergerequests", "GroupMergeRequestManager"), + ("milestones", "GroupMilestoneManager"), + ("notificationsettings", "GroupNotificationSettingsManager"), + ("projects", "GroupProjectManager"), + ("subgroups", "GroupSubgroupManager"), + ("variables", "GroupVariableManager"), + ("clusters", "GroupClusterManager"), + ) + + @cli.register_custom_action("Group", ("to_project_id",)) + @exc.on_http_error(exc.GitlabTransferProjectError) + def transfer_project(self, to_project_id, **kwargs): + """Transfer a project to this group. + + Args: + to_project_id (int): ID of the project to transfer + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabTransferProjectError: If the project could not be transfered + """ + path = "/groups/%s/projects/%s" % (self.id, to_project_id) + self.manager.gitlab.http_post(path, **kwargs) + + @cli.register_custom_action("Group", ("scope", "search")) + @exc.on_http_error(exc.GitlabSearchError) + def search(self, scope, search, **kwargs): + """Search the group resources matching the provided string.' + + Args: + scope (str): Scope of the search + search (str): Search string + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabSearchError: If the server failed to perform the request + + Returns: + GitlabList: A list of dicts describing the resources found. + """ + data = {"scope": scope, "search": search} + path = "/groups/%s/search" % self.get_id() + return self.manager.gitlab.http_list(path, query_data=data, **kwargs) + + @cli.register_custom_action("Group", ("cn", "group_access", "provider")) + @exc.on_http_error(exc.GitlabCreateError) + def add_ldap_group_link(self, cn, group_access, provider, **kwargs): + """Add an LDAP group link. + + Args: + cn (str): CN of the LDAP group + group_access (int): Minimum access level for members of the LDAP + group + provider (str): LDAP provider for the LDAP group + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabCreateError: If the server cannot perform the request + """ + path = "/groups/%s/ldap_group_links" % self.get_id() + data = {"cn": cn, "group_access": group_access, "provider": provider} + self.manager.gitlab.http_post(path, post_data=data, **kwargs) + + @cli.register_custom_action("Group", ("cn",), ("provider",)) + @exc.on_http_error(exc.GitlabDeleteError) + def delete_ldap_group_link(self, cn, provider=None, **kwargs): + """Delete an LDAP group link. + + Args: + cn (str): CN of the LDAP group + provider (str): LDAP provider for the LDAP group + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabDeleteError: If the server cannot perform the request + """ + path = "/groups/%s/ldap_group_links" % self.get_id() + if provider is not None: + path += "/%s" % provider + path += "/%s" % cn + self.manager.gitlab.http_delete(path) + + @cli.register_custom_action("Group") + @exc.on_http_error(exc.GitlabCreateError) + def ldap_sync(self, **kwargs): + """Sync LDAP groups. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabCreateError: If the server cannot perform the request + """ + path = "/groups/%s/ldap_sync" % self.get_id() + self.manager.gitlab.http_post(path, **kwargs) + + +class GroupManager(CRUDMixin, RESTManager): + _path = "/groups" + _obj_cls = Group + _list_filters = ( + "skip_groups", + "all_available", + "search", + "order_by", + "sort", + "statistics", + "owned", + "with_custom_attributes", + ) + _create_attrs = ( + ("name", "path"), + ( + "description", + "visibility", + "parent_id", + "lfs_enabled", + "request_access_enabled", + ), + ) + _update_attrs = ( + tuple(), + ( + "name", + "path", + "description", + "visibility", + "lfs_enabled", + "request_access_enabled", + ), + ) diff --git a/gitlab/v4/objects/hook.py b/gitlab/v4/objects/hook.py new file mode 100644 index 000000000..bcf5be53a --- /dev/null +++ b/gitlab/v4/objects/hook.py @@ -0,0 +1,9 @@ +class Hook(ObjectDeleteMixin, RESTObject): + _url = "/hooks" + _short_print_attr = "url" + + +class HookManager(NoUpdateMixin, RESTManager): + _path = "/hooks" + _obj_cls = Hook + _create_attrs = (("url",), tuple()) diff --git a/gitlab/v4/objects/issue.py b/gitlab/v4/objects/issue.py new file mode 100644 index 000000000..e877cdcc4 --- /dev/null +++ b/gitlab/v4/objects/issue.py @@ -0,0 +1,33 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Issue(RESTObject): + _url = "/issues" + _short_print_attr = "title" + + +class IssueManager(ListMixin, RESTManager): + _path = "/issues" + _obj_cls = Issue + _list_filters = ( + "state", + "labels", + "milestone", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "iids", + "order_by", + "sort", + "search", + "created_after", + "created_before", + "updated_after", + "updated_before", + ) + _types = {"labels": types.ListAttribute} diff --git a/gitlab/v4/objects/mergerequest.py b/gitlab/v4/objects/mergerequest.py new file mode 100644 index 000000000..12a0b1d7f --- /dev/null +++ b/gitlab/v4/objects/mergerequest.py @@ -0,0 +1,35 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class MergeRequest(RESTObject): + pass + + +class MergeRequestManager(ListMixin, RESTManager): + _path = "/merge_requests" + _obj_cls = MergeRequest + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "state", + "order_by", + "sort", + "milestone", + "view", + "labels", + "created_after", + "created_before", + "updated_after", + "updated_before", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "source_branch", + "target_branch", + "search", + ) + _types = {"labels": types.ListAttribute} diff --git a/gitlab/v4/objects/namespace.py b/gitlab/v4/objects/namespace.py new file mode 100644 index 000000000..4f2dec72e --- /dev/null +++ b/gitlab/v4/objects/namespace.py @@ -0,0 +1,15 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Namespace(RESTObject): + pass + + +class NamespaceManager(RetrieveMixin, RESTManager): + _path = "/namespaces" + _obj_cls = Namespace + _list_filters = ("search",) diff --git a/gitlab/v4/objects/notification_settings.py b/gitlab/v4/objects/notification_settings.py new file mode 100644 index 000000000..3ef1eb56e --- /dev/null +++ b/gitlab/v4/objects/notification_settings.py @@ -0,0 +1,32 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class NotificationSettings(SaveMixin, RESTObject): + _id_attr = None + + +class NotificationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): + _path = "/notification_settings" + _obj_cls = NotificationSettings + + _update_attrs = ( + tuple(), + ( + "level", + "notification_email", + "new_note", + "new_issue", + "reopen_issue", + "close_issue", + "reassign_issue", + "new_merge_request", + "reopen_merge_request", + "close_merge_request", + "reassign_merge_request", + "merge_merge_request", + ), + ) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects/project.py similarity index 95% rename from gitlab/v4/objects.py rename to gitlab/v4/objects/project.py index 88ede5623..0ec5f7e1c 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects/project.py @@ -1,31 +1,11 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from __future__ import print_function -from __future__ import absolute_import import base64 - from gitlab.base import * # noqa -from gitlab import cli from gitlab.exceptions import * # noqa from gitlab.mixins import * # noqa from gitlab import types from gitlab import utils + VISIBILITY_PRIVATE = "private" VISIBILITY_INTERNAL = "internal" VISIBILITY_PUBLIC = "public" @@ -1532,6 +1512,8 @@ class PagesDomain(RESTObject): class PagesDomainManager(ListMixin, RESTManager): _path = "/pages/domains" _obj_cls = PagesDomain +======= +>>>>>>> 509394d... refactor: structure python objects in a reasonable way:gitlab/v4/objects/project.py class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): @@ -4500,136 +4482,6 @@ def upload(self, filename, filedata=None, filepath=None, **kwargs): return {"alt": data["alt"], "url": data["url"], "markdown": data["markdown"]} - @cli.register_custom_action("Project", optional=("wiki",)) - @exc.on_http_error(exc.GitlabGetError) - def snapshot( - self, wiki=False, streamed=False, action=None, chunk_size=1024, **kwargs - ): - """Return a snapshot of the repository. - - Args: - wiki (bool): If True return the wiki repository - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment. - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the content could not be retrieved - - Returns: - str: The uncompressed tar archive of the repository - """ - path = "/projects/%s/snapshot" % self.get_id() - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - - @cli.register_custom_action("Project", ("scope", "search")) - @exc.on_http_error(exc.GitlabSearchError) - def search(self, scope, search, **kwargs): - """Search the project resources matching the provided string.' - - Args: - scope (str): Scope of the search - search (str): Search string - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSearchError: If the server failed to perform the request - - Returns: - GitlabList: A list of dicts describing the resources found. - """ - data = {"scope": scope, "search": search} - path = "/projects/%s/search" % self.get_id() - return self.manager.gitlab.http_list(path, query_data=data, **kwargs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabCreateError) - def mirror_pull(self, **kwargs): - """Start the pull mirroring process for the project. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server failed to perform the request - """ - path = "/projects/%s/mirror/pull" % self.get_id() - self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("Project", ("to_namespace",)) - @exc.on_http_error(exc.GitlabTransferProjectError) - def transfer_project(self, to_namespace, **kwargs): - """Transfer a project to the given namespace ID - - Args: - to_namespace (str): ID or path of the namespace to transfer the - project to - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTransferProjectError: If the project could not be transfered - """ - path = "/projects/%s/transfer" % (self.id,) - self.manager.gitlab.http_put( - path, post_data={"namespace": to_namespace}, **kwargs - ) - - @cli.register_custom_action("Project", ("ref_name", "artifact_path", "job")) - @exc.on_http_error(exc.GitlabGetError) - def artifact( - self, - ref_name, - artifact_path, - job, - streamed=False, - action=None, - chunk_size=1024, - **kwargs - ): - """Download a single artifact file from a specific tag or branch from within the job’s artifacts archive. - - Args: - ref_name (str): Branch or tag name in repository. HEAD or SHA references are not supported. - artifact_path (str): Path to a file inside the artifacts archive. - job (str): The name of the job. - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - treatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the artifacts could not be retrieved - - Returns: - str: The artifacts if `streamed` is False, None otherwise. - """ - - path = "/projects/%s/jobs/artifacts/%s/raw/%s?job=%s" % ( - self.get_id(), - ref_name, - artifact_path, - job, - ) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - class ProjectManager(CRUDMixin, RESTManager): _path = "/projects" @@ -4811,211 +4663,140 @@ def import_github( return result -class RunnerJob(RESTObject): - pass - - -class RunnerJobManager(ListMixin, RESTManager): - _path = "/runners/%(runner_id)s/jobs" - _obj_cls = RunnerJob - _from_parent_attrs = {"runner_id": "id"} - _list_filters = ("status",) - - -class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): - _managers = (("jobs", "RunnerJobManager"),) +class PagesDomain(RESTObject): + _id_attr = "domain" -class RunnerManager(CRUDMixin, RESTManager): - _path = "/runners" - _obj_cls = Runner - _list_filters = ("scope",) - _create_attrs = ( - ("token",), - ( - "description", - "info", - "active", - "locked", - "run_untagged", - "tag_list", - "maximum_timeout", - ), - ) - _update_attrs = ( - tuple(), - ( - "description", - "active", - "tag_list", - "run_untagged", - "locked", - "access_level", - "maximum_timeout", - ), - ) +class PagesDomainManager(ListMixin, RESTManager): + _path = "/pages/domains" + _obj_cls = PagesDomain - @cli.register_custom_action("RunnerManager", tuple(), ("scope",)) - @exc.on_http_error(exc.GitlabListError) - def all(self, scope=None, **kwargs): - """List all the runners. + @cli.register_custom_action("Project", optional=("wiki",)) + @exc.on_http_error(exc.GitlabGetError) + def snapshot( + self, wiki=False, streamed=False, action=None, chunk_size=1024, **kwargs + ): + """Return a snapshot of the repository. Args: - scope (str): The scope of runners to show, one of: specific, - shared, active, paused, online - all (bool): If True, return all the items, without pagination - per_page (int): Number of items to retrieve per request - page (int): ID of the page to return (starts with page 1) - as_list (bool): If set to False and no pagination option is - defined, return a generator instead of a list + wiki (bool): If True return the wiki repository + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data + chunk_size (int): Size of each chunk **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct - GitlabListError: If the server failed to perform the request + GitlabGetError: If the content could not be retrieved Returns: - list(Runner): a list of runners matching the scope. - """ - path = "/runners/all" - query_data = {} - if scope is not None: - query_data["scope"] = scope - return self.gitlab.http_list(path, query_data, **kwargs) - - @cli.register_custom_action("RunnerManager", ("token",)) - @exc.on_http_error(exc.GitlabVerifyError) - def verify(self, token, **kwargs): - """Validates authentication credentials for a registered Runner. - - Args: - token (str): The runner's authentication token - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabVerifyError: If the server failed to verify the token - """ - path = "/runners/verify" - post_data = {"token": token} - self.gitlab.http_post(path, post_data=post_data, **kwargs) - - -class Todo(ObjectDeleteMixin, RESTObject): - @cli.register_custom_action("Todo") - @exc.on_http_error(exc.GitlabTodoError) - def mark_as_done(self, **kwargs): - """Mark the todo as done. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTodoError: If the server failed to perform the request + str: The uncompressed tar archive of the repository """ - path = "%s/%s/mark_as_done" % (self.manager.path, self.id) - server_data = self.manager.gitlab.http_post(path, **kwargs) - self._update_attrs(server_data) - - -class TodoManager(ListMixin, DeleteMixin, RESTManager): - _path = "/todos" - _obj_cls = Todo - _list_filters = ("action", "author_id", "project_id", "state", "type") + path = "/projects/%s/snapshot" % self.get_id() + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) + return utils.response_content(result, streamed, action, chunk_size) - @cli.register_custom_action("TodoManager") - @exc.on_http_error(exc.GitlabTodoError) - def mark_all_as_done(self, **kwargs): - """Mark all the todos as done. + @cli.register_custom_action("Project", ("scope", "search")) + @exc.on_http_error(exc.GitlabSearchError) + def search(self, scope, search, **kwargs): + """Search the project resources matching the provided string.' Args: + scope (str): Scope of the search + search (str): Search string **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct - GitlabTodoError: If the server failed to perform the request + GitlabSearchError: If the server failed to perform the request Returns: - int: The number of todos maked done + GitlabList: A list of dicts describing the resources found. """ - result = self.gitlab.http_post("/todos/mark_as_done", **kwargs) - + data = {"scope": scope, "search": search} + path = "/projects/%s/search" % self.get_id() + return self.manager.gitlab.http_list(path, query_data=data, **kwargs) -class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject): - @cli.register_custom_action("GeoNode") - @exc.on_http_error(exc.GitlabRepairError) - def repair(self, **kwargs): - """Repair the OAuth authentication of the geo node. + @cli.register_custom_action("Project") + @exc.on_http_error(exc.GitlabCreateError) + def mirror_pull(self, **kwargs): + """Start the pull mirroring process for the project. Args: **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct - GitlabRepairError: If the server failed to perform the request + GitlabCreateError: If the server failed to perform the request """ - path = "/geo_nodes/%s/repair" % self.get_id() - server_data = self.manager.gitlab.http_post(path, **kwargs) - self._update_attrs(server_data) + path = "/projects/%s/mirror/pull" % self.get_id() + self.manager.gitlab.http_post(path, **kwargs) - @cli.register_custom_action("GeoNode") - @exc.on_http_error(exc.GitlabGetError) - def status(self, **kwargs): - """Get the status of the geo node. + @cli.register_custom_action("Project", ("to_namespace",)) + @exc.on_http_error(exc.GitlabTransferProjectError) + def transfer_project(self, to_namespace, **kwargs): + """Transfer a project to the given namespace ID Args: + to_namespace (str): ID or path of the namespace to transfer the + project to **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - dict: The status of the geo node + GitlabTransferProjectError: If the project could not be transfered """ - path = "/geo_nodes/%s/status" % self.get_id() - return self.manager.gitlab.http_get(path, **kwargs) - - -class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): - _path = "/geo_nodes" - _obj_cls = GeoNode - _update_attrs = ( - tuple(), - ("enabled", "url", "files_max_capacity", "repos_max_capacity"), - ) + path = "/projects/%s/transfer" % (self.id,) + self.manager.gitlab.http_put( + path, post_data={"namespace": to_namespace}, **kwargs + ) - @cli.register_custom_action("GeoNodeManager") + @cli.register_custom_action("Project", ("ref_name", "artifact_path", "job")) @exc.on_http_error(exc.GitlabGetError) - def status(self, **kwargs): - """Get the status of all the geo nodes. + def artifact( + self, + ref_name, + artifact_path, + job, + streamed=False, + action=None, + chunk_size=1024, + **kwargs + ): + """Download a single artifact file from a specific tag or branch from within the job’s artifacts archive. Args: + ref_name (str): Branch or tag name in repository. HEAD or SHA references are not supported. + artifact_path (str): Path to a file inside the artifacts archive. + job (str): The name of the job. + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment + action (callable): Callable responsible of dealing with chunk of + data + chunk_size (int): Size of each chunk **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request + GitlabGetError: If the artifacts could not be retrieved Returns: - list: The status of all the geo nodes + str: The artifacts if `streamed` is False, None otherwise. """ - return self.gitlab.http_list("/geo_nodes/status", **kwargs) - - @cli.register_custom_action("GeoNodeManager") - @exc.on_http_error(exc.GitlabGetError) - def current_failures(self, **kwargs): - """Get the list of failures on the current geo node. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - list: The list of failures - """ - return self.gitlab.http_list("/geo_nodes/current/failures", **kwargs) + path = "/projects/%s/jobs/artifacts/%s/raw/%s?job=%s" % ( + self.get_id(), + ref_name, + artifact_path, + job, + ) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) + return utils.response_content(result, streamed, action, chunk_size) diff --git a/gitlab/v4/objects/runner.py b/gitlab/v4/objects/runner.py new file mode 100644 index 000000000..fd758770d --- /dev/null +++ b/gitlab/v4/objects/runner.py @@ -0,0 +1,95 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class RunnerJob(RESTObject): + pass + + +class RunnerJobManager(ListMixin, RESTManager): + _path = "/runners/%(runner_id)s/jobs" + _obj_cls = RunnerJob + _from_parent_attrs = {"runner_id": "id"} + _list_filters = ("status",) + + +class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): + _managers = (("jobs", "RunnerJobManager"),) + + +class RunnerManager(CRUDMixin, RESTManager): + _path = "/runners" + _obj_cls = Runner + _list_filters = ("scope",) + _create_attrs = ( + ("token",), + ( + "description", + "info", + "active", + "locked", + "run_untagged", + "tag_list", + "maximum_timeout", + ), + ) + _update_attrs = ( + tuple(), + ( + "description", + "active", + "tag_list", + "run_untagged", + "locked", + "access_level", + "maximum_timeout", + ), + ) + + @cli.register_custom_action("RunnerManager", tuple(), ("scope",)) + @exc.on_http_error(exc.GitlabListError) + def all(self, scope=None, **kwargs): + """List all the runners. + + Args: + scope (str): The scope of runners to show, one of: specific, + shared, active, paused, online + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the server failed to perform the request + + Returns: + list(Runner): a list of runners matching the scope. + """ + path = "/runners/all" + query_data = {} + if scope is not None: + query_data["scope"] = scope + return self.gitlab.http_list(path, query_data, **kwargs) + + @cli.register_custom_action("RunnerManager", ("token",)) + @exc.on_http_error(exc.GitlabVerifyError) + def verify(self, token, **kwargs): + """Validates authentication credentials for a registered Runner. + + Args: + token (str): The runner's authentication token + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabVerifyError: If the server failed to verify the token + """ + path = "/runners/verify" + post_data = {"token": token} + self.gitlab.http_post(path, post_data=post_data, **kwargs) diff --git a/gitlab/v4/objects/sidekiq.py b/gitlab/v4/objects/sidekiq.py new file mode 100644 index 000000000..13d02ae11 --- /dev/null +++ b/gitlab/v4/objects/sidekiq.py @@ -0,0 +1,81 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class SidekiqManager(RESTManager): + """Manager for the Sidekiq methods. + + This manager doesn't actually manage objects but provides helper fonction + for the sidekiq metrics API. + """ + + @cli.register_custom_action("SidekiqManager") + @exc.on_http_error(exc.GitlabGetError) + def queue_metrics(self, **kwargs): + """Return the registred queues information. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the information couldn't be retrieved + + Returns: + dict: Information about the Sidekiq queues + """ + return self.gitlab.http_get("/sidekiq/queue_metrics", **kwargs) + + @cli.register_custom_action("SidekiqManager") + @exc.on_http_error(exc.GitlabGetError) + def process_metrics(self, **kwargs): + """Return the registred sidekiq workers. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the information couldn't be retrieved + + Returns: + dict: Information about the register Sidekiq worker + """ + return self.gitlab.http_get("/sidekiq/process_metrics", **kwargs) + + @cli.register_custom_action("SidekiqManager") + @exc.on_http_error(exc.GitlabGetError) + def job_stats(self, **kwargs): + """Return statistics about the jobs performed. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the information couldn't be retrieved + + Returns: + dict: Statistics about the Sidekiq jobs performed + """ + return self.gitlab.http_get("/sidekiq/job_stats", **kwargs) + + @cli.register_custom_action("SidekiqManager") + @exc.on_http_error(exc.GitlabGetError) + def compound_metrics(self, **kwargs): + """Return all available metrics and statistics. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the information couldn't be retrieved + + Returns: + dict: All available Sidekiq metrics and statistics + """ + return self.gitlab.http_get("/sidekiq/compound_metrics", **kwargs) diff --git a/gitlab/v4/objects/snippet.py b/gitlab/v4/objects/snippet.py new file mode 100644 index 000000000..5776dcc35 --- /dev/null +++ b/gitlab/v4/objects/snippet.py @@ -0,0 +1,59 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Snippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): + _short_print_attr = "title" + + @cli.register_custom_action("Snippet") + @exc.on_http_error(exc.GitlabGetError) + def content(self, streamed=False, action=None, chunk_size=1024, **kwargs): + """Return the content of a snippet. + + Args: + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data + chunk_size (int): Size of each chunk + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the content could not be retrieved + + Returns: + str: The snippet content + """ + path = "/snippets/%s/raw" % self.get_id() + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) + return utils.response_content(result, streamed, action, chunk_size) + + +class SnippetManager(CRUDMixin, RESTManager): + _path = "/snippets" + _obj_cls = Snippet + _create_attrs = (("title", "file_name", "content"), ("lifetime", "visibility")) + _update_attrs = (tuple(), ("title", "file_name", "content", "visibility")) + + @cli.register_custom_action("SnippetManager") + def public(self, **kwargs): + """List all the public snippets. + + Args: + all (bool): If True the returned object will be a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: A generator for the snippets list + """ + return self.list(path="/snippets/public", **kwargs) diff --git a/gitlab/v4/objects/template.py b/gitlab/v4/objects/template.py new file mode 100644 index 000000000..f9c919141 --- /dev/null +++ b/gitlab/v4/objects/template.py @@ -0,0 +1,43 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Dockerfile(RESTObject): + _id_attr = "name" + + +class DockerfileManager(RetrieveMixin, RESTManager): + _path = "/templates/dockerfiles" + _obj_cls = Dockerfile + + +class Gitignore(RESTObject): + _id_attr = "name" + + +class GitignoreManager(RetrieveMixin, RESTManager): + _path = "/templates/gitignores" + _obj_cls = Gitignore + + +class Gitlabciyml(RESTObject): + _id_attr = "name" + + +class GitlabciymlManager(RetrieveMixin, RESTManager): + _path = "/templates/gitlab_ci_ymls" + _obj_cls = Gitlabciyml + + +class License(RESTObject): + _id_attr = "key" + + +class LicenseManager(RetrieveMixin, RESTManager): + _path = "/templates/licenses" + _obj_cls = License + _list_filters = ("popular",) + _optional_get_attrs = ("project", "fullname") diff --git a/gitlab/v4/objects/todo.py b/gitlab/v4/objects/todo.py new file mode 100644 index 000000000..eb8d0059b --- /dev/null +++ b/gitlab/v4/objects/todo.py @@ -0,0 +1,46 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class Todo(ObjectDeleteMixin, RESTObject): + @cli.register_custom_action("Todo") + @exc.on_http_error(exc.GitlabTodoError) + def mark_as_done(self, **kwargs): + """Mark the todo as done. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabTodoError: If the server failed to perform the request + """ + path = "%s/%s/mark_as_done" % (self.manager.path, self.id) + server_data = self.manager.gitlab.http_post(path, **kwargs) + self._update_attrs(server_data) + + +class TodoManager(ListMixin, DeleteMixin, RESTManager): + _path = "/todos" + _obj_cls = Todo + _list_filters = ("action", "author_id", "project_id", "state", "type") + + @cli.register_custom_action("TodoManager") + @exc.on_http_error(exc.GitlabTodoError) + def mark_all_as_done(self, **kwargs): + """Mark all the todos as done. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabTodoError: If the server failed to perform the request + + Returns: + int: The number of todos maked done + """ + result = self.gitlab.http_post("/todos/mark_as_done", **kwargs) diff --git a/gitlab/v4/objects/user.py b/gitlab/v4/objects/user.py new file mode 100644 index 000000000..e324cc170 --- /dev/null +++ b/gitlab/v4/objects/user.py @@ -0,0 +1,318 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + +class UserActivities(RESTObject): + _id_attr = "username" + + +class UserStatus(RESTObject): + _id_attr = None + _short_print_attr = "message" + + +class UserStatusManager(GetWithoutIdMixin, RESTManager): + _path = "/users/%(user_id)s/status" + _obj_cls = UserStatus + _from_parent_attrs = {"user_id": "id"} + + +class UserActivitiesManager(ListMixin, RESTManager): + _path = "/user/activities" + _obj_cls = UserActivities + + +class UserCustomAttribute(ObjectDeleteMixin, RESTObject): + _id_attr = "key" + + +class UserCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): + _path = "/users/%(user_id)s/custom_attributes" + _obj_cls = UserCustomAttribute + _from_parent_attrs = {"user_id": "id"} + + +class UserEmail(ObjectDeleteMixin, RESTObject): + _short_print_attr = "email" + + +class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/users/%(user_id)s/emails" + _obj_cls = UserEmail + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("email",), tuple()) + + +class UserEvent(Event): + pass + + +class UserEventManager(EventManager): + _path = "/users/%(user_id)s/events" + _obj_cls = UserEvent + _from_parent_attrs = {"user_id": "id"} + + +class UserGPGKey(ObjectDeleteMixin, RESTObject): + pass + + +class UserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/users/%(user_id)s/gpg_keys" + _obj_cls = UserGPGKey + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("key",), tuple()) + + +class UserKey(ObjectDeleteMixin, RESTObject): + pass + + +class UserKeyManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/users/%(user_id)s/keys" + _obj_cls = UserKey + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("title", "key"), tuple()) + + +class UserImpersonationToken(ObjectDeleteMixin, RESTObject): + pass + + +class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): + _path = "/users/%(user_id)s/impersonation_tokens" + _obj_cls = UserImpersonationToken + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("name", "scopes"), ("expires_at",)) + _list_filters = ("state",) + + +class UserProject(RESTObject): + pass + + +class UserProjectManager(ListMixin, CreateMixin, RESTManager): + _path = "/projects/user/%(user_id)s" + _obj_cls = UserProject + _from_parent_attrs = {"user_id": "id"} + _create_attrs = ( + ("name",), + ( + "default_branch", + "issues_enabled", + "wall_enabled", + "merge_requests_enabled", + "wiki_enabled", + "snippets_enabled", + "public", + "visibility", + "description", + "builds_enabled", + "public_builds", + "import_url", + "only_allow_merge_if_build_succeeds", + ), + ) + _list_filters = ( + "archived", + "visibility", + "order_by", + "sort", + "search", + "simple", + "owned", + "membership", + "starred", + "statistics", + "with_issues_enabled", + "with_merge_requests_enabled", + ) + + def list(self, **kwargs): + """Retrieve a list of objects. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Returns: + list: The list of objects, or a generator if `as_list` is False + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the server cannot perform the request + """ + if self._parent: + path = "/users/%s/projects" % self._parent.id + else: + path = "/users/%s/projects" % kwargs["user_id"] + return ListMixin.list(self, path=path, **kwargs) + + +class User(SaveMixin, ObjectDeleteMixin, RESTObject): + _short_print_attr = "username" + _managers = ( + ("customattributes", "UserCustomAttributeManager"), + ("emails", "UserEmailManager"), + ("events", "UserEventManager"), + ("gpgkeys", "UserGPGKeyManager"), + ("impersonationtokens", "UserImpersonationTokenManager"), + ("keys", "UserKeyManager"), + ("projects", "UserProjectManager"), + ("status", "UserStatusManager"), + ) + + @cli.register_custom_action("User") + @exc.on_http_error(exc.GitlabBlockError) + def block(self, **kwargs): + """Block the user. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabBlockError: If the user could not be blocked + + Returns: + bool: Whether the user status has been changed + """ + path = "/users/%s/block" % self.id + server_data = self.manager.gitlab.http_post(path, **kwargs) + if server_data is True: + self._attrs["state"] = "blocked" + return server_data + + @cli.register_custom_action("User") + @exc.on_http_error(exc.GitlabUnblockError) + def unblock(self, **kwargs): + """Unblock the user. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabUnblockError: If the user could not be unblocked + + Returns: + bool: Whether the user status has been changed + """ + path = "/users/%s/unblock" % self.id + server_data = self.manager.gitlab.http_post(path, **kwargs) + if server_data is True: + self._attrs["state"] = "active" + return server_data + + @cli.register_custom_action("User") + @exc.on_http_error(exc.GitlabDeactivateError) + def deactivate(self, **kwargs): + """Deactivate the user. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabDeactivateError: If the user could not be deactivated + + Returns: + bool: Whether the user status has been changed + """ + path = "/users/%s/deactivate" % self.id + server_data = self.manager.gitlab.http_post(path, **kwargs) + if server_data: + self._attrs["state"] = "deactivated" + return server_data + + @cli.register_custom_action("User") + @exc.on_http_error(exc.GitlabActivateError) + def activate(self, **kwargs): + """Activate the user. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabActivateError: If the user could not be activated + + Returns: + bool: Whether the user status has been changed + """ + path = "/users/%s/activate" % self.id + server_data = self.manager.gitlab.http_post(path, **kwargs) + if server_data: + self._attrs["state"] = "active" + return server_data + + +class UserManager(CRUDMixin, RESTManager): + _path = "/users" + _obj_cls = User + + _list_filters = ( + "active", + "blocked", + "username", + "extern_uid", + "provider", + "external", + "search", + "custom_attributes", + "status", + ) + _create_attrs = ( + tuple(), + ( + "email", + "username", + "name", + "password", + "reset_password", + "skype", + "linkedin", + "twitter", + "projects_limit", + "extern_uid", + "provider", + "bio", + "admin", + "can_create_group", + "website_url", + "skip_confirmation", + "external", + "organization", + "location", + "avatar", + ), + ) + _update_attrs = ( + ("email", "username", "name"), + ( + "password", + "skype", + "linkedin", + "twitter", + "projects_limit", + "extern_uid", + "provider", + "bio", + "admin", + "can_create_group", + "website_url", + "skip_confirmation", + "external", + "organization", + "location", + "avatar", + ), + ) + _types = {"confirm": types.LowercaseStringAttribute, "avatar": types.ImageAttribute} From 31662c8557e156f998b4fa81f8b9b6d6b0d0d55a Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Wed, 22 Jan 2020 21:59:01 +0100 Subject: [PATCH 2/2] chore: another try --- gitlab/__init__.py | 76 +++++++++++++++++++++------------- gitlab/v4/cli.py | 1 - gitlab/v4/objects/__init__.py | 20 --------- gitlab/v4/objects/group.py | 6 +-- gitlab/v4/objects/hook.py | 7 ++++ gitlab/v4/objects/namespace.py | 3 -- gitlab/v4/objects/project.py | 2 - gitlab/v4/objects/user.py | 1 + 8 files changed, 58 insertions(+), 58 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 0fdfc7186..6ee04ad92 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -18,7 +18,27 @@ from __future__ import print_function from __future__ import absolute_import -from gitlab.v4 import objects +from gitlab.exceptions import * # noqa +from gitlab.v4.objects.application import * # noqa +from gitlab.v4.objects.broadcast_message import * # noqa +from gitlab.v4.objects.current_user import * # noqa +from gitlab.v4.objects.deploy_key import * # noqa +from gitlab.v4.objects.ee import * # noqa +from gitlab.v4.objects.event import * # noqa +from gitlab.v4.objects.feature import * # noqa +from gitlab.v4.objects.group import * # noqa +from gitlab.v4.objects.hook import * # noqa +from gitlab.v4.objects.issue import * # noqa +from gitlab.v4.objects.mergerequest import * # noqa +from gitlab.v4.objects.namespace import * # noqa +from gitlab.v4.objects.notification_settings import * # noqa +from gitlab.v4.objects.project import * # noqa +from gitlab.v4.objects.runner import * # noqa +from gitlab.v4.objects.sidekiq import * # noqa +from gitlab.v4.objects.snippet import * # noqa +from gitlab.v4.objects.template import * # noqa +from gitlab.v4.objects.todo import * # noqa +from gitlab.v4.objects.user import * # noqa import importlib import time import warnings @@ -106,34 +126,32 @@ def __init__( self.per_page = per_page - self._objects = objects - - self.broadcastmessages = objects.BroadcastMessageManager(self) - self.deploykeys = objects.DeployKeyManager(self) - self.geonodes = objects.GeoNodeManager(self) - self.gitlabciymls = objects.GitlabciymlManager(self) - self.gitignores = objects.GitignoreManager(self) - self.groups = objects.GroupManager(self) - self.hooks = objects.HookManager(self) - self.issues = objects.IssueManager(self) - self.ldapgroups = objects.LDAPGroupManager(self) - self.licenses = objects.LicenseManager(self) - self.namespaces = objects.NamespaceManager(self) - self.mergerequests = objects.MergeRequestManager(self) - self.notificationsettings = objects.NotificationSettingsManager(self) - self.projects = objects.ProjectManager(self) - self.runners = objects.RunnerManager(self) - self.settings = objects.ApplicationSettingsManager(self) - self.sidekiq = objects.SidekiqManager(self) - self.snippets = objects.SnippetManager(self) - self.users = objects.UserManager(self) - self.todos = objects.TodoManager(self) - self.dockerfiles = objects.DockerfileManager(self) - self.events = objects.EventManager(self) - self.audit_events = objects.AuditEventManager(self) - self.features = objects.FeatureManager(self) - self.pagesdomains = objects.PagesDomainManager(self) - self.user_activities = objects.UserActivitiesManager(self) + self.broadcastmessages = BroadcastMessageManager(self) + self.deploykeys = DeployKeyManager(self) + self.geonodes = GeoNodeManager(self) + self.gitlabciymls = GitlabciymlManager(self) + self.gitignores = GitignoreManager(self) + self.groups = GroupManager(self) + self.hooks = HookManager(self) + self.issues = IssueManager(self) + self.ldapgroups = LDAPGroupManager(self) + self.licenses = LicenseManager(self) + self.namespaces = NamespaceManager(self) + self.mergerequests = MergeRequestManager(self) + self.notificationsettings = NotificationSettingsManager(self) + self.projects = ProjectManager(self) + self.runners = RunnerManager(self) + self.settings = ApplicationSettingsManager(self) + self.sidekiq = SidekiqManager(self) + self.snippets = SnippetManager(self) + self.users = UserManager(self) + self.todos = TodoManager(self) + self.dockerfiles = DockerfileManager(self) + self.events = EventManager(self) + self.audit_events = AuditEventManager(self) + self.features = FeatureManager(self) + self.pagesdomains = PagesDomainManager(self) + self.user_activities = UserActivitiesManager(self) def __enter__(self): return self diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index a8752612e..333546b39 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -24,7 +24,6 @@ import gitlab import gitlab.base from gitlab import cli -import gitlab.v4.objects class GitlabCLI(object): diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py index 9595c928e..e69de29bb 100644 --- a/gitlab/v4/objects/__init__.py +++ b/gitlab/v4/objects/__init__.py @@ -1,20 +0,0 @@ -from gitlab.v4.objects.application import * # noqa -from gitlab.v4.objects.broadcast_message import * # noqa -from gitlab.v4.objects.current_user import * # noqa -from gitlab.v4.objects.deploy_key import * # noqa -from gitlab.v4.objects.ee import * # noqa -from gitlab.v4.objects.event import * # noqa -from gitlab.v4.objects.feature import * # noqa -from gitlab.v4.objects.group import * # noqa -from gitlab.v4.objects.hook import * # noqa -from gitlab.v4.objects.issue import * # noqa -from gitlab.v4.objects.mergerequest import * # noqa -from gitlab.v4.objects.namespace import * # noqa -from gitlab.v4.objects.notification_settings import * # noqa -from gitlab.v4.objects.project import * # noqa -from gitlab.v4.objects.runner import * # noqa -from gitlab.v4.objects.sidekiq import * # noqa -from gitlab.v4.objects.snippet import * # noqa -from gitlab.v4.objects.template import * # noqa -from gitlab.v4.objects.todo import * # noqa -from gitlab.v4.objects.user import * # noqa diff --git a/gitlab/v4/objects/group.py b/gitlab/v4/objects/group.py index 9be2b3f1a..74cb73ad4 100644 --- a/gitlab/v4/objects/group.py +++ b/gitlab/v4/objects/group.py @@ -2,7 +2,7 @@ from gitlab.exceptions import * # noqa from gitlab.mixins import * # noqa from gitlab import types -from gitlab.v4 import objects +from gitlab.v4.objects.notification_settings import NotificationSettings, NotificationSettingsManager from gitlab import utils @@ -421,11 +421,11 @@ class GroupMilestoneManager(CRUDMixin, RESTManager): _list_filters = ("iids", "state", "search") -class GroupNotificationSettings(objects.NotificationSettings): +class GroupNotificationSettings(NotificationSettings): pass -class GroupNotificationSettingsManager(objects.NotificationSettingsManager): +class GroupNotificationSettingsManager(NotificationSettingsManager): _path = "/groups/%(group_id)s/notification_settings" _obj_cls = GroupNotificationSettings _from_parent_attrs = {"group_id": "id"} diff --git a/gitlab/v4/objects/hook.py b/gitlab/v4/objects/hook.py index bcf5be53a..94d35761b 100644 --- a/gitlab/v4/objects/hook.py +++ b/gitlab/v4/objects/hook.py @@ -1,3 +1,10 @@ +from gitlab.base import * # noqa +from gitlab.exceptions import * # noqa +from gitlab.mixins import * # noqa +from gitlab import types +from gitlab import utils + + class Hook(ObjectDeleteMixin, RESTObject): _url = "/hooks" _short_print_attr = "url" diff --git a/gitlab/v4/objects/namespace.py b/gitlab/v4/objects/namespace.py index 4f2dec72e..7e66a3928 100644 --- a/gitlab/v4/objects/namespace.py +++ b/gitlab/v4/objects/namespace.py @@ -1,8 +1,5 @@ from gitlab.base import * # noqa -from gitlab.exceptions import * # noqa from gitlab.mixins import * # noqa -from gitlab import types -from gitlab import utils class Namespace(RESTObject): diff --git a/gitlab/v4/objects/project.py b/gitlab/v4/objects/project.py index 0ec5f7e1c..ad22dbbfc 100644 --- a/gitlab/v4/objects/project.py +++ b/gitlab/v4/objects/project.py @@ -1512,8 +1512,6 @@ class PagesDomain(RESTObject): class PagesDomainManager(ListMixin, RESTManager): _path = "/pages/domains" _obj_cls = PagesDomain -======= ->>>>>>> 509394d... refactor: structure python objects in a reasonable way:gitlab/v4/objects/project.py class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): diff --git a/gitlab/v4/objects/user.py b/gitlab/v4/objects/user.py index e324cc170..1da1e94a4 100644 --- a/gitlab/v4/objects/user.py +++ b/gitlab/v4/objects/user.py @@ -2,6 +2,7 @@ from gitlab.exceptions import * # noqa from gitlab.mixins import * # noqa from gitlab import types +from gitlab.v4.objects.event import Event, EventManager from gitlab import utils