diff options
author | Nejc Habjan <hab.nejc@gmail.com> | 2021-12-01 01:04:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-01 01:04:53 +0100 |
commit | 8d76826fa64460e504acc5924f859f8dbc246b42 (patch) | |
tree | 083fefada982c795e2415092794db429abb0c184 /gitlab/base.py | |
parent | 5a1678f43184bd459132102cc13cf8426fe0449d (diff) | |
parent | 86ab04e54ea4175f10053decfad5086cda7aa024 (diff) | |
download | gitlab-master.tar.gz |
Merge pull request #1723 from python-gitlab/jlvillal/dead_mastermaster
Close-out `master` branch
Diffstat (limited to 'gitlab/base.py')
-rw-r--r-- | gitlab/base.py | 331 |
1 files changed, 0 insertions, 331 deletions
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 |