From 0a2c30edde590fc875f6fea8c83025cdee546e23 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 20 Jul 2023 11:46:48 +0200 Subject: [PATCH 1/3] chore: add pylint linter --- Makefile | 1 + pyproject.toml | 17 +++++++++++++++++ setup.py | 1 + 3 files changed, 19 insertions(+) diff --git a/Makefile b/Makefile index 338c547..b63a6ff 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ venv: venv/bin/pip install -e .[docs,test] lint: venv + venv/bin/pylint hcloud venv/bin/mypy hcloud test: venv diff --git a/pyproject.toml b/pyproject.toml index beb8fd8..f3e2661 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,3 +12,20 @@ source = ["hcloud"] [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" + +[tool.pylint.main] +py-version = "3.8" +recursive = true +jobs = 0 + +[tool.pylint.reports] +output-format = "colorized" + +[tool.pylint.basic] +good-names = ["i", "j", "k", "ex", "_", "ip", "id"] + +[tool.pylint."messages control"] +disable = [ + "line-too-long", + "missing-module-docstring", +] diff --git a/setup.py b/setup.py index c52a9cf..b4a20b6 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ ], "test": [ "coverage>=7.3,<7.4", + "pylint>=2.17.4,<2.18", "pytest>=7.4,<7.5", "mypy>=1.5,<1.6", "types-python-dateutil", From 4c1efa2fa7d1cdf62b4721237adad8f48169f789 Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 8 Aug 2023 14:31:56 +0200 Subject: [PATCH 2/3] refactor: fix linting errors --- hcloud/_client.py | 2 +- hcloud/actions/client.py | 1 + hcloud/core/client.py | 1 + hcloud/core/domain.py | 16 +++++++++++++--- hcloud/firewalls/client.py | 25 ++++++++++++++----------- hcloud/firewalls/domain.py | 6 ++++++ hcloud/floating_ips/client.py | 1 + hcloud/hcloud.py | 1 + hcloud/helpers/labels.py | 22 ++++++++++++++-------- hcloud/images/client.py | 1 + hcloud/images/domain.py | 1 + hcloud/load_balancers/client.py | 1 + hcloud/load_balancers/domain.py | 8 ++++++++ hcloud/networks/client.py | 1 + hcloud/primary_ips/client.py | 1 + hcloud/servers/client.py | 2 ++ hcloud/servers/domain.py | 1 + hcloud/volumes/client.py | 3 ++- pyproject.toml | 9 +++++++++ 19 files changed, 79 insertions(+), 24 deletions(-) diff --git a/hcloud/_client.py b/hcloud/_client.py index db28fcd..a12f204 100644 --- a/hcloud/_client.py +++ b/hcloud/_client.py @@ -42,7 +42,7 @@ def __init__( poll_interval: int = 1, timeout: float | tuple[float, float] | None = None, ): - """Create an new Client instance + """Create a new Client instance :param token: Hetzner Cloud API token :param api_endpoint: Hetzner Cloud API endpoint diff --git a/hcloud/actions/client.py b/hcloud/actions/client.py index 2433bcd..6ac87cb 100644 --- a/hcloud/actions/client.py +++ b/hcloud/actions/client.py @@ -27,6 +27,7 @@ def wait_until_finished(self, max_retries: int = 100) -> None: while self.status == Action.STATUS_RUNNING: if max_retries > 0: self.reload() + # pylint: disable=protected-access time.sleep(self._client._client.poll_interval) max_retries = max_retries - 1 else: diff --git a/hcloud/core/client.py b/hcloud/core/client.py index 16c37e5..1d4edfd 100644 --- a/hcloud/core/client.py +++ b/hcloud/core/client.py @@ -45,6 +45,7 @@ def _iter_pages( # type: ignore[no-untyped-def] def _get_first_by(self, **kwargs): # type: ignore[no-untyped-def] assert hasattr(self, "get_list") + # pylint: disable=no-member entities, _ = self.get_list(**kwargs) return entities[0] if entities else None diff --git a/hcloud/core/domain.py b/hcloud/core/domain.py index 21aed34..2a34b34 100644 --- a/hcloud/core/domain.py +++ b/hcloud/core/domain.py @@ -6,6 +6,11 @@ class BaseDomain: @classmethod def from_dict(cls, data: dict): # type: ignore[no-untyped-def] + """ + Build the domain object from dict. + + :return: _description_ + """ supported_data = {k: v for k, v in data.items() if k in cls.__slots__} return cls(**supported_data) @@ -22,12 +27,14 @@ class DomainIdentityMixin: @property def id_or_name(self) -> int | str: + """ + Return the first defined value, and fails if none is defined. + """ if self.id is not None: return self.id - elif self.name is not None: + if self.name is not None: return self.name - else: - raise ValueError("id or name must be set") + raise ValueError("id or name must be set") class Pagination(BaseDomain): @@ -65,6 +72,9 @@ def __init__(self, pagination: Pagination | None = None): @classmethod def parse_meta(cls, response: dict) -> Meta | None: + """ + If present, extract the meta details from the response and return a meta object. + """ meta = None if response and "meta" in response: meta = cls() diff --git a/hcloud/firewalls/client.py b/hcloud/firewalls/client.py index b9b341b..b77c066 100644 --- a/hcloud/firewalls/client.py +++ b/hcloud/firewalls/client.py @@ -39,29 +39,32 @@ def __init__(self, client: FirewallsClient, data: dict, complete: bool = True): applied_to = data.get("applied_to", []) if applied_to: + # pylint: disable=import-outside-toplevel from ..servers import BoundServer - ats = [] - for a in applied_to: - if a["type"] == FirewallResource.TYPE_SERVER: - ats.append( + data_applied_to = [] + for firewall_resource in applied_to: + if firewall_resource["type"] == FirewallResource.TYPE_SERVER: + data_applied_to.append( FirewallResource( - type=a["type"], + type=firewall_resource["type"], server=BoundServer( - client._client.servers, a["server"], complete=False + client._client.servers, + firewall_resource["server"], + complete=False, ), ) ) - elif a["type"] == FirewallResource.TYPE_LABEL_SELECTOR: - ats.append( + elif firewall_resource["type"] == FirewallResource.TYPE_LABEL_SELECTOR: + data_applied_to.append( FirewallResource( - type=a["type"], + type=firewall_resource["type"], label_selector=FirewallResourceLabelSelector( - selector=a["label_selector"]["selector"] + selector=firewall_resource["label_selector"]["selector"] ), ) ) - data["applied_to"] = ats + data["applied_to"] = data_applied_to super().__init__(client, data, complete) diff --git a/hcloud/firewalls/domain.py b/hcloud/firewalls/domain.py index f9c7368..44bd7c9 100644 --- a/hcloud/firewalls/domain.py +++ b/hcloud/firewalls/domain.py @@ -108,6 +108,9 @@ def __init__( self.description = description def to_payload(self) -> dict[str, Any]: + """ + Generates the request payload from this domain object. + """ payload: dict[str, Any] = { "direction": self.direction, "protocol": self.protocol, @@ -151,6 +154,9 @@ def __init__( self.label_selector = label_selector def to_payload(self) -> dict[str, Any]: + """ + Generates the request payload from this domain object. + """ payload: dict[str, Any] = {"type": self.type} if self.server is not None: payload["server"] = {"id": self.server.id} diff --git a/hcloud/floating_ips/client.py b/hcloud/floating_ips/client.py index 49d7c8e..31e5477 100644 --- a/hcloud/floating_ips/client.py +++ b/hcloud/floating_ips/client.py @@ -19,6 +19,7 @@ class BoundFloatingIP(BoundModelBase): model = FloatingIP def __init__(self, client: FloatingIPsClient, data: dict, complete: bool = True): + # pylint: disable=import-outside-toplevel from ..servers import BoundServer server = data.get("server") diff --git a/hcloud/hcloud.py b/hcloud/hcloud.py index df67a5b..9de1cfe 100644 --- a/hcloud/hcloud.py +++ b/hcloud/hcloud.py @@ -8,4 +8,5 @@ stacklevel=2, ) +# pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import from ._client import * # noqa diff --git a/hcloud/helpers/labels.py b/hcloud/helpers/labels.py index d5af8bc..3604157 100644 --- a/hcloud/helpers/labels.py +++ b/hcloud/helpers/labels.py @@ -18,10 +18,10 @@ def validate(labels: dict[str, str]) -> bool: :return: bool """ - for k, v in labels.items(): - if LabelValidator.KEY_REGEX.match(k) is None: + for key, value in labels.items(): + if LabelValidator.KEY_REGEX.match(key) is None: return False - if LabelValidator.VALUE_REGEX.match(v) is None: + if LabelValidator.VALUE_REGEX.match(value) is None: return False return True @@ -32,9 +32,15 @@ def validate_verbose(labels: dict[str, str]) -> tuple[bool, str]: :return: bool, str """ - for k, v in labels.items(): - if LabelValidator.KEY_REGEX.match(k) is None: - return False, f"label key {k} is not correctly formatted" - if LabelValidator.VALUE_REGEX.match(v) is None: - return False, f"label value {v} (key: {k}) is not correctly formatted" + for key, value in labels.items(): + if LabelValidator.KEY_REGEX.match(key) is None: + return ( + False, + f"label key {key} is not correctly formatted", + ) + if LabelValidator.VALUE_REGEX.match(value) is None: + return ( + False, + f"label value {value} (key: {key}) is not correctly formatted", + ) return True, "" diff --git a/hcloud/images/client.py b/hcloud/images/client.py index dee910f..4d8cd10 100644 --- a/hcloud/images/client.py +++ b/hcloud/images/client.py @@ -16,6 +16,7 @@ class BoundImage(BoundModelBase): model = Image def __init__(self, client: ImagesClient, data: dict): + # pylint: disable=import-outside-toplevel from ..servers import BoundServer created_from = data.get("created_from") diff --git a/hcloud/images/domain.py b/hcloud/images/domain.py index 0f4add4..37d6d9f 100644 --- a/hcloud/images/domain.py +++ b/hcloud/images/domain.py @@ -71,6 +71,7 @@ class Image(BaseDomain, DomainIdentityMixin): "deprecated", ) + # pylint: disable=too-many-locals def __init__( self, id: int | None = None, diff --git a/hcloud/load_balancers/client.py b/hcloud/load_balancers/client.py index 72325f1..301240b 100644 --- a/hcloud/load_balancers/client.py +++ b/hcloud/load_balancers/client.py @@ -39,6 +39,7 @@ class BoundLoadBalancer(BoundModelBase): model = LoadBalancer + # pylint: disable=too-many-branches,too-many-locals def __init__(self, client: LoadBalancersClient, data: dict, complete: bool = True): algorithm = data.get("algorithm") if algorithm: diff --git a/hcloud/load_balancers/domain.py b/hcloud/load_balancers/domain.py index c5810d6..212bbdc 100644 --- a/hcloud/load_balancers/domain.py +++ b/hcloud/load_balancers/domain.py @@ -69,6 +69,7 @@ class LoadBalancer(BaseDomain): "included_traffic", ) + # pylint: disable=too-many-locals def __init__( self, id: int, @@ -137,7 +138,11 @@ def __init__( self.health_check = health_check self.http = http + # pylint: disable=too-many-branches def to_payload(self) -> dict[str, Any]: + """ + Generates the request payload from this domain object. + """ payload: dict[str, Any] = {} if self.protocol is not None: @@ -334,6 +339,9 @@ def __init__( self.health_status = health_status def to_payload(self) -> dict[str, Any]: + """ + Generates the request payload from this domain object. + """ payload: dict[str, Any] = { "type": self.type, } diff --git a/hcloud/networks/client.py b/hcloud/networks/client.py index 36eaaa4..f5894e7 100644 --- a/hcloud/networks/client.py +++ b/hcloud/networks/client.py @@ -26,6 +26,7 @@ def __init__(self, client: NetworksClient, data: dict, complete: bool = True): routes = [NetworkRoute.from_dict(route) for route in routes] data["routes"] = routes + # pylint: disable=import-outside-toplevel from ..servers import BoundServer servers = data.get("servers", []) diff --git a/hcloud/primary_ips/client.py b/hcloud/primary_ips/client.py index acd2f69..a55dfc7 100644 --- a/hcloud/primary_ips/client.py +++ b/hcloud/primary_ips/client.py @@ -17,6 +17,7 @@ class BoundPrimaryIP(BoundModelBase): model = PrimaryIP def __init__(self, client: PrimaryIPsClient, data: dict, complete: bool = True): + # pylint: disable=import-outside-toplevel from ..datacenters import BoundDatacenter datacenter = data.get("datacenter", {}) diff --git a/hcloud/servers/client.py b/hcloud/servers/client.py index d19180c..433c537 100644 --- a/hcloud/servers/client.py +++ b/hcloud/servers/client.py @@ -47,6 +47,7 @@ class BoundServer(BoundModelBase): model = Server + # pylint: disable=too-many-locals def __init__(self, client: ServersClient, data: dict, complete: bool = True): datacenter = data.get("datacenter") if datacenter is not None: @@ -540,6 +541,7 @@ def get_by_name(self, name: str) -> BoundServer | None: """ return self._get_first_by(name=name) + # pylint: disable=too-many-branches,too-many-locals def create( self, name: str, diff --git a/hcloud/servers/domain.py b/hcloud/servers/domain.py index 60cc745..2d55fd3 100644 --- a/hcloud/servers/domain.py +++ b/hcloud/servers/domain.py @@ -104,6 +104,7 @@ class Server(BaseDomain): "placement_group", ) + # pylint: disable=too-many-locals def __init__( self, id: int, diff --git a/hcloud/volumes/client.py b/hcloud/volumes/client.py index 9017ff4..cb9e883 100644 --- a/hcloud/volumes/client.py +++ b/hcloud/volumes/client.py @@ -23,6 +23,7 @@ def __init__(self, client: VolumesClient, data: dict, complete: bool = True): if location is not None: data["location"] = BoundLocation(client._client.locations, location) + # pylint: disable=import-outside-toplevel from ..servers import BoundServer server = data.get("server") @@ -254,7 +255,7 @@ def create( if size <= 0: raise ValueError("size must be greater than 0") - if not (bool(location) ^ bool(server)): + if not bool(location) ^ bool(server): raise ValueError("only one of server or location must be provided") data: dict[str, Any] = {"name": name, "size": size} diff --git a/pyproject.toml b/pyproject.toml index f3e2661..2b76834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,15 @@ good-names = ["i", "j", "k", "ex", "_", "ip", "id"] [tool.pylint."messages control"] disable = [ + "fixme", "line-too-long", + "missing-class-docstring", "missing-module-docstring", + "redefined-builtin", + # Consider disabling line-by-line + "too-few-public-methods", + "too-many-public-methods", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-lines", ] From 1a5546ad4c9202e36180155d22020c5ce338ba81 Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 5 Sep 2023 23:04:56 +0200 Subject: [PATCH 3/3] docs: fix incomplete docstring --- hcloud/core/domain.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hcloud/core/domain.py b/hcloud/core/domain.py index 2a34b34..692f748 100644 --- a/hcloud/core/domain.py +++ b/hcloud/core/domain.py @@ -7,9 +7,7 @@ class BaseDomain: @classmethod def from_dict(cls, data: dict): # type: ignore[no-untyped-def] """ - Build the domain object from dict. - - :return: _description_ + Build the domain object from the data dict. """ supported_data = {k: v for k, v in data.items() if k in cls.__slots__} return cls(**supported_data)