From 6e8550c30bf251fc6c056c70c790b493afee3daa Mon Sep 17 00:00:00 2001 From: MrNaif2018 Date: Mon, 16 Dec 2024 16:24:10 +0300 Subject: [PATCH] Finalize linting --- api/crud/invoices.py | 5 ++--- api/ext/ssh.py | 4 +--- api/models.py | 2 +- api/settings.py | 16 ++++++++++++---- api/utils/common.py | 8 ++++---- api/utils/notifications.py | 5 ++--- api/views/files.py | 9 +++++---- api/views/manage.py | 4 ++-- api/views/plugins.py | 4 ++-- api/views/products.py | 5 +++-- daemons/eth.py | 4 +--- daemons/storage.py | 7 ++++--- daemons/utils.py | 3 ++- pyproject.toml | 9 ++++++++- tests/test_utils.py | 12 ++++++------ 15 files changed, 55 insertions(+), 42 deletions(-) diff --git a/api/crud/invoices.py b/api/crud/invoices.py index f5578c8a..b29f222a 100644 --- a/api/crud/invoices.py +++ b/api/crud/invoices.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import secrets import time from collections import defaultdict @@ -245,11 +246,9 @@ async def update_invoice_payments(invoice, wallets_ids, discounts, store, produc if randomize_selection: symbols = defaultdict(list) for wallet in wallets: - try: + with contextlib.suppress(Exception): symbol = await utils.wallets.get_wallet_symbol(wallet) symbols[symbol].append(wallet) - except Exception: # pragma: no cover - pass coros = [ create_method_for_wallet(invoice, secrets.choice(symbols[symbol]), discounts, store, product, promocode) for symbol in symbols diff --git a/api/ext/ssh.py b/api/ext/ssh.py index fb638327..8cfe5245 100644 --- a/api/ext/ssh.py +++ b/api/ext/ssh.py @@ -95,7 +95,7 @@ def collect_server_settings(ssh_settings): # pragma: no cover from api.utils.common import str_to_bool settings = ConfiguratorServerSettings() - try: + with contextlib.suppress(Exception): client = ssh_settings.create_ssh_client() env = ServerEnv(client) cryptos = CommaSeparatedStrings(env.get("BITCART_CRYPTOS", "btc")) @@ -114,6 +114,4 @@ def collect_server_settings(ssh_settings): # pragma: no cover installation_pack=installation_pack, additional_components=additional_components ) client.close() - except Exception: - pass return settings diff --git a/api/models.py b/api/models.py index d33cceef..4476cda4 100644 --- a/api/models.py +++ b/api/models.py @@ -533,7 +533,7 @@ def to_dict(self, currency, index: int = None): return data @classmethod - def parse_chain_id(self, url): # pragma: no cover + def parse_chain_id(cls, url): # pragma: no cover k = url.find("@") if k == -1: return None diff --git a/api/settings.py b/api/settings.py index 4276103f..3e7d5f5a 100644 --- a/api/settings.py +++ b/api/settings.py @@ -12,6 +12,7 @@ from contextvars import ContextVar from typing import Annotated +import aiofiles import fido2.features from aiohttp import ClientSession from bitcart import COINS, APIManager @@ -151,42 +152,49 @@ def set_db_name(cls, db, info: ValidationInfo): return db @field_validator("datadir", mode="before") + @classmethod def set_datadir(cls, path): path = os.path.abspath(path) ensure_exists(path) return path @field_validator("backups_dir", mode="before") + @classmethod def set_backups_dir(cls, path): path = os.path.abspath(path) ensure_exists(path) return path @field_validator("backend_plugins_dir", mode="before") + @classmethod def set_backend_plugins_dir(cls, path): path = os.path.abspath(path) ensure_exists(path) return path @field_validator("admin_plugins_dir", mode="before") + @classmethod def set_admin_plugins_dir(cls, path): path = os.path.abspath(path) ensure_exists(path) return path @field_validator("store_plugins_dir", mode="before") + @classmethod def set_store_plugins_dir(cls, path): path = os.path.abspath(path) ensure_exists(path) return path @field_validator("daemon_plugins_dir", mode="before") + @classmethod def set_daemon_plugins_dir(cls, path): path = os.path.abspath(path) ensure_exists(path) return path @field_validator("docker_plugins_dir", mode="before") + @classmethod def set_docker_plugins_dir(cls, path): path = os.path.abspath(path) ensure_exists(path) @@ -301,15 +309,15 @@ async def with_db(self): async def fetch_schema(self): schema_path = os.path.join(self.datadir, "plugins_schema.json") if os.path.exists(schema_path): - with open(schema_path) as f: - plugins_schema = json.loads(f.read()) + async with aiofiles.open(schema_path) as f: + plugins_schema = json.loads(await f.read()) if plugins_schema["$id"] == PLUGINS_SCHEMA_URL: self.plugins_schema = plugins_schema return async with ClientSession() as session, session.get(PLUGINS_SCHEMA_URL) as resp: self.plugins_schema = await resp.json() - with open(schema_path, "w") as f: - f.write(json.dumps(self.plugins_schema)) + async with aiofiles.open(schema_path, "w") as f: + await f.write(json.dumps(self.plugins_schema)) async def init(self): sys.excepthook = excepthook_handler(self, sys.excepthook) diff --git a/api/utils/common.py b/api/utils/common.py index 4af80e1a..9dce1992 100644 --- a/api/utils/common.py +++ b/api/utils/common.py @@ -35,12 +35,12 @@ async def run_universal(func, *args, **kwargs): return result -async def run_repeated(func, timeout, start_timeout=None): # pragma: no cover - if start_timeout is None: - start_timeout = timeout +async def run_repeated(func, interval, initial_delay=None): # pragma: no cover + if initial_delay is None: + initial_delay = interval first_iter = True while True: - await asyncio.sleep(start_timeout if first_iter else timeout) + await asyncio.sleep(initial_delay if first_iter else interval) await run_universal(func) first_iter = False diff --git a/api/utils/notifications.py b/api/utils/notifications.py index f5a9a50a..3075c638 100644 --- a/api/utils/notifications.py +++ b/api/utils/notifications.py @@ -1,3 +1,4 @@ +import contextlib import traceback import apprise @@ -30,15 +31,13 @@ def validate_data(provider, data): # pragma: no cover for k, v in provider["details"][json_part].items(): if "type" in v and k in data: field_type = v["type"] - try: + with contextlib.suppress(Exception): if field_type == "int": data[k] = int(data[k]) elif field_type == "float": data[k] = float(data[k]) elif field_type == "bool": data[k] = utils.common.str_to_bool(data[k]) - except Exception: - pass data["schema"] = provider["details"]["tokens"]["schema"]["values"][0] return data diff --git a/api/views/files.py b/api/views/files.py index 7b72534f..52b4afa9 100644 --- a/api/views/files.py +++ b/api/views/files.py @@ -1,5 +1,6 @@ import os +import aiofiles from fastapi import APIRouter, File, HTTPException, Security, UploadFile from fastapi.responses import RedirectResponse from sqlalchemy import select @@ -22,8 +23,8 @@ async def create_file( raise HTTPException(403, "File uploads are not allowed") file_obj = await utils.database.create_object(models.File, {"filename": file.filename}, user) path = get_file_path(file_obj) - with open(path, "wb") as f: - f.write(await file.read()) + async with aiofiles.open(path, "wb") as f: + await f.write(await file.read()) return file_obj @@ -39,8 +40,8 @@ async def patch_file( utils.files.safe_remove(get_file_path(item)) await utils.database.modify_object(item, {"filename": file.filename}) path = get_file_path(item) - with open(path, "wb") as f: - f.write(await file.read()) + async with aiofiles.open(path, "wb") as f: + await f.write(await file.read()) return item diff --git a/api/views/manage.py b/api/views/manage.py index 6eecb179..500b4c92 100644 --- a/api/views/manage.py +++ b/api/views/manage.py @@ -119,8 +119,8 @@ async def get_log_contents( if not settings.settings.log_file: raise HTTPException(400, "Log file unconfigured") try: - with open(os.path.join(settings.settings.log_dir, log)) as f: - return f.read().strip() + async with aiofiles.open(os.path.join(settings.settings.log_dir, log)) as f: + return (await f.read()).strip() except OSError: raise HTTPException(404, "This log doesn't exist") from None diff --git a/api/views/plugins.py b/api/views/plugins.py index 632767a9..47798b66 100644 --- a/api/views/plugins.py +++ b/api/views/plugins.py @@ -37,8 +37,8 @@ async def install_plugin( manifest_path = os.path.join(plugin_path, "manifest.json") if not os.path.exists(manifest_path): return {"status": "error", "message": "Invalid plugin archive: missing manifest.json"} - with open(manifest_path) as f: - manifest = f.read() + async with aiofiles.open(manifest_path) as f: + manifest = await f.read() try: manifest = plugin_ext.parse_manifest(manifest) except ValueError as e: diff --git a/api/views/products.py b/api/views/products.py index e77f1076..fbbcbdec 100644 --- a/api/views/products.py +++ b/api/views/products.py @@ -2,6 +2,7 @@ import os from decimal import Decimal +import aiofiles from fastapi import APIRouter, Depends, File, Form, HTTPException, Security, UploadFile from pydantic import ValidationError from sqlalchemy import select @@ -24,8 +25,8 @@ def get_image_local_path(model_id): async def save_image(model, image): filename = get_image_local_path(model.id) - with open(filename, "wb") as f: - f.write(await image.read()) + async with aiofiles.open(filename, "wb") as f: + await f.write(await image.read()) def parse_data(data, scheme): diff --git a/daemons/eth.py b/daemons/eth.py index c02e3f81..c0036483 100644 --- a/daemons/eth.py +++ b/daemons/eth.py @@ -370,11 +370,9 @@ async def on_startup(self, app): self.trace_available = False self.trace_queue = asyncio.Queue() await self.create_coin(archive=True) - try: + with contextlib.suppress(Exception): await self.archive_coin.debug_trace_block(0) self.trace_available = True - except Exception: - pass await super().on_startup(app) if self.trace_available: self.archive_limiter = AsyncLimiter(1, 1 / self.ARCHIVE_RATE_LIMIT) diff --git a/daemons/storage.py b/daemons/storage.py index 5478b574..8b82fa38 100644 --- a/daemons/storage.py +++ b/daemons/storage.py @@ -59,8 +59,8 @@ def write(self, data: str) -> None: mode = os.stat(self.path).st_mode except FileNotFoundError: mode = stat.S_IREAD | stat.S_IWRITE - if not self.file_exists(): - assert not os.path.exists(self.path) + if not self.file_exists() and os.path.exists(self.path): + raise DBFileException(f"File {self.path} should not exist") os.replace(temp_path, self.path) os.chmod(self.path, mode) self._file_exists = True @@ -301,7 +301,8 @@ def _after_upgrade_tasks(self): self.data = StoredDict(self.data, self, []) def _is_upgrade_method_needed(self, min_version, max_version): - assert min_version <= max_version + if min_version > max_version: + raise DBFileException(f"Invalid version range: {min_version} > {max_version}") cur_version = self.get_version() if cur_version > max_version: return False diff --git a/daemons/utils.py b/daemons/utils.py index 542a65cc..8b42120f 100644 --- a/daemons/utils.py +++ b/daemons/utils.py @@ -266,7 +266,8 @@ class MultipleProviderRPC(metaclass=ABCMeta): RESET = object() # sentinel def __init__(self, providers: list[AbstractRPCProvider]): - assert isinstance(providers, list) + if not isinstance(providers, list): + raise TypeError("providers must be a list") self.providers = providers if not self.providers: raise ValueError("No urls provided") diff --git a/pyproject.toml b/pyproject.toml index 2f586320..6b36db83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,10 +16,17 @@ select = [ "RUF100", "RET", "A", + "S", + "ASYNC", ] -ignore = ["RET502", "RET503"] +ignore = ["RET502", "RET503", "S104", "S507", "ASYNC110"] mccabe = { max-complexity = 12 } +[tool.ruff.lint.per-file-ignores] +'tests/*' = ["S"] +'scripts/*' = ["S"] +'.circleci/*' = ["S"] + [tool.ruff.lint.isort] known-third-party = ["bitcart"] diff --git a/tests/test_utils.py b/tests/test_utils.py index c5be6784..03b62dd8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -174,18 +174,18 @@ async def test_notification_template(client, token, user): def test_run_host(mocker): - TEST_FILE = os.path.expanduser("~/test-output") - content = f"touch {TEST_FILE}" + test_file = os.path.expanduser("~/test-output") + content = f"touch {test_file}" # No valid ssh connection ok, error = utils.host.run_host(content) assert ok is False - assert not os.path.exists(TEST_FILE) + assert not os.path.exists(test_file) assert "Connection problem" in error assert utils.host.run_host_output(content, "good")["status"] == "error" # Same with key file settings.settings.ssh_settings.key_file = "something" assert utils.host.run_host(content)[0] is False - assert not os.path.exists(TEST_FILE) + assert not os.path.exists(test_file) settings.settings.ssh_settings.key_file = "" mocker.patch("paramiko.SSHClient.connect", return_value=True) mocker.patch( @@ -197,8 +197,8 @@ def test_run_host(mocker): assert error is None assert utils.host.run_host_output(content, "good") == {"status": "success", "message": "good"} time.sleep(1) # wait for command to execute (non-blocking) - assert os.path.exists(TEST_FILE) - os.remove(TEST_FILE) # Cleanup + assert os.path.exists(test_file) + os.remove(test_file) # Cleanup def test_versiontuple():