diff --git a/gitlab/base.py b/gitlab/base.py index e813fcd92..c2df65ae4 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -21,7 +21,7 @@ import pprint import textwrap from types import ModuleType -from typing import Any, Dict, Iterable, Optional, Type, Union +from typing import Any, Dict, Iterable, Optional, Type, TYPE_CHECKING, Union import gitlab from gitlab import types as g_types @@ -245,14 +245,20 @@ def get_id(self) -> Optional[Union[int, str]]: """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) + id_val = getattr(self, self._id_attr) + if TYPE_CHECKING: + assert id_val is None or isinstance(id_val, (int, str)) + return id_val @property def _repr_value(self) -> Optional[str]: """Safely returns the human-readable resource name if present.""" if self._repr_attr is None or not hasattr(self, self._repr_attr): return None - return getattr(self, self._repr_attr) + repr_val = getattr(self, self._repr_attr) + if TYPE_CHECKING: + assert isinstance(repr_val, str) + return repr_val @property def encoded_id(self) -> Optional[Union[int, str]]: diff --git a/gitlab/cli.py b/gitlab/cli.py index fd519c381..294d6a8a3 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -23,7 +23,18 @@ import re import sys from types import ModuleType -from typing import Any, Callable, cast, Dict, Optional, Tuple, Type, TypeVar, Union +from typing import ( + Any, + Callable, + cast, + Dict, + Optional, + Tuple, + Type, + TYPE_CHECKING, + TypeVar, + Union, +) from requests.structures import CaseInsensitiveDict @@ -113,8 +124,11 @@ def gitlab_resource_to_cls( ) -> Type[RESTObject]: classes = CaseInsensitiveDict(namespace.__dict__) lowercase_class = gitlab_resource.replace("-", "") - - return classes[lowercase_class] + class_type = classes[lowercase_class] + if TYPE_CHECKING: + assert isinstance(class_type, type) + assert issubclass(class_type, RESTObject) + return class_type def cls_to_gitlab_resource(cls: RESTObject) -> str: diff --git a/gitlab/client.py b/gitlab/client.py index e7c44ae5e..97ca63635 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -439,6 +439,7 @@ def markdown( data = self.http_post("/markdown", post_data=post_data, **kwargs) if TYPE_CHECKING: assert not isinstance(data, requests.Response) + assert isinstance(data["html"], str) return data["html"] @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabLicenseError) @@ -808,7 +809,10 @@ def http_get( and not raw ): try: - return result.json() + json_result = result.json() + if TYPE_CHECKING: + assert isinstance(json_result, dict) + return json_result except Exception as e: raise gitlab.exceptions.GitlabParsingError( error_message="Failed to parse the server message" @@ -989,7 +993,10 @@ def http_post( ) try: if result.headers.get("Content-Type", None) == "application/json": - return result.json() + json_result = result.json() + if TYPE_CHECKING: + assert isinstance(json_result, dict) + return json_result except Exception as e: raise gitlab.exceptions.GitlabParsingError( error_message="Failed to parse the server message" @@ -1037,7 +1044,10 @@ def http_put( **kwargs, ) try: - return result.json() + json_result = result.json() + if TYPE_CHECKING: + assert isinstance(json_result, dict) + return json_result except Exception as e: raise gitlab.exceptions.GitlabParsingError( error_message="Failed to parse the server message" diff --git a/gitlab/mixins.py b/gitlab/mixins.py index a48c032fe..d58227a47 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -755,7 +755,10 @@ def time_stats(self, **kwargs: Any) -> Dict[str, Any]: # 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"] + time_stats = self.attributes["time_stats"] + if TYPE_CHECKING: + assert isinstance(time_stats, dict) + return time_stats path = f"{self.manager.path}/{self.encoded_id}/time_stats" result = self.manager.gitlab.http_get(path, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index 43359e986..544543bc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ disallow_untyped_defs = true no_implicit_reexport = true strict_equality = true warn_redundant_casts = true +warn_return_any = true warn_unused_configs = true warn_unused_ignores = true @@ -23,7 +24,6 @@ warn_unused_ignores = true # disallow_any_generics = true # disallow_untyped_calls = true # no_implicit_optional = true -# warn_return_any = true [[tool.mypy.overrides]] # Overrides for currently untyped modules module = [ diff --git a/tests/unit/objects/test_projects.py b/tests/unit/objects/test_projects.py index 6134382f8..1e5e7d18b 100644 --- a/tests/unit/objects/test_projects.py +++ b/tests/unit/objects/test_projects.py @@ -431,7 +431,7 @@ def resp_start_housekeeping(): rsps.add( method=responses.POST, url="http://localhost/api/v4/projects/1/housekeeping", - json="0ee4c430667fb7be8461f310", + json={}, content_type="application/json", status=201, ) diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index ef33b5db9..30693d995 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -24,6 +24,7 @@ import pytest +import gitlab.base from gitlab import cli from gitlab.exceptions import GitlabError @@ -43,7 +44,7 @@ def test_gitlab_resource_to_cls(gitlab_resource, expected_class): def _namespace(): pass - ExpectedClass = type(expected_class, (), {}) + ExpectedClass = type(expected_class, (gitlab.base.RESTObject,), {}) _namespace.__dict__[expected_class] = ExpectedClass assert cli.gitlab_resource_to_cls(gitlab_resource, _namespace) == ExpectedClass