diff --git a/gitlab/__init__.py b/gitlab/__init__.py
index b37702319..6ee04ad92 100644
--- a/gitlab/__init__.py
+++ b/gitlab/__init__.py
@@ -18,16 +18,32 @@
from __future__ import print_function
from __future__ import absolute_import
+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
-
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,35 +126,32 @@ 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)
- 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/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/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
new file mode 100644
index 000000000..e69de29bb
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..74cb73ad4
--- /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.objects.notification_settings import NotificationSettings, NotificationSettingsManager
+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(NotificationSettings):
+ pass
+
+
+class GroupNotificationSettingsManager(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..94d35761b
--- /dev/null
+++ b/gitlab/v4/objects/hook.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 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..7e66a3928
--- /dev/null
+++ b/gitlab/v4/objects/namespace.py
@@ -0,0 +1,12 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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..ad22dbbfc 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"
@@ -4500,136 +4480,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 +4661,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..1da1e94a4
--- /dev/null
+++ b/gitlab/v4/objects/user.py
@@ -0,0 +1,319 @@
+from gitlab.base import * # noqa
+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
+
+
+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}