diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bf8f7b..489831f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,26 @@ name: Continuous Integration on: + push: + branches: [master] pull_request: - types: [opened, reopened] + branches: [master] jobs: unit_tests: name: Run Unit Tests runs-on: ubuntu-24.04 + permissions: + contents: read steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Install dependencies - run: sudo apt-get update -y && sudo apt-get install -y python3 + run: | + sudo apt-get update -y && sudo apt-get install -y python3 + pip install flake8 pyright black pyflakes + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install cfbs run: pip install cfbs - name: Check the status with cfbs @@ -22,3 +29,5 @@ jobs: run: cfbs validate - name: Check the formatting run: cfbs --check pretty ./cfbs.json + - name: Linting + run: ./.github/workflows/linting.sh diff --git a/.github/workflows/linting.sh b/.github/workflows/linting.sh new file mode 100755 index 0000000..29598e0 --- /dev/null +++ b/.github/workflows/linting.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +echo "Running flake8" +flake8 . --ignore=E203,W503,E722,E731 --max-complexity=100 --max-line-length=160 + +echo "Running pyright" +pyright . + +shopt -s globstar +echo "Running black" +black --check . + +echo "Running pyflakes" +pyflakes . \ No newline at end of file diff --git a/examples/git-using-lib/git_using_lib.py b/examples/git-using-lib/git_using_lib.py index 07747b1..96c742b 100644 --- a/examples/git-using-lib/git_using_lib.py +++ b/examples/git-using-lib/git_using_lib.py @@ -10,7 +10,7 @@ def validate_promise(self, promiser, attributes, metadata): if not promiser.startswith("/"): raise ValidationError(f"File path '{promiser}' must be absolute") if "repository" not in attributes: - raise ValidationError(f"Attribute 'repository' is required") + raise ValidationError("Attribute 'repository' is required") def evaluate_promise(self, promiser, attributes, metadata): url = attributes["repository"] diff --git a/examples/gpg/gpg.py b/examples/gpg/gpg.py index 333b992..abe8039 100644 --- a/examples/gpg/gpg.py +++ b/examples/gpg/gpg.py @@ -42,8 +42,7 @@ """ import json -from subprocess import Popen, PIPE -import sys +from subprocess import Popen, PIPE, TimeoutExpired from cfengine_module_library import PromiseModule, ValidationError, Result @@ -109,9 +108,9 @@ def validate_promise(self, promiser, attributes, metadata): raise ValidationError( f"Promiser '{promiser}' for 'gpg_keys' promise must be an absolute path" ) - if not "keylist" in attributes: + if "keylist" not in attributes: raise ValidationError( - f"Required attribute 'keylist' missing for 'gpg_keys' promise" + "Required attribute 'keylist' missing for 'gpg_keys' promise" ) def evaluate_promise(self, promiser, attributes, metadata): diff --git a/examples/rss/rss.py b/examples/rss/rss.py index 5de683f..bc93de1 100755 --- a/examples/rss/rss.py +++ b/examples/rss/rss.py @@ -1,4 +1,7 @@ -import requests, html, re, os, random +import requests +import re +import os +import random import xml.etree.ElementTree as ET from cfengine_module_library import PromiseModule, ValidationError, Result @@ -42,7 +45,7 @@ def validate_promise(self, promiser, attributes, metadata): # check that attribute select has a valid type if type(select) is not str: raise ValidationError( - f"Invalid type for attribute select: expected string" + "Invalid type for attribute select: expected string" ) # check that attribute select has a valid value @@ -159,10 +162,10 @@ def _write_promiser(self, item, promiser): return Result.NOT_KEPT def _is_win_file(self, path): - return re.search(r"^[a-zA-Z]:\\[\\\S|*\S]?.*$", path) != None + return re.search(r"^[a-zA-Z]:\\[\\\S|*\S]?.*$", path) is not None def _is_unix_file(self, path): - return re.search(r"^(/[^/ ]*)+/?$", path) != None + return re.search(r"^(/[^/ ]*)+/?$", path) is not None def _is_url(self, path): return ( @@ -170,7 +173,7 @@ def _is_url(self, path): r"^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", path, ) - != None + is not None ) diff --git a/examples/site-up/site_up.py b/examples/site-up/site_up.py index ec7775d..dc05e81 100644 --- a/examples/site-up/site_up.py +++ b/examples/site-up/site_up.py @@ -31,7 +31,7 @@ def evaluate_promise(self, promiser, attributes, metadata): error = None try: - code = urllib.request.urlopen(url, context=ssl_ctx).getcode() + urllib.request.urlopen(url, context=ssl_ctx).getcode() self.log_verbose(f"Site '{url}' is UP!") return Result.KEPT except urllib.error.HTTPError as e: diff --git a/libraries/python/cfengine_module_library.py b/libraries/python/cfengine_module_library.py index 3e8ddb4..da6a28e 100644 --- a/libraries/python/cfengine_module_library.py +++ b/libraries/python/cfengine_module_library.py @@ -226,6 +226,8 @@ def _handle_request(self, request): "debug", ] + promiser = None + attributes = {} if operation in ["validate_promise", "evaluate_promise"]: promiser = request["promiser"] attributes = request.get("attributes", {}) @@ -404,7 +406,7 @@ def _handle_evaluate(self, promiser, attributes, request): assert results is not None # Most likely someone forgot to return something # evaluate_promise should return either a result or a (result, result_classes) pair - if type(results) == str: + if isinstance(results, str): self._result = results else: assert len(results) == 2 diff --git a/promise-types/ansible/ansible_promise.py b/promise-types/ansible/ansible_promise.py index 5f9e39a..4ee05c3 100644 --- a/promise-types/ansible/ansible_promise.py +++ b/promise-types/ansible/ansible_promise.py @@ -1,74 +1,64 @@ import os from typing import Dict, Tuple, List - from cfengine_module_library import PromiseModule, ValidationError, Result -try: - from ansible import context - from ansible.cli import CLI - from ansible.executor.playbook_executor import PlaybookExecutor - from ansible.inventory.manager import InventoryManager - from ansible.module_utils.common.collections import ImmutableDict - from ansible.parsing.dataloader import DataLoader - from ansible.plugins.callback import CallbackBase - from ansible.vars.manager import VariableManager - from ansible.plugins.loader import init_plugin_loader - - ANSIBLE_AVAILABLE = True -except ImportError: - ANSIBLE_AVAILABLE = False - - -if ANSIBLE_AVAILABLE: - - class CallbackModule(CallbackBase): - CALLBACK_VERSION = 1.0 - CALLBACK_TYPE = "stdout" - CALLBACK_NAME = "cfengine" - - def __init__(self, *args, promise=None, **kw): - self.promise = promise - self.hosts = set() - self.changed = False - super(CallbackModule, self).__init__(*args, **kw) - - def v2_runner_on_start(self, host, task): - self.hosts.add(str(host)) - self.promise.log_verbose( - "Task '" + task.name + "' started on '" + str(host) + "'" - ) +import ansible.context as context +from ansible.cli import CLI +from ansible.executor.playbook_executor import PlaybookExecutor +from ansible.inventory.manager import InventoryManager +from ansible.module_utils.common.collections import ImmutableDict +from ansible.parsing.dataloader import DataLoader +from ansible.plugins.callback import CallbackBase +from ansible.vars.manager import VariableManager +from ansible.plugins.loader import init_plugin_loader + + +class CallbackModule(CallbackBase): + CALLBACK_VERSION = 1.0 + CALLBACK_TYPE = "stdout" + CALLBACK_NAME = "cfengine" + + def __init__(self, *args, promise, **kw): + self.promise = promise + self.hosts = set() + self.changed = False + super(CallbackModule, self).__init__(*args, **kw) + + def v2_runner_on_start(self, host, task): + self.hosts.add(str(host)) + self.promise.log_verbose( + "Task '" + task.name + "' started on '" + str(host) + "'" + ) - def v2_runner_on_ok(self, result): - is_changed = result.is_changed() - if is_changed: - self.changed = True - self.promise.log_info( - "Task '" + result.task_name + "' successfully changed" - ) - else: - self.promise.log_verbose( - "Task '" + result.task_name + "' didn't change" - ) + def v2_runner_on_ok(self, result): + is_changed = result.is_changed() + if is_changed: + self.changed = True + self.promise.log_info( + "Task '" + result.task_name + "' successfully changed" + ) + else: + self.promise.log_verbose("Task '" + result.task_name + "' didn't change") - def v2_runner_on_failed(self, result, **_): - self.promise.log_error("Task '" + result.task_name + "' failed") + def v2_runner_on_failed(self, result, ignore_errors=False): + self.promise.log_error("Task '" + result.task_name + "' failed") - def v2_runner_on_skipped(self, result): - self.promise.log_error("Task '" + result.task_name + "' was skipped") + def v2_runner_on_skipped(self, result): + self.promise.log_error("Task '" + result.task_name + "' was skipped") - def v2_playbook_on_stats(self, stats): - for host in self.hosts: - summary_dict = stats.summarize(host) - summary = " ".join( - "%s=%s" % (k, v) for k, v in summary_dict.items() if v > 0 + def v2_playbook_on_stats(self, stats): + for host in self.hosts: + summary_dict = stats.summarize(host) + summary = " ".join( + "%s=%s" % (k, v) for k, v in summary_dict.items() if v > 0 + ) + if summary_dict.get("unreachable"): + self.promise.log_error("Host '" + host + "' is unreachable") + elif summary: + self.promise.log_verbose( + "Summary of the tasks for '" + host + "' is: " + summary ) - if summary_dict.get("unreachable"): - self.promise.log_error("Host '" + host + "' is unreachable") - else: - summary and self.promise.log_verbose( - "Summary of the tasks for '" + host + "' is: " + summary - ) class AnsiblePromiseTypeModule(PromiseModule): @@ -100,8 +90,7 @@ def prepare_promiser_and_attributes(self, promiser, attributes): return (safe_promiser, attributes) def validate_promise(self, promiser: str, attributes: Dict, metadata: Dict): - if not ANSIBLE_AVAILABLE: - raise ValidationError("Ansible Python module not available") + return def evaluate_promise( self, safe_promiser: str, attributes: Dict, metadata: Dict @@ -153,7 +142,7 @@ def evaluate_promise( passwords={}, ) callback = CallbackModule(promise=self) - pbex._tqm._stdout_callback = callback + pbex._tqm._stdout_callback = callback # type: ignore exit_code = pbex.run() if exit_code != 0: diff --git a/promise-types/git/git.py b/promise-types/git/git.py index 3f5a077..9ed4cc5 100644 --- a/promise-types/git/git.py +++ b/promise-types/git/git.py @@ -4,7 +4,12 @@ from typing import Dict, List, Optional -from cfengine_module_library import PromiseModule, ValidationError, Result +from cfengine_module_library import ( + PromiseModule, + ValidationError, + Result, + AttributeObject, +) class GitPromiseTypeModule(PromiseModule): @@ -93,7 +98,8 @@ def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): result = Result.REPAIRED except subprocess.CalledProcessError as e: self.log_error("Failed clone: {error}".format(error=e.output or e)) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -134,7 +140,8 @@ def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): result = Result.REPAIRED except subprocess.CalledProcessError as e: self.log_error("Failed reset: {error}".format(error=e.output or e)) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -228,7 +235,8 @@ def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): ) except subprocess.CalledProcessError as e: self.log_error("Failed fetch: {error}".format(error=e.output or e)) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -241,7 +249,9 @@ def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): # everything okay return (result, classes) - def _git(self, model: object, args: List[str], cwd: Optional[str] = None) -> str: + def _git( + self, model: AttributeObject, args: List[str], cwd: Optional[str] = None + ) -> str: self.log_verbose("Run: {cmd}".format(cmd=" ".join(args))) output = ( subprocess.check_output( @@ -253,15 +263,16 @@ def _git(self, model: object, args: List[str], cwd: Optional[str] = None) -> str .strip() .decode("utf-8") ) - output != "" and self.log_verbose(output) + if output != "": + self.log_verbose(output) return output - def _git_envvars(self, model: object): + def _git_envvars(self, model: AttributeObject): env = os.environ.copy() env["GIT_SSH_COMMAND"] = model.ssh_executable if model.ssh_options: env["GIT_SSH_COMMAND"] += " " + model.ssh_options - if not "HOME" in env: + if "HOME" not in env: # git should have a HOME env var to retrieve .gitconfig, .git-credentials, etc env["HOME"] = str(Path.home()) return env diff --git a/promise-types/groups/groups.py b/promise-types/groups/groups.py index 7d6bd99..5b2b556 100644 --- a/promise-types/groups/groups.py +++ b/promise-types/groups/groups.py @@ -64,7 +64,7 @@ def validate_promise(self, promiser, attributes, metadata): ) # check attribute gid value - if type(gid) == str: + if isinstance(gid, str): try: int(gid) except ValueError: diff --git a/promise-types/http/http_promise_type.py b/promise-types/http/http_promise_type.py index fe71dce..aaa7b85 100644 --- a/promise-types/http/http_promise_type.py +++ b/promise-types/http/http_promise_type.py @@ -2,7 +2,7 @@ import filecmp import os -import urllib +import urllib.error import urllib.request import ssl import json @@ -10,7 +10,6 @@ from cfengine_module_library import PromiseModule, ValidationError, Result - _SUPPORTED_METHODS = {"GET", "POST", "PUT", "DELETE", "PATCH"} @@ -27,14 +26,14 @@ def __init__(self, name="http_promise_module", version="0.0.0", **kwargs): def validate_promise(self, promiser, attributes, metadata): if "url" in attributes: url = attributes["url"] - if type(url) != str: + if not isinstance(url, str): raise ValidationError("'url' must be a string") if not url.startswith(("https://", "http://")): raise ValidationError("Only HTTP(S) requests are supported") if "method" in attributes: method = attributes["method"] - if type(method) != str: + if not isinstance(method, str): raise ValidationError("'method' must be a string") if method not in _SUPPORTED_METHODS: raise ValidationError( @@ -44,18 +43,18 @@ def validate_promise(self, promiser, attributes, metadata): if "headers" in attributes: headers = attributes["headers"] headers_type = type(headers) - if headers_type == str: + if headers_type is str: headers_lines = headers.splitlines() if any(line.count(":") != 1 for line in headers_lines): raise ValidationError( "'headers' must be string with 'name: value' pairs on separate lines" ) - elif headers_type == list: + elif headers_type is list: if any(line.count(":") != 1 for line in headers): raise ValidationError( "'headers' must be a list of 'name: value' pairs" ) - elif headers_type == dict: + elif headers_type is dict: # nothing to check for dict? pass else: @@ -72,7 +71,7 @@ def validate_promise(self, promiser, attributes, metadata): ) if ( - type(payload) == str + isinstance(payload, str) and payload.startswith("@") and not os.path.isabs(payload[1:]) ): @@ -80,12 +79,12 @@ def validate_promise(self, promiser, attributes, metadata): if "file" in attributes: file_ = attributes["file"] - if type(file_) != str or not os.path.isabs(file_): + if not isinstance(file_, str) or not os.path.isabs(file_): raise ValidationError("'file' must be an absolute path to a file") if "insecure" in attributes: insecure = attributes["insecure"] - if type(insecure) != str or insecure not in ( + if not isinstance(insecure, str) or insecure not in ( "true", "True", "false", @@ -125,19 +124,19 @@ def evaluate_promise(self, promiser, attributes, metadata): str.maketrans({char: "_" for char in ("@", "/", ":", "?", "&", "%")}) ) - if headers and type(headers) != dict: - if type(headers) == str: + if headers and not isinstance(headers, dict): + if isinstance(headers, str): headers = { key: value for key, value in (line.split(":") for line in headers.splitlines()) } - elif type(headers) == list: + elif isinstance(headers, list): headers = { key: value for key, value in (line.split(":") for line in headers) } if payload: - if type(payload) == dict: + if isinstance(payload, dict): try: payload = json.dumps(payload) except TypeError: @@ -179,10 +178,10 @@ def evaluate_promise(self, promiser, attributes, metadata): ) if "Content-Length" not in headers: - headers["Content-Length"] = os.path.getsize(path) + headers["Content-Length"] = str(os.path.getsize(path)) # must be 'None' or bytes or file object - if type(payload) == str: + if isinstance(payload, str): payload = payload.encode("utf-8") request = urllib.request.Request( @@ -194,8 +193,9 @@ def evaluate_promise(self, promiser, attributes, metadata): # convert to a boolean insecure = insecure.lower() == "true" if insecure: - SSL_context = ssl.SSLContext() - SSL_context.verify_method = ssl.CERT_NONE + SSL_context = ssl.create_default_context() + SSL_context.check_hostname = False + SSL_context.verify_mode = ssl.CERT_NONE try: with urllib.request.urlopen(request, context=SSL_context) as url_req: diff --git a/promise-types/iptables/iptables.py b/promise-types/iptables/iptables.py index 8001813..2448922 100644 --- a/promise-types/iptables/iptables.py +++ b/promise-types/iptables/iptables.py @@ -222,7 +222,8 @@ def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict): if result == Result.NOT_KEPT: classes.append("{}_{}_failed".format(safe_promiser, command)) elif result in {Result.KEPT, Result.REPAIRED}: - result == Result.REPAIRED and self.log_info(model.log_str) + if result == Result.REPAIRED: + self.log_info(model.log_str) classes.append("{}_{}_successful".format(safe_promiser, command)) else: diff --git a/promise-types/symlinks/symlinks.py b/promise-types/symlinks/symlinks.py index 1b05864..9dfb262 100644 --- a/promise-types/symlinks/symlinks.py +++ b/promise-types/symlinks/symlinks.py @@ -66,7 +66,7 @@ def evaluate_promise(self, promiser, attributes, metadata): "'{}' is already unlinked from its old target".format(promiser) ) return Result.NOT_KEPT - except Exception: + except Exception as e: self.log_error( "'{}' has wrong target but couldn't be unlinked: {}".format( promiser, e diff --git a/promise-types/systemd/systemd.py b/promise-types/systemd/systemd.py index 7b2821a..25f9c7d 100644 --- a/promise-types/systemd/systemd.py +++ b/promise-types/systemd/systemd.py @@ -1,12 +1,10 @@ -import json import os import subprocess from enum import Enum from typing import Dict, List, Optional, Tuple -from cfengine_module_library import PromiseModule, ValidationError, Result - +from cfengine_module_library import PromiseModule, Result, AttributeObject SYSTEMD_LIB_PATH = "/lib/systemd/system" @@ -104,7 +102,8 @@ def evaluate_promise( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, ["{safe_promiser}_show_failed".format(safe_promiser=safe_promiser)], @@ -116,7 +115,7 @@ def evaluate_promise( return self._service_present(model, safe_promiser, service_status) def _service_absent( - self, model: object, safe_promiser: str, service_status: dict + self, model: AttributeObject, safe_promiser: str, service_status: dict ) -> Tuple[str, List[str]]: classes = [] result = Result.KEPT @@ -131,7 +130,8 @@ def _service_absent( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, ["{safe_promiser}_stop_failed".format(safe_promiser=safe_promiser)], @@ -147,7 +147,8 @@ def _service_absent( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -190,7 +191,8 @@ def _service_absent( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -204,7 +206,7 @@ def _service_absent( return (result, classes) def _service_present( - self, model: object, safe_promiser: str, service_status: dict + self, model: AttributeObject, safe_promiser: str, service_status: dict ) -> Tuple[str, List[str]]: classes = [] result = Result.KEPT @@ -245,7 +247,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -266,7 +269,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -287,7 +291,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, ["{safe_promiser}_mask_failed".format(safe_promiser=safe_promiser)], @@ -304,7 +309,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -331,7 +337,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -358,7 +365,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -384,7 +392,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -411,7 +420,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, ["{safe_promiser}_stop_failed".format(safe_promiser=safe_promiser)], @@ -430,7 +440,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -453,7 +464,8 @@ def _service_present( self.log_error( "Failed to run systemctl: {error}".format(error=e.output or e) ) - e.stderr and self.log_error(e.stderr.strip()) + if e.stderr: + self.log_error(e.stderr.strip()) return ( Result.NOT_KEPT, [ @@ -477,10 +489,11 @@ def _exec_command(self, args: List[str], cwd: Optional[str] = None) -> str: .strip() .decode("utf-8") ) - output != "" and self.log_verbose(output) + if output != "": + self.log_verbose(output) return output - def _render_service_template(self, model: object) -> str: + def _render_service_template(self, model: AttributeObject) -> str: blocks = { "unit": [], "service": [], @@ -518,7 +531,7 @@ def _render_service_template(self, model: object) -> str: value = getattr(model, attr) if value is None: continue - elif type(value) == list: + elif isinstance(value, list): for item in value: blocks[block].append("{key}={item}".format(key=key, item=item)) else: diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..389015c --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,3 @@ +{ + "reportMissingImports": "none" +}