diff --git a/gitlab/base.py b/gitlab/base.py index e813fcd92..6fe1ab1fe 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,22 @@ 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 id_val is None: + return None + if TYPE_CHECKING: + assert 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 979396407..e5c85e3b1 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 @@ -96,8 +107,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..e004e7ce6 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -44,6 +44,8 @@ f"api-usage.html#pagination" ) +HttpResponseType = Union[Dict[str, Any], List[Any], requests.Response] + class Gitlab: """Represents a GitLab server connection. @@ -411,7 +413,7 @@ def lint(self, content: str, **kwargs: Any) -> Tuple[bool, List[str]]: post_data = {"content": content} data = self.http_post("/ci/lint", post_data=post_data, **kwargs) if TYPE_CHECKING: - assert not isinstance(data, requests.Response) + assert isinstance(data, dict) return (data["status"] == "valid", data["errors"]) @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabMarkdownError) @@ -438,7 +440,8 @@ def markdown( post_data["project"] = project data = self.http_post("/markdown", post_data=post_data, **kwargs) if TYPE_CHECKING: - assert not isinstance(data, requests.Response) + assert isinstance(data, dict) + assert isinstance(data["html"], str) return data["html"] @gitlab.exceptions.on_http_error(gitlab.exceptions.GitlabLicenseError) @@ -478,7 +481,7 @@ def set_license(self, license: str, **kwargs: Any) -> Dict[str, Any]: data = {"license": license} result = self.http_post("/license", post_data=data, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result def _set_auth_info(self) -> None: @@ -777,7 +780,7 @@ def http_get( streamed: bool = False, raw: bool = False, **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: + ) -> HttpResponseType: """Make a GET request to the Gitlab server. Args: @@ -808,7 +811,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" @@ -954,7 +960,7 @@ def http_post( raw: bool = False, files: Optional[Dict[str, Any]] = None, **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: + ) -> HttpResponseType: """Make a POST request to the Gitlab server. Args: @@ -989,7 +995,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 +1046,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 f33a1fcf7..b323851d6 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -32,6 +32,7 @@ import requests import gitlab +import gitlab.client as gl_client from gitlab import base, cli from gitlab import exceptions as exc from gitlab import utils @@ -138,7 +139,7 @@ def get( return self._obj_cls(self, {self._obj_cls._id_attr: id}, lazy=lazy) server_data = self.gitlab.http_get(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) + assert isinstance(server_data, dict) return self._obj_cls(self, server_data, lazy=lazy) @@ -170,7 +171,7 @@ def get(self, **kwargs: Any) -> base.RESTObject: assert self.path is not None server_data = self.gitlab.http_get(self.path, **kwargs) if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) + assert isinstance(server_data, dict) assert self._obj_cls is not None return self._obj_cls(self, server_data) @@ -204,7 +205,7 @@ def refresh(self, **kwargs: Any) -> None: path = self.manager.path server_data = self.manager.gitlab.http_get(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) + assert isinstance(server_data, dict) self._update_attrs(server_data) @@ -309,7 +310,7 @@ def create( 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 isinstance(server_data, dict) assert self._obj_cls is not None return self._obj_cls(self, server_data) @@ -326,7 +327,7 @@ class UpdateMixin(_RestManagerBase): def _get_update_method( self, - ) -> Callable[..., Union[Dict[str, Any], requests.Response]]: + ) -> Callable[..., gl_client.HttpResponseType]: """Return the HTTP method to use. Returns: @@ -375,7 +376,7 @@ def update( 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) + assert isinstance(result, dict) return result @@ -562,7 +563,7 @@ def user_agent_detail(self, **kwargs: Any) -> Dict[str, Any]: path = f"{self.manager.path}/{self.encoded_id}/user_agent_detail" result = self.manager.gitlab.http_get(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result @@ -675,7 +676,7 @@ def subscribe(self, **kwargs: Any) -> None: path = f"{self.manager.path}/{self.encoded_id}/subscribe" server_data = self.manager.gitlab.http_post(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) + assert isinstance(server_data, dict) self._update_attrs(server_data) @cli.register_custom_action( @@ -695,7 +696,7 @@ def unsubscribe(self, **kwargs: Any) -> None: path = f"{self.manager.path}/{self.encoded_id}/unsubscribe" server_data = self.manager.gitlab.http_post(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(server_data, requests.Response) + assert isinstance(server_data, dict) self._update_attrs(server_data) @@ -746,12 +747,15 @@ 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) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) @@ -771,7 +775,7 @@ def time_estimate(self, duration: str, **kwargs: Any) -> Dict[str, Any]: data = {"duration": duration} result = self.manager.gitlab.http_post(path, post_data=data, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) @@ -789,7 +793,7 @@ def reset_time_estimate(self, **kwargs: Any) -> Dict[str, Any]: path = f"{self.manager.path}/{self.encoded_id}/reset_time_estimate" result = self.manager.gitlab.http_post(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) @@ -809,7 +813,7 @@ def add_spent_time(self, duration: str, **kwargs: Any) -> Dict[str, Any]: data = {"duration": duration} result = self.manager.gitlab.http_post(path, post_data=data, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) @@ -827,7 +831,7 @@ def reset_spent_time(self, **kwargs: Any) -> Dict[str, Any]: path = f"{self.manager.path}/{self.encoded_id}/reset_spent_time" result = self.manager.gitlab.http_post(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result @@ -841,7 +845,7 @@ class ParticipantsMixin(_RestObjectBase): @cli.register_custom_action(("ProjectMergeRequest", "ProjectIssue")) @exc.on_http_error(exc.GitlabListError) - def participants(self, **kwargs: Any) -> Dict[str, Any]: + def participants(self, **kwargs: Any) -> List[Any]: """List the participants. Args: @@ -861,7 +865,7 @@ def participants(self, **kwargs: Any) -> Dict[str, Any]: path = f"{self.manager.path}/{self.encoded_id}/participants" result = self.manager.gitlab.http_get(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, list) return result @@ -889,7 +893,7 @@ def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any] 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) + assert isinstance(result, dict) return result @@ -904,7 +908,7 @@ class PromoteMixin(_RestObjectBase): def _get_update_method( self, - ) -> Callable[..., Union[Dict[str, Any], requests.Response]]: + ) -> Callable[..., gl_client.HttpResponseType]: """Return the HTTP method to use. Returns: @@ -936,5 +940,5 @@ def promote(self, **kwargs: Any) -> Dict[str, Any]: http_method = self._get_update_method() result = http_method(path, **kwargs) if TYPE_CHECKING: - assert not isinstance(result, requests.Response) + assert isinstance(result, dict) return result diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py index 8558ef9ea..0c0372099 100644 --- a/gitlab/v4/objects/commits.py +++ b/gitlab/v4/objects/commits.py @@ -1,7 +1,5 @@ from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union -import requests - import gitlab from gitlab import cli from gitlab import exceptions as exc @@ -107,9 +105,7 @@ def merge_requests( @cli.register_custom_action("ProjectCommit", ("branch",)) @exc.on_http_error(exc.GitlabRevertError) - def revert( - self, branch: str, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + def revert(self, branch: str, **kwargs: Any) -> gitlab.client.HttpResponseType: """Revert a commit on a given branch. Args: @@ -129,7 +125,7 @@ def revert( @cli.register_custom_action("ProjectCommit") @exc.on_http_error(exc.GitlabGetError) - def signature(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def signature(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Get the signature of the commit. Args: diff --git a/gitlab/v4/objects/deploy_keys.py b/gitlab/v4/objects/deploy_keys.py index 0962b4a39..88c9122a7 100644 --- a/gitlab/v4/objects/deploy_keys.py +++ b/gitlab/v4/objects/deploy_keys.py @@ -1,7 +1,6 @@ -from typing import Any, cast, Dict, Union - -import requests +from typing import Any, cast, Union +import gitlab from gitlab import cli from gitlab import exceptions as exc from gitlab.base import RESTManager, RESTObject @@ -38,9 +37,7 @@ class ProjectKeyManager(CRUDMixin, RESTManager): @cli.register_custom_action("ProjectKeyManager", ("key_id",)) @exc.on_http_error(exc.GitlabProjectDeployKeyError) - def enable( - self, key_id: int, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + def enable(self, key_id: int, **kwargs: Any) -> gitlab.client.HttpResponseType: """Enable a deploy key for a project. Args: diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py index a8bd9d5dd..6cf0a6ee7 100644 --- a/gitlab/v4/objects/environments.py +++ b/gitlab/v4/objects/environments.py @@ -1,7 +1,6 @@ -from typing import Any, cast, Dict, Union - -import requests +from typing import Any, cast, Union +import gitlab from gitlab import cli from gitlab import exceptions as exc from gitlab.base import RESTManager, RESTObject @@ -26,7 +25,7 @@ class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject): @cli.register_custom_action("ProjectEnvironment") @exc.on_http_error(exc.GitlabStopError) - def stop(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def stop(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Stop the environment. Args: diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index ae7184634..15b79dcd2 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -1,7 +1,5 @@ from typing import Any, BinaryIO, cast, Dict, List, Optional, Type, TYPE_CHECKING, Union -import requests - import gitlab from gitlab import cli from gitlab import exceptions as exc @@ -332,7 +330,7 @@ def import_group( name: str, parent_id: Optional[str] = None, **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: + ) -> gitlab.client.HttpResponseType: """Import a group from an archive file. Args: diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py index 9eb965b93..6bcaf467d 100644 --- a/gitlab/v4/objects/merge_requests.py +++ b/gitlab/v4/objects/merge_requests.py @@ -5,8 +5,6 @@ """ from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union -import requests - import gitlab from gitlab import cli from gitlab import exceptions as exc @@ -244,7 +242,7 @@ def commits(self, **kwargs: Any) -> RESTObjectList: @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) - def changes(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def changes(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """List the merge request changes. Args: @@ -313,7 +311,7 @@ def unapprove(self, **kwargs: Any) -> None: @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabMRRebaseError) - def rebase(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def rebase(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Attempt to rebase the source branch onto the target branch Args: @@ -329,7 +327,7 @@ def rebase(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabGetError) - def merge_ref(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def merge_ref(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Attempt to merge changes between source and target branches into `refs/merge-requests/:iid/merge`. diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py index eec46a1b9..2bb0d6270 100644 --- a/gitlab/v4/objects/pipelines.py +++ b/gitlab/v4/objects/pipelines.py @@ -1,7 +1,6 @@ from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union -import requests - +import gitlab from gitlab import cli from gitlab import exceptions as exc from gitlab.base import RESTManager, RESTObject @@ -60,7 +59,7 @@ class ProjectPipeline(RefreshMixin, ObjectDeleteMixin, RESTObject): @cli.register_custom_action("ProjectPipeline") @exc.on_http_error(exc.GitlabPipelineCancelError) - def cancel(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def cancel(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Cancel the job. Args: @@ -75,7 +74,7 @@ def cancel(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("ProjectPipeline") @exc.on_http_error(exc.GitlabPipelineRetryError) - def retry(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def retry(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Retry the job. Args: diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 96d802bb0..c1ed23a77 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -245,7 +245,7 @@ def delete_fork_relation(self, **kwargs: Any) -> None: @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabGetError) - def languages(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def languages(self, **kwargs: Any) -> client.HttpResponseType: """Get languages used in the project with percentage value. Args: @@ -792,7 +792,7 @@ def import_project( overwrite: bool = False, override_params: Optional[Dict[str, Any]] = None, **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: + ) -> client.HttpResponseType: """Import a project from an archive file. Args: @@ -835,7 +835,7 @@ def import_bitbucket_server( new_name: Optional[str] = None, target_namespace: Optional[str] = None, **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: + ) -> client.HttpResponseType: """Import a project from BitBucket Server to Gitlab (schedule the import) This method will return when an import operation has been safely queued, @@ -924,7 +924,7 @@ def import_github( target_namespace: str, new_name: Optional[str] = None, **kwargs: Any, - ) -> Union[Dict[str, Any], requests.Response]: + ) -> client.HttpResponseType: """Import a project from Github to Gitlab (schedule the import) This method will return when an import operation has been safely queued, diff --git a/gitlab/v4/objects/repositories.py b/gitlab/v4/objects/repositories.py index e968a6a6a..ca7f409e6 100644 --- a/gitlab/v4/objects/repositories.py +++ b/gitlab/v4/objects/repositories.py @@ -24,7 +24,7 @@ class RepositoryMixin(_RestObjectBase): @exc.on_http_error(exc.GitlabUpdateError) def update_submodule( self, submodule: str, branch: str, commit_sha: str, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + ) -> gitlab.client.HttpResponseType: """Update a project submodule Args: @@ -83,7 +83,7 @@ def repository_tree( @exc.on_http_error(exc.GitlabGetError) def repository_blob( self, sha: str, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + ) -> gitlab.client.HttpResponseType: """Return a file by blob SHA. Args: @@ -148,7 +148,7 @@ def repository_raw_blob( @exc.on_http_error(exc.GitlabGetError) def repository_compare( self, from_: str, to: str, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + ) -> gitlab.client.HttpResponseType: """Return a diff between two branches/commits. Args: diff --git a/gitlab/v4/objects/sidekiq.py b/gitlab/v4/objects/sidekiq.py index c0bf9d249..78420fc57 100644 --- a/gitlab/v4/objects/sidekiq.py +++ b/gitlab/v4/objects/sidekiq.py @@ -1,7 +1,6 @@ -from typing import Any, Dict, Union - -import requests +from typing import Any +import gitlab from gitlab import cli from gitlab import exceptions as exc from gitlab.base import RESTManager @@ -20,7 +19,7 @@ class SidekiqManager(RESTManager): @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) - def queue_metrics(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def queue_metrics(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Return the registered queues information. Args: @@ -37,9 +36,7 @@ def queue_metrics(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Respons @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) - def process_metrics( - self, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + def process_metrics(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Return the registered sidekiq workers. Args: @@ -56,7 +53,7 @@ def process_metrics( @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) - def job_stats(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def job_stats(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Return statistics about the jobs performed. Args: @@ -73,9 +70,7 @@ def job_stats(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) - def compound_metrics( - self, **kwargs: Any - ) -> Union[Dict[str, Any], requests.Response]: + def compound_metrics(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Return all available metrics and statistics. Args: diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index 69d875ed9..fada6499c 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -3,10 +3,9 @@ https://docs.gitlab.com/ee/api/users.html https://docs.gitlab.com/ee/api/projects.html#list-projects-starred-by-a-user """ -from typing import Any, cast, Dict, List, Optional, Union - -import requests +from typing import Any, cast, List, Optional, Union +import gitlab from gitlab import cli from gitlab import exceptions as exc from gitlab import types @@ -188,7 +187,7 @@ def block(self, **kwargs: Any) -> Optional[bool]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabFollowError) - def follow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def follow(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Follow the user. Args: @@ -206,7 +205,7 @@ def follow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabUnfollowError) - def unfollow(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def unfollow(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Unfollow the user. Args: @@ -249,7 +248,7 @@ def unblock(self, **kwargs: Any) -> Optional[bool]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabDeactivateError) - def deactivate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def deactivate(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Deactivate the user. Args: @@ -270,7 +269,7 @@ def deactivate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabActivateError) - def activate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def activate(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Activate the user. Args: @@ -291,7 +290,7 @@ def activate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabUserApproveError) - def approve(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def approve(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Approve a user creation request. Args: @@ -309,7 +308,7 @@ def approve(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabUserRejectError) - def reject(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def reject(self, **kwargs: Any) -> gitlab.client.HttpResponseType: """Reject a user creation request. Args: @@ -327,7 +326,7 @@ def reject(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabBanError) - def ban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def ban(self, **kwargs: Any) -> bool: """Ban the user. Args: @@ -341,14 +340,16 @@ def ban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: Whether the user has been banned """ path = f"/users/{self.encoded_id}/ban" - server_data = self.manager.gitlab.http_post(path, **kwargs) - if server_data: + # NOTE: Undocumented behavior of the GitLab API is that it returns True + # on success. + server_data = cast(bool, self.manager.gitlab.http_post(path, **kwargs)) + if server_data is True: self._attrs["state"] = "banned" return server_data @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabUnbanError) - def unban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + def unban(self, **kwargs: Any) -> bool: """Unban the user. Args: @@ -362,8 +363,10 @@ def unban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: Whether the user has been unbanned """ path = f"/users/{self.encoded_id}/unban" - server_data = self.manager.gitlab.http_post(path, **kwargs) - if server_data: + # NOTE: Undocumented behavior of the GitLab API is that it returns True + # on success. + server_data = cast(bool, self.manager.gitlab.http_post(path, **kwargs)) + if server_data is True: self._attrs["state"] = "active" return server_data 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/functional/api/test_users.py b/tests/functional/api/test_users.py index a099e8fb2..ba6f60cd9 100644 --- a/tests/functional/api/test_users.py +++ b/tests/functional/api/test_users.py @@ -3,8 +3,11 @@ https://docs.gitlab.com/ee/api/users.html https://docs.gitlab.com/ee/api/users.html#delete-authentication-identity-from-user """ +import pytest import requests +import gitlab.exceptions + def test_create_user(gl, fixture_dir): user = gl.users.create( @@ -45,19 +48,29 @@ def test_block_user(gl, user): # unblock again result = user.unblock() - # Trying to unblock an already blocked user returns False + # Trying to unblock an already un-blocked user returns False assert result is False def test_ban_user(gl, user): - user.ban() + result = user.ban() + assert result is True retrieved_user = gl.users.get(user.id) assert retrieved_user.state == "banned" - user.unban() + # ban an already banned user raises an exception + with pytest.raises(gitlab.exceptions.GitlabBanError): + user.ban() + + result = user.unban() + assert result is True retrieved_user = gl.users.get(user.id) assert retrieved_user.state == "active" + # unban an already un-banned user raises an exception + with pytest.raises(gitlab.exceptions.GitlabUnbanError): + user.unban() + def test_delete_user(gl, wait_for_sidekiq): new_user = gl.users.create( diff --git a/tests/unit/objects/test_projects.py b/tests/unit/objects/test_projects.py index 85bae8600..3e2f75356 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