diff options
Diffstat (limited to 'gitlab')
76 files changed, 0 insertions, 11317 deletions
diff --git a/gitlab/__init__.py b/gitlab/__init__.py deleted file mode 100644 index 7b79f22..0000000 --- a/gitlab/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -"""Wrapper for the GitLab API.""" - -import warnings - -import gitlab.config # noqa: F401 -from gitlab.__version__ import ( # noqa: F401 - __author__, - __copyright__, - __email__, - __license__, - __title__, - __version__, -) -from gitlab.client import Gitlab, GitlabList # noqa: F401 -from gitlab.const import * # noqa: F401,F403 -from gitlab.exceptions import * # noqa: F401,F403 - -warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab") diff --git a/gitlab/__main__.py b/gitlab/__main__.py deleted file mode 100644 index e1a914c..0000000 --- a/gitlab/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -import gitlab.cli - -if __name__ == "__main__": - gitlab.cli.main() diff --git a/gitlab/__version__.py b/gitlab/__version__.py deleted file mode 100644 index d7e8431..0000000 --- a/gitlab/__version__.py +++ /dev/null @@ -1,6 +0,0 @@ -__author__ = "Gauvain Pocentek, python-gitlab team" -__copyright__ = "Copyright 2013-2019 Gauvain Pocentek, 2019-2021 python-gitlab team" -__email__ = "gauvainpocentek@gmail.com" -__license__ = "LGPL3" -__title__ = "python-gitlab" -__version__ = "2.10.1" diff --git a/gitlab/base.py b/gitlab/base.py deleted file mode 100644 index a4a1ef9..0000000 --- a/gitlab/base.py +++ /dev/null @@ -1,331 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import importlib -from types import ModuleType -from typing import Any, Dict, Iterable, NamedTuple, Optional, Tuple, Type - -from gitlab import types as g_types -from gitlab.exceptions import GitlabParsingError - -from .client import Gitlab, GitlabList - -__all__ = [ - "RequiredOptional", - "RESTObject", - "RESTObjectList", - "RESTManager", -] - - -class RESTObject(object): - """Represents an object built from server data. - - It holds the attributes know from the server, and the updated attributes in - another. This allows smart updates, if the object allows it. - - You can redefine ``_id_attr`` in child classes to specify which attribute - must be used as uniq ID. ``None`` means that the object can be updated - without ID in the url. - """ - - _id_attr: Optional[str] = "id" - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _short_print_attr: Optional[str] = None - _updated_attrs: Dict[str, Any] - manager: "RESTManager" - - def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None: - if not isinstance(attrs, dict): - raise GitlabParsingError( - "Attempted to initialize RESTObject with a non-dictionary value: " - "{!r}\nThis likely indicates an incorrect or malformed server " - "response.".format(attrs) - ) - self.__dict__.update( - { - "manager": manager, - "_attrs": attrs, - "_updated_attrs": {}, - "_module": importlib.import_module(self.__module__), - } - ) - self.__dict__["_parent_attrs"] = self.manager.parent_attrs - self._create_managers() - - def __getstate__(self) -> Dict[str, Any]: - state = self.__dict__.copy() - module = state.pop("_module") - state["_module_name"] = module.__name__ - return state - - def __setstate__(self, state: Dict[str, Any]) -> None: - module_name = state.pop("_module_name") - self.__dict__.update(state) - self.__dict__["_module"] = importlib.import_module(module_name) - - def __getattr__(self, name: str) -> Any: - try: - return self.__dict__["_updated_attrs"][name] - except KeyError: - try: - value = self.__dict__["_attrs"][name] - - # If the value is a list, we copy it in the _updated_attrs dict - # because we are not able to detect changes made on the object - # (append, insert, pop, ...). Without forcing the attr - # creation __setattr__ is never called, the list never ends up - # in the _updated_attrs dict, and the update() and save() - # method never push the new data to the server. - # See https://github.com/python-gitlab/python-gitlab/issues/306 - # - # note: _parent_attrs will only store simple values (int) so we - # don't make this check in the next except block. - if isinstance(value, list): - self.__dict__["_updated_attrs"][name] = value[:] - return self.__dict__["_updated_attrs"][name] - - return value - - except KeyError: - try: - return self.__dict__["_parent_attrs"][name] - except KeyError: - raise AttributeError(name) - - def __setattr__(self, name: str, value: Any) -> None: - self.__dict__["_updated_attrs"][name] = value - - def __str__(self) -> str: - data = self._attrs.copy() - data.update(self._updated_attrs) - return "%s => %s" % (type(self), data) - - def __repr__(self) -> str: - if self._id_attr: - return "<%s %s:%s>" % ( - self.__class__.__name__, - self._id_attr, - self.get_id(), - ) - else: - return "<%s>" % self.__class__.__name__ - - def __eq__(self, other: object) -> bool: - if not isinstance(other, RESTObject): - return NotImplemented - if self.get_id() and other.get_id(): - return self.get_id() == other.get_id() - return super(RESTObject, self) == other - - def __ne__(self, other: object) -> bool: - if not isinstance(other, RESTObject): - return NotImplemented - if self.get_id() and other.get_id(): - return self.get_id() != other.get_id() - return super(RESTObject, self) != other - - def __dir__(self) -> Iterable[str]: - return set(self.attributes).union(super(RESTObject, self).__dir__()) - - def __hash__(self) -> int: - if not self.get_id(): - return super(RESTObject, self).__hash__() - return hash(self.get_id()) - - def _create_managers(self) -> None: - # NOTE(jlvillal): We are creating our managers by looking at the class - # annotations. If an attribute is annotated as being a *Manager type - # then we create the manager and assign it to the attribute. - for attr, annotation in sorted(self.__annotations__.items()): - if not isinstance(annotation, (type, str)): - continue - if isinstance(annotation, type): - cls_name = annotation.__name__ - else: - cls_name = annotation - # All *Manager classes are used except for the base "RESTManager" class - if cls_name == "RESTManager" or not cls_name.endswith("Manager"): - continue - cls = getattr(self._module, cls_name) - manager = cls(self.manager.gitlab, parent=self) - # Since we have our own __setattr__ method, we can't use setattr() - self.__dict__[attr] = manager - - def _update_attrs(self, new_attrs: Dict[str, Any]) -> None: - self.__dict__["_updated_attrs"] = {} - self.__dict__["_attrs"] = new_attrs - - def get_id(self) -> Any: - """Returns the id of the resource.""" - if self._id_attr is None or not hasattr(self, self._id_attr): - return None - return getattr(self, self._id_attr) - - @property - def attributes(self) -> Dict[str, Any]: - d = self.__dict__["_updated_attrs"].copy() - d.update(self.__dict__["_attrs"]) - d.update(self.__dict__["_parent_attrs"]) - return d - - -class RESTObjectList(object): - """Generator object representing a list of RESTObject's. - - This generator uses the Gitlab pagination system to fetch new data when - required. - - Note: you should not instantiate such objects, they are returned by calls - to RESTManager.list() - - Args: - manager: Manager to attach to the created objects - obj_cls: Type of objects to create from the json data - _list: A GitlabList object - """ - - def __init__( - self, manager: "RESTManager", obj_cls: Type[RESTObject], _list: GitlabList - ) -> None: - """Creates an objects list from a GitlabList. - - You should not create objects of this type, but use managers list() - methods instead. - - Args: - manager: the RESTManager to attach to the objects - obj_cls: the class of the created objects - _list: the GitlabList holding the data - """ - self.manager = manager - self._obj_cls = obj_cls - self._list = _list - - def __iter__(self) -> "RESTObjectList": - return self - - def __len__(self) -> int: - return len(self._list) - - def __next__(self) -> RESTObject: - return self.next() - - def next(self) -> RESTObject: - data = self._list.next() - return self._obj_cls(self.manager, data) - - @property - def current_page(self) -> int: - """The current page number.""" - return self._list.current_page - - @property - def prev_page(self) -> Optional[int]: - """The previous page number. - - If None, the current page is the first. - """ - return self._list.prev_page - - @property - def next_page(self) -> Optional[int]: - """The next page number. - - If None, the current page is the last. - """ - return self._list.next_page - - @property - def per_page(self) -> int: - """The number of items per page.""" - return self._list.per_page - - @property - def total_pages(self) -> int: - """The total number of pages.""" - return self._list.total_pages - - @property - def total(self) -> int: - """The total number of items.""" - return self._list.total - - -class RequiredOptional(NamedTuple): - required: Tuple[str, ...] = tuple() - optional: Tuple[str, ...] = tuple() - - -class RESTManager(object): - """Base class for CRUD operations on objects. - - Derived class must define ``_path`` and ``_obj_cls``. - - ``_path``: Base URL path on which requests will be sent (e.g. '/projects') - ``_obj_cls``: The class of objects that will be created - """ - - _create_attrs: RequiredOptional = RequiredOptional() - _update_attrs: RequiredOptional = RequiredOptional() - _path: Optional[str] = None - _obj_cls: Optional[Type[RESTObject]] = None - _from_parent_attrs: Dict[str, Any] = {} - _types: Dict[str, Type[g_types.GitlabAttribute]] = {} - - _computed_path: Optional[str] - _parent: Optional[RESTObject] - _parent_attrs: Dict[str, Any] - gitlab: Gitlab - - def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None: - """REST manager constructor. - - Args: - gl (Gitlab): :class:`~gitlab.Gitlab` connection to use to make - requests. - parent: REST object to which the manager is attached. - """ - self.gitlab = gl - self._parent = parent # for nested managers - self._computed_path = self._compute_path() - - @property - def parent_attrs(self) -> Optional[Dict[str, Any]]: - return self._parent_attrs - - def _compute_path(self, path: Optional[str] = None) -> Optional[str]: - self._parent_attrs = {} - if path is None: - path = self._path - if path is None: - return None - if self._parent is None or not self._from_parent_attrs: - return path - - data = { - self_attr: getattr(self._parent, parent_attr, None) - for self_attr, parent_attr in self._from_parent_attrs.items() - } - self._parent_attrs = data - return path % data - - @property - def path(self) -> Optional[str]: - return self._computed_path diff --git a/gitlab/cli.py b/gitlab/cli.py deleted file mode 100644 index c053a38..0000000 --- a/gitlab/cli.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -import argparse -import functools -import re -import sys -from types import ModuleType -from typing import Any, Callable, cast, Dict, Optional, Tuple, Type, TypeVar, Union - -from requests.structures import CaseInsensitiveDict - -import gitlab.config -from gitlab.base import RESTObject - -# This regex is based on: -# https://github.com/jpvanhal/inflection/blob/master/inflection/__init__.py -camel_upperlower_regex = re.compile(r"([A-Z]+)([A-Z][a-z])") -camel_lowerupper_regex = re.compile(r"([a-z\d])([A-Z])") - -# custom_actions = { -# cls: { -# action: (mandatory_args, optional_args, in_obj), -# }, -# } -custom_actions: Dict[str, Dict[str, Tuple[Tuple[str, ...], Tuple[str, ...], bool]]] = {} - - -# For an explanation of how these type-hints work see: -# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators -# -# The goal here is that functions which get decorated will retain their types. -__F = TypeVar("__F", bound=Callable[..., Any]) - - -def register_custom_action( - cls_names: Union[str, Tuple[str, ...]], - mandatory: Tuple[str, ...] = tuple(), - optional: Tuple[str, ...] = tuple(), - custom_action: Optional[str] = None, -) -> Callable[[__F], __F]: - def wrap(f: __F) -> __F: - @functools.wraps(f) - def wrapped_f(*args: Any, **kwargs: Any) -> Any: - return f(*args, **kwargs) - - # in_obj defines whether the method belongs to the obj or the manager - in_obj = True - if isinstance(cls_names, tuple): - classes = cls_names - else: - classes = (cls_names,) - - for cls_name in classes: - final_name = cls_name - if cls_name.endswith("Manager"): - final_name = cls_name.replace("Manager", "") - in_obj = False - if final_name not in custom_actions: - custom_actions[final_name] = {} - - action = custom_action or f.__name__.replace("_", "-") - custom_actions[final_name][action] = (mandatory, optional, in_obj) - - return cast(__F, wrapped_f) - - return wrap - - -def die(msg: str, e: Optional[Exception] = None) -> None: - if e: - msg = "%s (%s)" % (msg, e) - sys.stderr.write(msg + "\n") - sys.exit(1) - - -def what_to_cls(what: str, namespace: ModuleType) -> Type[RESTObject]: - classes = CaseInsensitiveDict(namespace.__dict__) - lowercase_class = what.replace("-", "") - - return classes[lowercase_class] - - -def cls_to_what(cls: RESTObject) -> str: - dasherized_uppercase = camel_upperlower_regex.sub(r"\1-\2", cls.__name__) - dasherized_lowercase = camel_lowerupper_regex.sub(r"\1-\2", dasherized_uppercase) - return dasherized_lowercase.lower() - - -def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - add_help=add_help, description="GitLab API Command Line Interface" - ) - parser.add_argument("--version", help="Display the version.", action="store_true") - parser.add_argument( - "-v", - "--verbose", - "--fancy", - help="Verbose mode (legacy format only)", - action="store_true", - ) - parser.add_argument( - "-d", "--debug", help="Debug mode (display HTTP requests)", action="store_true" - ) - parser.add_argument( - "-c", - "--config-file", - action="append", - help="Configuration file to use. Can be used multiple times.", - ) - parser.add_argument( - "-g", - "--gitlab", - help=( - "Which configuration section should " - "be used. If not defined, the default selection " - "will be used." - ), - required=False, - ) - parser.add_argument( - "-o", - "--output", - help="Output format (v4 only): json|legacy|yaml", - required=False, - choices=["json", "legacy", "yaml"], - default="legacy", - ) - parser.add_argument( - "-f", - "--fields", - help=( - "Fields to display in the output (comma " - "separated). Not used with legacy output" - ), - required=False, - ) - - return parser - - -def _get_parser() -> argparse.ArgumentParser: - # NOTE: We must delay import of gitlab.v4.cli until now or - # otherwise it will cause circular import errors - import gitlab.v4.cli - - parser = _get_base_parser() - return gitlab.v4.cli.extend_parser(parser) - - -def _parse_value(v: Any) -> Any: - if isinstance(v, str) and v.startswith("@"): - # If the user-provided value starts with @, we try to read the file - # path provided after @ as the real value. Exit on any error. - try: - with open(v[1:]) as fl: - return fl.read() - except Exception as e: - sys.stderr.write("%s\n" % e) - sys.exit(1) - - return v - - -def docs() -> argparse.ArgumentParser: - """ - Provide a statically generated parser for sphinx only, so we don't need - to provide dummy gitlab config for readthedocs. - """ - if "sphinx" not in sys.modules: - sys.exit("Docs parser is only intended for build_sphinx") - - return _get_parser() - - -def main() -> None: - if "--version" in sys.argv: - print(gitlab.__version__) - sys.exit(0) - - parser = _get_base_parser(add_help=False) - - # This first parsing step is used to find the gitlab config to use, and - # load the propermodule (v3 or v4) accordingly. At that point we don't have - # any subparser setup - (options, _) = parser.parse_known_args(sys.argv) - try: - config = gitlab.config.GitlabConfigParser(options.gitlab, options.config_file) - except gitlab.config.ConfigError as e: - if "--help" in sys.argv or "-h" in sys.argv: - parser.print_help() - sys.exit(0) - sys.exit(e) - # We only support v4 API at this time - if config.api_version not in ("4",): - raise ModuleNotFoundError(name="gitlab.v%s.cli" % config.api_version) - - # Now we build the entire set of subcommands and do the complete parsing - parser = _get_parser() - try: - import argcomplete # type: ignore - - argcomplete.autocomplete(parser) - except Exception: - pass - args = parser.parse_args() - - config_files = args.config_file - gitlab_id = args.gitlab - verbose = args.verbose - output = args.output - fields = [] - if args.fields: - fields = [x.strip() for x in args.fields.split(",")] - debug = args.debug - action = args.whaction - what = args.what - - args_dict = vars(args) - # Remove CLI behavior-related args - for item in ( - "gitlab", - "config_file", - "verbose", - "debug", - "what", - "whaction", - "version", - "output", - ): - args_dict.pop(item) - args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None} - - try: - gl = gitlab.Gitlab.from_config(gitlab_id, config_files) - if gl.private_token or gl.oauth_token or gl.job_token: - gl.auth() - except Exception as e: - die(str(e)) - - if debug: - gl.enable_debug() - - gitlab.v4.cli.run(gl, what, action, args_dict, verbose, output, fields) diff --git a/gitlab/client.py b/gitlab/client.py deleted file mode 100644 index 8bec64f..0000000 --- a/gitlab/client.py +++ /dev/null @@ -1,1011 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -"""Wrapper for the GitLab API.""" - -import time -from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union - -import requests -import requests.utils -from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore - -import gitlab.config -import gitlab.const -import gitlab.exceptions -from gitlab import utils - -REDIRECT_MSG = ( - "python-gitlab detected a {status_code} ({reason!r}) redirection. You must update " - "your GitLab URL to the correct URL to avoid issues. The redirection was from: " - "{source!r} to {target!r}" -) - - -class Gitlab(object): - """Represents a GitLab server connection. - - Args: - url (str): The URL of the GitLab server (defaults to https://gitlab.com). - private_token (str): The user private token - oauth_token (str): An oauth token - job_token (str): A CI job token - ssl_verify (bool|str): Whether SSL certificates should be validated. If - the value is a string, it is the path to a CA file used for - certificate validation. - timeout (float): Timeout to use for requests to the GitLab server. - http_username (str): Username for HTTP authentication - http_password (str): Password for HTTP authentication - api_version (str): Gitlab API version to use (support for 4 only) - pagination (str): Can be set to 'keyset' to use keyset pagination - order_by (str): Set order_by globally - user_agent (str): A custom user agent to use for making HTTP requests. - retry_transient_errors (bool): Whether to retry after 500, 502, 503, or - 504 responses. Defaults to False. - """ - - def __init__( - self, - url: Optional[str] = None, - private_token: Optional[str] = None, - oauth_token: Optional[str] = None, - job_token: Optional[str] = None, - ssl_verify: Union[bool, str] = True, - http_username: Optional[str] = None, - http_password: Optional[str] = None, - timeout: Optional[float] = None, - api_version: str = "4", - session: Optional[requests.Session] = None, - per_page: Optional[int] = None, - pagination: Optional[str] = None, - order_by: Optional[str] = None, - user_agent: str = gitlab.const.USER_AGENT, - retry_transient_errors: bool = False, - ) -> None: - - self._api_version = str(api_version) - self._server_version: Optional[str] = None - self._server_revision: Optional[str] = None - self._base_url = self._get_base_url(url) - self._url = "%s/api/v%s" % (self._base_url, api_version) - #: Timeout to use for requests to gitlab server - self.timeout = timeout - self.retry_transient_errors = retry_transient_errors - #: Headers that will be used in request to GitLab - self.headers = {"User-Agent": user_agent} - - #: Whether SSL certificates should be validated - self.ssl_verify = ssl_verify - - self.private_token = private_token - self.http_username = http_username - self.http_password = http_password - self.oauth_token = oauth_token - self.job_token = job_token - self._set_auth_info() - - #: Create a session object for requests - self.session = session or requests.Session() - - self.per_page = per_page - self.pagination = pagination - self.order_by = order_by - - # We only support v4 API at this time - if self._api_version not in ("4",): - raise ModuleNotFoundError(name="gitlab.v%s.objects" % self._api_version) - # NOTE: We must delay import of gitlab.v4.objects until now or - # otherwise it will cause circular import errors - import gitlab.v4.objects - - objects = gitlab.v4.objects - self._objects = objects - - self.broadcastmessages = objects.BroadcastMessageManager(self) - """See :class:`~gitlab.v4.objects.BroadcastMessageManager`""" - self.deploykeys = objects.DeployKeyManager(self) - """See :class:`~gitlab.v4.objects.DeployKeyManager`""" - self.deploytokens = objects.DeployTokenManager(self) - """See :class:`~gitlab.v4.objects.DeployTokenManager`""" - self.geonodes = objects.GeoNodeManager(self) - """See :class:`~gitlab.v4.objects.GeoNodeManager`""" - self.gitlabciymls = objects.GitlabciymlManager(self) - """See :class:`~gitlab.v4.objects.GitlabciymlManager`""" - self.gitignores = objects.GitignoreManager(self) - """See :class:`~gitlab.v4.objects.GitignoreManager`""" - self.groups = objects.GroupManager(self) - """See :class:`~gitlab.v4.objects.GroupManager`""" - self.hooks = objects.HookManager(self) - """See :class:`~gitlab.v4.objects.HookManager`""" - self.issues = objects.IssueManager(self) - """See :class:`~gitlab.v4.objects.IssueManager`""" - self.issues_statistics = objects.IssuesStatisticsManager(self) - """See :class:`~gitlab.v4.objects.IssuesStatisticsManager`""" - self.keys = objects.KeyManager(self) - """See :class:`~gitlab.v4.objects.KeyManager`""" - self.ldapgroups = objects.LDAPGroupManager(self) - """See :class:`~gitlab.v4.objects.LDAPGroupManager`""" - self.licenses = objects.LicenseManager(self) - """See :class:`~gitlab.v4.objects.LicenseManager`""" - self.namespaces = objects.NamespaceManager(self) - """See :class:`~gitlab.v4.objects.NamespaceManager`""" - self.mergerequests = objects.MergeRequestManager(self) - """See :class:`~gitlab.v4.objects.MergeRequestManager`""" - self.notificationsettings = objects.NotificationSettingsManager(self) - """See :class:`~gitlab.v4.objects.NotificationSettingsManager`""" - self.projects = objects.ProjectManager(self) - """See :class:`~gitlab.v4.objects.ProjectManager`""" - self.runners = objects.RunnerManager(self) - """See :class:`~gitlab.v4.objects.RunnerManager`""" - self.settings = objects.ApplicationSettingsManager(self) - """See :class:`~gitlab.v4.objects.ApplicationSettingsManager`""" - self.appearance = objects.ApplicationAppearanceManager(self) - """See :class:`~gitlab.v4.objects.ApplicationAppearanceManager`""" - self.sidekiq = objects.SidekiqManager(self) - """See :class:`~gitlab.v4.objects.SidekiqManager`""" - self.snippets = objects.SnippetManager(self) - """See :class:`~gitlab.v4.objects.SnippetManager`""" - self.users = objects.UserManager(self) - """See :class:`~gitlab.v4.objects.UserManager`""" - self.todos = objects.TodoManager(self) - """See :class:`~gitlab.v4.objects.TodoManager`""" - self.dockerfiles = objects.DockerfileManager(self) - """See :class:`~gitlab.v4.objects.DockerfileManager`""" - self.events = objects.EventManager(self) - """See :class:`~gitlab.v4.objects.EventManager`""" - self.audit_events = objects.AuditEventManager(self) - """See :class:`~gitlab.v4.objects.AuditEventManager`""" - self.features = objects.FeatureManager(self) - """See :class:`~gitlab.v4.objects.FeatureManager`""" - self.pagesdomains = objects.PagesDomainManager(self) - """See :class:`~gitlab.v4.objects.PagesDomainManager`""" - self.user_activities = objects.UserActivitiesManager(self) - """See :class:`~gitlab.v4.objects.UserActivitiesManager`""" - self.applications = objects.ApplicationManager(self) - """See :class:`~gitlab.v4.objects.ApplicationManager`""" - self.variables = objects.VariableManager(self) - """See :class:`~gitlab.v4.objects.VariableManager`""" - self.personal_access_tokens = objects.PersonalAccessTokenManager(self) - """See :class:`~gitlab.v4.objects.PersonalAccessTokenManager`""" - - def __enter__(self) -> "Gitlab": - return self - - def __exit__(self, *args: Any) -> None: - self.session.close() - - def __getstate__(self) -> Dict[str, Any]: - state = self.__dict__.copy() - state.pop("_objects") - return state - - def __setstate__(self, state: Dict[str, Any]) -> None: - self.__dict__.update(state) - # We only support v4 API at this time - if self._api_version not in ("4",): - raise ModuleNotFoundError(name="gitlab.v%s.objects" % self._api_version) - # NOTE: We must delay import of gitlab.v4.objects until now or - # otherwise it will cause circular import errors - import gitlab.v4.objects - - self._objects = gitlab.v4.objects - - @property - def url(self) -> str: - """The user-provided server URL.""" - return self._base_url - - @property - def api_url(self) -> str: - """The computed API base URL.""" - return self._url - - @property - def api_version(self) -> str: - """The API version used (4 only).""" - return self._api_version - - @classmethod - def from_config( - cls, gitlab_id: Optional[str] = None, config_files: Optional[List[str]] = None - ) -> "Gitlab": - """Create a Gitlab connection from configuration files. - - Args: - gitlab_id (str): ID of the configuration section. - config_files list[str]: List of paths to configuration files. - - Returns: - (gitlab.Gitlab): A Gitlab connection. - - Raises: - gitlab.config.GitlabDataError: If the configuration is not correct. - """ - config = gitlab.config.GitlabConfigParser( - gitlab_id=gitlab_id, config_files=config_files - ) - return cls( - config.url, - private_token=config.private_token, - oauth_token=config.oauth_token, - job_token=config.job_token, - ssl_verify=config.ssl_verify, - timeout=config.timeout, - http_username=config.http_username, - http_password=config.http_password, - api_version=config.api_version, - per_page=config.per_page, - pagination=config.pagination, - order_by=config.order_by, - user_agent=config.user_agent, - retry_transient_errors=config.retry_transient_errors, - ) - - def auth(self) -> None: - """Performs an authentication using private token. - - The `user` attribute will hold a `gitlab.objects.CurrentUser` object on - success. - """ - self.user = self._objects.CurrentUserManager(self).get() - - def version(self) -> Tuple[str, str]: - """Returns the version and revision of the gitlab server. - - Note that self.version and self.revision will be set on the gitlab - object. - - Returns: - tuple (str, str): The server version and server revision. - ('unknown', 'unknwown') if the server doesn't - perform as expected. - """ - if self._server_version is None: - try: - data = self.http_get("/version") - if isinstance(data, dict): - self._server_version = data["version"] - self._server_revision = data["revision"] - else: - self._server_version = "unknown" - self._server_revision = "unknown" - except Exception: - self._server_version = "unknown" - self._server_revision = "unknown" - - return cast(str, self._server_version), cast(str, self._server_revision) - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabVerifyError) - def lint(self, content: str, **kwargs: Any) -> Tuple[bool, List[str]]: - """Validate a gitlab CI configuration. - - Args: - content (txt): The .gitlab-ci.yml content - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabVerifyError: If the validation could not be done - - Returns: - tuple: (True, []) if the file is valid, (False, errors(list)) - otherwise - """ - post_data = {"content": content} - data = self.http_post("/ci/lint", post_data=post_data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(data, requests.Response) - return (data["status"] == "valid", data["errors"]) - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabMarkdownError) - def markdown( - self, text: str, gfm: bool = False, project: Optional[str] = None, **kwargs: Any - ) -> str: - """Render an arbitrary Markdown document. - - Args: - text (str): The markdown text to render - gfm (bool): Render text using GitLab Flavored Markdown. Default is - False - project (str): Full path of a project used a context when `gfm` is - True - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabMarkdownError: If the server cannot perform the request - - Returns: - str: The HTML rendering of the markdown text. - """ - post_data = {"text": text, "gfm": gfm} - if project is not None: - post_data["project"] = project - data = self.http_post("/markdown", post_data=post_data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(data, requests.Response) - return data["html"] - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabLicenseError) - def get_license(self, **kwargs: Any) -> Dict[str, Any]: - """Retrieve information about the current license. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - - Returns: - dict: The current license information - """ - result = self.http_get("/license", **kwargs) - if isinstance(result, dict): - return result - return {} - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabLicenseError) - def set_license(self, license: str, **kwargs: Any) -> Dict[str, Any]: - """Add a new license. - - Args: - license (str): The license string - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabPostError: If the server cannot perform the request - - Returns: - dict: The new license information - """ - data = {"license": license} - result = self.http_post("/license", post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - def _set_auth_info(self) -> None: - tokens = [ - token - for token in [self.private_token, self.oauth_token, self.job_token] - if token - ] - if len(tokens) > 1: - raise ValueError( - "Only one of private_token, oauth_token or job_token should " - "be defined" - ) - if (self.http_username and not self.http_password) or ( - not self.http_username and self.http_password - ): - raise ValueError( - "Both http_username and http_password should " "be defined" - ) - if self.oauth_token and self.http_username: - raise ValueError( - "Only one of oauth authentication or http " - "authentication should be defined" - ) - - self._http_auth = None - if self.private_token: - self.headers.pop("Authorization", None) - self.headers["PRIVATE-TOKEN"] = self.private_token - self.headers.pop("JOB-TOKEN", None) - - if self.oauth_token: - self.headers["Authorization"] = "Bearer %s" % self.oauth_token - self.headers.pop("PRIVATE-TOKEN", None) - self.headers.pop("JOB-TOKEN", None) - - if self.job_token: - self.headers.pop("Authorization", None) - self.headers.pop("PRIVATE-TOKEN", None) - self.headers["JOB-TOKEN"] = self.job_token - - if self.http_username: - self._http_auth = requests.auth.HTTPBasicAuth( - self.http_username, self.http_password - ) - - def enable_debug(self) -> None: - import logging - from http.client import HTTPConnection # noqa - - HTTPConnection.debuglevel = 1 # type: ignore - logging.basicConfig() - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("requests.packages.urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True - - def _get_session_opts(self) -> Dict[str, Any]: - return { - "headers": self.headers.copy(), - "auth": self._http_auth, - "timeout": self.timeout, - "verify": self.ssl_verify, - } - - def _get_base_url(self, url: Optional[str] = None) -> str: - """Return the base URL with the trailing slash stripped. - If the URL is a Falsy value, return the default URL. - Returns: - str: The base URL - """ - if not url: - return gitlab.const.DEFAULT_URL - - return url.rstrip("/") - - def _build_url(self, path: str) -> str: - """Returns the full url from path. - - If path is already a url, return it unchanged. If it's a path, append - it to the stored url. - - Returns: - str: The full URL - """ - if path.startswith("http://") or path.startswith("https://"): - return path - else: - return "%s%s" % (self._url, path) - - def _check_redirects(self, result: requests.Response) -> None: - # Check the requests history to detect 301/302 redirections. - # If the initial verb is POST or PUT, the redirected request will use a - # GET request, leading to unwanted behaviour. - # If we detect a redirection with a POST or a PUT request, we - # raise an exception with a useful error message. - if not result.history: - return - - for item in result.history: - if item.status_code not in (301, 302): - continue - # GET methods can be redirected without issue - if item.request.method == "GET": - continue - target = item.headers.get("location") - raise gitlab.exceptions.RedirectError( - REDIRECT_MSG.format( - status_code=item.status_code, - reason=item.reason, - source=item.url, - target=target, - ) - ) - - def _prepare_send_data( - self, - files: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - ) -> Tuple[ - Optional[Dict[str, Any]], - Optional[Union[Dict[str, Any], MultipartEncoder]], - str, - ]: - if files: - if post_data is None: - post_data = {} - else: - # booleans does not exists for data (neither for MultipartEncoder): - # cast to string int to avoid: 'bool' object has no attribute 'encode' - for k, v in post_data.items(): - if isinstance(v, bool): - post_data[k] = str(int(v)) - post_data["file"] = files.get("file") - post_data["avatar"] = files.get("avatar") - - data = MultipartEncoder(post_data) - return (None, data, data.content_type) - - if raw and post_data: - return (None, post_data, "application/octet-stream") - - return (post_data, None, "application/json") - - def http_request( - self, - verb: str, - path: str, - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - streamed: bool = False, - files: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None, - obey_rate_limit: bool = True, - max_retries: int = 10, - **kwargs: Any, - ) -> requests.Response: - """Make an HTTP request to the Gitlab server. - - Args: - verb (str): The HTTP method to call ('get', 'post', 'put', - 'delete') - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - post_data (dict): Data to send in the body (will be converted to - json by default) - raw (bool): If True, do not convert post_data to json - streamed (bool): Whether the data should be streamed - files (dict): The files to send to the server - timeout (float): The timeout, in seconds, for the request - obey_rate_limit (bool): Whether to obey 429 Too Many Request - responses. Defaults to True. - max_retries (int): Max retries after 429 or transient errors, - set to -1 to retry forever. Defaults to 10. - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - A requests result object. - - Raises: - GitlabHttpError: When the return code is not 2xx - """ - query_data = query_data or {} - url = self._build_url(path) - - params: Dict[str, Any] = {} - utils.copy_dict(params, query_data) - - # Deal with kwargs: by default a user uses kwargs to send data to the - # gitlab server, but this generates problems (python keyword conflicts - # and python-gitlab/gitlab conflicts). - # So we provide a `query_parameters` key: if it's there we use its dict - # value as arguments for the gitlab server, and ignore the other - # arguments, except pagination ones (per_page and page) - if "query_parameters" in kwargs: - utils.copy_dict(params, kwargs["query_parameters"]) - for arg in ("per_page", "page"): - if arg in kwargs: - params[arg] = kwargs[arg] - else: - utils.copy_dict(params, kwargs) - - opts = self._get_session_opts() - - verify = opts.pop("verify") - opts_timeout = opts.pop("timeout") - # If timeout was passed into kwargs, allow it to override the default - if timeout is None: - timeout = opts_timeout - - # We need to deal with json vs. data when uploading files - json, data, content_type = self._prepare_send_data(files, post_data, raw) - opts["headers"]["Content-type"] = content_type - - # Requests assumes that `.` should not be encoded as %2E and will make - # changes to urls using this encoding. Using a prepped request we can - # get the desired behavior. - # The Requests behavior is right but it seems that web servers don't - # always agree with this decision (this is the case with a default - # gitlab installation) - req = requests.Request(verb, url, json=json, data=data, params=params, **opts) - prepped = self.session.prepare_request(req) - if TYPE_CHECKING: - assert prepped.url is not None - prepped.url = utils.sanitized_url(prepped.url) - settings = self.session.merge_environment_settings( - prepped.url, {}, streamed, verify, None - ) - - cur_retries = 0 - while True: - result = self.session.send(prepped, timeout=timeout, **settings) - - self._check_redirects(result) - - if 200 <= result.status_code < 300: - return result - - retry_transient_errors = kwargs.get( - "retry_transient_errors", self.retry_transient_errors - ) - if (429 == result.status_code and obey_rate_limit) or ( - result.status_code in [500, 502, 503, 504] and retry_transient_errors - ): - if max_retries == -1 or cur_retries < max_retries: - wait_time = 2 ** cur_retries * 0.1 - if "Retry-After" in result.headers: - wait_time = int(result.headers["Retry-After"]) - cur_retries += 1 - time.sleep(wait_time) - continue - - error_message = result.content - try: - error_json = result.json() - for k in ("message", "error"): - if k in error_json: - error_message = error_json[k] - except (KeyError, ValueError, TypeError): - pass - - if result.status_code == 401: - raise gitlab.exceptions.GitlabAuthenticationError( - response_code=result.status_code, - error_message=error_message, - response_body=result.content, - ) - - raise gitlab.exceptions.GitlabHttpError( - response_code=result.status_code, - error_message=error_message, - response_body=result.content, - ) - - def http_get( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - streamed: bool = False, - raw: bool = False, - **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: - """Make a GET request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - streamed (bool): Whether the data should be streamed - raw (bool): If True do not try to parse the output as json - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - A requests result object is streamed is True or the content type is - not json. - The parsed json data otherwise. - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - result = self.http_request( - "get", path, query_data=query_data, streamed=streamed, **kwargs - ) - - if ( - result.headers["Content-Type"] == "application/json" - and not streamed - and not raw - ): - try: - return result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - else: - return result - - def http_list( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - as_list: Optional[bool] = None, - **kwargs: Any, - ) -> Union["GitlabList", List[Dict[str, Any]]]: - """Make a GET request to the Gitlab server for list-oriented queries. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projects') - query_data (dict): Data to send as query parameters - **kwargs: Extra options to send to the server (e.g. sudo, page, - per_page) - - Returns: - list: A list of the objects returned by the server. If `as_list` is - False and no pagination-related arguments (`page`, `per_page`, - `all`) are defined then a GitlabList object (generator) is returned - instead. This object will make API calls when needed to fetch the - next items from the server. - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - - # In case we want to change the default behavior at some point - as_list = True if as_list is None else as_list - - get_all = kwargs.pop("all", False) - url = self._build_url(path) - - page = kwargs.get("page") - - if get_all is True and as_list is True: - return list(GitlabList(self, url, query_data, **kwargs)) - - if page or as_list is True: - # pagination requested, we return a list - return list(GitlabList(self, url, query_data, get_next=False, **kwargs)) - - # No pagination, generator requested - return GitlabList(self, url, query_data, **kwargs) - - def http_post( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - files: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: - """Make a POST request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - post_data (dict): Data to send in the body (will be converted to - json by default) - raw (bool): If True, do not convert post_data to json - files (dict): The files to send to the server - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - The parsed json returned by the server if json is return, else the - raw content - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - post_data = post_data or {} - - result = self.http_request( - "post", - path, - query_data=query_data, - post_data=post_data, - files=files, - **kwargs, - ) - try: - if result.headers.get("Content-Type", None) == "application/json": - return result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - return result - - def http_put( - self, - path: str, - query_data: Optional[Dict[str, Any]] = None, - post_data: Optional[Dict[str, Any]] = None, - raw: bool = False, - files: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: - """Make a PUT request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - query_data (dict): Data to send as query parameters - post_data (dict): Data to send in the body (will be converted to - json by default) - raw (bool): If True, do not convert post_data to json - files (dict): The files to send to the server - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - The parsed json returned by the server. - - Raises: - GitlabHttpError: When the return code is not 2xx - GitlabParsingError: If the json data could not be parsed - """ - query_data = query_data or {} - post_data = post_data or {} - - result = self.http_request( - "put", - path, - query_data=query_data, - post_data=post_data, - files=files, - raw=raw, - **kwargs, - ) - try: - return result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - - def http_delete(self, path: str, **kwargs: Any) -> requests.Response: - """Make a DELETE request to the Gitlab server. - - Args: - path (str): Path or full URL to query ('/projects' or - 'http://whatever/v4/api/projecs') - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns: - The requests object. - - Raises: - GitlabHttpError: When the return code is not 2xx - """ - return self.http_request("delete", path, **kwargs) - - @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabSearchError) - def search( - self, scope: str, search: str, **kwargs: Any - ) -> Union["GitlabList", List[Dict[str, Any]]]: - """Search GitLab 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} - return self.http_list("/search", query_data=data, **kwargs) - - -class GitlabList(object): - """Generator representing a list of remote objects. - - The object handles the links returned by a query to the API, and will call - the API again when needed. - """ - - def __init__( - self, - gl: Gitlab, - url: str, - query_data: Dict[str, Any], - get_next: bool = True, - **kwargs: Any, - ) -> None: - self._gl = gl - - # Preserve kwargs for subsequent queries - self._kwargs = kwargs.copy() - - self._query(url, query_data, **self._kwargs) - self._get_next = get_next - - # Remove query_parameters from kwargs, which are saved via the `next` URL - self._kwargs.pop("query_parameters", None) - - def _query( - self, url: str, query_data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> None: - query_data = query_data or {} - result = self._gl.http_request("get", url, query_data=query_data, **kwargs) - try: - links = result.links - if links: - next_url = links["next"]["url"] - else: - next_url = requests.utils.parse_header_links(result.headers["links"])[ - 0 - ]["url"] - self._next_url = next_url - except KeyError: - self._next_url = None - self._current_page: Optional[Union[str, int]] = result.headers.get("X-Page") - self._prev_page: Optional[Union[str, int]] = result.headers.get("X-Prev-Page") - self._next_page: Optional[Union[str, int]] = result.headers.get("X-Next-Page") - self._per_page: Optional[Union[str, int]] = result.headers.get("X-Per-Page") - self._total_pages: Optional[Union[str, int]] = result.headers.get( - "X-Total-Pages" - ) - self._total: Optional[Union[str, int]] = result.headers.get("X-Total") - - try: - self._data: List[Dict[str, Any]] = result.json() - except Exception as e: - raise gitlab.exceptions.GitlabParsingError( - error_message="Failed to parse the server message" - ) from e - - self._current = 0 - - @property - def current_page(self) -> int: - """The current page number.""" - if TYPE_CHECKING: - assert self._current_page is not None - return int(self._current_page) - - @property - def prev_page(self) -> Optional[int]: - """The previous page number. - - If None, the current page is the first. - """ - return int(self._prev_page) if self._prev_page else None - - @property - def next_page(self) -> Optional[int]: - """The next page number. - - If None, the current page is the last. - """ - return int(self._next_page) if self._next_page else None - - @property - def per_page(self) -> int: - """The number of items per page.""" - if TYPE_CHECKING: - assert self._per_page is not None - return int(self._per_page) - - @property - def total_pages(self) -> int: - """The total number of pages.""" - if TYPE_CHECKING: - assert self._total_pages is not None - return int(self._total_pages) - - @property - def total(self) -> int: - """The total number of items.""" - if TYPE_CHECKING: - assert self._total is not None - return int(self._total) - - def __iter__(self) -> "GitlabList": - return self - - def __len__(self) -> int: - if self._total is None: - return 0 - return int(self._total) - - def __next__(self) -> Dict[str, Any]: - return self.next() - - def next(self) -> Dict[str, Any]: - try: - item = self._data[self._current] - self._current += 1 - return item - except IndexError: - pass - - if self._next_url and self._get_next is True: - self._query(self._next_url, **self._kwargs) - return self.next() - - raise StopIteration diff --git a/gitlab/config.py b/gitlab/config.py deleted file mode 100644 index ba14468..0000000 --- a/gitlab/config.py +++ /dev/null @@ -1,249 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import configparser -import os -import shlex -import subprocess -from os.path import expanduser, expandvars -from typing import List, Optional, Union - -from gitlab.const import USER_AGENT - - -def _env_config() -> List[str]: - if "PYTHON_GITLAB_CFG" in os.environ: - return [os.environ["PYTHON_GITLAB_CFG"]] - return [] - - -_DEFAULT_FILES: List[str] = _env_config() + [ - "/etc/python-gitlab.cfg", - os.path.expanduser("~/.python-gitlab.cfg"), -] - -HELPER_PREFIX = "helper:" - -HELPER_ATTRIBUTES = ["job_token", "http_password", "private_token", "oauth_token"] - - -class ConfigError(Exception): - pass - - -class GitlabIDError(ConfigError): - pass - - -class GitlabDataError(ConfigError): - pass - - -class GitlabConfigMissingError(ConfigError): - pass - - -class GitlabConfigHelperError(ConfigError): - pass - - -class GitlabConfigParser(object): - def __init__( - self, gitlab_id: Optional[str] = None, config_files: Optional[List[str]] = None - ) -> None: - self.gitlab_id = gitlab_id - _files = config_files or _DEFAULT_FILES - file_exist = False - for file in _files: - if os.path.exists(file): - file_exist = True - if not file_exist: - raise GitlabConfigMissingError( - "Config file not found. \nPlease create one in " - "one of the following locations: {} \nor " - "specify a config file using the '-c' parameter.".format( - ", ".join(_DEFAULT_FILES) - ) - ) - - self._config = configparser.ConfigParser() - self._config.read(_files) - - if self.gitlab_id is None: - try: - self.gitlab_id = self._config.get("global", "default") - except Exception as e: - raise GitlabIDError( - "Impossible to get the gitlab id (not specified in config file)" - ) from e - - try: - self.url = self._config.get(self.gitlab_id, "url") - except Exception as e: - raise GitlabDataError( - "Impossible to get gitlab informations from " - "configuration (%s)" % self.gitlab_id - ) from e - - self.ssl_verify: Union[bool, str] = True - try: - self.ssl_verify = self._config.getboolean("global", "ssl_verify") - except ValueError: - # Value Error means the option exists but isn't a boolean. - # Get as a string instead as it should then be a local path to a - # CA bundle. - try: - self.ssl_verify = self._config.get("global", "ssl_verify") - except Exception: - pass - except Exception: - pass - try: - self.ssl_verify = self._config.getboolean(self.gitlab_id, "ssl_verify") - except ValueError: - # Value Error means the option exists but isn't a boolean. - # Get as a string instead as it should then be a local path to a - # CA bundle. - try: - self.ssl_verify = self._config.get(self.gitlab_id, "ssl_verify") - except Exception: - pass - except Exception: - pass - - self.timeout = 60 - try: - self.timeout = self._config.getint("global", "timeout") - except Exception: - pass - try: - self.timeout = self._config.getint(self.gitlab_id, "timeout") - except Exception: - pass - - self.private_token = None - try: - self.private_token = self._config.get(self.gitlab_id, "private_token") - except Exception: - pass - - self.oauth_token = None - try: - self.oauth_token = self._config.get(self.gitlab_id, "oauth_token") - except Exception: - pass - - self.job_token = None - try: - self.job_token = self._config.get(self.gitlab_id, "job_token") - except Exception: - pass - - self.http_username = None - self.http_password = None - try: - self.http_username = self._config.get(self.gitlab_id, "http_username") - self.http_password = self._config.get(self.gitlab_id, "http_password") - except Exception: - pass - - self._get_values_from_helper() - - self.api_version = "4" - try: - self.api_version = self._config.get("global", "api_version") - except Exception: - pass - try: - self.api_version = self._config.get(self.gitlab_id, "api_version") - except Exception: - pass - if self.api_version not in ("4",): - raise GitlabDataError("Unsupported API version: %s" % self.api_version) - - self.per_page = None - for section in ["global", self.gitlab_id]: - try: - self.per_page = self._config.getint(section, "per_page") - except Exception: - pass - if self.per_page is not None and not 0 <= self.per_page <= 100: - raise GitlabDataError("Unsupported per_page number: %s" % self.per_page) - - self.pagination = None - try: - self.pagination = self._config.get(self.gitlab_id, "pagination") - except Exception: - pass - - self.order_by = None - try: - self.order_by = self._config.get(self.gitlab_id, "order_by") - except Exception: - pass - - self.user_agent = USER_AGENT - try: - self.user_agent = self._config.get("global", "user_agent") - except Exception: - pass - try: - self.user_agent = self._config.get(self.gitlab_id, "user_agent") - except Exception: - pass - - self.retry_transient_errors = False - try: - self.retry_transient_errors = self._config.getboolean( - "global", "retry_transient_errors" - ) - except Exception: - pass - try: - self.retry_transient_errors = self._config.getboolean( - self.gitlab_id, "retry_transient_errors" - ) - except Exception: - pass - - def _get_values_from_helper(self) -> None: - """Update attributes that may get values from an external helper program""" - for attr in HELPER_ATTRIBUTES: - value = getattr(self, attr) - if not isinstance(value, str): - continue - - if not value.lower().strip().startswith(HELPER_PREFIX): - continue - - helper = value[len(HELPER_PREFIX) :].strip() - commmand = [expanduser(expandvars(token)) for token in shlex.split(helper)] - - try: - value = ( - subprocess.check_output(commmand, stderr=subprocess.PIPE) - .decode("utf-8") - .strip() - ) - except subprocess.CalledProcessError as e: - stderr = e.stderr.decode().strip() - raise GitlabConfigHelperError( - f"Failed to read {attr} value from helper " - f"for {self.gitlab_id}:\n{stderr}" - ) from e - - setattr(self, attr, value) diff --git a/gitlab/const.py b/gitlab/const.py deleted file mode 100644 index c57423e..0000000 --- a/gitlab/const.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2016-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from gitlab.__version__ import __title__, __version__ - -DEFAULT_URL: str = "https://gitlab.com" - -NO_ACCESS: int = 0 -MINIMAL_ACCESS: int = 5 -GUEST_ACCESS: int = 10 -REPORTER_ACCESS: int = 20 -DEVELOPER_ACCESS: int = 30 -MAINTAINER_ACCESS: int = 40 -OWNER_ACCESS: int = 50 - -VISIBILITY_PRIVATE: str = "private" -VISIBILITY_INTERNAL: str = "internal" -VISIBILITY_PUBLIC: str = "public" - -NOTIFICATION_LEVEL_DISABLED: str = "disabled" -NOTIFICATION_LEVEL_PARTICIPATING: str = "participating" -NOTIFICATION_LEVEL_WATCH: str = "watch" -NOTIFICATION_LEVEL_GLOBAL: str = "global" -NOTIFICATION_LEVEL_MENTION: str = "mention" -NOTIFICATION_LEVEL_CUSTOM: str = "custom" - -# Search scopes -# all scopes (global, group and project) -SEARCH_SCOPE_PROJECTS: str = "projects" -SEARCH_SCOPE_ISSUES: str = "issues" -SEARCH_SCOPE_MERGE_REQUESTS: str = "merge_requests" -SEARCH_SCOPE_MILESTONES: str = "milestones" -SEARCH_SCOPE_WIKI_BLOBS: str = "wiki_blobs" -SEARCH_SCOPE_COMMITS: str = "commits" -SEARCH_SCOPE_BLOBS: str = "blobs" -SEARCH_SCOPE_USERS: str = "users" - -# specific global scope -SEARCH_SCOPE_GLOBAL_SNIPPET_TITLES: str = "snippet_titles" - -# specific project scope -SEARCH_SCOPE_PROJECT_NOTES: str = "notes" - -USER_AGENT: str = "{}/{}".format(__title__, __version__) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py deleted file mode 100644 index 6f2d4c4..0000000 --- a/gitlab/exceptions.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import functools -from typing import Any, Callable, cast, Optional, Type, TYPE_CHECKING, TypeVar, Union - - -class GitlabError(Exception): - def __init__( - self, - error_message: Union[str, bytes] = "", - response_code: Optional[int] = None, - response_body: Optional[bytes] = None, - ) -> None: - - Exception.__init__(self, error_message) - # Http status code - self.response_code = response_code - # Full http response - self.response_body = response_body - # Parsed error message from gitlab - try: - # if we receive str/bytes we try to convert to unicode/str to have - # consistent message types (see #616) - if TYPE_CHECKING: - assert isinstance(error_message, bytes) - self.error_message = error_message.decode() - except Exception: - if TYPE_CHECKING: - assert isinstance(error_message, str) - self.error_message = error_message - - def __str__(self) -> str: - if self.response_code is not None: - return "{0}: {1}".format(self.response_code, self.error_message) - else: - return "{0}".format(self.error_message) - - -class GitlabAuthenticationError(GitlabError): - pass - - -class RedirectError(GitlabError): - pass - - -class GitlabParsingError(GitlabError): - pass - - -class GitlabConnectionError(GitlabError): - pass - - -class GitlabOperationError(GitlabError): - pass - - -class GitlabHttpError(GitlabError): - pass - - -class GitlabListError(GitlabOperationError): - pass - - -class GitlabGetError(GitlabOperationError): - pass - - -class GitlabCreateError(GitlabOperationError): - pass - - -class GitlabUpdateError(GitlabOperationError): - pass - - -class GitlabDeleteError(GitlabOperationError): - pass - - -class GitlabSetError(GitlabOperationError): - pass - - -class GitlabProtectError(GitlabOperationError): - pass - - -class GitlabTransferProjectError(GitlabOperationError): - pass - - -class GitlabProjectDeployKeyError(GitlabOperationError): - pass - - -class GitlabCancelError(GitlabOperationError): - pass - - -class GitlabPipelineCancelError(GitlabCancelError): - pass - - -class GitlabRetryError(GitlabOperationError): - pass - - -class GitlabBuildCancelError(GitlabCancelError): - pass - - -class GitlabBuildRetryError(GitlabRetryError): - pass - - -class GitlabBuildPlayError(GitlabRetryError): - pass - - -class GitlabBuildEraseError(GitlabRetryError): - pass - - -class GitlabJobCancelError(GitlabCancelError): - pass - - -class GitlabJobRetryError(GitlabRetryError): - pass - - -class GitlabJobPlayError(GitlabRetryError): - pass - - -class GitlabJobEraseError(GitlabRetryError): - pass - - -class GitlabPipelinePlayError(GitlabRetryError): - pass - - -class GitlabPipelineRetryError(GitlabRetryError): - pass - - -class GitlabBlockError(GitlabOperationError): - pass - - -class GitlabUnblockError(GitlabOperationError): - pass - - -class GitlabDeactivateError(GitlabOperationError): - pass - - -class GitlabActivateError(GitlabOperationError): - pass - - -class GitlabSubscribeError(GitlabOperationError): - pass - - -class GitlabUnsubscribeError(GitlabOperationError): - pass - - -class GitlabMRForbiddenError(GitlabOperationError): - pass - - -class GitlabMRApprovalError(GitlabOperationError): - pass - - -class GitlabMRRebaseError(GitlabOperationError): - pass - - -class GitlabMRClosedError(GitlabOperationError): - pass - - -class GitlabMROnBuildSuccessError(GitlabOperationError): - pass - - -class GitlabTodoError(GitlabOperationError): - pass - - -class GitlabTimeTrackingError(GitlabOperationError): - pass - - -class GitlabUploadError(GitlabOperationError): - pass - - -class GitlabAttachFileError(GitlabOperationError): - pass - - -class GitlabImportError(GitlabOperationError): - pass - - -class GitlabCherryPickError(GitlabOperationError): - pass - - -class GitlabHousekeepingError(GitlabOperationError): - pass - - -class GitlabOwnershipError(GitlabOperationError): - pass - - -class GitlabSearchError(GitlabOperationError): - pass - - -class GitlabStopError(GitlabOperationError): - pass - - -class GitlabMarkdownError(GitlabOperationError): - pass - - -class GitlabVerifyError(GitlabOperationError): - pass - - -class GitlabRenderError(GitlabOperationError): - pass - - -class GitlabRepairError(GitlabOperationError): - pass - - -class GitlabRevertError(GitlabOperationError): - pass - - -class GitlabLicenseError(GitlabOperationError): - pass - - -class GitlabFollowError(GitlabOperationError): - pass - - -class GitlabUnfollowError(GitlabOperationError): - pass - - -# For an explanation of how these type-hints work see: -# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators -# -# The goal here is that functions which get decorated will retain their types. -__F = TypeVar("__F", bound=Callable[..., Any]) - - -def on_http_error(error: Type[Exception]) -> Callable[[__F], __F]: - """Manage GitlabHttpError exceptions. - - This decorator function can be used to catch GitlabHttpError exceptions - raise specialized exceptions instead. - - Args: - error(Exception): The exception type to raise -- must inherit from - GitlabError - """ - - def wrap(f: __F) -> __F: - @functools.wraps(f) - def wrapped_f(*args: Any, **kwargs: Any) -> Any: - try: - return f(*args, **kwargs) - except GitlabHttpError as e: - raise error(e.error_message, e.response_code, e.response_body) from e - - return cast(__F, wrapped_f) - - return wrap diff --git a/gitlab/mixins.py b/gitlab/mixins.py deleted file mode 100644 index 0c2cd94..0000000 --- a/gitlab/mixins.py +++ /dev/null @@ -1,928 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from types import ModuleType -from typing import ( - Any, - Callable, - Dict, - List, - Optional, - Tuple, - Type, - TYPE_CHECKING, - Union, -) - -import requests - -import gitlab -from gitlab import base, cli -from gitlab import exceptions as exc -from gitlab import types as g_types -from gitlab import utils - -__all__ = [ - "GetMixin", - "GetWithoutIdMixin", - "RefreshMixin", - "ListMixin", - "RetrieveMixin", - "CreateMixin", - "UpdateMixin", - "SetMixin", - "DeleteMixin", - "CRUDMixin", - "NoUpdateMixin", - "SaveMixin", - "ObjectDeleteMixin", - "UserAgentDetailMixin", - "AccessRequestMixin", - "DownloadMixin", - "SubscribableMixin", - "TodoMixin", - "TimeTrackingMixin", - "ParticipantsMixin", - "BadgeRenderMixin", -] - -if TYPE_CHECKING: - # When running mypy we use these as the base classes - _RestManagerBase = base.RESTManager - _RestObjectBase = base.RESTObject -else: - _RestManagerBase = object - _RestObjectBase = object - - -class GetMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _optional_get_attrs: Tuple[str, ...] = () - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabGetError) - def get( - self, id: Union[str, int], lazy: bool = False, **kwargs: Any - ) -> base.RESTObject: - """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 - """ - if not isinstance(id, int): - id = utils.clean_str_id(id) - path = "%s/%s" % (self.path, id) - if TYPE_CHECKING: - assert self._obj_cls is not None - if lazy is True: - if TYPE_CHECKING: - assert self._obj_cls._id_attr is not None - return self._obj_cls(self, {self._obj_cls._id_attr: id}) - server_data = self.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - return self._obj_cls(self, server_data) - - -class GetWithoutIdMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _optional_get_attrs: Tuple[str, ...] = () - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabGetError) - def get( - self, id: Optional[Union[int, str]] = None, **kwargs: Any - ) -> Optional[base.RESTObject]: - """Retrieve a single object. - - Args: - **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 - """ - if TYPE_CHECKING: - assert self.path is not None - server_data = self.gitlab.http_get(self.path, **kwargs) - if server_data is None: - return None - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None - return self._obj_cls(self, server_data) - - -class RefreshMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @exc.on_http_error(exc.GitlabGetError) - def refresh(self, **kwargs: Any) -> None: - """Refresh a single object from server. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Returns None (updates the object) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - if self._id_attr: - path = "%s/%s" % (self.manager.path, self.id) - else: - if TYPE_CHECKING: - assert self.manager.path is not None - path = self.manager.path - server_data = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - -class ListMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _list_filters: Tuple[str, ...] = () - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabListError) - def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.RESTObject]]: - """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 - """ - - # Duplicate data to avoid messing with what the user sent us - data = kwargs.copy() - if self.gitlab.per_page: - data.setdefault("per_page", self.gitlab.per_page) - - # global keyset pagination - if self.gitlab.pagination: - data.setdefault("pagination", self.gitlab.pagination) - - if self.gitlab.order_by: - data.setdefault("order_by", self.gitlab.order_by) - - # We get the attributes that need some special transformation - if self._types: - for attr_name, type_cls in self._types.items(): - if attr_name in data.keys(): - type_obj = type_cls(data[attr_name]) - data[attr_name] = type_obj.get_for_api() - - # Allow to overwrite the path, handy for custom listings - path = data.pop("path", self.path) - - if TYPE_CHECKING: - assert self._obj_cls is not None - 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 RetrieveMixin(ListMixin, GetMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - pass - - -class CreateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - def _check_missing_create_attrs(self, data: Dict[str, Any]) -> None: - missing = [] - for attr in self._create_attrs.required: - if attr not in data: - missing.append(attr) - continue - if missing: - raise AttributeError("Missing attributes: %s" % ", ".join(missing)) - - @exc.on_http_error(exc.GitlabCreateError) - def create( - self, data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> base.RESTObject: - """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 - """ - if data is None: - data = {} - - self._check_missing_create_attrs(data) - files = {} - - # We get the attributes that need some special transformation - if self._types: - # Duplicate data to avoid messing with what the user sent us - data = data.copy() - for attr_name, type_cls in self._types.items(): - if attr_name in data.keys(): - type_obj = type_cls(data[attr_name]) - - # if the type if FileAttribute we need to pass the data as - # file - if isinstance(type_obj, g_types.FileAttribute): - k = type_obj.get_file_name(attr_name) - files[attr_name] = (k, data.pop(attr_name)) - else: - data[attr_name] = type_obj.get_for_api() - - # Handle specific URL for creation - path = kwargs.pop("path", self.path) - server_data = self.gitlab.http_post(path, post_data=data, files=files, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None - return self._obj_cls(self, server_data) - - -class UpdateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - _update_uses_post: bool = False - gitlab: gitlab.Gitlab - - def _check_missing_update_attrs(self, data: Dict[str, Any]) -> None: - if TYPE_CHECKING: - assert self._obj_cls is not None - # Remove the id field from the required list as it was previously moved - # to the http path. - required = tuple( - [k for k in self._update_attrs.required if k != self._obj_cls._id_attr] - ) - missing = [] - for attr in required: - if attr not in data: - missing.append(attr) - continue - if missing: - raise AttributeError("Missing attributes: %s" % ", ".join(missing)) - - def _get_update_method( - self, - ) -> Callable[..., Union[Dict[str, Any], requests.Response]]: - """Return the HTTP method to use. - - Returns: - object: http_put (default) or http_post - """ - if self._update_uses_post: - http_method = self.gitlab.http_post - else: - http_method = self.gitlab.http_put - return http_method - - @exc.on_http_error(exc.GitlabUpdateError) - def update( - self, - id: Optional[Union[str, int]] = None, - new_data: Optional[Dict[str, Any]] = None, - **kwargs: Any - ) -> Dict[str, Any]: - """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 {} - - if id is None: - path = self.path - else: - path = "%s/%s" % (self.path, id) - - self._check_missing_update_attrs(new_data) - files = {} - - # We get the attributes that need some special transformation - if self._types: - # Duplicate data to avoid messing with what the user sent us - new_data = new_data.copy() - for attr_name, type_cls in self._types.items(): - if attr_name in new_data.keys(): - type_obj = type_cls(new_data[attr_name]) - - # if the type if FileAttribute we need to pass the data as - # file - if isinstance(type_obj, g_types.FileAttribute): - k = type_obj.get_file_name(attr_name) - files[attr_name] = (k, new_data.pop(attr_name)) - else: - new_data[attr_name] = type_obj.get_for_api() - - http_method = self._get_update_method() - result = http_method(path, post_data=new_data, files=files, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class SetMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabSetError) - def set(self, key: str, value: str, **kwargs: Any) -> base.RESTObject: - """Create or update the object. - - Args: - key (str): The key of the object to create/update - value (str): The value to set for the object - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSetError: If an error occurred - - Returns: - obj: The created/updated attribute - """ - path = "%s/%s" % (self.path, utils.clean_str_id(key)) - data = {"value": value} - server_data = self.gitlab.http_put(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None - return self._obj_cls(self, server_data) - - -class DeleteMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - @exc.on_http_error(exc.GitlabDeleteError) - def delete(self, id: Union[str, int], **kwargs: Any) -> None: - """Delete an object on the server. - - Args: - id: ID of the object to delete - **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 - """ - if id is None: - path = self.path - else: - if not isinstance(id, int): - id = utils.clean_str_id(id) - path = "%s/%s" % (self.path, id) - self.gitlab.http_delete(path, **kwargs) - - -class CRUDMixin(GetMixin, ListMixin, CreateMixin, UpdateMixin, DeleteMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - pass - - -class NoUpdateMixin(GetMixin, ListMixin, CreateMixin, DeleteMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - - pass - - -class SaveMixin(_RestObjectBase): - """Mixin for RESTObject's that can be updated.""" - - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - def _get_updated_data(self) -> Dict[str, Any]: - updated_data = {} - for attr in self.manager._update_attrs.required: - # Get everything required, no matter if it's been updated - updated_data[attr] = getattr(self, attr) - # Add the updated attributes - updated_data.update(self._updated_attrs) - - return updated_data - - def save(self, **kwargs: Any) -> None: - """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() - if TYPE_CHECKING: - assert isinstance(self.manager, UpdateMixin) - server_data = self.manager.update(obj_id, updated_data, **kwargs) - if server_data is not None: - self._update_attrs(server_data) - - -class ObjectDeleteMixin(_RestObjectBase): - """Mixin for RESTObject's that can be deleted.""" - - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - def delete(self, **kwargs: Any) -> None: - """Delete the object from the server. - - Args: - **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 - """ - if TYPE_CHECKING: - assert isinstance(self.manager, DeleteMixin) - self.manager.delete(self.get_id(), **kwargs) - - -class UserAgentDetailMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("Snippet", "ProjectSnippet", "ProjectIssue")) - @exc.on_http_error(exc.GitlabGetError) - def user_agent_detail(self, **kwargs: Any) -> Dict[str, Any]: - """Get the user agent detail. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server cannot perform the request - """ - path = "%s/%s/user_agent_detail" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class AccessRequestMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action( - ("ProjectAccessRequest", "GroupAccessRequest"), tuple(), ("access_level",) - ) - @exc.on_http_error(exc.GitlabUpdateError) - def approve( - self, access_level: int = gitlab.DEVELOPER_ACCESS, **kwargs: Any - ) -> None: - """Approve an access request. - - Args: - access_level (int): The access level for the user - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server fails to perform the request - """ - - path = "%s/%s/approve" % (self.manager.path, self.id) - data = {"access_level": access_level} - server_data = self.manager.gitlab.http_put(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - -class DownloadMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("GroupExport", "ProjectExport")) - @exc.on_http_error(exc.GitlabGetError) - def download( - self, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """Download the archive of a resource export. - - 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 server failed to perform the request - - Returns: - str: The blob content if streamed is False, None otherwise - """ - path = "%s/download" % (self.manager.path) - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - -class SubscribableMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action( - ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel") - ) - @exc.on_http_error(exc.GitlabSubscribeError) - def subscribe(self, **kwargs: Any) -> None: - """Subscribe to the object notifications. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - raises: - GitlabAuthenticationError: If authentication is not correct - GitlabSubscribeError: If the subscription cannot be done - """ - path = "%s/%s/subscribe" % (self.manager.path, self.get_id()) - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - @cli.register_custom_action( - ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel") - ) - @exc.on_http_error(exc.GitlabUnsubscribeError) - def unsubscribe(self, **kwargs: Any) -> None: - """Unsubscribe from the object notifications. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUnsubscribeError: If the unsubscription cannot be done - """ - path = "%s/%s/unsubscribe" % (self.manager.path, self.get_id()) - server_data = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) - self._update_attrs(server_data) - - -class TodoMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTodoError) - def todo(self, **kwargs: Any) -> None: - """Create a todo associated to the object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTodoError: If the todo cannot be set - """ - path = "%s/%s/todo" % (self.manager.path, self.get_id()) - self.manager.gitlab.http_post(path, **kwargs) - - -class TimeTrackingMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def time_stats(self, **kwargs: Any) -> Dict[str, Any]: - """Get time stats for the object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - # Use the existing time_stats attribute if it exist, otherwise make an - # API call - if "time_stats" in self.attributes: - return self.attributes["time_stats"] - - path = "%s/%s/time_stats" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def time_estimate(self, duration: str, **kwargs: Any) -> Dict[str, Any]: - """Set an estimated time of work for the object. - - Args: - duration (str): Duration in human format (e.g. 3h30) - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/time_estimate" % (self.manager.path, self.get_id()) - data = {"duration": duration} - result = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def reset_time_estimate(self, **kwargs: Any) -> Dict[str, Any]: - """Resets estimated time for the object to 0 seconds. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/reset_time_estimate" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def add_spent_time(self, duration: str, **kwargs: Any) -> Dict[str, Any]: - """Add time spent working on the object. - - Args: - duration (str): Duration in human format (e.g. 3h30) - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/add_spent_time" % (self.manager.path, self.get_id()) - data = {"duration": duration} - result = self.manager.gitlab.http_post(path, post_data=data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) - @exc.on_http_error(exc.GitlabTimeTrackingError) - def reset_spent_time(self, **kwargs: Any) -> Dict[str, Any]: - """Resets the time spent working on the object. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabTimeTrackingError: If the time tracking update cannot be done - """ - path = "%s/%s/reset_spent_time" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_post(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class ParticipantsMixin(_RestObjectBase): - _id_attr: Optional[str] - _attrs: Dict[str, Any] - _module: ModuleType - _parent_attrs: Dict[str, Any] - _updated_attrs: Dict[str, Any] - manager: base.RESTManager - - @cli.register_custom_action(("ProjectMergeRequest", "ProjectIssue")) - @exc.on_http_error(exc.GitlabListError) - def participants(self, **kwargs: Any) -> Dict[str, Any]: - """List the participants. - - 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 participants - """ - - path = "%s/%s/participants" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result - - -class BadgeRenderMixin(_RestManagerBase): - @cli.register_custom_action( - ("GroupBadgeManager", "ProjectBadgeManager"), ("link_url", "image_url") - ) - @exc.on_http_error(exc.GitlabRenderError) - def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any]: - """Preview link_url and image_url after interpolation. - - Args: - link_url (str): URL of the badge link - image_url (str): URL of the badge image - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabRenderError: If the rendering failed - - Returns: - dict: The rendering properties - """ - path = "%s/render" % self.path - data = {"link_url": link_url, "image_url": image_url} - result = self.gitlab.http_get(path, data, **kwargs) - if TYPE_CHECKING: - assert not isinstance(result, requests.Response) - return result diff --git a/gitlab/py.typed b/gitlab/py.typed deleted file mode 100644 index e69de29..0000000 --- a/gitlab/py.typed +++ /dev/null diff --git a/gitlab/types.py b/gitlab/types.py deleted file mode 100644 index 22d51e7..0000000 --- a/gitlab/types.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from typing import Any, Optional, TYPE_CHECKING - - -class GitlabAttribute(object): - def __init__(self, value: Any = None) -> None: - self._value = value - - def get(self) -> Any: - return self._value - - def set_from_cli(self, cli_value: Any) -> None: - self._value = cli_value - - def get_for_api(self) -> Any: - return self._value - - -class ListAttribute(GitlabAttribute): - def set_from_cli(self, cli_value: str) -> None: - if not cli_value.strip(): - self._value = [] - else: - self._value = [item.strip() for item in cli_value.split(",")] - - def get_for_api(self) -> str: - # Do not comma-split single value passed as string - if isinstance(self._value, str): - return self._value - - if TYPE_CHECKING: - assert isinstance(self._value, list) - return ",".join([str(x) for x in self._value]) - - -class LowercaseStringAttribute(GitlabAttribute): - def get_for_api(self) -> str: - return str(self._value).lower() - - -class FileAttribute(GitlabAttribute): - def get_file_name(self, attr_name: Optional[str] = None) -> Optional[str]: - return attr_name - - -class ImageAttribute(FileAttribute): - def get_file_name(self, attr_name: Optional[str] = None) -> str: - return "%s.png" % attr_name if attr_name else "image.png" diff --git a/gitlab/utils.py b/gitlab/utils.py deleted file mode 100644 index 91b3fb0..0000000 --- a/gitlab/utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2016-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from typing import Any, Callable, Dict, Optional -from urllib.parse import quote, urlparse - -import requests - - -class _StdoutStream(object): - def __call__(self, chunk: Any) -> None: - print(chunk) - - -def response_content( - response: requests.Response, - streamed: bool, - action: Optional[Callable], - chunk_size: int, -) -> Optional[bytes]: - if streamed is False: - return response.content - - if action is None: - action = _StdoutStream() - - for chunk in response.iter_content(chunk_size=chunk_size): - if chunk: - action(chunk) - return None - - -def copy_dict(dest: Dict[str, Any], src: Dict[str, Any]) -> None: - for k, v in src.items(): - if isinstance(v, dict): - # Transform dict values to new attributes. For example: - # custom_attributes: {'foo', 'bar'} => - # "custom_attributes['foo']": "bar" - for dict_k, dict_v in v.items(): - dest["%s[%s]" % (k, dict_k)] = dict_v - else: - dest[k] = v - - -def clean_str_id(id: str) -> str: - return quote(id, safe="") - - -def sanitized_url(url: str) -> str: - parsed = urlparse(url) - new_path = parsed.path.replace(".", "%2E") - return parsed._replace(path=new_path).geturl() - - -def remove_none_from_dict(data: Dict[str, Any]) -> Dict[str, Any]: - return {k: v for k, v in data.items() if v is not None} diff --git a/gitlab/v4/__init__.py b/gitlab/v4/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/gitlab/v4/__init__.py +++ /dev/null diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py deleted file mode 100644 index 6986552..0000000 --- a/gitlab/v4/cli.py +++ /dev/null @@ -1,500 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import argparse -import operator -import sys -from typing import Any, Dict, List, Optional, Type, TYPE_CHECKING, Union - -import gitlab -import gitlab.base -import gitlab.v4.objects -from gitlab import cli - - -class GitlabCLI(object): - def __init__( - self, gl: gitlab.Gitlab, what: str, action: str, args: Dict[str, str] - ) -> None: - self.cls: Type[gitlab.base.RESTObject] = cli.what_to_cls( - what, namespace=gitlab.v4.objects - ) - self.cls_name = self.cls.__name__ - self.what = what.replace("-", "_") - self.action = action.lower() - self.gl = gl - self.args = args - self.mgr_cls: Union[ - Type[gitlab.mixins.CreateMixin], - Type[gitlab.mixins.DeleteMixin], - Type[gitlab.mixins.GetMixin], - Type[gitlab.mixins.GetWithoutIdMixin], - Type[gitlab.mixins.ListMixin], - Type[gitlab.mixins.UpdateMixin], - ] = getattr(gitlab.v4.objects, self.cls.__name__ + "Manager") - # We could do something smart, like splitting the manager name to find - # parents, build the chain of managers to get to the final object. - # Instead we do something ugly and efficient: interpolate variables in - # the class _path attribute, and replace the value with the result. - if TYPE_CHECKING: - assert self.mgr_cls._path is not None - self.mgr_cls._path = self.mgr_cls._path % self.args - self.mgr = self.mgr_cls(gl) - - if self.mgr_cls._types: - for attr_name, type_cls in self.mgr_cls._types.items(): - if attr_name in self.args.keys(): - obj = type_cls() - obj.set_from_cli(self.args[attr_name]) - self.args[attr_name] = obj.get() - - def __call__(self) -> Any: - # Check for a method that matches object + action - method = "do_%s_%s" % (self.what, self.action) - if hasattr(self, method): - return getattr(self, method)() - - # Fallback to standard actions (get, list, create, ...) - method = "do_%s" % self.action - if hasattr(self, method): - return getattr(self, method)() - - # Finally try to find custom methods - return self.do_custom() - - def do_custom(self) -> Any: - in_obj = cli.custom_actions[self.cls_name][self.action][2] - - # Get the object (lazy), then act - if in_obj: - data = {} - if self.mgr._from_parent_attrs: - for k in self.mgr._from_parent_attrs: - data[k] = self.args[k] - if not issubclass(self.cls, gitlab.mixins.GetWithoutIdMixin): - if TYPE_CHECKING: - assert isinstance(self.cls._id_attr, str) - data[self.cls._id_attr] = self.args.pop(self.cls._id_attr) - obj = self.cls(self.mgr, data) - method_name = self.action.replace("-", "_") - return getattr(obj, method_name)(**self.args) - else: - return getattr(self.mgr, self.action)(**self.args) - - def do_project_export_download(self) -> None: - try: - project = self.gl.projects.get(int(self.args["project_id"]), lazy=True) - export_status = project.exports.get() - if TYPE_CHECKING: - assert export_status is not None - data = export_status.download() - sys.stdout.buffer.write(data) - - except Exception as e: - cli.die("Impossible to download the export", e) - - def do_create(self) -> gitlab.base.RESTObject: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.CreateMixin) - try: - result = self.mgr.create(self.args) - except Exception as e: - cli.die("Impossible to create object", e) - return result - - def do_list( - self, - ) -> Union[gitlab.base.RESTObjectList, List[gitlab.base.RESTObject]]: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.ListMixin) - try: - result = self.mgr.list(**self.args) - except Exception as e: - cli.die("Impossible to list objects", e) - return result - - def do_get(self) -> Optional[gitlab.base.RESTObject]: - if isinstance(self.mgr, gitlab.mixins.GetWithoutIdMixin): - try: - result = self.mgr.get(id=None, **self.args) - except Exception as e: - cli.die("Impossible to get object", e) - return result - - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.GetMixin) - assert isinstance(self.cls._id_attr, str) - - id = self.args.pop(self.cls._id_attr) - try: - result = self.mgr.get(id, lazy=False, **self.args) - except Exception as e: - cli.die("Impossible to get object", e) - return result - - def do_delete(self) -> None: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.DeleteMixin) - assert isinstance(self.cls._id_attr, str) - id = self.args.pop(self.cls._id_attr) - try: - self.mgr.delete(id, **self.args) - except Exception as e: - cli.die("Impossible to destroy object", e) - - def do_update(self) -> Dict[str, Any]: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.UpdateMixin) - if issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin): - id = None - else: - if TYPE_CHECKING: - assert isinstance(self.cls._id_attr, str) - id = self.args.pop(self.cls._id_attr) - - try: - result = self.mgr.update(id, self.args) - except Exception as e: - cli.die("Impossible to update object", e) - return result - - -def _populate_sub_parser_by_class( - cls: Type[gitlab.base.RESTObject], sub_parser: argparse._SubParsersAction -) -> None: - mgr_cls_name = cls.__name__ + "Manager" - mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name) - - for action_name in ["list", "get", "create", "update", "delete"]: - if not hasattr(mgr_cls, action_name): - continue - - sub_parser_action = sub_parser.add_parser(action_name) - sub_parser_action.add_argument("--sudo", required=False) - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - - if action_name == "list": - for x in mgr_cls._list_filters: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - sub_parser_action.add_argument("--page", required=False) - sub_parser_action.add_argument("--per-page", required=False) - sub_parser_action.add_argument("--all", required=False, action="store_true") - - if action_name == "delete": - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - if action_name == "get": - if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin): - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - for x in mgr_cls._optional_get_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if action_name == "create": - for x in mgr_cls._create_attrs.required: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in mgr_cls._create_attrs.optional: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if action_name == "update": - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - for x in mgr_cls._update_attrs.required: - if x != cls._id_attr: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - - for x in mgr_cls._update_attrs.optional: - if x != cls._id_attr: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if cls.__name__ in cli.custom_actions: - name = cls.__name__ - for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) - # Get the attributes for URL/path construction - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - sub_parser_action.add_argument("--sudo", required=False) - - # We need to get the object somehow - if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin): - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - required, optional, dummy = cli.custom_actions[name][action_name] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in required - if x != cls._id_attr - ] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - for x in optional - if x != cls._id_attr - ] - - if mgr_cls.__name__ in cli.custom_actions: - name = mgr_cls.__name__ - for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - sub_parser_action.add_argument("--sudo", required=False) - - required, optional, dummy = cli.custom_actions[name][action_name] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in required - if x != cls._id_attr - ] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - for x in optional - if x != cls._id_attr - ] - - -def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: - subparsers = parser.add_subparsers( - title="object", dest="what", help="Object to manipulate." - ) - subparsers.required = True - - # populate argparse for all Gitlab Object - classes = [] - for cls in gitlab.v4.objects.__dict__.values(): - if not isinstance(cls, type): - continue - if issubclass(cls, gitlab.base.RESTManager): - if cls._obj_cls is not None: - classes.append(cls._obj_cls) - classes.sort(key=operator.attrgetter("__name__")) - - for cls in classes: - arg_name = cli.cls_to_what(cls) - object_group = subparsers.add_parser(arg_name) - - object_subparsers = object_group.add_subparsers( - title="action", dest="whaction", help="Action to execute." - ) - _populate_sub_parser_by_class(cls, object_subparsers) - object_subparsers.required = True - - return parser - - -def get_dict( - obj: Union[str, gitlab.base.RESTObject], fields: List[str] -) -> Union[str, Dict[str, Any]]: - if isinstance(obj, str): - return obj - - if fields: - return {k: v for k, v in obj.attributes.items() if k in fields} - return obj.attributes - - -class JSONPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - import json # noqa - - print(json.dumps(d)) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - import json # noqa - - print(json.dumps([get_dict(obj, fields) for obj in data])) - - -class YAMLPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - try: - import yaml # noqa - - print(yaml.safe_dump(d, default_flow_style=False)) - except ImportError: - exit( - "PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature" - ) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - try: - import yaml # noqa - - print( - yaml.safe_dump( - [get_dict(obj, fields) for obj in data], default_flow_style=False - ) - ) - except ImportError: - exit( - "PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature" - ) - - -class LegacyPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - verbose = kwargs.get("verbose", False) - padding = kwargs.get("padding", 0) - obj: Optional[Union[Dict[str, Any], gitlab.base.RESTObject]] = kwargs.get("obj") - if TYPE_CHECKING: - assert obj is not None - - def display_dict(d: Dict[str, Any], padding: int) -> None: - for k in sorted(d.keys()): - v = d[k] - if isinstance(v, dict): - print("%s%s:" % (" " * padding, k.replace("_", "-"))) - new_padding = padding + 2 - self.display(v, verbose=True, padding=new_padding, obj=v) - continue - print("%s%s: %s" % (" " * padding, k.replace("_", "-"), v)) - - if verbose: - if isinstance(obj, dict): - display_dict(obj, padding) - return - - # not a dict, we assume it's a RESTObject - if obj._id_attr: - id = getattr(obj, obj._id_attr, None) - print("%s: %s" % (obj._id_attr, id)) - attrs = obj.attributes - if obj._id_attr: - attrs.pop(obj._id_attr) - display_dict(attrs, padding) - - else: - if TYPE_CHECKING: - assert isinstance(obj, gitlab.base.RESTObject) - if obj._id_attr: - id = getattr(obj, obj._id_attr) - print("%s: %s" % (obj._id_attr.replace("_", "-"), id)) - if obj._short_print_attr: - value = getattr(obj, obj._short_print_attr) or "None" - value = value.replace("\r", "").replace("\n", " ") - # If the attribute is a note (ProjectCommitComment) then we do - # some modifications to fit everything on one line - line = "%s: %s" % (obj._short_print_attr, value) - # ellipsize long lines (comments) - if len(line) > 79: - line = line[:76] + "..." - print(line) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - verbose = kwargs.get("verbose", False) - for obj in data: - if isinstance(obj, gitlab.base.RESTObject): - self.display(get_dict(obj, fields), verbose=verbose, obj=obj) - else: - print(obj) - print("") - - -PRINTERS: Dict[ - str, Union[Type[JSONPrinter], Type[LegacyPrinter], Type[YAMLPrinter]] -] = { - "json": JSONPrinter, - "legacy": LegacyPrinter, - "yaml": YAMLPrinter, -} - - -def run( - gl: gitlab.Gitlab, - what: str, - action: str, - args: Dict[str, Any], - verbose: bool, - output: str, - fields: List[str], -) -> None: - g_cli = GitlabCLI(gl=gl, what=what, action=action, args=args) - data = g_cli() - - printer: Union[JSONPrinter, LegacyPrinter, YAMLPrinter] = PRINTERS[output]() - - if isinstance(data, dict): - printer.display(data, verbose=True, obj=data) - elif isinstance(data, list): - printer.display_list(data, fields, verbose=verbose) - elif isinstance(data, gitlab.base.RESTObject): - printer.display(get_dict(data, fields), verbose=verbose, obj=data) - elif isinstance(data, str): - print(data) - elif isinstance(data, bytes): - sys.stdout.buffer.write(data) - elif hasattr(data, "decode"): - print(data.decode()) diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py deleted file mode 100644 index c2ff4fb..0000000 --- a/gitlab/v4/objects/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from .access_requests import * -from .appearance import * -from .applications import * -from .audit_events 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 .deploy_tokens import * -from .deployments 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 .keys 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 .personal_access_tokens import * -from .pipelines import * -from .projects import * -from .push_rules import * -from .releases 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 * - -__all__ = [name for name in dir() if not name.startswith("_")] diff --git a/gitlab/v4/objects/access_requests.py b/gitlab/v4/objects/access_requests.py deleted file mode 100644 index 4e3328a..0000000 --- a/gitlab/v4/objects/access_requests.py +++ /dev/null @@ -1,35 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import ( - AccessRequestMixin, - CreateMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, -) - -__all__ = [ - "GroupAccessRequest", - "GroupAccessRequestManager", - "ProjectAccessRequest", - "ProjectAccessRequestManager", -] - - -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 deleted file mode 100644 index a34398e..0000000 --- a/gitlab/v4/objects/appearance.py +++ /dev/null @@ -1,52 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin - -__all__ = [ - "ApplicationAppearance", - "ApplicationAppearanceManager", -] - - -class ApplicationAppearance(SaveMixin, RESTObject): - _id_attr = None - - -class ApplicationAppearanceManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/application/appearance" - _obj_cls = ApplicationAppearance - _update_attrs = RequiredOptional( - optional=( - "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 deleted file mode 100644 index c91dee1..0000000 --- a/gitlab/v4/objects/applications.py +++ /dev/null @@ -1,20 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "Application", - "ApplicationManager", -] - - -class Application(ObjectDeleteMixin, RESTObject): - _url = "/applications" - _short_print_attr = "name" - - -class ApplicationManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/applications" - _obj_cls = Application - _create_attrs = RequiredOptional( - required=("name", "redirect_uri", "scopes"), optional=("confidential",) - ) diff --git a/gitlab/v4/objects/audit_events.py b/gitlab/v4/objects/audit_events.py deleted file mode 100644 index 20ea116..0000000 --- a/gitlab/v4/objects/audit_events.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/audit_events.html -""" -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RetrieveMixin - -__all__ = [ - "AuditEvent", - "AuditEventManager", - "GroupAuditEvent", - "GroupAuditEventManager", - "ProjectAuditEvent", - "ProjectAuditEventManager", - "ProjectAudit", - "ProjectAuditManager", -] - - -class AuditEvent(RESTObject): - _id_attr = "id" - - -class AuditEventManager(RetrieveMixin, RESTManager): - _path = "/audit_events" - _obj_cls = AuditEvent - _list_filters = ("created_after", "created_before", "entity_type", "entity_id") - - -class GroupAuditEvent(RESTObject): - _id_attr = "id" - - -class GroupAuditEventManager(RetrieveMixin, RESTManager): - _path = "/groups/%(group_id)s/audit_events" - _obj_cls = GroupAuditEvent - _from_parent_attrs = {"group_id": "id"} - _list_filters = ("created_after", "created_before") - - -class ProjectAuditEvent(RESTObject): - _id_attr = "id" - - -class ProjectAuditEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/audit_events" - _obj_cls = ProjectAuditEvent - _from_parent_attrs = {"project_id": "id"} - _list_filters = ("created_after", "created_before") - - -class ProjectAudit(ProjectAuditEvent): - pass - - -class ProjectAuditManager(ProjectAuditEventManager): - pass diff --git a/gitlab/v4/objects/award_emojis.py b/gitlab/v4/objects/award_emojis.py deleted file mode 100644 index 1a7aecd..0000000 --- a/gitlab/v4/objects/award_emojis.py +++ /dev/null @@ -1,103 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectIssueAwardEmoji", - "ProjectIssueAwardEmojiManager", - "ProjectIssueNoteAwardEmoji", - "ProjectIssueNoteAwardEmojiManager", - "ProjectMergeRequestAwardEmoji", - "ProjectMergeRequestAwardEmojiManager", - "ProjectMergeRequestNoteAwardEmoji", - "ProjectMergeRequestNoteAwardEmojiManager", - "ProjectSnippetAwardEmoji", - "ProjectSnippetAwardEmojiManager", - "ProjectSnippetNoteAwardEmoji", - "ProjectSnippetNoteAwardEmojiManager", -] - - -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 = RequiredOptional(required=("name",)) - - -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 = RequiredOptional(required=("name",)) - - -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 = RequiredOptional(required=("name",)) - - -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 = RequiredOptional(required=("name",)) - - -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 = RequiredOptional(required=("name",)) - - -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 = RequiredOptional(required=("name",)) diff --git a/gitlab/v4/objects/badges.py b/gitlab/v4/objects/badges.py deleted file mode 100644 index 198f6ea..0000000 --- a/gitlab/v4/objects/badges.py +++ /dev/null @@ -1,33 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import BadgeRenderMixin, CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "GroupBadge", - "GroupBadgeManager", - "ProjectBadge", - "ProjectBadgeManager", -] - - -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 = RequiredOptional(required=("link_url", "image_url")) - _update_attrs = RequiredOptional(optional=("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 = RequiredOptional(required=("link_url", "image_url")) - _update_attrs = RequiredOptional(optional=("link_url", "image_url")) diff --git a/gitlab/v4/objects/boards.py b/gitlab/v4/objects/boards.py deleted file mode 100644 index 8b2959d..0000000 --- a/gitlab/v4/objects/boards.py +++ /dev/null @@ -1,59 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "GroupBoardList", - "GroupBoardListManager", - "GroupBoard", - "GroupBoardManager", - "ProjectBoardList", - "ProjectBoardListManager", - "ProjectBoard", - "ProjectBoardManager", -] - - -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 = RequiredOptional(required=("label_id",)) - _update_attrs = RequiredOptional(required=("position",)) - - -class GroupBoard(SaveMixin, ObjectDeleteMixin, RESTObject): - lists: GroupBoardListManager - - -class GroupBoardManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/boards" - _obj_cls = GroupBoard - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional(required=("name",)) - - -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 = RequiredOptional(required=("label_id",)) - _update_attrs = RequiredOptional(required=("position",)) - - -class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject): - lists: ProjectBoardListManager - - -class ProjectBoardManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/boards" - _obj_cls = ProjectBoard - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("name",)) diff --git a/gitlab/v4/objects/branches.py b/gitlab/v4/objects/branches.py deleted file mode 100644 index 5bd8442..0000000 --- a/gitlab/v4/objects/branches.py +++ /dev/null @@ -1,42 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectBranch", - "ProjectBranchManager", - "ProjectProtectedBranch", - "ProjectProtectedBranchManager", -] - - -class ProjectBranch(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - - -class ProjectBranchManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/branches" - _obj_cls = ProjectBranch - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("branch", "ref")) - - -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 = RequiredOptional( - required=("name",), - optional=( - "push_access_level", - "merge_access_level", - "unprotect_access_level", - "allowed_to_push", - "allowed_to_merge", - "allowed_to_unprotect", - "code_owner_approval_required", - ), - ) diff --git a/gitlab/v4/objects/broadcast_messages.py b/gitlab/v4/objects/broadcast_messages.py deleted file mode 100644 index 7784997..0000000 --- a/gitlab/v4/objects/broadcast_messages.py +++ /dev/null @@ -1,23 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "BroadcastMessage", - "BroadcastMessageManager", -] - - -class BroadcastMessage(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class BroadcastMessageManager(CRUDMixin, RESTManager): - _path = "/broadcast_messages" - _obj_cls = BroadcastMessage - - _create_attrs = RequiredOptional( - required=("message",), optional=("starts_at", "ends_at", "color", "font") - ) - _update_attrs = RequiredOptional( - optional=("message", "starts_at", "ends_at", "color", "font") - ) diff --git a/gitlab/v4/objects/clusters.py b/gitlab/v4/objects/clusters.py deleted file mode 100644 index 10ff202..0000000 --- a/gitlab/v4/objects/clusters.py +++ /dev/null @@ -1,98 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "GroupCluster", - "GroupClusterManager", - "ProjectCluster", - "ProjectClusterManager", -] - - -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 = RequiredOptional( - required=("name", "platform_kubernetes_attributes"), - optional=("domain", "enabled", "managed", "environment_scope"), - ) - _update_attrs = RequiredOptional( - optional=( - "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 = RequiredOptional( - required=("name", "platform_kubernetes_attributes"), - optional=("domain", "enabled", "managed", "environment_scope"), - ) - _update_attrs = RequiredOptional( - optional=( - "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 deleted file mode 100644 index 05b55b0..0000000 --- a/gitlab/v4/objects/commits.py +++ /dev/null @@ -1,200 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, ListMixin, RefreshMixin, RetrieveMixin - -from .discussions import ProjectCommitDiscussionManager # noqa: F401 - -__all__ = [ - "ProjectCommit", - "ProjectCommitManager", - "ProjectCommitComment", - "ProjectCommitCommentManager", - "ProjectCommitStatus", - "ProjectCommitStatusManager", -] - - -class ProjectCommit(RESTObject): - _short_print_attr = "title" - - 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 = RequiredOptional( - required=("branch", "commit_message", "actions"), - optional=("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 = RequiredOptional( - required=("note",), optional=("path", "line", "line_type") - ) - - -class ProjectCommitStatus(RefreshMixin, RESTObject): - 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 = RequiredOptional( - required=("state",), - optional=("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 deleted file mode 100644 index ce03d35..0000000 --- a/gitlab/v4/objects/container_registry.py +++ /dev/null @@ -1,58 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin, RetrieveMixin - -__all__ = [ - "ProjectRegistryRepository", - "ProjectRegistryRepositoryManager", - "ProjectRegistryTag", - "ProjectRegistryTagManager", -] - - -class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): - 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", - ("name_regex_delete",), - optional=("keep_n", "name_regex_keep", "older_than"), - ) - @exc.on_http_error(exc.GitlabDeleteError) - def delete_in_bulk(self, name_regex_delete, **kwargs): - """Delete Tag in bulk - - Args: - name_regex_delete (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. - name_regex_keep (string): The regex of the name to keep. This value - overrides any matches from name_regex. - 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", "name_regex_keep", "older_than"] - data = {"name_regex_delete": name_regex_delete} - 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 deleted file mode 100644 index 48296ca..0000000 --- a/gitlab/v4/objects/custom_attributes.py +++ /dev/null @@ -1,41 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ObjectDeleteMixin, RetrieveMixin, SetMixin - -__all__ = [ - "GroupCustomAttribute", - "GroupCustomAttributeManager", - "ProjectCustomAttribute", - "ProjectCustomAttributeManager", - "UserCustomAttribute", - "UserCustomAttributeManager", -] - - -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 deleted file mode 100644 index cf0507d..0000000 --- a/gitlab/v4/objects/deploy_keys.py +++ /dev/null @@ -1,48 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "DeployKey", - "DeployKeyManager", - "ProjectKey", - "ProjectKeyManager", -] - - -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 = RequiredOptional(required=("title", "key"), optional=("can_push",)) - _update_attrs = RequiredOptional(optional=("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 deleted file mode 100644 index c6ba0d6..0000000 --- a/gitlab/v4/objects/deploy_tokens.py +++ /dev/null @@ -1,63 +0,0 @@ -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "DeployToken", - "DeployTokenManager", - "GroupDeployToken", - "GroupDeployTokenManager", - "ProjectDeployToken", - "ProjectDeployTokenManager", -] - - -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 = RequiredOptional( - required=( - "name", - "scopes", - ), - optional=( - "expires_at", - "username", - ), - ) - _types = {"scopes": types.ListAttribute} - - -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 = RequiredOptional( - required=( - "name", - "scopes", - ), - optional=( - "expires_at", - "username", - ), - ) - _types = {"scopes": types.ListAttribute} diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py deleted file mode 100644 index 11c60d1..0000000 --- a/gitlab/v4/objects/deployments.py +++ /dev/null @@ -1,30 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin - -from .merge_requests import ProjectDeploymentMergeRequestManager # noqa: F401 - -__all__ = [ - "ProjectDeployment", - "ProjectDeploymentManager", -] - - -class ProjectDeployment(SaveMixin, RESTObject): - mergerequests: ProjectDeploymentMergeRequestManager - - -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", - "updated_after", - "updated_before", - "environment", - "status", - ) - _create_attrs = RequiredOptional( - required=("sha", "ref", "tag", "status", "environment") - ) diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py deleted file mode 100644 index ae7a4d5..0000000 --- a/gitlab/v4/objects/discussions.py +++ /dev/null @@ -1,69 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin - -from .notes import ( # noqa: F401 - ProjectCommitDiscussionNoteManager, - ProjectIssueDiscussionNoteManager, - ProjectMergeRequestDiscussionNoteManager, - ProjectSnippetDiscussionNoteManager, -) - -__all__ = [ - "ProjectCommitDiscussion", - "ProjectCommitDiscussionManager", - "ProjectIssueDiscussion", - "ProjectIssueDiscussionManager", - "ProjectMergeRequestDiscussion", - "ProjectMergeRequestDiscussionManager", - "ProjectSnippetDiscussion", - "ProjectSnippetDiscussionManager", -] - - -class ProjectCommitDiscussion(RESTObject): - 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 = RequiredOptional(required=("body",), optional=("created_at",)) - - -class ProjectIssueDiscussion(RESTObject): - 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 = RequiredOptional(required=("body",), optional=("created_at",)) - - -class ProjectMergeRequestDiscussion(SaveMixin, RESTObject): - 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 = RequiredOptional( - required=("body",), optional=("created_at", "position") - ) - _update_attrs = RequiredOptional(required=("resolved",)) - - -class ProjectSnippetDiscussion(RESTObject): - 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 = RequiredOptional(required=("body",), optional=("created_at",)) diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py deleted file mode 100644 index e318da8..0000000 --- a/gitlab/v4/objects/environments.py +++ /dev/null @@ -1,43 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectEnvironment", - "ProjectEnvironmentManager", -] - - -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 = RequiredOptional(required=("name",), optional=("external_url",)) - _update_attrs = RequiredOptional(optional=("name", "external_url")) diff --git a/gitlab/v4/objects/epics.py b/gitlab/v4/objects/epics.py deleted file mode 100644 index 90dc6ad..0000000 --- a/gitlab/v4/objects/epics.py +++ /dev/null @@ -1,104 +0,0 @@ -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -from .events import GroupEpicResourceLabelEventManager # noqa: F401 - -__all__ = [ - "GroupEpic", - "GroupEpicManager", - "GroupEpicIssue", - "GroupEpicIssueManager", -] - - -class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): - _id_attr = "iid" - - 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 = RequiredOptional( - required=("title",), - optional=("labels", "description", "start_date", "end_date"), - ) - _update_attrs = RequiredOptional( - optional=("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 = RequiredOptional(required=("issue_id",)) - _update_attrs = RequiredOptional(optional=("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 deleted file mode 100644 index 8772e8d..0000000 --- a/gitlab/v4/objects/events.py +++ /dev/null @@ -1,130 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import ListMixin, RetrieveMixin - -__all__ = [ - "Event", - "EventManager", - "GroupEpicResourceLabelEvent", - "GroupEpicResourceLabelEventManager", - "ProjectEvent", - "ProjectEventManager", - "ProjectIssueResourceLabelEvent", - "ProjectIssueResourceLabelEventManager", - "ProjectIssueResourceMilestoneEvent", - "ProjectIssueResourceMilestoneEventManager", - "ProjectIssueResourceStateEvent", - "ProjectIssueResourceStateEventManager", - "ProjectMergeRequestResourceLabelEvent", - "ProjectMergeRequestResourceLabelEventManager", - "ProjectMergeRequestResourceMilestoneEvent", - "ProjectMergeRequestResourceMilestoneEventManager", - "ProjectMergeRequestResourceStateEvent", - "ProjectMergeRequestResourceStateEventManager", - "UserEvent", - "UserEventManager", -] - - -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 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 ProjectIssueResourceStateEvent(RESTObject): - pass - - -class ProjectIssueResourceStateEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/issues/%(issue_iid)s/resource_state_events" - _obj_cls = ProjectIssueResourceStateEvent - _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 ProjectMergeRequestResourceStateEvent(RESTObject): - pass - - -class ProjectMergeRequestResourceStateEventManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/resource_state_events" - _obj_cls = ProjectMergeRequestResourceStateEvent - _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 deleted file mode 100644 index ec4532a..0000000 --- a/gitlab/v4/objects/export_import.py +++ /dev/null @@ -1,54 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DownloadMixin, GetWithoutIdMixin, RefreshMixin - -__all__ = [ - "GroupExport", - "GroupExportManager", - "GroupImport", - "GroupImportManager", - "ProjectExport", - "ProjectExportManager", - "ProjectImport", - "ProjectImportManager", -] - - -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 = RequiredOptional(optional=("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 deleted file mode 100644 index f4117c8..0000000 --- a/gitlab/v4/objects/features.py +++ /dev/null @@ -1,59 +0,0 @@ -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "Feature", - "FeatureManager", -] - - -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 occurred - - 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 deleted file mode 100644 index ff45478..0000000 --- a/gitlab/v4/objects/files.py +++ /dev/null @@ -1,228 +0,0 @@ -import base64 - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectFile", - "ProjectFileManager", -] - - -class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "file_path" - _short_print_attr = "file_path" - - def decode(self) -> bytes: - """Returns the decoded content of the file. - - Returns: - (bytes): 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 = RequiredOptional( - required=("file_path", "branch", "content", "commit_message"), - optional=("encoding", "author_email", "author_name"), - ) - _update_attrs = RequiredOptional( - required=("file_path", "branch", "content", "commit_message"), - optional=("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 - """ - 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 deleted file mode 100644 index 16fc783..0000000 --- a/gitlab/v4/objects/geo_nodes.py +++ /dev/null @@ -1,93 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - DeleteMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "GeoNode", - "GeoNodeManager", -] - - -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 = RequiredOptional( - optional=("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 deleted file mode 100644 index b675a39..0000000 --- a/gitlab/v4/objects/groups.py +++ /dev/null @@ -1,334 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin - -from .access_requests import GroupAccessRequestManager # noqa: F401 -from .audit_events import GroupAuditEventManager # noqa: F401 -from .badges import GroupBadgeManager # noqa: F401 -from .boards import GroupBoardManager # noqa: F401 -from .clusters import GroupClusterManager # noqa: F401 -from .custom_attributes import GroupCustomAttributeManager # noqa: F401 -from .deploy_tokens import GroupDeployTokenManager # noqa: F401 -from .epics import GroupEpicManager # noqa: F401 -from .export_import import GroupExportManager, GroupImportManager # noqa: F401 -from .hooks import GroupHookManager # noqa: F401 -from .issues import GroupIssueManager # noqa: F401 -from .labels import GroupLabelManager # noqa: F401 -from .members import ( # noqa: F401 - GroupBillableMemberManager, - GroupMemberAllManager, - GroupMemberManager, -) -from .merge_requests import GroupMergeRequestManager # noqa: F401 -from .milestones import GroupMilestoneManager # noqa: F401 -from .notification_settings import GroupNotificationSettingsManager # noqa: F401 -from .packages import GroupPackageManager # noqa: F401 -from .projects import GroupProjectManager # noqa: F401 -from .runners import GroupRunnerManager # noqa: F401 -from .statistics import GroupIssuesStatisticsManager # noqa: F401 -from .variables import GroupVariableManager # noqa: F401 -from .wikis import GroupWikiManager # noqa: F401 - -__all__ = [ - "Group", - "GroupManager", - "GroupDescendantGroup", - "GroupDescendantGroupManager", - "GroupSubgroup", - "GroupSubgroupManager", -] - - -class Group(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "name" - - accessrequests: GroupAccessRequestManager - audit_events: GroupAuditEventManager - badges: GroupBadgeManager - billable_members: GroupBillableMemberManager - boards: GroupBoardManager - clusters: GroupClusterManager - customattributes: GroupCustomAttributeManager - deploytokens: GroupDeployTokenManager - descendant_groups: "GroupDescendantGroupManager" - epics: GroupEpicManager - exports: GroupExportManager - hooks: GroupHookManager - imports: GroupImportManager - issues: GroupIssueManager - issues_statistics: GroupIssuesStatisticsManager - labels: GroupLabelManager - members: GroupMemberManager - members_all: GroupMemberAllManager - mergerequests: GroupMergeRequestManager - milestones: GroupMilestoneManager - notificationsettings: GroupNotificationSettingsManager - packages: GroupPackageManager - projects: GroupProjectManager - runners: GroupRunnerManager - subgroups: "GroupSubgroupManager" - variables: GroupVariableManager - wikis: GroupWikiManager - - @cli.register_custom_action("Group", ("project_id",)) - @exc.on_http_error(exc.GitlabTransferProjectError) - def transfer_project(self, 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 transferred - """ - path = "/groups/%s/projects/%s" % (self.id, 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", - "top_level_only", - ) - _create_attrs = RequiredOptional( - required=("name", "path"), - optional=( - "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", - "shared_runners_minutes_limit", - "extra_shared_runners_minutes_limit", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "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", - "file_template_project_id", - "shared_runners_minutes_limit", - "extra_shared_runners_minutes_limit", - "prevent_forking_outside_group", - "shared_runners_setting", - ), - ) - _types = {"avatar": types.ImageAttribute, "skip_groups": types.ListAttribute} - - @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", - "min_access_level", - ) - _types = {"skip_groups": types.ListAttribute} - - -class GroupDescendantGroup(RESTObject): - pass - - -class GroupDescendantGroupManager(GroupSubgroupManager): - """ - This manager inherits from GroupSubgroupManager as descendant groups - share all attributes with subgroups, except the path and object class. - """ - - _path = "/groups/%(group_id)s/descendant_groups" - _obj_cls = GroupDescendantGroup diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py deleted file mode 100644 index 428fd76..0000000 --- a/gitlab/v4/objects/hooks.py +++ /dev/null @@ -1,114 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, NoUpdateMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "Hook", - "HookManager", - "ProjectHook", - "ProjectHookManager", - "GroupHook", - "GroupHookManager", -] - - -class Hook(ObjectDeleteMixin, RESTObject): - _url = "/hooks" - _short_print_attr = "url" - - -class HookManager(NoUpdateMixin, RESTManager): - _path = "/hooks" - _obj_cls = Hook - _create_attrs = RequiredOptional(required=("url",)) - - -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 = RequiredOptional( - required=("url",), - optional=( - "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 = RequiredOptional( - required=("url",), - optional=( - "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 GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "url" - - -class GroupHookManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/hooks" - _obj_cls = GroupHook - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("url",), - optional=( - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "job_events", - "pipeline_events", - "wiki_page_events", - "deployment_events", - "releases_events", - "subgroup_events", - "enable_ssl_verification", - "token", - ), - ) - _update_attrs = RequiredOptional( - required=("url",), - optional=( - "push_events", - "issues_events", - "confidential_issues_events", - "merge_requests_events", - "tag_push_events", - "note_events", - "confidential_note_events", - "job_events", - "pipeline_events", - "wiki_page_events", - "deployment_events", - "releases_events", - "subgroup_events", - "enable_ssl_verification", - "token", - ), - ) diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py deleted file mode 100644 index 9272908..0000000 --- a/gitlab/v4/objects/issues.py +++ /dev/null @@ -1,256 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - ParticipantsMixin, - RetrieveMixin, - SaveMixin, - SubscribableMixin, - TimeTrackingMixin, - TodoMixin, - UserAgentDetailMixin, -) - -from .award_emojis import ProjectIssueAwardEmojiManager # noqa: F401 -from .discussions import ProjectIssueDiscussionManager # noqa: F401 -from .events import ( # noqa: F401 - ProjectIssueResourceLabelEventManager, - ProjectIssueResourceMilestoneEventManager, - ProjectIssueResourceStateEventManager, -) -from .notes import ProjectIssueNoteManager # noqa: F401 - -__all__ = [ - "Issue", - "IssueManager", - "GroupIssue", - "GroupIssueManager", - "ProjectIssue", - "ProjectIssueManager", - "ProjectIssueLink", - "ProjectIssueLinkManager", -] - - -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 = {"iids": types.ListAttribute, "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 = {"iids": types.ListAttribute, "labels": types.ListAttribute} - - -class ProjectIssue( - UserAgentDetailMixin, - SubscribableMixin, - TodoMixin, - TimeTrackingMixin, - ParticipantsMixin, - SaveMixin, - ObjectDeleteMixin, - RESTObject, -): - _short_print_attr = "title" - _id_attr = "iid" - - awardemojis: ProjectIssueAwardEmojiManager - discussions: ProjectIssueDiscussionManager - links: "ProjectIssueLinkManager" - notes: ProjectIssueNoteManager - resourcelabelevents: ProjectIssueResourceLabelEventManager - resourcemilestoneevents: ProjectIssueResourceMilestoneEventManager - resourcestateevents: ProjectIssueResourceStateEventManager - - @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 = RequiredOptional( - required=("title",), - optional=( - "description", - "confidential", - "assignee_ids", - "assignee_id", - "milestone_id", - "labels", - "created_at", - "due_date", - "merge_request_to_resolve_discussions_of", - "discussion_to_resolve", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "title", - "description", - "confidential", - "assignee_ids", - "assignee_id", - "milestone_id", - "labels", - "state_event", - "updated_at", - "due_date", - "discussion_locked", - ), - ) - _types = {"iids": types.ListAttribute, "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 = RequiredOptional(required=("target_project_id", "target_issue_iid")) - - @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 deleted file mode 100644 index 2e7693d..0000000 --- a/gitlab/v4/objects/jobs.py +++ /dev/null @@ -1,190 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RefreshMixin, RetrieveMixin - -__all__ = [ - "ProjectJob", - "ProjectJobManager", -] - - -class ProjectJob(RefreshMixin, RESTObject): - @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()) - return 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()) - return 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/keys.py b/gitlab/v4/objects/keys.py deleted file mode 100644 index 7f8fa0e..0000000 --- a/gitlab/v4/objects/keys.py +++ /dev/null @@ -1,26 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import GetMixin - -__all__ = [ - "Key", - "KeyManager", -] - - -class Key(RESTObject): - pass - - -class KeyManager(GetMixin, RESTManager): - _path = "/keys" - _obj_cls = Key - - def get(self, id=None, **kwargs): - if id is not None: - return super(KeyManager, self).get(id, **kwargs) - - if "fingerprint" not in kwargs: - raise AttributeError("Missing attribute: id or fingerprint") - - server_data = self.gitlab.http_get(self.path, **kwargs) - return self._obj_cls(self, server_data) diff --git a/gitlab/v4/objects/labels.py b/gitlab/v4/objects/labels.py deleted file mode 100644 index 544c3cd..0000000 --- a/gitlab/v4/objects/labels.py +++ /dev/null @@ -1,149 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - SubscribableMixin, - UpdateMixin, -) - -__all__ = [ - "GroupLabel", - "GroupLabelManager", - "ProjectLabel", - "ProjectLabelManager", -] - - -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 = RequiredOptional( - required=("name", "color"), optional=("description", "priority") - ) - _update_attrs = RequiredOptional( - required=("name",), optional=("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 = RequiredOptional( - required=("name", "color"), optional=("description", "priority") - ) - _update_attrs = RequiredOptional( - required=("name",), optional=("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 deleted file mode 100644 index e0202a1..0000000 --- a/gitlab/v4/objects/ldap.py +++ /dev/null @@ -1,51 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject, RESTObjectList - -__all__ = [ - "LDAPGroup", - "LDAPGroupManager", -] - - -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 RESTObjectList(self, self._obj_cls, obj) diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py deleted file mode 100644 index 0c92185..0000000 --- a/gitlab/v4/objects/members.py +++ /dev/null @@ -1,92 +0,0 @@ -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CRUDMixin, - DeleteMixin, - ListMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, -) - -__all__ = [ - "GroupBillableMember", - "GroupBillableMemberManager", - "GroupBillableMemberMembership", - "GroupBillableMemberMembershipManager", - "GroupMember", - "GroupMemberManager", - "GroupMemberAllManager", - "ProjectMember", - "ProjectMemberManager", - "ProjectMemberAllManager", -] - - -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 = RequiredOptional( - required=("access_level", "user_id"), optional=("expires_at",) - ) - _update_attrs = RequiredOptional( - required=("access_level",), optional=("expires_at",) - ) - _types = {"user_ids": types.ListAttribute} - - -class GroupBillableMember(ObjectDeleteMixin, RESTObject): - _short_print_attr = "username" - - memberships: "GroupBillableMemberMembershipManager" - - -class GroupBillableMemberManager(ListMixin, DeleteMixin, RESTManager): - _path = "/groups/%(group_id)s/billable_members" - _obj_cls = GroupBillableMember - _from_parent_attrs = {"group_id": "id"} - _list_filters = ("search", "sort") - - -class GroupBillableMemberMembership(RESTObject): - _id_attr = "user_id" - - -class GroupBillableMemberMembershipManager(ListMixin, RESTManager): - _path = "/groups/%(group_id)s/billable_members/%(user_id)s/memberships" - _obj_cls = GroupBillableMemberMembership - _from_parent_attrs = {"group_id": "group_id", "user_id": "id"} - - -class GroupMemberAllManager(RetrieveMixin, RESTManager): - _path = "/groups/%(group_id)s/members/all" - _obj_cls = GroupMember - _from_parent_attrs = {"group_id": "id"} - - -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 = RequiredOptional( - required=("access_level", "user_id"), optional=("expires_at",) - ) - _update_attrs = RequiredOptional( - required=("access_level",), optional=("expires_at",) - ) - _types = {"user_ids": types.ListAttribute} - - -class ProjectMemberAllManager(RetrieveMixin, RESTManager): - _path = "/projects/%(project_id)s/members/all" - _obj_cls = ProjectMember - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/merge_request_approvals.py b/gitlab/v4/objects/merge_request_approvals.py deleted file mode 100644 index 4a41ca4..0000000 --- a/gitlab/v4/objects/merge_request_approvals.py +++ /dev/null @@ -1,206 +0,0 @@ -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetWithoutIdMixin, - ListMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectApproval", - "ProjectApprovalManager", - "ProjectApprovalRule", - "ProjectApprovalRuleManager", - "ProjectMergeRequestApproval", - "ProjectMergeRequestApprovalManager", - "ProjectMergeRequestApprovalRule", - "ProjectMergeRequestApprovalRuleManager", -] - - -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 = RequiredOptional( - optional=( - "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 = RequiredOptional( - required=("name", "approvals_required"), - optional=("user_ids", "group_ids", "protected_branch_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 = RequiredOptional(required=("approvals_required",)) - _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 = RequiredOptional( - required=( - "id", - "merge_request_iid", - "approval_rule_id", - "name", - "approvals_required", - ), - optional=("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 = RequiredOptional( - required=("id", "merge_request_iid", "name", "approvals_required"), - optional=("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 deleted file mode 100644 index 4def98c..0000000 --- a/gitlab/v4/objects/merge_requests.py +++ /dev/null @@ -1,439 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject, RESTObjectList -from gitlab.mixins import ( - CRUDMixin, - ListMixin, - ObjectDeleteMixin, - ParticipantsMixin, - RetrieveMixin, - SaveMixin, - SubscribableMixin, - TimeTrackingMixin, - TodoMixin, -) - -from .award_emojis import ProjectMergeRequestAwardEmojiManager # noqa: F401 -from .commits import ProjectCommit, ProjectCommitManager -from .discussions import ProjectMergeRequestDiscussionManager # noqa: F401 -from .events import ( # noqa: F401 - ProjectMergeRequestResourceLabelEventManager, - ProjectMergeRequestResourceMilestoneEventManager, - ProjectMergeRequestResourceStateEventManager, -) -from .issues import ProjectIssue, ProjectIssueManager -from .merge_request_approvals import ( # noqa: F401 - ProjectMergeRequestApprovalManager, - ProjectMergeRequestApprovalRuleManager, -) -from .notes import ProjectMergeRequestNoteManager # noqa: F401 -from .pipelines import ProjectMergeRequestPipelineManager # noqa: F401 - -__all__ = [ - "MergeRequest", - "MergeRequestManager", - "GroupMergeRequest", - "GroupMergeRequestManager", - "ProjectMergeRequest", - "ProjectMergeRequestManager", - "ProjectDeploymentMergeRequest", - "ProjectDeploymentMergeRequestManager", - "ProjectMergeRequestDiff", - "ProjectMergeRequestDiffManager", -] - - -class MergeRequest(RESTObject): - pass - - -class MergeRequestManager(ListMixin, RESTManager): - _path = "/merge_requests" - _obj_cls = MergeRequest - _list_filters = ( - "state", - "order_by", - "sort", - "milestone", - "view", - "labels", - "with_labels_details", - "with_merge_status_recheck", - "created_after", - "created_before", - "updated_after", - "updated_before", - "scope", - "author_id", - "author_username", - "assignee_id", - "approver_ids", - "approved_by_ids", - "reviewer_id", - "reviewer_username", - "my_reaction_emoji", - "source_branch", - "target_branch", - "search", - "in", - "wip", - "not", - "environment", - "deployed_before", - "deployed_after", - ) - _types = { - "approver_ids": types.ListAttribute, - "approved_by_ids": types.ListAttribute, - "in": types.ListAttribute, - "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", - "approver_ids", - "approved_by_ids", - "my_reaction_emoji", - "source_branch", - "target_branch", - "search", - "wip", - ) - _types = { - "approver_ids": types.ListAttribute, - "approved_by_ids": types.ListAttribute, - "labels": types.ListAttribute, - } - - -class ProjectMergeRequest( - SubscribableMixin, - TodoMixin, - TimeTrackingMixin, - ParticipantsMixin, - SaveMixin, - ObjectDeleteMixin, - RESTObject, -): - _id_attr = "iid" - - approval_rules: ProjectMergeRequestApprovalRuleManager - approvals: ProjectMergeRequestApprovalManager - awardemojis: ProjectMergeRequestAwardEmojiManager - diffs: "ProjectMergeRequestDiffManager" - discussions: ProjectMergeRequestDiscussionManager - notes: ProjectMergeRequestNoteManager - pipelines: ProjectMergeRequestPipelineManager - resourcelabelevents: ProjectMergeRequestResourceLabelEventManager - resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager - resourcestateevents: ProjectMergeRequestResourceStateEventManager - - @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", 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") - @exc.on_http_error(exc.GitlabGetError) - def merge_ref(self, **kwargs): - """Attempt to merge changes between source and target branches into - `refs/merge-requests/:iid/merge`. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabGetError: If cannot be merged - """ - path = "%s/%s/merge_ref" % (self.manager.path, self.get_id()) - return self.manager.gitlab.http_get(path, **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 is not None: - data["should_remove_source_branch"] = should_remove_source_branch - if merge_when_pipeline_succeeds: - data["merge_when_pipeline_succeeds"] = True - - server_data = self.manager.gitlab.http_put(path, post_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 = RequiredOptional( - required=("source_branch", "target_branch", "title"), - optional=( - "assignee_id", - "description", - "target_project_id", - "labels", - "milestone_id", - "remove_source_branch", - "allow_maintainer_to_push", - "squash", - "reviewer_ids", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "target_branch", - "assignee_id", - "title", - "description", - "state_event", - "labels", - "milestone_id", - "remove_source_branch", - "discussion_locked", - "allow_maintainer_to_push", - "squash", - "reviewer_ids", - ), - ) - _list_filters = ( - "state", - "order_by", - "sort", - "milestone", - "view", - "labels", - "created_after", - "created_before", - "updated_after", - "updated_before", - "scope", - "iids", - "author_id", - "assignee_id", - "approver_ids", - "approved_by_ids", - "my_reaction_emoji", - "source_branch", - "target_branch", - "search", - "wip", - ) - _types = { - "approver_ids": types.ListAttribute, - "approved_by_ids": types.ListAttribute, - "iids": types.ListAttribute, - "labels": types.ListAttribute, - } - - -class ProjectDeploymentMergeRequest(MergeRequest): - pass - - -class ProjectDeploymentMergeRequestManager(MergeRequestManager): - _path = "/projects/%(project_id)s/deployments/%(deployment_id)s/merge_requests" - _obj_cls = ProjectDeploymentMergeRequest - _from_parent_attrs = {"deployment_id": "id", "project_id": "project_id"} - - -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 deleted file mode 100644 index 0a53e1b..0000000 --- a/gitlab/v4/objects/milestones.py +++ /dev/null @@ -1,164 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject, RESTObjectList -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -from .issues import GroupIssue, GroupIssueManager, ProjectIssue, ProjectIssueManager -from .merge_requests import ( - GroupMergeRequest, - ProjectMergeRequest, - ProjectMergeRequestManager, -) - -__all__ = [ - "GroupMilestone", - "GroupMilestoneManager", - "ProjectMilestone", - "ProjectMilestoneManager", -] - - -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 = RequiredOptional( - required=("title",), optional=("description", "due_date", "start_date") - ) - _update_attrs = RequiredOptional( - optional=("title", "description", "due_date", "start_date", "state_event"), - ) - _list_filters = ("iids", "state", "search") - _types = {"iids": 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 = RequiredOptional( - required=("title",), - optional=("description", "due_date", "start_date", "state_event"), - ) - _update_attrs = RequiredOptional( - optional=("title", "description", "due_date", "start_date", "state_event"), - ) - _list_filters = ("iids", "state", "search") - _types = {"iids": types.ListAttribute} diff --git a/gitlab/v4/objects/namespaces.py b/gitlab/v4/objects/namespaces.py deleted file mode 100644 index deee281..0000000 --- a/gitlab/v4/objects/namespaces.py +++ /dev/null @@ -1,17 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RetrieveMixin - -__all__ = [ - "Namespace", - "NamespaceManager", -] - - -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 deleted file mode 100644 index cbd237e..0000000 --- a/gitlab/v4/objects/notes.py +++ /dev/null @@ -1,169 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -from .award_emojis import ( # noqa: F401 - ProjectIssueNoteAwardEmojiManager, - ProjectMergeRequestNoteAwardEmojiManager, - ProjectSnippetNoteAwardEmojiManager, -) - -__all__ = [ - "ProjectNote", - "ProjectNoteManager", - "ProjectCommitDiscussionNote", - "ProjectCommitDiscussionNoteManager", - "ProjectIssueNote", - "ProjectIssueNoteManager", - "ProjectIssueDiscussionNote", - "ProjectIssueDiscussionNoteManager", - "ProjectMergeRequestNote", - "ProjectMergeRequestNoteManager", - "ProjectMergeRequestDiscussionNote", - "ProjectMergeRequestDiscussionNoteManager", - "ProjectSnippetNote", - "ProjectSnippetNoteManager", - "ProjectSnippetDiscussionNote", - "ProjectSnippetDiscussionNoteManager", -] - - -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 = RequiredOptional(required=("body",)) - - -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 = RequiredOptional( - required=("body",), optional=("created_at", "position") - ) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject): - 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 = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) - - -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 = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject): - 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 = RequiredOptional(required=("body",)) - _update_attrs = RequiredOptional(required=("body",)) - - -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 = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) - - -class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): - awardemojis: ProjectMergeRequestNoteAwardEmojiManager - - -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 = RequiredOptional(required=("body",)) - _update_attrs = RequiredOptional(required=("body",)) - - -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 = RequiredOptional(required=("body",), optional=("created_at",)) - _update_attrs = RequiredOptional(required=("body",)) diff --git a/gitlab/v4/objects/notification_settings.py b/gitlab/v4/objects/notification_settings.py deleted file mode 100644 index 3682ed0..0000000 --- a/gitlab/v4/objects/notification_settings.py +++ /dev/null @@ -1,57 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin - -__all__ = [ - "NotificationSettings", - "NotificationSettingsManager", - "GroupNotificationSettings", - "GroupNotificationSettingsManager", - "ProjectNotificationSettings", - "ProjectNotificationSettingsManager", -] - - -class NotificationSettings(SaveMixin, RESTObject): - _id_attr = None - - -class NotificationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/notification_settings" - _obj_cls = NotificationSettings - - _update_attrs = RequiredOptional( - optional=( - "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 deleted file mode 100644 index e76a5c6..0000000 --- a/gitlab/v4/objects/packages.py +++ /dev/null @@ -1,168 +0,0 @@ -from pathlib import Path -from typing import Any, Callable, Optional, TYPE_CHECKING, Union - -import requests - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, GetMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "GenericPackage", - "GenericPackageManager", - "GroupPackage", - "GroupPackageManager", - "ProjectPackage", - "ProjectPackageManager", - "ProjectPackageFile", - "ProjectPackageFileManager", -] - - -class GenericPackage(RESTObject): - _id_attr = "package_name" - - -class GenericPackageManager(RESTManager): - _path = "/projects/%(project_id)s/packages/generic" - _obj_cls = GenericPackage - _from_parent_attrs = {"project_id": "id"} - - @cli.register_custom_action( - "GenericPackageManager", - ("package_name", "package_version", "file_name", "path"), - ) - @exc.on_http_error(exc.GitlabUploadError) - def upload( - self, - package_name: str, - package_version: str, - file_name: str, - path: Union[str, Path], - **kwargs, - ) -> GenericPackage: - """Upload a file as a generic package. - - Args: - package_name (str): The package name. Must follow generic package - name regex rules - package_version (str): The package version. Must follow semantic - version regex rules - file_name (str): The name of the file as uploaded in the registry - path (str): The path to a local file to upload - - Raises: - GitlabConnectionError: If the server cannot be reached - GitlabUploadError: If the file upload fails - GitlabUploadError: If ``filepath`` cannot be read - - Returns: - GenericPackage: An object storing the metadata of the uploaded package. - """ - - try: - with open(path, "rb") as f: - file_data = f.read() - except OSError: - raise exc.GitlabUploadError(f"Failed to read package file {path}") - - url = f"{self._computed_path}/{package_name}/{package_version}/{file_name}" - server_data = self.gitlab.http_put(url, post_data=file_data, raw=True, **kwargs) - - return self._obj_cls( - self, - attrs={ - "package_name": package_name, - "package_version": package_version, - "file_name": file_name, - "path": path, - "message": server_data["message"], - }, - ) - - @cli.register_custom_action( - "GenericPackageManager", - ("package_name", "package_version", "file_name"), - ) - @exc.on_http_error(exc.GitlabGetError) - def download( - self, - package_name: str, - package_version: str, - file_name: str, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any, - ) -> Optional[bytes]: - """Download a generic package. - - Args: - package_name (str): The package name. - package_version (str): The package version. - file_name (str): The name of the file in the registry - 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 package content if streamed is False, None otherwise - """ - path = f"{self._computed_path}/{package_name}/{package_version}/{file_name}" - result = self.gitlab.http_get(path, streamed=streamed, raw=True, **kwargs) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - -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): - package_files: "ProjectPackageFileManager" - - -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 ProjectPackageFile(RESTObject): - pass - - -class ProjectPackageFileManager(ListMixin, RESTManager): - _path = "/projects/%(project_id)s/packages/%(package_id)s/package_files" - _obj_cls = ProjectPackageFile - _from_parent_attrs = {"project_id": "project_id", "package_id": "id"} diff --git a/gitlab/v4/objects/pages.py b/gitlab/v4/objects/pages.py deleted file mode 100644 index 709d9f0..0000000 --- a/gitlab/v4/objects/pages.py +++ /dev/null @@ -1,32 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "PagesDomain", - "PagesDomainManager", - "ProjectPagesDomain", - "ProjectPagesDomainManager", -] - - -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 = RequiredOptional( - required=("domain",), optional=("certificate", "key") - ) - _update_attrs = RequiredOptional(optional=("certificate", "key")) diff --git a/gitlab/v4/objects/personal_access_tokens.py b/gitlab/v4/objects/personal_access_tokens.py deleted file mode 100644 index 6cdb305..0000000 --- a/gitlab/v4/objects/personal_access_tokens.py +++ /dev/null @@ -1,32 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "PersonalAccessToken", - "PersonalAccessTokenManager", - "UserPersonalAccessToken", - "UserPersonalAccessTokenManager", -] - - -class PersonalAccessToken(ObjectDeleteMixin, RESTObject): - pass - - -class PersonalAccessTokenManager(DeleteMixin, ListMixin, RESTManager): - _path = "/personal_access_tokens" - _obj_cls = PersonalAccessToken - _list_filters = ("user_id",) - - -class UserPersonalAccessToken(RESTObject): - pass - - -class UserPersonalAccessTokenManager(CreateMixin, RESTManager): - _path = "/users/%(user_id)s/personal_access_tokens" - _obj_cls = UserPersonalAccessToken - _from_parent_attrs = {"user_id": "id"} - _create_attrs = RequiredOptional( - required=("name", "scopes"), optional=("expires_at",) - ) diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py deleted file mode 100644 index 2d212a6..0000000 --- a/gitlab/v4/objects/pipelines.py +++ /dev/null @@ -1,227 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetWithoutIdMixin, - ListMixin, - ObjectDeleteMixin, - RefreshMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectMergeRequestPipeline", - "ProjectMergeRequestPipelineManager", - "ProjectPipeline", - "ProjectPipelineManager", - "ProjectPipelineJob", - "ProjectPipelineJobManager", - "ProjectPipelineBridge", - "ProjectPipelineBridgeManager", - "ProjectPipelineVariable", - "ProjectPipelineVariableManager", - "ProjectPipelineScheduleVariable", - "ProjectPipelineScheduleVariableManager", - "ProjectPipelineSchedule", - "ProjectPipelineScheduleManager", - "ProjectPipelineTestReport", - "ProjectPipelineTestReportManager", -] - - -class ProjectMergeRequestPipeline(RESTObject): - pass - - -class ProjectMergeRequestPipelineManager(CreateMixin, ListMixin, RESTManager): - _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/pipelines" - _obj_cls = ProjectMergeRequestPipeline - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} - - -class ProjectPipeline(RefreshMixin, ObjectDeleteMixin, RESTObject): - bridges: "ProjectPipelineBridgeManager" - jobs: "ProjectPipelineJobManager" - test_report: "ProjectPipelineTestReportManager" - 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()) - return 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()) - return 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 = RequiredOptional(required=("ref",)) - - 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", "include_retried") - - -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 = RequiredOptional(required=("key", "value")) - _update_attrs = RequiredOptional(required=("key", "value")) - - -class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject): - 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 = RequiredOptional( - required=("description", "ref", "cron"), optional=("cron_timezone", "active") - ) - _update_attrs = RequiredOptional( - optional=("description", "ref", "cron", "cron_timezone", "active"), - ) - - -class ProjectPipelineTestReport(RESTObject): - _id_attr = None - - -class ProjectPipelineTestReportManager(GetWithoutIdMixin, RESTManager): - _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/test_report" - _obj_cls = ProjectPipelineTestReport - _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} diff --git a/gitlab/v4/objects/project_access_tokens.py b/gitlab/v4/objects/project_access_tokens.py deleted file mode 100644 index f59ea85..0000000 --- a/gitlab/v4/objects/project_access_tokens.py +++ /dev/null @@ -1,17 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectAccessToken", - "ProjectAccessTokenManager", -] - - -class ProjectAccessToken(ObjectDeleteMixin, RESTObject): - pass - - -class ProjectAccessTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/projects/%(project_id)s/access_tokens" - _obj_cls = ProjectAccessToken - _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py deleted file mode 100644 index 551079a..0000000 --- a/gitlab/v4/objects/projects.py +++ /dev/null @@ -1,1047 +0,0 @@ -from typing import Any, Callable, cast, Dict, List, Optional, TYPE_CHECKING, Union - -import requests - -from gitlab import cli, client -from gitlab import exceptions as exc -from gitlab import types, utils -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - ListMixin, - ObjectDeleteMixin, - RefreshMixin, - SaveMixin, - UpdateMixin, -) - -from .access_requests import ProjectAccessRequestManager # noqa: F401 -from .audit_events import ProjectAuditEventManager # noqa: F401 -from .badges import ProjectBadgeManager # noqa: F401 -from .boards import ProjectBoardManager # noqa: F401 -from .branches import ProjectBranchManager, ProjectProtectedBranchManager # noqa: F401 -from .clusters import ProjectClusterManager # noqa: F401 -from .commits import ProjectCommitManager # noqa: F401 -from .container_registry import ProjectRegistryRepositoryManager # noqa: F401 -from .custom_attributes import ProjectCustomAttributeManager # noqa: F401 -from .deploy_keys import ProjectKeyManager # noqa: F401 -from .deploy_tokens import ProjectDeployTokenManager # noqa: F401 -from .deployments import ProjectDeploymentManager # noqa: F401 -from .environments import ProjectEnvironmentManager # noqa: F401 -from .events import ProjectEventManager # noqa: F401 -from .export_import import ProjectExportManager, ProjectImportManager # noqa: F401 -from .files import ProjectFileManager # noqa: F401 -from .hooks import ProjectHookManager # noqa: F401 -from .issues import ProjectIssueManager # noqa: F401 -from .jobs import ProjectJobManager # noqa: F401 -from .labels import ProjectLabelManager # noqa: F401 -from .members import ProjectMemberAllManager, ProjectMemberManager # noqa: F401 -from .merge_request_approvals import ( # noqa: F401 - ProjectApprovalManager, - ProjectApprovalRuleManager, -) -from .merge_requests import ProjectMergeRequestManager # noqa: F401 -from .milestones import ProjectMilestoneManager # noqa: F401 -from .notes import ProjectNoteManager # noqa: F401 -from .notification_settings import ProjectNotificationSettingsManager # noqa: F401 -from .packages import GenericPackageManager, ProjectPackageManager # noqa: F401 -from .pages import ProjectPagesDomainManager # noqa: F401 -from .pipelines import ( # noqa: F401 - ProjectPipeline, - ProjectPipelineManager, - ProjectPipelineScheduleManager, -) -from .project_access_tokens import ProjectAccessTokenManager # noqa: F401 -from .push_rules import ProjectPushRulesManager # noqa: F401 -from .releases import ProjectReleaseManager # noqa: F401 -from .repositories import RepositoryMixin -from .runners import ProjectRunnerManager # noqa: F401 -from .services import ProjectServiceManager # noqa: F401 -from .snippets import ProjectSnippetManager # noqa: F401 -from .statistics import ( # noqa: F401 - ProjectAdditionalStatisticsManager, - ProjectIssuesStatisticsManager, -) -from .tags import ProjectProtectedTagManager, ProjectTagManager # noqa: F401 -from .triggers import ProjectTriggerManager # noqa: F401 -from .users import ProjectUserManager # noqa: F401 -from .variables import ProjectVariableManager # noqa: F401 -from .wikis import ProjectWikiManager # noqa: F401 - -__all__ = [ - "GroupProject", - "GroupProjectManager", - "Project", - "ProjectManager", - "ProjectFork", - "ProjectForkManager", - "ProjectRemoteMirror", - "ProjectRemoteMirrorManager", -] - - -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(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTObject): - _short_print_attr = "path" - - access_tokens: ProjectAccessTokenManager - accessrequests: ProjectAccessRequestManager - additionalstatistics: ProjectAdditionalStatisticsManager - approvalrules: ProjectApprovalRuleManager - approvals: ProjectApprovalManager - audit_events: ProjectAuditEventManager - badges: ProjectBadgeManager - boards: ProjectBoardManager - branches: ProjectBranchManager - clusters: ProjectClusterManager - commits: ProjectCommitManager - customattributes: ProjectCustomAttributeManager - deployments: ProjectDeploymentManager - deploytokens: ProjectDeployTokenManager - environments: ProjectEnvironmentManager - events: ProjectEventManager - exports: ProjectExportManager - files: ProjectFileManager - forks: "ProjectForkManager" - generic_packages: GenericPackageManager - hooks: ProjectHookManager - imports: ProjectImportManager - issues: ProjectIssueManager - issues_statistics: ProjectIssuesStatisticsManager - jobs: ProjectJobManager - keys: ProjectKeyManager - labels: ProjectLabelManager - members: ProjectMemberManager - members_all: ProjectMemberAllManager - mergerequests: ProjectMergeRequestManager - milestones: ProjectMilestoneManager - notes: ProjectNoteManager - notificationsettings: ProjectNotificationSettingsManager - packages: ProjectPackageManager - pagesdomains: ProjectPagesDomainManager - pipelines: ProjectPipelineManager - pipelineschedules: ProjectPipelineScheduleManager - protectedbranches: ProjectProtectedBranchManager - protectedtags: ProjectProtectedTagManager - pushrules: ProjectPushRulesManager - releases: ProjectReleaseManager - remote_mirrors: "ProjectRemoteMirrorManager" - repositories: ProjectRegistryRepositoryManager - runners: ProjectRunnerManager - services: ProjectServiceManager - snippets: ProjectSnippetManager - tags: ProjectTagManager - triggers: ProjectTriggerManager - users: ProjectUserManager - variables: ProjectVariableManager - wikis: ProjectWikiManager - - @cli.register_custom_action("Project", ("forked_from_id",)) - @exc.on_http_error(exc.GitlabCreateError) - def create_fork_relation(self, forked_from_id: int, **kwargs: Any) -> None: - """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: Any) -> None: - """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.GitlabGetError) - def languages(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """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: Any) -> None: - """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) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabDeleteError) - def unstar(self, **kwargs: Any) -> None: - """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) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabCreateError) - def archive(self, **kwargs: Any) -> None: - """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) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - self._update_attrs(server_data) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabDeleteError) - def unarchive(self, **kwargs: Any) -> None: - """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) - if TYPE_CHECKING: - assert isinstance(server_data, dict) - 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: int, - group_access: int, - expires_at: Optional[str] = None, - **kwargs: Any - ) -> None: - """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: int, **kwargs: Any) -> None: - """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: str, - token: str, - variables: Optional[Dict[str, Any]] = None, - **kwargs: Any - ) -> ProjectPipeline: - """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) - if TYPE_CHECKING: - assert isinstance(attrs, dict) - return ProjectPipeline(self.pipelines, attrs) - - @cli.register_custom_action("Project") - @exc.on_http_error(exc.GitlabHousekeepingError) - def housekeeping(self, **kwargs: Any) -> None: - """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: str, - filedata: Optional[bytes] = None, - filepath: Optional[str] = None, - **kwargs: Any - ) -> Dict[str, Any]: - """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 exc.GitlabUploadError("No file contents or path specified") - - if filedata is not None and filepath is not None: - raise exc.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) - - if TYPE_CHECKING: - assert isinstance(data, dict) - 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: bool = False, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """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 - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - 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: str, search: str, **kwargs: Any - ) -> Union[client.GitlabList, List[Dict[str, Any]]]: - """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: Any) -> None: - """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: str, **kwargs: Any) -> None: - """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 transferred - """ - 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: str, - job: str, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """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 - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - 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: str, - artifact_path: str, - job: str, - streamed: bool = False, - action: Optional[Callable] = None, - chunk_size: int = 1024, - **kwargs: Any - ) -> Optional[bytes]: - """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 - ) - if TYPE_CHECKING: - assert isinstance(result, requests.Response) - return utils.response_content(result, streamed, action, chunk_size) - - -class ProjectManager(CRUDMixin, RESTManager): - _path = "/projects" - _obj_cls = Project - # Please keep these _create_attrs in same order as they are at: - # https://docs.gitlab.com/ee/api/projects.html#create-project - _create_attrs = RequiredOptional( - optional=( - "name", - "path", - "allow_merge_on_skipped_pipeline", - "analytics_access_level", - "approvals_before_merge", - "auto_cancel_pending_pipelines", - "auto_devops_deploy_strategy", - "auto_devops_enabled", - "autoclose_referenced_issues", - "avatar", - "build_coverage_regex", - "build_git_strategy", - "build_timeout", - "builds_access_level", - "ci_config_path", - "container_expiration_policy_attributes", - "container_registry_enabled", - "default_branch", - "description", - "emails_disabled", - "external_authorization_classification_label", - "forking_access_level", - "group_with_project_templates_id", - "import_url", - "initialize_with_readme", - "issues_access_level", - "issues_enabled", - "jobs_enabled", - "lfs_enabled", - "merge_method", - "merge_requests_access_level", - "merge_requests_enabled", - "mirror_trigger_builds", - "mirror", - "namespace_id", - "operations_access_level", - "only_allow_merge_if_all_discussions_are_resolved", - "only_allow_merge_if_pipeline_succeeds", - "packages_enabled", - "pages_access_level", - "requirements_access_level", - "printing_merge_request_link_enabled", - "public_builds", - "remove_source_branch_after_merge", - "repository_access_level", - "repository_storage", - "request_access_enabled", - "resolve_outdated_diff_discussions", - "shared_runners_enabled", - "show_default_award_emojis", - "snippets_access_level", - "snippets_enabled", - "tag_list", - "template_name", - "template_project_id", - "use_custom_template", - "visibility", - "wiki_access_level", - "wiki_enabled", - ), - ) - # Please keep these _update_attrs in same order as they are at: - # https://docs.gitlab.com/ee/api/projects.html#edit-project - _update_attrs = RequiredOptional( - optional=( - "allow_merge_on_skipped_pipeline", - "analytics_access_level", - "approvals_before_merge", - "auto_cancel_pending_pipelines", - "auto_devops_deploy_strategy", - "auto_devops_enabled", - "autoclose_referenced_issues", - "avatar", - "build_coverage_regex", - "build_git_strategy", - "build_timeout", - "builds_access_level", - "ci_config_path", - "ci_default_git_depth", - "ci_forward_deployment_enabled", - "container_expiration_policy_attributes", - "container_registry_enabled", - "default_branch", - "description", - "emails_disabled", - "external_authorization_classification_label", - "forking_access_level", - "import_url", - "issues_access_level", - "issues_enabled", - "jobs_enabled", - "lfs_enabled", - "merge_method", - "merge_requests_access_level", - "merge_requests_enabled", - "mirror_overwrites_diverged_branches", - "mirror_trigger_builds", - "mirror_user_id", - "mirror", - "name", - "operations_access_level", - "only_allow_merge_if_all_discussions_are_resolved", - "only_allow_merge_if_pipeline_succeeds", - "only_mirror_protected_branches", - "packages_enabled", - "pages_access_level", - "requirements_access_level", - "restrict_user_defined_variables", - "path", - "public_builds", - "remove_source_branch_after_merge", - "repository_access_level", - "repository_storage", - "request_access_enabled", - "resolve_outdated_diff_discussions", - "service_desk_enabled", - "shared_runners_enabled", - "show_default_award_emojis", - "snippets_access_level", - "snippets_enabled", - "suggestion_commit_message", - "tag_list", - "visibility", - "wiki_access_level", - "wiki_enabled", - "issues_template", - "merge_requests_template", - ), - ) - _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", - "topic", - "visibility", - "wiki_checksum_failed", - "with_custom_attributes", - "with_issues_enabled", - "with_merge_requests_enabled", - "with_programming_language", - ) - _types = {"avatar": types.ImageAttribute, "topic": types.ListAttribute} - - def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Project: - return cast(Project, super().get(id=id, lazy=lazy, **kwargs)) - - def import_project( - self, - file: str, - path: str, - name: Optional[str] = None, - namespace: Optional[str] = None, - overwrite: bool = False, - override_params: Optional[Dict[str, Any]] = None, - **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: - """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: str, - bitbucket_server_username: str, - personal_access_token: str, - bitbucket_server_project: str, - bitbucket_server_repo: str, - new_name: Optional[str] = None, - target_namespace: Optional[str] = None, - **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: - """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: - - .. code-block:: python - - 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: str, - repo_id: int, - target_namespace: str, - new_name: Optional[str] = None, - **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: - """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: - - .. code-block:: python - - 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 = RequiredOptional(optional=("namespace",)) - - def create( - self, data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> ProjectFork: - """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 - """ - if TYPE_CHECKING: - assert self.path is not None - path = self.path[:-1] # drop the 's' - return cast(ProjectFork, 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 = RequiredOptional( - required=("url",), optional=("enabled", "only_protected_branches") - ) - _update_attrs = RequiredOptional(optional=("enabled", "only_protected_branches")) diff --git a/gitlab/v4/objects/push_rules.py b/gitlab/v4/objects/push_rules.py deleted file mode 100644 index ee20f96..0000000 --- a/gitlab/v4/objects/push_rules.py +++ /dev/null @@ -1,50 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CreateMixin, - DeleteMixin, - GetWithoutIdMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectPushRules", - "ProjectPushRulesManager", -] - - -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 = RequiredOptional( - optional=( - "deny_delete_tag", - "member_check", - "prevent_secrets", - "commit_message_regex", - "branch_name_regex", - "author_email_regex", - "file_name_regex", - "max_file_size", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "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/releases.py b/gitlab/v4/objects/releases.py deleted file mode 100644 index 2af3248..0000000 --- a/gitlab/v4/objects/releases.py +++ /dev/null @@ -1,41 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "ProjectRelease", - "ProjectReleaseManager", - "ProjectReleaseLink", - "ProjectReleaseLinkManager", -] - - -class ProjectRelease(SaveMixin, RESTObject): - _id_attr = "tag_name" - - links: "ProjectReleaseLinkManager" - - -class ProjectReleaseManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/releases" - _obj_cls = ProjectRelease - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("tag_name", "description"), optional=("name", "ref", "assets") - ) - _update_attrs = RequiredOptional( - optional=("name", "description", "milestones", "released_at") - ) - - -class ProjectReleaseLink(ObjectDeleteMixin, SaveMixin, RESTObject): - pass - - -class ProjectReleaseLinkManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/releases/%(tag_name)s/assets/links" - _obj_cls = ProjectReleaseLink - _from_parent_attrs = {"project_id": "project_id", "tag_name": "tag_name"} - _create_attrs = RequiredOptional( - required=("name", "url"), optional=("filepath", "link_type") - ) - _update_attrs = RequiredOptional(optional=("name", "url", "filepath", "link_type")) diff --git a/gitlab/v4/objects/repositories.py b/gitlab/v4/objects/repositories.py deleted file mode 100644 index de5f0d2..0000000 --- a/gitlab/v4/objects/repositories.py +++ /dev/null @@ -1,207 +0,0 @@ -""" -GitLab API: https://docs.gitlab.com/ee/api/repositories.html - -Currently this module only contains repository-related methods for projects. -""" - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils - - -class RepositoryMixin: - @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: - bytes: 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") - @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) diff --git a/gitlab/v4/objects/runners.py b/gitlab/v4/objects/runners.py deleted file mode 100644 index a32dc84..0000000 --- a/gitlab/v4/objects/runners.py +++ /dev/null @@ -1,140 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import ( - CRUDMixin, - ListMixin, - NoUpdateMixin, - ObjectDeleteMixin, - SaveMixin, -) - -__all__ = [ - "RunnerJob", - "RunnerJobManager", - "Runner", - "RunnerManager", - "GroupRunner", - "GroupRunnerManager", - "ProjectRunner", - "ProjectRunnerManager", -] - - -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): - jobs: RunnerJobManager - - -class RunnerManager(CRUDMixin, RESTManager): - _path = "/runners" - _obj_cls = Runner - _create_attrs = RequiredOptional( - required=("token",), - optional=( - "description", - "info", - "active", - "locked", - "run_untagged", - "tag_list", - "access_level", - "maximum_timeout", - ), - ) - _update_attrs = RequiredOptional( - optional=( - "description", - "active", - "tag_list", - "run_untagged", - "locked", - "access_level", - "maximum_timeout", - ), - ) - _list_filters = ("scope", "tag_list") - _types = {"tag_list": types.ListAttribute} - - @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 = RequiredOptional(required=("runner_id",)) - _list_filters = ("scope", "tag_list") - _types = {"tag_list": types.ListAttribute} - - -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 = RequiredOptional(required=("runner_id",)) - _list_filters = ("scope", "tag_list") - _types = {"tag_list": types.ListAttribute} diff --git a/gitlab/v4/objects/services.py b/gitlab/v4/objects/services.py deleted file mode 100644 index 6aedc39..0000000 --- a/gitlab/v4/objects/services.py +++ /dev/null @@ -1,303 +0,0 @@ -from gitlab import cli -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import ( - DeleteMixin, - GetMixin, - ListMixin, - ObjectDeleteMixin, - SaveMixin, - UpdateMixin, -) - -__all__ = [ - "ProjectService", - "ProjectServiceManager", -] - - -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 deleted file mode 100644 index 1c8be25..0000000 --- a/gitlab/v4/objects/settings.py +++ /dev/null @@ -1,109 +0,0 @@ -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin - -__all__ = [ - "ApplicationSettings", - "ApplicationSettingsManager", -] - - -class ApplicationSettings(SaveMixin, RESTObject): - _id_attr = None - - -class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/application/settings" - _obj_cls = ApplicationSettings - _update_attrs = RequiredOptional( - optional=( - "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", - "disabled_oauth_sign_in_sources", - "domain_whitelist", - "domain_blacklist_enabled", - "domain_blacklist", - "domain_allowlist", - "domain_denylist_enabled", - "domain_denylist", - "external_authorization_service_enabled", - "external_authorization_service_url", - "external_authorization_service_default_label", - "external_authorization_service_timeout", - "import_sources", - "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", - "asset_proxy_allowlist", - "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", - ), - ) - _types = { - "asset_proxy_allowlist": types.ListAttribute, - "disabled_oauth_sign_in_sources": types.ListAttribute, - "domain_allowlist": types.ListAttribute, - "domain_denylist": types.ListAttribute, - "import_sources": types.ListAttribute, - "restricted_visibility_levels": types.ListAttribute, - } - - @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 deleted file mode 100644 index dc1094a..0000000 --- a/gitlab/v4/objects/sidekiq.py +++ /dev/null @@ -1,83 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RESTManager - -__all__ = [ - "SidekiqManager", -] - - -class SidekiqManager(RESTManager): - """Manager for the Sidekiq methods. - - This manager doesn't actually manage objects but provides helper function - for the sidekiq metrics API. - """ - - @cli.register_custom_action("SidekiqManager") - @exc.on_http_error(exc.GitlabGetError) - def queue_metrics(self, **kwargs): - """Return the registered 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 registered 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 deleted file mode 100644 index 164b30c..0000000 --- a/gitlab/v4/objects/snippets.py +++ /dev/null @@ -1,123 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import utils -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin, UserAgentDetailMixin - -from .award_emojis import ProjectSnippetAwardEmojiManager # noqa: F401 -from .discussions import ProjectSnippetDiscussionManager # noqa: F401 -from .notes import ProjectSnippetNoteManager # noqa: F401 - -__all__ = [ - "Snippet", - "SnippetManager", - "ProjectSnippet", - "ProjectSnippetManager", -] - - -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 = RequiredOptional( - required=("title", "file_name", "content"), optional=("lifetime", "visibility") - ) - _update_attrs = RequiredOptional( - optional=("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" - - 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 = RequiredOptional( - required=("title", "file_name", "content", "visibility"), - optional=("description",), - ) - _update_attrs = RequiredOptional( - optional=("title", "file_name", "content", "visibility", "description"), - ) diff --git a/gitlab/v4/objects/statistics.py b/gitlab/v4/objects/statistics.py deleted file mode 100644 index 5d7c19e..0000000 --- a/gitlab/v4/objects/statistics.py +++ /dev/null @@ -1,52 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import GetWithoutIdMixin, RefreshMixin - -__all__ = [ - "GroupIssuesStatistics", - "GroupIssuesStatisticsManager", - "ProjectAdditionalStatistics", - "ProjectAdditionalStatisticsManager", - "IssuesStatistics", - "IssuesStatisticsManager", - "ProjectIssuesStatistics", - "ProjectIssuesStatisticsManager", -] - - -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 IssuesStatistics(RefreshMixin, RESTObject): - _id_attr = None - - -class IssuesStatisticsManager(GetWithoutIdMixin, RESTManager): - _path = "/issues_statistics" - _obj_cls = IssuesStatistics - - -class GroupIssuesStatistics(RefreshMixin, RESTObject): - _id_attr = None - - -class GroupIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): - _path = "/groups/%(group_id)s/issues_statistics" - _obj_cls = GroupIssuesStatistics - _from_parent_attrs = {"group_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 deleted file mode 100644 index 44fc23c..0000000 --- a/gitlab/v4/objects/tags.py +++ /dev/null @@ -1,37 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin - -__all__ = [ - "ProjectTag", - "ProjectTagManager", - "ProjectProtectedTag", - "ProjectProtectedTagManager", -] - - -class ProjectTag(ObjectDeleteMixin, RESTObject): - _id_attr = "name" - _short_print_attr = "name" - - -class ProjectTagManager(NoUpdateMixin, RESTManager): - _path = "/projects/%(project_id)s/repository/tags" - _obj_cls = ProjectTag - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("tag_name", "ref"), optional=("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 = RequiredOptional( - required=("name",), optional=("create_access_level",) - ) diff --git a/gitlab/v4/objects/templates.py b/gitlab/v4/objects/templates.py deleted file mode 100644 index 04de463..0000000 --- a/gitlab/v4/objects/templates.py +++ /dev/null @@ -1,51 +0,0 @@ -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import RetrieveMixin - -__all__ = [ - "Dockerfile", - "DockerfileManager", - "Gitignore", - "GitignoreManager", - "Gitlabciyml", - "GitlabciymlManager", - "License", - "LicenseManager", -] - - -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 deleted file mode 100644 index de82437..0000000 --- a/gitlab/v4/objects/todos.py +++ /dev/null @@ -1,50 +0,0 @@ -from gitlab import cli -from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin - -__all__ = [ - "Todo", - "TodoManager", -] - - -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 marked done - """ - self.gitlab.http_post("/todos/mark_as_done", **kwargs) diff --git a/gitlab/v4/objects/triggers.py b/gitlab/v4/objects/triggers.py deleted file mode 100644 index f203d93..0000000 --- a/gitlab/v4/objects/triggers.py +++ /dev/null @@ -1,19 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "ProjectTrigger", - "ProjectTriggerManager", -] - - -class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - -class ProjectTriggerManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/triggers" - _obj_cls = ProjectTrigger - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional(required=("description",)) - _update_attrs = RequiredOptional(required=("description",)) diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py deleted file mode 100644 index e4bbcf1..0000000 --- a/gitlab/v4/objects/users.py +++ /dev/null @@ -1,514 +0,0 @@ -from typing import Any, cast, Dict, List, Union - -import requests - -from gitlab import cli -from gitlab import exceptions as exc -from gitlab import types -from gitlab.base import RequiredOptional, RESTManager, RESTObject, RESTObjectList -from gitlab.mixins import ( - CreateMixin, - CRUDMixin, - DeleteMixin, - GetWithoutIdMixin, - ListMixin, - NoUpdateMixin, - ObjectDeleteMixin, - RetrieveMixin, - SaveMixin, - UpdateMixin, -) - -from .custom_attributes import UserCustomAttributeManager # noqa: F401 -from .events import UserEventManager # noqa: F401 -from .personal_access_tokens import UserPersonalAccessTokenManager # noqa: F401 - -__all__ = [ - "CurrentUserEmail", - "CurrentUserEmailManager", - "CurrentUserGPGKey", - "CurrentUserGPGKeyManager", - "CurrentUserKey", - "CurrentUserKeyManager", - "CurrentUserStatus", - "CurrentUserStatusManager", - "CurrentUser", - "CurrentUserManager", - "User", - "UserManager", - "ProjectUser", - "ProjectUserManager", - "UserEmail", - "UserEmailManager", - "UserActivities", - "UserStatus", - "UserStatusManager", - "UserActivitiesManager", - "UserGPGKey", - "UserGPGKeyManager", - "UserKey", - "UserKeyManager", - "UserIdentityProviderManager", - "UserImpersonationToken", - "UserImpersonationTokenManager", - "UserMembership", - "UserMembershipManager", - "UserProject", - "UserProjectManager", -] - - -class CurrentUserEmail(ObjectDeleteMixin, RESTObject): - _short_print_attr = "email" - - -class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/user/emails" - _obj_cls = CurrentUserEmail - _create_attrs = RequiredOptional(required=("email",)) - - -class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): - pass - - -class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/user/gpg_keys" - _obj_cls = CurrentUserGPGKey - _create_attrs = RequiredOptional(required=("key",)) - - -class CurrentUserKey(ObjectDeleteMixin, RESTObject): - _short_print_attr = "title" - - -class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = "/user/keys" - _obj_cls = CurrentUserKey - _create_attrs = RequiredOptional(required=("title", "key")) - - -class CurrentUserStatus(SaveMixin, RESTObject): - _id_attr = None - _short_print_attr = "message" - - -class CurrentUserStatusManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = "/user/status" - _obj_cls = CurrentUserStatus - _update_attrs = RequiredOptional(optional=("emoji", "message")) - - -class CurrentUser(RESTObject): - _id_attr = None - _short_print_attr = "username" - - emails: CurrentUserEmailManager - gpgkeys: CurrentUserGPGKeyManager - keys: CurrentUserKeyManager - status: CurrentUserStatusManager - - -class CurrentUserManager(GetWithoutIdMixin, RESTManager): - _path = "/user" - _obj_cls = CurrentUser - - -class User(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = "username" - - customattributes: UserCustomAttributeManager - emails: "UserEmailManager" - events: UserEventManager - followers_users: "UserFollowersManager" - following_users: "UserFollowingManager" - gpgkeys: "UserGPGKeyManager" - identityproviders: "UserIdentityProviderManager" - impersonationtokens: "UserImpersonationTokenManager" - keys: "UserKeyManager" - memberships: "UserMembershipManager" - personal_access_tokens: UserPersonalAccessTokenManager - projects: "UserProjectManager" - status: "UserStatusManager" - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabBlockError) - def block(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """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.GitlabFollowError) - def follow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Follow the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabFollowError: If the user could not be followed - - Returns: - dict: The new object data (*not* a RESTObject) - """ - path = "/users/%s/follow" % self.id - return self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabUnfollowError) - def unfollow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """Unfollow the user. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabUnfollowError: If the user could not be followed - - Returns: - dict: The new object data (*not* a RESTObject) - """ - path = "/users/%s/unfollow" % self.id - return self.manager.gitlab.http_post(path, **kwargs) - - @cli.register_custom_action("User") - @exc.on_http_error(exc.GitlabUnblockError) - def unblock(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: - """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: Any) -> Union[Dict[str, Any], requests.Response]: - """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: Any) -> Union[Dict[str, Any], requests.Response]: - """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 = RequiredOptional( - optional=( - "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 = RequiredOptional( - required=("email", "username", "name"), - optional=( - "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} - - def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> User: - return cast(User, super().get(id=id, lazy=lazy, **kwargs)) - - -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", "skip_users") - _types = {"skip_users": types.ListAttribute} - - -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 = RequiredOptional(required=("email",)) - - -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 = RequiredOptional(required=("key",)) - - -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 = RequiredOptional(required=("title", "key")) - - -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 = RequiredOptional( - required=("name", "scopes"), optional=("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 = RequiredOptional( - required=("name",), - optional=( - "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: Any) -> Union[RESTObjectList, List[RESTObject]]: - """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 UserFollowersManager(ListMixin, RESTManager): - _path = "/users/%(user_id)s/followers" - _obj_cls = User - _from_parent_attrs = {"user_id": "id"} - - -class UserFollowingManager(ListMixin, RESTManager): - _path = "/users/%(user_id)s/following" - _obj_cls = User - _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/variables.py b/gitlab/v4/objects/variables.py deleted file mode 100644 index 2e5e483..0000000 --- a/gitlab/v4/objects/variables.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -GitLab API: -https://docs.gitlab.com/ee/api/instance_level_ci_variables.html -https://docs.gitlab.com/ee/api/project_level_variables.html -https://docs.gitlab.com/ee/api/group_level_variables.html -""" -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "Variable", - "VariableManager", - "GroupVariable", - "GroupVariableManager", - "ProjectVariable", - "ProjectVariableManager", -] - - -class Variable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class VariableManager(CRUDMixin, RESTManager): - _path = "/admin/ci/variables" - _obj_cls = Variable - _create_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - _update_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - - -class GroupVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class GroupVariableManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/variables" - _obj_cls = GroupVariable - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - _update_attrs = RequiredOptional( - required=("key", "value"), optional=("protected", "variable_type", "masked") - ) - - -class ProjectVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "key" - - -class ProjectVariableManager(CRUDMixin, RESTManager): - _path = "/projects/%(project_id)s/variables" - _obj_cls = ProjectVariable - _from_parent_attrs = {"project_id": "id"} - _create_attrs = RequiredOptional( - required=("key", "value"), - optional=("protected", "variable_type", "masked", "environment_scope"), - ) - _update_attrs = RequiredOptional( - required=("key", "value"), - optional=("protected", "variable_type", "masked", "environment_scope"), - ) diff --git a/gitlab/v4/objects/wikis.py b/gitlab/v4/objects/wikis.py deleted file mode 100644 index a86b442..0000000 --- a/gitlab/v4/objects/wikis.py +++ /dev/null @@ -1,41 +0,0 @@ -from gitlab.base import RequiredOptional, RESTManager, RESTObject -from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin - -__all__ = [ - "ProjectWiki", - "ProjectWikiManager", - "GroupWiki", - "GroupWikiManager", -] - - -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 = RequiredOptional( - required=("title", "content"), optional=("format",) - ) - _update_attrs = RequiredOptional(optional=("title", "content", "format")) - _list_filters = ("with_content",) - - -class GroupWiki(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = "slug" - _short_print_attr = "slug" - - -class GroupWikiManager(CRUDMixin, RESTManager): - _path = "/groups/%(group_id)s/wikis" - _obj_cls = GroupWiki - _from_parent_attrs = {"group_id": "id"} - _create_attrs = RequiredOptional( - required=("title", "content"), optional=("format",) - ) - _update_attrs = RequiredOptional(optional=("title", "content", "format")) - _list_filters = ("with_content",) |