From dc1650c2306d23574a930d4aa9ded733d54751e8 Mon Sep 17 00:00:00 2001 From: Peder Hovdan Andresen <107681714+pederhan@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:02:20 +0100 Subject: [PATCH] refactor: Fix boolean traps in signatures (#287) --- docs/scripts/utils/markup.py | 2 +- pyproject.toml | 1 + tests/pyzabbix/test_client.py | 7 ++++- tests/utils.py | 1 + zabbix_cli/_patches/typer.py | 2 +- zabbix_cli/auth.py | 15 ++++++---- zabbix_cli/commands/results/cli.py | 2 +- zabbix_cli/commands/results/proxy.py | 2 +- zabbix_cli/commands/template.py | 12 ++++---- zabbix_cli/commands/templategroup.py | 6 ++-- zabbix_cli/config/model.py | 6 ++-- zabbix_cli/config/utils.py | 7 +++-- zabbix_cli/output/console.py | 1 + zabbix_cli/output/formatting/bytes.py | 2 +- zabbix_cli/output/formatting/dates.py | 38 ------------------------- zabbix_cli/output/formatting/grammar.py | 2 +- zabbix_cli/output/formatting/path.py | 2 +- zabbix_cli/output/prompts.py | 10 +++++++ zabbix_cli/output/style.py | 2 +- zabbix_cli/pyzabbix/client.py | 31 ++++++++++++++++---- zabbix_cli/pyzabbix/enums.py | 9 ++++-- zabbix_cli/pyzabbix/types.py | 3 +- zabbix_cli/repl/completer.py | 3 +- zabbix_cli/repl/repl.py | 2 ++ zabbix_cli/table.py | 1 + zabbix_cli/update.py | 2 +- zabbix_cli/utils/args.py | 8 ++++-- zabbix_cli/utils/fs.py | 4 +-- zabbix_cli/utils/rich.py | 2 +- zabbix_cli/utils/utils.py | 7 +++-- 30 files changed, 108 insertions(+), 84 deletions(-) delete mode 100644 zabbix_cli/output/formatting/dates.py diff --git a/docs/scripts/utils/markup.py b/docs/scripts/utils/markup.py index 9c773de1..e165ba83 100644 --- a/docs/scripts/utils/markup.py +++ b/docs/scripts/utils/markup.py @@ -62,7 +62,7 @@ def symbol(self) -> str: return s @classmethod - def from_span(cls, span: MarkdownSpan, end: bool = False) -> MarkdownSymbol: + def from_span(cls, span: MarkdownSpan, *, end: bool = False) -> MarkdownSymbol: return cls( position=span.end if end else span.start, italic=span.italic, diff --git a/pyproject.toml b/pyproject.toml index 18d6bbe8..6ab30bdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -171,6 +171,7 @@ extend-select = [ "C4", # flake8-comprehensions "B", # flake8-bugbear "UP", # pyupgrade + "FBT002", # flake8-boolean-trap (Positional bool values with defaults) ] ignore = [ "E501", # Avoid enforcing line-length violations diff --git a/tests/pyzabbix/test_client.py b/tests/pyzabbix/test_client.py index be52ca34..6f18be0e 100644 --- a/tests/pyzabbix/test_client.py +++ b/tests/pyzabbix/test_client.py @@ -240,7 +240,12 @@ def test_client_logout(httpserver: HTTPServer, auth_type: AuthType, auth: str) - # We only expect a logout request if we are using a sessionid and have an auth token if auth_type == "sessionid" and auth: - add_zabbix_endpoint(httpserver, "user.logout", {}, True) + add_zabbix_endpoint( + httpserver, + "user.logout", + params={}, + response=True, # the value `True` as the response + ) zabbix_client = ZabbixAPI(server=httpserver.url_for("/api_jsonrpc.php")) zabbix_client.auth = auth if auth_type == "token": diff --git a/tests/utils.py b/tests/utils.py index 77e454b2..01d75288 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -13,6 +13,7 @@ def add_zabbix_endpoint( httpserver: HTTPServer, method: str, # method is zabbix API method, not HTTP method + *, params: dict[str, Any], response: Json, auth: Optional[str] = None, diff --git a/zabbix_cli/_patches/typer.py b/zabbix_cli/_patches/typer.py index 782cbebe..a0539610 100644 --- a/zabbix_cli/_patches/typer.py +++ b/zabbix_cli/_patches/typer.py @@ -294,7 +294,7 @@ def patch__get_rich_console() -> None: ) TYPER_THEME = Theme(styles) - def _get_rich_console(stderr: bool = False) -> Console: + def _get_rich_console(stderr: bool = False) -> Console: # noqa: FBT002 return Console( theme=TYPER_THEME, highlighter=highlighter, diff --git a/zabbix_cli/auth.py b/zabbix_cli/auth.py index 44c1b7e5..dbe100c7 100644 --- a/zabbix_cli/auth.py +++ b/zabbix_cli/auth.py @@ -126,7 +126,7 @@ def set_user_session(self, url: str, username: str, session_id: str) -> None: self.set_sessions(url, session) @classmethod - def load(cls, file: Path, allow_insecure: bool = False) -> SessionFile: + def load(cls, file: Path, *, allow_insecure: bool = False) -> SessionFile: """Load the contents of a session file.""" if not file.exists(): raise SessionFileNotFoundError("Session file does not exist: %s", file) @@ -143,7 +143,9 @@ def load(cls, file: Path, allow_insecure: bool = False) -> SessionFile: except Exception as e: raise SessionFileError(f"Unable to load session file {file}: {e}") from e - def save(self, path: Optional[Path] = None, allow_insecure: bool = False) -> None: + def save( + self, path: Optional[Path] = None, *, allow_insecure: bool = False + ) -> None: path = path or self._path if not path: raise SessionFileError("Cannot save session file without a path.") @@ -264,13 +266,13 @@ def login_with_any(self) -> tuple[ZabbixAPI, LoginInfo]: ) def _iter_all_credentials( - self, prompt_password: bool = True + self, *, prompt_password: bool = True ) -> Generator[Credentials, None, None]: - """Generator that yields credentials from all possible sources. + """Generator of credentials from all possible sources. Only yields non-empty credentials, but does not check if they are valid. - Finally yields a prompt for username and password if `prompt_password` is True. + Finally yields a prompt for username and password if `prompt_password=True`. """ for func in [ self._get_auth_token_env, @@ -455,7 +457,7 @@ def _update_config(self, credentials: Credentials) -> None: ): self.config.api.auth_token = SecretStr(credentials.auth_token) - def get_zabbix_url(self, prompt: bool = True) -> str: + def get_zabbix_url(self, *, prompt: bool = True) -> str: """Get the URL of the Zabbix server from env, config, then finally prompt for it..""" for source in [self._get_zabbix_url_env, self._get_zabbix_url_config]: url = source() @@ -697,6 +699,7 @@ def write_auth_token_file( username: str, auth_token: str, file: Path = AUTH_TOKEN_FILE, + *, allow_insecure: bool = False, ) -> Path: """Write a username/auth token pair to the auth token file.""" diff --git a/zabbix_cli/commands/results/cli.py b/zabbix_cli/commands/results/cli.py index 7620a7ea..fea265b3 100644 --- a/zabbix_cli/commands/results/cli.py +++ b/zabbix_cli/commands/results/cli.py @@ -61,7 +61,7 @@ def config_path_str(self) -> str: ) @classmethod - def from_debug_data(cls, state: State, with_auth: bool = False) -> DebugInfo: + def from_debug_data(cls, state: State, *, with_auth: bool = False) -> DebugInfo: # So far we only use state, but we can expand this in the future from zabbix_cli.exceptions import ZabbixCLIError diff --git a/zabbix_cli/commands/results/proxy.py b/zabbix_cli/commands/results/proxy.py index db435348..c7818bd3 100644 --- a/zabbix_cli/commands/results/proxy.py +++ b/zabbix_cli/commands/results/proxy.py @@ -136,7 +136,7 @@ class ShowProxiesResult(TableRenderable): show_hosts: bool = Field(default=False, exclude=True) @classmethod - def from_result(cls, proxy: Proxy, show_hosts: bool = False) -> Self: + def from_result(cls, proxy: Proxy, *, show_hosts: bool = False) -> Self: return cls(proxy=proxy, show_hosts=show_hosts) @property diff --git a/zabbix_cli/commands/template.py b/zabbix_cli/commands/template.py index 5245a7f8..83972a03 100644 --- a/zabbix_cli/commands/template.py +++ b/zabbix_cli/commands/template.py @@ -66,9 +66,9 @@ def link_template_to_host( from zabbix_cli.models import AggregateResult templates = parse_templates_arg( - app, template_names_or_ids, strict, select_hosts=True + app, template_names_or_ids, strict=strict, select_hosts=True ) - hosts = parse_hosts_arg(app, hostnames_or_ids, strict) + hosts = parse_hosts_arg(app, hostnames_or_ids, strict=strict) if not dryrun: with app.state.console.status("Linking templates..."): app.state.client.link_templates_to_hosts(templates, hosts) @@ -280,9 +280,9 @@ def unlink_template_from_host( from zabbix_cli.models import AggregateResult templates = parse_templates_arg( - app, template_names_or_ids, strict, select_hosts=True + app, template_names_or_ids, strict=strict, select_hosts=True ) - hosts = parse_hosts_arg(app, hostnames_or_ids, strict) + hosts = parse_hosts_arg(app, hostnames_or_ids, strict=strict) action = "Unlink and clear" if clear else "Unlink" if not dryrun: @@ -362,8 +362,8 @@ def unlink_template_from_template( """ from zabbix_cli.commands.results.template import LinkTemplateResult - source_templates = parse_templates_arg(app, source, strict) - dest_templates = parse_templates_arg(app, dest, strict) + source_templates = parse_templates_arg(app, source, strict=strict) + dest_templates = parse_templates_arg(app, dest, strict=strict) if not dryrun: with app.state.console.status("Unlinking templates..."): app.state.client.unlink_templates( diff --git a/zabbix_cli/commands/templategroup.py b/zabbix_cli/commands/templategroup.py index 3ba33383..5575b482 100644 --- a/zabbix_cli/commands/templategroup.py +++ b/zabbix_cli/commands/templategroup.py @@ -60,11 +60,11 @@ def add_template_to_group( groups: Union[list[HostGroup], list[TemplateGroup]] if app.state.client.version.release >= (6, 2, 0): - groups = parse_templategroups_arg(app, group_names_or_ids, strict) + groups = parse_templategroups_arg(app, group_names_or_ids, strict=strict) else: - groups = parse_hostgroups_arg(app, group_names_or_ids, strict) + groups = parse_hostgroups_arg(app, group_names_or_ids, strict=strict) - templates = parse_templates_arg(app, template_names_or_ids, strict) + templates = parse_templates_arg(app, template_names_or_ids, strict=strict) with app.state.console.status("Adding templates..."): app.state.client.link_templates_to_groups(templates, groups) diff --git a/zabbix_cli/config/model.py b/zabbix_cli/config/model.py index a783afdc..a662e849 100644 --- a/zabbix_cli/config/model.py +++ b/zabbix_cli/config/model.py @@ -563,7 +563,7 @@ def set(self, key: str, value: Any) -> None: class PluginsConfig(RootModel[dict[str, PluginConfig]]): root: dict[str, PluginConfig] = Field(default_factory=dict) - def get(self, key: str, strict: bool = False) -> Optional[PluginConfig]: + def get(self, key: str, *, strict: bool = False) -> Optional[PluginConfig]: """Get a plugin configuration by name.""" conf = self.root.get(key) if conf is None and strict: @@ -614,7 +614,9 @@ def sample_config(cls) -> Config: return cls(api=APIConfig(url="https://zabbix.example.com"), sample=True) @classmethod - def from_file(cls, filename: Optional[Path] = None, init: bool = False) -> Config: + def from_file( + cls, filename: Optional[Path] = None, *, init: bool = False + ) -> Config: """Load configuration from a file. Attempts to find a config file to load if none is specified. diff --git a/zabbix_cli/config/utils.py b/zabbix_cli/config/utils.py index e78ead57..adf8068c 100644 --- a/zabbix_cli/config/utils.py +++ b/zabbix_cli/config/utils.py @@ -67,7 +67,7 @@ def find_config( return None -def get_config(filename: Optional[Path] = None, init: bool = False) -> Config: +def get_config(filename: Optional[Path] = None, *, init: bool = False) -> Config: """Get a configuration object. Args: @@ -187,6 +187,7 @@ class DeprecatedField(NamedTuple): def init_config( config: Optional[Config] = None, config_file: Optional[Path] = None, + *, overwrite: bool = False, # Compatibility with V2 zabbix-cli-init args url: Optional[str] = None, @@ -215,7 +216,9 @@ def init_config( if not config: config = Config.sample_config() if not url: - url = str_prompt("Zabbix URL (without /api_jsonrpc.php)", url or config.api.url) + url = str_prompt( + "Zabbix URL (without /api_jsonrpc.php)", default=url or config.api.url + ) config.api.url = url # Add username if provided diff --git a/zabbix_cli/output/console.py b/zabbix_cli/output/console.py index e8781adc..5ffc2d98 100644 --- a/zabbix_cli/output/console.py +++ b/zabbix_cli/output/console.py @@ -101,6 +101,7 @@ def warning(message: str, icon: str = Icon.WARNING, **kwargs: Any) -> None: def error( message: str, icon: str = Icon.ERROR, + *, exc_info: bool = False, log: bool = True, **kwargs: Any, diff --git a/zabbix_cli/output/formatting/bytes.py b/zabbix_cli/output/formatting/bytes.py index 14619534..9e1fe68b 100644 --- a/zabbix_cli/output/formatting/bytes.py +++ b/zabbix_cli/output/formatting/bytes.py @@ -5,7 +5,7 @@ from .constants import NONE_STR -def bytesize_str(b: int | None, decimal: bool = False) -> str: +def bytesize_str(b: int | None, *, decimal: bool = False) -> str: if b is None or b < 0: return NONE_STR return ByteSize(b).human_readable(decimal=decimal) diff --git a/zabbix_cli/output/formatting/dates.py b/zabbix_cli/output/formatting/dates.py deleted file mode 100644 index 5b0f5bd5..00000000 --- a/zabbix_cli/output/formatting/dates.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from datetime import datetime - -from zabbix_cli.logs import logger -from zabbix_cli.output.formatting.constants import NONE_STR - - -def datetime_str( - d: datetime | int | float | None, with_time: bool = True, subsecond: bool = False -) -> str: - """Formats an optional datetime object as as a string. - - Parameters - ---------- - d : datetime | None - The datetime object to format. - with_time : bool, optional - Whether to include the time in the formatted string, by default True - subsecond : bool, optional - Whether to include subsecond precision in the formatted string, by default False - Has no effect if `with_time` is False. - """ - if d is None: - return NONE_STR - if isinstance(d, (int, float)): - try: - d = datetime.fromtimestamp(d) - except (ValueError, OSError) as e: # OSError if timestamp is out of range - if isinstance(e, OSError): - logger.error("Timestamp out of range: %s", d) - return NONE_STR - fmt = "%Y-%m-%d" - if with_time: - fmt = f"{fmt} %H:%M:%S" - if subsecond: - fmt = f"{fmt}.%f" - return d.strftime(fmt) diff --git a/zabbix_cli/output/formatting/grammar.py b/zabbix_cli/output/formatting/grammar.py index 82ca053c..6c04ff98 100644 --- a/zabbix_cli/output/formatting/grammar.py +++ b/zabbix_cli/output/formatting/grammar.py @@ -9,7 +9,7 @@ def _pluralize_word(word: str, count: int) -> str: return word + "s" -def pluralize(word: str, count: int, with_count: bool = True) -> str: +def pluralize(word: str, count: int, *, with_count: bool = True) -> str: """Pluralize a word based on a count. Examples: diff --git a/zabbix_cli/output/formatting/path.py b/zabbix_cli/output/formatting/path.py index dfd1e3e5..e60e3a31 100644 --- a/zabbix_cli/output/formatting/path.py +++ b/zabbix_cli/output/formatting/path.py @@ -5,7 +5,7 @@ from pathlib import Path -def path_link(path: Path, absolute: bool = True) -> str: +def path_link(path: Path, *, absolute: bool = True) -> str: """Return a link to a path.""" abspath = path.resolve().absolute() if absolute: diff --git a/zabbix_cli/output/prompts.py b/zabbix_cli/output/prompts.py index 043b9e04..766c76b4 100644 --- a/zabbix_cli/output/prompts.py +++ b/zabbix_cli/output/prompts.py @@ -87,6 +87,7 @@ def prompt_msg(*msgs: str) -> str: @no_headless def str_prompt( prompt: str, + *, default: str = ..., # pyright: ignore[reportArgumentType] # rich uses ... to signify no default password: bool = False, show_default: bool = True, @@ -158,6 +159,7 @@ def str_prompt( @no_headless def str_prompt_optional( prompt: str, + *, default: str = "", password: bool = False, show_default: bool = False, @@ -184,6 +186,7 @@ def str_prompt_optional( @no_headless def list_prompt( prompt: str, + *, empty_ok: bool = True, strip: bool = True, keep_empty: bool = False, @@ -208,6 +211,7 @@ def list_prompt( @no_headless def int_prompt( prompt: str, + *, default: int | None = None, show_default: bool = True, min: int | None = None, @@ -230,6 +234,7 @@ def int_prompt( @no_headless def float_prompt( prompt: str, + *, default: float | None = None, show_default: bool = True, min: float | None = None, @@ -256,6 +261,7 @@ def float_prompt( def _number_prompt( prompt_type: type[IntPrompt], prompt: str, + *, default: int | float | None = ..., show_default: bool = ..., min: int | float | None = ..., @@ -269,6 +275,7 @@ def _number_prompt( def _number_prompt( prompt_type: type[FloatPrompt], prompt: str, + *, default: int | float | None = ..., show_default: bool = ..., min: int | float | None = ..., @@ -281,6 +288,7 @@ def _number_prompt( def _number_prompt( prompt_type: type[IntPrompt] | type[FloatPrompt], prompt: str, + *, default: int | float | None = None, show_default: bool = True, min: int | float | None = None, @@ -338,6 +346,7 @@ def _number_prompt( @no_headless def bool_prompt( prompt: str, + *, default: bool = ..., # pyright: ignore[reportArgumentType] # rich uses ... to signify no default show_default: bool = True, warning: bool = False, @@ -356,6 +365,7 @@ def bool_prompt( def path_prompt( prompt: str, default: str | Path = ..., # pyright: ignore[reportArgumentType] # rich uses ... to signify no default + *, show_default: bool = True, exist_ok: bool = True, must_exist: bool = False, diff --git a/zabbix_cli/output/style.py b/zabbix_cli/output/style.py index 3ce310e7..d5afe9c5 100644 --- a/zabbix_cli/output/style.py +++ b/zabbix_cli/output/style.py @@ -183,7 +183,7 @@ class Emoji(StrEnum): NO = EMOJI_NO @classmethod - def fmt_bool(cls, value: bool) -> str: + def fmt_bool(cls, value: bool) -> str: # noqa: FBT001 return success(cls.YES) if value else error(cls.NO) diff --git a/zabbix_cli/pyzabbix/client.py b/zabbix_cli/pyzabbix/client.py index 38bae313..cdf29516 100644 --- a/zabbix_cli/pyzabbix/client.py +++ b/zabbix_cli/pyzabbix/client.py @@ -156,6 +156,7 @@ def add_param( def parse_name_or_id_arg( params: ParamsType, names_or_ids: tuple[str, ...], + *, name_param: str, id_param: str, search: bool = True, @@ -239,6 +240,7 @@ class ZabbixAPI: def __init__( self, server: str = "http://localhost/zabbix", + *, timeout: Optional[int] = None, verify_ssl: bool = True, ) -> None: @@ -278,7 +280,7 @@ def from_config(cls, config: Config) -> ZabbixAPI: return client def _get_client( - self, verify_ssl: bool, timeout: Union[float, int, None] = None + self, *, verify_ssl: bool, timeout: Union[float, int, None] = None ) -> httpx.Client: kwargs: HTTPXClientKwargs = {} if timeout is not None: @@ -544,6 +546,7 @@ def _check_response_errors( def get_hostgroup( self, name_or_id: str, + *, search: bool = False, select_hosts: bool = False, select_templates: bool = False, @@ -685,6 +688,7 @@ def remove_hosts_from_hostgroups( def get_templategroup( self, name_or_id: str, + *, search: bool = False, select_templates: bool = False, ) -> TemplateGroup: @@ -789,6 +793,7 @@ def delete_templategroup(self, templategroup_id: str) -> None: def get_host( self, name_or_id: str, + *, select_groups: bool = False, select_templates: bool = False, select_interfaces: bool = False, @@ -1084,6 +1089,7 @@ def get_hostinterfaces( def create_host_interface( self, + *, host: Host, main: bool, type: InterfaceType, @@ -1174,6 +1180,7 @@ def delete_host_interface(self, interface_id: str) -> None: def get_usergroup( self, name_or_id: str, + *, select_users: bool = False, select_rights: bool = False, search: bool = True, @@ -1231,6 +1238,7 @@ def get_usergroups( def create_usergroup( self, usergroup_name: str, + *, disabled: bool = False, gui_access: GUIAccess = GUIAccess.DEFAULT, ) -> str: @@ -1260,7 +1268,7 @@ def remove_usergroup_users(self, usergroup_name: str, users: list[User]) -> None self._update_usergroup_users(usergroup_name, users, remove=True) def _update_usergroup_users( - self, usergroup_name: str, users: list[User], remove: bool = False + self, usergroup_name: str, users: list[User], *, remove: bool = False ) -> None: """Add/remove users from user group. @@ -1290,6 +1298,7 @@ def update_usergroup_rights( usergroup_name: str, groups: list[str], permission: UsergroupPermission, + *, hostgroup: bool, ) -> None: """Update usergroup rights for host or template groups.""" @@ -1346,7 +1355,7 @@ def _get_updated_rights( return rights def get_proxy( - self, name_or_id: str, select_hosts: bool = False, search: bool = True + self, name_or_id: str, *, select_hosts: bool = False, search: bool = True ) -> Proxy: """Fetches a single proxy matching the given name.""" proxies = self.get_proxies(name_or_id, select_hosts=select_hosts, search=search) @@ -1388,6 +1397,7 @@ def get_proxies( def get_proxy_group( self, name_or_id: str, + *, proxies: Optional[list[Proxy]] = None, select_proxies: bool = False, ) -> ProxyGroup: @@ -1490,6 +1500,7 @@ def add_hosts_to_proxygroup( def get_macro( self, + *, host: Optional[Host] = None, macro_name: Optional[str] = None, search: bool = False, @@ -1521,6 +1532,7 @@ def get_hosts_with_macro(self, macro: str) -> list[Host]: def get_macros( self, + *, macro_name: Optional[str] = None, host: Optional[Host] = None, search: bool = False, @@ -1560,6 +1572,7 @@ def get_macros( def get_global_macro( self, + *, macro_name: Optional[str] = None, search: bool = False, sort_field: Optional[str] = "macro", @@ -1578,6 +1591,7 @@ def get_global_macro( def get_global_macros( self, + *, macro_name: Optional[str] = None, search: bool = False, sort_field: Optional[str] = "macro", @@ -1723,6 +1737,7 @@ def move_hosts_to_proxy(self, hosts: list[Host], proxy: Proxy) -> None: def get_template( self, template_name_or_id: str, + *, select_hosts: bool = False, select_templates: bool = False, select_parent_templates: bool = False, @@ -1805,7 +1820,7 @@ def link_templates_to_hosts( raise ZabbixAPICallError("Failed to link templates") from e def unlink_templates_from_hosts( - self, templates: list[Template], hosts: list[Host], clear: bool = True + self, templates: list[Template], hosts: list[Host], *, clear: bool = True ) -> None: """Unlinks and clears one or more templates from one or more hosts. @@ -1861,7 +1876,7 @@ def link_templates( raise ZabbixAPICallError("Failed to link templates") from e def unlink_templates( - self, source: list[Template], destination: list[Template], clear: bool = True + self, source: list[Template], destination: list[Template], *, clear: bool = True ) -> None: """Unlinks template(s) from template(s) and optionally clears them. @@ -2201,6 +2216,7 @@ def get_maintenances( def create_maintenance( self, + *, name: str, active_since: datetime, active_till: datetime, @@ -2304,6 +2320,7 @@ def get_event( # NOTE: Does this API make sense? # Should we just expose event_id instead, and then # use `get_events()` for everything else? + *, event_id: Optional[str] = None, group_id: Optional[str] = None, host_id: Optional[str] = None, @@ -2338,6 +2355,7 @@ def get_event( def get_events( self, + *, event_ids: Union[str, list[str], None] = None, # Why are we taking in strings here instead of objects? # Should we instead take in objects and then extract the IDs? @@ -2367,6 +2385,7 @@ def get_events( def get_triggers( self, + *, trigger_ids: Union[str, list[str], None] = None, hostgroups: Optional[list[HostGroup]] = None, templates: Optional[list[Template]] = None, @@ -2469,6 +2488,7 @@ def get_media_types(self, *names: str) -> list[MediaType]: def export_configuration( self, + *, host_groups: Optional[list[HostGroup]] = None, template_groups: Optional[list[TemplateGroup]] = None, hosts: Optional[list[Host]] = None, @@ -2519,6 +2539,7 @@ def export_configuration( def import_configuration( self, to_import: Path, + *, create_missing: bool = True, update_existing: bool = True, delete_missing: bool = False, diff --git a/zabbix_cli/pyzabbix/enums.py b/zabbix_cli/pyzabbix/enums.py index e468b229..3207c3b6 100644 --- a/zabbix_cli/pyzabbix/enums.py +++ b/zabbix_cli/pyzabbix/enums.py @@ -31,6 +31,7 @@ def __new__( s: str, api_value: T = None, metadata: Optional[Mapping[str, Any]] = None, + *, hidden: bool = False, ) -> APIStr[T]: if isinstance(s, APIStr): @@ -189,12 +190,16 @@ class APIStrEnum(Choice): # to string, thereby losing the API associated value. # If we are to do that, we need to hijack the object creation and inject # the member value somehow? - def as_status(self, default: str = "Unknown", with_code: bool = False) -> str: + def as_status(self, default: str = "Unknown", *, with_code: bool = False) -> str: return self.string_from_value(self.value, default=default, with_code=with_code) @classmethod def string_from_value( - cls: type[Self], value: Any, default: str = "Unknown", with_code: bool = False + cls: type[Self], + value: Any, + default: str = "Unknown", + *, + with_code: bool = False, ) -> str: """Get a formatted status string given a value.""" try: diff --git a/zabbix_cli/pyzabbix/types.py b/zabbix_cli/pyzabbix/types.py index aa5d5345..9255143a 100644 --- a/zabbix_cli/pyzabbix/types.py +++ b/zabbix_cli/pyzabbix/types.py @@ -475,7 +475,7 @@ def set_proxy(self, proxy_map: dict[str, Proxy]) -> None: return self.proxy = proxy - def get_active_status(self, with_code: bool = False) -> str: + def get_active_status(self, *, with_code: bool = False) -> str: """Returns the active interface status as a formatted string.""" if self.zabbix_version.release >= (7, 0, 0): return ActiveInterface.string_from_value( @@ -1245,6 +1245,7 @@ class ImportRules(ZabbixAPIBaseModel): @classmethod def get( cls, + *, create_missing: bool = False, update_existing: bool = False, delete_missing: bool = False, diff --git a/zabbix_cli/repl/completer.py b/zabbix_cli/repl/completer.py index 3bedbd19..2352a260 100644 --- a/zabbix_cli/repl/completer.py +++ b/zabbix_cli/repl/completer.py @@ -25,7 +25,7 @@ AUTO_COMPLETION_PARAM = "shell_complete" -def split_arg_string(string: str, posix: bool = True) -> list[str]: +def split_arg_string(string: str, *, posix: bool = True) -> list[str]: r"""Split an argument in a shlex-like way, but don't fail if the string is incomplete. Args: @@ -119,6 +119,7 @@ def __init__( self, cli: click.Group, ctx: click.Context, + *, show_only_unused: bool = False, shortest_only: bool = False, ) -> None: diff --git a/zabbix_cli/repl/repl.py b/zabbix_cli/repl/repl.py index bb94ec22..b8f29984 100644 --- a/zabbix_cli/repl/repl.py +++ b/zabbix_cli/repl/repl.py @@ -107,6 +107,7 @@ def bootstrap_prompt( prompt_kwargs: Optional[dict[str, Any]], group: click.Group, ctx: click.Context, + *, show_only_unused: bool = False, shortest_only: bool = False, ) -> dict[str, Any]: @@ -171,6 +172,7 @@ def repl( # noqa: C901 old_ctx: Context, app: StatefulApp, prompt_kwargs: Optional[dict[str, Any]] = None, + *, allow_system_commands: bool = True, allow_internal_commands: bool = True, ) -> None: diff --git a/zabbix_cli/table.py b/zabbix_cli/table.py index cb9d1745..e2c2c1ed 100644 --- a/zabbix_cli/table.py +++ b/zabbix_cli/table.py @@ -14,6 +14,7 @@ def get_table( cols: ColsType, rows: RowsType, title: str | None = None, + *, show_lines: bool = True, box: box.Box = box.ROUNDED, ) -> Table: diff --git a/zabbix_cli/update.py b/zabbix_cli/update.py index 5cfc577a..84524cfa 100644 --- a/zabbix_cli/update.py +++ b/zabbix_cli/update.py @@ -497,7 +497,7 @@ def to_path(p: str) -> Optional[Path]: return None -def cmd_exists(command: str, help: bool = True) -> bool: +def cmd_exists(command: str, *, help: bool = True) -> bool: """Check if a command is available in the system.""" cmd = [command, "--help"] if help else [command] try: diff --git a/zabbix_cli/utils/args.py b/zabbix_cli/utils/args.py index a52a30c7..fb32001d 100644 --- a/zabbix_cli/utils/args.py +++ b/zabbix_cli/utils/args.py @@ -53,7 +53,7 @@ def parse_int_arg(arg: str) -> int: raise ZabbixCLIError(f"Invalid integer value: {arg}") from e -def parse_list_arg(arg: Optional[str], keep_empty: bool = False) -> list[str]: +def parse_list_arg(arg: Optional[str], *, keep_empty: bool = False) -> list[str]: """Convert comma-separated string to list.""" try: args = arg.strip().split(",") if arg else [] @@ -79,6 +79,7 @@ def parse_int_list_arg(arg: str) -> list[int]: def parse_hostgroups_arg( app: StatefulApp, hgroup_names_or_ids: Optional[str], + *, strict: bool = False, select_hosts: bool = False, select_templates: bool = False, @@ -110,6 +111,7 @@ def parse_hostgroups_arg( def parse_hosts_arg( app: StatefulApp, hostnames_or_ids: Optional[str], + *, strict: bool = False, ) -> list[Host]: """Parse host names or IDs and return a list of hosts.""" @@ -134,6 +136,7 @@ def parse_hosts_arg( def parse_templates_arg( app: StatefulApp, template_names_or_ids: Optional[str], + *, strict: bool = False, select_hosts: bool = False, ) -> list[Template]: @@ -158,6 +161,7 @@ def parse_templates_arg( def parse_templategroups_arg( app: StatefulApp, tgroup_names_or_ids: str, + *, strict: bool = False, select_templates: bool = False, ) -> list[TemplateGroup]: @@ -193,7 +197,7 @@ def parse_bool_arg(arg: str) -> bool: raise ZabbixCLIError(f"Invalid boolean value: {arg}") -def parse_path_arg(arg: str, must_exist: bool = False) -> Path: +def parse_path_arg(arg: str, *, must_exist: bool = False) -> Path: """Convert string to Path.""" try: p = Path(arg) diff --git a/zabbix_cli/utils/fs.py b/zabbix_cli/utils/fs.py index bea574a7..264cf366 100644 --- a/zabbix_cli/utils/fs.py +++ b/zabbix_cli/utils/fs.py @@ -34,7 +34,7 @@ def read_file(file: Path) -> str: def open_directory( - directory: Path, command: Optional[str] = None, force: bool = False + directory: Path, command: Optional[str] = None, *, force: bool = False ) -> None: """Open directory in file explorer. @@ -113,7 +113,7 @@ def make_executable(path: Path) -> None: logger.debug("File %s is already executable", path) -def move_file(src: Path, dest: Path, mkdir: bool = True) -> None: +def move_file(src: Path, dest: Path, *, mkdir: bool = True) -> None: """Move a file to a new location.""" try: if mkdir: diff --git a/zabbix_cli/utils/rich.py b/zabbix_cli/utils/rich.py index 69f90aa6..62791564 100644 --- a/zabbix_cli/utils/rich.py +++ b/zabbix_cli/utils/rich.py @@ -21,7 +21,7 @@ def get_safe_renderable(renderable: RenderableType) -> RenderableType: return renderable -def get_text(text: str, log: bool = True) -> Text: +def get_text(text: str, *, log: bool = True) -> Text: """Interpret text as markup-styled text, or plain text if it fails.""" try: return Text.from_markup(text) diff --git a/zabbix_cli/utils/utils.py b/zabbix_cli/utils/utils.py index 2326e2d3..666c587e 100644 --- a/zabbix_cli/utils/utils.py +++ b/zabbix_cli/utils/utils.py @@ -22,7 +22,7 @@ # NOTE: consider setting with_code to False by default... # The only downside is possibly breaking backwards compatibility def _format_code( - code: Union[str, int, None], status_map: dict[Any, str], with_code: bool = True + code: Union[str, int, None], status_map: dict[Any, str], *, with_code: bool = True ) -> str: status = status_map.get(code, "Unknown") if with_code and code is not None: @@ -35,14 +35,14 @@ def _format_code( # than the ones used in Zabbix-cli v2. This function is used to map the # the API values to the old string values when serializing to JSON. # Should be removed when we drop support for legacy JSON output. -def get_maintenance_status(code: Optional[str], with_code: bool = False) -> str: +def get_maintenance_status(code: Optional[str], *, with_code: bool = False) -> str: """Get maintenance status from code.""" maintenance_status = {"0": "No maintenance", "1": "In progress"} return _format_code(code, maintenance_status, with_code=with_code) # LEGACY: Kept for backwards compatibility in JSON output -def get_monitoring_status(code: Optional[str], with_code: bool = False) -> str: +def get_monitoring_status(code: Optional[str], *, with_code: bool = False) -> str: """Get monitoring status from code.""" monitoring_status = {"0": "Monitored", "1": "Not monitored"} return _format_code(code, monitoring_status, with_code=with_code) @@ -131,6 +131,7 @@ def get_maintenance_active_months(schedule: int | None) -> list[str]: def get_acknowledge_action_value( + *, close: bool = False, acknowledge: bool = False, message: bool = False,