Skip to content

Commit

Permalink
Merge pull request #1299 from JohnVillalovos/mypy
Browse files Browse the repository at this point in the history
Enable mypy type checking and add type hints to gitlab/base.py
  • Loading branch information
nejch committed Feb 23, 2021
2 parents d9fdf1d + 3727cbd commit a18bc5c
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 32 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/lint.yml
Expand Up @@ -27,3 +27,11 @@ jobs:
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v2

mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install --upgrade tox
- run: tox -e mypy
2 changes: 2 additions & 0 deletions .mypy.ini
@@ -0,0 +1,2 @@
[mypy]
files = gitlab/*.py
63 changes: 34 additions & 29 deletions gitlab/base.py
Expand Up @@ -16,7 +16,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import importlib
from typing import Any, Dict, Optional

from .client import Gitlab, GitlabList

__all__ = [
"RESTObject",
Expand All @@ -38,7 +40,7 @@ class RESTObject(object):

_id_attr = "id"

def __init__(self, manager, attrs):
def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None:
self.__dict__.update(
{
"manager": manager,
Expand All @@ -50,18 +52,18 @@ def __init__(self, manager, attrs):
self.__dict__["_parent_attrs"] = self.manager.parent_attrs
self._create_managers()

def __getstate__(self):
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):
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):
def __getattr__(self, name: str) -> Any:
try:
return self.__dict__["_updated_attrs"][name]
except KeyError:
Expand Down Expand Up @@ -90,15 +92,15 @@ def __getattr__(self, name):
except KeyError:
raise AttributeError(name)

def __setattr__(self, name, value):
def __setattr__(self, name: str, value) -> None:
self.__dict__["_updated_attrs"][name] = value

def __str__(self):
def __str__(self) -> str:
data = self._attrs.copy()
data.update(self._updated_attrs)
return "%s => %s" % (type(self), data)

def __repr__(self):
def __repr__(self) -> str:
if self._id_attr:
return "<%s %s:%s>" % (
self.__class__.__name__,
Expand All @@ -108,25 +110,25 @@ def __repr__(self):
else:
return "<%s>" % self.__class__.__name__

def __eq__(self, other):
def __eq__(self, other) -> bool:
if self.get_id() and other.get_id():
return self.get_id() == other.get_id()
return super(RESTObject, self) == other

def __ne__(self, other):
def __ne__(self, other) -> bool:
if self.get_id() and other.get_id():
return self.get_id() != other.get_id()
return super(RESTObject, self) != other

def __dir__(self):
return super(RESTObject, self).__dir__() + list(self.attributes)

def __hash__(self):
def __hash__(self) -> int:
if not self.get_id():
return super(RESTObject, self).__hash__()
return hash(self.get_id())

def _create_managers(self):
def _create_managers(self) -> None:
managers = getattr(self, "_managers", None)
if managers is None:
return
Expand All @@ -136,7 +138,7 @@ def _create_managers(self):
manager = cls(self.manager.gitlab, parent=self)
self.__dict__[attr] = manager

def _update_attrs(self, new_attrs):
def _update_attrs(self, new_attrs) -> None:
self.__dict__["_updated_attrs"] = {}
self.__dict__["_attrs"] = new_attrs

Expand All @@ -147,7 +149,7 @@ def get_id(self):
return getattr(self, self._id_attr)

@property
def attributes(self):
def attributes(self) -> Dict[str, Any]:
d = self.__dict__["_updated_attrs"].copy()
d.update(self.__dict__["_attrs"])
d.update(self.__dict__["_parent_attrs"])
Expand All @@ -169,7 +171,7 @@ class RESTObjectList(object):
_list: A GitlabList object
"""

def __init__(self, manager, obj_cls, _list):
def __init__(self, manager: "RESTManager", obj_cls, _list: GitlabList) -> None:
"""Creates an objects list from a GitlabList.
You should not create objects of this type, but use managers list()
Expand All @@ -184,10 +186,10 @@ def __init__(self, manager, obj_cls, _list):
self._obj_cls = obj_cls
self._list = _list

def __iter__(self):
def __iter__(self) -> "RESTObjectList":
return self

def __len__(self):
def __len__(self) -> int:
return len(self._list)

def __next__(self):
Expand All @@ -198,38 +200,38 @@ def next(self):
return self._obj_cls(self.manager, data)

@property
def current_page(self):
def current_page(self) -> int:
"""The current page number."""
return self._list.current_page

@property
def prev_page(self):
def prev_page(self) -> int:
"""The previous page number.
If None, the current page is the first.
"""
return self._list.prev_page

@property
def next_page(self):
def next_page(self) -> int:
"""The next page number.
If None, the current page is the last.
"""
return self._list.next_page

@property
def per_page(self):
def per_page(self) -> int:
"""The number of items per page."""
return self._list.per_page

@property
def total_pages(self):
def total_pages(self) -> int:
"""The total number of pages."""
return self._list.total_pages

@property
def total(self):
def total(self) -> int:
"""The total number of items."""
return self._list.total

Expand All @@ -243,10 +245,11 @@ class RESTManager(object):
``_obj_cls``: The class of objects that will be created
"""

_path = None
_obj_cls = None
_path: Optional[str] = None
_obj_cls: Optional[Any] = None
_from_parent_attrs: Dict[str, Any] = {}

def __init__(self, gl, parent=None):
def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None:
"""REST manager constructor.
Args:
Expand All @@ -259,23 +262,25 @@ def __init__(self, gl, parent=None):
self._computed_path = self._compute_path()

@property
def parent_attrs(self):
def parent_attrs(self) -> Optional[Dict[str, Any]]:
return self._parent_attrs

def _compute_path(self, path=None):
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 hasattr(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()
for self_attr, parent_attr in self._from_parent_attrs.items() # type: ignore
}
self._parent_attrs = data
return path % data

@property
def path(self):
def path(self) -> Optional[str]:
return self._computed_path
2 changes: 1 addition & 1 deletion gitlab/cli.py
Expand Up @@ -193,7 +193,7 @@ def main():
# Now we build the entire set of subcommands and do the complete parsing
parser = _get_parser(gitlab.v4.cli)
try:
import argcomplete
import argcomplete # type: ignore

argcomplete.autocomplete(parser)
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion gitlab/client.py
Expand Up @@ -25,7 +25,7 @@
import gitlab.const
import gitlab.exceptions
from gitlab import utils
from requests_toolbelt.multipart.encoder import MultipartEncoder
from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore


REDIRECT_MSG = (
Expand Down
1 change: 1 addition & 0 deletions test-requirements.txt
@@ -1,6 +1,7 @@
coverage
httmock
mock
mypy
pytest
pytest-cov
responses
9 changes: 8 additions & 1 deletion tox.ini
@@ -1,7 +1,7 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py39,py38,py37,py36,pep8,black,twine-check
envlist = py39,py38,py37,py36,pep8,black,twine-check,mypy

[testenv]
passenv = GITLAB_IMAGE GITLAB_TAG
Expand Down Expand Up @@ -35,6 +35,13 @@ deps = -r{toxinidir}/requirements.txt
commands =
twine check dist/*

[testenv:mypy]
basepython = python3
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
mypy {posargs}

[testenv:venv]
commands = {posargs}

Expand Down

0 comments on commit a18bc5c

Please sign in to comment.