summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gitlab/v4/objects/__init__.py5907
-rw-r--r--gitlab/v4/objects/access_requests.py22
-rw-r--r--gitlab/v4/objects/appearance.py48
-rw-r--r--gitlab/v4/objects/applications.py13
-rw-r--r--gitlab/v4/objects/award_emojis.py88
-rw-r--r--gitlab/v4/objects/badges.py26
-rw-r--r--gitlab/v4/objects/boards.py48
-rw-r--r--gitlab/v4/objects/branches.py80
-rw-r--r--gitlab/v4/objects/broadcast_messages.py14
-rw-r--r--gitlab/v4/objects/clusters.py93
-rw-r--r--gitlab/v4/objects/commits.py189
-rw-r--r--gitlab/v4/objects/container_registry.py47
-rw-r--r--gitlab/v4/objects/custom_attributes.py32
-rw-r--r--gitlab/v4/objects/deploy_keys.py41
-rw-r--r--gitlab/v4/objects/deploy_tokens.py51
-rw-r--r--gitlab/v4/objects/deployments.py14
-rw-r--r--gitlab/v4/objects/discussions.py55
-rw-r--r--gitlab/v4/objects/environments.py31
-rw-r--r--gitlab/v4/objects/epics.py87
-rw-r--r--gitlab/v4/objects/events.py98
-rw-r--r--gitlab/v4/objects/export_import.py43
-rw-r--r--gitlab/v4/objects/features.py53
-rw-r--r--gitlab/v4/objects/files.py216
-rw-r--r--gitlab/v4/objects/geo_nodes.py83
-rw-r--r--gitlab/v4/objects/groups.py286
-rw-r--r--gitlab/v4/objects/hooks.py55
-rw-r--r--gitlab/v4/objects/issues.py229
-rw-r--r--gitlab/v4/objects/jobs.py184
-rw-r--r--gitlab/v4/objects/labels.py125
-rw-r--r--gitlab/v4/objects/ldap.py46
-rw-r--r--gitlab/v4/objects/members.py78
-rw-r--r--gitlab/v4/objects/merge_request_approvals.py179
-rw-r--r--gitlab/v4/objects/merge_requests.py375
-rw-r--r--gitlab/v4/objects/milestones.py154
-rw-r--r--gitlab/v4/objects/namespaces.py12
-rw-r--r--gitlab/v4/objects/notes.py140
-rw-r--r--gitlab/v4/objects/notification_settings.py49
-rw-r--r--gitlab/v4/objects/packages.py35
-rw-r--r--gitlab/v4/objects/pages.py23
-rw-r--r--gitlab/v4/objects/pipelines.py174
-rw-r--r--gitlab/v4/objects/projects.py1120
-rw-r--r--gitlab/v4/objects/push_rules.py40
-rw-r--r--gitlab/v4/objects/runners.py118
-rw-r--r--gitlab/v4/objects/services.py291
-rw-r--r--gitlab/v4/objects/settings.py89
-rw-r--r--gitlab/v4/objects/sidekiq.py80
-rw-r--r--gitlab/v4/objects/snippets.py110
-rw-r--r--gitlab/v4/objects/statistics.py22
-rw-r--r--gitlab/v4/objects/tags.py74
-rw-r--r--gitlab/v4/objects/templates.py40
-rw-r--r--gitlab/v4/objects/todos.py45
-rw-r--r--gitlab/v4/objects/triggers.py30
-rw-r--r--gitlab/v4/objects/users.py419
-rw-r--r--gitlab/v4/objects/wikis.py16
54 files changed, 6167 insertions, 5850 deletions
diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py
index a98481e..4708012 100644
--- a/gitlab/v4/objects/__init__.py
+++ b/gitlab/v4/objects/__init__.py
@@ -15,17 +15,63 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-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
-from gitlab.v4.objects.variables import *
-
-
+from .access_requests import *
+from .appearance import *
+from .applications import *
+from .award_emojis import *
+from .badges import *
+from .boards import *
+from .branches import *
+from .broadcast_messages import *
+from .clusters import *
+from .commits import *
+from .container_registry import *
+from .custom_attributes import *
+from .deploy_keys import *
+from .deployments import *
+from .deploy_tokens import *
+from .discussions import *
+from .environments import *
+from .epics import *
+from .events import *
+from .export_import import *
+from .features import *
+from .files import *
+from .geo_nodes import *
+from .groups import *
+from .hooks import *
+from .issues import *
+from .jobs import *
+from .labels import *
+from .ldap import *
+from .members import *
+from .merge_request_approvals import *
+from .merge_requests import *
+from .milestones import *
+from .namespaces import *
+from .notes import *
+from .notification_settings import *
+from .packages import *
+from .pages import *
+from .pipelines import *
+from .projects import *
+from .push_rules import *
+from .runners import *
+from .services import *
+from .settings import *
+from .sidekiq import *
+from .snippets import *
+from .statistics import *
+from .tags import *
+from .templates import *
+from .todos import *
+from .triggers import *
+from .users import *
+from .variables import *
+from .wikis import *
+
+
+# TODO: deprecate these in favor of gitlab.const.*
VISIBILITY_PRIVATE = "private"
VISIBILITY_INTERNAL = "internal"
VISIBILITY_PUBLIC = "public"
@@ -35,5842 +81,3 @@ ACCESS_REPORTER = 20
ACCESS_DEVELOPER = 30
ACCESS_MASTER = 40
ACCESS_OWNER = 50
-
-
-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)
-
-
-class Event(RESTObject):
- _id_attr = None
- _short_print_attr = "target_title"
-
-
-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 EventManager(ListMixin, RESTManager):
- _path = "/events"
- _obj_cls = Event
- _list_filters = ("action", "target_type", "before", "after", "sort")
-
-
-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 UserStatus(RESTObject):
- pass
-
-
-class UserStatusManager(GetWithoutIdMixin, RESTManager):
- _path = "/users/%(user_id)s/status"
- _obj_cls = UserStatus
- _from_parent_attrs = {"user_id": "id"}
-
-
-class UserIdentityProviderManager(DeleteMixin, RESTManager):
- """Manager for user identities.
-
- This manager does not actually manage objects but enables
- functionality for deletion of user identities by provider.
- """
-
- _path = "/users/%(user_id)s/identities"
- _from_parent_attrs = {"user_id": "id"}
-
-
-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 UserMembership(RESTObject):
- _id_attr = "source_id"
-
-
-class UserMembershipManager(RetrieveMixin, RESTManager):
- _path = "/users/%(user_id)s/memberships"
- _obj_cls = UserMembership
- _from_parent_attrs = {"user_id": "id"}
- _list_filters = ("type",)
-
-
-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",
- "with_custom_attributes",
- "with_programming_language",
- "wiki_checksum_failed",
- "repository_checksum_failed",
- "min_access_level",
- "id_after",
- "id_before",
- )
-
- 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"),
- ("identityproviders", "UserIdentityProviderManager"),
- ("impersonationtokens", "UserImpersonationTokenManager"),
- ("keys", "UserKeyManager"),
- ("memberships", "UserMembershipManager"),
- ("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",
- "two_factor",
- )
- _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",
- "public_email",
- "private_profile",
- "color_scheme_id",
- "theme_id",
- ),
- )
- _update_attrs = (
- ("email", "username", "name"),
- (
- "password",
- "skype",
- "linkedin",
- "twitter",
- "projects_limit",
- "extern_uid",
- "provider",
- "bio",
- "admin",
- "can_create_group",
- "website_url",
- "skip_reconfirmation",
- "external",
- "organization",
- "location",
- "avatar",
- "public_email",
- "private_profile",
- "color_scheme_id",
- "theme_id",
- ),
- )
- _types = {"confirm": types.LowercaseStringAttribute, "avatar": types.ImageAttribute}
-
-
-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
-
-
-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)
-
-
-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"))
-
-
-class DeployKey(RESTObject):
- pass
-
-
-class DeployKeyManager(ListMixin, RESTManager):
- _path = "/deploy_keys"
- _obj_cls = DeployKey
-
-
-class DeployToken(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class DeployTokenManager(ListMixin, RESTManager):
- _path = "/deploy_tokens"
- _obj_cls = DeployToken
-
-
-class ProjectDeployToken(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/deploy_tokens"
- _from_parent_attrs = {"project_id": "id"}
- _obj_cls = ProjectDeployToken
- _create_attrs = (
- (
- "name",
- "scopes",
- ),
- (
- "expires_at",
- "username",
- ),
- )
-
-
-class GroupDeployToken(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class GroupDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
- _path = "/groups/%(group_id)s/deploy_tokens"
- _from_parent_attrs = {"group_id": "id"}
- _obj_cls = GroupDeployToken
- _create_attrs = (
- (
- "name",
- "scopes",
- ),
- (
- "expires_at",
- "username",
- ),
- )
-
-
-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",
- ),
- )
-
-
-class Dockerfile(RESTObject):
- _id_attr = "name"
-
-
-class DockerfileManager(RetrieveMixin, RESTManager):
- _path = "/templates/dockerfiles"
- _obj_cls = Dockerfile
-
-
-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,
- group=None,
- project=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
- group (str): A GitLab group
- project (str): A GitLab project in form group/project
- **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,
- "group": group,
- "project": project,
- }
- data = utils.remove_none_from_dict(data)
- server_data = self.gitlab.http_post(path, post_data=data, **kwargs)
- return self._obj_cls(self, server_data)
-
-
-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 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 GroupExport(DownloadMixin, RESTObject):
- _id_attr = None
-
-
-class GroupExportManager(GetWithoutIdMixin, CreateMixin, RESTManager):
- _path = "/groups/%(group_id)s/export"
- _obj_cls = GroupExport
- _from_parent_attrs = {"group_id": "id"}
-
-
-class GroupImport(RESTObject):
- _id_attr = None
-
-
-class GroupImportManager(GetWithoutIdMixin, RESTManager):
- _path = "/groups/%(group_id)s/import"
- _obj_cls = GroupImport
- _from_parent_attrs = {"group_id": "id"}
-
-
-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",
- "wip",
- )
- _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 GroupPackage(RESTObject):
- pass
-
-
-class GroupPackageManager(ListMixin, RESTManager):
- _path = "/groups/%(group_id)s/packages"
- _obj_cls = GroupPackage
- _from_parent_attrs = {"group_id": "id"}
- _list_filters = (
- "exclude_subgroups",
- "order_by",
- "sort",
- "package_type",
- "package_name",
- )
-
-
-class GroupProject(RESTObject):
- pass
-
-
-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",
- "simple",
- "owned",
- "starred",
- "with_custom_attributes",
- "include_subgroups",
- "with_issues_enabled",
- "with_merge_requests_enabled",
- "with_shared",
- "min_access_level",
- "with_security_reports",
- )
-
-
-class GroupRunner(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class GroupRunnerManager(NoUpdateMixin, RESTManager):
- _path = "/groups/%(group_id)s/runners"
- _obj_cls = GroupRunner
- _from_parent_attrs = {"group_id": "id"}
- _create_attrs = (("runner_id",), tuple())
-
-
-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 Group(SaveMixin, ObjectDeleteMixin, RESTObject):
- _short_print_attr = "name"
- _managers = (
- ("accessrequests", "GroupAccessRequestManager"),
- ("badges", "GroupBadgeManager"),
- ("boards", "GroupBoardManager"),
- ("customattributes", "GroupCustomAttributeManager"),
- ("exports", "GroupExportManager"),
- ("epics", "GroupEpicManager"),
- ("imports", "GroupImportManager"),
- ("issues", "GroupIssueManager"),
- ("labels", "GroupLabelManager"),
- ("members", "GroupMemberManager"),
- ("mergerequests", "GroupMergeRequestManager"),
- ("milestones", "GroupMilestoneManager"),
- ("notificationsettings", "GroupNotificationSettingsManager"),
- ("packages", "GroupPackageManager"),
- ("projects", "GroupProjectManager"),
- ("runners", "GroupRunnerManager"),
- ("subgroups", "GroupSubgroupManager"),
- ("variables", "GroupVariableManager"),
- ("clusters", "GroupClusterManager"),
- ("deploytokens", "GroupDeployTokenManager"),
- )
-
- @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)
-
- @cli.register_custom_action("Group", ("group_id", "group_access"), ("expires_at",))
- @exc.on_http_error(exc.GitlabCreateError)
- def share(self, group_id, group_access, expires_at=None, **kwargs):
- """Share the group with a group.
-
- Args:
- group_id (int): ID of the group.
- group_access (int): Access level for the group.
- **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 = "/groups/%s/share" % self.get_id()
- data = {
- "group_id": group_id,
- "group_access": group_access,
- "expires_at": expires_at,
- }
- self.manager.gitlab.http_post(path, post_data=data, **kwargs)
-
- @cli.register_custom_action("Group", ("group_id",))
- @exc.on_http_error(exc.GitlabDeleteError)
- def unshare(self, group_id, **kwargs):
- """Delete a shared group link within a group.
-
- Args:
- group_id (int): ID of the group.
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the server failed to perform the request
- """
- path = "/groups/%s/share/%s" % (self.get_id(), group_id)
- self.manager.gitlab.http_delete(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",
- "min_access_level",
- )
- _create_attrs = (
- ("name", "path"),
- (
- "description",
- "membership_lock",
- "visibility",
- "share_with_group_lock",
- "require_two_factor_authentication",
- "two_factor_grace_period",
- "project_creation_level",
- "auto_devops_enabled",
- "subgroup_creation_level",
- "emails_disabled",
- "avatar",
- "mentions_disabled",
- "lfs_enabled",
- "request_access_enabled",
- "parent_id",
- "default_branch_protection",
- ),
- )
- _update_attrs = (
- tuple(),
- (
- "name",
- "path",
- "description",
- "membership_lock",
- "share_with_group_lock",
- "visibility",
- "require_two_factor_authentication",
- "two_factor_grace_period",
- "project_creation_level",
- "auto_devops_enabled",
- "subgroup_creation_level",
- "emails_disabled",
- "avatar",
- "mentions_disabled",
- "lfs_enabled",
- "request_access_enabled",
- "default_branch_protection",
- ),
- )
- _types = {"avatar": types.ImageAttribute}
-
- @exc.on_http_error(exc.GitlabImportError)
- def import_group(self, file, path, name, parent_id=None, **kwargs):
- """Import a group from an archive file.
-
- Args:
- file: Data or file object containing the group
- path (str): The path for the new group to be imported.
- name (str): The name for the new group.
- parent_id (str): ID of a parent group that the group will
- be imported into.
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabImportError: If the server failed to perform the request
-
- Returns:
- dict: A representation of the import status.
- """
- files = {"file": ("file.tar.gz", file, "application/octet-stream")}
- data = {"path": path, "name": name}
- if parent_id is not None:
- data["parent_id"] = parent_id
-
- return self.gitlab.http_post(
- "/groups/import", post_data=data, files=files, **kwargs
- )
-
-
-class Hook(ObjectDeleteMixin, RESTObject):
- _url = "/hooks"
- _short_print_attr = "url"
-
-
-class HookManager(NoUpdateMixin, RESTManager):
- _path = "/hooks"
- _obj_cls = Hook
- _create_attrs = (("url",), tuple())
-
-
-class Issue(RESTObject):
- _url = "/issues"
- _short_print_attr = "title"
-
-
-class IssueManager(RetrieveMixin, 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}
-
-
-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)
-
-
-class License(RESTObject):
- _id_attr = "key"
-
-
-class LicenseManager(RetrieveMixin, RESTManager):
- _path = "/templates/licenses"
- _obj_cls = License
- _list_filters = ("popular",)
- _optional_get_attrs = ("project", "fullname")
-
-
-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",
- "wip",
- )
- _types = {"labels": types.ListAttribute}
-
-
-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)
-
-
-class Namespace(RESTObject):
- pass
-
-
-class NamespaceManager(RetrieveMixin, RESTManager):
- _path = "/namespaces"
- _obj_cls = Namespace
- _list_filters = ("search",)
-
-
-class PagesDomain(RESTObject):
- _id_attr = "domain"
-
-
-class PagesDomainManager(ListMixin, RESTManager):
- _path = "/pages/domains"
- _obj_cls = PagesDomain
-
-
-class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject):
- _managers = (("tags", "ProjectRegistryTagManager"),)
-
-
-class ProjectRegistryRepositoryManager(DeleteMixin, ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/registry/repositories"
- _obj_cls = ProjectRegistryRepository
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectRegistryTag(ObjectDeleteMixin, RESTObject):
- _id_attr = "name"
-
-
-class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager):
- _obj_cls = ProjectRegistryTag
- _from_parent_attrs = {"project_id": "project_id", "repository_id": "id"}
- _path = "/projects/%(project_id)s/registry/repositories/%(repository_id)s/tags"
-
- @cli.register_custom_action(
- "ProjectRegistryTagManager", optional=("name_regex", "keep_n", "older_than")
- )
- @exc.on_http_error(exc.GitlabDeleteError)
- def delete_in_bulk(self, name_regex=".*", **kwargs):
- """Delete Tag in bulk
-
- Args:
- name_regex (string): The regex of the name to delete. To delete all
- tags specify .*.
- keep_n (integer): The amount of latest tags of given name to keep.
- older_than (string): Tags to delete that are older than the given time,
- written in human readable form 1h, 1d, 1month.
- **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
- """
- valid_attrs = ["keep_n", "older_than"]
- data = {"name_regex": name_regex}
- data.update({k: v for k, v in kwargs.items() if k in valid_attrs})
- self.gitlab.http_delete(self.path, query_data=data, **kwargs)
-
-
-class ProjectRemoteMirror(SaveMixin, RESTObject):
- pass
-
-
-class ProjectRemoteMirrorManager(ListMixin, CreateMixin, UpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/remote_mirrors"
- _obj_cls = ProjectRemoteMirror
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("url",), ("enabled", "only_protected_branches"))
- _update_attrs = (tuple(), ("enabled", "only_protected_branches"))
-
-
-class ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectBoardListManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/boards/%(board_id)s/lists"
- _obj_cls = ProjectBoardList
- _from_parent_attrs = {"project_id": "project_id", "board_id": "id"}
- _create_attrs = (("label_id",), tuple())
- _update_attrs = (("position",), tuple())
-
-
-class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject):
- _managers = (("lists", "ProjectBoardListManager"),)
-
-
-class ProjectBoardManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/boards"
- _obj_cls = ProjectBoard
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("name",), tuple())
-
-
-class ProjectBranch(ObjectDeleteMixin, RESTObject):
- _id_attr = "name"
-
- @cli.register_custom_action(
- "ProjectBranch", tuple(), ("developers_can_push", "developers_can_merge")
- )
- @exc.on_http_error(exc.GitlabProtectError)
- def protect(self, developers_can_push=False, developers_can_merge=False, **kwargs):
- """Protect the branch.
-
- Args:
- developers_can_push (bool): Set to True if developers are allowed
- to push to the branch
- developers_can_merge (bool): Set to True if developers are allowed
- to merge to the branch
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabProtectError: If the branch could not be protected
- """
- id = self.get_id().replace("/", "%2F")
- path = "%s/%s/protect" % (self.manager.path, id)
- post_data = {
- "developers_can_push": developers_can_push,
- "developers_can_merge": developers_can_merge,
- }
- self.manager.gitlab.http_put(path, post_data=post_data, **kwargs)
- self._attrs["protected"] = True
-
- @cli.register_custom_action("ProjectBranch")
- @exc.on_http_error(exc.GitlabProtectError)
- def unprotect(self, **kwargs):
- """Unprotect the branch.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabProtectError: If the branch could not be unprotected
- """
- id = self.get_id().replace("/", "%2F")
- path = "%s/%s/unprotect" % (self.manager.path, id)
- self.manager.gitlab.http_put(path, **kwargs)
- self._attrs["protected"] = False
-
-
-class ProjectBranchManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/branches"
- _obj_cls = ProjectBranch
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("branch", "ref"), tuple())
-
-
-class ProjectCluster(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectClusterManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/clusters"
- _obj_cls = ProjectCluster
- _from_parent_attrs = {"project_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 ProjectCustomAttribute(ObjectDeleteMixin, RESTObject):
- _id_attr = "key"
-
-
-class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/custom_attributes"
- _obj_cls = ProjectCustomAttribute
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectJob(RESTObject, RefreshMixin):
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabJobCancelError)
- def cancel(self, **kwargs):
- """Cancel the job.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabJobCancelError: If the job could not be canceled
- """
- path = "%s/%s/cancel" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabJobRetryError)
- def retry(self, **kwargs):
- """Retry the job.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabJobRetryError: If the job could not be retried
- """
- path = "%s/%s/retry" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabJobPlayError)
- def play(self, **kwargs):
- """Trigger a job explicitly.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabJobPlayError: If the job could not be triggered
- """
- path = "%s/%s/play" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabJobEraseError)
- def erase(self, **kwargs):
- """Erase the job (remove job artifacts and trace).
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabJobEraseError: If the job could not be erased
- """
- path = "%s/%s/erase" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabCreateError)
- def keep_artifacts(self, **kwargs):
- """Prevent artifacts from being deleted when expiration is set.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabCreateError: If the request could not be performed
- """
- path = "%s/%s/artifacts/keep" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabCreateError)
- def delete_artifacts(self, **kwargs):
- """Delete artifacts of a job.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the request could not be performed
- """
- path = "%s/%s/artifacts" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_delete(path)
-
- @cli.register_custom_action("ProjectJob")
- @exc.on_http_error(exc.GitlabGetError)
- def artifacts(self, streamed=False, action=None, chunk_size=1024, **kwargs):
- """Get the job artifacts.
-
- 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 artifacts could not be retrieved
-
- Returns:
- str: The artifacts if `streamed` is False, None otherwise.
- """
- path = "%s/%s/artifacts" % (self.manager.path, 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("ProjectJob")
- @exc.on_http_error(exc.GitlabGetError)
- def artifact(self, path, streamed=False, action=None, chunk_size=1024, **kwargs):
- """Get a single artifact file from within the job's artifacts archive.
-
- Args:
- path (str): Path of the artifact
- 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 = "%s/%s/artifacts/%s" % (self.manager.path, self.get_id(), path)
- 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("ProjectJob")
- @exc.on_http_error(exc.GitlabGetError)
- def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs):
- """Get the job trace.
-
- 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 artifacts could not be retrieved
-
- Returns:
- str: The trace
- """
- path = "%s/%s/trace" % (self.manager.path, 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 ProjectJobManager(RetrieveMixin, RESTManager):
- _path = "/projects/%(project_id)s/jobs"
- _obj_cls = ProjectJob
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectCommitStatus(RESTObject, RefreshMixin):
- pass
-
-
-class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/statuses"
- _obj_cls = ProjectCommitStatus
- _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"}
- _create_attrs = (
- ("state",),
- ("description", "name", "context", "ref", "target_url", "coverage"),
- )
-
- @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 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
- """
- # project_id and commit_id are in the data dict when using the CLI, but
- # they are missing when using only the API
- # See #511
- base_path = "/projects/%(project_id)s/statuses/%(commit_id)s"
- if "project_id" in data and "commit_id" in data:
- path = base_path % data
- else:
- path = self._compute_path(base_path)
- return CreateMixin.create(self, data, path=path, **kwargs)
-
-
-class ProjectCommitComment(RESTObject):
- _id_attr = None
- _short_print_attr = "note"
-
-
-class ProjectCommitCommentManager(ListMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/comments"
- _obj_cls = ProjectCommitComment
- _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"}
- _create_attrs = (("note",), ("path", "line", "line_type"))
-
-
-class ProjectCommitDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectCommitDiscussionNoteManager(
- GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = (
- "/projects/%(project_id)s/repository/commits/%(commit_id)s/"
- "discussions/%(discussion_id)s/notes"
- )
- _obj_cls = ProjectCommitDiscussionNote
- _from_parent_attrs = {
- "project_id": "project_id",
- "commit_id": "commit_id",
- "discussion_id": "id",
- }
- _create_attrs = (("body",), ("created_at", "position"))
- _update_attrs = (("body",), tuple())
-
-
-class ProjectCommitDiscussion(RESTObject):
- _managers = (("notes", "ProjectCommitDiscussionNoteManager"),)
-
-
-class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s/" "discussions"
- _obj_cls = ProjectCommitDiscussion
- _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"}
- _create_attrs = (("body",), ("created_at",))
-
-
-class ProjectCommit(RESTObject):
- _short_print_attr = "title"
- _managers = (
- ("comments", "ProjectCommitCommentManager"),
- ("discussions", "ProjectCommitDiscussionManager"),
- ("statuses", "ProjectCommitStatusManager"),
- )
-
- @cli.register_custom_action("ProjectCommit")
- @exc.on_http_error(exc.GitlabGetError)
- def diff(self, **kwargs):
- """Generate the commit diff.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetError: If the diff could not be retrieved
-
- Returns:
- list: The changes done in this commit
- """
- path = "%s/%s/diff" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("ProjectCommit", ("branch",))
- @exc.on_http_error(exc.GitlabCherryPickError)
- def cherry_pick(self, branch, **kwargs):
- """Cherry-pick a commit into a branch.
-
- Args:
- branch (str): Name of target branch
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabCherryPickError: If the cherry-pick could not be performed
- """
- path = "%s/%s/cherry_pick" % (self.manager.path, self.get_id())
- post_data = {"branch": branch}
- self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
-
- @cli.register_custom_action("ProjectCommit", optional=("type",))
- @exc.on_http_error(exc.GitlabGetError)
- def refs(self, type="all", **kwargs):
- """List the references the commit is pushed to.
-
- Args:
- type (str): The scope of references ('branch', 'tag' or 'all')
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetError: If the references could not be retrieved
-
- Returns:
- list: The references the commit is pushed to.
- """
- path = "%s/%s/refs" % (self.manager.path, self.get_id())
- data = {"type": type}
- return self.manager.gitlab.http_get(path, query_data=data, **kwargs)
-
- @cli.register_custom_action("ProjectCommit")
- @exc.on_http_error(exc.GitlabGetError)
- def merge_requests(self, **kwargs):
- """List the merge requests related to the commit.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetError: If the references could not be retrieved
-
- Returns:
- list: The merge requests related to the commit.
- """
- path = "%s/%s/merge_requests" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("ProjectCommit", ("branch",))
- @exc.on_http_error(exc.GitlabRevertError)
- def revert(self, branch, **kwargs):
- """Revert a commit on a given branch.
-
- Args:
- branch (str): Name of target branch
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabRevertError: If the revert could not be performed
-
- Returns:
- dict: The new commit data (*not* a RESTObject)
- """
- path = "%s/%s/revert" % (self.manager.path, self.get_id())
- post_data = {"branch": branch}
- return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
-
- @cli.register_custom_action("ProjectCommit")
- @exc.on_http_error(exc.GitlabGetError)
- def signature(self, **kwargs):
- """Get the signature of the commit.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetError: If the signature could not be retrieved
-
- Returns:
- dict: The commit's signature data
- """
- path = "%s/%s/signature" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
-
-class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/commits"
- _obj_cls = ProjectCommit
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- ("branch", "commit_message", "actions"),
- ("author_email", "author_name"),
- )
-
-
-class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject):
- @cli.register_custom_action("ProjectEnvironment")
- @exc.on_http_error(exc.GitlabStopError)
- def stop(self, **kwargs):
- """Stop the environment.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabStopError: If the operation failed
- """
- path = "%s/%s/stop" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path, **kwargs)
-
-
-class ProjectEnvironmentManager(
- RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = "/projects/%(project_id)s/environments"
- _obj_cls = ProjectEnvironment
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("name",), ("external_url",))
- _update_attrs = (tuple(), ("name", "external_url"))
-
-
-class ProjectKey(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectKeyManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/deploy_keys"
- _obj_cls = ProjectKey
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("title", "key"), ("can_push",))
- _update_attrs = (tuple(), ("title", "can_push"))
-
- @cli.register_custom_action("ProjectKeyManager", ("key_id",))
- @exc.on_http_error(exc.GitlabProjectDeployKeyError)
- def enable(self, key_id, **kwargs):
- """Enable a deploy key for a project.
-
- Args:
- key_id (int): The ID of the key to enable
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabProjectDeployKeyError: If the key could not be enabled
- """
- path = "%s/%s/enable" % (self.path, key_id)
- self.gitlab.http_post(path, **kwargs)
-
-
-class ProjectBadge(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/badges"
- _obj_cls = ProjectBadge
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("link_url", "image_url"), tuple())
- _update_attrs = (tuple(), ("link_url", "image_url"))
-
-
-class ProjectEvent(Event):
- pass
-
-
-class ProjectEventManager(EventManager):
- _path = "/projects/%(project_id)s/events"
- _obj_cls = ProjectEvent
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectFork(RESTObject):
- pass
-
-
-class ProjectForkManager(CreateMixin, ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/forks"
- _obj_cls = ProjectFork
- _from_parent_attrs = {"project_id": "id"}
- _list_filters = (
- "archived",
- "visibility",
- "order_by",
- "sort",
- "search",
- "simple",
- "owned",
- "membership",
- "starred",
- "statistics",
- "with_custom_attributes",
- "with_issues_enabled",
- "with_merge_requests_enabled",
- )
- _create_attrs = (tuple(), ("namespace",))
-
- def create(self, data, **kwargs):
- """Creates 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 managed object class build with
- the data sent by the server
- """
- path = self.path[:-1] # drop the 's'
- return CreateMixin.create(self, data, path=path, **kwargs)
-
-
-class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject):
- _short_print_attr = "url"
-
-
-class ProjectHookManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/hooks"
- _obj_cls = ProjectHook
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- ("url",),
- (
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "job_events",
- "pipeline_events",
- "wiki_page_events",
- "enable_ssl_verification",
- "token",
- ),
- )
- _update_attrs = (
- ("url",),
- (
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "job_events",
- "pipeline_events",
- "wiki_events",
- "enable_ssl_verification",
- "token",
- ),
- )
-
-
-class ProjectIssueAwardEmoji(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectIssueAwardEmojiManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues/%(issue_iid)s/award_emoji"
- _obj_cls = ProjectIssueAwardEmoji
- _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
- _create_attrs = (("name",), tuple())
-
-
-class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin, RESTManager):
- _path = (
- "/projects/%(project_id)s/issues/%(issue_iid)s" "/notes/%(note_id)s/award_emoji"
- )
- _obj_cls = ProjectIssueNoteAwardEmoji
- _from_parent_attrs = {
- "project_id": "project_id",
- "issue_iid": "issue_iid",
- "note_id": "id",
- }
- _create_attrs = (("name",), tuple())
-
-
-class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- _managers = (("awardemojis", "ProjectIssueNoteAwardEmojiManager"),)
-
-
-class ProjectIssueNoteManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues/%(issue_iid)s/notes"
- _obj_cls = ProjectIssueNote
- _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
- _create_attrs = (("body",), ("created_at",))
- _update_attrs = (("body",), tuple())
-
-
-class ProjectIssueDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectIssueDiscussionNoteManager(
- GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = (
- "/projects/%(project_id)s/issues/%(issue_iid)s/"
- "discussions/%(discussion_id)s/notes"
- )
- _obj_cls = ProjectIssueDiscussionNote
- _from_parent_attrs = {
- "project_id": "project_id",
- "issue_iid": "issue_iid",
- "discussion_id": "id",
- }
- _create_attrs = (("body",), ("created_at",))
- _update_attrs = (("body",), tuple())
-
-
-class ProjectIssueDiscussion(RESTObject):
- _managers = (("notes", "ProjectIssueDiscussionNoteManager"),)
-
-
-class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues/%(issue_iid)s/discussions"
- _obj_cls = ProjectIssueDiscussion
- _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
- _create_attrs = (("body",), ("created_at",))
-
-
-class ProjectIssueLink(ObjectDeleteMixin, RESTObject):
- _id_attr = "issue_link_id"
-
-
-class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues/%(issue_iid)s/links"
- _obj_cls = ProjectIssueLink
- _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
- _create_attrs = (("target_project_id", "target_issue_iid"), tuple())
-
- @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)
-
- Returns:
- RESTObject, RESTObject: The source and target issues
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabCreateError: If the server cannot perform the request
- """
- self._check_missing_create_attrs(data)
- server_data = self.gitlab.http_post(self.path, post_data=data, **kwargs)
- source_issue = ProjectIssue(self._parent.manager, server_data["source_issue"])
- target_issue = ProjectIssue(self._parent.manager, server_data["target_issue"])
- return source_issue, target_issue
-
-
-class ProjectIssueResourceLabelEvent(RESTObject):
- pass
-
-
-class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues/%(issue_iid)s" "/resource_label_events"
- _obj_cls = ProjectIssueResourceLabelEvent
- _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
-
-
-class ProjectIssueResourceMilestoneEvent(RESTObject):
- pass
-
-
-class ProjectIssueResourceMilestoneEventManager(RetrieveMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues/%(issue_iid)s/resource_milestone_events"
- _obj_cls = ProjectIssueResourceMilestoneEvent
- _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
-
-
-class ProjectIssue(
- UserAgentDetailMixin,
- SubscribableMixin,
- TodoMixin,
- TimeTrackingMixin,
- ParticipantsMixin,
- SaveMixin,
- ObjectDeleteMixin,
- RESTObject,
-):
- _short_print_attr = "title"
- _id_attr = "iid"
- _managers = (
- ("awardemojis", "ProjectIssueAwardEmojiManager"),
- ("discussions", "ProjectIssueDiscussionManager"),
- ("links", "ProjectIssueLinkManager"),
- ("notes", "ProjectIssueNoteManager"),
- ("resourcelabelevents", "ProjectIssueResourceLabelEventManager"),
- ("resourcemilestoneevents", "ProjectIssueResourceMilestoneEventManager"),
- )
-
- @cli.register_custom_action("ProjectIssue", ("to_project_id",))
- @exc.on_http_error(exc.GitlabUpdateError)
- def move(self, to_project_id, **kwargs):
- """Move the issue to another project.
-
- Args:
- to_project_id(int): ID of the target project
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabUpdateError: If the issue could not be moved
- """
- path = "%s/%s/move" % (self.manager.path, self.get_id())
- data = {"to_project_id": to_project_id}
- server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("ProjectIssue")
- @exc.on_http_error(exc.GitlabGetError)
- def related_merge_requests(self, **kwargs):
- """List merge requests related to the issue.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetErrot: If the merge requests could not be retrieved
-
- Returns:
- list: The list of merge requests.
- """
- path = "%s/%s/related_merge_requests" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("ProjectIssue")
- @exc.on_http_error(exc.GitlabGetError)
- def closed_by(self, **kwargs):
- """List merge requests that will close the issue when merged.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetErrot: If the merge requests could not be retrieved
-
- Returns:
- list: The list of merge requests.
- """
- path = "%s/%s/closed_by" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
-
-class ProjectIssueManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues"
- _obj_cls = ProjectIssue
- _from_parent_attrs = {"project_id": "id"}
- _list_filters = (
- "iids",
- "state",
- "labels",
- "milestone",
- "scope",
- "author_id",
- "assignee_id",
- "my_reaction_emoji",
- "order_by",
- "sort",
- "search",
- "created_after",
- "created_before",
- "updated_after",
- "updated_before",
- )
- _create_attrs = (
- ("title",),
- (
- "description",
- "confidential",
- "assignee_ids",
- "assignee_id",
- "milestone_id",
- "labels",
- "created_at",
- "due_date",
- "merge_request_to_resolve_discussions_of",
- "discussion_to_resolve",
- ),
- )
- _update_attrs = (
- tuple(),
- (
- "title",
- "description",
- "confidential",
- "assignee_ids",
- "assignee_id",
- "milestone_id",
- "labels",
- "state_event",
- "updated_at",
- "due_date",
- "discussion_locked",
- ),
- )
- _types = {"labels": types.ListAttribute}
-
-
-class ProjectMember(SaveMixin, ObjectDeleteMixin, RESTObject):
- _short_print_attr = "username"
-
-
-class ProjectMemberManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/members"
- _obj_cls = ProjectMember
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("access_level", "user_id"), ("expires_at",))
- _update_attrs = (("access_level",), ("expires_at",))
-
- @cli.register_custom_action("ProjectMemberManager")
- @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 ProjectNote(RESTObject):
- pass
-
-
-class ProjectNoteManager(RetrieveMixin, RESTManager):
- _path = "/projects/%(project_id)s/notes"
- _obj_cls = ProjectNote
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("body",), tuple())
-
-
-class ProjectNotificationSettings(NotificationSettings):
- pass
-
-
-class ProjectNotificationSettingsManager(NotificationSettingsManager):
- _path = "/projects/%(project_id)s/notification_settings"
- _obj_cls = ProjectNotificationSettings
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectPackage(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/packages"
- _obj_cls = ProjectPackage
- _from_parent_attrs = {"project_id": "id"}
- _list_filters = (
- "order_by",
- "sort",
- "package_type",
- "package_name",
- )
-
-
-class ProjectPagesDomain(SaveMixin, ObjectDeleteMixin, RESTObject):
- _id_attr = "domain"
-
-
-class ProjectPagesDomainManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/pages/domains"
- _obj_cls = ProjectPagesDomain
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("domain",), ("certificate", "key"))
- _update_attrs = (tuple(), ("certificate", "key"))
-
-
-class ProjectRelease(RESTObject):
- _id_attr = "tag_name"
-
-
-class ProjectReleaseManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/releases"
- _obj_cls = ProjectRelease
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))
-
-
-class ProjectTag(ObjectDeleteMixin, RESTObject):
- _id_attr = "name"
- _short_print_attr = "name"
-
- @cli.register_custom_action("ProjectTag", ("description",))
- def set_release_description(self, description, **kwargs):
- """Set the release notes on the tag.
-
- If the release doesn't exist yet, it will be created. If it already
- exists, its description will be updated.
-
- Args:
- description (str): Description of the release.
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabCreateError: If the server fails to create the release
- GitlabUpdateError: If the server fails to update the release
- """
- id = self.get_id().replace("/", "%2F")
- path = "%s/%s/release" % (self.manager.path, id)
- data = {"description": description}
- if self.release is None:
- try:
- server_data = self.manager.gitlab.http_post(
- path, post_data=data, **kwargs
- )
- except exc.GitlabHttpError as e:
- raise exc.GitlabCreateError(e.response_code, e.error_message) from e
- else:
- try:
- server_data = self.manager.gitlab.http_put(
- path, post_data=data, **kwargs
- )
- except exc.GitlabHttpError as e:
- raise exc.GitlabUpdateError(e.response_code, e.error_message) from e
- self.release = server_data
-
-
-class ProjectTagManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/tags"
- _obj_cls = ProjectTag
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("tag_name", "ref"), ("message",))
-
-
-class ProjectProtectedTag(ObjectDeleteMixin, RESTObject):
- _id_attr = "name"
- _short_print_attr = "name"
-
-
-class ProjectProtectedTagManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/protected_tags"
- _obj_cls = ProjectProtectedTag
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("name",), ("create_access_level",))
-
-
-class ProjectMergeRequestApproval(SaveMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approvals"
- _obj_cls = ProjectMergeRequestApproval
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
- _update_attrs = (("approvals_required",), tuple())
- _update_uses_post = True
-
- @exc.on_http_error(exc.GitlabUpdateError)
- def set_approvers(
- self,
- approvals_required,
- approver_ids=None,
- approver_group_ids=None,
- approval_rule_name="name",
- **kwargs
- ):
- """Change MR-level allowed approvers and approver groups.
-
- Args:
- approvals_required (integer): The number of required approvals for this rule
- approver_ids (list of integers): User IDs that can approve MRs
- approver_group_ids (list): Group IDs whose members can approve MRs
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabUpdateError: If the server failed to perform the request
- """
- approver_ids = approver_ids or []
- approver_group_ids = approver_group_ids or []
-
- data = {
- "name": approval_rule_name,
- "approvals_required": approvals_required,
- "rule_type": "regular",
- "user_ids": approver_ids,
- "group_ids": approver_group_ids,
- }
- approval_rules = self._parent.approval_rules
- """ update any existing approval rule matching the name"""
- existing_approval_rules = approval_rules.list()
- for ar in existing_approval_rules:
- if ar.name == approval_rule_name:
- ar.user_ids = data["user_ids"]
- ar.approvals_required = data["approvals_required"]
- ar.group_ids = data["group_ids"]
- ar.save()
- return ar
- """ if there was no rule matching the rule name, create a new one"""
- return approval_rules.create(data=data)
-
-
-class ProjectMergeRequestApprovalRule(SaveMixin, RESTObject):
- _id_attr = "approval_rule_id"
- _short_print_attr = "approval_rule"
-
- @exc.on_http_error(exc.GitlabUpdateError)
- 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
- """
- # There is a mismatch between the name of our id attribute and the put REST API name for the
- # project_id, so we override it here.
- self.approval_rule_id = self.id
- self.merge_request_iid = self._parent_attrs["mr_iid"]
- self.id = self._parent_attrs["project_id"]
- # save will update self.id with the result from the server, so no need to overwrite with
- # what it was before we overwrote it."""
- SaveMixin.save(self, **kwargs)
-
-
-class ProjectMergeRequestApprovalRuleManager(
- ListMixin, UpdateMixin, CreateMixin, RESTManager
-):
- _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approval_rules"
- _obj_cls = ProjectMergeRequestApprovalRule
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
- _list_filters = ("name", "rule_type")
- _update_attrs = (
- ("id", "merge_request_iid", "approval_rule_id", "name", "approvals_required"),
- ("user_ids", "group_ids"),
- )
- # Important: When approval_project_rule_id is set, the name, users and groups of
- # project-level rule will be copied. The approvals_required specified will be used. """
- _create_attrs = (
- ("id", "merge_request_iid", "name", "approvals_required"),
- ("approval_project_rule_id", "user_ids", "group_ids"),
- )
-
- 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
- """
- new_data = data.copy()
- new_data["id"] = self._from_parent_attrs["project_id"]
- new_data["merge_request_iid"] = self._from_parent_attrs["mr_iid"]
- return CreateMixin.create(self, new_data, **kwargs)
-
-
-class ProjectMergeRequestAwardEmoji(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectMergeRequestAwardEmojiManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/award_emoji"
- _obj_cls = ProjectMergeRequestAwardEmoji
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
- _create_attrs = (("name",), tuple())
-
-
-class ProjectMergeRequestDiff(RESTObject):
- pass
-
-
-class ProjectMergeRequestDiffManager(RetrieveMixin, RESTManager):
- _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/versions"
- _obj_cls = ProjectMergeRequestDiff
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
-
-
-class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectMergeRequestNoteAwardEmojiManager(NoUpdateMixin, RESTManager):
- _path = (
- "/projects/%(project_id)s/merge_requests/%(mr_iid)s"
- "/notes/%(note_id)s/award_emoji"
- )
- _obj_cls = ProjectMergeRequestNoteAwardEmoji
- _from_parent_attrs = {
- "project_id": "project_id",
- "mr_iid": "mr_iid",
- "note_id": "id",
- }
- _create_attrs = (("name",), tuple())
-
-
-class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- _managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),)
-
-
-class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/notes"
- _obj_cls = ProjectMergeRequestNote
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
- _create_attrs = (("body",), tuple())
- _update_attrs = (("body",), tuple())
-
-
-class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectMergeRequestDiscussionNoteManager(
- GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = (
- "/projects/%(project_id)s/merge_requests/%(mr_iid)s/"
- "discussions/%(discussion_id)s/notes"
- )
- _obj_cls = ProjectMergeRequestDiscussionNote
- _from_parent_attrs = {
- "project_id": "project_id",
- "mr_iid": "mr_iid",
- "discussion_id": "id",
- }
- _create_attrs = (("body",), ("created_at",))
- _update_attrs = (("body",), tuple())
-
-
-class ProjectMergeRequestDiscussion(SaveMixin, RESTObject):
- _managers = (("notes", "ProjectMergeRequestDiscussionNoteManager"),)
-
-
-class ProjectMergeRequestDiscussionManager(
- RetrieveMixin, CreateMixin, UpdateMixin, RESTManager
-):
- _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/discussions"
- _obj_cls = ProjectMergeRequestDiscussion
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
- _create_attrs = (("body",), ("created_at", "position"))
- _update_attrs = (("resolved",), tuple())
-
-
-class ProjectMergeRequestResourceLabelEvent(RESTObject):
- pass
-
-
-class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager):
- _path = (
- "/projects/%(project_id)s/merge_requests/%(mr_iid)s" "/resource_label_events"
- )
- _obj_cls = ProjectMergeRequestResourceLabelEvent
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
-
-
-class ProjectMergeRequestResourceMilestoneEvent(RESTObject):
- pass
-
-
-class ProjectMergeRequestResourceMilestoneEventManager(RetrieveMixin, RESTManager):
- _path = (
- "/projects/%(project_id)s/merge_requests/%(mr_iid)s/resource_milestone_events"
- )
- _obj_cls = ProjectMergeRequestResourceMilestoneEvent
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
-
-
-class ProjectMergeRequest(
- SubscribableMixin,
- TodoMixin,
- TimeTrackingMixin,
- ParticipantsMixin,
- SaveMixin,
- ObjectDeleteMixin,
- RESTObject,
-):
- _id_attr = "iid"
-
- _managers = (
- ("approvals", "ProjectMergeRequestApprovalManager"),
- ("approval_rules", "ProjectMergeRequestApprovalRuleManager"),
- ("awardemojis", "ProjectMergeRequestAwardEmojiManager"),
- ("diffs", "ProjectMergeRequestDiffManager"),
- ("discussions", "ProjectMergeRequestDiscussionManager"),
- ("notes", "ProjectMergeRequestNoteManager"),
- ("resourcelabelevents", "ProjectMergeRequestResourceLabelEventManager"),
- ("resourcemilestoneevents", "ProjectMergeRequestResourceMilestoneEventManager"),
- )
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabMROnBuildSuccessError)
- def cancel_merge_when_pipeline_succeeds(self, **kwargs):
- """Cancel merge when the pipeline succeeds.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabMROnBuildSuccessError: If the server could not handle the
- request
- """
-
- path = "%s/%s/cancel_merge_when_pipeline_succeeds" % (
- self.manager.path,
- self.get_id(),
- )
- server_data = self.manager.gitlab.http_put(path, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabListError)
- def closes_issues(self, **kwargs):
- """List issues that will close on merge."
-
- 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: List of issues
- """
- path = "%s/%s/closes_issues" % (self.manager.path, self.get_id())
- data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs)
- manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent)
- return RESTObjectList(manager, ProjectIssue, data_list)
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabListError)
- def commits(self, **kwargs):
- """List the merge request commits.
-
- 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 commits
- """
-
- path = "%s/%s/commits" % (self.manager.path, self.get_id())
- data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs)
- manager = ProjectCommitManager(self.manager.gitlab, parent=self.manager._parent)
- return RESTObjectList(manager, ProjectCommit, data_list)
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabListError)
- def changes(self, **kwargs):
- """List the merge request changes.
-
- Args:
- **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: List of changes
- """
- path = "%s/%s/changes" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabListError)
- def pipelines(self, **kwargs):
- """List the merge request pipelines.
-
- Args:
- **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: List of changes
- """
-
- path = "%s/%s/pipelines" % (self.manager.path, self.get_id())
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("ProjectMergeRequest", tuple(), ("sha"))
- @exc.on_http_error(exc.GitlabMRApprovalError)
- def approve(self, sha=None, **kwargs):
- """Approve the merge request.
-
- Args:
- sha (str): Head SHA of MR
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabMRApprovalError: If the approval failed
- """
- path = "%s/%s/approve" % (self.manager.path, self.get_id())
- data = {}
- if sha:
- data["sha"] = sha
-
- server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabMRApprovalError)
- def unapprove(self, **kwargs):
- """Unapprove the merge request.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabMRApprovalError: If the unapproval failed
- """
- path = "%s/%s/unapprove" % (self.manager.path, self.get_id())
- data = {}
-
- server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("ProjectMergeRequest")
- @exc.on_http_error(exc.GitlabMRRebaseError)
- def rebase(self, **kwargs):
- """Attempt to rebase the source branch onto the target branch
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabMRRebaseError: If rebasing failed
- """
- path = "%s/%s/rebase" % (self.manager.path, self.get_id())
- data = {}
- return self.manager.gitlab.http_put(path, post_data=data, **kwargs)
-
- @cli.register_custom_action(
- "ProjectMergeRequest",
- tuple(),
- (
- "merge_commit_message",
- "should_remove_source_branch",
- "merge_when_pipeline_succeeds",
- ),
- )
- @exc.on_http_error(exc.GitlabMRClosedError)
- def merge(
- self,
- merge_commit_message=None,
- should_remove_source_branch=False,
- merge_when_pipeline_succeeds=False,
- **kwargs
- ):
- """Accept the merge request.
-
- Args:
- merge_commit_message (bool): Commit message
- should_remove_source_branch (bool): If True, removes the source
- branch
- merge_when_pipeline_succeeds (bool): Wait for the build to succeed,
- then merge
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabMRClosedError: If the merge failed
- """
- path = "%s/%s/merge" % (self.manager.path, self.get_id())
- data = {}
- if merge_commit_message:
- data["merge_commit_message"] = merge_commit_message
- if should_remove_source_branch:
- data["should_remove_source_branch"] = True
- if merge_when_pipeline_succeeds:
- data["merge_when_pipeline_succeeds"] = True
-
- server_data = self.manager.gitlab.http_put(path, query_data=data, **kwargs)
- self._update_attrs(server_data)
-
-
-class ProjectMergeRequestManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/merge_requests"
- _obj_cls = ProjectMergeRequest
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- ("source_branch", "target_branch", "title"),
- (
- "assignee_id",
- "description",
- "target_project_id",
- "labels",
- "milestone_id",
- "remove_source_branch",
- "allow_maintainer_to_push",
- "squash",
- ),
- )
- _update_attrs = (
- tuple(),
- (
- "target_branch",
- "assignee_id",
- "title",
- "description",
- "state_event",
- "labels",
- "milestone_id",
- "remove_source_branch",
- "discussion_locked",
- "allow_maintainer_to_push",
- "squash",
- ),
- )
- _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",
- "wip",
- )
- _types = {"labels": types.ListAttribute}
-
-
-class ProjectMilestone(SaveMixin, ObjectDeleteMixin, RESTObject):
- _short_print_attr = "title"
-
- @cli.register_custom_action("ProjectMilestone")
- @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 = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent)
- # FIXME(gpocentek): the computed manager path is not correct
- return RESTObjectList(manager, ProjectIssue, data_list)
-
- @cli.register_custom_action("ProjectMilestone")
- @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 = ProjectMergeRequestManager(
- self.manager.gitlab, parent=self.manager._parent
- )
- # FIXME(gpocentek): the computed manager path is not correct
- return RESTObjectList(manager, ProjectMergeRequest, data_list)
-
-
-class ProjectMilestoneManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/milestones"
- _obj_cls = ProjectMilestone
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- ("title",),
- ("description", "due_date", "start_date", "state_event"),
- )
- _update_attrs = (
- tuple(),
- ("title", "description", "due_date", "start_date", "state_event"),
- )
- _list_filters = ("iids", "state", "search")
-
-
-class ProjectLabel(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 ProjectLabelManager(
- RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = "/projects/%(project_id)s/labels"
- _obj_cls = ProjectLabel
- _from_parent_attrs = {"project_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 ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject):
- _id_attr = "file_path"
- _short_print_attr = "file_path"
-
- def decode(self):
- """Returns the decoded content of the file.
-
- Returns:
- (str): the decoded content.
- """
- return base64.b64decode(self.content)
-
- def save(self, branch, commit_message, **kwargs):
- """Save the changes made to the file to the server.
-
- The object is updated to match what the server returns.
-
- Args:
- branch (str): Branch in which the file will be updated
- commit_message (str): Message to send with the commit
- **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
- """
- self.branch = branch
- self.commit_message = commit_message
- self.file_path = self.file_path.replace("/", "%2F")
- super(ProjectFile, self).save(**kwargs)
-
- def delete(self, branch, commit_message, **kwargs):
- """Delete the file from the server.
-
- Args:
- branch (str): Branch from which the file will be removed
- commit_message (str): Commit message for the deletion
- **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
- """
- file_path = self.get_id().replace("/", "%2F")
- self.manager.delete(file_path, branch, commit_message, **kwargs)
-
-
-class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/repository/files"
- _obj_cls = ProjectFile
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- ("file_path", "branch", "content", "commit_message"),
- ("encoding", "author_email", "author_name"),
- )
- _update_attrs = (
- ("file_path", "branch", "content", "commit_message"),
- ("encoding", "author_email", "author_name"),
- )
-
- @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
- def get(self, file_path, ref, **kwargs):
- """Retrieve a single file.
-
- Args:
- file_path (str): Path of the file to retrieve
- ref (str): Name of the branch, tag or commit
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetError: If the file could not be retrieved
-
- Returns:
- object: The generated RESTObject
- """
- file_path = file_path.replace("/", "%2F")
- return GetMixin.get(self, file_path, ref=ref, **kwargs)
-
- @cli.register_custom_action(
- "ProjectFileManager",
- ("file_path", "branch", "content", "commit_message"),
- ("encoding", "author_email", "author_name"),
- )
- @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)
-
- Returns:
- RESTObject: a new instance of the managed object class built with
- the data sent by the server
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabCreateError: If the server cannot perform the request
- """
-
- self._check_missing_create_attrs(data)
- new_data = data.copy()
- file_path = new_data.pop("file_path").replace("/", "%2F")
- path = "%s/%s" % (self.path, file_path)
- server_data = self.gitlab.http_post(path, post_data=new_data, **kwargs)
- return self._obj_cls(self, server_data)
-
- @exc.on_http_error(exc.GitlabUpdateError)
- def update(self, file_path, 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()
- file_path = file_path.replace("/", "%2F")
- data["file_path"] = file_path
- path = "%s/%s" % (self.path, file_path)
- self._check_missing_update_attrs(data)
- return self.gitlab.http_put(path, post_data=data, **kwargs)
-
- @cli.register_custom_action(
- "ProjectFileManager", ("file_path", "branch", "commit_message")
- )
- @exc.on_http_error(exc.GitlabDeleteError)
- def delete(self, file_path, branch, commit_message, **kwargs):
- """Delete a file on the server.
-
- Args:
- file_path (str): Path of the file to remove
- branch (str): Branch from which the file will be removed
- commit_message (str): Commit message for the deletion
- **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 = "%s/%s" % (self.path, file_path.replace("/", "%2F"))
- data = {"branch": branch, "commit_message": commit_message}
- self.gitlab.http_delete(path, query_data=data, **kwargs)
-
- @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
- @exc.on_http_error(exc.GitlabGetError)
- def raw(
- self, file_path, ref, streamed=False, action=None, chunk_size=1024, **kwargs
- ):
- """Return the content of a file for a commit.
-
- Args:
- ref (str): ID of the commit
- filepath (str): Path of the file to return
- 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 file could not be retrieved
-
- Returns:
- str: The file content
- """
- file_path = file_path.replace("/", "%2F").replace(".", "%2E")
- path = "%s/%s/raw" % (self.path, file_path)
- query_data = {"ref": ref}
- result = self.gitlab.http_get(
- path, query_data=query_data, streamed=streamed, raw=True, **kwargs
- )
- return utils.response_content(result, streamed, action, chunk_size)
-
- @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
- @exc.on_http_error(exc.GitlabListError)
- def blame(self, file_path, ref, **kwargs):
- """Return the content of a file for a commit.
-
- Args:
- file_path (str): Path of the file to retrieve
- ref (str): Name of the branch, tag or commit
- **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(blame): a list of commits/lines matching the file
- """
- file_path = file_path.replace("/", "%2F").replace(".", "%2E")
- path = "%s/%s/blame" % (self.path, file_path)
- query_data = {"ref": ref}
- return self.gitlab.http_list(path, query_data, **kwargs)
-
-
-class ProjectPipelineJob(RESTObject):
- pass
-
-
-class ProjectPipelineJobManager(ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs"
- _obj_cls = ProjectPipelineJob
- _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
- _list_filters = ("scope",)
-
-
-class ProjectPipelineBridge(RESTObject):
- pass
-
-
-class ProjectPipelineBridgeManager(ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/bridges"
- _obj_cls = ProjectPipelineBridge
- _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
- _list_filters = ("scope",)
-
-
-class ProjectPipelineVariable(RESTObject):
- _id_attr = "key"
-
-
-class ProjectPipelineVariableManager(ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/variables"
- _obj_cls = ProjectPipelineVariable
- _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
-
-
-class ProjectPipeline(RESTObject, RefreshMixin, ObjectDeleteMixin):
- _managers = (
- ("jobs", "ProjectPipelineJobManager"),
- ("bridges", "ProjectPipelineBridgeManager"),
- ("variables", "ProjectPipelineVariableManager"),
- )
-
- @cli.register_custom_action("ProjectPipeline")
- @exc.on_http_error(exc.GitlabPipelineCancelError)
- def cancel(self, **kwargs):
- """Cancel the job.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabPipelineCancelError: If the request failed
- """
- path = "%s/%s/cancel" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
- @cli.register_custom_action("ProjectPipeline")
- @exc.on_http_error(exc.GitlabPipelineRetryError)
- def retry(self, **kwargs):
- """Retry the job.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabPipelineRetryError: If the request failed
- """
- path = "%s/%s/retry" % (self.manager.path, self.get_id())
- self.manager.gitlab.http_post(path)
-
-
-class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/pipelines"
- _obj_cls = ProjectPipeline
- _from_parent_attrs = {"project_id": "id"}
- _list_filters = (
- "scope",
- "status",
- "ref",
- "sha",
- "yaml_errors",
- "name",
- "username",
- "order_by",
- "sort",
- )
- _create_attrs = (("ref",), tuple())
-
- def create(self, data, **kwargs):
- """Creates 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 managed object class build with
- the data sent by the server
- """
- path = self.path[:-1] # drop the 's'
- return CreateMixin.create(self, data, path=path, **kwargs)
-
-
-class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject):
- _id_attr = "key"
-
-
-class ProjectPipelineScheduleVariableManager(
- CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = (
- "/projects/%(project_id)s/pipeline_schedules/"
- "%(pipeline_schedule_id)s/variables"
- )
- _obj_cls = ProjectPipelineScheduleVariable
- _from_parent_attrs = {"project_id": "project_id", "pipeline_schedule_id": "id"}
- _create_attrs = (("key", "value"), tuple())
- _update_attrs = (("key", "value"), tuple())
-
-
-class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject):
- _managers = (("variables", "ProjectPipelineScheduleVariableManager"),)
-
- @cli.register_custom_action("ProjectPipelineSchedule")
- @exc.on_http_error(exc.GitlabOwnershipError)
- def take_ownership(self, **kwargs):
- """Update the owner of a pipeline schedule.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabOwnershipError: If the request failed
- """
- path = "%s/%s/take_ownership" % (self.manager.path, self.get_id())
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("ProjectPipelineSchedule")
- @exc.on_http_error(exc.GitlabPipelinePlayError)
- def play(self, **kwargs):
- """Trigger a new scheduled pipeline, which runs immediately.
- The next scheduled run of this pipeline is not affected.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabPipelinePlayError: If the request failed
- """
- path = "%s/%s/play" % (self.manager.path, self.get_id())
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
- return server_data
-
-
-class ProjectPipelineScheduleManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/pipeline_schedules"
- _obj_cls = ProjectPipelineSchedule
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("description", "ref", "cron"), ("cron_timezone", "active"))
- _update_attrs = (tuple(), ("description", "ref", "cron", "cron_timezone", "active"))
-
-
-class ProjectPushRules(SaveMixin, ObjectDeleteMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectPushRulesManager(
- GetWithoutIdMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = "/projects/%(project_id)s/push_rule"
- _obj_cls = ProjectPushRules
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- tuple(),
- (
- "deny_delete_tag",
- "member_check",
- "prevent_secrets",
- "commit_message_regex",
- "branch_name_regex",
- "author_email_regex",
- "file_name_regex",
- "max_file_size",
- ),
- )
- _update_attrs = (
- tuple(),
- (
- "deny_delete_tag",
- "member_check",
- "prevent_secrets",
- "commit_message_regex",
- "branch_name_regex",
- "author_email_regex",
- "file_name_regex",
- "max_file_size",
- ),
- )
-
-
-class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin, RESTManager):
- _path = (
- "/projects/%(project_id)s/snippets/%(snippet_id)s"
- "/notes/%(note_id)s/award_emoji"
- )
- _obj_cls = ProjectSnippetNoteAwardEmoji
- _from_parent_attrs = {
- "project_id": "project_id",
- "snippet_id": "snippet_id",
- "note_id": "id",
- }
- _create_attrs = (("name",), tuple())
-
-
-class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- _managers = (("awardemojis", "ProjectSnippetNoteAwardEmojiManager"),)
-
-
-class ProjectSnippetNoteManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/notes"
- _obj_cls = ProjectSnippetNote
- _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"}
- _create_attrs = (("body",), tuple())
- _update_attrs = (("body",), tuple())
-
-
-class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectSnippetAwardEmojiManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/award_emoji"
- _obj_cls = ProjectSnippetAwardEmoji
- _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"}
- _create_attrs = (("name",), tuple())
-
-
-class ProjectSnippetDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectSnippetDiscussionNoteManager(
- GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = (
- "/projects/%(project_id)s/snippets/%(snippet_id)s/"
- "discussions/%(discussion_id)s/notes"
- )
- _obj_cls = ProjectSnippetDiscussionNote
- _from_parent_attrs = {
- "project_id": "project_id",
- "snippet_id": "snippet_id",
- "discussion_id": "id",
- }
- _create_attrs = (("body",), ("created_at",))
- _update_attrs = (("body",), tuple())
-
-
-class ProjectSnippetDiscussion(RESTObject):
- _managers = (("notes", "ProjectSnippetDiscussionNoteManager"),)
-
-
-class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/discussions"
- _obj_cls = ProjectSnippetDiscussion
- _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"}
- _create_attrs = (("body",), ("created_at",))
-
-
-class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
- _url = "/projects/%(project_id)s/snippets"
- _short_print_attr = "title"
- _managers = (
- ("awardemojis", "ProjectSnippetAwardEmojiManager"),
- ("discussions", "ProjectSnippetDiscussionManager"),
- ("notes", "ProjectSnippetNoteManager"),
- )
-
- @cli.register_custom_action("ProjectSnippet")
- @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 = "%s/%s/raw" % (self.manager.path, 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 ProjectSnippetManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/snippets"
- _obj_cls = ProjectSnippet
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("title", "file_name", "content", "visibility"), ("description",))
- _update_attrs = (
- tuple(),
- ("title", "file_name", "content", "visibility", "description"),
- )
-
-
-class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject):
- @cli.register_custom_action("ProjectTrigger")
- @exc.on_http_error(exc.GitlabOwnershipError)
- def take_ownership(self, **kwargs):
- """Update the owner of a trigger.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabOwnershipError: If the request failed
- """
- path = "%s/%s/take_ownership" % (self.manager.path, self.get_id())
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
-
-
-class ProjectTriggerManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/triggers"
- _obj_cls = ProjectTrigger
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("description",), tuple())
- _update_attrs = (("description",), tuple())
-
-
-class ProjectUser(RESTObject):
- pass
-
-
-class ProjectUserManager(ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/users"
- _obj_cls = ProjectUser
- _from_parent_attrs = {"project_id": "id"}
- _list_filters = ("search",)
-
-
-class ProjectService(SaveMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectServiceManager(GetMixin, UpdateMixin, DeleteMixin, ListMixin, RESTManager):
- _path = "/projects/%(project_id)s/services"
- _from_parent_attrs = {"project_id": "id"}
- _obj_cls = ProjectService
-
- _service_attrs = {
- "asana": (("api_key",), ("restrict_to_branch", "push_events")),
- "assembla": (("token",), ("subdomain", "push_events")),
- "bamboo": (
- ("bamboo_url", "build_key", "username", "password"),
- ("push_events",),
- ),
- "bugzilla": (
- ("new_issue_url", "issues_url", "project_url"),
- ("description", "title", "push_events"),
- ),
- "buildkite": (
- ("token", "project_url"),
- ("enable_ssl_verification", "push_events"),
- ),
- "campfire": (("token",), ("subdomain", "room", "push_events")),
- "circuit": (
- ("webhook",),
- (
- "notify_only_broken_pipelines",
- "branches_to_be_notified",
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "confidential_note_events",
- "pipeline_events",
- "wiki_page_events",
- ),
- ),
- "custom-issue-tracker": (
- ("new_issue_url", "issues_url", "project_url"),
- ("description", "title", "push_events"),
- ),
- "drone-ci": (
- ("token", "drone_url"),
- (
- "enable_ssl_verification",
- "push_events",
- "merge_requests_events",
- "tag_push_events",
- ),
- ),
- "emails-on-push": (
- ("recipients",),
- (
- "disable_diffs",
- "send_from_committer_email",
- "push_events",
- "tag_push_events",
- "branches_to_be_notified",
- ),
- ),
- "pipelines-email": (
- ("recipients",),
- (
- "add_pusher",
- "notify_only_broken_builds",
- "branches_to_be_notified",
- "notify_only_default_branch",
- "pipeline_events",
- ),
- ),
- "external-wiki": (("external_wiki_url",), tuple()),
- "flowdock": (("token",), ("push_events",)),
- "github": (("token", "repository_url"), ("static_context",)),
- "hangouts-chat": (
- ("webhook",),
- (
- "notify_only_broken_pipelines",
- "notify_only_default_branch",
- "branches_to_be_notified",
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "confidential_note_events",
- "pipeline_events",
- "wiki_page_events",
- ),
- ),
- "hipchat": (
- ("token",),
- (
- "color",
- "notify",
- "room",
- "api_version",
- "server",
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "confidential_note_events",
- "pipeline_events",
- ),
- ),
- "irker": (
- ("recipients",),
- (
- "default_irc_uri",
- "server_port",
- "server_host",
- "colorize_messages",
- "push_events",
- ),
- ),
- "jira": (
- (
- "url",
- "username",
- "password",
- ),
- (
- "api_url",
- "active",
- "jira_issue_transition_id",
- "commit_events",
- "merge_requests_events",
- "comment_on_event_enabled",
- ),
- ),
- "slack-slash-commands": (("token",), tuple()),
- "mattermost-slash-commands": (("token",), ("username",)),
- "packagist": (
- ("username", "token"),
- ("server", "push_events", "merge_requests_events", "tag_push_events"),
- ),
- "mattermost": (
- ("webhook",),
- (
- "username",
- "channel",
- "notify_only_broken_pipelines",
- "notify_only_default_branch",
- "branches_to_be_notified",
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "confidential_note_events",
- "pipeline_events",
- "wiki_page_events",
- "push_channel",
- "issue_channel",
- "confidential_issue_channel" "merge_request_channel",
- "note_channel",
- "confidential_note_channel",
- "tag_push_channel",
- "pipeline_channel",
- "wiki_page_channel",
- ),
- ),
- "pivotaltracker": (("token",), ("restrict_to_branch", "push_events")),
- "prometheus": (("api_url",), tuple()),
- "pushover": (
- ("api_key", "user_key", "priority"),
- ("device", "sound", "push_events"),
- ),
- "redmine": (
- ("new_issue_url", "project_url", "issues_url"),
- ("description", "push_events"),
- ),
- "slack": (
- ("webhook",),
- (
- "username",
- "channel",
- "notify_only_broken_pipelines",
- "notify_only_default_branch",
- "branches_to_be_notified",
- "commit_events",
- "confidential_issue_channel",
- "confidential_issues_events",
- "confidential_note_channel",
- "confidential_note_events",
- "deployment_channel",
- "deployment_events",
- "issue_channel",
- "issues_events",
- "job_events",
- "merge_request_channel",
- "merge_requests_events",
- "note_channel",
- "note_events",
- "pipeline_channel",
- "pipeline_events",
- "push_channel",
- "push_events",
- "tag_push_channel",
- "tag_push_events",
- "wiki_page_channel",
- "wiki_page_events",
- ),
- ),
- "microsoft-teams": (
- ("webhook",),
- (
- "notify_only_broken_pipelines",
- "notify_only_default_branch",
- "branches_to_be_notified",
- "push_events",
- "issues_events",
- "confidential_issues_events",
- "merge_requests_events",
- "tag_push_events",
- "note_events",
- "confidential_note_events",
- "pipeline_events",
- "wiki_page_events",
- ),
- ),
- "teamcity": (
- ("teamcity_url", "build_type", "username", "password"),
- ("push_events",),
- ),
- "jenkins": (("jenkins_url", "project_name"), ("username", "password")),
- "mock-ci": (("mock_service_url",), tuple()),
- "youtrack": (("issues_url", "project_url"), ("description", "push_events")),
- }
-
- def get(self, id, **kwargs):
- """Retrieve a single object.
-
- Args:
- id (int or str): ID of the object to retrieve
- lazy (bool): If True, don't request the server, but create a
- shallow object giving access to the managers. This is
- useful if you want to avoid useless calls to the API.
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Returns:
- object: The generated RESTObject.
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabGetError: If the server cannot perform the request
- """
- obj = super(ProjectServiceManager, self).get(id, **kwargs)
- obj.id = id
- return obj
-
- 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 {}
- super(ProjectServiceManager, self).update(id, new_data, **kwargs)
- self.id = id
-
- @cli.register_custom_action("ProjectServiceManager")
- def available(self, **kwargs):
- """List the services known by python-gitlab.
-
- Returns:
- list (str): The list of service code names.
- """
- return list(self._service_attrs.keys())
-
-
-class ProjectAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
- _path = "/projects/%(project_id)s/access_requests"
- _obj_cls = ProjectAccessRequest
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectApproval(SaveMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/approvals"
- _obj_cls = ProjectApproval
- _from_parent_attrs = {"project_id": "id"}
- _update_attrs = (
- tuple(),
- (
- "approvals_before_merge",
- "reset_approvals_on_push",
- "disable_overriding_approvers_per_merge_request",
- "merge_requests_author_approval",
- "merge_requests_disable_committers_approval",
- ),
- )
- _update_uses_post = True
-
- @exc.on_http_error(exc.GitlabUpdateError)
- def set_approvers(self, approver_ids=None, approver_group_ids=None, **kwargs):
- """Change project-level allowed approvers and approver groups.
-
- Args:
- approver_ids (list): User IDs that can approve MRs
- approver_group_ids (list): Group IDs whose members can approve MRs
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabUpdateError: If the server failed to perform the request
- """
- approver_ids = approver_ids or []
- approver_group_ids = approver_group_ids or []
-
- path = "/projects/%s/approvers" % self._parent.get_id()
- data = {"approver_ids": approver_ids, "approver_group_ids": approver_group_ids}
- self.gitlab.http_put(path, post_data=data, **kwargs)
-
-
-class ProjectApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject):
- _id_attr = "id"
-
-
-class ProjectApprovalRuleManager(
- ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
-):
- _path = "/projects/%(project_id)s/approval_rules"
- _obj_cls = ProjectApprovalRule
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("name", "approvals_required"), ("user_ids", "group_ids"))
-
-
-class ProjectDeployment(RESTObject, SaveMixin):
- pass
-
-
-class ProjectDeploymentManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/deployments"
- _obj_cls = ProjectDeployment
- _from_parent_attrs = {"project_id": "id"}
- _list_filters = ("order_by", "sort")
- _create_attrs = (("sha", "ref", "tag", "status", "environment"), tuple())
-
-
-class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject):
- _id_attr = "name"
-
-
-class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/protected_branches"
- _obj_cls = ProjectProtectedBranch
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (
- ("name",),
- (
- "push_access_level",
- "merge_access_level",
- "unprotect_access_level",
- "allowed_to_push",
- "allowed_to_merge",
- "allowed_to_unprotect",
- ),
- )
-
-
-class ProjectRunner(ObjectDeleteMixin, RESTObject):
- pass
-
-
-class ProjectRunnerManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/runners"
- _obj_cls = ProjectRunner
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("runner_id",), tuple())
-
-
-class ProjectWiki(SaveMixin, ObjectDeleteMixin, RESTObject):
- _id_attr = "slug"
- _short_print_attr = "slug"
-
-
-class ProjectWikiManager(CRUDMixin, RESTManager):
- _path = "/projects/%(project_id)s/wikis"
- _obj_cls = ProjectWiki
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("title", "content"), ("format",))
- _update_attrs = (tuple(), ("title", "content", "format"))
- _list_filters = ("with_content",)
-
-
-class ProjectExport(DownloadMixin, RefreshMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager):
- _path = "/projects/%(project_id)s/export"
- _obj_cls = ProjectExport
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (tuple(), ("description",))
-
-
-class ProjectImport(RefreshMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectImportManager(GetWithoutIdMixin, RESTManager):
- _path = "/projects/%(project_id)s/import"
- _obj_cls = ProjectImport
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectAdditionalStatistics(RefreshMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectAdditionalStatisticsManager(GetWithoutIdMixin, RESTManager):
- _path = "/projects/%(project_id)s/statistics"
- _obj_cls = ProjectAdditionalStatistics
- _from_parent_attrs = {"project_id": "id"}
-
-
-class ProjectIssuesStatistics(RefreshMixin, RESTObject):
- _id_attr = None
-
-
-class ProjectIssuesStatisticsManager(GetWithoutIdMixin, RESTManager):
- _path = "/projects/%(project_id)s/issues_statistics"
- _obj_cls = ProjectIssuesStatistics
- _from_parent_attrs = {"project_id": "id"}
-
-
-class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
- _short_print_attr = "path"
- _managers = (
- ("accessrequests", "ProjectAccessRequestManager"),
- ("approvals", "ProjectApprovalManager"),
- ("approvalrules", "ProjectApprovalRuleManager"),
- ("badges", "ProjectBadgeManager"),
- ("boards", "ProjectBoardManager"),
- ("branches", "ProjectBranchManager"),
- ("jobs", "ProjectJobManager"),
- ("commits", "ProjectCommitManager"),
- ("customattributes", "ProjectCustomAttributeManager"),
- ("deployments", "ProjectDeploymentManager"),
- ("environments", "ProjectEnvironmentManager"),
- ("events", "ProjectEventManager"),
- ("exports", "ProjectExportManager"),
- ("files", "ProjectFileManager"),
- ("forks", "ProjectForkManager"),
- ("hooks", "ProjectHookManager"),
- ("keys", "ProjectKeyManager"),
- ("imports", "ProjectImportManager"),
- ("issues", "ProjectIssueManager"),
- ("labels", "ProjectLabelManager"),
- ("members", "ProjectMemberManager"),
- ("mergerequests", "ProjectMergeRequestManager"),
- ("milestones", "ProjectMilestoneManager"),
- ("notes", "ProjectNoteManager"),
- ("notificationsettings", "ProjectNotificationSettingsManager"),
- ("packages", "ProjectPackageManager"),
- ("pagesdomains", "ProjectPagesDomainManager"),
- ("pipelines", "ProjectPipelineManager"),
- ("protectedbranches", "ProjectProtectedBranchManager"),
- ("protectedtags", "ProjectProtectedTagManager"),
- ("pipelineschedules", "ProjectPipelineScheduleManager"),
- ("pushrules", "ProjectPushRulesManager"),
- ("releases", "ProjectReleaseManager"),
- ("remote_mirrors", "ProjectRemoteMirrorManager"),
- ("repositories", "ProjectRegistryRepositoryManager"),
- ("runners", "ProjectRunnerManager"),
- ("services", "ProjectServiceManager"),
- ("snippets", "ProjectSnippetManager"),
- ("tags", "ProjectTagManager"),
- ("users", "ProjectUserManager"),
- ("triggers", "ProjectTriggerManager"),
- ("variables", "ProjectVariableManager"),
- ("wikis", "ProjectWikiManager"),
- ("clusters", "ProjectClusterManager"),
- ("additionalstatistics", "ProjectAdditionalStatisticsManager"),
- ("issuesstatistics", "ProjectIssuesStatisticsManager"),
- ("deploytokens", "ProjectDeployTokenManager"),
- )
-
- @cli.register_custom_action("Project", ("submodule", "branch", "commit_sha"))
- @exc.on_http_error(exc.GitlabUpdateError)
- def update_submodule(self, submodule, branch, commit_sha, **kwargs):
- """Update a project submodule
-
- Args:
- submodule (str): Full path to the submodule
- branch (str): Name of the branch to commit into
- commit_sha (str): Full commit SHA to update the submodule to
- commit_message (str): Commit message. If no message is provided, a default one will be set (optional)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabPutError: If the submodule could not be updated
- """
-
- submodule = submodule.replace("/", "%2F") # .replace('.', '%2E')
- path = "/projects/%s/repository/submodules/%s" % (self.get_id(), submodule)
- data = {"branch": branch, "commit_sha": commit_sha}
- if "commit_message" in kwargs:
- data["commit_message"] = kwargs["commit_message"]
- return self.manager.gitlab.http_put(path, post_data=data)
-
- @cli.register_custom_action("Project", tuple(), ("path", "ref", "recursive"))
- @exc.on_http_error(exc.GitlabGetError)
- def repository_tree(self, path="", ref="", recursive=False, **kwargs):
- """Return a list of files in the repository.
-
- Args:
- path (str): Path of the top folder (/ by default)
- ref (str): Reference to a commit or branch
- recursive (bool): Whether to get the tree recursively
- 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
- GitlabGetError: If the server failed to perform the request
-
- Returns:
- list: The representation of the tree
- """
- gl_path = "/projects/%s/repository/tree" % self.get_id()
- query_data = {"recursive": recursive}
- if path:
- query_data["path"] = path
- if ref:
- query_data["ref"] = ref
- return self.manager.gitlab.http_list(gl_path, query_data=query_data, **kwargs)
-
- @cli.register_custom_action("Project", ("sha",))
- @exc.on_http_error(exc.GitlabGetError)
- def repository_blob(self, sha, **kwargs):
- """Return a file by blob SHA.
-
- Args:
- sha(str): ID of the blob
- **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 blob content and metadata
- """
-
- path = "/projects/%s/repository/blobs/%s" % (self.get_id(), sha)
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("Project", ("sha",))
- @exc.on_http_error(exc.GitlabGetError)
- def repository_raw_blob(
- self, sha, streamed=False, action=None, chunk_size=1024, **kwargs
- ):
- """Return the raw file contents for a blob.
-
- Args:
- sha(str): ID of the blob
- 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
-
- Returns:
- str: The blob content if streamed is False, None otherwise
- """
- path = "/projects/%s/repository/blobs/%s/raw" % (self.get_id(), sha)
- 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", ("from_", "to"))
- @exc.on_http_error(exc.GitlabGetError)
- def repository_compare(self, from_, to, **kwargs):
- """Return a diff between two branches/commits.
-
- Args:
- from_(str): Source branch/SHA
- to(str): Destination branch/SHA
- **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:
- str: The diff
- """
- path = "/projects/%s/repository/compare" % self.get_id()
- query_data = {"from": from_, "to": to}
- return self.manager.gitlab.http_get(path, query_data=query_data, **kwargs)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabGetError)
- def repository_contributors(self, **kwargs):
- """Return a list of contributors for the project.
-
- 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
- GitlabGetError: If the server failed to perform the request
-
- Returns:
- list: The contributors
- """
- path = "/projects/%s/repository/contributors" % self.get_id()
- return self.manager.gitlab.http_list(path, **kwargs)
-
- @cli.register_custom_action("Project", tuple(), ("sha",))
- @exc.on_http_error(exc.GitlabListError)
- def repository_archive(
- self, sha=None, streamed=False, action=None, chunk_size=1024, **kwargs
- ):
- """Return a tarball of the repository.
-
- Args:
- sha (str): ID of the commit (default branch by default)
- 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
-
- Returns:
- str: The binary data of the archive
- """
- path = "/projects/%s/repository/archive" % self.get_id()
- query_data = {}
- if sha:
- query_data["sha"] = sha
- result = self.manager.gitlab.http_get(
- path, query_data=query_data, raw=True, streamed=streamed, **kwargs
- )
- return utils.response_content(result, streamed, action, chunk_size)
-
- @cli.register_custom_action("Project", ("forked_from_id",))
- @exc.on_http_error(exc.GitlabCreateError)
- def create_fork_relation(self, forked_from_id, **kwargs):
- """Create a forked from/to relation between existing projects.
-
- Args:
- forked_from_id (int): The ID of the project that was forked from
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabCreateError: If the relation could not be created
- """
- path = "/projects/%s/fork/%s" % (self.get_id(), forked_from_id)
- self.manager.gitlab.http_post(path, **kwargs)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabDeleteError)
- def delete_fork_relation(self, **kwargs):
- """Delete a forked relation between existing projects.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the server failed to perform the request
- """
- path = "/projects/%s/fork" % self.get_id()
- self.manager.gitlab.http_delete(path, **kwargs)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabDeleteError)
- def delete_merged_branches(self, **kwargs):
- """Delete merged branches.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the server failed to perform the request
- """
- path = "/projects/%s/repository/merged_branches" % self.get_id()
- self.manager.gitlab.http_delete(path, **kwargs)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabGetError)
- def languages(self, **kwargs):
- """Get languages used in the project with percentage value.
-
- 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
- """
- path = "/projects/%s/languages" % self.get_id()
- return self.manager.gitlab.http_get(path, **kwargs)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabCreateError)
- def star(self, **kwargs):
- """Star a 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/star" % self.get_id()
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabDeleteError)
- def unstar(self, **kwargs):
- """Unstar a project.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the server failed to perform the request
- """
- path = "/projects/%s/unstar" % self.get_id()
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabCreateError)
- def archive(self, **kwargs):
- """Archive a 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/archive" % self.get_id()
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabDeleteError)
- def unarchive(self, **kwargs):
- """Unarchive a project.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the server failed to perform the request
- """
- path = "/projects/%s/unarchive" % self.get_id()
- server_data = self.manager.gitlab.http_post(path, **kwargs)
- self._update_attrs(server_data)
-
- @cli.register_custom_action(
- "Project", ("group_id", "group_access"), ("expires_at",)
- )
- @exc.on_http_error(exc.GitlabCreateError)
- def share(self, group_id, group_access, expires_at=None, **kwargs):
- """Share the project with a group.
-
- Args:
- group_id (int): ID of the group.
- group_access (int): Access level for the group.
- **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/share" % self.get_id()
- data = {
- "group_id": group_id,
- "group_access": group_access,
- "expires_at": expires_at,
- }
- self.manager.gitlab.http_post(path, post_data=data, **kwargs)
-
- @cli.register_custom_action("Project", ("group_id",))
- @exc.on_http_error(exc.GitlabDeleteError)
- def unshare(self, group_id, **kwargs):
- """Delete a shared project link within a group.
-
- Args:
- group_id (int): ID of the group.
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabDeleteError: If the server failed to perform the request
- """
- path = "/projects/%s/share/%s" % (self.get_id(), group_id)
- self.manager.gitlab.http_delete(path, **kwargs)
-
- # variables not supported in CLI
- @cli.register_custom_action("Project", ("ref", "token"))
- @exc.on_http_error(exc.GitlabCreateError)
- def trigger_pipeline(self, ref, token, variables=None, **kwargs):
- """Trigger a CI build.
-
- See https://gitlab.com/help/ci/triggers/README.md#trigger-a-build
-
- Args:
- ref (str): Commit to build; can be a branch name or a tag
- token (str): The trigger token
- variables (dict): Variables passed to the build script
- **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
- """
- variables = variables or {}
- path = "/projects/%s/trigger/pipeline" % self.get_id()
- post_data = {"ref": ref, "token": token, "variables": variables}
- attrs = self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
- return ProjectPipeline(self.pipelines, attrs)
-
- @cli.register_custom_action("Project")
- @exc.on_http_error(exc.GitlabHousekeepingError)
- def housekeeping(self, **kwargs):
- """Start the housekeeping task.
-
- Args:
- **kwargs: Extra options to send to the server (e.g. sudo)
-
- Raises:
- GitlabAuthenticationError: If authentication is not correct
- GitlabHousekeepingError: If the server failed to perform the
- request
- """
- path = "/projects/%s/housekeeping" % self.get_id()
- self.manager.gitlab.http_post(path, **kwargs)
-
- # see #56 - add file attachment features
- @cli.register_custom_action("Project", ("filename", "filepath"))
- @exc.on_http_error(exc.GitlabUploadError)
- def upload(self, filename, filedata=None, filepath=None, **kwargs):
- """Upload the specified file into the project.
-
- .. note::
-
- Either ``filedata`` or ``filepath`` *MUST* be specified.
-
- Args:
- filename (str): The name of the file being uploaded
- filedata (bytes): The raw data of the file being uploaded
- filepath (str): The path to a local file to upload (optional)
-
- Raises:
- GitlabConnectionError: If the server cannot be reached
- GitlabUploadError: If the file upload fails
- GitlabUploadError: If ``filedata`` and ``filepath`` are not
- specified
- GitlabUploadError: If both ``filedata`` and ``filepath`` are
- specified
-
- Returns:
- dict: A ``dict`` with the keys:
- * ``alt`` - The alternate text for the upload
- * ``url`` - The direct url to the uploaded file
- * ``markdown`` - Markdown for the uploaded file
- """
- if filepath is None and filedata is None:
- raise GitlabUploadError("No file contents or path specified")
-
- if filedata is not None and filepath is not None:
- raise GitlabUploadError("File contents and file path specified")
-
- if filepath is not None:
- with open(filepath, "rb") as f:
- filedata = f.read()
-
- url = "/projects/%(id)s/uploads" % {"id": self.id}
- file_info = {"file": (filename, filedata)}
- data = self.manager.gitlab.http_post(url, files=file_info)
-
- 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", "job"), ("job_token",))
- @exc.on_http_error(exc.GitlabGetError)
- def artifacts(
- self, ref_name, job, streamed=False, action=None, chunk_size=1024, **kwargs
- ):
- """Get the job artifacts archive from a specific tag or branch.
-
- 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.
- job_token (str): Job token for multi-project pipeline triggers.
- 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/download" % (self.get_id(), ref_name)
- result = self.manager.gitlab.http_get(
- path, job=job, streamed=streamed, raw=True, **kwargs
- )
- return utils.response_content(result, streamed, action, chunk_size)
-
- @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"
- _obj_cls = Project
- _create_attrs = (
- tuple(),
- (
- "name",
- "path",
- "namespace_id",
- "default_branch",
- "description",
- "issues_enabled",
- "merge_requests_enabled",
- "jobs_enabled",
- "wiki_enabled",
- "snippets_enabled",
- "issues_access_level",
- "repository_access_level",
- "merge_requests_access_level",
- "forking_access_level",
- "builds_access_level",
- "wiki_access_level",
- "snippets_access_level",
- "pages_access_level",
- "emails_disabled",
- "resolve_outdated_diff_discussions",
- "container_registry_enabled",
- "container_expiration_policy_attributes",
- "shared_runners_enabled",
- "visibility",
- "import_url",
- "public_builds",
- "only_allow_merge_if_pipeline_succeeds",
- "only_allow_merge_if_all_discussions_are_resolved",
- "merge_method",
- "autoclose_referenced_issues",
- "remove_source_branch_after_merge",
- "lfs_enabled",
- "request_access_enabled",
- "tag_list",
- "avatar",
- "printing_merge_request_link_enabled",
- "build_git_strategy",
- "build_timeout",
- "auto_cancel_pending_pipelines",
- "build_coverage_regex",
- "ci_config_path",
- "auto_devops_enabled",
- "auto_devops_deploy_strategy",
- "repository_storage",
- "approvals_before_merge",
- "external_authorization_classification_label",
- "mirror",
- "mirror_trigger_builds",
- "initialize_with_readme",
- "template_name",
- "template_project_id",
- "use_custom_template",
- "group_with_project_templates_id",
- "packages_enabled",
- ),
- )
- _update_attrs = (
- tuple(),
- (
- "name",
- "path",
- "default_branch",
- "description",
- "issues_enabled",
- "merge_requests_enabled",
- "jobs_enabled",
- "wiki_enabled",
- "snippets_enabled",
- "issues_access_level",
- "repository_access_level",
- "merge_requests_access_level",
- "forking_access_level",
- "builds_access_level",
- "wiki_access_level",
- "snippets_access_level",
- "pages_access_level",
- "emails_disabled",
- "resolve_outdated_diff_discussions",
- "container_registry_enabled",
- "container_expiration_policy_attributes",
- "shared_runners_enabled",
- "visibility",
- "import_url",
- "public_builds",
- "only_allow_merge_if_pipeline_succeeds",
- "only_allow_merge_if_all_discussions_are_resolved",
- "merge_method",
- "autoclose_referenced_issues",
- "suggestion_commit_message",
- "remove_source_branch_after_merge",
- "lfs_enabled",
- "request_access_enabled",
- "tag_list",
- "avatar",
- "build_git_strategy",
- "build_timeout",
- "auto_cancel_pending_pipelines",
- "build_coverage_regex",
- "ci_config_path",
- "ci_default_git_depth",
- "auto_devops_enabled",
- "auto_devops_deploy_strategy",
- "repository_storage",
- "approvals_before_merge",
- "external_authorization_classification_label",
- "mirror",
- "mirror_user_id",
- "mirror_trigger_builds",
- "only_mirror_protected_branches",
- "mirror_overwrites_diverged_branches",
- "packages_enabled",
- "service_desk_enabled",
- ),
- )
- _types = {"avatar": types.ImageAttribute}
- _list_filters = (
- "archived",
- "id_after",
- "id_before",
- "last_activity_after",
- "last_activity_before",
- "membership",
- "min_access_level",
- "order_by",
- "owned",
- "repository_checksum_failed",
- "repository_storage",
- "search_namespaces",
- "search",
- "simple",
- "sort",
- "starred",
- "statistics",
- "visibility",
- "wiki_checksum_failed",
- "with_custom_attributes",
- "with_issues_enabled",
- "with_merge_requests_enabled",
- "with_programming_language",
- )
-
- def import_project(
- self,
- file,
- path,
- name=None,
- namespace=None,
- overwrite=False,
- override_params=None,
- **kwargs
- ):
- """Import a project from an archive file.
-
- Args:
- file: Data or file object containing the project
- path (str): Name and path for the new project
- namespace (str): The ID or path of the namespace that the project
- will be imported to
- overwrite (bool): If True overwrite an existing project with the
- same path
- override_params (dict): Set the specific settings for the project
- **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:
- dict: A representation of the import status.
- """
- files = {"file": ("file.tar.gz", file, "application/octet-stream")}
- data = {"path": path, "overwrite": str(overwrite)}
- if override_params:
- for k, v in override_params.items():
- data["override_params[%s]" % k] = v
- if name is not None:
- data["name"] = name
- if namespace:
- data["namespace"] = namespace
- return self.gitlab.http_post(
- "/projects/import", post_data=data, files=files, **kwargs
- )
-
- def import_bitbucket_server(
- self,
- bitbucket_server_url,
- bitbucket_server_username,
- personal_access_token,
- bitbucket_server_project,
- bitbucket_server_repo,
- new_name=None,
- target_namespace=None,
- **kwargs
- ):
- """Import a project from BitBucket Server to Gitlab (schedule the import)
-
- This method will return when an import operation has been safely queued,
- or an error has occurred. After triggering an import, check the
- `import_status` of the newly created project to detect when the import
- operation has completed.
-
- NOTE: this request may take longer than most other API requests.
- So this method will specify a 60 second default timeout if none is specified.
- A timeout can be specified via kwargs to override this functionality.
-
- Args:
- bitbucket_server_url (str): Bitbucket Server URL
- bitbucket_server_username (str): Bitbucket Server Username
- personal_access_token (str): Bitbucket Server personal access
- token/password
- bitbucket_server_project (str): Bitbucket Project Key
- bitbucket_server_repo (str): Bitbucket Repository Name
- new_name (str): New repository name (Optional)
- target_namespace (str): Namespace to import repository into.
- Supports subgroups like /namespace/subgroup (Optional)
- **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:
- dict: A representation of the import status.
-
- Example:
- ```
- gl = gitlab.Gitlab_from_config()
- print("Triggering import")
- result = gl.projects.import_bitbucket_server(
- bitbucket_server_url="https://some.server.url",
- bitbucket_server_username="some_bitbucket_user",
- personal_access_token="my_password_or_access_token",
- bitbucket_server_project="my_project",
- bitbucket_server_repo="my_repo",
- new_name="gl_project_name",
- target_namespace="gl_project_path"
- )
- project = gl.projects.get(ret['id'])
- print("Waiting for import to complete")
- while project.import_status == u'started':
- time.sleep(1.0)
- project = gl.projects.get(project.id)
- print("BitBucket import complete")
- ```
- """
- data = {
- "bitbucket_server_url": bitbucket_server_url,
- "bitbucket_server_username": bitbucket_server_username,
- "personal_access_token": personal_access_token,
- "bitbucket_server_project": bitbucket_server_project,
- "bitbucket_server_repo": bitbucket_server_repo,
- }
- if new_name:
- data["new_name"] = new_name
- if target_namespace:
- data["target_namespace"] = target_namespace
- if (
- "timeout" not in kwargs
- or self.gitlab.timeout is None
- or self.gitlab.timeout < 60.0
- ):
- # Ensure that this HTTP request has a longer-than-usual default timeout
- # The base gitlab object tends to have a default that is <10 seconds,
- # and this is too short for this API command, typically.
- # On the order of 24 seconds has been measured on a typical gitlab instance.
- kwargs["timeout"] = 60.0
- result = self.gitlab.http_post(
- "/import/bitbucket_server", post_data=data, **kwargs
- )
- return result
-
- def import_github(
- self, personal_access_token, repo_id, target_namespace, new_name=None, **kwargs
- ):
- """Import a project from Github to Gitlab (schedule the import)
-
- This method will return when an import operation has been safely queued,
- or an error has occurred. After triggering an import, check the
- `import_status` of the newly created project to detect when the import
- operation has completed.
-
- NOTE: this request may take longer than most other API requests.
- So this method will specify a 60 second default timeout if none is specified.
- A timeout can be specified via kwargs to override this functionality.
-
- Args:
- personal_access_token (str): GitHub personal access token
- repo_id (int): Github repository ID
- target_namespace (str): Namespace to import repo into
- new_name (str): New repo name (Optional)
- **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:
- dict: A representation of the import status.
-
- Example:
- ```
- gl = gitlab.Gitlab_from_config()
- print("Triggering import")
- result = gl.projects.import_github(ACCESS_TOKEN,
- 123456,
- "my-group/my-subgroup")
- project = gl.projects.get(ret['id'])
- print("Waiting for import to complete")
- while project.import_status == u'started':
- time.sleep(1.0)
- project = gl.projects.get(project.id)
- print("Github import complete")
- ```
- """
- data = {
- "personal_access_token": personal_access_token,
- "repo_id": repo_id,
- "target_namespace": target_namespace,
- }
- if new_name:
- data["new_name"] = new_name
- if (
- "timeout" not in kwargs
- or self.gitlab.timeout is None
- or self.gitlab.timeout < 60.0
- ):
- # Ensure that this HTTP request has a longer-than-usual default timeout
- # The base gitlab object tends to have a default that is <10 seconds,
- # and this is too short for this API command, typically.
- # On the order of 24 seconds has been measured on a typical gitlab instance.
- kwargs["timeout"] = 60.0
- result = self.gitlab.http_post("/import/github", post_data=data, **kwargs)
- 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 RunnerManager(CRUDMixin, RESTManager):
- _path = "/runners"
- _obj_cls = Runner
- _list_filters = ("scope",)
- _create_attrs = (
- ("token",),
- (
- "description",
- "info",
- "active",
- "locked",
- "run_untagged",
- "tag_list",
- "access_level",
- "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
- obj = self.gitlab.http_list(path, query_data, **kwargs)
- return [self._obj_cls(self, item) for item in obj]
-
- @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
- """
- 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)
-
-
-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 Application(ObjectDeleteMixin, RESTObject):
- _url = "/applications"
- _short_print_attr = "name"
-
-
-class ApplicationManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
- _path = "/applications"
- _obj_cls = Application
- _create_attrs = (("name", "redirect_uri", "scopes"), ("confidential",))
diff --git a/gitlab/v4/objects/access_requests.py b/gitlab/v4/objects/access_requests.py
new file mode 100644
index 0000000..2acae50
--- /dev/null
+++ b/gitlab/v4/objects/access_requests.py
@@ -0,0 +1,22 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/access_requests"
+ _obj_cls = ProjectAccessRequest
+ _from_parent_attrs = {"project_id": "id"}
diff --git a/gitlab/v4/objects/appearance.py b/gitlab/v4/objects/appearance.py
new file mode 100644
index 0000000..4854e2a
--- /dev/null
+++ b/gitlab/v4/objects/appearance.py
@@ -0,0 +1,48 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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)
diff --git a/gitlab/v4/objects/applications.py b/gitlab/v4/objects/applications.py
new file mode 100644
index 0000000..3fa1983
--- /dev/null
+++ b/gitlab/v4/objects/applications.py
@@ -0,0 +1,13 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class Application(ObjectDeleteMixin, RESTObject):
+ _url = "/applications"
+ _short_print_attr = "name"
+
+
+class ApplicationManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
+ _path = "/applications"
+ _obj_cls = Application
+ _create_attrs = (("name", "redirect_uri", "scopes"), ("confidential",))
diff --git a/gitlab/v4/objects/award_emojis.py b/gitlab/v4/objects/award_emojis.py
new file mode 100644
index 0000000..fe87109
--- /dev/null
+++ b/gitlab/v4/objects/award_emojis.py
@@ -0,0 +1,88 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectIssueAwardEmoji(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectIssueAwardEmojiManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues/%(issue_iid)s/award_emoji"
+ _obj_cls = ProjectIssueAwardEmoji
+ _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
+ _create_attrs = (("name",), tuple())
+
+
+class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin, RESTManager):
+ _path = (
+ "/projects/%(project_id)s/issues/%(issue_iid)s" "/notes/%(note_id)s/award_emoji"
+ )
+ _obj_cls = ProjectIssueNoteAwardEmoji
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "issue_iid": "issue_iid",
+ "note_id": "id",
+ }
+ _create_attrs = (("name",), tuple())
+
+
+class ProjectMergeRequestAwardEmoji(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectMergeRequestAwardEmojiManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/award_emoji"
+ _obj_cls = ProjectMergeRequestAwardEmoji
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+ _create_attrs = (("name",), tuple())
+
+
+class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectMergeRequestNoteAwardEmojiManager(NoUpdateMixin, RESTManager):
+ _path = (
+ "/projects/%(project_id)s/merge_requests/%(mr_iid)s"
+ "/notes/%(note_id)s/award_emoji"
+ )
+ _obj_cls = ProjectMergeRequestNoteAwardEmoji
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "mr_iid": "mr_iid",
+ "note_id": "id",
+ }
+ _create_attrs = (("name",), tuple())
+
+
+class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectSnippetAwardEmojiManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/award_emoji"
+ _obj_cls = ProjectSnippetAwardEmoji
+ _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"}
+ _create_attrs = (("name",), tuple())
+
+
+class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin, RESTManager):
+ _path = (
+ "/projects/%(project_id)s/snippets/%(snippet_id)s"
+ "/notes/%(note_id)s/award_emoji"
+ )
+ _obj_cls = ProjectSnippetNoteAwardEmoji
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "snippet_id": "snippet_id",
+ "note_id": "id",
+ }
+ _create_attrs = (("name",), tuple())
diff --git a/gitlab/v4/objects/badges.py b/gitlab/v4/objects/badges.py
new file mode 100644
index 0000000..5e11354
--- /dev/null
+++ b/gitlab/v4/objects/badges.py
@@ -0,0 +1,26 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectBadge(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/badges"
+ _obj_cls = ProjectBadge
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("link_url", "image_url"), tuple())
+ _update_attrs = (tuple(), ("link_url", "image_url"))
diff --git a/gitlab/v4/objects/boards.py b/gitlab/v4/objects/boards.py
new file mode 100644
index 0000000..cd5aa14
--- /dev/null
+++ b/gitlab/v4/objects/boards.py
@@ -0,0 +1,48 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectBoardListManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/boards/%(board_id)s/lists"
+ _obj_cls = ProjectBoardList
+ _from_parent_attrs = {"project_id": "project_id", "board_id": "id"}
+ _create_attrs = (("label_id",), tuple())
+ _update_attrs = (("position",), tuple())
+
+
+class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _managers = (("lists", "ProjectBoardListManager"),)
+
+
+class ProjectBoardManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/boards"
+ _obj_cls = ProjectBoard
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("name",), tuple())
diff --git a/gitlab/v4/objects/branches.py b/gitlab/v4/objects/branches.py
new file mode 100644
index 0000000..c6ff1e8
--- /dev/null
+++ b/gitlab/v4/objects/branches.py
@@ -0,0 +1,80 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectBranch(ObjectDeleteMixin, RESTObject):
+ _id_attr = "name"
+
+ @cli.register_custom_action(
+ "ProjectBranch", tuple(), ("developers_can_push", "developers_can_merge")
+ )
+ @exc.on_http_error(exc.GitlabProtectError)
+ def protect(self, developers_can_push=False, developers_can_merge=False, **kwargs):
+ """Protect the branch.
+
+ Args:
+ developers_can_push (bool): Set to True if developers are allowed
+ to push to the branch
+ developers_can_merge (bool): Set to True if developers are allowed
+ to merge to the branch
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabProtectError: If the branch could not be protected
+ """
+ id = self.get_id().replace("/", "%2F")
+ path = "%s/%s/protect" % (self.manager.path, id)
+ post_data = {
+ "developers_can_push": developers_can_push,
+ "developers_can_merge": developers_can_merge,
+ }
+ self.manager.gitlab.http_put(path, post_data=post_data, **kwargs)
+ self._attrs["protected"] = True
+
+ @cli.register_custom_action("ProjectBranch")
+ @exc.on_http_error(exc.GitlabProtectError)
+ def unprotect(self, **kwargs):
+ """Unprotect the branch.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabProtectError: If the branch could not be unprotected
+ """
+ id = self.get_id().replace("/", "%2F")
+ path = "%s/%s/unprotect" % (self.manager.path, id)
+ self.manager.gitlab.http_put(path, **kwargs)
+ self._attrs["protected"] = False
+
+
+class ProjectBranchManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/branches"
+ _obj_cls = ProjectBranch
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("branch", "ref"), tuple())
+
+
+class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject):
+ _id_attr = "name"
+
+
+class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/protected_branches"
+ _obj_cls = ProjectProtectedBranch
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ ("name",),
+ (
+ "push_access_level",
+ "merge_access_level",
+ "unprotect_access_level",
+ "allowed_to_push",
+ "allowed_to_merge",
+ "allowed_to_unprotect",
+ ),
+ )
diff --git a/gitlab/v4/objects/broadcast_messages.py b/gitlab/v4/objects/broadcast_messages.py
new file mode 100644
index 0000000..66933a1
--- /dev/null
+++ b/gitlab/v4/objects/broadcast_messages.py
@@ -0,0 +1,14 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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/clusters.py b/gitlab/v4/objects/clusters.py
new file mode 100644
index 0000000..d136365
--- /dev/null
+++ b/gitlab/v4/objects/clusters.py
@@ -0,0 +1,93 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectCluster(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectClusterManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/clusters"
+ _obj_cls = ProjectCluster
+ _from_parent_attrs = {"project_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)
diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py
new file mode 100644
index 0000000..3f2232b
--- /dev/null
+++ b/gitlab/v4/objects/commits.py
@@ -0,0 +1,189 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .discussions import ProjectCommitDiscussionManager
+
+
+class ProjectCommit(RESTObject):
+ _short_print_attr = "title"
+ _managers = (
+ ("comments", "ProjectCommitCommentManager"),
+ ("discussions", "ProjectCommitDiscussionManager"),
+ ("statuses", "ProjectCommitStatusManager"),
+ )
+
+ @cli.register_custom_action("ProjectCommit")
+ @exc.on_http_error(exc.GitlabGetError)
+ def diff(self, **kwargs):
+ """Generate the commit diff.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the diff could not be retrieved
+
+ Returns:
+ list: The changes done in this commit
+ """
+ path = "%s/%s/diff" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("ProjectCommit", ("branch",))
+ @exc.on_http_error(exc.GitlabCherryPickError)
+ def cherry_pick(self, branch, **kwargs):
+ """Cherry-pick a commit into a branch.
+
+ Args:
+ branch (str): Name of target branch
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCherryPickError: If the cherry-pick could not be performed
+ """
+ path = "%s/%s/cherry_pick" % (self.manager.path, self.get_id())
+ post_data = {"branch": branch}
+ self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
+
+ @cli.register_custom_action("ProjectCommit", optional=("type",))
+ @exc.on_http_error(exc.GitlabGetError)
+ def refs(self, type="all", **kwargs):
+ """List the references the commit is pushed to.
+
+ Args:
+ type (str): The scope of references ('branch', 'tag' or 'all')
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the references could not be retrieved
+
+ Returns:
+ list: The references the commit is pushed to.
+ """
+ path = "%s/%s/refs" % (self.manager.path, self.get_id())
+ data = {"type": type}
+ return self.manager.gitlab.http_get(path, query_data=data, **kwargs)
+
+ @cli.register_custom_action("ProjectCommit")
+ @exc.on_http_error(exc.GitlabGetError)
+ def merge_requests(self, **kwargs):
+ """List the merge requests related to the commit.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the references could not be retrieved
+
+ Returns:
+ list: The merge requests related to the commit.
+ """
+ path = "%s/%s/merge_requests" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("ProjectCommit", ("branch",))
+ @exc.on_http_error(exc.GitlabRevertError)
+ def revert(self, branch, **kwargs):
+ """Revert a commit on a given branch.
+
+ Args:
+ branch (str): Name of target branch
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabRevertError: If the revert could not be performed
+
+ Returns:
+ dict: The new commit data (*not* a RESTObject)
+ """
+ path = "%s/%s/revert" % (self.manager.path, self.get_id())
+ post_data = {"branch": branch}
+ return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
+
+ @cli.register_custom_action("ProjectCommit")
+ @exc.on_http_error(exc.GitlabGetError)
+ def signature(self, **kwargs):
+ """Get the signature of the commit.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the signature could not be retrieved
+
+ Returns:
+ dict: The commit's signature data
+ """
+ path = "%s/%s/signature" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+
+class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/commits"
+ _obj_cls = ProjectCommit
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ ("branch", "commit_message", "actions"),
+ ("author_email", "author_name"),
+ )
+
+
+class ProjectCommitComment(RESTObject):
+ _id_attr = None
+ _short_print_attr = "note"
+
+
+class ProjectCommitCommentManager(ListMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/comments"
+ _obj_cls = ProjectCommitComment
+ _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"}
+ _create_attrs = (("note",), ("path", "line", "line_type"))
+
+
+class ProjectCommitStatus(RESTObject, RefreshMixin):
+ pass
+
+
+class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/statuses"
+ _obj_cls = ProjectCommitStatus
+ _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"}
+ _create_attrs = (
+ ("state",),
+ ("description", "name", "context", "ref", "target_url", "coverage"),
+ )
+
+ @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 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
+ """
+ # project_id and commit_id are in the data dict when using the CLI, but
+ # they are missing when using only the API
+ # See #511
+ base_path = "/projects/%(project_id)s/statuses/%(commit_id)s"
+ if "project_id" in data and "commit_id" in data:
+ path = base_path % data
+ else:
+ path = self._compute_path(base_path)
+ return CreateMixin.create(self, data, path=path, **kwargs)
diff --git a/gitlab/v4/objects/container_registry.py b/gitlab/v4/objects/container_registry.py
new file mode 100644
index 0000000..a6d0983
--- /dev/null
+++ b/gitlab/v4/objects/container_registry.py
@@ -0,0 +1,47 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject):
+ _managers = (("tags", "ProjectRegistryTagManager"),)
+
+
+class ProjectRegistryRepositoryManager(DeleteMixin, ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/registry/repositories"
+ _obj_cls = ProjectRegistryRepository
+ _from_parent_attrs = {"project_id": "id"}
+
+
+class ProjectRegistryTag(ObjectDeleteMixin, RESTObject):
+ _id_attr = "name"
+
+
+class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager):
+ _obj_cls = ProjectRegistryTag
+ _from_parent_attrs = {"project_id": "project_id", "repository_id": "id"}
+ _path = "/projects/%(project_id)s/registry/repositories/%(repository_id)s/tags"
+
+ @cli.register_custom_action(
+ "ProjectRegistryTagManager", optional=("name_regex", "keep_n", "older_than")
+ )
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def delete_in_bulk(self, name_regex=".*", **kwargs):
+ """Delete Tag in bulk
+
+ Args:
+ name_regex (string): The regex of the name to delete. To delete all
+ tags specify .*.
+ keep_n (integer): The amount of latest tags of given name to keep.
+ older_than (string): Tags to delete that are older than the given time,
+ written in human readable form 1h, 1d, 1month.
+ **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
+ """
+ valid_attrs = ["keep_n", "older_than"]
+ data = {"name_regex": name_regex}
+ data.update({k: v for k, v in kwargs.items() if k in valid_attrs})
+ self.gitlab.http_delete(self.path, query_data=data, **kwargs)
diff --git a/gitlab/v4/objects/custom_attributes.py b/gitlab/v4/objects/custom_attributes.py
new file mode 100644
index 0000000..3a86072
--- /dev/null
+++ b/gitlab/v4/objects/custom_attributes.py
@@ -0,0 +1,32 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectCustomAttribute(ObjectDeleteMixin, RESTObject):
+ _id_attr = "key"
+
+
+class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/custom_attributes"
+ _obj_cls = ProjectCustomAttribute
+ _from_parent_attrs = {"project_id": "id"}
+
+
+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"}
diff --git a/gitlab/v4/objects/deploy_keys.py b/gitlab/v4/objects/deploy_keys.py
new file mode 100644
index 0000000..9143fc2
--- /dev/null
+++ b/gitlab/v4/objects/deploy_keys.py
@@ -0,0 +1,41 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class DeployKey(RESTObject):
+ pass
+
+
+class DeployKeyManager(ListMixin, RESTManager):
+ _path = "/deploy_keys"
+ _obj_cls = DeployKey
+
+
+class ProjectKey(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectKeyManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/deploy_keys"
+ _obj_cls = ProjectKey
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("title", "key"), ("can_push",))
+ _update_attrs = (tuple(), ("title", "can_push"))
+
+ @cli.register_custom_action("ProjectKeyManager", ("key_id",))
+ @exc.on_http_error(exc.GitlabProjectDeployKeyError)
+ def enable(self, key_id, **kwargs):
+ """Enable a deploy key for a project.
+
+ Args:
+ key_id (int): The ID of the key to enable
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabProjectDeployKeyError: If the key could not be enabled
+ """
+ path = "%s/%s/enable" % (self.path, key_id)
+ self.gitlab.http_post(path, **kwargs)
diff --git a/gitlab/v4/objects/deploy_tokens.py b/gitlab/v4/objects/deploy_tokens.py
new file mode 100644
index 0000000..43f8299
--- /dev/null
+++ b/gitlab/v4/objects/deploy_tokens.py
@@ -0,0 +1,51 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class DeployToken(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class DeployTokenManager(ListMixin, RESTManager):
+ _path = "/deploy_tokens"
+ _obj_cls = DeployToken
+
+
+class GroupDeployToken(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class GroupDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
+ _path = "/groups/%(group_id)s/deploy_tokens"
+ _from_parent_attrs = {"group_id": "id"}
+ _obj_cls = GroupDeployToken
+ _create_attrs = (
+ (
+ "name",
+ "scopes",
+ ),
+ (
+ "expires_at",
+ "username",
+ ),
+ )
+
+
+class ProjectDeployToken(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/deploy_tokens"
+ _from_parent_attrs = {"project_id": "id"}
+ _obj_cls = ProjectDeployToken
+ _create_attrs = (
+ (
+ "name",
+ "scopes",
+ ),
+ (
+ "expires_at",
+ "username",
+ ),
+ )
diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py
new file mode 100644
index 0000000..cc15f66
--- /dev/null
+++ b/gitlab/v4/objects/deployments.py
@@ -0,0 +1,14 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectDeployment(RESTObject, SaveMixin):
+ pass
+
+
+class ProjectDeploymentManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/deployments"
+ _obj_cls = ProjectDeployment
+ _from_parent_attrs = {"project_id": "id"}
+ _list_filters = ("order_by", "sort")
+ _create_attrs = (("sha", "ref", "tag", "status", "environment"), tuple())
diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py
new file mode 100644
index 0000000..a45864b
--- /dev/null
+++ b/gitlab/v4/objects/discussions.py
@@ -0,0 +1,55 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .notes import (
+ ProjectCommitDiscussionNoteManager,
+ ProjectIssueDiscussionNoteManager,
+ ProjectMergeRequestDiscussionNoteManager,
+ ProjectSnippetDiscussionNoteManager,
+)
+
+
+class ProjectCommitDiscussion(RESTObject):
+ _managers = (("notes", "ProjectCommitDiscussionNoteManager"),)
+
+
+class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s/" "discussions"
+ _obj_cls = ProjectCommitDiscussion
+ _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"}
+ _create_attrs = (("body",), ("created_at",))
+
+
+class ProjectIssueDiscussion(RESTObject):
+ _managers = (("notes", "ProjectIssueDiscussionNoteManager"),)
+
+
+class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues/%(issue_iid)s/discussions"
+ _obj_cls = ProjectIssueDiscussion
+ _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
+ _create_attrs = (("body",), ("created_at",))
+
+
+class ProjectMergeRequestDiscussion(SaveMixin, RESTObject):
+ _managers = (("notes", "ProjectMergeRequestDiscussionNoteManager"),)
+
+
+class ProjectMergeRequestDiscussionManager(
+ RetrieveMixin, CreateMixin, UpdateMixin, RESTManager
+):
+ _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/discussions"
+ _obj_cls = ProjectMergeRequestDiscussion
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+ _create_attrs = (("body",), ("created_at", "position"))
+ _update_attrs = (("resolved",), tuple())
+
+
+class ProjectSnippetDiscussion(RESTObject):
+ _managers = (("notes", "ProjectSnippetDiscussionNoteManager"),)
+
+
+class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/discussions"
+ _obj_cls = ProjectSnippetDiscussion
+ _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"}
+ _create_attrs = (("body",), ("created_at",))
diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py
new file mode 100644
index 0000000..6a39689
--- /dev/null
+++ b/gitlab/v4/objects/environments.py
@@ -0,0 +1,31 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject):
+ @cli.register_custom_action("ProjectEnvironment")
+ @exc.on_http_error(exc.GitlabStopError)
+ def stop(self, **kwargs):
+ """Stop the environment.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabStopError: If the operation failed
+ """
+ path = "%s/%s/stop" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path, **kwargs)
+
+
+class ProjectEnvironmentManager(
+ RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = "/projects/%(project_id)s/environments"
+ _obj_cls = ProjectEnvironment
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("name",), ("external_url",))
+ _update_attrs = (tuple(), ("name", "external_url"))
diff --git a/gitlab/v4/objects/epics.py b/gitlab/v4/objects/epics.py
new file mode 100644
index 0000000..2cbadfa
--- /dev/null
+++ b/gitlab/v4/objects/epics.py
@@ -0,0 +1,87 @@
+from gitlab import types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .events import GroupEpicResourceLabelEventManager
+
+
+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 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)
diff --git a/gitlab/v4/objects/events.py b/gitlab/v4/objects/events.py
new file mode 100644
index 0000000..2ecd60c
--- /dev/null
+++ b/gitlab/v4/objects/events.py
@@ -0,0 +1,98 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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")
+
+
+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 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 ProjectEvent(Event):
+ pass
+
+
+class ProjectEventManager(EventManager):
+ _path = "/projects/%(project_id)s/events"
+ _obj_cls = ProjectEvent
+ _from_parent_attrs = {"project_id": "id"}
+
+
+class ProjectIssueResourceLabelEvent(RESTObject):
+ pass
+
+
+class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues/%(issue_iid)s" "/resource_label_events"
+ _obj_cls = ProjectIssueResourceLabelEvent
+ _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
+
+
+class ProjectIssueResourceMilestoneEvent(RESTObject):
+ pass
+
+
+class ProjectIssueResourceMilestoneEventManager(RetrieveMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues/%(issue_iid)s/resource_milestone_events"
+ _obj_cls = ProjectIssueResourceMilestoneEvent
+ _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
+
+
+class ProjectMergeRequestResourceLabelEvent(RESTObject):
+ pass
+
+
+class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager):
+ _path = (
+ "/projects/%(project_id)s/merge_requests/%(mr_iid)s" "/resource_label_events"
+ )
+ _obj_cls = ProjectMergeRequestResourceLabelEvent
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+
+
+class ProjectMergeRequestResourceMilestoneEvent(RESTObject):
+ pass
+
+
+class ProjectMergeRequestResourceMilestoneEventManager(RetrieveMixin, RESTManager):
+ _path = (
+ "/projects/%(project_id)s/merge_requests/%(mr_iid)s/resource_milestone_events"
+ )
+ _obj_cls = ProjectMergeRequestResourceMilestoneEvent
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+
+
+class UserEvent(Event):
+ pass
+
+
+class UserEventManager(EventManager):
+ _path = "/users/%(user_id)s/events"
+ _obj_cls = UserEvent
+ _from_parent_attrs = {"user_id": "id"}
diff --git a/gitlab/v4/objects/export_import.py b/gitlab/v4/objects/export_import.py
new file mode 100644
index 0000000..c7cea20
--- /dev/null
+++ b/gitlab/v4/objects/export_import.py
@@ -0,0 +1,43 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class GroupExport(DownloadMixin, RESTObject):
+ _id_attr = None
+
+
+class GroupExportManager(GetWithoutIdMixin, CreateMixin, RESTManager):
+ _path = "/groups/%(group_id)s/export"
+ _obj_cls = GroupExport
+ _from_parent_attrs = {"group_id": "id"}
+
+
+class GroupImport(RESTObject):
+ _id_attr = None
+
+
+class GroupImportManager(GetWithoutIdMixin, RESTManager):
+ _path = "/groups/%(group_id)s/import"
+ _obj_cls = GroupImport
+ _from_parent_attrs = {"group_id": "id"}
+
+
+class ProjectExport(DownloadMixin, RefreshMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/export"
+ _obj_cls = ProjectExport
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (tuple(), ("description",))
+
+
+class ProjectImport(RefreshMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectImportManager(GetWithoutIdMixin, RESTManager):
+ _path = "/projects/%(project_id)s/import"
+ _obj_cls = ProjectImport
+ _from_parent_attrs = {"project_id": "id"}
diff --git a/gitlab/v4/objects/features.py b/gitlab/v4/objects/features.py
new file mode 100644
index 0000000..da756e0
--- /dev/null
+++ b/gitlab/v4/objects/features.py
@@ -0,0 +1,53 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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,
+ group=None,
+ project=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
+ group (str): A GitLab group
+ project (str): A GitLab project in form group/project
+ **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,
+ "group": group,
+ "project": project,
+ }
+ data = utils.remove_none_from_dict(data)
+ server_data = self.gitlab.http_post(path, post_data=data, **kwargs)
+ return self._obj_cls(self, server_data)
diff --git a/gitlab/v4/objects/files.py b/gitlab/v4/objects/files.py
new file mode 100644
index 0000000..bffa4e4
--- /dev/null
+++ b/gitlab/v4/objects/files.py
@@ -0,0 +1,216 @@
+import base64
+
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _id_attr = "file_path"
+ _short_print_attr = "file_path"
+
+ def decode(self):
+ """Returns the decoded content of the file.
+
+ Returns:
+ (str): the decoded content.
+ """
+ return base64.b64decode(self.content)
+
+ def save(self, branch, commit_message, **kwargs):
+ """Save the changes made to the file to the server.
+
+ The object is updated to match what the server returns.
+
+ Args:
+ branch (str): Branch in which the file will be updated
+ commit_message (str): Message to send with the commit
+ **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
+ """
+ self.branch = branch
+ self.commit_message = commit_message
+ self.file_path = self.file_path.replace("/", "%2F")
+ super(ProjectFile, self).save(**kwargs)
+
+ def delete(self, branch, commit_message, **kwargs):
+ """Delete the file from the server.
+
+ Args:
+ branch (str): Branch from which the file will be removed
+ commit_message (str): Commit message for the deletion
+ **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
+ """
+ file_path = self.get_id().replace("/", "%2F")
+ self.manager.delete(file_path, branch, commit_message, **kwargs)
+
+
+class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/files"
+ _obj_cls = ProjectFile
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ ("file_path", "branch", "content", "commit_message"),
+ ("encoding", "author_email", "author_name"),
+ )
+ _update_attrs = (
+ ("file_path", "branch", "content", "commit_message"),
+ ("encoding", "author_email", "author_name"),
+ )
+
+ @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
+ def get(self, file_path, ref, **kwargs):
+ """Retrieve a single file.
+
+ Args:
+ file_path (str): Path of the file to retrieve
+ ref (str): Name of the branch, tag or commit
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the file could not be retrieved
+
+ Returns:
+ object: The generated RESTObject
+ """
+ file_path = file_path.replace("/", "%2F")
+ return GetMixin.get(self, file_path, ref=ref, **kwargs)
+
+ @cli.register_custom_action(
+ "ProjectFileManager",
+ ("file_path", "branch", "content", "commit_message"),
+ ("encoding", "author_email", "author_name"),
+ )
+ @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)
+
+ Returns:
+ RESTObject: a new instance of the managed object class built with
+ the data sent by the server
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCreateError: If the server cannot perform the request
+ """
+
+ self._check_missing_create_attrs(data)
+ new_data = data.copy()
+ file_path = new_data.pop("file_path").replace("/", "%2F")
+ path = "%s/%s" % (self.path, file_path)
+ server_data = self.gitlab.http_post(path, post_data=new_data, **kwargs)
+ return self._obj_cls(self, server_data)
+
+ @exc.on_http_error(exc.GitlabUpdateError)
+ def update(self, file_path, 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()
+ file_path = file_path.replace("/", "%2F")
+ data["file_path"] = file_path
+ path = "%s/%s" % (self.path, file_path)
+ self._check_missing_update_attrs(data)
+ return self.gitlab.http_put(path, post_data=data, **kwargs)
+
+ @cli.register_custom_action(
+ "ProjectFileManager", ("file_path", "branch", "commit_message")
+ )
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def delete(self, file_path, branch, commit_message, **kwargs):
+ """Delete a file on the server.
+
+ Args:
+ file_path (str): Path of the file to remove
+ branch (str): Branch from which the file will be removed
+ commit_message (str): Commit message for the deletion
+ **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 = "%s/%s" % (self.path, file_path.replace("/", "%2F"))
+ data = {"branch": branch, "commit_message": commit_message}
+ self.gitlab.http_delete(path, query_data=data, **kwargs)
+
+ @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
+ @exc.on_http_error(exc.GitlabGetError)
+ def raw(
+ self, file_path, ref, streamed=False, action=None, chunk_size=1024, **kwargs
+ ):
+ """Return the content of a file for a commit.
+
+ Args:
+ ref (str): ID of the commit
+ filepath (str): Path of the file to return
+ 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 file could not be retrieved
+
+ Returns:
+ str: The file content
+ """
+ file_path = file_path.replace("/", "%2F").replace(".", "%2E")
+ path = "%s/%s/raw" % (self.path, file_path)
+ query_data = {"ref": ref}
+ result = self.gitlab.http_get(
+ path, query_data=query_data, streamed=streamed, raw=True, **kwargs
+ )
+ return utils.response_content(result, streamed, action, chunk_size)
+
+ @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
+ @exc.on_http_error(exc.GitlabListError)
+ def blame(self, file_path, ref, **kwargs):
+ """Return the content of a file for a commit.
+
+ Args:
+ file_path (str): Path of the file to retrieve
+ ref (str): Name of the branch, tag or commit
+ **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(blame): a list of commits/lines matching the file
+ """
+ file_path = file_path.replace("/", "%2F").replace(".", "%2E")
+ path = "%s/%s/blame" % (self.path, file_path)
+ query_data = {"ref": ref}
+ return self.gitlab.http_list(path, query_data, **kwargs)
diff --git a/gitlab/v4/objects/geo_nodes.py b/gitlab/v4/objects/geo_nodes.py
new file mode 100644
index 0000000..913bfca
--- /dev/null
+++ b/gitlab/v4/objects/geo_nodes.py
@@ -0,0 +1,83 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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)
diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py
new file mode 100644
index 0000000..086a87d
--- /dev/null
+++ b/gitlab/v4/objects/groups.py
@@ -0,0 +1,286 @@
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .access_requests import GroupAccessRequestManager
+from .badges import GroupBadgeManager
+from .boards import GroupBoardManager
+from .custom_attributes import GroupCustomAttributeManager
+from .export_import import GroupExportManager, GroupImportManager
+from .epics import GroupEpicManager
+from .issues import GroupIssueManager
+from .labels import GroupLabelManager
+from .members import GroupMemberManager
+from .merge_requests import GroupMergeRequestManager
+from .milestones import GroupMilestoneManager
+from .notification_settings import GroupNotificationSettingsManager
+from .packages import GroupPackageManager
+from .projects import GroupProjectManager
+from .runners import GroupRunnerManager
+from .variables import GroupVariableManager
+from .clusters import GroupClusterManager
+from .deploy_tokens import GroupDeployTokenManager
+
+
+class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _short_print_attr = "name"
+ _managers = (
+ ("accessrequests", "GroupAccessRequestManager"),
+ ("badges", "GroupBadgeManager"),
+ ("boards", "GroupBoardManager"),
+ ("customattributes", "GroupCustomAttributeManager"),
+ ("exports", "GroupExportManager"),
+ ("epics", "GroupEpicManager"),
+ ("imports", "GroupImportManager"),
+ ("issues", "GroupIssueManager"),
+ ("labels", "GroupLabelManager"),
+ ("members", "GroupMemberManager"),
+ ("mergerequests", "GroupMergeRequestManager"),
+ ("milestones", "GroupMilestoneManager"),
+ ("notificationsettings", "GroupNotificationSettingsManager"),
+ ("packages", "GroupPackageManager"),
+ ("projects", "GroupProjectManager"),
+ ("runners", "GroupRunnerManager"),
+ ("subgroups", "GroupSubgroupManager"),
+ ("variables", "GroupVariableManager"),
+ ("clusters", "GroupClusterManager"),
+ ("deploytokens", "GroupDeployTokenManager"),
+ )
+
+ @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)
+
+ @cli.register_custom_action("Group", ("group_id", "group_access"), ("expires_at",))
+ @exc.on_http_error(exc.GitlabCreateError)
+ def share(self, group_id, group_access, expires_at=None, **kwargs):
+ """Share the group with a group.
+
+ Args:
+ group_id (int): ID of the group.
+ group_access (int): Access level for the group.
+ **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 = "/groups/%s/share" % self.get_id()
+ data = {
+ "group_id": group_id,
+ "group_access": group_access,
+ "expires_at": expires_at,
+ }
+ self.manager.gitlab.http_post(path, post_data=data, **kwargs)
+
+ @cli.register_custom_action("Group", ("group_id",))
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def unshare(self, group_id, **kwargs):
+ """Delete a shared group link within a group.
+
+ Args:
+ group_id (int): ID of the group.
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the server failed to perform the request
+ """
+ path = "/groups/%s/share/%s" % (self.get_id(), group_id)
+ self.manager.gitlab.http_delete(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",
+ "min_access_level",
+ )
+ _create_attrs = (
+ ("name", "path"),
+ (
+ "description",
+ "membership_lock",
+ "visibility",
+ "share_with_group_lock",
+ "require_two_factor_authentication",
+ "two_factor_grace_period",
+ "project_creation_level",
+ "auto_devops_enabled",
+ "subgroup_creation_level",
+ "emails_disabled",
+ "avatar",
+ "mentions_disabled",
+ "lfs_enabled",
+ "request_access_enabled",
+ "parent_id",
+ "default_branch_protection",
+ ),
+ )
+ _update_attrs = (
+ tuple(),
+ (
+ "name",
+ "path",
+ "description",
+ "membership_lock",
+ "share_with_group_lock",
+ "visibility",
+ "require_two_factor_authentication",
+ "two_factor_grace_period",
+ "project_creation_level",
+ "auto_devops_enabled",
+ "subgroup_creation_level",
+ "emails_disabled",
+ "avatar",
+ "mentions_disabled",
+ "lfs_enabled",
+ "request_access_enabled",
+ "default_branch_protection",
+ ),
+ )
+ _types = {"avatar": types.ImageAttribute}
+
+ @exc.on_http_error(exc.GitlabImportError)
+ def import_group(self, file, path, name, parent_id=None, **kwargs):
+ """Import a group from an archive file.
+
+ Args:
+ file: Data or file object containing the group
+ path (str): The path for the new group to be imported.
+ name (str): The name for the new group.
+ parent_id (str): ID of a parent group that the group will
+ be imported into.
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabImportError: If the server failed to perform the request
+
+ Returns:
+ dict: A representation of the import status.
+ """
+ files = {"file": ("file.tar.gz", file, "application/octet-stream")}
+ data = {"path": path, "name": name}
+ if parent_id is not None:
+ data["parent_id"] = parent_id
+
+ return self.gitlab.http_post(
+ "/groups/import", post_data=data, files=files, **kwargs
+ )
+
+
+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",
+ )
diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py
new file mode 100644
index 0000000..3bd9132
--- /dev/null
+++ b/gitlab/v4/objects/hooks.py
@@ -0,0 +1,55 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class Hook(ObjectDeleteMixin, RESTObject):
+ _url = "/hooks"
+ _short_print_attr = "url"
+
+
+class HookManager(NoUpdateMixin, RESTManager):
+ _path = "/hooks"
+ _obj_cls = Hook
+ _create_attrs = (("url",), tuple())
+
+
+class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _short_print_attr = "url"
+
+
+class ProjectHookManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/hooks"
+ _obj_cls = ProjectHook
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ ("url",),
+ (
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "job_events",
+ "pipeline_events",
+ "wiki_page_events",
+ "enable_ssl_verification",
+ "token",
+ ),
+ )
+ _update_attrs = (
+ ("url",),
+ (
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "job_events",
+ "pipeline_events",
+ "wiki_events",
+ "enable_ssl_verification",
+ "token",
+ ),
+ )
diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py
new file mode 100644
index 0000000..1d8358d
--- /dev/null
+++ b/gitlab/v4/objects/issues.py
@@ -0,0 +1,229 @@
+from gitlab import types
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .award_emojis import ProjectIssueAwardEmojiManager
+from .discussions import ProjectIssueDiscussionManager
+from .events import (
+ ProjectIssueResourceLabelEventManager,
+ ProjectIssueResourceMilestoneEventManager,
+)
+from .notes import ProjectIssueNoteManager
+
+
+class Issue(RESTObject):
+ _url = "/issues"
+ _short_print_attr = "title"
+
+
+class IssueManager(RetrieveMixin, 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}
+
+
+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 ProjectIssue(
+ UserAgentDetailMixin,
+ SubscribableMixin,
+ TodoMixin,
+ TimeTrackingMixin,
+ ParticipantsMixin,
+ SaveMixin,
+ ObjectDeleteMixin,
+ RESTObject,
+):
+ _short_print_attr = "title"
+ _id_attr = "iid"
+ _managers = (
+ ("awardemojis", "ProjectIssueAwardEmojiManager"),
+ ("discussions", "ProjectIssueDiscussionManager"),
+ ("links", "ProjectIssueLinkManager"),
+ ("notes", "ProjectIssueNoteManager"),
+ ("resourcelabelevents", "ProjectIssueResourceLabelEventManager"),
+ ("resourcemilestoneevents", "ProjectIssueResourceMilestoneEventManager"),
+ )
+
+ @cli.register_custom_action("ProjectIssue", ("to_project_id",))
+ @exc.on_http_error(exc.GitlabUpdateError)
+ def move(self, to_project_id, **kwargs):
+ """Move the issue to another project.
+
+ Args:
+ to_project_id(int): ID of the target project
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabUpdateError: If the issue could not be moved
+ """
+ path = "%s/%s/move" % (self.manager.path, self.get_id())
+ data = {"to_project_id": to_project_id}
+ server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("ProjectIssue")
+ @exc.on_http_error(exc.GitlabGetError)
+ def related_merge_requests(self, **kwargs):
+ """List merge requests related to the issue.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetErrot: If the merge requests could not be retrieved
+
+ Returns:
+ list: The list of merge requests.
+ """
+ path = "%s/%s/related_merge_requests" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("ProjectIssue")
+ @exc.on_http_error(exc.GitlabGetError)
+ def closed_by(self, **kwargs):
+ """List merge requests that will close the issue when merged.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetErrot: If the merge requests could not be retrieved
+
+ Returns:
+ list: The list of merge requests.
+ """
+ path = "%s/%s/closed_by" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+
+class ProjectIssueManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues"
+ _obj_cls = ProjectIssue
+ _from_parent_attrs = {"project_id": "id"}
+ _list_filters = (
+ "iids",
+ "state",
+ "labels",
+ "milestone",
+ "scope",
+ "author_id",
+ "assignee_id",
+ "my_reaction_emoji",
+ "order_by",
+ "sort",
+ "search",
+ "created_after",
+ "created_before",
+ "updated_after",
+ "updated_before",
+ )
+ _create_attrs = (
+ ("title",),
+ (
+ "description",
+ "confidential",
+ "assignee_ids",
+ "assignee_id",
+ "milestone_id",
+ "labels",
+ "created_at",
+ "due_date",
+ "merge_request_to_resolve_discussions_of",
+ "discussion_to_resolve",
+ ),
+ )
+ _update_attrs = (
+ tuple(),
+ (
+ "title",
+ "description",
+ "confidential",
+ "assignee_ids",
+ "assignee_id",
+ "milestone_id",
+ "labels",
+ "state_event",
+ "updated_at",
+ "due_date",
+ "discussion_locked",
+ ),
+ )
+ _types = {"labels": types.ListAttribute}
+
+
+class ProjectIssueLink(ObjectDeleteMixin, RESTObject):
+ _id_attr = "issue_link_id"
+
+
+class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues/%(issue_iid)s/links"
+ _obj_cls = ProjectIssueLink
+ _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
+ _create_attrs = (("target_project_id", "target_issue_iid"), tuple())
+
+ @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)
+
+ Returns:
+ RESTObject, RESTObject: The source and target issues
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCreateError: If the server cannot perform the request
+ """
+ self._check_missing_create_attrs(data)
+ server_data = self.gitlab.http_post(self.path, post_data=data, **kwargs)
+ source_issue = ProjectIssue(self._parent.manager, server_data["source_issue"])
+ target_issue = ProjectIssue(self._parent.manager, server_data["target_issue"])
+ return source_issue, target_issue
diff --git a/gitlab/v4/objects/jobs.py b/gitlab/v4/objects/jobs.py
new file mode 100644
index 0000000..b17632c
--- /dev/null
+++ b/gitlab/v4/objects/jobs.py
@@ -0,0 +1,184 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectJob(RESTObject, RefreshMixin):
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabJobCancelError)
+ def cancel(self, **kwargs):
+ """Cancel the job.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabJobCancelError: If the job could not be canceled
+ """
+ path = "%s/%s/cancel" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabJobRetryError)
+ def retry(self, **kwargs):
+ """Retry the job.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabJobRetryError: If the job could not be retried
+ """
+ path = "%s/%s/retry" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabJobPlayError)
+ def play(self, **kwargs):
+ """Trigger a job explicitly.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabJobPlayError: If the job could not be triggered
+ """
+ path = "%s/%s/play" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabJobEraseError)
+ def erase(self, **kwargs):
+ """Erase the job (remove job artifacts and trace).
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabJobEraseError: If the job could not be erased
+ """
+ path = "%s/%s/erase" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabCreateError)
+ def keep_artifacts(self, **kwargs):
+ """Prevent artifacts from being deleted when expiration is set.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCreateError: If the request could not be performed
+ """
+ path = "%s/%s/artifacts/keep" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabCreateError)
+ def delete_artifacts(self, **kwargs):
+ """Delete artifacts of a job.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the request could not be performed
+ """
+ path = "%s/%s/artifacts" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_delete(path)
+
+ @cli.register_custom_action("ProjectJob")
+ @exc.on_http_error(exc.GitlabGetError)
+ def artifacts(self, streamed=False, action=None, chunk_size=1024, **kwargs):
+ """Get the job artifacts.
+
+ 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 artifacts could not be retrieved
+
+ Returns:
+ str: The artifacts if `streamed` is False, None otherwise.
+ """
+ path = "%s/%s/artifacts" % (self.manager.path, 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("ProjectJob")
+ @exc.on_http_error(exc.GitlabGetError)
+ def artifact(self, path, streamed=False, action=None, chunk_size=1024, **kwargs):
+ """Get a single artifact file from within the job's artifacts archive.
+
+ Args:
+ path (str): Path of the artifact
+ 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 = "%s/%s/artifacts/%s" % (self.manager.path, self.get_id(), path)
+ 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("ProjectJob")
+ @exc.on_http_error(exc.GitlabGetError)
+ def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs):
+ """Get the job trace.
+
+ 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 artifacts could not be retrieved
+
+ Returns:
+ str: The trace
+ """
+ path = "%s/%s/trace" % (self.manager.path, 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 ProjectJobManager(RetrieveMixin, RESTManager):
+ _path = "/projects/%(project_id)s/jobs"
+ _obj_cls = ProjectJob
+ _from_parent_attrs = {"project_id": "id"}
diff --git a/gitlab/v4/objects/labels.py b/gitlab/v4/objects/labels.py
new file mode 100644
index 0000000..ef6511f
--- /dev/null
+++ b/gitlab/v4/objects/labels.py
@@ -0,0 +1,125 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectLabel(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 ProjectLabelManager(
+ RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = "/projects/%(project_id)s/labels"
+ _obj_cls = ProjectLabel
+ _from_parent_attrs = {"project_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)
diff --git a/gitlab/v4/objects/ldap.py b/gitlab/v4/objects/ldap.py
new file mode 100644
index 0000000..ed3dd72
--- /dev/null
+++ b/gitlab/v4/objects/ldap.py
@@ -0,0 +1,46 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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/members.py b/gitlab/v4/objects/members.py
new file mode 100644
index 0000000..e8a5038
--- /dev/null
+++ b/gitlab/v4/objects/members.py
@@ -0,0 +1,78 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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 ProjectMember(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _short_print_attr = "username"
+
+
+class ProjectMemberManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/members"
+ _obj_cls = ProjectMember
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("access_level", "user_id"), ("expires_at",))
+ _update_attrs = (("access_level",), ("expires_at",))
+
+ @cli.register_custom_action("ProjectMemberManager")
+ @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]
diff --git a/gitlab/v4/objects/merge_request_approvals.py b/gitlab/v4/objects/merge_request_approvals.py
new file mode 100644
index 0000000..8e5cbb3
--- /dev/null
+++ b/gitlab/v4/objects/merge_request_approvals.py
@@ -0,0 +1,179 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectApproval(SaveMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/approvals"
+ _obj_cls = ProjectApproval
+ _from_parent_attrs = {"project_id": "id"}
+ _update_attrs = (
+ tuple(),
+ (
+ "approvals_before_merge",
+ "reset_approvals_on_push",
+ "disable_overriding_approvers_per_merge_request",
+ "merge_requests_author_approval",
+ "merge_requests_disable_committers_approval",
+ ),
+ )
+ _update_uses_post = True
+
+ @exc.on_http_error(exc.GitlabUpdateError)
+ def set_approvers(self, approver_ids=None, approver_group_ids=None, **kwargs):
+ """Change project-level allowed approvers and approver groups.
+
+ Args:
+ approver_ids (list): User IDs that can approve MRs
+ approver_group_ids (list): Group IDs whose members can approve MRs
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabUpdateError: If the server failed to perform the request
+ """
+ approver_ids = approver_ids or []
+ approver_group_ids = approver_group_ids or []
+
+ path = "/projects/%s/approvers" % self._parent.get_id()
+ data = {"approver_ids": approver_ids, "approver_group_ids": approver_group_ids}
+ self.gitlab.http_put(path, post_data=data, **kwargs)
+
+
+class ProjectApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _id_attr = "id"
+
+
+class ProjectApprovalRuleManager(
+ ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = "/projects/%(project_id)s/approval_rules"
+ _obj_cls = ProjectApprovalRule
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("name", "approvals_required"), ("user_ids", "group_ids"))
+
+
+class ProjectMergeRequestApproval(SaveMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approvals"
+ _obj_cls = ProjectMergeRequestApproval
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+ _update_attrs = (("approvals_required",), tuple())
+ _update_uses_post = True
+
+ @exc.on_http_error(exc.GitlabUpdateError)
+ def set_approvers(
+ self,
+ approvals_required,
+ approver_ids=None,
+ approver_group_ids=None,
+ approval_rule_name="name",
+ **kwargs
+ ):
+ """Change MR-level allowed approvers and approver groups.
+
+ Args:
+ approvals_required (integer): The number of required approvals for this rule
+ approver_ids (list of integers): User IDs that can approve MRs
+ approver_group_ids (list): Group IDs whose members can approve MRs
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabUpdateError: If the server failed to perform the request
+ """
+ approver_ids = approver_ids or []
+ approver_group_ids = approver_group_ids or []
+
+ data = {
+ "name": approval_rule_name,
+ "approvals_required": approvals_required,
+ "rule_type": "regular",
+ "user_ids": approver_ids,
+ "group_ids": approver_group_ids,
+ }
+ approval_rules = self._parent.approval_rules
+ """ update any existing approval rule matching the name"""
+ existing_approval_rules = approval_rules.list()
+ for ar in existing_approval_rules:
+ if ar.name == approval_rule_name:
+ ar.user_ids = data["user_ids"]
+ ar.approvals_required = data["approvals_required"]
+ ar.group_ids = data["group_ids"]
+ ar.save()
+ return ar
+ """ if there was no rule matching the rule name, create a new one"""
+ return approval_rules.create(data=data)
+
+
+class ProjectMergeRequestApprovalRule(SaveMixin, RESTObject):
+ _id_attr = "approval_rule_id"
+ _short_print_attr = "approval_rule"
+
+ @exc.on_http_error(exc.GitlabUpdateError)
+ 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
+ """
+ # There is a mismatch between the name of our id attribute and the put REST API name for the
+ # project_id, so we override it here.
+ self.approval_rule_id = self.id
+ self.merge_request_iid = self._parent_attrs["mr_iid"]
+ self.id = self._parent_attrs["project_id"]
+ # save will update self.id with the result from the server, so no need to overwrite with
+ # what it was before we overwrote it."""
+ SaveMixin.save(self, **kwargs)
+
+
+class ProjectMergeRequestApprovalRuleManager(
+ ListMixin, UpdateMixin, CreateMixin, RESTManager
+):
+ _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approval_rules"
+ _obj_cls = ProjectMergeRequestApprovalRule
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+ _list_filters = ("name", "rule_type")
+ _update_attrs = (
+ ("id", "merge_request_iid", "approval_rule_id", "name", "approvals_required"),
+ ("user_ids", "group_ids"),
+ )
+ # Important: When approval_project_rule_id is set, the name, users and groups of
+ # project-level rule will be copied. The approvals_required specified will be used. """
+ _create_attrs = (
+ ("id", "merge_request_iid", "name", "approvals_required"),
+ ("approval_project_rule_id", "user_ids", "group_ids"),
+ )
+
+ 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
+ """
+ new_data = data.copy()
+ new_data["id"] = self._from_parent_attrs["project_id"]
+ new_data["merge_request_iid"] = self._from_parent_attrs["mr_iid"]
+ return CreateMixin.create(self, new_data, **kwargs)
diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py
new file mode 100644
index 0000000..477ccc6
--- /dev/null
+++ b/gitlab/v4/objects/merge_requests.py
@@ -0,0 +1,375 @@
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .commits import ProjectCommit, ProjectCommitManager
+from .issues import ProjectIssue, ProjectIssueManager
+from .merge_request_approvals import (
+ ProjectMergeRequestApprovalManager,
+ ProjectMergeRequestApprovalRuleManager,
+)
+from .award_emojis import ProjectMergeRequestAwardEmojiManager
+from .discussions import ProjectMergeRequestDiscussionManager
+from .notes import ProjectMergeRequestNoteManager
+from .events import (
+ ProjectMergeRequestResourceLabelEventManager,
+ ProjectMergeRequestResourceMilestoneEventManager,
+)
+
+
+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",
+ "wip",
+ )
+ _types = {"labels": types.ListAttribute}
+
+
+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",
+ "wip",
+ )
+ _types = {"labels": types.ListAttribute}
+
+
+class ProjectMergeRequest(
+ SubscribableMixin,
+ TodoMixin,
+ TimeTrackingMixin,
+ ParticipantsMixin,
+ SaveMixin,
+ ObjectDeleteMixin,
+ RESTObject,
+):
+ _id_attr = "iid"
+
+ _managers = (
+ ("approvals", "ProjectMergeRequestApprovalManager"),
+ ("approval_rules", "ProjectMergeRequestApprovalRuleManager"),
+ ("awardemojis", "ProjectMergeRequestAwardEmojiManager"),
+ ("diffs", "ProjectMergeRequestDiffManager"),
+ ("discussions", "ProjectMergeRequestDiscussionManager"),
+ ("notes", "ProjectMergeRequestNoteManager"),
+ ("resourcelabelevents", "ProjectMergeRequestResourceLabelEventManager"),
+ ("resourcemilestoneevents", "ProjectMergeRequestResourceMilestoneEventManager"),
+ )
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabMROnBuildSuccessError)
+ def cancel_merge_when_pipeline_succeeds(self, **kwargs):
+ """Cancel merge when the pipeline succeeds.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabMROnBuildSuccessError: If the server could not handle the
+ request
+ """
+
+ path = "%s/%s/cancel_merge_when_pipeline_succeeds" % (
+ self.manager.path,
+ self.get_id(),
+ )
+ server_data = self.manager.gitlab.http_put(path, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabListError)
+ def closes_issues(self, **kwargs):
+ """List issues that will close on merge."
+
+ 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: List of issues
+ """
+ path = "%s/%s/closes_issues" % (self.manager.path, self.get_id())
+ data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs)
+ manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent)
+ return RESTObjectList(manager, ProjectIssue, data_list)
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabListError)
+ def commits(self, **kwargs):
+ """List the merge request commits.
+
+ 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 commits
+ """
+
+ path = "%s/%s/commits" % (self.manager.path, self.get_id())
+ data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs)
+ manager = ProjectCommitManager(self.manager.gitlab, parent=self.manager._parent)
+ return RESTObjectList(manager, ProjectCommit, data_list)
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabListError)
+ def changes(self, **kwargs):
+ """List the merge request changes.
+
+ Args:
+ **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: List of changes
+ """
+ path = "%s/%s/changes" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabListError)
+ def pipelines(self, **kwargs):
+ """List the merge request pipelines.
+
+ Args:
+ **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: List of changes
+ """
+
+ path = "%s/%s/pipelines" % (self.manager.path, self.get_id())
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("ProjectMergeRequest", tuple(), ("sha"))
+ @exc.on_http_error(exc.GitlabMRApprovalError)
+ def approve(self, sha=None, **kwargs):
+ """Approve the merge request.
+
+ Args:
+ sha (str): Head SHA of MR
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabMRApprovalError: If the approval failed
+ """
+ path = "%s/%s/approve" % (self.manager.path, self.get_id())
+ data = {}
+ if sha:
+ data["sha"] = sha
+
+ server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabMRApprovalError)
+ def unapprove(self, **kwargs):
+ """Unapprove the merge request.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabMRApprovalError: If the unapproval failed
+ """
+ path = "%s/%s/unapprove" % (self.manager.path, self.get_id())
+ data = {}
+
+ server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("ProjectMergeRequest")
+ @exc.on_http_error(exc.GitlabMRRebaseError)
+ def rebase(self, **kwargs):
+ """Attempt to rebase the source branch onto the target branch
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabMRRebaseError: If rebasing failed
+ """
+ path = "%s/%s/rebase" % (self.manager.path, self.get_id())
+ data = {}
+ return self.manager.gitlab.http_put(path, post_data=data, **kwargs)
+
+ @cli.register_custom_action(
+ "ProjectMergeRequest",
+ tuple(),
+ (
+ "merge_commit_message",
+ "should_remove_source_branch",
+ "merge_when_pipeline_succeeds",
+ ),
+ )
+ @exc.on_http_error(exc.GitlabMRClosedError)
+ def merge(
+ self,
+ merge_commit_message=None,
+ should_remove_source_branch=False,
+ merge_when_pipeline_succeeds=False,
+ **kwargs
+ ):
+ """Accept the merge request.
+
+ Args:
+ merge_commit_message (bool): Commit message
+ should_remove_source_branch (bool): If True, removes the source
+ branch
+ merge_when_pipeline_succeeds (bool): Wait for the build to succeed,
+ then merge
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabMRClosedError: If the merge failed
+ """
+ path = "%s/%s/merge" % (self.manager.path, self.get_id())
+ data = {}
+ if merge_commit_message:
+ data["merge_commit_message"] = merge_commit_message
+ if should_remove_source_branch:
+ data["should_remove_source_branch"] = True
+ if merge_when_pipeline_succeeds:
+ data["merge_when_pipeline_succeeds"] = True
+
+ server_data = self.manager.gitlab.http_put(path, query_data=data, **kwargs)
+ self._update_attrs(server_data)
+
+
+class ProjectMergeRequestManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/merge_requests"
+ _obj_cls = ProjectMergeRequest
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ ("source_branch", "target_branch", "title"),
+ (
+ "assignee_id",
+ "description",
+ "target_project_id",
+ "labels",
+ "milestone_id",
+ "remove_source_branch",
+ "allow_maintainer_to_push",
+ "squash",
+ ),
+ )
+ _update_attrs = (
+ tuple(),
+ (
+ "target_branch",
+ "assignee_id",
+ "title",
+ "description",
+ "state_event",
+ "labels",
+ "milestone_id",
+ "remove_source_branch",
+ "discussion_locked",
+ "allow_maintainer_to_push",
+ "squash",
+ ),
+ )
+ _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",
+ "wip",
+ )
+ _types = {"labels": types.ListAttribute}
+
+
+class ProjectMergeRequestDiff(RESTObject):
+ pass
+
+
+class ProjectMergeRequestDiffManager(RetrieveMixin, RESTManager):
+ _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/versions"
+ _obj_cls = ProjectMergeRequestDiff
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
diff --git a/gitlab/v4/objects/milestones.py b/gitlab/v4/objects/milestones.py
new file mode 100644
index 0000000..e15ec5a
--- /dev/null
+++ b/gitlab/v4/objects/milestones.py
@@ -0,0 +1,154 @@
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .issues import GroupIssue, GroupIssueManager, ProjectIssue, ProjectIssueManager
+from .merge_requests import (
+ ProjectMergeRequest,
+ ProjectMergeRequestManager,
+ GroupMergeRequest,
+ GroupMergeRequestManager,
+)
+
+
+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 ProjectMilestone(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _short_print_attr = "title"
+
+ @cli.register_custom_action("ProjectMilestone")
+ @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 = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent)
+ # FIXME(gpocentek): the computed manager path is not correct
+ return RESTObjectList(manager, ProjectIssue, data_list)
+
+ @cli.register_custom_action("ProjectMilestone")
+ @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 = ProjectMergeRequestManager(
+ self.manager.gitlab, parent=self.manager._parent
+ )
+ # FIXME(gpocentek): the computed manager path is not correct
+ return RESTObjectList(manager, ProjectMergeRequest, data_list)
+
+
+class ProjectMilestoneManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/milestones"
+ _obj_cls = ProjectMilestone
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ ("title",),
+ ("description", "due_date", "start_date", "state_event"),
+ )
+ _update_attrs = (
+ tuple(),
+ ("title", "description", "due_date", "start_date", "state_event"),
+ )
+ _list_filters = ("iids", "state", "search")
diff --git a/gitlab/v4/objects/namespaces.py b/gitlab/v4/objects/namespaces.py
new file mode 100644
index 0000000..7e66a39
--- /dev/null
+++ b/gitlab/v4/objects/namespaces.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/notes.py b/gitlab/v4/objects/notes.py
new file mode 100644
index 0000000..4cd1f25
--- /dev/null
+++ b/gitlab/v4/objects/notes.py
@@ -0,0 +1,140 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+from .award_emojis import (
+ ProjectIssueNoteAwardEmojiManager,
+ ProjectMergeRequestNoteAwardEmojiManager,
+ ProjectSnippetNoteAwardEmojiManager,
+)
+
+
+class ProjectNote(RESTObject):
+ pass
+
+
+class ProjectNoteManager(RetrieveMixin, RESTManager):
+ _path = "/projects/%(project_id)s/notes"
+ _obj_cls = ProjectNote
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("body",), tuple())
+
+
+class ProjectCommitDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectCommitDiscussionNoteManager(
+ GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = (
+ "/projects/%(project_id)s/repository/commits/%(commit_id)s/"
+ "discussions/%(discussion_id)s/notes"
+ )
+ _obj_cls = ProjectCommitDiscussionNote
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "commit_id": "commit_id",
+ "discussion_id": "id",
+ }
+ _create_attrs = (("body",), ("created_at", "position"))
+ _update_attrs = (("body",), tuple())
+
+
+class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _managers = (("awardemojis", "ProjectIssueNoteAwardEmojiManager"),)
+
+
+class ProjectIssueNoteManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues/%(issue_iid)s/notes"
+ _obj_cls = ProjectIssueNote
+ _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"}
+ _create_attrs = (("body",), ("created_at",))
+ _update_attrs = (("body",), tuple())
+
+
+class ProjectIssueDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectIssueDiscussionNoteManager(
+ GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = (
+ "/projects/%(project_id)s/issues/%(issue_iid)s/"
+ "discussions/%(discussion_id)s/notes"
+ )
+ _obj_cls = ProjectIssueDiscussionNote
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "issue_iid": "issue_iid",
+ "discussion_id": "id",
+ }
+ _create_attrs = (("body",), ("created_at",))
+ _update_attrs = (("body",), tuple())
+
+
+class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),)
+
+
+class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/notes"
+ _obj_cls = ProjectMergeRequestNote
+ _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
+ _create_attrs = (("body",), tuple())
+ _update_attrs = (("body",), tuple())
+
+
+class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectMergeRequestDiscussionNoteManager(
+ GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = (
+ "/projects/%(project_id)s/merge_requests/%(mr_iid)s/"
+ "discussions/%(discussion_id)s/notes"
+ )
+ _obj_cls = ProjectMergeRequestDiscussionNote
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "mr_iid": "mr_iid",
+ "discussion_id": "id",
+ }
+ _create_attrs = (("body",), ("created_at",))
+ _update_attrs = (("body",), tuple())
+
+
+class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _managers = (("awardemojis", "ProjectSnippetNoteAwardEmojiManager"),)
+
+
+class ProjectSnippetNoteManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/notes"
+ _obj_cls = ProjectSnippetNote
+ _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"}
+ _create_attrs = (("body",), tuple())
+ _update_attrs = (("body",), tuple())
+
+
+class ProjectSnippetDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectSnippetDiscussionNoteManager(
+ GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = (
+ "/projects/%(project_id)s/snippets/%(snippet_id)s/"
+ "discussions/%(discussion_id)s/notes"
+ )
+ _obj_cls = ProjectSnippetDiscussionNote
+ _from_parent_attrs = {
+ "project_id": "project_id",
+ "snippet_id": "snippet_id",
+ "discussion_id": "id",
+ }
+ _create_attrs = (("body",), ("created_at",))
+ _update_attrs = (("body",), tuple())
diff --git a/gitlab/v4/objects/notification_settings.py b/gitlab/v4/objects/notification_settings.py
new file mode 100644
index 0000000..94b9e3b
--- /dev/null
+++ b/gitlab/v4/objects/notification_settings.py
@@ -0,0 +1,49 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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",
+ ),
+ )
+
+
+class GroupNotificationSettings(NotificationSettings):
+ pass
+
+
+class GroupNotificationSettingsManager(NotificationSettingsManager):
+ _path = "/groups/%(group_id)s/notification_settings"
+ _obj_cls = GroupNotificationSettings
+ _from_parent_attrs = {"group_id": "id"}
+
+
+class ProjectNotificationSettings(NotificationSettings):
+ pass
+
+
+class ProjectNotificationSettingsManager(NotificationSettingsManager):
+ _path = "/projects/%(project_id)s/notification_settings"
+ _obj_cls = ProjectNotificationSettings
+ _from_parent_attrs = {"project_id": "id"}
diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py
new file mode 100644
index 0000000..be8292c
--- /dev/null
+++ b/gitlab/v4/objects/packages.py
@@ -0,0 +1,35 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class GroupPackage(RESTObject):
+ pass
+
+
+class GroupPackageManager(ListMixin, RESTManager):
+ _path = "/groups/%(group_id)s/packages"
+ _obj_cls = GroupPackage
+ _from_parent_attrs = {"group_id": "id"}
+ _list_filters = (
+ "exclude_subgroups",
+ "order_by",
+ "sort",
+ "package_type",
+ "package_name",
+ )
+
+
+class ProjectPackage(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/packages"
+ _obj_cls = ProjectPackage
+ _from_parent_attrs = {"project_id": "id"}
+ _list_filters = (
+ "order_by",
+ "sort",
+ "package_type",
+ "package_name",
+ )
diff --git a/gitlab/v4/objects/pages.py b/gitlab/v4/objects/pages.py
new file mode 100644
index 0000000..1de92c3
--- /dev/null
+++ b/gitlab/v4/objects/pages.py
@@ -0,0 +1,23 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class PagesDomain(RESTObject):
+ _id_attr = "domain"
+
+
+class PagesDomainManager(ListMixin, RESTManager):
+ _path = "/pages/domains"
+ _obj_cls = PagesDomain
+
+
+class ProjectPagesDomain(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _id_attr = "domain"
+
+
+class ProjectPagesDomainManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/pages/domains"
+ _obj_cls = ProjectPagesDomain
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("domain",), ("certificate", "key"))
+ _update_attrs = (tuple(), ("certificate", "key"))
diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py
new file mode 100644
index 0000000..f23df93
--- /dev/null
+++ b/gitlab/v4/objects/pipelines.py
@@ -0,0 +1,174 @@
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectPipeline(RESTObject, RefreshMixin, ObjectDeleteMixin):
+ _managers = (
+ ("jobs", "ProjectPipelineJobManager"),
+ ("bridges", "ProjectPipelineBridgeManager"),
+ ("variables", "ProjectPipelineVariableManager"),
+ )
+
+ @cli.register_custom_action("ProjectPipeline")
+ @exc.on_http_error(exc.GitlabPipelineCancelError)
+ def cancel(self, **kwargs):
+ """Cancel the job.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabPipelineCancelError: If the request failed
+ """
+ path = "%s/%s/cancel" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+ @cli.register_custom_action("ProjectPipeline")
+ @exc.on_http_error(exc.GitlabPipelineRetryError)
+ def retry(self, **kwargs):
+ """Retry the job.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabPipelineRetryError: If the request failed
+ """
+ path = "%s/%s/retry" % (self.manager.path, self.get_id())
+ self.manager.gitlab.http_post(path)
+
+
+class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager):
+ _path = "/projects/%(project_id)s/pipelines"
+ _obj_cls = ProjectPipeline
+ _from_parent_attrs = {"project_id": "id"}
+ _list_filters = (
+ "scope",
+ "status",
+ "ref",
+ "sha",
+ "yaml_errors",
+ "name",
+ "username",
+ "order_by",
+ "sort",
+ )
+ _create_attrs = (("ref",), tuple())
+
+ def create(self, data, **kwargs):
+ """Creates 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 managed object class build with
+ the data sent by the server
+ """
+ path = self.path[:-1] # drop the 's'
+ return CreateMixin.create(self, data, path=path, **kwargs)
+
+
+class ProjectPipelineJob(RESTObject):
+ pass
+
+
+class ProjectPipelineJobManager(ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs"
+ _obj_cls = ProjectPipelineJob
+ _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
+ _list_filters = ("scope",)
+
+
+class ProjectPipelineBridge(RESTObject):
+ pass
+
+
+class ProjectPipelineBridgeManager(ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/bridges"
+ _obj_cls = ProjectPipelineBridge
+ _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
+ _list_filters = ("scope",)
+
+
+class ProjectPipelineVariable(RESTObject):
+ _id_attr = "key"
+
+
+class ProjectPipelineVariableManager(ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/variables"
+ _obj_cls = ProjectPipelineVariable
+ _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
+
+
+class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _id_attr = "key"
+
+
+class ProjectPipelineScheduleVariableManager(
+ CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = (
+ "/projects/%(project_id)s/pipeline_schedules/"
+ "%(pipeline_schedule_id)s/variables"
+ )
+ _obj_cls = ProjectPipelineScheduleVariable
+ _from_parent_attrs = {"project_id": "project_id", "pipeline_schedule_id": "id"}
+ _create_attrs = (("key", "value"), tuple())
+ _update_attrs = (("key", "value"), tuple())
+
+
+class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _managers = (("variables", "ProjectPipelineScheduleVariableManager"),)
+
+ @cli.register_custom_action("ProjectPipelineSchedule")
+ @exc.on_http_error(exc.GitlabOwnershipError)
+ def take_ownership(self, **kwargs):
+ """Update the owner of a pipeline schedule.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabOwnershipError: If the request failed
+ """
+ path = "%s/%s/take_ownership" % (self.manager.path, self.get_id())
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("ProjectPipelineSchedule")
+ @exc.on_http_error(exc.GitlabPipelinePlayError)
+ def play(self, **kwargs):
+ """Trigger a new scheduled pipeline, which runs immediately.
+ The next scheduled run of this pipeline is not affected.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabPipelinePlayError: If the request failed
+ """
+ path = "%s/%s/play" % (self.manager.path, self.get_id())
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+ return server_data
+
+
+class ProjectPipelineScheduleManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/pipeline_schedules"
+ _obj_cls = ProjectPipelineSchedule
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("description", "ref", "cron"), ("cron_timezone", "active"))
+ _update_attrs = (tuple(), ("description", "ref", "cron", "cron_timezone", "active"))
diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py
new file mode 100644
index 0000000..0ad9db1
--- /dev/null
+++ b/gitlab/v4/objects/projects.py
@@ -0,0 +1,1120 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab import types, utils
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+from .access_requests import ProjectAccessRequestManager
+from .badges import ProjectBadgeManager
+from .boards import ProjectBoardManager
+from .branches import ProjectBranchManager, ProjectProtectedBranchManager
+from .clusters import ProjectClusterManager
+from .commits import ProjectCommitManager
+from .container_registry import ProjectRegistryRepositoryManager
+from .custom_attributes import ProjectCustomAttributeManager
+from .deploy_keys import ProjectKeyManager
+from .deploy_tokens import ProjectDeployTokenManager
+from .deployments import ProjectDeploymentManager
+from .environments import ProjectEnvironmentManager
+from .events import ProjectEventManager
+from .export_import import ProjectExportManager, ProjectImportManager
+from .files import ProjectFileManager
+from .hooks import ProjectHookManager
+from .issues import ProjectIssueManager
+from .jobs import ProjectJobManager
+from .labels import ProjectLabelManager
+from .members import ProjectMemberManager
+from .merge_request_approvals import ProjectApprovalManager, ProjectApprovalRuleManager
+from .merge_requests import ProjectMergeRequestManager
+from .milestones import ProjectMilestoneManager
+from .notes import ProjectNoteManager
+from .notification_settings import ProjectNotificationSettingsManager
+from .packages import ProjectPackageManager
+from .pages import ProjectPagesDomainManager
+from .pipelines import ProjectPipelineManager, ProjectPipelineScheduleManager
+from .push_rules import ProjectPushRulesManager
+from .runners import ProjectRunnerManager
+from .services import ProjectServiceManager
+from .snippets import ProjectSnippetManager
+from .statistics import (
+ ProjectAdditionalStatisticsManager,
+ ProjectIssuesStatisticsManager,
+)
+from .tags import ProjectProtectedTagManager, ProjectReleaseManager, ProjectTagManager
+from .triggers import ProjectTriggerManager
+from .users import ProjectUserManager
+from .variables import ProjectVariableManager
+from .wikis import ProjectWikiManager
+
+
+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",
+ "simple",
+ "owned",
+ "starred",
+ "with_custom_attributes",
+ "include_subgroups",
+ "with_issues_enabled",
+ "with_merge_requests_enabled",
+ "with_shared",
+ "min_access_level",
+ "with_security_reports",
+ )
+
+
+class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _short_print_attr = "path"
+ _managers = (
+ ("accessrequests", "ProjectAccessRequestManager"),
+ ("approvals", "ProjectApprovalManager"),
+ ("approvalrules", "ProjectApprovalRuleManager"),
+ ("badges", "ProjectBadgeManager"),
+ ("boards", "ProjectBoardManager"),
+ ("branches", "ProjectBranchManager"),
+ ("jobs", "ProjectJobManager"),
+ ("commits", "ProjectCommitManager"),
+ ("customattributes", "ProjectCustomAttributeManager"),
+ ("deployments", "ProjectDeploymentManager"),
+ ("environments", "ProjectEnvironmentManager"),
+ ("events", "ProjectEventManager"),
+ ("exports", "ProjectExportManager"),
+ ("files", "ProjectFileManager"),
+ ("forks", "ProjectForkManager"),
+ ("hooks", "ProjectHookManager"),
+ ("keys", "ProjectKeyManager"),
+ ("imports", "ProjectImportManager"),
+ ("issues", "ProjectIssueManager"),
+ ("labels", "ProjectLabelManager"),
+ ("members", "ProjectMemberManager"),
+ ("mergerequests", "ProjectMergeRequestManager"),
+ ("milestones", "ProjectMilestoneManager"),
+ ("notes", "ProjectNoteManager"),
+ ("notificationsettings", "ProjectNotificationSettingsManager"),
+ ("packages", "ProjectPackageManager"),
+ ("pagesdomains", "ProjectPagesDomainManager"),
+ ("pipelines", "ProjectPipelineManager"),
+ ("protectedbranches", "ProjectProtectedBranchManager"),
+ ("protectedtags", "ProjectProtectedTagManager"),
+ ("pipelineschedules", "ProjectPipelineScheduleManager"),
+ ("pushrules", "ProjectPushRulesManager"),
+ ("releases", "ProjectReleaseManager"),
+ ("remote_mirrors", "ProjectRemoteMirrorManager"),
+ ("repositories", "ProjectRegistryRepositoryManager"),
+ ("runners", "ProjectRunnerManager"),
+ ("services", "ProjectServiceManager"),
+ ("snippets", "ProjectSnippetManager"),
+ ("tags", "ProjectTagManager"),
+ ("users", "ProjectUserManager"),
+ ("triggers", "ProjectTriggerManager"),
+ ("variables", "ProjectVariableManager"),
+ ("wikis", "ProjectWikiManager"),
+ ("clusters", "ProjectClusterManager"),
+ ("additionalstatistics", "ProjectAdditionalStatisticsManager"),
+ ("issuesstatistics", "ProjectIssuesStatisticsManager"),
+ ("deploytokens", "ProjectDeployTokenManager"),
+ )
+
+ @cli.register_custom_action("Project", ("submodule", "branch", "commit_sha"))
+ @exc.on_http_error(exc.GitlabUpdateError)
+ def update_submodule(self, submodule, branch, commit_sha, **kwargs):
+ """Update a project submodule
+
+ Args:
+ submodule (str): Full path to the submodule
+ branch (str): Name of the branch to commit into
+ commit_sha (str): Full commit SHA to update the submodule to
+ commit_message (str): Commit message. If no message is provided, a default one will be set (optional)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabPutError: If the submodule could not be updated
+ """
+
+ submodule = submodule.replace("/", "%2F") # .replace('.', '%2E')
+ path = "/projects/%s/repository/submodules/%s" % (self.get_id(), submodule)
+ data = {"branch": branch, "commit_sha": commit_sha}
+ if "commit_message" in kwargs:
+ data["commit_message"] = kwargs["commit_message"]
+ return self.manager.gitlab.http_put(path, post_data=data)
+
+ @cli.register_custom_action("Project", tuple(), ("path", "ref", "recursive"))
+ @exc.on_http_error(exc.GitlabGetError)
+ def repository_tree(self, path="", ref="", recursive=False, **kwargs):
+ """Return a list of files in the repository.
+
+ Args:
+ path (str): Path of the top folder (/ by default)
+ ref (str): Reference to a commit or branch
+ recursive (bool): Whether to get the tree recursively
+ 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
+ GitlabGetError: If the server failed to perform the request
+
+ Returns:
+ list: The representation of the tree
+ """
+ gl_path = "/projects/%s/repository/tree" % self.get_id()
+ query_data = {"recursive": recursive}
+ if path:
+ query_data["path"] = path
+ if ref:
+ query_data["ref"] = ref
+ return self.manager.gitlab.http_list(gl_path, query_data=query_data, **kwargs)
+
+ @cli.register_custom_action("Project", ("sha",))
+ @exc.on_http_error(exc.GitlabGetError)
+ def repository_blob(self, sha, **kwargs):
+ """Return a file by blob SHA.
+
+ Args:
+ sha(str): ID of the blob
+ **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 blob content and metadata
+ """
+
+ path = "/projects/%s/repository/blobs/%s" % (self.get_id(), sha)
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("Project", ("sha",))
+ @exc.on_http_error(exc.GitlabGetError)
+ def repository_raw_blob(
+ self, sha, streamed=False, action=None, chunk_size=1024, **kwargs
+ ):
+ """Return the raw file contents for a blob.
+
+ Args:
+ sha(str): ID of the blob
+ 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
+
+ Returns:
+ str: The blob content if streamed is False, None otherwise
+ """
+ path = "/projects/%s/repository/blobs/%s/raw" % (self.get_id(), sha)
+ 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", ("from_", "to"))
+ @exc.on_http_error(exc.GitlabGetError)
+ def repository_compare(self, from_, to, **kwargs):
+ """Return a diff between two branches/commits.
+
+ Args:
+ from_(str): Source branch/SHA
+ to(str): Destination branch/SHA
+ **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:
+ str: The diff
+ """
+ path = "/projects/%s/repository/compare" % self.get_id()
+ query_data = {"from": from_, "to": to}
+ return self.manager.gitlab.http_get(path, query_data=query_data, **kwargs)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabGetError)
+ def repository_contributors(self, **kwargs):
+ """Return a list of contributors for the project.
+
+ 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
+ GitlabGetError: If the server failed to perform the request
+
+ Returns:
+ list: The contributors
+ """
+ path = "/projects/%s/repository/contributors" % self.get_id()
+ return self.manager.gitlab.http_list(path, **kwargs)
+
+ @cli.register_custom_action("Project", tuple(), ("sha",))
+ @exc.on_http_error(exc.GitlabListError)
+ def repository_archive(
+ self, sha=None, streamed=False, action=None, chunk_size=1024, **kwargs
+ ):
+ """Return a tarball of the repository.
+
+ Args:
+ sha (str): ID of the commit (default branch by default)
+ 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
+
+ Returns:
+ str: The binary data of the archive
+ """
+ path = "/projects/%s/repository/archive" % self.get_id()
+ query_data = {}
+ if sha:
+ query_data["sha"] = sha
+ result = self.manager.gitlab.http_get(
+ path, query_data=query_data, raw=True, streamed=streamed, **kwargs
+ )
+ return utils.response_content(result, streamed, action, chunk_size)
+
+ @cli.register_custom_action("Project", ("forked_from_id",))
+ @exc.on_http_error(exc.GitlabCreateError)
+ def create_fork_relation(self, forked_from_id, **kwargs):
+ """Create a forked from/to relation between existing projects.
+
+ Args:
+ forked_from_id (int): The ID of the project that was forked from
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCreateError: If the relation could not be created
+ """
+ path = "/projects/%s/fork/%s" % (self.get_id(), forked_from_id)
+ self.manager.gitlab.http_post(path, **kwargs)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def delete_fork_relation(self, **kwargs):
+ """Delete a forked relation between existing projects.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the server failed to perform the request
+ """
+ path = "/projects/%s/fork" % self.get_id()
+ self.manager.gitlab.http_delete(path, **kwargs)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def delete_merged_branches(self, **kwargs):
+ """Delete merged branches.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the server failed to perform the request
+ """
+ path = "/projects/%s/repository/merged_branches" % self.get_id()
+ self.manager.gitlab.http_delete(path, **kwargs)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabGetError)
+ def languages(self, **kwargs):
+ """Get languages used in the project with percentage value.
+
+ 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
+ """
+ path = "/projects/%s/languages" % self.get_id()
+ return self.manager.gitlab.http_get(path, **kwargs)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabCreateError)
+ def star(self, **kwargs):
+ """Star a 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/star" % self.get_id()
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def unstar(self, **kwargs):
+ """Unstar a project.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the server failed to perform the request
+ """
+ path = "/projects/%s/unstar" % self.get_id()
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabCreateError)
+ def archive(self, **kwargs):
+ """Archive a 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/archive" % self.get_id()
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def unarchive(self, **kwargs):
+ """Unarchive a project.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the server failed to perform the request
+ """
+ path = "/projects/%s/unarchive" % self.get_id()
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+
+ @cli.register_custom_action(
+ "Project", ("group_id", "group_access"), ("expires_at",)
+ )
+ @exc.on_http_error(exc.GitlabCreateError)
+ def share(self, group_id, group_access, expires_at=None, **kwargs):
+ """Share the project with a group.
+
+ Args:
+ group_id (int): ID of the group.
+ group_access (int): Access level for the group.
+ **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/share" % self.get_id()
+ data = {
+ "group_id": group_id,
+ "group_access": group_access,
+ "expires_at": expires_at,
+ }
+ self.manager.gitlab.http_post(path, post_data=data, **kwargs)
+
+ @cli.register_custom_action("Project", ("group_id",))
+ @exc.on_http_error(exc.GitlabDeleteError)
+ def unshare(self, group_id, **kwargs):
+ """Delete a shared project link within a group.
+
+ Args:
+ group_id (int): ID of the group.
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabDeleteError: If the server failed to perform the request
+ """
+ path = "/projects/%s/share/%s" % (self.get_id(), group_id)
+ self.manager.gitlab.http_delete(path, **kwargs)
+
+ # variables not supported in CLI
+ @cli.register_custom_action("Project", ("ref", "token"))
+ @exc.on_http_error(exc.GitlabCreateError)
+ def trigger_pipeline(self, ref, token, variables=None, **kwargs):
+ """Trigger a CI build.
+
+ See https://gitlab.com/help/ci/triggers/README.md#trigger-a-build
+
+ Args:
+ ref (str): Commit to build; can be a branch name or a tag
+ token (str): The trigger token
+ variables (dict): Variables passed to the build script
+ **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
+ """
+ variables = variables or {}
+ path = "/projects/%s/trigger/pipeline" % self.get_id()
+ post_data = {"ref": ref, "token": token, "variables": variables}
+ attrs = self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
+ return ProjectPipeline(self.pipelines, attrs)
+
+ @cli.register_custom_action("Project")
+ @exc.on_http_error(exc.GitlabHousekeepingError)
+ def housekeeping(self, **kwargs):
+ """Start the housekeeping task.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabHousekeepingError: If the server failed to perform the
+ request
+ """
+ path = "/projects/%s/housekeeping" % self.get_id()
+ self.manager.gitlab.http_post(path, **kwargs)
+
+ # see #56 - add file attachment features
+ @cli.register_custom_action("Project", ("filename", "filepath"))
+ @exc.on_http_error(exc.GitlabUploadError)
+ def upload(self, filename, filedata=None, filepath=None, **kwargs):
+ """Upload the specified file into the project.
+
+ .. note::
+
+ Either ``filedata`` or ``filepath`` *MUST* be specified.
+
+ Args:
+ filename (str): The name of the file being uploaded
+ filedata (bytes): The raw data of the file being uploaded
+ filepath (str): The path to a local file to upload (optional)
+
+ Raises:
+ GitlabConnectionError: If the server cannot be reached
+ GitlabUploadError: If the file upload fails
+ GitlabUploadError: If ``filedata`` and ``filepath`` are not
+ specified
+ GitlabUploadError: If both ``filedata`` and ``filepath`` are
+ specified
+
+ Returns:
+ dict: A ``dict`` with the keys:
+ * ``alt`` - The alternate text for the upload
+ * ``url`` - The direct url to the uploaded file
+ * ``markdown`` - Markdown for the uploaded file
+ """
+ if filepath is None and filedata is None:
+ raise GitlabUploadError("No file contents or path specified")
+
+ if filedata is not None and filepath is not None:
+ raise GitlabUploadError("File contents and file path specified")
+
+ if filepath is not None:
+ with open(filepath, "rb") as f:
+ filedata = f.read()
+
+ url = "/projects/%(id)s/uploads" % {"id": self.id}
+ file_info = {"file": (filename, filedata)}
+ data = self.manager.gitlab.http_post(url, files=file_info)
+
+ 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", "job"), ("job_token",))
+ @exc.on_http_error(exc.GitlabGetError)
+ def artifacts(
+ self, ref_name, job, streamed=False, action=None, chunk_size=1024, **kwargs
+ ):
+ """Get the job artifacts archive from a specific tag or branch.
+
+ 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.
+ job_token (str): Job token for multi-project pipeline triggers.
+ 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/download" % (self.get_id(), ref_name)
+ result = self.manager.gitlab.http_get(
+ path, job=job, streamed=streamed, raw=True, **kwargs
+ )
+ return utils.response_content(result, streamed, action, chunk_size)
+
+ @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"
+ _obj_cls = Project
+ _create_attrs = (
+ tuple(),
+ (
+ "name",
+ "path",
+ "namespace_id",
+ "default_branch",
+ "description",
+ "issues_enabled",
+ "merge_requests_enabled",
+ "jobs_enabled",
+ "wiki_enabled",
+ "snippets_enabled",
+ "issues_access_level",
+ "repository_access_level",
+ "merge_requests_access_level",
+ "forking_access_level",
+ "builds_access_level",
+ "wiki_access_level",
+ "snippets_access_level",
+ "pages_access_level",
+ "emails_disabled",
+ "resolve_outdated_diff_discussions",
+ "container_registry_enabled",
+ "container_expiration_policy_attributes",
+ "shared_runners_enabled",
+ "visibility",
+ "import_url",
+ "public_builds",
+ "only_allow_merge_if_pipeline_succeeds",
+ "only_allow_merge_if_all_discussions_are_resolved",
+ "merge_method",
+ "autoclose_referenced_issues",
+ "remove_source_branch_after_merge",
+ "lfs_enabled",
+ "request_access_enabled",
+ "tag_list",
+ "avatar",
+ "printing_merge_request_link_enabled",
+ "build_git_strategy",
+ "build_timeout",
+ "auto_cancel_pending_pipelines",
+ "build_coverage_regex",
+ "ci_config_path",
+ "auto_devops_enabled",
+ "auto_devops_deploy_strategy",
+ "repository_storage",
+ "approvals_before_merge",
+ "external_authorization_classification_label",
+ "mirror",
+ "mirror_trigger_builds",
+ "initialize_with_readme",
+ "template_name",
+ "template_project_id",
+ "use_custom_template",
+ "group_with_project_templates_id",
+ "packages_enabled",
+ ),
+ )
+ _update_attrs = (
+ tuple(),
+ (
+ "name",
+ "path",
+ "default_branch",
+ "description",
+ "issues_enabled",
+ "merge_requests_enabled",
+ "jobs_enabled",
+ "wiki_enabled",
+ "snippets_enabled",
+ "issues_access_level",
+ "repository_access_level",
+ "merge_requests_access_level",
+ "forking_access_level",
+ "builds_access_level",
+ "wiki_access_level",
+ "snippets_access_level",
+ "pages_access_level",
+ "emails_disabled",
+ "resolve_outdated_diff_discussions",
+ "container_registry_enabled",
+ "container_expiration_policy_attributes",
+ "shared_runners_enabled",
+ "visibility",
+ "import_url",
+ "public_builds",
+ "only_allow_merge_if_pipeline_succeeds",
+ "only_allow_merge_if_all_discussions_are_resolved",
+ "merge_method",
+ "autoclose_referenced_issues",
+ "suggestion_commit_message",
+ "remove_source_branch_after_merge",
+ "lfs_enabled",
+ "request_access_enabled",
+ "tag_list",
+ "avatar",
+ "build_git_strategy",
+ "build_timeout",
+ "auto_cancel_pending_pipelines",
+ "build_coverage_regex",
+ "ci_config_path",
+ "ci_default_git_depth",
+ "auto_devops_enabled",
+ "auto_devops_deploy_strategy",
+ "repository_storage",
+ "approvals_before_merge",
+ "external_authorization_classification_label",
+ "mirror",
+ "mirror_user_id",
+ "mirror_trigger_builds",
+ "only_mirror_protected_branches",
+ "mirror_overwrites_diverged_branches",
+ "packages_enabled",
+ "service_desk_enabled",
+ ),
+ )
+ _types = {"avatar": types.ImageAttribute}
+ _list_filters = (
+ "archived",
+ "id_after",
+ "id_before",
+ "last_activity_after",
+ "last_activity_before",
+ "membership",
+ "min_access_level",
+ "order_by",
+ "owned",
+ "repository_checksum_failed",
+ "repository_storage",
+ "search_namespaces",
+ "search",
+ "simple",
+ "sort",
+ "starred",
+ "statistics",
+ "visibility",
+ "wiki_checksum_failed",
+ "with_custom_attributes",
+ "with_issues_enabled",
+ "with_merge_requests_enabled",
+ "with_programming_language",
+ )
+
+ def import_project(
+ self,
+ file,
+ path,
+ name=None,
+ namespace=None,
+ overwrite=False,
+ override_params=None,
+ **kwargs
+ ):
+ """Import a project from an archive file.
+
+ Args:
+ file: Data or file object containing the project
+ path (str): Name and path for the new project
+ namespace (str): The ID or path of the namespace that the project
+ will be imported to
+ overwrite (bool): If True overwrite an existing project with the
+ same path
+ override_params (dict): Set the specific settings for the project
+ **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:
+ dict: A representation of the import status.
+ """
+ files = {"file": ("file.tar.gz", file, "application/octet-stream")}
+ data = {"path": path, "overwrite": str(overwrite)}
+ if override_params:
+ for k, v in override_params.items():
+ data["override_params[%s]" % k] = v
+ if name is not None:
+ data["name"] = name
+ if namespace:
+ data["namespace"] = namespace
+ return self.gitlab.http_post(
+ "/projects/import", post_data=data, files=files, **kwargs
+ )
+
+ def import_bitbucket_server(
+ self,
+ bitbucket_server_url,
+ bitbucket_server_username,
+ personal_access_token,
+ bitbucket_server_project,
+ bitbucket_server_repo,
+ new_name=None,
+ target_namespace=None,
+ **kwargs
+ ):
+ """Import a project from BitBucket Server to Gitlab (schedule the import)
+
+ This method will return when an import operation has been safely queued,
+ or an error has occurred. After triggering an import, check the
+ `import_status` of the newly created project to detect when the import
+ operation has completed.
+
+ NOTE: this request may take longer than most other API requests.
+ So this method will specify a 60 second default timeout if none is specified.
+ A timeout can be specified via kwargs to override this functionality.
+
+ Args:
+ bitbucket_server_url (str): Bitbucket Server URL
+ bitbucket_server_username (str): Bitbucket Server Username
+ personal_access_token (str): Bitbucket Server personal access
+ token/password
+ bitbucket_server_project (str): Bitbucket Project Key
+ bitbucket_server_repo (str): Bitbucket Repository Name
+ new_name (str): New repository name (Optional)
+ target_namespace (str): Namespace to import repository into.
+ Supports subgroups like /namespace/subgroup (Optional)
+ **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:
+ dict: A representation of the import status.
+
+ Example:
+ ```
+ gl = gitlab.Gitlab_from_config()
+ print("Triggering import")
+ result = gl.projects.import_bitbucket_server(
+ bitbucket_server_url="https://some.server.url",
+ bitbucket_server_username="some_bitbucket_user",
+ personal_access_token="my_password_or_access_token",
+ bitbucket_server_project="my_project",
+ bitbucket_server_repo="my_repo",
+ new_name="gl_project_name",
+ target_namespace="gl_project_path"
+ )
+ project = gl.projects.get(ret['id'])
+ print("Waiting for import to complete")
+ while project.import_status == u'started':
+ time.sleep(1.0)
+ project = gl.projects.get(project.id)
+ print("BitBucket import complete")
+ ```
+ """
+ data = {
+ "bitbucket_server_url": bitbucket_server_url,
+ "bitbucket_server_username": bitbucket_server_username,
+ "personal_access_token": personal_access_token,
+ "bitbucket_server_project": bitbucket_server_project,
+ "bitbucket_server_repo": bitbucket_server_repo,
+ }
+ if new_name:
+ data["new_name"] = new_name
+ if target_namespace:
+ data["target_namespace"] = target_namespace
+ if (
+ "timeout" not in kwargs
+ or self.gitlab.timeout is None
+ or self.gitlab.timeout < 60.0
+ ):
+ # Ensure that this HTTP request has a longer-than-usual default timeout
+ # The base gitlab object tends to have a default that is <10 seconds,
+ # and this is too short for this API command, typically.
+ # On the order of 24 seconds has been measured on a typical gitlab instance.
+ kwargs["timeout"] = 60.0
+ result = self.gitlab.http_post(
+ "/import/bitbucket_server", post_data=data, **kwargs
+ )
+ return result
+
+ def import_github(
+ self, personal_access_token, repo_id, target_namespace, new_name=None, **kwargs
+ ):
+ """Import a project from Github to Gitlab (schedule the import)
+
+ This method will return when an import operation has been safely queued,
+ or an error has occurred. After triggering an import, check the
+ `import_status` of the newly created project to detect when the import
+ operation has completed.
+
+ NOTE: this request may take longer than most other API requests.
+ So this method will specify a 60 second default timeout if none is specified.
+ A timeout can be specified via kwargs to override this functionality.
+
+ Args:
+ personal_access_token (str): GitHub personal access token
+ repo_id (int): Github repository ID
+ target_namespace (str): Namespace to import repo into
+ new_name (str): New repo name (Optional)
+ **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:
+ dict: A representation of the import status.
+
+ Example:
+ ```
+ gl = gitlab.Gitlab_from_config()
+ print("Triggering import")
+ result = gl.projects.import_github(ACCESS_TOKEN,
+ 123456,
+ "my-group/my-subgroup")
+ project = gl.projects.get(ret['id'])
+ print("Waiting for import to complete")
+ while project.import_status == u'started':
+ time.sleep(1.0)
+ project = gl.projects.get(project.id)
+ print("Github import complete")
+ ```
+ """
+ data = {
+ "personal_access_token": personal_access_token,
+ "repo_id": repo_id,
+ "target_namespace": target_namespace,
+ }
+ if new_name:
+ data["new_name"] = new_name
+ if (
+ "timeout" not in kwargs
+ or self.gitlab.timeout is None
+ or self.gitlab.timeout < 60.0
+ ):
+ # Ensure that this HTTP request has a longer-than-usual default timeout
+ # The base gitlab object tends to have a default that is <10 seconds,
+ # and this is too short for this API command, typically.
+ # On the order of 24 seconds has been measured on a typical gitlab instance.
+ kwargs["timeout"] = 60.0
+ result = self.gitlab.http_post("/import/github", post_data=data, **kwargs)
+ return result
+
+
+class ProjectFork(RESTObject):
+ pass
+
+
+class ProjectForkManager(CreateMixin, ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/forks"
+ _obj_cls = ProjectFork
+ _from_parent_attrs = {"project_id": "id"}
+ _list_filters = (
+ "archived",
+ "visibility",
+ "order_by",
+ "sort",
+ "search",
+ "simple",
+ "owned",
+ "membership",
+ "starred",
+ "statistics",
+ "with_custom_attributes",
+ "with_issues_enabled",
+ "with_merge_requests_enabled",
+ )
+ _create_attrs = (tuple(), ("namespace",))
+
+ def create(self, data, **kwargs):
+ """Creates 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 managed object class build with
+ the data sent by the server
+ """
+ path = self.path[:-1] # drop the 's'
+ return CreateMixin.create(self, data, path=path, **kwargs)
+
+
+class ProjectRemoteMirror(SaveMixin, RESTObject):
+ pass
+
+
+class ProjectRemoteMirrorManager(ListMixin, CreateMixin, UpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/remote_mirrors"
+ _obj_cls = ProjectRemoteMirror
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("url",), ("enabled", "only_protected_branches"))
+ _update_attrs = (tuple(), ("enabled", "only_protected_branches"))
diff --git a/gitlab/v4/objects/push_rules.py b/gitlab/v4/objects/push_rules.py
new file mode 100644
index 0000000..8b8c8e4
--- /dev/null
+++ b/gitlab/v4/objects/push_rules.py
@@ -0,0 +1,40 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectPushRules(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectPushRulesManager(
+ GetWithoutIdMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
+):
+ _path = "/projects/%(project_id)s/push_rule"
+ _obj_cls = ProjectPushRules
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (
+ tuple(),
+ (
+ "deny_delete_tag",
+ "member_check",
+ "prevent_secrets",
+ "commit_message_regex",
+ "branch_name_regex",
+ "author_email_regex",
+ "file_name_regex",
+ "max_file_size",
+ ),
+ )
+ _update_attrs = (
+ tuple(),
+ (
+ "deny_delete_tag",
+ "member_check",
+ "prevent_secrets",
+ "commit_message_regex",
+ "branch_name_regex",
+ "author_email_regex",
+ "file_name_regex",
+ "max_file_size",
+ ),
+ )
diff --git a/gitlab/v4/objects/runners.py b/gitlab/v4/objects/runners.py
new file mode 100644
index 0000000..1ce5437
--- /dev/null
+++ b/gitlab/v4/objects/runners.py
@@ -0,0 +1,118 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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",
+ "access_level",
+ "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
+ obj = self.gitlab.http_list(path, query_data, **kwargs)
+ return [self._obj_cls(self, item) for item in obj]
+
+ @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 GroupRunner(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class GroupRunnerManager(NoUpdateMixin, RESTManager):
+ _path = "/groups/%(group_id)s/runners"
+ _obj_cls = GroupRunner
+ _from_parent_attrs = {"group_id": "id"}
+ _create_attrs = (("runner_id",), tuple())
+
+
+class ProjectRunner(ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectRunnerManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/runners"
+ _obj_cls = ProjectRunner
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("runner_id",), tuple())
diff --git a/gitlab/v4/objects/services.py b/gitlab/v4/objects/services.py
new file mode 100644
index 0000000..7667d2a
--- /dev/null
+++ b/gitlab/v4/objects/services.py
@@ -0,0 +1,291 @@
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectService(SaveMixin, ObjectDeleteMixin, RESTObject):
+ pass
+
+
+class ProjectServiceManager(GetMixin, UpdateMixin, DeleteMixin, ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/services"
+ _from_parent_attrs = {"project_id": "id"}
+ _obj_cls = ProjectService
+
+ _service_attrs = {
+ "asana": (("api_key",), ("restrict_to_branch", "push_events")),
+ "assembla": (("token",), ("subdomain", "push_events")),
+ "bamboo": (
+ ("bamboo_url", "build_key", "username", "password"),
+ ("push_events",),
+ ),
+ "bugzilla": (
+ ("new_issue_url", "issues_url", "project_url"),
+ ("description", "title", "push_events"),
+ ),
+ "buildkite": (
+ ("token", "project_url"),
+ ("enable_ssl_verification", "push_events"),
+ ),
+ "campfire": (("token",), ("subdomain", "room", "push_events")),
+ "circuit": (
+ ("webhook",),
+ (
+ "notify_only_broken_pipelines",
+ "branches_to_be_notified",
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "confidential_note_events",
+ "pipeline_events",
+ "wiki_page_events",
+ ),
+ ),
+ "custom-issue-tracker": (
+ ("new_issue_url", "issues_url", "project_url"),
+ ("description", "title", "push_events"),
+ ),
+ "drone-ci": (
+ ("token", "drone_url"),
+ (
+ "enable_ssl_verification",
+ "push_events",
+ "merge_requests_events",
+ "tag_push_events",
+ ),
+ ),
+ "emails-on-push": (
+ ("recipients",),
+ (
+ "disable_diffs",
+ "send_from_committer_email",
+ "push_events",
+ "tag_push_events",
+ "branches_to_be_notified",
+ ),
+ ),
+ "pipelines-email": (
+ ("recipients",),
+ (
+ "add_pusher",
+ "notify_only_broken_builds",
+ "branches_to_be_notified",
+ "notify_only_default_branch",
+ "pipeline_events",
+ ),
+ ),
+ "external-wiki": (("external_wiki_url",), tuple()),
+ "flowdock": (("token",), ("push_events",)),
+ "github": (("token", "repository_url"), ("static_context",)),
+ "hangouts-chat": (
+ ("webhook",),
+ (
+ "notify_only_broken_pipelines",
+ "notify_only_default_branch",
+ "branches_to_be_notified",
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "confidential_note_events",
+ "pipeline_events",
+ "wiki_page_events",
+ ),
+ ),
+ "hipchat": (
+ ("token",),
+ (
+ "color",
+ "notify",
+ "room",
+ "api_version",
+ "server",
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "confidential_note_events",
+ "pipeline_events",
+ ),
+ ),
+ "irker": (
+ ("recipients",),
+ (
+ "default_irc_uri",
+ "server_port",
+ "server_host",
+ "colorize_messages",
+ "push_events",
+ ),
+ ),
+ "jira": (
+ (
+ "url",
+ "username",
+ "password",
+ ),
+ (
+ "api_url",
+ "active",
+ "jira_issue_transition_id",
+ "commit_events",
+ "merge_requests_events",
+ "comment_on_event_enabled",
+ ),
+ ),
+ "slack-slash-commands": (("token",), tuple()),
+ "mattermost-slash-commands": (("token",), ("username",)),
+ "packagist": (
+ ("username", "token"),
+ ("server", "push_events", "merge_requests_events", "tag_push_events"),
+ ),
+ "mattermost": (
+ ("webhook",),
+ (
+ "username",
+ "channel",
+ "notify_only_broken_pipelines",
+ "notify_only_default_branch",
+ "branches_to_be_notified",
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "confidential_note_events",
+ "pipeline_events",
+ "wiki_page_events",
+ "push_channel",
+ "issue_channel",
+ "confidential_issue_channel" "merge_request_channel",
+ "note_channel",
+ "confidential_note_channel",
+ "tag_push_channel",
+ "pipeline_channel",
+ "wiki_page_channel",
+ ),
+ ),
+ "pivotaltracker": (("token",), ("restrict_to_branch", "push_events")),
+ "prometheus": (("api_url",), tuple()),
+ "pushover": (
+ ("api_key", "user_key", "priority"),
+ ("device", "sound", "push_events"),
+ ),
+ "redmine": (
+ ("new_issue_url", "project_url", "issues_url"),
+ ("description", "push_events"),
+ ),
+ "slack": (
+ ("webhook",),
+ (
+ "username",
+ "channel",
+ "notify_only_broken_pipelines",
+ "notify_only_default_branch",
+ "branches_to_be_notified",
+ "commit_events",
+ "confidential_issue_channel",
+ "confidential_issues_events",
+ "confidential_note_channel",
+ "confidential_note_events",
+ "deployment_channel",
+ "deployment_events",
+ "issue_channel",
+ "issues_events",
+ "job_events",
+ "merge_request_channel",
+ "merge_requests_events",
+ "note_channel",
+ "note_events",
+ "pipeline_channel",
+ "pipeline_events",
+ "push_channel",
+ "push_events",
+ "tag_push_channel",
+ "tag_push_events",
+ "wiki_page_channel",
+ "wiki_page_events",
+ ),
+ ),
+ "microsoft-teams": (
+ ("webhook",),
+ (
+ "notify_only_broken_pipelines",
+ "notify_only_default_branch",
+ "branches_to_be_notified",
+ "push_events",
+ "issues_events",
+ "confidential_issues_events",
+ "merge_requests_events",
+ "tag_push_events",
+ "note_events",
+ "confidential_note_events",
+ "pipeline_events",
+ "wiki_page_events",
+ ),
+ ),
+ "teamcity": (
+ ("teamcity_url", "build_type", "username", "password"),
+ ("push_events",),
+ ),
+ "jenkins": (("jenkins_url", "project_name"), ("username", "password")),
+ "mock-ci": (("mock_service_url",), tuple()),
+ "youtrack": (("issues_url", "project_url"), ("description", "push_events")),
+ }
+
+ def get(self, id, **kwargs):
+ """Retrieve a single object.
+
+ Args:
+ id (int or str): ID of the object to retrieve
+ lazy (bool): If True, don't request the server, but create a
+ shallow object giving access to the managers. This is
+ useful if you want to avoid useless calls to the API.
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Returns:
+ object: The generated RESTObject.
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the server cannot perform the request
+ """
+ obj = super(ProjectServiceManager, self).get(id, **kwargs)
+ obj.id = id
+ return obj
+
+ 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 {}
+ super(ProjectServiceManager, self).update(id, new_data, **kwargs)
+ self.id = id
+
+ @cli.register_custom_action("ProjectServiceManager")
+ def available(self, **kwargs):
+ """List the services known by python-gitlab.
+
+ Returns:
+ list (str): The list of service code names.
+ """
+ return list(self._service_attrs.keys())
diff --git a/gitlab/v4/objects/settings.py b/gitlab/v4/objects/settings.py
new file mode 100644
index 0000000..e4d3cc7
--- /dev/null
+++ b/gitlab/v4/objects/settings.py
@@ -0,0 +1,89 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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/sidekiq.py b/gitlab/v4/objects/sidekiq.py
new file mode 100644
index 0000000..0c0c02c
--- /dev/null
+++ b/gitlab/v4/objects/sidekiq.py
@@ -0,0 +1,80 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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/snippets.py b/gitlab/v4/objects/snippets.py
new file mode 100644
index 0000000..ec5de95
--- /dev/null
+++ b/gitlab/v4/objects/snippets.py
@@ -0,0 +1,110 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+from .award_emojis import ProjectSnippetAwardEmojiManager
+from .discussions import ProjectSnippetDiscussionManager
+from .notes import ProjectSnippetNoteManager, ProjectSnippetDiscussionNoteManager
+
+
+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)
+
+
+class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
+ _url = "/projects/%(project_id)s/snippets"
+ _short_print_attr = "title"
+ _managers = (
+ ("awardemojis", "ProjectSnippetAwardEmojiManager"),
+ ("discussions", "ProjectSnippetDiscussionManager"),
+ ("notes", "ProjectSnippetNoteManager"),
+ )
+
+ @cli.register_custom_action("ProjectSnippet")
+ @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 = "%s/%s/raw" % (self.manager.path, 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 ProjectSnippetManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/snippets"
+ _obj_cls = ProjectSnippet
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("title", "file_name", "content", "visibility"), ("description",))
+ _update_attrs = (
+ tuple(),
+ ("title", "file_name", "content", "visibility", "description"),
+ )
diff --git a/gitlab/v4/objects/statistics.py b/gitlab/v4/objects/statistics.py
new file mode 100644
index 0000000..5ae17bf
--- /dev/null
+++ b/gitlab/v4/objects/statistics.py
@@ -0,0 +1,22 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectAdditionalStatistics(RefreshMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectAdditionalStatisticsManager(GetWithoutIdMixin, RESTManager):
+ _path = "/projects/%(project_id)s/statistics"
+ _obj_cls = ProjectAdditionalStatistics
+ _from_parent_attrs = {"project_id": "id"}
+
+
+class ProjectIssuesStatistics(RefreshMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectIssuesStatisticsManager(GetWithoutIdMixin, RESTManager):
+ _path = "/projects/%(project_id)s/issues_statistics"
+ _obj_cls = ProjectIssuesStatistics
+ _from_parent_attrs = {"project_id": "id"}
diff --git a/gitlab/v4/objects/tags.py b/gitlab/v4/objects/tags.py
new file mode 100644
index 0000000..d515ec1
--- /dev/null
+++ b/gitlab/v4/objects/tags.py
@@ -0,0 +1,74 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectTag(ObjectDeleteMixin, RESTObject):
+ _id_attr = "name"
+ _short_print_attr = "name"
+
+ @cli.register_custom_action("ProjectTag", ("description",))
+ def set_release_description(self, description, **kwargs):
+ """Set the release notes on the tag.
+
+ If the release doesn't exist yet, it will be created. If it already
+ exists, its description will be updated.
+
+ Args:
+ description (str): Description of the release.
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCreateError: If the server fails to create the release
+ GitlabUpdateError: If the server fails to update the release
+ """
+ id = self.get_id().replace("/", "%2F")
+ path = "%s/%s/release" % (self.manager.path, id)
+ data = {"description": description}
+ if self.release is None:
+ try:
+ server_data = self.manager.gitlab.http_post(
+ path, post_data=data, **kwargs
+ )
+ except exc.GitlabHttpError as e:
+ raise exc.GitlabCreateError(e.response_code, e.error_message) from e
+ else:
+ try:
+ server_data = self.manager.gitlab.http_put(
+ path, post_data=data, **kwargs
+ )
+ except exc.GitlabHttpError as e:
+ raise exc.GitlabUpdateError(e.response_code, e.error_message) from e
+ self.release = server_data
+
+
+class ProjectTagManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/repository/tags"
+ _obj_cls = ProjectTag
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("tag_name", "ref"), ("message",))
+
+
+class ProjectProtectedTag(ObjectDeleteMixin, RESTObject):
+ _id_attr = "name"
+ _short_print_attr = "name"
+
+
+class ProjectProtectedTagManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/protected_tags"
+ _obj_cls = ProjectProtectedTag
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("name",), ("create_access_level",))
+
+
+class ProjectRelease(RESTObject):
+ _id_attr = "tag_name"
+
+
+class ProjectReleaseManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/releases"
+ _obj_cls = ProjectRelease
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))
diff --git a/gitlab/v4/objects/templates.py b/gitlab/v4/objects/templates.py
new file mode 100644
index 0000000..5334baf
--- /dev/null
+++ b/gitlab/v4/objects/templates.py
@@ -0,0 +1,40 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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/todos.py b/gitlab/v4/objects/todos.py
new file mode 100644
index 0000000..429005c
--- /dev/null
+++ b/gitlab/v4/objects/todos.py
@@ -0,0 +1,45 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+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/triggers.py b/gitlab/v4/objects/triggers.py
new file mode 100644
index 0000000..c30d33a
--- /dev/null
+++ b/gitlab/v4/objects/triggers.py
@@ -0,0 +1,30 @@
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject):
+ @cli.register_custom_action("ProjectTrigger")
+ @exc.on_http_error(exc.GitlabOwnershipError)
+ def take_ownership(self, **kwargs):
+ """Update the owner of a trigger.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabOwnershipError: If the request failed
+ """
+ path = "%s/%s/take_ownership" % (self.manager.path, self.get_id())
+ server_data = self.manager.gitlab.http_post(path, **kwargs)
+ self._update_attrs(server_data)
+
+
+class ProjectTriggerManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/triggers"
+ _obj_cls = ProjectTrigger
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("description",), tuple())
+ _update_attrs = (("description",), tuple())
diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py
new file mode 100644
index 0000000..bcd924e
--- /dev/null
+++ b/gitlab/v4/objects/users.py
@@ -0,0 +1,419 @@
+from gitlab import cli, types
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+from .custom_attributes import UserCustomAttributeManager
+from .events import UserEventManager
+
+
+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
+
+
+class User(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _short_print_attr = "username"
+ _managers = (
+ ("customattributes", "UserCustomAttributeManager"),
+ ("emails", "UserEmailManager"),
+ ("events", "UserEventManager"),
+ ("gpgkeys", "UserGPGKeyManager"),
+ ("identityproviders", "UserIdentityProviderManager"),
+ ("impersonationtokens", "UserImpersonationTokenManager"),
+ ("keys", "UserKeyManager"),
+ ("memberships", "UserMembershipManager"),
+ ("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",
+ "two_factor",
+ )
+ _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",
+ "public_email",
+ "private_profile",
+ "color_scheme_id",
+ "theme_id",
+ ),
+ )
+ _update_attrs = (
+ ("email", "username", "name"),
+ (
+ "password",
+ "skype",
+ "linkedin",
+ "twitter",
+ "projects_limit",
+ "extern_uid",
+ "provider",
+ "bio",
+ "admin",
+ "can_create_group",
+ "website_url",
+ "skip_reconfirmation",
+ "external",
+ "organization",
+ "location",
+ "avatar",
+ "public_email",
+ "private_profile",
+ "color_scheme_id",
+ "theme_id",
+ ),
+ )
+ _types = {"confirm": types.LowercaseStringAttribute, "avatar": types.ImageAttribute}
+
+
+class ProjectUser(RESTObject):
+ pass
+
+
+class ProjectUserManager(ListMixin, RESTManager):
+ _path = "/projects/%(project_id)s/users"
+ _obj_cls = ProjectUser
+ _from_parent_attrs = {"project_id": "id"}
+ _list_filters = ("search",)
+
+
+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 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 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 UserStatus(RESTObject):
+ pass
+
+
+class UserStatusManager(GetWithoutIdMixin, RESTManager):
+ _path = "/users/%(user_id)s/status"
+ _obj_cls = UserStatus
+ _from_parent_attrs = {"user_id": "id"}
+
+
+class UserIdentityProviderManager(DeleteMixin, RESTManager):
+ """Manager for user identities.
+
+ This manager does not actually manage objects but enables
+ functionality for deletion of user identities by provider.
+ """
+
+ _path = "/users/%(user_id)s/identities"
+ _from_parent_attrs = {"user_id": "id"}
+
+
+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 UserMembership(RESTObject):
+ _id_attr = "source_id"
+
+
+class UserMembershipManager(RetrieveMixin, RESTManager):
+ _path = "/users/%(user_id)s/memberships"
+ _obj_cls = UserMembership
+ _from_parent_attrs = {"user_id": "id"}
+ _list_filters = ("type",)
+
+
+# Having this outside projects avoids circular imports due to ProjectUser
+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",
+ "with_custom_attributes",
+ "with_programming_language",
+ "wiki_checksum_failed",
+ "repository_checksum_failed",
+ "min_access_level",
+ "id_after",
+ "id_before",
+ )
+
+ 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)
diff --git a/gitlab/v4/objects/wikis.py b/gitlab/v4/objects/wikis.py
new file mode 100644
index 0000000..4c8dc89
--- /dev/null
+++ b/gitlab/v4/objects/wikis.py
@@ -0,0 +1,16 @@
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+class ProjectWiki(SaveMixin, ObjectDeleteMixin, RESTObject):
+ _id_attr = "slug"
+ _short_print_attr = "slug"
+
+
+class ProjectWikiManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/wikis"
+ _obj_cls = ProjectWiki
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("title", "content"), ("format",))
+ _update_attrs = (tuple(), ("title", "content", "format"))
+ _list_filters = ("with_content",)