| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a6b6cd4 commit 6aef2da
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,3 +4,6 @@ files = gitlab/*.py | |||
| 4 | 4 | # disallow_incomplete_defs: This flag reports an error whenever it encounters a | |
| 5 | 5 | # partly annotated function definition. | |
| 6 | 6 | disallow_incomplete_defs = True | |
| 7 | + # disallow_untyped_defs: This flag reports an error whenever it encounters a | ||
| 8 | + # function without type annotations or with incomplete type annotations. | ||
| 9 | + disallow_untyped_defs = True | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | import gitlab.cli | |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | - __name__ == "__main__" and gitlab.cli.main() | ||
| 4 | + if __name__ == "__main__": | ||
| 5 | + gitlab.cli.main() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,7 +17,7 @@ | |||
| 17 | 17 | ||
| 18 | 18 | import importlib | |
| 19 | 19 | from types import ModuleType | |
| 20 | - from typing import Any, Dict, NamedTuple, Optional, Tuple, Type | ||
| 20 | + from typing import Any, Dict, Iterable, NamedTuple, Optional, Tuple, Type | ||
| 21 | 21 | ||
| 22 | 22 | from .client import Gitlab, GitlabList | |
| 23 | 23 | from gitlab import types as g_types | |
@@ -133,8 +133,8 @@ def __ne__(self, other: object) -> bool: | |||
| 133 | 133 | return self.get_id() != other.get_id() | |
| 134 | 134 | return super(RESTObject, self) != other | |
| 135 | 135 | ||
| 136 | - def __dir__(self): | ||
| 137 | - return super(RESTObject, self).__dir__() | self.attributes.keys() | ||
| 136 | + def __dir__(self) -> Iterable[str]: | ||
| 137 | + return set(self.attributes).union(super(RESTObject, self).__dir__()) | ||
| 138 | 138 | ||
| 139 | 139 | def __hash__(self) -> int: | |
| 140 | 140 | if not self.get_id(): | |
@@ -155,7 +155,7 @@ def _update_attrs(self, new_attrs: Dict[str, Any]) -> None: | |||
| 155 | 155 | self.__dict__["_updated_attrs"] = {} | |
| 156 | 156 | self.__dict__["_attrs"] = new_attrs | |
| 157 | 157 | ||
| 158 | - def get_id(self): | ||
| 158 | + def get_id(self) -> Any: | ||
| 159 | 159 | """Returns the id of the resource.""" | |
| 160 | 160 | if self._id_attr is None or not hasattr(self, self._id_attr): | |
| 161 | 161 | return None | |
@@ -207,10 +207,10 @@ def __iter__(self) -> "RESTObjectList": | |||
| 207 | 207 | def __len__(self) -> int: | |
| 208 | 208 | return len(self._list) | |
| 209 | 209 | ||
| 210 | - def __next__(self): | ||
| 210 | + def __next__(self) -> RESTObject: | ||
| 211 | 211 | return self.next() | |
| 212 | 212 | ||
| 213 | - def next(self): | ||
| 213 | + def next(self) -> RESTObject: | ||
| 214 | 214 | data = self._list.next() | |
| 215 | 215 | return self._obj_cls(self.manager, data) | |
| 216 | 216 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,7 +21,7 @@ | |||
| 21 | 21 | import functools | |
| 22 | 22 | import re | |
| 23 | 23 | import sys | |
| 24 | - from typing import Any, Callable, Dict, Optional, Tuple, Union | ||
| 24 | + from typing import Any, Callable, cast, Dict, Optional, Tuple, TypeVar, Union | ||
| 25 | 25 | ||
| 26 | 26 | import gitlab.config # noqa: F401 | |
| 27 | 27 | ||
@@ -35,14 +35,21 @@ | |||
| 35 | 35 | custom_actions: Dict[str, Dict[str, Tuple[Tuple[str, ...], Tuple[str, ...], bool]]] = {} | |
| 36 | 36 | ||
| 37 | 37 | ||
| 38 | + # For an explanation of how these type-hints work see: | ||
| 39 | + # https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators | ||
| 40 | + # | ||
| 41 | + # The goal here is that functions which get decorated will retain their types. | ||
| 42 | + __F = TypeVar("__F", bound=Callable[..., Any]) | ||
| 43 | + | ||
| 44 | + | ||
| 38 | 45 | def register_custom_action( | |
| 39 | 46 | cls_names: Union[str, Tuple[str, ...]], | |
| 40 | 47 | mandatory: Tuple[str, ...] = tuple(), | |
| 41 | 48 | optional: Tuple[str, ...] = tuple(), | |
| 42 | - ) -> Callable: | ||
| 43 | - def wrap(f: Callable) -> Callable: | ||
| 49 | + ) -> Callable[[__F], __F]: | ||
| 50 | + def wrap(f: __F) -> __F: | ||
| 44 | 51 | @functools.wraps(f) | |
| 45 | - def wrapped_f(*args, **kwargs): | ||
| 52 | + def wrapped_f(*args: Any, **kwargs: Any) -> Any: | ||
| 46 | 53 | return f(*args, **kwargs) | |
| 47 | 54 | ||
| 48 | 55 | # in_obj defines whether the method belongs to the obj or the manager | |
@@ -63,7 +70,7 @@ def wrapped_f(*args, **kwargs): | |||
| 63 | 70 | action = f.__name__.replace("_", "-") | |
| 64 | 71 | custom_actions[final_name][action] = (mandatory, optional, in_obj) | |
| 65 | 72 | ||
| 66 | - return wrapped_f | ||
| 73 | + return cast(__F, wrapped_f) | ||
| 67 | 74 | ||
| 68 | 75 | return wrap | |
| 69 | 76 | ||
@@ -135,12 +142,16 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser: | |||
| 135 | 142 | return parser | |
| 136 | 143 | ||
| 137 | 144 | ||
| 138 | - def _get_parser(cli_module): | ||
| 145 | + def _get_parser() -> argparse.ArgumentParser: | ||
| 146 | + # NOTE: We must delay import of gitlab.v4.cli until now or | ||
| 147 | + # otherwise it will cause circular import errors | ||
| 148 | + import gitlab.v4.cli | ||
| 149 | + | ||
| 139 | 150 | parser = _get_base_parser() | |
| 140 | - return cli_module.extend_parser(parser) | ||
| 151 | + return gitlab.v4.cli.extend_parser(parser) | ||
| 141 | 152 | ||
| 142 | 153 | ||
| 143 | - def _parse_value(v): | ||
| 154 | + def _parse_value(v: Any) -> Any: | ||
| 144 | 155 | if isinstance(v, str) and v.startswith("@"): | |
| 145 | 156 | # If the user-provided value starts with @, we try to read the file | |
| 146 | 157 | # path provided after @ as the real value. Exit on any error. | |
@@ -162,18 +173,10 @@ def docs() -> argparse.ArgumentParser: | |||
| 162 | 173 | if "sphinx" not in sys.modules: | |
| 163 | 174 | sys.exit("Docs parser is only intended for build_sphinx") | |
| 164 | 175 | ||
| 165 | - # NOTE: We must delay import of gitlab.v4.cli until now or | ||
| 166 | - # otherwise it will cause circular import errors | ||
| 167 | - import gitlab.v4.cli | ||
| 168 | - | ||
| 169 | - return _get_parser(gitlab.v4.cli) | ||
| 170 | - | ||
| 176 | + return _get_parser() | ||
| 171 | 177 | ||
| 172 | - def main(): | ||
| 173 | - # NOTE: We must delay import of gitlab.v4.cli until now or | ||
| 174 | - # otherwise it will cause circular import errors | ||
| 175 | - import gitlab.v4.cli | ||
| 176 | 178 | ||
| 179 | + def main() -> None: | ||
| 177 | 180 | if "--version" in sys.argv: | |
| 178 | 181 | print(gitlab.__version__) | |
| 179 | 182 | sys.exit(0) | |
@@ -183,7 +186,7 @@ def main(): | |||
| 183 | 186 | # This first parsing step is used to find the gitlab config to use, and | |
| 184 | 187 | # load the propermodule (v3 or v4) accordingly. At that point we don't have | |
| 185 | 188 | # any subparser setup | |
| 186 | - (options, args) = parser.parse_known_args(sys.argv) | ||
| 189 | + (options, _) = parser.parse_known_args(sys.argv) | ||
| 187 | 190 | try: | |
| 188 | 191 | config = gitlab.config.GitlabConfigParser(options.gitlab, options.config_file) | |
| 189 | 192 | except gitlab.config.ConfigError as e: | |
@@ -196,14 +199,14 @@ def main(): | |||
| 196 | 199 | raise ModuleNotFoundError(name="gitlab.v%s.cli" % config.api_version) | |
| 197 | 200 | ||
| 198 | 201 | # Now we build the entire set of subcommands and do the complete parsing | |
| 199 | - parser = _get_parser(gitlab.v4.cli) | ||
| 202 | + parser = _get_parser() | ||
| 200 | 203 | try: | |
| 201 | 204 | import argcomplete # type: ignore | |
| 202 | 205 | ||
| 203 | 206 | argcomplete.autocomplete(parser) | |
| 204 | 207 | except Exception: | |
| 205 | 208 | pass | |
| 206 | - args = parser.parse_args(sys.argv[1:]) | ||
| 209 | + args = parser.parse_args() | ||
| 207 | 210 | ||
| 208 | 211 | config_files = args.config_file | |
| 209 | 212 | gitlab_id = args.gitlab | |
@@ -216,7 +219,7 @@ def main(): | |||
| 216 | 219 | action = args.whaction | |
| 217 | 220 | what = args.what | |
| 218 | 221 | ||
| 219 | - args = args.__dict__ | ||
| 222 | + args_dict = vars(args) | ||
| 220 | 223 | # Remove CLI behavior-related args | |
| 221 | 224 | for item in ( | |
| 222 | 225 | "gitlab", | |
@@ -228,8 +231,8 @@ def main(): | |||
| 228 | 231 | "version", | |
| 229 | 232 | "output", | |
| 230 | 233 | ): | |
| 231 | - args.pop(item) | ||
| 232 | - args = {k: _parse_value(v) for k, v in args.items() if v is not None} | ||
| 234 | + args_dict.pop(item) | ||
| 235 | + args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None} | ||
| 233 | 236 | ||
| 234 | 237 | try: | |
| 235 | 238 | gl = gitlab.Gitlab.from_config(gitlab_id, config_files) | |
@@ -241,6 +244,4 @@ def main(): | |||
| 241 | 244 | if debug: | |
| 242 | 245 | gl.enable_debug() | |
| 243 | 246 | ||
| 244 | - gitlab.v4.cli.run(gl, what, action, args, verbose, output, fields) | ||
| 245 | - | ||
| 246 | - sys.exit(0) | ||
| 247 | + gitlab.v4.cli.run(gl, what, action, args_dict, verbose, output, fields) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -206,7 +206,7 @@ def __init__( | |||
| 206 | 206 | except Exception: | |
| 207 | 207 | pass | |
| 208 | 208 | ||
| 209 | - def _get_values_from_helper(self): | ||
| 209 | + def _get_values_from_helper(self) -> None: | ||
| 210 | 210 | """Update attributes that may get values from an external helper program""" | |
| 211 | 211 | for attr in HELPER_ATTRIBUTES: | |
| 212 | 212 | value = getattr(self, attr) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,10 +16,16 @@ | |||
| 16 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 17 | 17 | ||
| 18 | 18 | import functools | |
| 19 | + from typing import Any, Callable, cast, Optional, Type, TypeVar, TYPE_CHECKING, Union | ||
| 19 | 20 | ||
| 20 | 21 | ||
| 21 | 22 | class GitlabError(Exception): | |
| 22 | - def __init__(self, error_message="", response_code=None, response_body=None): | ||
| 23 | + def __init__( | ||
| 24 | + self, | ||
| 25 | + error_message: Union[str, bytes] = "", | ||
| 26 | + response_code: Optional[int] = None, | ||
| 27 | + response_body: Optional[bytes] = None, | ||
| 28 | + ) -> None: | ||
| 23 | 29 | ||
| 24 | 30 | Exception.__init__(self, error_message) | |
| 25 | 31 | # Http status code | |
@@ -30,11 +36,15 @@ def __init__(self, error_message="", response_code=None, response_body=None): | |||
| 30 | 36 | try: | |
| 31 | 37 | # if we receive str/bytes we try to convert to unicode/str to have | |
| 32 | 38 | # consistent message types (see #616) | |
| 39 | + if TYPE_CHECKING: | ||
| 40 | + assert isinstance(error_message, bytes) | ||
| 33 | 41 | self.error_message = error_message.decode() | |
| 34 | 42 | except Exception: | |
| 43 | + if TYPE_CHECKING: | ||
| 44 | + assert isinstance(error_message, str) | ||
| 35 | 45 | self.error_message = error_message | |
| 36 | 46 | ||
| 37 | - def __str__(self): | ||
| 47 | + def __str__(self) -> str: | ||
| 38 | 48 | if self.response_code is not None: | |
| 39 | 49 | return "{0}: {1}".format(self.response_code, self.error_message) | |
| 40 | 50 | else: | |
@@ -269,7 +279,14 @@ class GitlabUnfollowError(GitlabOperationError): | |||
| 269 | 279 | pass | |
| 270 | 280 | ||
| 271 | 281 | ||
| 272 | - def on_http_error(error): | ||
| 282 | + # For an explanation of how these type-hints work see: | ||
| 283 | + # https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators | ||
| 284 | + # | ||
| 285 | + # The goal here is that functions which get decorated will retain their types. | ||
| 286 | + __F = TypeVar("__F", bound=Callable[..., Any]) | ||
| 287 | + | ||
| 288 | + | ||
| 289 | + def on_http_error(error: Type[Exception]) -> Callable[[__F], __F]: | ||
| 273 | 290 | """Manage GitlabHttpError exceptions. | |
| 274 | 291 | ||
| 275 | 292 | This decorator function can be used to catch GitlabHttpError exceptions | |
@@ -280,14 +297,14 @@ def on_http_error(error): | |||
| 280 | 297 | GitlabError | |
| 281 | 298 | """ | |
| 282 | 299 | ||
| 283 | - def wrap(f): | ||
| 300 | + def wrap(f: __F) -> __F: | ||
| 284 | 301 | @functools.wraps(f) | |
| 285 | - def wrapped_f(*args, **kwargs): | ||
| 302 | + def wrapped_f(*args: Any, **kwargs: Any) -> Any: | ||
| 286 | 303 | try: | |
| 287 | 304 | return f(*args, **kwargs) | |
| 288 | 305 | except GitlabHttpError as e: | |
| 289 | 306 | raise error(e.error_message, e.response_code, e.response_body) from e | |
| 290 | 307 | ||
| 291 | - return wrapped_f | ||
| 308 | + return cast(__F, wrapped_f) | ||
| 292 | 309 | ||
| 293 | 310 | return wrap | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,7 +26,6 @@ | |||
| 26 | 26 | import pytest | |
| 27 | 27 | ||
| 28 | 28 | from gitlab import cli | |
| 29 | - import gitlab.v4.cli | ||
| 30 | 29 | ||
| 31 | 30 | ||
| 32 | 31 | def test_what_to_cls(): | |
@@ -94,14 +93,14 @@ def test_base_parser(): | |||
| 94 | 93 | ||
| 95 | 94 | ||
| 96 | 95 | def test_v4_parse_args(): | |
| 97 | - parser = cli._get_parser(gitlab.v4.cli) | ||
| 96 | + parser = cli._get_parser() | ||
| 98 | 97 | args = parser.parse_args(["project", "list"]) | |
| 99 | 98 | assert args.what == "project" | |
| 100 | 99 | assert args.whaction == "list" | |
| 101 | 100 | ||
| 102 | 101 | ||
| 103 | 102 | def test_v4_parser(): | |
| 104 | - parser = cli._get_parser(gitlab.v4.cli) | ||
| 103 | + parser = cli._get_parser() | ||
| 105 | 104 | subparsers = next( | |
| 106 | 105 | action | |
| 107 | 106 | for action in parser._actions | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,46 +15,50 @@ | |||
| 15 | 15 | # You should have received a copy of the GNU Lesser General Public License | |
| 16 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 17 | 17 | ||
| 18 | + from typing import Any, Optional, TYPE_CHECKING | ||
| 19 | + | ||
| 18 | 20 | ||
| 19 | 21 | class GitlabAttribute(object): | |
| 20 | - def __init__(self, value=None): | ||
| 22 | + def __init__(self, value: Any = None) -> None: | ||
| 21 | 23 | self._value = value | |
| 22 | 24 | ||
| 23 | - def get(self): | ||
| 25 | + def get(self) -> Any: | ||
| 24 | 26 | return self._value | |
| 25 | 27 | ||
| 26 | - def set_from_cli(self, cli_value): | ||
| 28 | + def set_from_cli(self, cli_value: Any) -> None: | ||
| 27 | 29 | self._value = cli_value | |
| 28 | 30 | ||
| 29 | - def get_for_api(self): | ||
| 31 | + def get_for_api(self) -> Any: | ||
| 30 | 32 | return self._value | |
| 31 | 33 | ||
| 32 | 34 | ||
| 33 | 35 | class ListAttribute(GitlabAttribute): | |
| 34 | - def set_from_cli(self, cli_value): | ||
| 36 | + def set_from_cli(self, cli_value: str) -> None: | ||
| 35 | 37 | if not cli_value.strip(): | |
| 36 | 38 | self._value = [] | |
| 37 | 39 | else: | |
| 38 | 40 | self._value = [item.strip() for item in cli_value.split(",")] | |
| 39 | 41 | ||
| 40 | - def get_for_api(self): | ||
| 42 | + def get_for_api(self) -> str: | ||
| 41 | 43 | # Do not comma-split single value passed as string | |
| 42 | 44 | if isinstance(self._value, str): | |
| 43 | 45 | return self._value | |
| 44 | 46 | ||
| 47 | + if TYPE_CHECKING: | ||
| 48 | + assert isinstance(self._value, list) | ||
| 45 | 49 | return ",".join([str(x) for x in self._value]) | |
| 46 | 50 | ||
| 47 | 51 | ||
| 48 | 52 | class LowercaseStringAttribute(GitlabAttribute): | |
| 49 | - def get_for_api(self): | ||
| 53 | + def get_for_api(self) -> str: | ||
| 50 | 54 | return str(self._value).lower() | |
| 51 | 55 | ||
| 52 | 56 | ||
| 53 | 57 | class FileAttribute(GitlabAttribute): | |
| 54 | - def get_file_name(self, attr_name=None): | ||
| 58 | + def get_file_name(self, attr_name: Optional[str] = None) -> Optional[str]: | ||
| 55 | 59 | return attr_name | |
| 56 | 60 | ||
| 57 | 61 | ||
| 58 | 62 | class ImageAttribute(FileAttribute): | |
| 59 | - def get_file_name(self, attr_name=None): | ||
| 63 | + def get_file_name(self, attr_name: Optional[str] = None) -> str: | ||
| 60 | 64 | return "%s.png" % attr_name if attr_name else "image.png" | |
| Back | FazBrowse Home | New Git URL |
0 commit comments