diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d2580b6..eb301f0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: rev: v3.19.0 hooks: - id: pyupgrade - args: [--py37-plus, --keep-runtime-typing] + args: [--py39-plus, --keep-runtime-typing] - repo: https://github.com/charliermarsh/ruff-pre-commit rev: "v0.7.0" hooks: diff --git a/docs/scripts/gen_command_list.py b/docs/scripts/gen_command_list.py index d2476cc1..8e669bb1 100644 --- a/docs/scripts/gen_command_list.py +++ b/docs/scripts/gen_command_list.py @@ -12,8 +12,6 @@ import sys from pathlib import Path from typing import Any -from typing import Dict -from typing import List import yaml # type: ignore from zabbix_cli.app import app @@ -27,7 +25,7 @@ def main() -> None: commands = get_app_commands(app) command_names = [c.name for c in commands] - categories: Dict[str, List[Dict[str, Any]]] = {} + categories: dict[str, list[dict[str, Any]]] = {} for command in commands: category = command.category or "" if category not in categories: diff --git a/docs/scripts/gen_commands.py b/docs/scripts/gen_commands.py index 70102e96..645a9514 100644 --- a/docs/scripts/gen_commands.py +++ b/docs/scripts/gen_commands.py @@ -5,8 +5,6 @@ import sys from pathlib import Path from typing import Any -from typing import Dict -from typing import List import jinja2 import yaml # type: ignore @@ -34,7 +32,7 @@ def gen_category_command_map(commands: list[CommandSummary]) -> None: """Generates a YAML file with all categories and detailed information about their respective commands. """ - categories: Dict[str, List[Dict[str, Any]]] = {} + categories: dict[str, list[dict[str, Any]]] = {} for command in commands: category = command.category or "" if category not in categories: @@ -51,7 +49,7 @@ def gen_category_pages(commands: list[CommandSummary]) -> None: """Renders markdown pages for each category with detailed information about each command. """ - categories: Dict[str, List[CommandSummary]] = {} + categories: dict[str, list[CommandSummary]] = {} for command in commands: if command.hidden: continue diff --git a/docs/scripts/utils/commands.py b/docs/scripts/utils/commands.py index 0d74107b..99b83b48 100644 --- a/docs/scripts/utils/commands.py +++ b/docs/scripts/utils/commands.py @@ -2,8 +2,6 @@ from functools import lru_cache from typing import Any -from typing import Dict -from typing import List from typing import Optional from typing import Union from typing import cast @@ -35,7 +33,7 @@ class ParamSummary(BaseModel): allow_from_autoenv: Optional[bool] = None confirmation_prompt: Optional[bool] = None - choices: Optional[List[str]] = None + choices: Optional[list[str]] = None count: Optional[bool] = None default: Optional[Any] = None envvar: Optional[str] @@ -54,11 +52,11 @@ class ParamSummary(BaseModel): multiple: bool name: Optional[str] nargs: int - opts: List[str] + opts: list[str] prompt: Optional[str] = None prompt_required: Optional[bool] = None required: bool - secondary_opts: List[str] = [] + secondary_opts: list[str] = [] show_choices: Optional[bool] = None show_default: Optional[bool] = None show_envvar: Optional[bool] = None @@ -152,7 +150,7 @@ class CommandSummary(BaseModel): hidden: bool name: str options_metavar: str - params: List[ParamSummary] = Field([], exclude=True) + params: list[ParamSummary] = Field([], exclude=True) score: int = 0 # match score (not part of TyperCommand) short_help: Optional[str] @@ -162,7 +160,7 @@ def _replace_placeholders(cls, values: Any) -> Any: """Replace DefaultPlaceholder values with empty strings.""" if not isinstance(values, dict): return values - values = cast(Dict[str, Any], values) + values = cast(dict[str, Any], values) for key, value in values.items(): if isinstance(value, DefaultPlaceholder): # Use its value, otherwise empty string @@ -233,12 +231,12 @@ def usage(self) -> str: @computed_field @property - def options(self) -> List[ParamSummary]: + def options(self) -> list[ParamSummary]: return [p for p in self.params if _include_opt(p)] @computed_field @property - def arguments(self) -> List[ParamSummary]: + def arguments(self) -> list[ParamSummary]: return [p for p in self.params if _include_arg(p)] @@ -322,7 +320,7 @@ def _get_app_commands( def get_app_callback_options(app: typer.Typer) -> list[typer.models.OptionInfo]: """Get the options of the main callback of a Typer app.""" - options: List[typer.models.OptionInfo] = [] + options: list[typer.models.OptionInfo] = [] if not app.registered_callback: return options diff --git a/docs/scripts/utils/markup.py b/docs/scripts/utils/markup.py index c781e084..9c773de1 100644 --- a/docs/scripts/utils/markup.py +++ b/docs/scripts/utils/markup.py @@ -3,7 +3,6 @@ import itertools from dataclasses import dataclass from functools import cmp_to_key -from typing import List from rich.text import Text from zabbix_cli.output.style import CodeBlockStyle @@ -44,7 +43,7 @@ class MarkdownSymbol: @property def symbol(self) -> str: - symbol: List[str] = [] + symbol: list[str] = [] if self.codeblock: # Only insert language when opening codeblock lang = self.language if not self.end else "" @@ -102,7 +101,7 @@ def markup_to_markdown(s: str) -> str: good enough for our purposes. """ t = Text.from_markup(normalize_spaces(s)) - spans: List[MarkdownSpan] = [] + spans: list[MarkdownSpan] = [] # Markdown has more limited styles than Rich markup, so we just # identify the ones we care about and ignore the rest. for span in t.spans: @@ -141,7 +140,7 @@ def markup_to_markdown(s: str) -> str: def normalize_spaces(s: str) -> str: """Normalizes spaces in a string while keeping newlines intact.""" split = filter(None, s.split(" ")) - parts: List[str] = [] + parts: list[str] = [] for part in split: if part.endswith("\n"): parts.append(part) diff --git a/tests/conftest.py b/tests/conftest.py index 6861c5e1..7e281dc0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,9 @@ from __future__ import annotations +from collections.abc import Generator +from collections.abc import Iterator from pathlib import Path from typing import Any -from typing import Generator -from typing import Iterator import pytest import typer diff --git a/tests/pyzabbix/test_client.py b/tests/pyzabbix/test_client.py index 08875811..f1806f59 100644 --- a/tests/pyzabbix/test_client.py +++ b/tests/pyzabbix/test_client.py @@ -1,7 +1,6 @@ from __future__ import annotations from typing import Any -from typing import Dict import pytest from inline_snapshot import snapshot @@ -53,7 +52,7 @@ ), ], ) -def test_append_param(inp: Any, key: str, value: Any, expect: Dict[str, Any]) -> None: +def test_append_param(inp: Any, key: str, value: Any, expect: dict[str, Any]) -> None: result = append_param(inp, key, value) assert result == expect # Check in-place modification @@ -71,7 +70,7 @@ def test_append_param(inp: Any, key: str, value: Any, expect: Dict[str, Any]) -> ), ], ) -def test_add_param(inp: Any, subkey: str, value: Any, expect: Dict[str, Any]) -> None: +def test_add_param(inp: Any, subkey: str, value: Any, expect: dict[str, Any]) -> None: result = add_param(inp, "search", subkey, value) assert result == expect # Check in-place modification diff --git a/tests/pyzabbix/test_enums.py b/tests/pyzabbix/test_enums.py index 16c0eac4..7f9a0794 100644 --- a/tests/pyzabbix/test_enums.py +++ b/tests/pyzabbix/test_enums.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Type - import pytest from zabbix_cli.pyzabbix.enums import AckStatus from zabbix_cli.pyzabbix.enums import ActiveInterface @@ -68,7 +66,7 @@ @pytest.mark.parametrize("enum", APISTR_ENUMS) -def test_apistrenum(enum: Type[APIStrEnum]) -> None: +def test_apistrenum(enum: type[APIStrEnum]) -> None: assert enum.__members__ members = list(enum) assert members diff --git a/tests/test_config.py b/tests/test_config.py index ec6fd5e2..f8c3ebbf 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,12 +1,8 @@ from __future__ import annotations -import sys from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import Optional -from typing import Type from typing import Union import pytest @@ -194,15 +190,13 @@ def test_config_get_with_annotations() -> None: # List type assert config.get("extra4", type=list) == [1, 2, 3] - assert config.get("extra4", type=List[int]) == [1, 2, 3] - if sys.version_info >= (3, 9): - assert config.get("extra4", type=list[int]) == [1, 2, 3] + assert config.get("extra4", type=list[int]) == [1, 2, 3] + assert config.get("extra4", type=list[int]) == [1, 2, 3] # Dict type assert config.get("extra5", type=dict) == {"foo": [1, 2, 3]} - assert config.get("extra5", type=Dict[str, List[int]]) == {"foo": [1, 2, 3]} - if sys.version_info >= (3, 9): - assert config.get("extra5", type=dict[str, list[int]]) == {"foo": [1, 2, 3]} + assert config.get("extra5", type=dict[str, list[int]]) == {"foo": [1, 2, 3]} + assert config.get("extra5", type=dict[str, list[int]]) == {"foo": [1, 2, 3]} def test_plugin_config_set() -> None: @@ -443,9 +437,9 @@ def test_deprecated_fields_updated() -> None: assert conf.api.username == "System-User" -def get_deprecated_fields(model: Union[Type[BaseModel], BaseModel]) -> List[str]: +def get_deprecated_fields(model: Union[type[BaseModel], BaseModel]) -> list[str]: """Get a set of names of deprecated fields in a model and its submodels.""" - fields: List[str] = [] + fields: list[str] = [] for field_name, field in model.model_fields.items(): if field.deprecated: fields.append(field_name) diff --git a/tests/test_console.py b/tests/test_console.py index efbecfba..a2649433 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -2,7 +2,6 @@ import logging from typing import Any -from typing import Dict import pytest from inline_snapshot import snapshot @@ -39,14 +38,14 @@ ), ], ) -def test_get_extra_dict(inp: Dict[str, Any], expect: Dict[str, Any]) -> None: +def test_get_extra_dict(inp: dict[str, Any], expect: dict[str, Any]) -> None: extra = get_extra_dict(**inp) assert extra == expect def test_get_extra_dict_reserved_keys() -> None: """Test that all reserved keys are renamed.""" - d: Dict[str, Any] = {} + d: dict[str, Any] = {} for key in RESERVED_EXTRA_KEYS: d[key] = key extra = get_extra_dict(**d) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index bee0fc70..771bce0a 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Type - import pytest from inline_snapshot import snapshot from zabbix_cli.exceptions import ZabbixAPIException @@ -15,7 +13,7 @@ @pytest.mark.parametrize( "outer_t", [TypeError, ValueError, ZabbixCLIError, ZabbixAPIException] ) -def test_get_cause_args(outer_t: Type[Exception]) -> None: +def test_get_cause_args(outer_t: type[Exception]) -> None: try: try: try: diff --git a/tests/test_models.py b/tests/test_models.py index 18fb5fea..5530a0c0 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from typing import List import pytest from inline_snapshot import snapshot @@ -45,10 +44,10 @@ class TestTableRenderable(TableRenderable): ], ) def test_table_renderable_metakey_join_char( - content: List[str], join_char: str, expect: str + content: list[str], join_char: str, expect: str ) -> None: class TestTableRenderable(TableRenderable): - foo: List[str] = Field(..., json_schema_extra={MetaKey.JOIN_CHAR: join_char}) + foo: list[str] = Field(..., json_schema_extra={MetaKey.JOIN_CHAR: join_char}) t = TestTableRenderable(foo=content) assert t.__rows__() == [expect] @@ -56,7 +55,7 @@ class TestTableRenderable(TableRenderable): def test_all_metakeys() -> None: class TestTableRenderable(TableRenderable): - foo: List[str] = Field( + foo: list[str] = Field( ..., json_schema_extra={MetaKey.JOIN_CHAR: "|", MetaKey.HEADER: "Foo Header"}, ) @@ -75,7 +74,7 @@ class FooModel(BaseModel): foo: str bar: int baz: float - qux: List[str] + qux: list[str] class TestTableRenderable(TableRenderable): foo: FooModel diff --git a/tests/test_utils.py b/tests/test_utils.py index 14d1f5ec..275a6951 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,7 +2,6 @@ from datetime import datetime from datetime import timedelta -from typing import Tuple import pytest from freezegun import freeze_time @@ -95,7 +94,7 @@ def test_convert_duration(input: str, expect: timedelta) -> None: ], ) def test_convert_timestamp_interval( - input: str, expect: Tuple[datetime, datetime] + input: str, expect: tuple[datetime, datetime] ) -> None: assert convert_timestamp_interval(input) == expect # TODO: test with mix of formats. e.g. "2016-11-21T22:00 to 2016-11-21 23:00:00" diff --git a/zabbix_cli/_patches/common.py b/zabbix_cli/_patches/common.py index 796a401b..adb5f3cd 100644 --- a/zabbix_cli/_patches/common.py +++ b/zabbix_cli/_patches/common.py @@ -7,7 +7,6 @@ if TYPE_CHECKING: from types import TracebackType from typing import Optional - from typing import Type class BasePatcher(ABC): @@ -27,7 +26,7 @@ def __enter__(self) -> BasePatcher: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> bool: @@ -70,7 +69,7 @@ def __exit__( raise SystemExit(1) -def get_patcher(info: str) -> Type[BasePatcher]: +def get_patcher(info: str) -> type[BasePatcher]: """Returns a patcher for a given package.""" class Patcher(BasePatcher): diff --git a/zabbix_cli/_patches/typer.py b/zabbix_cli/_patches/typer.py index 73f5f8d8..782cbebe 100644 --- a/zabbix_cli/_patches/typer.py +++ b/zabbix_cli/_patches/typer.py @@ -7,14 +7,13 @@ from __future__ import annotations import inspect +from collections.abc import Iterable from datetime import datetime from enum import Enum from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import Callable -from typing import Iterable -from typing import Type from typing import Union from typing import cast from uuid import UUID @@ -29,8 +28,6 @@ from zabbix_cli.pyzabbix.enums import APIStrEnum if TYPE_CHECKING: - from typing import Dict - from rich.style import Style patcher = get_patcher(f"Typer version: {typer.__version__}") @@ -131,7 +128,7 @@ def patch_generate_enum_convertor() -> None: instantiating the enum with the value directly. """ - def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]: + def generate_enum_convertor(enum: type[Enum]) -> Callable[[Any], Any]: lower_val_map = {str(val.value).lower(): val for val in enum} def convertor(value: Any) -> Any: @@ -248,7 +245,7 @@ def get_click_type( ) # our patch for APIStrEnum elif lenient_issubclass(annotation, APIStrEnum): - annotation = cast(Type[APIStrEnum], annotation) + annotation = cast(type[APIStrEnum], annotation) return click.Choice( annotation.all_choices(), case_sensitive=parameter_info.case_sensitive, @@ -283,7 +280,7 @@ def patch__get_rich_console() -> None: from zabbix_cli.output.style import RICH_THEME - styles: Dict[str, Union[str, Style]] = RICH_THEME.styles.copy() + styles: dict[str, Union[str, Style]] = RICH_THEME.styles.copy() styles.update( { "option": STYLE_OPTION, diff --git a/zabbix_cli/_v2_compat.py b/zabbix_cli/_v2_compat.py index e9464447..11c59bd7 100644 --- a/zabbix_cli/_v2_compat.py +++ b/zabbix_cli/_v2_compat.py @@ -9,7 +9,6 @@ import os import shlex from pathlib import Path -from typing import List from typing import Optional import typer @@ -75,8 +74,8 @@ def run_command_from_option(ctx: typer.Context, command: str) -> None: def args_callback( - ctx: typer.Context, value: Optional[List[str]] -) -> Optional[List[str]]: + ctx: typer.Context, value: Optional[list[str]] +) -> Optional[list[str]]: if ctx.resilient_parsing: return # for auto-completion if value: diff --git a/zabbix_cli/app/app.py b/zabbix_cli/app/app.py index 3cffe198..88b5d077 100644 --- a/zabbix_cli/app/app.py +++ b/zabbix_cli/app/app.py @@ -8,18 +8,14 @@ import inspect import logging +from collections.abc import Iterable from types import ModuleType from typing import TYPE_CHECKING from typing import Any from typing import Callable -from typing import Dict -from typing import Iterable -from typing import List from typing import NamedTuple from typing import Optional from typing import Protocol -from typing import Tuple -from typing import Type from typing import Union import typer @@ -63,7 +59,7 @@ def __str__(self) -> str: # the current typer/click API class CommandInfo(TyperCommandInfo): def __init__( - self, *args: Any, examples: Optional[List[Example]] = None, **kwargs: Any + self, *args: Any, examples: Optional[list[Example]] = None, **kwargs: Any ) -> None: super().__init__(*args, **kwargs) self.examples = examples or [] @@ -107,7 +103,7 @@ class StatefulApp(typer.Typer): """A Typer app that provides access to the global state.""" parent: Optional[StatefulApp] - plugins: Dict[str, ModuleType] + plugins: dict[str, ModuleType] # NOTE: might be a good idea to add a typing.Unpack definition for the kwargs? def __init__(self, **kwargs: Any) -> None: @@ -160,8 +156,8 @@ def command( self, name: Optional[str] = None, *, - cls: Optional[Type[TyperCommand]] = None, - context_settings: Optional[Dict[Any, Any]] = None, + cls: Optional[type[TyperCommand]] = None, + context_settings: Optional[dict[Any, Any]] = None, help: Optional[str] = None, epilog: Optional[str] = None, short_help: Optional[str] = None, @@ -173,7 +169,7 @@ def command( # Rich settings rich_help_panel: Union[str, None] = Default(None), # Zabbix-cli kwargs - examples: Optional[List[Example]] = None, + examples: Optional[list[Example]] = None, ) -> Callable[[CommandFunctionType], CommandFunctionType]: if cls is None: cls = TyperCommand @@ -208,7 +204,7 @@ def state(self) -> State: return get_state() @property - def api_version(self) -> Tuple[int, ...]: + def api_version(self) -> tuple[int, ...]: """Get the current API version. Will fail if not connected to the API.""" return self.state.client.version.release diff --git a/zabbix_cli/app/plugins.py b/zabbix_cli/app/plugins.py index deef8869..6b192827 100644 --- a/zabbix_cli/app/plugins.py +++ b/zabbix_cli/app/plugins.py @@ -5,9 +5,7 @@ from pathlib import Path from types import ModuleType from typing import TYPE_CHECKING -from typing import Dict from typing import Protocol -from typing import Tuple from typing import cast from typing import runtime_checkable @@ -36,7 +34,7 @@ def __configure__(self, config: PluginConfig) -> None: ... class PluginLoader: def __init__(self) -> None: - self.plugins: Dict[str, ModuleType] = {} + self.plugins: dict[str, ModuleType] = {} def load(self, config: Config) -> None: self._load_plugins(config) @@ -95,7 +93,7 @@ def _load_plugins_from_metadata(self, config: Config) -> None: # This is one of the drawbacks of running in 3.9 mode, but # it's necessary to ensure we don't introduce features that # do not exist in our minimum supported version. - discovered_plugins = cast(Tuple[EntryPoint], discovered_plugins) + discovered_plugins = cast(tuple[EntryPoint], discovered_plugins) for plugin in discovered_plugins: conf = config.plugins.get(plugin.name) try: diff --git a/zabbix_cli/auth.py b/zabbix_cli/auth.py index 109c3a57..b519071f 100644 --- a/zabbix_cli/auth.py +++ b/zabbix_cli/auth.py @@ -14,15 +14,13 @@ import logging import os import sys +from collections.abc import Generator from functools import cached_property from pathlib import Path from typing import TYPE_CHECKING from typing import Final -from typing import Generator -from typing import List from typing import NamedTuple from typing import Optional -from typing import Tuple from typing import Union from rich.console import ScreenContext @@ -116,7 +114,7 @@ def __init__(self, config: Config) -> None: def screen(self) -> ScreenContext: return err_console.screen() - def login_with_any(self) -> Tuple[ZabbixAPI, LoginInfo]: + def login_with_any(self) -> tuple[ZabbixAPI, LoginInfo]: """Log in to the Zabbix API using any valid method. Returns the Zabbix API client object. @@ -163,7 +161,7 @@ def _iter_all_credentials( yield self._get_username_password_prompt() @classmethod - def login_with_prompt(cls, config: Config) -> Tuple[ZabbixAPI, LoginInfo]: + def login_with_prompt(cls, config: Config) -> tuple[ZabbixAPI, LoginInfo]: """Log in to the Zabbix API using username and password from a prompt.""" auth = cls(config) creds = auth._get_username_password_prompt() @@ -176,7 +174,7 @@ def login_with_prompt(cls, config: Config) -> Tuple[ZabbixAPI, LoginInfo]: @classmethod def login_with_username_password( cls, config: Config, username: str, password: str - ) -> Tuple[ZabbixAPI, LoginInfo]: + ) -> tuple[ZabbixAPI, LoginInfo]: """Log in to the Zabbix API using username and password from a prompt.""" auth = cls(config) creds = Credentials( @@ -192,7 +190,7 @@ def login_with_username_password( @classmethod def login_with_token( cls, config: Config, token: str - ) -> Tuple[ZabbixAPI, LoginInfo]: + ) -> tuple[ZabbixAPI, LoginInfo]: """Log in to the Zabbix API using username and password from a prompt.""" auth = cls(config) creds = Credentials( @@ -393,7 +391,7 @@ def _get_auth_token_file(self) -> Credentials: username=username, auth_token=auth_token, source=CredentialsSource.FILE ) - def load_auth_token_file(self) -> Union[Tuple[Path, str], Tuple[None, None]]: + def load_auth_token_file(self) -> Union[tuple[Path, str], tuple[None, None]]: paths = get_auth_token_file_paths(self.config) for path in paths: contents = self._do_load_auth_file(path) @@ -404,7 +402,7 @@ def load_auth_token_file(self) -> Union[Tuple[Path, str], Tuple[None, None]]: ) return None, None - def load_auth_file(self) -> Tuple[Optional[Path], Optional[str]]: + def load_auth_file(self) -> tuple[Optional[Path], Optional[str]]: """Attempts to load an auth file.""" paths = get_auth_file_paths(self.config) for path in paths: @@ -450,7 +448,7 @@ def logout(client: ZabbixAPI, config: Config) -> None: clear_auth_token_file(config) -def prompt_username_password(username: str) -> Tuple[str, str]: +def prompt_username_password(username: str) -> tuple[str, str]: """Re-useable prompt for username and password.""" username = str_prompt("Username", default=username, empty_ok=False) password = str_prompt("Password", password=True, empty_ok=False) @@ -459,7 +457,7 @@ def prompt_username_password(username: str) -> Tuple[str, str]: def _parse_auth_file_contents( contents: Optional[str], -) -> Tuple[Optional[str], Optional[str]]: +) -> tuple[Optional[str], Optional[str]]: """Parse the contents of an auth file. We store auth files in the format `username::secret`. @@ -473,7 +471,7 @@ def _parse_auth_file_contents( return None, None -def get_auth_file_paths(config: Optional[Config] = None) -> List[Path]: +def get_auth_file_paths(config: Optional[Config] = None) -> list[Path]: """Get all possible auth token file paths.""" paths = [ AUTH_FILE, @@ -484,7 +482,7 @@ def get_auth_file_paths(config: Optional[Config] = None) -> List[Path]: return paths -def get_auth_token_file_paths(config: Optional[Config] = None) -> List[Path]: +def get_auth_token_file_paths(config: Optional[Config] = None) -> list[Path]: """Get all possible auth token file paths.""" paths = [ AUTH_TOKEN_FILE, diff --git a/zabbix_cli/bulk.py b/zabbix_cli/bulk.py index b1940856..08c1db3d 100644 --- a/zabbix_cli/bulk.py +++ b/zabbix_cli/bulk.py @@ -8,12 +8,11 @@ import logging import shlex +from collections import Counter from contextlib import contextmanager from dataclasses import dataclass from enum import Enum from pathlib import Path -from typing import Counter -from typing import List from typing import Optional import typer @@ -51,7 +50,7 @@ class CommentLine(SkippableLine): class BulkCommand(BaseModel): """A command to be run in bulk.""" - args: List[str] = Field(default_factory=list) + args: list[str] = Field(default_factory=list) line: str = "" # original line from file line_number: int = 0 @@ -116,9 +115,9 @@ def __init__( self.ctx = ctx self.file = file self.mode = mode - self.executions: List[CommandExecution] = [] + self.executions: list[CommandExecution] = [] """Commands that were executed.""" - self.skipped: List[CommandExecution] = [] + self.skipped: list[CommandExecution] = [] """Lines that were skipped during parsing.""" @contextmanager @@ -222,14 +221,14 @@ def run_bulk(self) -> Counter[CommandResult]: return results - def load_command_file(self) -> List[BulkCommand]: + def load_command_file(self) -> list[BulkCommand]: """Parse the contents of a command file.""" try: contents = read_file(self.file) except Exception as e: raise CommandFileError(f"Could not read command file: {e}") from e - commands: List[BulkCommand] = [] + commands: list[BulkCommand] = [] def add_skipped( line: str, line_number: int, error: Optional[BaseException] = None diff --git a/zabbix_cli/cache.py b/zabbix_cli/cache.py index 11734f80..0793e23e 100644 --- a/zabbix_cli/cache.py +++ b/zabbix_cli/cache.py @@ -5,7 +5,6 @@ import logging from typing import TYPE_CHECKING -from typing import Dict from typing import Optional from zabbix_cli.exceptions import ZabbixCLIError @@ -19,16 +18,16 @@ class ZabbixCache: def __init__(self, client: ZabbixAPI) -> None: self.client = client - self._hostgroup_name_cache: Dict[str, str] = {} + self._hostgroup_name_cache: dict[str, str] = {} """Mapping of hostgroup names to hostgroup IDs""" - self._hostgroup_id_cache: Dict[str, str] = {} + self._hostgroup_id_cache: dict[str, str] = {} """Mapping of hostgroup IDs to hostgroup names""" - self._templategroup_name_cache: Dict[str, str] = {} + self._templategroup_name_cache: dict[str, str] = {} """Mapping of templategroup names to templategroup IDs""" - self._templategroup_id_cache: Dict[str, str] = {} # NOTE: unused + self._templategroup_id_cache: dict[str, str] = {} # NOTE: unused """Mapping of templategroup IDs to templategroup names""" def populate(self) -> None: diff --git a/zabbix_cli/commands/export.py b/zabbix_cli/commands/export.py index 0c5124d2..8c90fac6 100644 --- a/zabbix_cli/commands/export.py +++ b/zabbix_cli/commands/export.py @@ -1,13 +1,11 @@ from __future__ import annotations import time +from collections.abc import Iterator from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import Iterator -from typing import List from typing import NamedTuple from typing import Optional from typing import Protocol @@ -53,13 +51,13 @@ from zabbix_cli.pyzabbix.types import TemplateGroup class ExportKwargs(TypedDict, total=False): - hosts: List[Host] - host_groups: List[HostGroup] - images: List[Image] - maps: List[Map] - media_types: List[MediaType] - templates: List[Template] - template_groups: List[TemplateGroup] + hosts: list[Host] + host_groups: list[HostGroup] + images: list[Image] + maps: list[Map] + media_types: list[MediaType] + templates: list[Template] + template_groups: list[TemplateGroup] HELP_PANEL = "Import/Export" @@ -102,8 +100,8 @@ def __init__( self, client: ZabbixAPI, config: Config, - types: List[ExportType], - names: List[str], + types: list[ExportType], + names: list[str], directory: Path, format: ExportFormat, legacy_filenames: bool, @@ -125,7 +123,7 @@ def __init__( # Will need to be rewritten to use threads to achieve this. # TODO: test that mapping contains all export types - self.exporter_map: Dict[ExportType, ExporterFunc] = { + self.exporter_map: dict[ExportType, ExporterFunc] = { ExportType.HOST_GROUPS: self.export_host_groups, ExportType.TEMPLATE_GROUPS: self.export_template_groups, ExportType.HOSTS: self.export_hosts, @@ -137,9 +135,9 @@ def __init__( self.check_export_types() - def run(self) -> List[Path]: + def run(self) -> list[Path]: """Run exporters.""" - files: List[Path] = [] + files: list[Path] = [] with err_console.status("") as status: for exporter in self.get_exporters(): status.update(f"Exporting {exporter.type.human_readable()}...") @@ -162,9 +160,9 @@ def _check_export_type_compat(self, export_type: ExportType) -> None: "Template group exports are not supported in Zabbix versions < 6.2." ) - def get_exporters(self) -> List[Exporter]: + def get_exporters(self) -> list[Exporter]: """Get a list of exporters to run.""" - exporters: List[Exporter] = [] + exporters: list[Exporter] = [] for export_type in self.export_types: exporter = self.exporter_map.get(export_type, None) if not exporter: # should never happen - tests should catch this @@ -293,14 +291,14 @@ def write_exported(self, exported: str, filename: Path) -> Path: return filename -def parse_export_types(value: List[str]) -> List[ExportType]: +def parse_export_types(value: list[str]) -> list[ExportType]: # If we have no specific exports, export all object types if not value: value = list(ExportType) elif "#all#" in value: warning("#all# is a deprecated value and will be removed in a future version.") value = list(ExportType) - objs: List[ExportType] = [] + objs: list[ExportType] = [] for obj in value: try: export_type = ExportType(obj) @@ -316,8 +314,8 @@ def parse_export_types(value: List[str]) -> List[ExportType]: def parse_export_types_callback( - ctx: typer.Context, param: typer.CallbackParam, value: List[str] -) -> List[ExportType]: + ctx: typer.Context, param: typer.CallbackParam, value: list[str] +) -> list[ExportType]: """Parses list of object export type names. In V2, this was called "objects", which isn't very descriptive... @@ -360,7 +358,7 @@ def export_configuration( ), # NOTE: We can't accept comma-separated values AND multiple values when using enums! # Typer performs its parsing before callbacks are run, sadly. - types: List[ExportType] = typer.Option( + types: list[ExportType] = typer.Option( [], "--type", help="Type(s) of objects to export. Can be specified multiple times. Defaults to all object types.", @@ -397,7 +395,7 @@ def export_configuration( ), # TODO: add --ignore-errors option # Legacy positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: r"""Export Zabbix configuration for one or more components. @@ -478,7 +476,7 @@ def __init__( self, client: ZabbixAPI, config: Config, - files: List[Path], + files: list[Path], create_missing: bool, update_existing: bool, delete_missing: bool, @@ -492,8 +490,8 @@ def __init__( self.update_existing = update_existing self.delete_missing = delete_missing - self.imported: List[Path] = [] - self.failed: List[Path] = [] + self.imported: list[Path] = [] + self.failed: list[Path] = [] def run(self) -> None: """Runs the importer.""" @@ -536,10 +534,10 @@ def import_file(self, file: Path) -> None: logger.info(f"Imported file {file}") -def filter_valid_imports(files: List[Path]) -> List[Path]: +def filter_valid_imports(files: list[Path]) -> list[Path]: """Filter list of files to include only valid imports.""" importables = [i.casefold() for i in ExportFormat.get_importables()] - valid: List[Path] = [] + valid: list[Path] = [] for f in files: if not f.exists(): continue @@ -574,7 +572,7 @@ def import_configuration( help="Enable best-effort importing. Print errors from failed imports but continue importing.", ), # Legacy positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Import Zabbix configuration from file, directory or glob pattern. diff --git a/zabbix_cli/commands/host.py b/zabbix_cli/commands/host.py index f8b18468..53e2a189 100644 --- a/zabbix_cli/commands/host.py +++ b/zabbix_cli/commands/host.py @@ -1,7 +1,6 @@ from __future__ import annotations import ipaddress -from typing import List from typing import Optional import typer @@ -63,7 +62,7 @@ def create_host( help="Description of the host.", ), # LEGACY: V2-style positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Create a host. @@ -116,7 +115,7 @@ def create_host( ] # Determine host group IDs - hg_args: List[str] = [] + hg_args: list[str] = [] # Default host groups from config def_hgs = app.state.config.app.default_hostgroups diff --git a/zabbix_cli/commands/host_interface.py b/zabbix_cli/commands/host_interface.py index 32be509a..8863d689 100644 --- a/zabbix_cli/commands/host_interface.py +++ b/zabbix_cli/commands/host_interface.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import List from typing import Optional import typer @@ -160,7 +159,7 @@ def create_host_interface( show_default=False, ), # V2-style positional args (deprecated) - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Create a host interface. diff --git a/zabbix_cli/commands/hostgroup.py b/zabbix_cli/commands/hostgroup.py index 329bc0ca..f1cceecf 100644 --- a/zabbix_cli/commands/hostgroup.py +++ b/zabbix_cli/commands/hostgroup.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import List from typing import Optional import typer @@ -73,7 +72,7 @@ def add_host_to_hostgroup( with app.status("Adding hosts to host groups..."): app.state.client.add_hosts_to_hostgroups(hosts, hgs) - result: List[AddHostsToHostGroup] = [] + result: list[AddHostsToHostGroup] = [] for hg in hgs: r = AddHostsToHostGroup.from_result(hosts, hg) if not r.hosts: @@ -151,8 +150,8 @@ def create_hostgroup( app_config = app.state.config.app - rw_grps: List[str] = [] - ro_grps: List[str] = [] + rw_grps: list[str] = [] + ro_grps: list[str] = [] if not no_usergroup_permissions: rw_grps = parse_list_arg(rw_groups) or app_config.default_admin_usergroups ro_grps = parse_list_arg(ro_groups) or app_config.default_create_user_usergroups @@ -322,7 +321,7 @@ def remove_host_from_hostgroup( with app.status("Removing hosts from host groups..."): app.state.client.remove_hosts_from_hostgroups(hosts, hgs) - result: List[RemoveHostsFromHostGroup] = [] + result: list[RemoveHostsFromHostGroup] = [] for hg in hgs: r = RemoveHostsFromHostGroup.from_result(hosts, hg) if not r.hosts: @@ -471,9 +470,9 @@ def show_hostgroup_permissions( search=True, ) - result: List[HostGroupPermissions] = [] + result: list[HostGroupPermissions] = [] for hg in hgs: - permissions: List[str] = [] + permissions: list[str] = [] for usergroup in usergroups: if app.api_version >= (6, 2, 0): rights = usergroup.hostgroup_rights diff --git a/zabbix_cli/commands/item.py b/zabbix_cli/commands/item.py index 43fc1e7b..cb134efe 100644 --- a/zabbix_cli/commands/item.py +++ b/zabbix_cli/commands/item.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import List from typing import Optional import typer @@ -42,7 +41,7 @@ def show_last_values( False, "--group", help="Group items with the same value." ), limit: Optional[int] = OPTION_LIMIT, - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Show the last values of given items of monitored hosts.""" from zabbix_cli.commands.results.item import ItemResult diff --git a/zabbix_cli/commands/problem.py b/zabbix_cli/commands/problem.py index 485ba291..5179f73a 100644 --- a/zabbix_cli/commands/problem.py +++ b/zabbix_cli/commands/problem.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import List from typing import Optional import typer @@ -39,7 +38,7 @@ def acknowledge_event( help="Close the event after acknowledging it", ), # Legacy positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Acknowledge events by ID.""" from zabbix_cli.commands.results.problem import AcknowledgeEventResult @@ -105,7 +104,7 @@ def acknowledge_trigger_last_event( help="Close event", ), # Legacy positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Acknowledge the the last event for the given triggers.""" from zabbix_cli.commands.results.problem import AcknowledgeTriggerLastEventResult @@ -167,7 +166,7 @@ def show_alarms( "--unack/--ack", help="Show only alarms whose last event is unacknowledged.", ), - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Show the latest events for the given triggers, hosts, and/or host groups. @@ -246,7 +245,7 @@ def show_trigger_events( "-l", help="Maximum number of events to show.", ), - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Show the latest events for the given triggers, hosts, and/or host groups. diff --git a/zabbix_cli/commands/proxy.py b/zabbix_cli/commands/proxy.py index c371b85c..c5a86ea2 100644 --- a/zabbix_cli/commands/proxy.py +++ b/zabbix_cli/commands/proxy.py @@ -2,11 +2,8 @@ import logging from typing import TYPE_CHECKING -from typing import Dict -from typing import List from typing import NamedTuple from typing import Optional -from typing import Set import typer @@ -34,7 +31,7 @@ class PrevProxyHosts(NamedTuple): - hosts: List[Host] + hosts: list[Host] proxy: Optional[Proxy] = None @@ -44,16 +41,16 @@ def ensure_proxy_group_support() -> None: def group_hosts_by_proxy( - app: StatefulApp, hosts: List[Host], default_proxy_id: str = "" -) -> Dict[str, PrevProxyHosts]: + app: StatefulApp, hosts: list[Host], default_proxy_id: str = "" +) -> dict[str, PrevProxyHosts]: """Group hosts by the proxy they had prior to the update.""" - proxy_ids: Set[str] = set() + proxy_ids: set[str] = set() for host in hosts: if host.proxyid: proxy_ids.add(host.proxyid) # Fetch proxies for all observed proxy IDs - proxy_mapping: Dict[str, PrevProxyHosts] = {} + proxy_mapping: dict[str, PrevProxyHosts] = {} for proxy_id in proxy_ids: try: p = app.state.client.get_proxy(proxy_id) @@ -396,7 +393,7 @@ def update_host_proxy( hosts = app.state.client.get_hosts(*hostnames, search=True) dest_proxy = app.state.client.get_proxy(proxy) - to_update: List[Host] = [] + to_update: list[Host] = [] for host in hosts: if host.proxyid != dest_proxy.proxyid: to_update.append(host) @@ -453,7 +450,7 @@ def update_hostgroup_proxy( prx = app.state.client.get_proxy(proxy) hosts = get_hostgroup_hosts(app, hostgroup) - to_update: List[Host] = [] + to_update: list[Host] = [] for host in hosts: if host.proxyid != prx.proxyid: to_update.append(host) @@ -649,13 +646,13 @@ def update_hostgroup_proxygroup( grp = app.state.client.get_proxy_group(proxygroup) hosts = get_hostgroup_hosts(app, hostgroup) - to_update: List[Host] = [] + to_update: list[Host] = [] for host in hosts: if host.proxy_groupid != grp.proxy_groupid: to_update.append(host) # Sort hosts by host group - updated: List[str] = [] # list of host IDs + updated: list[str] = [] # list of host IDs if not dryrun: if not to_update: exit_err("All hosts already have the specified proxy group.") diff --git a/zabbix_cli/commands/results/cli.py b/zabbix_cli/commands/results/cli.py index ebf79d59..7620a7ea 100644 --- a/zabbix_cli/commands/results/cli.py +++ b/zabbix_cli/commands/results/cli.py @@ -4,10 +4,7 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List from typing import Optional -from typing import Tuple from packaging.version import Version from pydantic import ConfigDict @@ -31,7 +28,7 @@ class ImplementationInfo(TypedDict): name: str - version: Tuple[Any, ...] + version: tuple[Any, ...] hexversion: int cache_tag: str @@ -124,16 +121,16 @@ class HistoryResult(TableRenderable): __show_lines__ = False __box__ = SIMPLE_HEAD - commands: List[str] = [] + commands: list[str] = [] class DirectoriesResult(TableRenderable): """Result type for `show_dirs` command.""" - directories: List[Dict[str, Path]] = [] + directories: list[dict[str, Path]] = [] @classmethod - def from_directory_types(cls, dirs: List[DirectoryType]) -> Self: + def from_directory_types(cls, dirs: list[DirectoryType]) -> Self: return cls(directories=[{str(d.value): d.as_path()} for d in dirs]) def __cols_rows__(self) -> ColsRowsType: diff --git a/zabbix_cli/commands/results/export.py b/zabbix_cli/commands/results/export.py index 9db1666f..c7b2c254 100644 --- a/zabbix_cli/commands/results/export.py +++ b/zabbix_cli/commands/results/export.py @@ -2,7 +2,6 @@ from pathlib import Path from typing import TYPE_CHECKING -from typing import List from typing import Optional from pydantic import field_serializer @@ -20,10 +19,10 @@ class ExportResult(TableRenderable): """Result type for `export_configuration` command.""" - exported: List[Path] = [] + exported: list[Path] = [] """List of paths to exported files.""" - types: List[ExportType] = [] - names: List[str] = [] + types: list[ExportType] = [] + names: list[str] = [] format: ExportFormat @@ -32,18 +31,18 @@ class ImportResult(TableRenderable): success: bool = True dryrun: bool = False - imported: List[Path] = [] - failed: List[Path] = [] + imported: list[Path] = [] + failed: list[Path] = [] duration: Optional[float] = None """Duration it took to import files in seconds. Is None if import failed.""" @field_serializer("imported", "failed", when_used="json") - def _serialize_files(self, files: List[Path]) -> List[str]: + def _serialize_files(self, files: list[Path]) -> list[str]: """Serializes files as list of normalized, absolute paths with symlinks resolved.""" return [str(f.resolve()) for f in files] def __cols_rows__(self) -> ColsRowsType: - cols: List[str] = ["Imported", "Failed"] + cols: list[str] = ["Imported", "Failed"] rows: RowsType = [ [ "\n".join(path_link(f) for f in self.imported), diff --git a/zabbix_cli/commands/results/hostgroup.py b/zabbix_cli/commands/results/hostgroup.py index de05e3ff..eb4ced90 100644 --- a/zabbix_cli/commands/results/hostgroup.py +++ b/zabbix_cli/commands/results/hostgroup.py @@ -1,9 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import List from typing import Optional -from typing import Set from pydantic import computed_field from typing_extensions import TypedDict @@ -24,15 +22,15 @@ class AddHostsToHostGroup(TableRenderable): """Result type for `add_host_to_hostgroup` and `remove_host_from_hostgroup` commands.""" hostgroup: str - hosts: List[str] + hosts: list[str] @classmethod def from_result( cls, - hosts: List[Host], + hosts: list[Host], hostgroup: HostGroup, ) -> AddHostsToHostGroup: - to_add: Set[str] = set() # names of templates to link + to_add: set[str] = set() # names of templates to link for host in hosts: for hg_host in hostgroup.hosts: if host.host == hg_host.host: @@ -49,15 +47,15 @@ class RemoveHostsFromHostGroup(TableRenderable): """Result type for `remove_host_from_hostgroup`.""" hostgroup: str - hosts: List[str] + hosts: list[str] @classmethod def from_result( cls, - hosts: List[Host], + hosts: list[Host], hostgroup: HostGroup, ) -> RemoveHostsFromHostGroup: - to_remove: Set[str] = set() # names of templates to link + to_remove: set[str] = set() # names of templates to link for host in hosts: for hg_host in hostgroup.hosts: if host.host == hg_host.host: @@ -73,12 +71,12 @@ class ExtendHostgroupResult(TableRenderable): """Result type for `extend_hostgroup` command.""" source: str - destination: List[str] - hosts: List[str] + destination: list[str] + hosts: list[str] @classmethod def from_result( - cls, source: HostGroup, destination: List[HostGroup] + cls, source: HostGroup, destination: list[HostGroup] ) -> ExtendHostgroupResult: return cls( source=source.name, @@ -92,7 +90,7 @@ class MoveHostsResult(TableRenderable): source: str destination: str - hosts: List[str] + hosts: list[str] @classmethod def from_result(cls, source: HostGroup, destination: HostGroup) -> MoveHostsResult: @@ -104,7 +102,7 @@ def from_result(cls, source: HostGroup, destination: HostGroup) -> MoveHostsResu class HostGroupDeleteResult(TableRenderable): - groups: List[str] + groups: list[str] class HostGroupHost(TypedDict): @@ -169,7 +167,7 @@ class HostGroupPermissions(TableRenderable): groupid: str name: str - permissions: List[str] + permissions: list[str] def __cols_rows__(self) -> ColsRowsType: cols = ["GroupID", "Name", "Permissions"] diff --git a/zabbix_cli/commands/results/item.py b/zabbix_cli/commands/results/item.py index 3dac80d7..9973d3bb 100644 --- a/zabbix_cli/commands/results/item.py +++ b/zabbix_cli/commands/results/item.py @@ -1,8 +1,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import Dict -from typing import List from typing import Optional from pydantic import Field @@ -80,7 +78,7 @@ def __cols_rows__(self) -> ColsRowsType: return cols, rows -def group_items(items: List[Item]) -> List[ItemResult]: +def group_items(items: list[Item]) -> list[ItemResult]: """Group items by key+lastvalue. Keeps first item for each key+lastvalue pair, and adds hosts from @@ -106,7 +104,7 @@ def group_items(items: List[Item]) -> List[ItemResult]: """ from zabbix_cli.commands.results.item import ItemResult - item_map: Dict[str, ItemResult] = {} + item_map: dict[str, ItemResult] = {} for item in items: if not item.name or not item.lastvalue or not item.key or not item.hosts: diff --git a/zabbix_cli/commands/results/macro.py b/zabbix_cli/commands/results/macro.py index bd074f63..867e8ae5 100644 --- a/zabbix_cli/commands/results/macro.py +++ b/zabbix_cli/commands/results/macro.py @@ -2,8 +2,6 @@ from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List from typing import Optional from pydantic import Field @@ -75,7 +73,7 @@ def __cols_rows__(self) -> ColsRowsType: return ["Macro", "Value", "HostID", "Host"], rows @model_serializer() - def model_ser(self) -> Dict[str, Any]: + def model_ser(self) -> dict[str, Any]: if not self.macro.hosts: return {} # match V2 output return { @@ -111,5 +109,5 @@ class ShowUsermacroTemplateListResult(TableRenderable): templateid: str template: str - def __cols__(self) -> List[str]: + def __cols__(self) -> list[str]: return ["Macro", "Value", "Template ID", "Template"] diff --git a/zabbix_cli/commands/results/maintenance.py b/zabbix_cli/commands/results/maintenance.py index 4b6d3a5d..d0c0c704 100644 --- a/zabbix_cli/commands/results/maintenance.py +++ b/zabbix_cli/commands/results/maintenance.py @@ -2,7 +2,6 @@ from datetime import datetime from typing import Any -from typing import List from typing import Optional from pydantic import Field @@ -25,9 +24,9 @@ class CreateMaintenanceDefinitionResult(TableRenderable): class ShowMaintenancePeriodsResult(TableRenderable): maintenanceid: str = Field(title="Maintenance ID") name: str - timeperiods: List[TimePeriod] - hosts: List[str] - groups: List[str] + timeperiods: list[TimePeriod] + hosts: list[str] + groups: list[str] class ShowMaintenanceDefinitionsResult(TableRenderable): @@ -38,8 +37,8 @@ class ShowMaintenanceDefinitionsResult(TableRenderable): type: Optional[int] active_till: datetime description: Optional[str] - hosts: List[str] - groups: List[str] + hosts: list[str] + groups: list[str] @computed_field @property diff --git a/zabbix_cli/commands/results/problem.py b/zabbix_cli/commands/results/problem.py index fb0b8205..afe7cfc2 100644 --- a/zabbix_cli/commands/results/problem.py +++ b/zabbix_cli/commands/results/problem.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import List from typing import Optional from zabbix_cli.models import TableRenderable @@ -9,7 +8,7 @@ class AcknowledgeEventResult(TableRenderable): """Result type for `acknowledge_event` command.""" - event_ids: List[str] = [] + event_ids: list[str] = [] close: bool = False message: Optional[str] = None @@ -17,4 +16,4 @@ class AcknowledgeEventResult(TableRenderable): class AcknowledgeTriggerLastEventResult(AcknowledgeEventResult): """Result type for `acknowledge_trigger_last_event` command.""" - trigger_ids: List[str] = [] + trigger_ids: list[str] = [] diff --git a/zabbix_cli/commands/results/proxy.py b/zabbix_cli/commands/results/proxy.py index 0be6969a..db435348 100644 --- a/zabbix_cli/commands/results/proxy.py +++ b/zabbix_cli/commands/results/proxy.py @@ -1,7 +1,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import List from typing import Optional from pydantic import BaseModel @@ -25,7 +24,7 @@ class BaseHostProxyResult(TableRenderable): source: str = Field(..., json_schema_extra={MetaKey.HEADER: "Source Proxy"}) """Name of the old proxy.""" - hosts: List[str] = [] + hosts: list[str] = [] """Name of the hosts that were updated.""" @@ -40,7 +39,7 @@ class UpdateHostProxyResult(BaseHostProxyResult): @classmethod def from_result( cls, - hosts: List[Host], + hosts: list[Host], source_proxy: Optional[Proxy], dest_proxy: Optional[Proxy], ) -> Self: @@ -57,7 +56,7 @@ class ClearHostProxyResult(BaseHostProxyResult): @classmethod def from_result( cls, - hosts: List[Host], + hosts: list[Host], source_proxy: Optional[Proxy], ) -> Self: return cls( @@ -73,14 +72,14 @@ class MoveProxyHostsResult(TableRenderable): """ID of the source (old) proxy.""" destination: Optional[str] = None """ID of the destination (new) proxy.""" - hosts: List[str] = [] + hosts: list[str] = [] class LBProxy(BaseModel): """A load balanced proxy.""" proxy: Proxy - hosts: List[Host] = [] + hosts: list[Host] = [] weight: int count: int = 0 @@ -98,7 +97,7 @@ def ser_model(self): class LBProxyResult(TableRenderable): """Result type for `load_balance_proxy_hosts` command.""" - proxies: List[LBProxy] + proxies: list[LBProxy] def __cols_rows__(self) -> ColsRowsType: cols = ["Proxy", "Weight", "Hosts"] @@ -112,10 +111,10 @@ class UpdateHostGroupProxyResult(TableRenderable): """Result type for `update_hostgroup_proxy` command.""" proxy: str - hosts: List[str] = [] + hosts: list[str] = [] @classmethod - def from_result(cls, proxy: Proxy, hosts: List[Host]) -> Self: + def from_result(cls, proxy: Proxy, hosts: list[Host]) -> Self: return cls(proxy=proxy.name, hosts=[host.host for host in hosts]) @@ -123,10 +122,10 @@ class UpdateHostGroupProxyGroupResult(TableRenderable): """Result type for `update_hostgroup_proxygroup` command.""" proxy_group: str - hosts: List[str] = [] + hosts: list[str] = [] @classmethod - def from_result(cls, proxy_group: ProxyGroup, hosts: List[Host]) -> Self: + def from_result(cls, proxy_group: ProxyGroup, hosts: list[Host]) -> Self: return cls(proxy_group=proxy_group.name, hosts=[host.host for host in hosts]) diff --git a/zabbix_cli/commands/results/template.py b/zabbix_cli/commands/results/template.py index 4ddf30fb..934fea0a 100644 --- a/zabbix_cli/commands/results/template.py +++ b/zabbix_cli/commands/results/template.py @@ -1,8 +1,6 @@ from __future__ import annotations -from typing import List from typing import Literal -from typing import Set from typing import Union from zabbix_cli.models import TableRenderable @@ -14,17 +12,17 @@ class LinkTemplateToHostResult(TableRenderable): host: str - templates: List[str] + templates: list[str] action: str @classmethod def from_result( cls, - templates: List[Template], + templates: list[Template], host: Host, action: str, ) -> LinkTemplateToHostResult: - to_link: Set[str] = set() # names of templates to link + to_link: set[str] = set() # names of templates to link for t in templates: for h in t.hosts: if h.host == host.host: @@ -40,18 +38,18 @@ def from_result( class UnlinkTemplateFromHostResult(TableRenderable): host: str - templates: List[str] + templates: list[str] action: str @classmethod def from_result( cls, - templates: List[Template], + templates: list[Template], host: Host, action: str, ) -> UnlinkTemplateFromHostResult: """Only show templates that are actually unlinked.""" - to_remove: Set[str] = set() + to_remove: set[str] = set() for t in templates: for h in t.hosts: if h.host == host.host: @@ -67,15 +65,15 @@ def from_result( class LinkTemplateResult(TableRenderable): """Result type for (un)linking templates to templates.""" - source: List[str] - destination: List[str] + source: list[str] + destination: list[str] action: str @classmethod def from_result( cls, - source: List[Template], - destination: List[Template], + source: list[Template], + destination: list[Template], action: Literal["Link", "Unlink", "Unlink and clear"], ) -> LinkTemplateResult: return cls( @@ -86,14 +84,14 @@ def from_result( class TemplateGroupResult(TableRenderable): - templates: List[str] - groups: List[str] + templates: list[str] + groups: list[str] @classmethod def from_result( cls, - templates: List[Template], - groups: Union[List[TemplateGroup], List[HostGroup]], + templates: list[Template], + groups: Union[list[TemplateGroup], list[HostGroup]], ) -> TemplateGroupResult: return cls( templates=[t.host for t in templates], @@ -103,15 +101,15 @@ def from_result( class RemoveTemplateFromGroupResult(TableRenderable): group: str - templates: List[str] + templates: list[str] @classmethod def from_result( cls, - templates: List[Template], + templates: list[Template], group: Union[TemplateGroup, HostGroup], ) -> RemoveTemplateFromGroupResult: - to_remove: Set[str] = set() + to_remove: set[str] = set() for template in group.templates: for t in templates: if t.host == template.host: diff --git a/zabbix_cli/commands/results/templategroup.py b/zabbix_cli/commands/results/templategroup.py index e28f79c6..8b835930 100644 --- a/zabbix_cli/commands/results/templategroup.py +++ b/zabbix_cli/commands/results/templategroup.py @@ -2,8 +2,6 @@ from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List from typing import Union from pydantic import Field @@ -25,7 +23,7 @@ class ShowTemplateGroupResult(TableRenderable): groupid: str = Field(..., json_schema_extra={"header": "Group ID"}) name: str - templates: List[Template] = [] + templates: list[Template] = [] show_templates: bool = Field(True, exclude=True) @classmethod @@ -45,7 +43,7 @@ def template_count(self) -> int: return len(self.templates) @field_serializer("templates") - def templates_serializer(self, value: List[Template]) -> List[Dict[str, Any]]: + def templates_serializer(self, value: list[Template]) -> list[dict[str, Any]]: if self.show_templates: return [t.model_dump(mode="json") for t in value] return [] @@ -64,15 +62,15 @@ def __rows__(self) -> RowsType: class ExtendTemplateGroupResult(TableRenderable): source: str - destination: List[str] - templates: List[str] + destination: list[str] + templates: list[str] @classmethod def from_result( cls, src_group: Union[HostGroup, TemplateGroup], - dest_group: Union[List[HostGroup], List[TemplateGroup]], - templates: List[Template], + dest_group: Union[list[HostGroup], list[TemplateGroup]], + templates: list[Template], ) -> ExtendTemplateGroupResult: return cls( source=src_group.name, @@ -86,7 +84,7 @@ class MoveTemplatesResult(TableRenderable): source: str destination: str - templates: List[str] + templates: list[str] @classmethod def from_result( diff --git a/zabbix_cli/commands/results/usergroup.py b/zabbix_cli/commands/results/usergroup.py index 3d941e78..7ba6f018 100644 --- a/zabbix_cli/commands/results/usergroup.py +++ b/zabbix_cli/commands/results/usergroup.py @@ -1,10 +1,8 @@ from __future__ import annotations +from collections.abc import Mapping from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List -from typing import Mapping from typing import Union import rich @@ -31,8 +29,8 @@ class UgroupUpdateUsersResult(TableRenderable): - usergroups: List[str] - users: List[str] + usergroups: list[str] + users: list[str] def __cols_rows__(self) -> ColsRowsType: return ( @@ -51,8 +49,8 @@ class UsergroupRemoveUsers(UgroupUpdateUsersResult): class AddUsergroupPermissionsResult(TableRenderable): usergroup: str - hostgroups: List[str] - templategroups: List[str] + hostgroups: list[str] + templategroups: list[str] permission: UsergroupPermission @computed_field @@ -85,7 +83,7 @@ class ShowUsergroupResult(TableRenderable): name: str gui_access: str = Field(..., json_schema_extra={MetaKey.HEADER: "GUI Access"}) status: str - users: List[str] = Field( + users: list[str] = Field( default_factory=list, json_schema_extra={MetaKey.JOIN_CHAR: ", "} ) @@ -119,11 +117,11 @@ class GroupRights(TableRenderable): __box__ = rich.box.MINIMAL - groups: Union[Dict[str, HostGroup], Dict[str, TemplateGroup]] = Field( + groups: Union[dict[str, HostGroup], dict[str, TemplateGroup]] = Field( default_factory=dict, ) - rights: List[ZabbixRight] = Field( + rights: list[ZabbixRight] = Field( default_factory=list, description="Group rights for the user group.", ) @@ -144,25 +142,25 @@ def __cols_rows__(self) -> ColsRowsType: class ShowUsergroupPermissionsResult(TableRenderable): usrgrpid: str name: str - hostgroups: Dict[str, HostGroup] = Field( + hostgroups: dict[str, HostGroup] = Field( default_factory=dict, exclude=True, description="Host groups the user group has access to. Used to render host group rights.", ) - templategroups: Dict[str, TemplateGroup] = Field( + templategroups: dict[str, TemplateGroup] = Field( default_factory=dict, exclude=True, description="Mapping of all template groups. Used to render template group rights.", ) - hostgroup_rights: List[ZabbixRight] = [] - templategroup_rights: List[ZabbixRight] = [] + hostgroup_rights: list[ZabbixRight] = [] + templategroup_rights: list[ZabbixRight] = [] @model_serializer - def model_ser(self) -> Dict[str, Any]: + def model_ser(self) -> dict[str, Any]: """LEGACY: Include the permission strings in the serialized output if we have legacy JSON output enabled. """ - d: Dict[str, Any] = { + d: dict[str, Any] = { "usrgrpid": self.usrgrpid, "name": self.name, "hostgroup_rights": self.hostgroup_rights, @@ -174,9 +172,9 @@ def model_ser(self) -> Dict[str, Any]: return d @property - def permissions(self) -> List[str]: + def permissions(self) -> list[str]: """LEGACY: The field `hostgroup_rights` was called `permissions` in V2.""" - r: List[str] = [] + r: list[str] = [] def permission_str( right: ZabbixRight, groups: Mapping[str, Union[HostGroup, TemplateGroup]] @@ -201,8 +199,8 @@ def permission_str( def from_usergroup( cls, usergroup: Usergroup, - hostgroups: List[HostGroup], - templategroups: List[TemplateGroup], + hostgroups: list[HostGroup], + templategroups: list[TemplateGroup], ) -> ShowUsergroupPermissionsResult: cls.model_rebuild() # TODO: can we avoid this? res = cls( diff --git a/zabbix_cli/commands/template.py b/zabbix_cli/commands/template.py index 168ff196..5245a7f8 100644 --- a/zabbix_cli/commands/template.py +++ b/zabbix_cli/commands/template.py @@ -1,7 +1,6 @@ from __future__ import annotations from itertools import chain -from typing import List from typing import Optional import typer @@ -74,7 +73,7 @@ def link_template_to_host( with app.state.console.status("Linking templates..."): app.state.client.link_templates_to_hosts(templates, hosts) - result: List[LinkTemplateToHostResult] = [] + result: list[LinkTemplateToHostResult] = [] for host in hosts: r = LinkTemplateToHostResult.from_result(templates, host, "Link") if not r.templates: @@ -291,7 +290,7 @@ def unlink_template_from_host( app.state.client.unlink_templates_from_hosts(templates, hosts) # Only show hosts with matching templates to unlink - result: List[UnlinkTemplateFromHostResult] = [] + result: list[UnlinkTemplateFromHostResult] = [] for host in hosts: r = UnlinkTemplateFromHostResult.from_result(templates, host, action) if not r.templates: diff --git a/zabbix_cli/commands/templategroup.py b/zabbix_cli/commands/templategroup.py index 879942d4..3ba33383 100644 --- a/zabbix_cli/commands/templategroup.py +++ b/zabbix_cli/commands/templategroup.py @@ -2,7 +2,6 @@ from itertools import chain from typing import TYPE_CHECKING -from typing import List from typing import Optional from typing import Union @@ -59,7 +58,7 @@ def add_template_to_group( """ from zabbix_cli.commands.results.template import TemplateGroupResult - groups: Union[List[HostGroup], List[TemplateGroup]] + 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) else: @@ -138,8 +137,8 @@ def create_templategroup( app_config = app.state.config.app - rw_grps: List[str] = [] - ro_grps: List[str] = [] + rw_grps: list[str] = [] + ro_grps: list[str] = [] if not no_usergroup_permissions: rw_grps = parse_list_arg(rw_groups) or app_config.default_admin_usergroups ro_grps = parse_list_arg(ro_groups) or app_config.default_create_user_usergroups @@ -204,7 +203,7 @@ def extend_templategroup( dest_arg = parse_list_arg(dest_group) src: Union[HostGroup, TemplateGroup] - dest: Union[List[HostGroup], List[TemplateGroup]] + dest: Union[list[HostGroup], list[TemplateGroup]] if app.state.client.version.release > (6, 2, 0): src = app.state.client.get_templategroup(src_group, select_templates=True) dest = app.state.client.get_templategroups( @@ -380,7 +379,7 @@ def remove_template_from_group( from zabbix_cli.commands.results.template import RemoveTemplateFromGroupResult from zabbix_cli.models import AggregateResult - groups: Union[List[HostGroup], List[TemplateGroup]] + 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=strict, select_templates=True @@ -402,7 +401,7 @@ def remove_template_from_group( templates, groups, ) - result: List[RemoveTemplateFromGroupResult] = [] + result: list[RemoveTemplateFromGroupResult] = [] for group in groups: r = RemoveTemplateFromGroupResult.from_result(templates, group) if not r.templates: @@ -488,7 +487,7 @@ def show_templategroups( names = parse_list_arg(name) - groups: Union[List[HostGroup], List[TemplateGroup]] + groups: Union[list[HostGroup], list[TemplateGroup]] with app.status("Fetching template groups..."): if app.state.client.version.release < (6, 2, 0): groups = app.state.client.get_hostgroups( diff --git a/zabbix_cli/commands/user.py b/zabbix_cli/commands/user.py index d9ae066f..c289b319 100644 --- a/zabbix_cli/commands/user.py +++ b/zabbix_cli/commands/user.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import List from typing import Optional from typing import TypeVar @@ -78,7 +77,7 @@ def create_user( None, help="Comma-separated list of group IDs to add the user to." ), # Legacy V2 positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Create a user.""" from zabbix_cli.models import Result @@ -171,7 +170,7 @@ def create_notification_user( help="Comma-separated list of usergroups to add the user to. Overrides user groups in config file.", ), # Legacy V2 args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: # TODO: Improve phrasing of this help text. "Defining media for usergroup"??? """Create a notification user. @@ -231,7 +230,7 @@ def create_notification_user( if usergroups: ug_list = parse_list_arg(usergroups) else: - ug_list: List[str] = [] + ug_list: list[str] = [] ug_list.extend(app.state.config.app.default_notification_users_usergroups) ug_list.extend(app.state.config.app.default_create_user_usergroups) with app.status("Fetching user group(s)..."): @@ -436,7 +435,7 @@ def update_user( help="User session lifetime in seconds. Set to 0 to never expire. Can be a time unit with suffix (0s, 15m, 1h, 1d, etc.)", ), # Legacy V2 positional args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Update a user. diff --git a/zabbix_cli/commands/usergroup.py b/zabbix_cli/commands/usergroup.py index 0ecf29e9..a65e99d0 100644 --- a/zabbix_cli/commands/usergroup.py +++ b/zabbix_cli/commands/usergroup.py @@ -5,7 +5,6 @@ import logging from contextlib import suppress from typing import TYPE_CHECKING -from typing import List from typing import Optional from typing import TypeVar @@ -44,8 +43,8 @@ class UsergroupSorting(StrEnum): def sort_ugroups( - ugroups: List[UsergroupLikeT], sort: UsergroupSorting -) -> List[UsergroupLikeT]: + ugroups: list[UsergroupLikeT], sort: UsergroupSorting +) -> list[UsergroupLikeT]: """Sort result types based on user group objects. I.e. we have some custom types that all share the samse attributes @@ -139,7 +138,7 @@ def add_usergroup_permissions( case_sensitive=False, ), # Legacy V2 args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Give a user group permissions to host/template groups. @@ -210,7 +209,7 @@ def create_usergroup( help="Create the user group in a disabled state.", ), # V2 legacy args - args: Optional[List[str]] = ARGS_POSITIONAL, + args: Optional[list[str]] = ARGS_POSITIONAL, ) -> None: """Create a user group.""" # We already have name and GUI access, so we expect 1 more arg at most @@ -348,7 +347,7 @@ def _do_show_usergroups( usergroups = app.state.client.get_usergroups( *ugs, select_users=True, search=True, limit=limit ) - res: List[ShowUsergroupResult] = [] + res: list[ShowUsergroupResult] = [] for ugroup in usergroups: res.append(ShowUsergroupResult.from_usergroup(ugroup)) # NOTE: why client-side sorting? @@ -406,7 +405,7 @@ def show_usergroup_permissions( templategroups = app.state.client.get_templategroups() else: templategroups = [] - res: List[ShowUsergroupPermissionsResult] = [] + res: list[ShowUsergroupPermissionsResult] = [] for ugroup in usergroups: res.append( ShowUsergroupPermissionsResult.from_usergroup( diff --git a/zabbix_cli/config/constants.py b/zabbix_cli/config/constants.py index 0231c78d..c7b34496 100644 --- a/zabbix_cli/config/constants.py +++ b/zabbix_cli/config/constants.py @@ -4,7 +4,6 @@ from pathlib import Path from typing import Any from typing import Callable -from typing import Dict from typing import Optional from typing import Union @@ -59,7 +58,7 @@ class SecretMode(StrEnum): @classmethod def from_context( - cls, context: Optional[Union[Dict[str, Any], Callable[..., Dict[str, Any]]]] + cls, context: Optional[Union[dict[str, Any], Callable[..., dict[str, Any]]]] ) -> SecretMode: """Get the secret mode from a serialization context.""" if isinstance(context, dict) and (ctx := context.get("secrets")) is not None: diff --git a/zabbix_cli/config/model.py b/zabbix_cli/config/model.py index cb203310..3624ca05 100644 --- a/zabbix_cli/config/model.py +++ b/zabbix_cli/config/model.py @@ -24,10 +24,7 @@ import logging from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import Optional -from typing import Type from typing import TypeVar from typing import Union from typing import overload @@ -166,26 +163,26 @@ def _ignore_enum_case(cls, v: Any) -> Any: class AppConfig(BaseModel): - default_hostgroups: List[str] = Field( + default_hostgroups: list[str] = Field( default=["All-hosts"], # Changed in V3: default_hostgroup -> default_hostgroups validation_alias=AliasChoices("default_hostgroups", "default_hostgroup"), ) - default_admin_usergroups: List[str] = Field( + default_admin_usergroups: list[str] = Field( default=[], # Changed in V3: default_admin_usergroup -> default_admin_usergroups validation_alias=AliasChoices( "default_admin_usergroups", "default_admin_usergroup" ), ) - default_create_user_usergroups: List[str] = Field( + default_create_user_usergroups: list[str] = Field( default=[], # Changed in V3: default_create_user_usergroup -> default_create_user_usergroups validation_alias=AliasChoices( "default_create_user_usergroups", "default_create_user_usergroup" ), ) - default_notification_users_usergroups: List[str] = Field( + default_notification_users_usergroups: list[str] = Field( default=["All-notification-users"], # Changed in V3: default_notification_users_usergroup -> default_notification_users_usergroups validation_alias=AliasChoices( @@ -355,8 +352,8 @@ def _empty_string_is_none(cls, v: Any) -> Any: # Can consider moving this elsewhere -@functools.lru_cache(maxsize=None) -def _get_type_adapter(type: Type[T]) -> TypeAdapter[T]: +@functools.cache +def _get_type_adapter(type: type[T]) -> TypeAdapter[T]: """Get a type adapter for a given type.""" return TypeAdapter(type) @@ -384,7 +381,7 @@ def get(self, key: str) -> Any: ... # Type with no default @overload - def get(self, key: str, *, type: Type[T]) -> T: ... + def get(self, key: str, *, type: type[T]) -> T: ... # No type with default @overload @@ -396,7 +393,7 @@ def get( self, key: str, default: T, - type: Type[T], + type: type[T], ) -> T: ... # Union type with no default @@ -405,7 +402,7 @@ def get( self, key: str, *, - type: Optional[Type[T]], + type: Optional[type[T]], ) -> Optional[T]: ... # Union type with default @@ -414,14 +411,14 @@ def get( self, key: str, default: Optional[T], - type: Optional[Type[T]], + type: Optional[type[T]], ) -> Optional[T]: ... def get( self, key: str, default: Union[T, Any] = NotSet, - type: Optional[Type[T]] = object, + type: Optional[type[T]] = object, ) -> Union[T, Optional[T], Any]: """Get a plugin configuration value by key. @@ -448,8 +445,8 @@ def set(self, key: str, value: Any) -> None: setattr(self, key, value) -class PluginsConfig(RootModel[Dict[str, PluginConfig]]): - root: Dict[str, PluginConfig] = Field(default_factory=dict) +class PluginsConfig(RootModel[dict[str, PluginConfig]]): + root: dict[str, PluginConfig] = Field(default_factory=dict) def get(self, key: str, strict: bool = False) -> Optional[PluginConfig]: """Get a plugin configuration by name.""" diff --git a/zabbix_cli/config/utils.py b/zabbix_cli/config/utils.py index 03ff546a..f20799be 100644 --- a/zabbix_cli/config/utils.py +++ b/zabbix_cli/config/utils.py @@ -4,11 +4,8 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List from typing import NamedTuple from typing import Optional -from typing import Tuple from pydantic import BaseModel @@ -22,7 +19,7 @@ logger = logging.getLogger(__name__) -def load_config_toml(filename: Path) -> Dict[str, Any]: +def load_config_toml(filename: Path) -> dict[str, Any]: """Load a TOML configuration file.""" import tomli @@ -34,7 +31,7 @@ def load_config_toml(filename: Path) -> Dict[str, Any]: raise ConfigError(f"Error reading TOML file {filename}: {e}") from e -def load_config_conf(filename: Path) -> Dict[str, Any]: +def load_config_conf(filename: Path) -> dict[str, Any]: """Load a conf configuration file with ConfigParser.""" import configparser @@ -50,7 +47,7 @@ def load_config_conf(filename: Path) -> Dict[str, Any]: def find_config( filename: Optional[Path] = None, - priority: Tuple[Path, ...] = CONFIG_PRIORITY, + priority: tuple[Path, ...] = CONFIG_PRIORITY, ) -> Optional[Path]: """Find all available configuration files. @@ -107,9 +104,9 @@ def check_deprecated_fields(model: BaseModel) -> None: def get_deprecated_fields_set( model: BaseModel, parent: Optional[str] = None -) -> List[DeprecatedField]: +) -> list[DeprecatedField]: """Get a list of deprecated fields set on a model and all its submodels.""" - fields: List[DeprecatedField] = [] + fields: list[DeprecatedField] = [] # Sort for reproducibility + readability for field_name in sorted(model.model_fields_set): field = model.model_fields.get(field_name) diff --git a/zabbix_cli/exceptions.py b/zabbix_cli/exceptions.py index 72d5da02..71e45990 100644 --- a/zabbix_cli/exceptions.py +++ b/zabbix_cli/exceptions.py @@ -3,12 +3,9 @@ import functools from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List from typing import NoReturn from typing import Optional from typing import Protocol -from typing import Type from typing import runtime_checkable if TYPE_CHECKING: @@ -200,20 +197,20 @@ class HandleFunc(Protocol): def __call__(self, e: Any) -> NoReturn: ... -def get_cause_args(e: Optional[BaseException]) -> List[str]: +def get_cause_args(e: Optional[BaseException]) -> list[str]: """Retrieves all args as strings from all exceptions in the cause chain. Flattens the args into a single list. """ - args: List[str] = [] + args: list[str] = [] while e: args.extend(get_exc_args(e)) e = e.__cause__ return args -def get_exc_args(e: BaseException) -> List[str]: +def get_exc_args(e: BaseException) -> list[str]: """Returns the error message as a string.""" - args: List[str] = [str(arg) for arg in e.args] + args: list[str] = [str(arg) for arg in e.args] if isinstance(e, ZabbixAPIRequestError): args.append(e.reason()) return args @@ -278,13 +275,13 @@ def handle_zabbix_api_exception(e: ZabbixAPIException) -> NoReturn: handle_notraceback(e) -def get_exception_handler(type_: Type[Exception]) -> Optional[HandleFunc]: +def get_exception_handler(type_: type[Exception]) -> Optional[HandleFunc]: """Returns the exception handler for the given exception type.""" from httpx import ConnectError from pydantic import ValidationError # Defined inline for performance reasons (httpx and pydantic imports) - EXC_HANDLERS: Dict[type[Exception], HandleFunc] = { + EXC_HANDLERS: dict[type[Exception], HandleFunc] = { # ZabbixAPICallError: handle_zabbix_api_call_error, # NOTE: use different strategy for this? ZabbixAPIException: handle_zabbix_api_exception, # NOTE: use different strategy for this? ZabbixCLIError: handle_notraceback, diff --git a/zabbix_cli/main.py b/zabbix_cli/main.py index ac2ec788..01387b7b 100644 --- a/zabbix_cli/main.py +++ b/zabbix_cli/main.py @@ -25,7 +25,6 @@ import sys from pathlib import Path from typing import TYPE_CHECKING -from typing import Dict from typing import Optional import typer @@ -73,7 +72,7 @@ def pre_run() -> None: state.repl = True state.revert_config_overrides() - prompt_kwargs: Dict[str, Any] = {"pre_run": pre_run, "history": state.history} + prompt_kwargs: dict[str, Any] = {"pre_run": pre_run, "history": state.history} start_repl(ctx, app, prompt_kwargs=prompt_kwargs) diff --git a/zabbix_cli/models.py b/zabbix_cli/models.py index 6c3146df..f05bfdd6 100644 --- a/zabbix_cli/models.py +++ b/zabbix_cli/models.py @@ -1,15 +1,12 @@ from __future__ import annotations +from collections.abc import MutableSequence from enum import Enum from typing import TYPE_CHECKING from typing import Any from typing import ClassVar -from typing import Dict from typing import Generic -from typing import List -from typing import MutableSequence from typing import Optional -from typing import Tuple from typing import Union from typing import cast @@ -38,7 +35,7 @@ class ReturnCode(StrEnum): ERROR = "Error" -ColsType = List[str] +ColsType = list[str] """A list of column headers.""" RowContent = MutableSequence["RenderableType"] @@ -47,7 +44,7 @@ class ReturnCode(StrEnum): RowsType = MutableSequence[RowContent] """A list of rows, where each row is a list of strings.""" -ColsRowsType = Tuple[ColsType, RowsType] +ColsRowsType = tuple[ColsType, RowsType] """A tuple containing a list of columns and a list of rows, where each row is a list of strings.""" @@ -119,11 +116,11 @@ def _get_extra(self, field: str, key: MetaKey, default: T) -> T: # But that will only happen once we actually encounter such a bug. return cast(T, f.json_schema_extra.get(key, default)) - def __all_fields__(self) -> Dict[str, Union[FieldInfo, ComputedFieldInfo]]: + def __all_fields__(self) -> dict[str, Union[FieldInfo, ComputedFieldInfo]]: """Returns all fields for the model, including computed fields, but excluding excluded fields. """ - all_fields: Dict[str, Union[FieldInfo, ComputedFieldInfo]] = { + all_fields: dict[str, Union[FieldInfo, ComputedFieldInfo]] = { **self.model_fields, **self.model_computed_fields, } @@ -146,7 +143,7 @@ def __cols__(self) -> ColsType: >>> User().__cols__() ["User ID", "Username"] """ - cols: List[str] = [] + cols: list[str] = [] for field_name, field in self.__all_fields__().items(): if ( @@ -181,7 +178,7 @@ def __rows__(self) -> RowsType: >>> User(userid="1", username="admin", groups=["foo", "bar", "baz"]).__rows__() [["1", "admin", "foo\nbar\nbaz"]] """ # noqa: D416 - fields: Dict[str, Any | str] = { + fields: dict[str, Any | str] = { field_name: getattr(self, field_name, "") for field_name in self.__all_fields__() } @@ -197,7 +194,7 @@ def __rows__(self) -> RowsType: ) fields[field_name] = value.model_dump_json(indent=2) elif isinstance(value, list): - value = cast(List[Any], value) + value = cast(list[Any], value) # A list either contains TableRenderable objects or stringable objects if value and all(isinstance(v, TableRenderable) for v in value): # TableRenderables are wrapped in an AggregateResult to render them @@ -205,7 +202,7 @@ def __rows__(self) -> RowsType: # NOTE: we assume list contains items of the same type # Rendering an aggregate result with mixed types is not supported # and will probably break. - value = cast(List[TableRenderable], value) + value = cast(list[TableRenderable], value) fields[field_name] = AggregateResult(result=value).as_table() else: # Other lists are rendered as newline delimited strings. @@ -254,7 +251,7 @@ def as_table(self) -> Table: class BaseResult(TableRenderable): message: str = Field(default="") """Field that signals that the result should be printed as a message, not a table.""" - errors: List[str] = Field(default_factory=list) + errors: list[str] = Field(default_factory=list) return_code: ReturnCode = ReturnCode.DONE table: bool = Field(default=True, exclude=True) @@ -266,10 +263,10 @@ class BaseResult(TableRenderable): class Result(BaseResult, Generic[DataT]): """A result wrapping a single data object.""" - result: Optional[Union[DataT, List[DataT]]] = None + result: Optional[Union[DataT, list[DataT]]] = None # https://docs.pydantic.dev/latest/concepts/serialization/#serialize_as_any-runtime-setting - def model_dump(self, **kwargs: Any) -> Dict[str, Any]: + def model_dump(self, **kwargs: Any) -> dict[str, Any]: return super().model_dump(serialize_as_any=True, **kwargs) def model_dump_json(self, **kwargs: Any) -> str: @@ -287,7 +284,7 @@ class AggregateResult(BaseResult, Generic[TableRenderableT]): results. """ - result: List[TableRenderableT] = Field(default_factory=list) + result: list[TableRenderableT] = Field(default_factory=list) def __cols_rows__(self) -> ColsRowsType: cols: ColsType = [] diff --git a/zabbix_cli/output/console.py b/zabbix_cli/output/console.py index ab233e6e..e8781adc 100644 --- a/zabbix_cli/output/console.py +++ b/zabbix_cli/output/console.py @@ -4,7 +4,6 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Dict from typing import NoReturn from typing import Optional @@ -52,7 +51,7 @@ def get_theme(name: str) -> Theme: ) -def get_extra_dict(**kwargs: Any) -> Dict[str, Any]: +def get_extra_dict(**kwargs: Any) -> dict[str, Any]: """Format the extra dict for logging. Renames some keys to avoid collisions with the default keys. diff --git a/zabbix_cli/output/prompts.py b/zabbix_cli/output/prompts.py index b535178d..8cd43d64 100644 --- a/zabbix_cli/output/prompts.py +++ b/zabbix_cli/output/prompts.py @@ -8,9 +8,7 @@ from pathlib import Path from typing import Any from typing import Callable -from typing import List from typing import Optional -from typing import Type from typing import overload from rich.prompt import Confirm @@ -92,7 +90,7 @@ def str_prompt( default: str = ..., # pyright: ignore[reportArgumentType] # rich uses ... to signify no default password: bool = False, show_default: bool = True, - choices: Optional[List[str]] = None, + choices: Optional[list[str]] = None, empty_ok: bool = False, strip: bool = True, **kwargs: Any, @@ -163,7 +161,7 @@ def str_prompt_optional( default: str = "", password: bool = False, show_default: bool = False, - choices: Optional[List[str]] = None, + choices: Optional[list[str]] = None, strip: bool = True, **kwargs: Any, ) -> str: @@ -193,7 +191,7 @@ def list_prompt( # https://github.com/python/mypy/issues/3737#issuecomment-1446769973 # Using this weird TypeConstructor type seems very hacky type: TypeConstructor[T] = str, -) -> List[T]: +) -> list[T]: """Prompt user for a comma-separated list of values.""" from zabbix_cli.utils.args import parse_list_arg @@ -256,7 +254,7 @@ def float_prompt( @overload def _number_prompt( - prompt_type: Type[IntPrompt], + prompt_type: type[IntPrompt], prompt: str, default: int | float | None = ..., show_default: bool = ..., @@ -269,7 +267,7 @@ def _number_prompt( @overload def _number_prompt( - prompt_type: Type[FloatPrompt], + prompt_type: type[FloatPrompt], prompt: str, default: int | float | None = ..., show_default: bool = ..., @@ -281,7 +279,7 @@ def _number_prompt( def _number_prompt( - prompt_type: Type[IntPrompt] | Type[FloatPrompt], + prompt_type: type[IntPrompt] | type[FloatPrompt], prompt: str, default: int | float | None = None, show_default: bool = True, diff --git a/zabbix_cli/output/render.py b/zabbix_cli/output/render.py index 7a033eca..bf0a71e4 100644 --- a/zabbix_cli/output/render.py +++ b/zabbix_cli/output/render.py @@ -4,7 +4,6 @@ from contextlib import nullcontext from typing import TYPE_CHECKING from typing import Any -from typing import Dict import typer @@ -142,7 +141,7 @@ def render_json_legacy( else: from zabbix_cli.models import AggregateResult - jdict: Dict[str, Any] = {} # always a dict in legacy mode + jdict: dict[str, Any] = {} # always a dict in legacy mode res = result.model_dump(mode="json", by_alias=True) if isinstance(result, AggregateResult): py_result = res.get("result", []) diff --git a/zabbix_cli/pyzabbix/client.py b/zabbix_cli/pyzabbix/client.py index 1ff177c9..59d43a39 100644 --- a/zabbix_cli/pyzabbix/client.py +++ b/zabbix_cli/pyzabbix/client.py @@ -15,16 +15,13 @@ from __future__ import annotations import logging +from collections.abc import MutableMapping from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List from typing import Literal -from typing import MutableMapping from typing import Optional -from typing import Tuple from typing import Union from typing import cast @@ -107,9 +104,9 @@ class HTTPXClientKwargs(TypedDict, total=False): RPC_ENDPOINT = "/api_jsonrpc.php" -def strip_none(data: Dict[str, Any]) -> Dict[str, Any]: +def strip_none(data: dict[str, Any]) -> dict[str, Any]: """Recursively strip None values from a dictionary.""" - new: Dict[str, Any] = {} + new: dict[str, Any] = {} for key, value in data.items(): if value is not None: if isinstance(value, dict): @@ -158,7 +155,7 @@ def add_param( def parse_name_or_id_arg( params: ParamsType, - names_or_ids: Tuple[str, ...], + names_or_ids: tuple[str, ...], name_param: str, id_param: str, search: bool = True, @@ -196,7 +193,7 @@ def parse_name_or_id_arg( def add_common_params( params: ParamsType, - sort_field: Optional[Union[str, List[str]]] = None, + sort_field: Optional[Union[str, list[str]]] = None, sort_order: Optional[SortOrder] = None, limit: Optional[int] = None, ) -> ParamsType: @@ -217,7 +214,7 @@ def add_common_params( return params -def get_returned_list(returned: Any, key: str, endpoint: str) -> List[str]: +def get_returned_list(returned: Any, key: str, endpoint: str) -> list[str]: """Retrieve a list from a given key in a Zabbix API response.""" if not isinstance(returned, dict): raise ZabbixAPIException( @@ -228,7 +225,7 @@ def get_returned_list(returned: Any, key: str, endpoint: str) -> List[str]: raise ZabbixAPIException( f"{endpoint!r} response did not contain a list for key {key!r}" ) - return cast(List[str], response_list) + return cast(list[str], response_list) class ZabbixAPI: @@ -584,7 +581,7 @@ def get_hostgroups( sort_order: Optional[SortOrder] = None, sort_field: Optional[str] = None, limit: Optional[int] = None, - ) -> List[HostGroup]: + ) -> list[HostGroup]: """Fetches a list of host groups given its name or ID. Name or ID argument is interpeted as an ID if the argument is numeric. @@ -625,7 +622,7 @@ def get_hostgroups( params, sort_field=sort_field, sort_order=sort_order, limit=limit ) - resp: List[Any] = self.hostgroup.get(**params) or [] + resp: list[Any] = self.hostgroup.get(**params) or [] return [HostGroup(**hostgroup) for hostgroup in resp] def create_hostgroup(self, name: str) -> str: @@ -650,7 +647,7 @@ def delete_hostgroup(self, hostgroup_id: str) -> None: ) from e def add_hosts_to_hostgroups( - self, hosts: List[Host], hostgroups: List[HostGroup] + self, hosts: list[Host], hostgroups: list[HostGroup] ) -> None: """Adds hosts to one or more host groups.""" try: @@ -663,7 +660,7 @@ def add_hosts_to_hostgroups( raise ZabbixAPICallError(f"Failed to add hosts to {hgs}") from e def remove_hosts_from_hostgroups( - self, hosts: List[Host], hostgroups: List[HostGroup] + self, hosts: list[Host], hostgroups: list[HostGroup] ) -> None: """Removes the given hosts from one or more host groups.""" try: @@ -714,7 +711,7 @@ def get_templategroups( select_templates: bool = False, sort_field: Optional[str] = None, sort_order: Optional[SortOrder] = None, - ) -> List[TemplateGroup]: + ) -> list[TemplateGroup]: """Fetches a list of template groups, optionally filtered by name(s). Name or ID argument is interpeted as an ID if the argument is numeric. @@ -753,7 +750,7 @@ def get_templategroups( add_common_params(params, sort_field=sort_field, sort_order=sort_order) try: - resp: List[Any] = self.templategroup.get(**params) or [] + resp: list[Any] = self.templategroup.get(**params) or [] except ZabbixAPIException as e: raise ZabbixAPICallError("Failed to fetch template groups") from e return [TemplateGroup(**tgroup) for tgroup in resp] @@ -830,7 +827,7 @@ def get_hosts( select_interfaces: bool = False, proxy: Optional[Proxy] = None, proxy_group: Optional[ProxyGroup] = None, - hostgroups: Optional[List[HostGroup]] = None, + hostgroups: Optional[list[HostGroup]] = None, # These params take special API values we don't want to evaluate # inside this method, so we delegate it to the enums. maintenance: Optional[MaintenanceStatus] = None, @@ -840,7 +837,7 @@ def get_hosts( sort_order: Optional[Literal["ASC", "DESC"]] = None, search: bool = True, # we generally always want to search when multiple hosts are requested limit: Optional[int] = None, - ) -> List[Host]: + ) -> list[Host]: """Fetches all hosts matching the given criteria(s). Hosts can be filtered by name or ID. Names and IDs cannot be mixed. @@ -923,7 +920,7 @@ def get_hosts( params, sort_field=sort_field, sort_order=sort_order, limit=limit ) - resp: List[Any] = self.host.get(**params) or [] + resp: list[Any] = self.host.get(**params) or [] # TODO add result to cache return [Host(**r) for r in resp] @@ -944,12 +941,12 @@ def count(self, object_type: str, params: Optional[ParamsType] = None) -> int: def create_host( self, host: str, - groups: List[HostGroup], + groups: list[HostGroup], proxy: Optional[Proxy] = None, status: MonitoringStatus = MonitoringStatus.ON, - interfaces: Optional[List[HostInterface]] = None, + interfaces: Optional[list[HostInterface]] = None, inventory_mode: InventoryMode = InventoryMode.AUTOMATIC, - inventory: Optional[Dict[str, Any]] = None, + inventory: Optional[dict[str, Any]] = None, description: Optional[str] = None, ) -> str: params: ParamsType = { @@ -1051,12 +1048,12 @@ def get_hostinterface( def get_hostinterfaces( self, - hostids: Union[str, List[str], None] = None, - interfaceids: Union[str, List[str], None] = None, - itemids: Union[str, List[str], None] = None, - triggerids: Union[str, List[str], None] = None, + hostids: Union[str, list[str], None] = None, + interfaceids: Union[str, list[str], None] = None, + itemids: Union[str, list[str], None] = None, + triggerids: Union[str, list[str], None] = None, # Can expand with the rest of the parameters if needed - ) -> List[HostInterface]: + ) -> list[HostInterface]: """Fetches a list of host interfaces, optionally filtered by host ID, interface ID, item ID or trigger ID. """ @@ -1190,7 +1187,7 @@ def get_usergroups( select_rights: bool = True, search: bool = True, limit: Optional[int] = None, - ) -> List[Usergroup]: + ) -> list[Usergroup]: """Fetches all user groups. Optionally includes users and rights.""" params: ParamsType = { "output": "extend", @@ -1244,16 +1241,16 @@ def create_usergroup( ) return str(resp["usrgrpids"][0]) - def add_usergroup_users(self, usergroup_name: str, users: List[User]) -> None: + def add_usergroup_users(self, usergroup_name: str, users: list[User]) -> None: """Add users to a user group. Ignores users already in the group.""" self._update_usergroup_users(usergroup_name, users, remove=False) - def remove_usergroup_users(self, usergroup_name: str, users: List[User]) -> None: + def remove_usergroup_users(self, usergroup_name: str, users: list[User]) -> None: """Remove users from a user group. Ignores users not in the group.""" 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. @@ -1281,7 +1278,7 @@ def _update_usergroup_users( def update_usergroup_rights( self, usergroup_name: str, - groups: List[str], + groups: list[str], permission: UsergroupPermission, hostgroup: bool, ) -> None: @@ -1320,11 +1317,11 @@ def update_usergroup_rights( def _get_updated_rights( self, - rights: List[ZabbixRight], + rights: list[ZabbixRight], permission: UsergroupPermission, - groups: Union[List[HostGroup], List[TemplateGroup]], - ) -> List[ZabbixRight]: - new_rights: List[ZabbixRight] = [] # list of new rights to add + groups: Union[list[HostGroup], list[TemplateGroup]], + ) -> list[ZabbixRight]: + new_rights: list[ZabbixRight] = [] # list of new rights to add rights = list(rights) # copy rights (don't modify original) for group in groups: for right in rights: @@ -1353,7 +1350,7 @@ def get_proxies( select_hosts: bool = False, search: bool = True, **kwargs: Any, - ) -> List[Proxy]: + ) -> list[Proxy]: """Fetches all proxies. NOTE: IDs and names cannot be mixed @@ -1381,7 +1378,7 @@ def get_proxies( def get_proxy_group( self, name_or_id: str, - proxies: Optional[List[Proxy]] = None, + proxies: Optional[list[Proxy]] = None, select_proxies: bool = False, ) -> ProxyGroup: """Fetches a proxy group given its ID or name.""" @@ -1397,9 +1394,9 @@ def get_proxy_group( def get_proxy_groups( self, *names_or_ids: str, - proxies: Optional[List[Proxy]] = None, + proxies: Optional[list[Proxy]] = None, select_proxies: bool = False, - ) -> List[ProxyGroup]: + ) -> list[ProxyGroup]: """Fetches a proxy group given its ID or name.""" params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( @@ -1466,8 +1463,8 @@ def add_host_to_proxygroup(self, host: Host, proxygroup: ProxyGroup) -> None: ) from e def add_hosts_to_proxygroup( - self, hosts: List[Host], proxygroup: ProxyGroup - ) -> List[str]: + self, hosts: list[Host], proxygroup: ProxyGroup + ) -> list[str]: try: updated = self.host.massupdate( hosts=[{"hostid": host.hostid} for host in hosts], @@ -1505,7 +1502,7 @@ def get_macro( raise ZabbixNotFoundError("Macro not found") return macros[0] - def get_hosts_with_macro(self, macro: str) -> List[Host]: + def get_hosts_with_macro(self, macro: str) -> list[Host]: """Fetches a macro given a host ID and macro name.""" macros = self.get_macros(macro_name=macro) if not macros: @@ -1522,7 +1519,7 @@ def get_macros( sort_field: Optional[str] = "macro", sort_order: Optional[SortOrder] = None, limit: Optional[int] = None, - ) -> List[Macro]: + ) -> list[Macro]: params: ParamsType = {"output": "extend"} if host: @@ -1575,7 +1572,7 @@ def get_global_macros( search: bool = False, sort_field: Optional[str] = "macro", sort_order: Optional[SortOrder] = None, - ) -> List[GlobalMacro]: + ) -> list[GlobalMacro]: params: ParamsType = {"output": "extend", "globalmacro": True} if macro_name: @@ -1632,7 +1629,7 @@ def update_macro(self, macroid: str, value: str) -> str: ) return resp["hostmacroids"][0] - def update_host_inventory(self, host: Host, inventory: Dict[str, str]) -> str: + def update_host_inventory(self, host: Host, inventory: dict[str, str]) -> str: """Updates a host inventory given a host and inventory.""" try: resp = self.host.update(hostid=host.hostid, inventory=inventory) @@ -1651,7 +1648,7 @@ def update_host_proxy(self, host: Host, proxy: Proxy) -> str: resp = self.update_hosts_proxy([host], proxy) return resp[0] if resp else "" - def update_hosts_proxy(self, hosts: List[Host], proxy: Proxy) -> List[str]: + def update_hosts_proxy(self, hosts: list[Host], proxy: Proxy) -> list[str]: """Updates a list of hosts' proxy.""" params: ParamsType = { "hosts": [{"hostid": host.hostid} for host in hosts], @@ -1667,7 +1664,7 @@ def update_hosts_proxy(self, hosts: List[Host], proxy: Proxy) -> List[str]: ) from e return get_returned_list(resp, "hostids", "host.massupdate") - def clear_host_proxies(self, hosts: List[Host]) -> List[str]: + def clear_host_proxies(self, hosts: list[Host]) -> list[str]: """Clears a host's proxy.""" params: ParamsType = { "hosts": [{"hostid": host.hostid} for host in hosts], @@ -1698,7 +1695,7 @@ def update_host_status(self, host: Host, status: MonitoringStatus) -> str: # NOTE: maybe passing in a list of hosts to this is overkill? # Just pass in a list of host IDs instead? - def move_hosts_to_proxy(self, hosts: List[Host], proxy: Proxy) -> None: + def move_hosts_to_proxy(self, hosts: list[Host], proxy: Proxy) -> None: """Moves a list of hosts to a proxy.""" params: ParamsType = { "hosts": [{"hostid": host.hostid} for host in hosts], @@ -1737,7 +1734,7 @@ def get_templates( select_hosts: bool = False, select_templates: bool = False, select_parent_templates: bool = False, - ) -> List[Template]: + ) -> list[Template]: """Fetches one or more templates given a name or ID.""" params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( @@ -1762,8 +1759,8 @@ def get_templates( def add_templates_to_groups( self, - templates: List[Template], - groups: Union[List[HostGroup], List[TemplateGroup]], + templates: list[Template], + groups: Union[list[HostGroup], list[TemplateGroup]], ) -> None: try: self.template.massadd( @@ -1776,7 +1773,7 @@ def add_templates_to_groups( raise ZabbixAPICallError("Failed to add templates to group(s)") from e def link_templates_to_hosts( - self, templates: List[Template], hosts: List[Host] + self, templates: list[Template], hosts: list[Host] ) -> None: """Links one or more templates to one or more hosts. @@ -1798,7 +1795,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. @@ -1826,7 +1823,7 @@ def unlink_templates_from_hosts( raise ZabbixAPICallError("Failed to unlink and clear templates") from e def link_templates( - self, source: List[Template], destination: List[Template] + self, source: list[Template], destination: list[Template] ) -> None: """Links one or more templates to one or more templates @@ -1854,7 +1851,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. @@ -1887,8 +1884,8 @@ def unlink_templates( def link_templates_to_groups( self, - templates: List[Template], - groups: Union[List[HostGroup], List[TemplateGroup]], + templates: list[Template], + groups: Union[list[HostGroup], list[TemplateGroup]], ) -> None: """Links one or more templates to one or more host/template groups. @@ -1916,8 +1913,8 @@ def link_templates_to_groups( def remove_templates_from_groups( self, - templates: List[Template], - groups: Union[List[HostGroup], List[TemplateGroup]], + templates: list[Template], + groups: Union[list[HostGroup], list[TemplateGroup]], ) -> None: """Removes template(s) from host/template group(s). @@ -1946,9 +1943,9 @@ def remove_templates_from_groups( def get_items( self, *names: str, - templates: Optional[List[Template]] = None, - hosts: Optional[List[Template]] = None, # NYI - proxies: Optional[List[Proxy]] = None, # NYI + templates: Optional[list[Template]] = None, + hosts: Optional[list[Template]] = None, # NYI + proxies: Optional[list[Proxy]] = None, # NYI search: bool = True, monitored: bool = False, select_hosts: bool = False, @@ -1956,7 +1953,7 @@ def get_items( # TODO: implement interfaces # TODO: implement graphs # TODO: implement triggers - ) -> List[Item]: + ) -> list[Item]: params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( params, @@ -1989,8 +1986,8 @@ def create_user( role: Optional[UserRole] = None, autologin: Optional[bool] = None, autologout: Union[str, int, None] = None, - usergroups: Union[List[Usergroup], None] = None, - media: Optional[List[UserMedia]] = None, + usergroups: Union[list[Usergroup], None] = None, + media: Optional[list[UserMedia]] = None, ) -> str: # TODO: handle invalid password # TODO: handle invalid type @@ -2033,7 +2030,7 @@ def get_role(self, name_or_id: str) -> Role: raise ZabbixNotFoundError(f"Role {name_or_id!r} not found") return roles[0] - def get_roles(self, name_or_id: Optional[str] = None) -> List[Role]: + def get_roles(self, name_or_id: Optional[str] = None) -> list[Role]: params: ParamsType = {"output": "extend"} if name_or_id is not None: if name_or_id.isdigit(): @@ -2058,7 +2055,7 @@ def get_users( limit: Optional[int] = None, sort_field: Optional[str] = None, sort_order: Optional[SortOrder] = None, - ) -> List[User]: + ) -> list[User]: params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( params, @@ -2143,7 +2140,7 @@ def get_mediatype(self, name_or_id: str) -> MediaType: def get_mediatypes( self, *names_or_ids: str, search: bool = False - ) -> List[MediaType]: + ) -> list[MediaType]: params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( params, @@ -2165,12 +2162,12 @@ def get_maintenance(self, maintenance_id: str) -> Maintenance: def get_maintenances( self, - maintenance_ids: Optional[List[str]] = None, - hostgroups: Optional[List[HostGroup]] = None, - hosts: Optional[List[Host]] = None, + maintenance_ids: Optional[list[str]] = None, + hostgroups: Optional[list[HostGroup]] = None, + hosts: Optional[list[Host]] = None, name: Optional[str] = None, limit: Optional[int] = None, - ) -> List[Maintenance]: + ) -> list[Maintenance]: params: ParamsType = { "output": "extend", "selectHosts": "extend", @@ -2198,8 +2195,8 @@ def create_maintenance( active_since: datetime, active_till: datetime, description: Optional[str] = None, - hosts: Optional[List[Host]] = None, - hostgroups: Optional[List[HostGroup]] = None, + hosts: Optional[list[Host]] = None, + hostgroups: Optional[list[HostGroup]] = None, data_collection: Optional[DataCollectionMode] = None, ) -> str: """Create a one-time maintenance definition.""" @@ -2234,7 +2231,7 @@ def create_maintenance( raise ZabbixAPICallError(f"Creating maintenance {name!r} returned no ID.") return resp["maintenanceids"][0] - def delete_maintenance(self, *maintenance_ids: str) -> List[str]: + def delete_maintenance(self, *maintenance_ids: str) -> list[str]: """Deletes one or more maintenances given their IDs Returns IDs of deleted maintenances. @@ -2263,7 +2260,7 @@ def acknowledge_event( unsuppress: bool = False, change_to_cause: bool = False, change_to_symptom: bool = False, - ) -> List[str]: + ) -> list[str]: # The action is an integer that is created based on # the combination of the parameters passed in. action = get_acknowledge_action_value( @@ -2314,7 +2311,7 @@ def get_event( sort_order=sort_order, ) if not events: - reasons: List[str] = [] + reasons: list[str] = [] if event_id: reasons.append(f"event ID {event_id!r}") if group_id: @@ -2331,16 +2328,16 @@ def get_event( def get_events( self, - event_ids: Union[str, List[str], None] = None, + 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? - group_ids: Union[str, List[str], None] = None, - host_ids: Union[str, List[str], None] = None, - object_ids: Union[str, List[str], None] = None, - sort_field: Union[str, List[str], None] = None, + group_ids: Union[str, list[str], None] = None, + host_ids: Union[str, list[str], None] = None, + object_ids: Union[str, list[str], None] = None, + sort_field: Union[str, list[str], None] = None, sort_order: Optional[SortOrder] = None, limit: Optional[int] = None, - ) -> List[Event]: + ) -> list[Event]: params: ParamsType = {"output": "extend"} if event_ids: params["eventids"] = event_ids @@ -2360,9 +2357,9 @@ 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, + trigger_ids: Union[str, list[str], None] = None, + hostgroups: Optional[list[HostGroup]] = None, + templates: Optional[list[Template]] = None, description: Optional[str] = None, priority: Optional[TriggerPriority] = None, unacknowledged: bool = False, @@ -2370,11 +2367,11 @@ def get_triggers( monitored: Optional[bool] = None, active: Optional[bool] = None, expand_description: Optional[bool] = None, - filter: Optional[Dict[str, Any]] = None, + filter: Optional[dict[str, Any]] = None, select_hosts: bool = False, sort_field: Optional[str] = "lastchange", sort_order: SortOrder = "DESC", - ) -> List[Trigger]: + ) -> list[Trigger]: params: ParamsType = {"output": "extend"} if description: params["search"] = {"description": description} @@ -2415,7 +2412,7 @@ def get_triggers( raise ZabbixAPICallError("Failed to fetch triggers") from e return [Trigger(**trigger) for trigger in resp] - def get_images(self, *image_names: str, select_image: bool = True) -> List[Image]: + def get_images(self, *image_names: str, select_image: bool = True) -> list[Image]: """Fetches images, optionally filtered by name(s).""" params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( @@ -2431,7 +2428,7 @@ def get_images(self, *image_names: str, select_image: bool = True) -> List[Image raise ZabbixAPICallError("Failed to fetch images") from e return [Image(**image) for image in resp] - def get_maps(self, *map_names: str) -> List[Map]: + def get_maps(self, *map_names: str) -> list[Map]: """Fetches maps, optionally filtered by name(s).""" params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( @@ -2447,7 +2444,7 @@ def get_maps(self, *map_names: str) -> List[Map]: raise ZabbixAPICallError("Failed to fetch maps") from e return [Map(**m) for m in resp] - def get_media_types(self, *names: str) -> List[MediaType]: + def get_media_types(self, *names: str) -> list[MediaType]: """Fetches media types, optionally filtered by name(s).""" params: ParamsType = {"output": "extend"} params = parse_name_or_id_arg( @@ -2462,13 +2459,13 @@ 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, - images: Optional[List[Image]] = None, - maps: Optional[List[Map]] = None, - templates: Optional[List[Template]] = None, - media_types: Optional[List[MediaType]] = None, + host_groups: Optional[list[HostGroup]] = None, + template_groups: Optional[list[TemplateGroup]] = None, + hosts: Optional[list[Host]] = None, + images: Optional[list[Image]] = None, + maps: Optional[list[Map]] = None, + templates: Optional[list[Template]] = None, + media_types: Optional[list[MediaType]] = None, format: ExportFormat = ExportFormat.JSON, pretty: bool = True, ) -> str: @@ -2564,7 +2561,7 @@ def get(self, *args: Any, **kwargs: Any) -> Any: if isinstance(output_kwargs, list) and any( p in output_kwargs for p in params ): - output_kwargs = cast(List[str], output_kwargs) + output_kwargs = cast(list[str], output_kwargs) for param in params: try: output_kwargs.remove(param) diff --git a/zabbix_cli/pyzabbix/enums.py b/zabbix_cli/pyzabbix/enums.py index dfbd649e..588e927c 100644 --- a/zabbix_cli/pyzabbix/enums.py +++ b/zabbix_cli/pyzabbix/enums.py @@ -1,12 +1,10 @@ from __future__ import annotations +from collections.abc import Mapping from enum import Enum from typing import Any from typing import Generic -from typing import List -from typing import Mapping from typing import Optional -from typing import Type from typing import TypeVar from strenum import StrEnum @@ -110,7 +108,7 @@ def __fmt_name__(cls) -> str: # NOTE: should we use a custom prompt class instead of repurposing the str prompt? @classmethod def from_prompt( - cls: Type[MixinType], + cls: type[MixinType], prompt: Optional[str] = None, default: MixinType = ..., # pyright: ignore[reportArgumentType] # rich uses ... to signify no default ) -> MixinType: @@ -140,23 +138,23 @@ def from_prompt( return cls(choice) @classmethod - def public_members(cls) -> List[Self]: + def public_members(cls) -> list[Self]: """Return list of visible enum members.""" return [e for e in cls if not e.value.hidden] @classmethod - def choices(cls) -> List[str]: + def choices(cls) -> list[str]: """Return list of string values of the enum members.""" return [str(e) for e in cls.public_members()] @classmethod - def api_choices(cls) -> List[int]: + def api_choices(cls) -> list[int]: """Return list of API values of the enum members.""" # NOTE: should this be a list of strings instead? return [e.as_api_value() for e in cls.public_members()] @classmethod - def all_choices(cls) -> List[str]: + def all_choices(cls) -> list[str]: """All public choices as well as their API values.""" return cls.choices() + [str(c) for c in cls.api_choices()] @@ -196,7 +194,7 @@ def as_status(self, default: str = "Unknown", with_code: bool = False) -> str: @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: @@ -264,7 +262,7 @@ def _missing_(cls, value: object) -> ExportFormat: raise ValueError(f"Invalid format: {value!r}.") @classmethod - def get_importables(cls) -> List[ExportFormat]: + def get_importables(cls) -> list[ExportFormat]: """Return list of formats that can be imported.""" return [cls.JSON, cls.YAML, cls.XML] diff --git a/zabbix_cli/pyzabbix/types.py b/zabbix_cli/pyzabbix/types.py index eb2d65f8..a946c72d 100644 --- a/zabbix_cli/pyzabbix/types.py +++ b/zabbix_cli/pyzabbix/types.py @@ -14,15 +14,14 @@ from __future__ import annotations import logging +from collections.abc import Iterable +from collections.abc import MutableMapping +from collections.abc import Sequence from datetime import datetime from datetime import timedelta +from typing import Annotated from typing import Any -from typing import Dict -from typing import Iterable -from typing import List -from typing import MutableMapping from typing import Optional -from typing import Sequence from typing import Union from pydantic import AliasChoices @@ -40,7 +39,6 @@ from pydantic import field_serializer from pydantic import field_validator from pydantic_core import PydanticCustomError -from typing_extensions import Annotated from typing_extensions import Literal from typing_extensions import TypeAliasType from typing_extensions import TypedDict @@ -127,8 +125,8 @@ def json_custom_error_validator( def serialize_host_list_json( - hosts: List[Host], info: SerializationInfo -) -> List[Dict[str, str]]: + hosts: list[Host], info: SerializationInfo +) -> list[dict[str, str]]: """Custom JSON serializer for a list of hosts. Most of the time we don't want to serialize _all_ fields of a host. @@ -141,7 +139,7 @@ def serialize_host_list_json( HostList = Annotated[ - List["Host"], PlainSerializer(serialize_host_list_json, when_used="json") + list["Host"], PlainSerializer(serialize_host_list_json, when_used="json") ] """List of hosts that serialize as the minimal representation of a list of hosts.""" @@ -171,7 +169,7 @@ class ModifyHostItem(TypedDict): hostid: Union[str, int] -ModifyHostParams = List[ModifyHostItem] +ModifyHostParams = list[ModifyHostItem] """A list of host IDs in an API request. @@ -185,7 +183,7 @@ class ModifyGroupItem(TypedDict): groupid: Union[str, int] -ModifyGroupParams = List[ModifyGroupItem] +ModifyGroupParams = list[ModifyGroupItem] """A list of host/template group IDs in an API request. E.g. `[{"groupid": "123"}, {"groupid": "456"}]` @@ -198,7 +196,7 @@ class ModifyTemplateItem(TypedDict): templateid: Union[str, int] -ModifyTemplateParams = List[ModifyTemplateItem] +ModifyTemplateParams = list[ModifyTemplateItem] """A list of template IDs in an API request. E.g. `[{"templateid": "123"}, {"templateid": "456"}]` @@ -233,7 +231,7 @@ class ZabbixAPIBaseModel(TableRenderable): model_config = ConfigDict(validate_assignment=True, extra="ignore") - def model_dump_api(self) -> Dict[str, Any]: + def model_dump_api(self) -> dict[str, Any]: """Dump the model as a JSON-serializable dict used in API calls. Excludes computed fields by default.""" @@ -255,7 +253,7 @@ def permission_str(self) -> str: """Returns the permission as a formatted string.""" return UsergroupPermission.string_from_value(self.permission) - def model_dump_api(self) -> Dict[str, Any]: + def model_dump_api(self) -> dict[str, Any]: return self.model_dump( mode="json", include={"permission", "id"}, exclude_none=True ) @@ -302,10 +300,10 @@ class Usergroup(ZabbixAPIBaseModel): usrgrpid: str gui_access: int users_status: int - rights: List[ZabbixRight] = [] - hostgroup_rights: List[ZabbixRight] = [] - templategroup_rights: List[ZabbixRight] = [] - users: List[User] = [] + rights: list[ZabbixRight] = [] + hostgroup_rights: list[ZabbixRight] = [] + templategroup_rights: list[ZabbixRight] = [] + users: list[User] = [] @computed_field @property @@ -345,10 +343,10 @@ class Template(ZabbixAPIBaseModel): templateid: str host: str hosts: HostList = [] - templates: List[Template] = [] + templates: list[Template] = [] """Child templates (templates inherited from this template).""" - parent_templates: List[Template] = Field( + parent_templates: list[Template] = Field( default_factory=list, validation_alias=AliasChoices("parentTemplates", "parent_templates"), serialization_alias="parentTemplates", # match JSON output to API format @@ -376,7 +374,7 @@ class TemplateGroup(ZabbixAPIBaseModel): groupid: str name: str uuid: str - templates: List[Template] = [] + templates: list[Template] = [] class HostGroup(ZabbixAPIBaseModel): @@ -385,7 +383,7 @@ class HostGroup(ZabbixAPIBaseModel): hosts: HostList = [] flags: int = 0 internal: Optional[int] = None # <6.2 - templates: List[Template] = [] # <6.2 + templates: list[Template] = [] # <6.2 def __cols_rows__(self) -> ColsRowsType: # FIXME: is this ever used? Can we remove? @@ -422,12 +420,12 @@ class Host(ZabbixAPIBaseModel): hostid: str host: str = "" description: Optional[str] = None - groups: List[HostGroup] = Field( + groups: list[HostGroup] = Field( default_factory=list, # Compat for >= 6.2.0 validation_alias=AliasChoices("groups", "hostgroups"), ) - templates: List[Template] = Field( + templates: list[Template] = Field( default_factory=list, validation_alias=AliasChoices("templates", "parentTemplates"), ) @@ -451,8 +449,8 @@ class Host(ZabbixAPIBaseModel): ), ) status: Optional[str] = None - macros: List[Macro] = Field(default_factory=list) - interfaces: List[HostInterface] = Field(default_factory=list) + macros: list[Macro] = Field(default_factory=list) + interfaces: list[HostInterface] = Field(default_factory=list) # HACK: Add a field for the host's proxy that we can inject later proxy: Optional[Proxy] = None @@ -460,14 +458,14 @@ class Host(ZabbixAPIBaseModel): def __str__(self) -> str: return f"{self.host!r} ({self.hostid})" - def model_simple_dump(self) -> Dict[str, str]: + def model_simple_dump(self) -> dict[str, str]: """Dump the model with minimal fields for simple output.""" return { "host": self.host, "hostid": self.hostid, } - def set_proxy(self, proxy_map: Dict[str, Proxy]) -> None: + def set_proxy(self, proxy_map: dict[str, Proxy]) -> None: """Set proxy info for the host given a mapping of proxy IDs to proxies.""" if not (proxy := proxy_map.get(str(self.proxyid))): return @@ -697,7 +695,7 @@ class ProxyGroup(ZabbixAPIBaseModel): failover_delay: str min_online: int # This is a str in the spec, but it's a number 1-1000! state: ProxyGroupState - proxies: List[Proxy] = Field(default_factory=list) + proxies: list[Proxy] = Field(default_factory=list) @field_validator("min_online", mode="before") def _handle_non_numeric_min_online(cls, v: Any) -> Any: @@ -757,7 +755,7 @@ class Macro(MacroBase): hostmacroid: str automatic: Optional[int] = None # >= 7.0 only. 0 = user, 1 = discovery rule hosts: HostList = Field(default_factory=list) - templates: List[Template] = Field(default_factory=list) + templates: list[Template] = Field(default_factory=list) class GlobalMacro(MacroBase): @@ -904,12 +902,12 @@ def start_date_str(self) -> str: @computed_field @property - def month_str(self) -> List[str]: + def month_str(self) -> list[str]: return get_maintenance_active_months(self.month) @computed_field @property - def dayofweek_str(self) -> List[str]: + def dayofweek_str(self) -> list[str]: return get_maintenance_active_days(self.dayofweek) @computed_field @@ -983,16 +981,16 @@ class Maintenance(ZabbixAPIBaseModel): description: Optional[str] = None maintenance_type: Optional[int] = None tags_evaltype: Optional[int] = None - timeperiods: List[TimePeriod] = [] - tags: List[ProblemTag] = [] + timeperiods: list[TimePeriod] = [] + tags: list[ProblemTag] = [] hosts: HostList = [] - hostgroups: List[HostGroup] = Field( + hostgroups: list[HostGroup] = Field( default_factory=list, validation_alias=AliasChoices("groups", "hostgroups") ) @field_validator("timeperiods", mode="after") @classmethod - def _sort_time_periods(cls, v: List[TimePeriod]) -> List[TimePeriod]: + def _sort_time_periods(cls, v: list[TimePeriod]) -> list[TimePeriod]: """Cheeky hack to consistently render mixed time period types. This validator ensures time periods are sorted by complexity, so that the @@ -1129,7 +1127,7 @@ class Trigger(ZabbixAPIBaseModel): correlation_tag: Optional[str] = None manual_close: Optional[int] = None uuid: Optional[str] = None - hosts: List[Host] = [] + hosts: list[Host] = [] # NYI: # groups: List[HostGroup] = Field( # default_factory=list, validation_alias=AliasChoices("groups", "hostgroups") diff --git a/zabbix_cli/pyzabbix/utils.py b/zabbix_cli/pyzabbix/utils.py index ff4a3279..380969c8 100644 --- a/zabbix_cli/pyzabbix/utils.py +++ b/zabbix_cli/pyzabbix/utils.py @@ -3,7 +3,6 @@ import random import re from typing import TYPE_CHECKING -from typing import Dict from typing import Optional from zabbix_cli.exceptions import ZabbixAPICallError @@ -30,7 +29,7 @@ def get_random_proxy(client: ZabbixAPI, pattern: Optional[str] = None) -> Proxy: return random.choice(proxies) -def get_proxy_map(client: ZabbixAPI) -> Dict[str, Proxy]: +def get_proxy_map(client: ZabbixAPI) -> dict[str, Proxy]: """Fetch all proxies and return a mapping of proxy IDs to Proxy objects.""" proxies = client.get_proxies() return {proxy.proxyid: proxy for proxy in proxies} diff --git a/zabbix_cli/repl/completer.py b/zabbix_cli/repl/completer.py index e78b8fa4..305aeb7a 100644 --- a/zabbix_cli/repl/completer.py +++ b/zabbix_cli/repl/completer.py @@ -2,10 +2,9 @@ import os import shlex +from collections.abc import Generator from glob import iglob from typing import Any -from typing import Generator -from typing import List from typing import Optional import click @@ -24,7 +23,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]: """Split an argument string as with :func:`shlex.split`, but don't fail if the string is incomplete. Ignores a missing closing quote or incomplete escape sequence and uses the partial token as-is. @@ -39,7 +38,7 @@ def split_arg_string(string: str, posix: bool = True) -> List[str]: lex = shlex.shlex(string, posix=posix) lex.whitespace_split = True lex.commenters = "" - out: List[str] = [] + out: list[str] = [] try: for token in lex: @@ -53,7 +52,7 @@ def split_arg_string(string: str, posix: bool = True) -> List[str]: return out -def _resolve_context(args: List[Any], ctx: click.Context) -> click.Context: +def _resolve_context(args: list[Any], ctx: click.Context) -> click.Context: """Produce the context hierarchy starting with the command and traversing the complete arguments. This only follows the commands, it doesn't trigger input prompts or callbacks. @@ -121,22 +120,22 @@ def _get_completion_from_autocompletion_functions( self, param: click.Parameter, autocomplete_ctx: click.Context, - args: List[str], + args: list[str], incomplete: str, ): - param_choices: List[Completion] = [] + param_choices: list[Completion] = [] autocompletions = param.shell_complete(autocomplete_ctx, incomplete) for autocomplete in autocompletions: param_choices.append(Completion(str(autocomplete.value), -len(incomplete))) return param_choices def _get_completion_for_Path_types( - self, param: click.Parameter, args: List[str], incomplete: str - ) -> List[Completion]: + self, param: click.Parameter, args: list[str], incomplete: str + ) -> list[Completion]: if "*" in incomplete: return [] - choices: List[Completion] = [] + choices: list[Completion] = [] _incomplete = os.path.expandvars(incomplete) search_pattern = _incomplete.strip("'\"\t\n\r\v ").replace("\\\\", "\\") + "*" quote = "" @@ -180,7 +179,7 @@ def _get_completion_for_Boolean_type(self, param: click.Parameter, incomplete: s def _get_completion_from_command_param( self, param: click.Parameter, incomplete: str - ) -> List[Completion]: + ) -> list[Completion]: return [ Completion(command, -len(incomplete)) for command in self.cli.list_commands(self.parsed_ctx) @@ -190,11 +189,11 @@ def _get_completion_from_command_param( def _get_completion_from_params( self, autocomplete_ctx: click.Context, - args: List[str], + args: list[str], param: click.Parameter, incomplete: str, - ) -> List[Completion]: - choices: List[Completion] = [] + ) -> list[Completion]: + choices: list[Completion] = [] if isinstance(param.type, click.types.BoolParamType): # Only suggest completion if parameter is not a flag if isinstance(param, click.Option) and not param.is_flag: @@ -220,9 +219,9 @@ def _get_completion_for_cmd_args( ctx_command: click.Command, incomplete: str, autocomplete_ctx: click.Context, - args: List[str], - ) -> List[Completion]: - choices: List[Completion] = [] + args: list[str], + ) -> list[Completion]: + choices: list[Completion] = [] param_called = False for param in ctx_command.params: @@ -289,7 +288,7 @@ def get_completions( args = split_arg_string(document.text_before_cursor, posix=False) - choices: List[Completion] = [] + choices: list[Completion] = [] cursor_within_command = ( document.text_before_cursor.rstrip() == document.text_before_cursor ) diff --git a/zabbix_cli/repl/repl.py b/zabbix_cli/repl/repl.py index 5c9be8bc..8ae7ecef 100644 --- a/zabbix_cli/repl/repl.py +++ b/zabbix_cli/repl/repl.py @@ -6,13 +6,11 @@ import shlex import sys from collections import defaultdict +from collections.abc import Iterable from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import DefaultDict -from typing import Dict -from typing import Iterable -from typing import List from typing import NamedTuple from typing import NoReturn from typing import Optional @@ -51,7 +49,7 @@ class InternalCommand(NamedTuple): description: str -_internal_commands: Dict[str, InternalCommand] = dict() +_internal_commands: dict[str, InternalCommand] = dict() def _register_internal_command( @@ -85,7 +83,7 @@ def _help_internal() -> str: formatter.write_text('prefix external commands with "!"') with formatter.section("Internal Commands"): formatter.write_text('prefix internal commands with ":"') - info_table: DefaultDict[str, List[str]] = defaultdict(list) + info_table: DefaultDict[str, list[str]] = defaultdict(list) for mnemonic, target_info in _internal_commands.items(): info_table[target_info[1]].append(mnemonic) formatter.write_dl( @@ -107,12 +105,12 @@ def _help_internal() -> str: def bootstrap_prompt( - prompt_kwargs: Optional[Dict[str, Any]], + prompt_kwargs: Optional[dict[str, Any]], group: click.Group, ctx: click.Context, show_only_unused: bool = False, shortest_only: bool = False, -) -> Dict[str, Any]: +) -> dict[str, Any]: """ Bootstrap prompt_toolkit kwargs or use user defined values. @@ -174,7 +172,7 @@ def handle_internal_commands(command: str) -> Any: def repl( # noqa: C901 old_ctx: Context, app: StatefulApp, - prompt_kwargs: Optional[Dict[str, Any]] = None, + prompt_kwargs: Optional[dict[str, Any]] = None, allow_system_commands: bool = True, allow_internal_commands: bool = True, ) -> None: diff --git a/zabbix_cli/update.py b/zabbix_cli/update.py index 85f12954..6ff61536 100644 --- a/zabbix_cli/update.py +++ b/zabbix_cli/update.py @@ -20,12 +20,8 @@ from abc import abstractmethod from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import NamedTuple from typing import Optional -from typing import Set -from typing import Type import httpx from pydantic import BaseModel @@ -81,18 +77,18 @@ def package_manager(self) -> str: @property @abstractmethod - def uninstall_command(self) -> List[str]: + def uninstall_command(self) -> list[str]: """The command used to uninstall the package.""" raise NotImplementedError @property @abstractmethod - def upgrade_command(self) -> List[str]: + def upgrade_command(self) -> list[str]: """The command used to upgrade the package.""" raise NotImplementedError @abstractmethod - def get_packages(self) -> Set[str]: + def get_packages(self) -> set[str]: """Get a list of installed packages.""" raise NotImplementedError @@ -140,13 +136,13 @@ def update(self) -> None: class PipxListOutput(BaseModel): # pipx_spec_version: str # ignore this for now - venvs: Dict[str, Any] # we just care about the keys + venvs: dict[str, Any] # we just care about the keys @classmethod def from_json(cls, j: str) -> Self: return cls.model_validate_json(j) - def package_names(self) -> Set[str]: + def package_names(self) -> set[str]: """Get installed package names.""" return set(self.venvs.keys()) @@ -158,17 +154,17 @@ def package_manager(self) -> str: return "pipx" @property - def uninstall_command(self) -> List[str]: + def uninstall_command(self) -> list[str]: """The command used to uninstall the package.""" return ["pipx", "uninstall"] @property @abstractmethod - def upgrade_command(self) -> List[str]: + def upgrade_command(self) -> list[str]: """The command used to upgrade the package.""" return ["pipx", "upgrade"] - def get_packages(self) -> Set[str]: + def get_packages(self) -> set[str]: out = subprocess.check_output(["pipx", "list", "--json"], text=True) try: @@ -186,18 +182,18 @@ def package_manager(self) -> str: return "pip" @property - def uninstall_command(self) -> List[str]: + def uninstall_command(self) -> list[str]: """The command used to uninstall the package.""" return ["pip", "uninstall"] @property @abstractmethod - def upgrade_command(self) -> List[str]: + def upgrade_command(self) -> list[str]: """The command used to upgrade the package.""" return ["pip", "install", "--upgrade"] - def get_packages(self) -> Set[str]: - pkgs: Set[str] = set() + def get_packages(self) -> set[str]: + pkgs: set[str] = set() out = subprocess.check_output(["pip", "freeze"], text=True) lines = out.splitlines() @@ -235,18 +231,18 @@ def package_manager(self) -> str: return "uv" @property - def uninstall_command(self) -> List[str]: + def uninstall_command(self) -> list[str]: """The command used to uninstall the package.""" return ["uv", "tool" "uninstall"] @property @abstractmethod - def upgrade_command(self) -> List[str]: + def upgrade_command(self) -> list[str]: """The command used to upgrade the package.""" return ["uv", "tool", "upgrade"] - def get_packages(self) -> Set[str]: - pkgs: Set[str] = set() + def get_packages(self) -> set[str]: + pkgs: set[str] = set() out = subprocess.check_output(["uv", "tool", "list"], text=True) @@ -293,7 +289,7 @@ def get_release_arch(arch: str) -> str: Attempts to map the platform.machine() name to the name used in the GitHub release artifacts. If no mapping is found, the original name is returned.""" - ARCH_MAP: Dict[str, str] = { + ARCH_MAP: dict[str, str] = { "x86_64": "x86_64", "amd64": "x86_64", "arm64": "arm64", @@ -310,7 +306,7 @@ def get_release_os(os: str) -> str: Attempts to map the sys.platform name to the name used in the GitHub release artifacts. If no mapping is found, the original name is returned.""" - PLATFORM_MAP: Dict[str, str] = { + PLATFORM_MAP: dict[str, str] = { "linux": "linux", "darwin": "macos", "win32": "win", @@ -418,7 +414,7 @@ def get_release_arch(arch: str) -> str: Attempts to map the platform.machine() name to the name used in the GitHub release artifacts. If no mapping is found, the original name is returned.""" - ARCH_MAP: Dict[str, str] = { + ARCH_MAP: dict[str, str] = { "x86_64": "x86_64", "amd64": "x86_64", "arm64": "arm64", @@ -435,7 +431,7 @@ def get_release_os(os: str) -> str: Attempts to map the sys.platform name to the name used in the GitHub release artifacts. If no mapping is found, the original name is returned.""" - PLATFORM_MAP: Dict[str, str] = { + PLATFORM_MAP: dict[str, str] = { "linux": "linux", "darwin": "macos", "win32": "win", @@ -600,7 +596,7 @@ def detect_uv(self) -> Optional[InstallationInfo]: return InstallationInfo(method=InstallationMethod.UV, bindir=bindir) -UPDATERS: Dict[InstallationMethod, Type[Updater]] = { +UPDATERS: dict[InstallationMethod, type[Updater]] = { InstallationMethod.PYINSTALLER: PyInstallerUpdater, # InstallationMethod.GIT: GitUpdater, # InstallationMethod.PIP: PipUpdater, @@ -615,7 +611,7 @@ class UpdateInfo(NamedTuple): path: Optional[Path] = None -def get_updater(method: InstallationMethod) -> Type[Updater]: +def get_updater(method: InstallationMethod) -> type[Updater]: updater = UPDATERS.get(method) if updater is None: raise UpdateError(f"No updater available for installation method {method}") diff --git a/zabbix_cli/utils/args.py b/zabbix_cli/utils/args.py index 682f90fb..e711166f 100644 --- a/zabbix_cli/utils/args.py +++ b/zabbix_cli/utils/args.py @@ -7,9 +7,7 @@ import logging from pathlib import Path from typing import TYPE_CHECKING -from typing import List from typing import Optional -from typing import Set import typer @@ -55,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 [] @@ -66,7 +64,7 @@ def parse_list_arg(arg: Optional[str], keep_empty: bool = False) -> List[str]: raise ZabbixCLIError(f"Invalid comma-separated string value: {arg}") from e -def parse_int_list_arg(arg: str) -> List[int]: +def parse_int_list_arg(arg: str) -> list[int]: """Convert comma-separated string of ints to list of ints.""" args = parse_list_arg( arg, @@ -84,7 +82,7 @@ def parse_hostgroups_arg( strict: bool = False, select_hosts: bool = False, select_templates: bool = False, -) -> List[HostGroup]: +) -> list[HostGroup]: from zabbix_cli.output.console import exit_err from zabbix_cli.output.prompts import str_prompt @@ -112,7 +110,7 @@ def parse_hosts_arg( app: StatefulApp, hostnames_or_ids: Optional[str], strict: bool = False, -) -> List[Host]: +) -> list[Host]: from zabbix_cli.output.console import exit_err from zabbix_cli.output.prompts import str_prompt @@ -136,7 +134,7 @@ def parse_templates_arg( template_names_or_ids: Optional[str], strict: bool = False, select_hosts: bool = False, -) -> List[Template]: +) -> list[Template]: from zabbix_cli.output.console import exit_err template_args = parse_list_arg(template_names_or_ids) @@ -159,7 +157,7 @@ def parse_templategroups_arg( tgroup_names_or_ids: str, strict: bool = False, select_templates: bool = False, -) -> List[TemplateGroup]: +) -> list[TemplateGroup]: tg_args = parse_list_arg(tgroup_names_or_ids) if not tg_args: exit_err("At least one template group name/ID is required.") @@ -207,8 +205,8 @@ def parse_path_arg(arg: str, must_exist: bool = False) -> Path: def get_hostgroup_hosts( - app: StatefulApp, hostgroups: List[HostGroup] | str -) -> List[Host]: + app: StatefulApp, hostgroups: list[HostGroup] | str +) -> list[Host]: """Get all hosts from a list of host groups. Args: @@ -221,8 +219,8 @@ def get_hostgroup_hosts( ) # Get all hosts from all host groups # Some hosts can be in multiple host groups - ensure no dupes - hosts: List[Host] = [] - seen: Set[str] = set() + hosts: list[Host] = [] + seen: set[str] = set() for hg in hostgroups: for host in hg.hosts: if host.host not in seen: @@ -236,7 +234,7 @@ def check_at_least_one_option_set(ctx: typer.Context) -> None: Useful for commands used to update resources, where all options are optional, but at least one is required to make a change.""" - optional_params: Set[str] = set() + optional_params: set[str] = set() for param in ctx.command.params: if param.required: continue diff --git a/zabbix_cli/utils/fs.py b/zabbix_cli/utils/fs.py index b1ce2452..f10fbbb5 100644 --- a/zabbix_cli/utils/fs.py +++ b/zabbix_cli/utils/fs.py @@ -5,9 +5,9 @@ import re import subprocess import sys +from collections.abc import Generator from contextlib import contextmanager from pathlib import Path -from typing import Generator from typing import Optional from zabbix_cli.exceptions import ZabbixCLIError diff --git a/zabbix_cli/utils/utils.py b/zabbix_cli/utils/utils.py index ad51177d..d016b9fb 100644 --- a/zabbix_cli/utils/utils.py +++ b/zabbix_cli/utils/utils.py @@ -6,16 +6,13 @@ from __future__ import annotations import re +from collections.abc import Iterable from datetime import datetime from datetime import timedelta from typing import Any -from typing import Dict from typing import Final -from typing import Iterable -from typing import List from typing import NamedTuple from typing import Optional -from typing import Tuple from typing import Union from zabbix_cli.exceptions import ZabbixCLIError @@ -50,7 +47,7 @@ def get_monitoring_status(code: Optional[str], with_code: bool = False) -> str: return _format_code(code, monitoring_status, with_code=with_code) -def get_maintenance_active_days(schedule: int | None) -> List[str]: +def get_maintenance_active_days(schedule: int | None) -> list[str]: """Get maintenance day of week from code.""" if schedule is None: return [] @@ -65,14 +62,14 @@ def get_maintenance_active_days(schedule: int | None) -> List[str]: } # Bitwise AND schedule with each DoW's bit mask # If the result is non-zero, the DoW is active - active_days: List[str] = [] + active_days: list[str] = [] for n, dow in days.items(): if schedule & n: active_days.append(dow) return active_days -def get_maintenance_active_months(schedule: int | None) -> List[str]: +def get_maintenance_active_months(schedule: int | None) -> list[str]: if schedule is None: return [] months = { @@ -91,7 +88,7 @@ def get_maintenance_active_months(schedule: int | None) -> List[str]: } # Bitwise AND schedule with each month's bit mask # If the result is non-zero, the month is active - active_months: List[str] = [] + active_months: list[str] = [] for n, month in months.items(): if schedule & n: active_months.append(month) @@ -100,7 +97,7 @@ def get_maintenance_active_months(schedule: int | None) -> List[str]: # NOTE: we could turn these into str Enums or Literals, # so that it's easier to type check the values -ACKNOWLEDGE_ACTION_BITMASK: Final[Dict[str, int]] = { +ACKNOWLEDGE_ACTION_BITMASK: Final[dict[str, int]] = { "close": 0b000000001, "acknowledge": 0b000000010, "message": 0b000000100, @@ -146,14 +143,14 @@ def get_acknowledge_action_value( return value -def get_acknowledge_actions(code: int) -> List[str]: +def get_acknowledge_actions(code: int) -> list[str]: """Get acknowledge actions from code. See: https://www.zabbix.com/documentation/current/en/manual/api/reference/event/acknowledge (action parameter) """ # Create reverse lookup for action bitmask acknowledge_actions = {v: k for k, v in ACKNOWLEDGE_ACTION_BITMASK.items()} - active_action: List[str] = [] + active_action: list[str] = [] for n, action in acknowledge_actions.items(): if code & n: active_action.append(action) @@ -191,7 +188,7 @@ class TimeUnit(NamedTuple): ] -def convert_time_to_interval(time: str) -> Tuple[datetime, datetime]: +def convert_time_to_interval(time: str) -> tuple[datetime, datetime]: """Convert time to an interval of datetimes. `time` is a string that specifies a duration of time in @@ -240,7 +237,7 @@ def convert_timestamp(ts: str) -> datetime: raise ZabbixCLIError(f"Invalid timestamp: {ts}") -def convert_timestamp_interval(time: str) -> Tuple[datetime, datetime]: +def convert_timestamp_interval(time: str) -> tuple[datetime, datetime]: """Convert timestamp interval to seconds. `time` is a string that specifies a timestamp interval in