diff --git a/pyproject.toml b/pyproject.toml index 9acfffb..69d0ad6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,21 +65,49 @@ exclude = ["/.github", "/tests", "/path"] [tool.hatch.build.targets.wheel] packages = ["zabbix_auto_config"] + +[tool.pyright] +pythonVersion = "3.9" + +[tool.ruff] +# Same as Black. +line-length = 88 +src = ["zabbix_auto_config"] +extend-exclude = ["zabbix_auto_config/__init__.py"] +extend-include = [ + "pyproject.toml", + "zabbix_auto_config/**/*.py", + "tests/**/*.py", +] + [tool.ruff.lint] extend-select = [ + "E", # pydecodestyle (errors) + "W", # pydecodestyle (warnings) "G", # flake8-logging-format "I", # isort "LOG", # flake8-logging "PLE1205", # pylint (too many logging args) "PLE1206", # pylint (too few logging args) "TID252", # flake8-tidy-imports (prefer absolute imports) + "C4", # flake8-comprehensions + "B", # flake8-bugbear ] +# 2. Avoid enforcing line-length violations (`E501`) +ignore = ["E501"] + +# 3. Avoid trying to fix flake8-bugbear (`B`) violations. +unfixable = ["B"] + +[tool.ruff.format] +# Enable auto-formatting for docstrings. +docstring-code-format = true + +[tool.ruff.lint.pydocstyle] +convention = "google" [tool.ruff.lint.isort] # Force one line per import to simplify diffing and merging force-single-line = true # Add annotations import to every file required-imports = ["from __future__ import annotations"] - -[tool.pyright] -pythonVersion = "3.9" diff --git a/tests/conftest.py b/tests/conftest.py index ca56f13..2819d90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,6 @@ import pytest import tomli - from zabbix_auto_config import models diff --git a/tests/test_config.py b/tests/test_config.py index cd3c23a..cf2822a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,14 +2,13 @@ import pytest import tomli +import zabbix_auto_config.models as models from hypothesis import given from hypothesis import settings from hypothesis import strategies as st from inline_snapshot import snapshot from pydantic import ValidationError -import zabbix_auto_config.models as models - def test_sample_config(sample_config: str): models.Settings(**tomli.loads(sample_config)) diff --git a/tests/test_errcount.py b/tests/test_errcount.py index 94c08ed..3e53e5b 100644 --- a/tests/test_errcount.py +++ b/tests/test_errcount.py @@ -6,7 +6,6 @@ from typing import Callable import pytest - from zabbix_auto_config.errcount import Error from zabbix_auto_config.errcount import RollingErrorCounter from zabbix_auto_config.errcount import get_td diff --git a/tests/test_failsafe.py b/tests/test_failsafe.py index d0639f4..62b234a 100644 --- a/tests/test_failsafe.py +++ b/tests/test_failsafe.py @@ -7,7 +7,6 @@ from unittest.mock import MagicMock import pytest - from zabbix_auto_config.exceptions import ZACException from zabbix_auto_config.failsafe import check_failsafe from zabbix_auto_config.failsafe import check_failsafe_ok_file diff --git a/tests/test_health.py b/tests/test_health.py index f85684e..18e6288 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -3,7 +3,6 @@ import datetime import pytest - from zabbix_auto_config.health import HealthFile from zabbix_auto_config.health import ProcessInfo from zabbix_auto_config.state import State diff --git a/tests/test_models.py b/tests/test_models.py index 7f1b282..bd963f6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -5,7 +5,6 @@ import pytest from pydantic import ValidationError - from zabbix_auto_config import models # NOTE: Do not test msg and ctx of Pydantic errors! diff --git a/tests/test_processing/test_sourcecollectorprocess.py b/tests/test_processing/test_sourcecollectorprocess.py index 77cc1f7..a3c73ea 100644 --- a/tests/test_processing/test_sourcecollectorprocess.py +++ b/tests/test_processing/test_sourcecollectorprocess.py @@ -5,7 +5,6 @@ from typing import List import pytest - from zabbix_auto_config.models import Host from zabbix_auto_config.models import SourceCollectorSettings from zabbix_auto_config.processing import SourceCollectorProcess diff --git a/tests/test_processing/test_zabbixupdater.py b/tests/test_processing/test_zabbixupdater.py index c4e1ee9..0da5bbe 100644 --- a/tests/test_processing/test_zabbixupdater.py +++ b/tests/test_processing/test_zabbixupdater.py @@ -7,15 +7,15 @@ import pytest from httpx import ConnectTimeout from httpx import ReadTimeout - -from tests.conftest import MockZabbixAPI -from tests.conftest import PicklableMock from zabbix_auto_config import exceptions from zabbix_auto_config.models import Settings from zabbix_auto_config.models import ZabbixSettings from zabbix_auto_config.processing import ZabbixUpdater from zabbix_auto_config.state import get_manager +from tests.conftest import MockZabbixAPI +from tests.conftest import PicklableMock + def raises_connect_timeout(*args, **kwargs): raise ConnectTimeout("connect timeout") diff --git a/tests/test_state.py b/tests/test_state.py index 28a6351..3935589 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -5,7 +5,6 @@ import pytest from inline_snapshot import snapshot - from zabbix_auto_config.exceptions import ZACException from zabbix_auto_config.processing import BaseProcess from zabbix_auto_config.state import State diff --git a/tests/test_utils.py b/tests/test_utils.py index a916a74..9227db3 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,7 +17,6 @@ from hypothesis import settings from hypothesis import strategies as st from pytest import LogCaptureFixture - from zabbix_auto_config import utils from zabbix_auto_config.pyzabbix.types import HostTag diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 5d73bef..d1ed52c 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -458,7 +458,7 @@ def merge(self, other: "Host") -> None: self.hostname, ) # TODO: Do something different? Is alphabetically first "good enough"? It will be consistent at least. - self.proxy_pattern = sorted(list(proxy_patterns))[0] + self.proxy_pattern = sorted(proxy_patterns)[0] elif len(proxy_patterns) == 1: self.proxy_pattern = proxy_patterns.pop() diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 2b2768f..079fd0b 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -326,7 +326,7 @@ def collect(self) -> None: f"Collected object is not a Host object: {host!r}. Type: {type(host)}" ) - host.sources = set([self.name]) + host.sources = {self.name} valid_hosts.append(host) # Add source hosts to queue @@ -381,7 +381,7 @@ def __init__( # TODO: Test connection? Cursor? except psycopg2.OperationalError as e: logging.error("Unable to connect to database.") - raise ZACException(*e.args) + raise ZACException(*e.args) from e self.source_hosts_queues = source_hosts_queues for source_hosts_queue in self.source_hosts_queues: @@ -696,7 +696,7 @@ def __init__( # TODO: Test connection? Cursor? except psycopg2.OperationalError as e: logging.error("Unable to connect to database. Process exiting with error") - raise ZACException(*e.args) + raise ZACException(*e.args) from e self.config = settings.zabbix self.settings = settings @@ -732,15 +732,15 @@ def login(self) -> None: self.api.login(self.config.username, self.config.password) except httpx.ConnectError as e: logging.error("Error while connecting to Zabbix: %s", self.config.url) - raise ZACException(*e.args) + raise ZACException(*e.args) from e except httpx.TimeoutException as e: logging.error( "Timed out while connecting to Zabbix API: %s", self.config.url ) - raise ZACException(*e.args) + raise ZACException(*e.args) from e except (ZabbixAPIException, httpx.HTTPError) as e: logging.error("Unable to login to Zabbix API: %s", str(e)) - raise ZACException(*e.args) + raise ZACException(*e.args) from e def work(self) -> None: start_time = time.time() @@ -778,7 +778,7 @@ def get_hostgroups(self, name: Optional[str] = None) -> List[HostGroup]: names = [name] if name else [] hostgroups = self.api.get_hostgroups(*names) except ZabbixAPIException as e: - raise ZACException("Error when fetching hostgroups: %s", e) + raise ZACException("Error when fetching hostgroups: %s", e) from e return hostgroups @@ -1250,7 +1250,7 @@ def do_update(self) -> None: zabbix_managed_hosts: List[Host] = [] zabbix_manual_hosts: List[Host] = [] - for hostname, host in zabbix_hosts.items(): + for host in zabbix_hosts.values(): if self.stop_event.is_set(): logging.debug("Told to stop. Breaking") break @@ -1688,7 +1688,7 @@ def create_hostgroup(self, hostgroup_name: str) -> Optional[str]: def create_extra_hostgroups(self, existing_hostgroups: List[HostGroup]) -> None: """Creates additonal host groups based on the prefixes specified in the config file. These host groups are not assigned hosts by ZAC.""" - hostgroup_names = set(h.name for h in existing_hostgroups) + hostgroup_names = {h.name for h in existing_hostgroups} for prefix in self.config.extra_siteadmin_hostgroup_prefixes: mapping = utils.mapping_values_with_prefix( @@ -1730,7 +1730,7 @@ def create_templategroups(self, existing_hostgroups: List[HostGroup]) -> None: # The prefix is determined by the separator defined in the config file. # If we use the template group prefix `Templates-`, we go from # `Siteadmin-bob-hosts` to `Templates-bob-hosts`. - tgroups = set( + tgroups = { utils.with_prefix( tg, self.config.templategroup_prefix, @@ -1739,7 +1739,7 @@ def create_templategroups(self, existing_hostgroups: List[HostGroup]) -> None: for tg in itertools.chain.from_iterable( self.siteadmin_hostgroup_map.values() ) - ) + } if compat.templategroups_supported(self.zabbix_version): logging.debug( "Zabbix version is %s. Will create template groups.", @@ -1760,7 +1760,7 @@ def _create_templategroups(self, tgroups: Set[str]) -> None: tgroups: A set of template group names to create. """ res = self.api.get_templategroups() - existing_tgroups = set(tg.name for tg in res) + existing_tgroups = {tg.name for tg in res} for tgroup in tgroups: if tgroup in existing_tgroups: continue @@ -1777,7 +1777,7 @@ def _create_templategroups_pre_62_compat( Args: tgroups: A set of host group names to create. """ - existing_hgroup_names = set(h.name for h in existing_hostgroups) + existing_hgroup_names = {h.name for h in existing_hostgroups} for tgroup in tgroups: if tgroup in existing_hgroup_names: continue @@ -1846,7 +1846,7 @@ def do_update(self) -> None: # Determine host groups to sync for host # Sync host groups derived from its properties, siteadmins, sources, etc. - synced_hostgroup_names = set([self.config.hostgroup_all]) + synced_hostgroup_names = {self.config.hostgroup_all} for prop in db_host.properties: if prop in self.property_hostgroup_map: synced_hostgroup_names.update(self.property_hostgroup_map[prop]) diff --git a/zabbix_auto_config/pyzabbix/client.py b/zabbix_auto_config/pyzabbix/client.py index f46d4e2..c4bf4f5 100644 --- a/zabbix_auto_config/pyzabbix/client.py +++ b/zabbix_auto_config/pyzabbix/client.py @@ -183,7 +183,7 @@ def login( # Without an API connection, we cannot determine # the user parameter name to use when logging in. try: - self.version # property + self.version # property # noqa: B018 except ZabbixAPIRequestError as e: raise ZabbixAPIException( f"Failed to connect to Zabbix API at {self.url}" @@ -386,7 +386,7 @@ def get_hostgroups( search_params: ParamsType = {} if "*" in names_or_ids: - names_or_ids = tuple() + names_or_ids = () if names_or_ids: for name_or_id in names_or_ids: @@ -544,7 +544,7 @@ def get_templategroups( search_params: ParamsType = {} if "*" in names_or_ids: - names_or_ids = tuple() + names_or_ids = () if names_or_ids: for name_or_id in names_or_ids: @@ -846,7 +846,7 @@ def update_host( except ZabbixAPIException as e: raise ZabbixAPICallError( f"Failed to update host {host.host} ({host.hostid}): {e}" - ) + ) from e def delete_host(self, host_id: str) -> None: """Deletes a host.""" @@ -1043,7 +1043,7 @@ def get_usergroups( search_params: ParamsType = {} if "*" in names: - names = tuple() + names = () if search: params["searchByAny"] = True # Union search (default is intersection) params["searchWildcardsEnabled"] = True @@ -1128,10 +1128,10 @@ def _update_usergroup_users( new_userids = list(set(current_userids + ids_update)) if self.version.release >= (6, 0, 0): - params["users"] = {"userid": uid for uid in new_userids} + params["users"] = [{"userid": uid} for uid in new_userids] else: params["userids"] = new_userids - self.usergroup.update(usrgrpid=usergroup.usrgrpid, userids=new_userids) + self.usergroup.update(**params) def update_usergroup_rights( self, @@ -1471,7 +1471,7 @@ def get_templates( # TODO: refactor this along with other methods that take names or ids (or wildcards) if "*" in template_names_or_ids: - template_names_or_ids = tuple() + template_names_or_ids = () for name_or_id in template_names_or_ids: name_or_id = name_or_id.strip() @@ -1961,7 +1961,7 @@ def create_maintenance( params["hostids"] = [h.hostid for h in hosts] if hostgroups: if self.version.release >= (6, 0, 0): - params["groups"] = {"groupid": hg.groupid for hg in hostgroups} + params["groups"] = [{"groupid": hg.groupid} for hg in hostgroups] else: params["groupids"] = [hg.groupid for hg in hostgroups] if data_collection: @@ -2152,29 +2152,27 @@ def __getattr__(self, attr: str): return ZabbixAPIObjectClass(attr, self) -WRITE_OPERATIONS = set( - [ - "create", - "delete", - "update", - "massadd", - "massupdate", - "massremove", - "push", # history - "clear", # history - "acknowledge", # event - "import", # configuration - "propagate", # hostgroup, templategroup - "replacehostinterfaces", # hostinterface - "copy", # discoveryrule - "execute", # script - "resettotp", # user - "unblock", # user - "createglobal", # macro - "deleteglobal", # macro - "updateglobal", # macro - ] -) +WRITE_OPERATIONS = { + "create", + "delete", + "update", + "massadd", + "massupdate", + "massremove", + "push", # history + "clear", # history + "acknowledge", # event + "import", # configuration + "propagate", # hostgroup, templategroup + "replacehostinterfaces", # hostinterface + "copy", # discoveryrule + "execute", # script + "resettotp", # user + "unblock", # user + "createglobal", # macro + "deleteglobal", # macro + "updateglobal", # macro +} class ZabbixAPIObjectClass: diff --git a/zabbix_auto_config/pyzabbix/enums.py b/zabbix_auto_config/pyzabbix/enums.py index dd3acf0..1f4ac59 100644 --- a/zabbix_auto_config/pyzabbix/enums.py +++ b/zabbix_auto_config/pyzabbix/enums.py @@ -106,7 +106,7 @@ def get_port(self: InterfaceType) -> str: try: return PORTS[self] except KeyError: - raise ZACException(f"Unknown interface type: {self}") + raise ZACException(f"Unknown interface type: {self}") from None class SNMPSecurityLevel(IntEnum): diff --git a/zabbix_auto_config/pyzabbix/utils.py b/zabbix_auto_config/pyzabbix/utils.py index 90a0749..1b24295 100644 --- a/zabbix_auto_config/pyzabbix/utils.py +++ b/zabbix_auto_config/pyzabbix/utils.py @@ -21,8 +21,8 @@ def get_random_proxy(client: ZabbixAPI, pattern: Optional[str] = None) -> Proxy: if pattern: try: re_pattern = re.compile(pattern) - except re.error: - raise ZabbixAPICallError(f"Invalid proxy regex pattern: {pattern!r}") + except re.error as e: + raise ZabbixAPICallError(f"Invalid proxy regex pattern: {pattern!r}") from e proxies = [proxy for proxy in proxies if re_pattern.match(proxy.name)] if not proxies: raise ZabbixNotFoundError(f"No proxies matching pattern {pattern!r}")