diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ece245d..88a8a51 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,7 @@ repos: hooks: - id: isort args: ["--profile", "black", "--filter-files"] + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.267' + hooks: + - id: ruff diff --git a/app/api/utils.py b/app/api/utils.py index f01bd8a..d0ebbf3 100644 --- a/app/api/utils.py +++ b/app/api/utils.py @@ -30,7 +30,11 @@ class ProcessResult: self.stderr = stderr def __str__(self) -> str: - return f"ProcessResult: \nreturn_code: {self.return_code} \nstdout: {self.stdout} \nstderr: {self.stderr}" + return ( + f"ProcessResult: \nreturn_code: {self.return_code}\n" + f"stdout: {self.stdout}\n" + f"stderr: {self.stderr}" + ) def build_sse_event(event: str, json_data: Optional[Dict]): @@ -72,9 +76,10 @@ async def redis_get(key: str) -> str: # TODO -# idea is to have a second redis channel called system, that the API subscribes to. If for example -# the 'state' value gets changed by the _cache.sh script, it should publish this to this channel -# so the API can forward the change to thru the SSE to the WebUI +# idea is to have a second redis channel called system, that the API subscribes to. +# If for example the 'state' value gets changed by the _cache.sh script, it should +# publish this to this channel so the API can forward the change to thru the SSE to +# the WebUI class SSE: @@ -169,11 +174,13 @@ def next_push_id() -> str: """Generates a unique random 20 character long string id * They're based on timestamp so that they sort *after* any existing ids. - * They contain 72-bits of random data after the timestamp so that IDs won't collide with other clients' IDs. - * They sort *lexicographically* (so the timestamp is converted to characters that will sort properly). - * They're monotonically increasing. Even if you generate more than one in the same timestamp, the - latter ones will sort after the former ones. We do this by using the previous random bits - but "incrementing" them by 1 (only in the case of a timestamp collision). + * They contain 72-bits of random data after the timestamp so that IDs won't collide + * with other clients' IDs. They sort *lexicographically* (so the timestamp is + * converted to characters that will sort properly). + * They're monotonically increasing. Even if you generate more than one in the same + * timestamp, the latter ones will sort after the former ones. We do this by using + * the previous random bits but "incrementing" them by 1 (only in the case of a + * timestamp collision). """ return pid_gen.next_id() diff --git a/app/api/warmup.py b/app/api/warmup.py index e2c7d4d..304e46e 100644 --- a/app/api/warmup.py +++ b/app/api/warmup.py @@ -49,10 +49,14 @@ async def get_full_client_warmup_data() -> List: if isinstance(r, HTTPException): if r.status_code == status.HTTP_501_NOT_IMPLEMENTED: logger.trace(f"Not implemented Error in warmup data {i}: {r.detail}") - # TODO: find a better way to handle this, client receives an error but disguised - # as a valid response. For example: + # TODO: find a better way to handle this, client receives an error but + # disguised as a valid response. For example: # event: installed_app_status - # data: {"status_code": 501, "detail": "Not available in native python mode.", "headers": null} + # data: { + # "status_code": 501, + # "detail": "Not available in native python mode.", + # "headers": null + # } res[i] = r elif isinstance(r, Exception): logger.error(f"Error in warmup data {i}: {r}") diff --git a/app/apps/impl/raspiblitz.py b/app/apps/impl/raspiblitz.py index 5a687c8..31044a7 100644 --- a/app/apps/impl/raspiblitz.py +++ b/app/apps/impl/raspiblitz.py @@ -1,3 +1,5 @@ +# ruff: noqa: E722 + import asyncio import json import os @@ -15,7 +17,8 @@ from app.apps.impl.apps_base import AppsBase available_app_ids = { "btc-rpc-explorer", "rtl", - # Specter is deactivated for now because it uses its own self signed HTTPS cert that makes trouble in Chrome on last test + # Specter is deactivated for now because it uses its own self signed HTTPS cert that + # makes trouble in Chrome on last test # "specter", "btcpayserver", "lnbits", @@ -35,7 +38,7 @@ class RaspiBlitzApps(AppsBase): if app_id not in available_app_ids: return { "id": f"{app_id}", - "error": f"appID not in list", + "error": "appID not in list", } script_call = ( os.path.join(SHELL_SCRIPT_PATH, "config.scripts", f"bonus.{app_id}.sh") @@ -148,7 +151,8 @@ class RaspiBlitzApps(AppsBase): while True: status = "online" if switch else "offline" app_list = [ - # Specter is deactivated for now because it uses its own self signed HTTPS cert that makes trouble in Chrome on last test + # Specter is deactivated for now because it uses its own self signed + # HTTPS cert that makes trouble in Chrome on last test # also see: app/constants.py where specter is deactivated # {"id": "specter", "name": "Specter Desktop", "status": status}, {"id": "sphinx", "name": "Sphinx Chat", "status": status}, @@ -162,7 +166,7 @@ class RaspiBlitzApps(AppsBase): switch = not switch async def install_app_sub(self, app_id: str): - if not app_id in available_app_ids: + if app_id not in available_app_ids: raise HTTPException( status.HTTP_400_BAD_REQUEST, detail=app_id + "install script does not exist / is not supported", @@ -179,7 +183,7 @@ class RaspiBlitzApps(AppsBase): return jsonable_encoder({"id": app_id}) async def uninstall_app_sub(self, app_id: str, delete_data: bool): - if not app_id in available_app_ids: + if app_id not in available_app_ids: raise HTTPException( status.HTTP_400_BAD_REQUEST, detail="script not exist/supported" ) @@ -198,7 +202,8 @@ class RaspiBlitzApps(AppsBase): return jsonable_encoder({"id": app_id}) async def run_bonus_script(self, app_id: str, params: str): - # to satisfy CodeQL: test again against predefined array and don't use 'user value' + # to satisfy CodeQL: test again against predefined array and + # don't use 'user value' tested_app_id = "" for id in available_app_ids: if id == app_id: @@ -221,30 +226,31 @@ class RaspiBlitzApps(AppsBase): if stdout: logging.debug(f"[stdout]\n{stdout.decode()}") else: - logging.debug(f"NO [stdout]") + logging.debug("NO [stdout]") if stderr: logging.debug(f"[stderr]\n{stderr.decode()}") else: - logging.debug(f"NO [stderr]") + logging.debug("NO [stderr]") # create log file logFileName = f"/var/cache/raspiblitz/temp/install.{app_id}.log" logging.info(f"WRITING LOG FILE: {logFileName}") with open(logFileName, "w", encoding="utf-8") as f: f.write(f"API triggered script: {cmd}\n") - f.write(f"###### STDOUT #######\n") + f.write("###### STDOUT #######\n") if stdout: f.write(stdout.decode()) - f.write(f"\n###### STDERR #######\n") + f.write("\n###### STDERR #######\n") if stderr: f.write(stderr.decode()) # sending final feedback event - logging.debug(f"SENDING RESULT EVENT ...") + logging.debug("SENDING RESULT EVENT ...") if stdout: stdoutData = parse_key_value_text(stdout.decode()) logging.debug(f"PARSED STDOUT DATA: {stdoutData}") - # when there is a defined error message (if multiple it wil lbe the last one) + # when there is a defined error message (if multiple it will + # be the last one) if "error" in stdoutData: logging.error( f"FOUND `error=` returned by script: {stdoutData['error']}" @@ -258,9 +264,10 @@ class RaspiBlitzApps(AppsBase): "details": stdoutData["error"], }, ) - # when there is no result (e.g. result="OK") at the end of install script stdout - consider also script had error - elif not "result" in stdoutData: - logging.error(f"NO `result=` returned by script:") + # when there is no result (e.g. result="OK") at the end of install script + # stdout - consider also script had error + elif "result" not in stdoutData: + logging.error("NO `result=` returned by script:") await broadcast_sse_msg( SSE.INSTALL_APP, { @@ -277,7 +284,7 @@ class RaspiBlitzApps(AppsBase): # in case of script error if updatedAppData["error"] != "": - logging.warning(f"Error Detected ...") + logging.warning("Error Detected ...") logging.warning(f"updatedAppData: {updatedAppData}") await broadcast_sse_msg( SSE.INSTALL_APP, diff --git a/app/apps/router.py b/app/apps/router.py index 5b4f9e0..47607e5 100644 --- a/app/apps/router.py +++ b/app/apps/router.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, HTTPException, status +from fastapi import APIRouter, HTTPException from fastapi.params import Depends from loguru import logger from pydantic import BaseModel @@ -44,7 +44,7 @@ async def get_single_status(id): dependencies=[Depends(JWTBearer())], ) @logger.catch(exclude=(HTTPException,)) -async def get_status(): +async def get_status_sub(): return EventSourceResponse(repo.get_app_status_sub()) diff --git a/app/auth/auth_bearer.py b/app/auth/auth_bearer.py index 5490a33..0e69087 100644 --- a/app/auth/auth_bearer.py +++ b/app/auth/auth_bearer.py @@ -37,8 +37,10 @@ class JWTBearer(HTTPBearer): try: payload = decodeJWT(jwtoken) - except: + except: # noqa: E722 payload = None + if payload: isTokenValid = True + return isTokenValid diff --git a/app/bitcoind/docs.py b/app/bitcoind/docs.py index 4ed6ff0..50fa828 100644 --- a/app/bitcoind/docs.py +++ b/app/bitcoind/docs.py @@ -1,3 +1,5 @@ +# ruff: noqa: E501 + blocks_sub_doc = """ Similar to Bitcoin Core getblock diff --git a/app/bitcoind/models.py b/app/bitcoind/models.py index 87d59ef..0b000cc 100644 --- a/app/bitcoind/models.py +++ b/app/bitcoind/models.py @@ -33,7 +33,9 @@ class BtcNetwork(BaseModel): reachable: bool = Query(..., description="Is the network reachable?") proxy: Optional[str] = Query( "", - description="host:port of the proxy that is used for this network, or empty if none", + description=( + "host:port of the proxy that is used for this network, or empty if none" + ), ) proxy_randomize_credentials: bool = Query( ..., description="Whether randomized credentials are used" @@ -67,7 +69,10 @@ class BtcLocalAddress(BaseModel): class RawTransaction(BaseModel): in_active_chain: Union[None, bool] = Query( None, - description='Whether specified block is in the active chain or not (only present with explicit "blockhash" argument)', + description=( + "Whether specified block is in the active chain or not (only present with " + "explicit 'blockhash' argument)" + ), ) txid: str = Query(..., description="The transaction id (same as provided)") hash: str = Query( @@ -77,7 +82,9 @@ class RawTransaction(BaseModel): size: int = Query(..., description="The serialized transaction size") vsize: int = Query( ..., - description="The virtual transaction size (differs from size for witness transactions)", + description=( + "The virtual transaction size (differs from size for witness transactions)" + ), ) weight: int = Query( ..., description="The transaction's weight (between vsize*4 - 3 and vsize*4)" @@ -138,7 +145,10 @@ class NetworkInfo(BaseModel): ) incremental_fee: int = Query( ..., - description="Minimum fee increment for mempool limiting or BIP 125 replacement in BTC/kB", + description=( + "Minimum fee increment for mempool limiting or BIP 125 " + "replacement in BTC/kB" + ), ) local_addresses: List[BtcLocalAddress] = Query( [], description="List of local addresses" @@ -177,19 +187,29 @@ class Bip9Statistics(BaseModel): ) threshold: int = Query( ..., - description="The number of blocks with the version bit set required to activate the feature", + description=( + "The number of blocks with the version bit set required to activate " + "the feature" + ), ) elapsed: int = Query( ..., - description="The number of blocks elapsed since the beginning of the current period", + description=( + "The number of blocks elapsed since the beginning of the current period" + ), ) count: int = Query( ..., - description="The number of blocks with the version bit set in the current period", + description=( + "The number of blocks with the version bit set in the current period" + ), ) possible: bool = Query( ..., - description="False if there are not enough blocks left in this period to pass activation threshold", + description=( + "False if there are not enough blocks left in this period to " + "pass activation threshold" + ), ) @classmethod @@ -206,19 +226,28 @@ class Bip9Statistics(BaseModel): class Bip9Data(BaseModel): status: str = Query( ..., - description="""One of "defined", "started", "locked_in", "active", "failed" """, + description='One of "defined", "started", "locked_in", "active", "failed"', ) bit: int = Query( None, - description="the bit(0-28) in the block version field used to signal this softfork(only for `started` status)", + description=( + "the bit(0-28) in the block version field used to signal this " + "softfork(only for `started` status)" + ), ) start_time: int = Query( ..., - description="The minimum median time past of a block at which the bit gains its meaning", + description=( + "The minimum median time past of a block at which the bit gains " + "its meaning" + ), ) timeout: int = Query( ..., - description="The median time past of a block at which the deployment is considered failed if not yet locked in", + description=( + "The median time past of a block at which the deployment is " + "considered failed if not yet locked in" + ), ) since: int = Query( ..., description="Height of the first block to which the status applies" @@ -228,11 +257,17 @@ class Bip9Data(BaseModel): ) statistics: Bip9Statistics = Query( None, - description="numeric statistics about BIP9 signalling for a softfork(only for `started` status)", + description=( + "numeric statistics about BIP9 signalling for a " + "softfork(only for `started` status)" + ), ) height: int = Query( None, - description="Height of the first block which the rules are or will be enforced(only for `buried` type, or `bip9` type with `active` status)", + description=( + "Height of the first block which the rules are or will be " + "enforced(only for `buried` type, or `bip9` type with `active` status)" + ), ) active: bool = Query( None, @@ -261,14 +296,19 @@ class SoftFork(BaseModel): type: str = Query(..., description='One of "buried", "bip9"') active: bool = Query( ..., - description="True **if** the rules are enforced for the mempool and the next block", + description=( + "True **if** the rules are enforced for the mempool and the next block" + ), ) bip9: Bip9Data = Query( None, description='Status of bip9 softforks(only for "bip9" type)' ) height: int = Query( None, - description="Height of the first block which the rules are or will be enforced (only for `buried` type, or `bip9` type with `active` status)", + description=( + "Height of the first block which the rules are or will be enforced " + "(only for `buried` type, or `bip9` type with `active` status)" + ), ) @classmethod @@ -286,7 +326,10 @@ class BlockchainInfo(BaseModel): chain: str = Query(..., description="Current network name(main, test, regtest)") blocks: int = Query( ..., - description="The height of the most-work fully-validated chain. The genesis block has height 0", + description=( + "The height of the most-work fully-validated chain. " + "The genesis block has height 0" + ), ) headers: int = Query( ..., description="The current number of headers we have validated" @@ -312,15 +355,22 @@ class BlockchainInfo(BaseModel): pruned: bool = Query(..., description="If the blocks are subject to pruning") prune_height: int = Query( None, - description="Lowest-height complete block stored(only present if pruning is enabled)", + description=( + "Lowest-height complete block stored(only present if pruning is enabled)" + ), ) automatic_pruning: bool = Query( None, - description="Whether automatic pruning is enabled(only present if pruning is enabled)", + description=( + "Whether automatic pruning is enabled(only present if pruning is enabled)" + ), ) prune_target_size: int = Query( None, - description="The target size used by pruning(only present if automatic pruning is enabled)", + description=( + "The target size used by pruning(only present if automatic pruning is " + "enabled)" + ), ) warnings: str = Query(..., description="Any network and blockchain warnings") softforks: List[SoftFork] = Query(..., description="Status of softforks") @@ -345,12 +395,12 @@ class BlockchainInfo(BaseModel): chainwork=r["chainwork"], size_on_disk=r["size_on_disk"], pruned=r["pruned"], - pruned_height=None if not "pruneheight" in r else r["pruneheight"], + pruned_height=None if "pruneheight" not in r else r["pruneheight"], automatic_pruning=None - if not "automatic_pruning" in r + if "automatic_pruning" not in r else r["automatic_pruning"], prune_target_size=None - if not "prune_target_size" in r + if "prune_target_size" not in r else r["prune_target_size"], warnings=r["warnings"], softforks=softforks, @@ -361,7 +411,10 @@ class BtcInfo(BaseModel): # Info regarding bitcoind blocks: int = Query( ..., - description="The height of the most-work fully-validated chain. The genesis block has height 0", + description=( + "The height of the most-work fully-validated chain. " + "The genesis block has height 0" + ), ) headers: int = Query( ..., description="The current number of headers we have validated" diff --git a/app/bitcoind/router.py b/app/bitcoind/router.py index d2c6120..4fd8938 100644 --- a/app/bitcoind/router.py +++ b/app/bitcoind/router.py @@ -29,7 +29,10 @@ router = APIRouter(prefix=f"/{_PREFIX}", tags=["Bitcoin Core"]) @router.get( "/btc-info", name=f"{_PREFIX}.btc-info", - description="Get general information about bitcoin core. Combines most important information from `getblockchaininfo` and `getnetworkinfo`", + description=( + "Get general information about bitcoin core. Combines most important " + "information from `getblockchaininfo` and `getnetworkinfo`" + ), dependencies=[Depends(JWTBearer())], response_model=BtcInfo, ) diff --git a/app/bitcoind/service.py b/app/bitcoind/service.py index 7510019..e2fd1d1 100644 --- a/app/bitcoind/service.py +++ b/app/bitcoind/service.py @@ -51,7 +51,10 @@ async def initialize_bitcoin_repo() -> bool: logger.error(e.detail) logger.debug( - f"Connected to Bitcoin Core but it seems to be initializing, waiting 2 seconds... \n{e.detail}" + ( + "Connected to Bitcoin Core but it seems to be " + f"initializing, waiting 2 seconds... \n{e.detail}" + ) ) await asyncio.sleep(2) @@ -61,7 +64,7 @@ async def initialize_bitcoin_repo() -> bool: async def get_blockchain_info() -> BlockchainInfo: result = await bitcoin_rpc_async("getblockchaininfo") - if result["error"] != None: + if result["error"] is not None: raise HTTPException(result["status"], detail=result["error"]) return BlockchainInfo.from_rpc(result["result"]) @@ -74,7 +77,7 @@ async def estimate_fee( ) -> int: result = await bitcoin_rpc_async("estimatesmartfee", [target_conf, mode]) - if result["error"] != None: + if result["error"] is not None: raise HTTPException(result["status"], detail=result["error"]) if "errors" in result["result"]: @@ -96,7 +99,7 @@ async def estimate_fee( async def get_network_info() -> NetworkInfo: result = await bitcoin_rpc_async("getnetworkinfo") - if result["error"] != None: + if result["error"] is not None: raise HTTPException(result["status"], detail=result["error"]) return NetworkInfo.from_rpc(result["result"]) @@ -106,7 +109,7 @@ async def get_network_info() -> NetworkInfo: async def get_raw_transaction(txid: str) -> RawTransaction: result = await bitcoin_rpc_async("getrawtransaction", [txid, 1]) - if result["error"] == None: + if result["error"] is None: return RawTransaction.from_rpc(result["result"]) if "No such mempool or blockchain transaction." in result["error"]: @@ -183,7 +186,7 @@ async def _handle_gather_bitcoin_status(): while True: try: info = await get_btc_info() - if info == None: + if info is None: continue info.verification_progress = round(info.verification_progress, 2) diff --git a/app/bitcoind/utils.py b/app/bitcoind/utils.py index 2a9024d..0a49afd 100644 --- a/app/bitcoind/utils.py +++ b/app/bitcoind/utils.py @@ -5,7 +5,6 @@ from types import coroutine import aiohttp import requests from decouple import config -from loguru import logger from starlette import status from app.bitcoind.models import BlockRpcFunc @@ -101,13 +100,19 @@ async def _process_response(resp: aiohttp.ClientResponse): if resp.status == status.HTTP_401_UNAUTHORIZED: return { - "error": "Access denied to Bitcoin Core RPC. Check if username and password is correct", + "error": ( + "Access denied to Bitcoin Core RPC. Check if " + "username and password is correct" + ), "status": status.HTTP_403_FORBIDDEN, } if resp.status == status.HTTP_403_FORBIDDEN: return { - "error": "Access denied to Bitcoin Core RPC. If this is a remote node, check if 'network.rpcallowip=0.0.0.0/0' is set.", + "error": ( + "Access denied to Bitcoin Core RPC. If this is a remote node, " + "check if 'network.rpcallowip=0.0.0.0/0' is set." + ), "status": status.HTTP_403_FORBIDDEN, } @@ -121,7 +126,10 @@ async def _process_response(resp: aiohttp.ClientResponse): or "Starting network threads" in m ): return { - "error": "Initializing Bitcoin Core (loading, verifying blocks or starting network threads etc)", + "error": ( + "Initializing Bitcoin Core (loading, verifying " + "blocks or starting network threads etc)" + ), "status": status.HTTP_425_TOO_EARLY, } if "No such mempool or blockchain transaction." in m: diff --git a/app/external/fastapi_versioning/__init__.py b/app/external/fastapi_versioning/__init__.py index 8164b00..8b2d53b 100644 --- a/app/external/fastapi_versioning/__init__.py +++ b/app/external/fastapi_versioning/__init__.py @@ -1,2 +1,4 @@ +# ruff: noqa: F401 + from .routing import versioned_api_route from .versioning import VersionedFastAPI, version diff --git a/app/external/sse_starlette/sse_starlette.py b/app/external/sse_starlette/sse_starlette.py index 3514165..e931e52 100644 --- a/app/external/sse_starlette/sse_starlette.py +++ b/app/external/sse_starlette/sse_starlette.py @@ -5,7 +5,7 @@ import logging import re from datetime import datetime from functools import partial -from typing import Any, AsyncIterable, Callable, Coroutine, Dict, Optional, Union +from typing import Any, Callable, Coroutine, Dict, Optional, Union import anyio from starlette.background import BackgroundTask @@ -74,8 +74,8 @@ class ServerSentEvent: specifying the reconnection time in milliseconds. If a non-integer value is specified, the field is ignored. :param str comment: A colon as the first character of a line is essence - a comment, and is ignored. Usually used as a ping message to keep connecting. - If set, this will be a comment message. + a comment, and is ignored. Usually used as a ping message to keep + connecting. f set, this will be a comment message. """ self.data = data self.event = event @@ -244,15 +244,16 @@ class EventSourceResponse(Response): self._ping_interval = value async def _ping(self, send: Send) -> None: - # Legacy proxy servers are known to, in certain cases, drop HTTP connections after a short timeout. - # To protect against such proxy servers, authors can send a custom (ping) event - # every 15 seconds or so. + # Legacy proxy servers are known to, in certain cases, drop HTTP connections + # after a short timeout. To protect against such proxy servers, authors can + # send a custom (ping) event every 15 seconds or so. # Alternatively one can send periodically a comment line # (one starting with a ':' character) while self.active: await anyio.sleep(self._ping_interval) if self.ping_message_factory: - assert isinstance(self.ping_message_factory, Callable) # type: ignore # https://github.com/python/mypy/issues/6864 + # https://github.com/python/mypy/issues/6864 + assert isinstance(self.ping_message_factory, Callable) # type: ignore ping = ( ServerSentEvent(datetime.utcnow(), event="ping").encode() if self.ping_message_factory is None diff --git a/app/lightning/docs.py b/app/lightning/docs.py index 4f52f75..58432cd 100644 --- a/app/lightning/docs.py +++ b/app/lightning/docs.py @@ -1,3 +1,5 @@ +# ruff: noqa: E501 + tx_id_desc = """ Unique identifier for this transaction. diff --git a/app/lightning/impl/cln_grpc.py b/app/lightning/impl/cln_grpc.py index bd7f99b..86a0dca 100644 --- a/app/lightning/impl/cln_grpc.py +++ b/app/lightning/impl/cln_grpc.py @@ -1,7 +1,6 @@ import asyncio import json import sys -import time from typing import AsyncGenerator, List, Optional import grpc @@ -29,7 +28,6 @@ from app.lightning.models import ( LnInfo, LnInitState, NewAddressInput, - OnchainAddressType, OnChainTransaction, Payment, PaymentRequest, @@ -55,11 +53,14 @@ async def _make_local_call(cmd: str): ) stdout, stderr = await proc.communicate() - if stderr != None and stderr != b"": + if stderr is not None and stderr != b"": err = stderr.decode() if "lightning-cli: Connecting to 'lightning-rpc': Permission denied" in err: logger.critical( - "Unable to connect to lightning-cli: Permission denied. Is the lightning-rpc socket readable for the API user?" + ( + "Unable to connect to lightning-cli: Permission denied. " + "Is the lightning-rpc socket readable for the API user?" + ) ) raise HTTPException( @@ -69,19 +70,28 @@ async def _make_local_call(cmd: str): if "lightning-cli: Moving into" in err and "No such file or directory" in err: logger.critical( - "Unable to connect to lightning-cli: No such file or directory. Is the lightning-rpc socket available to the API user?" + ( + "Unable to connect to lightning-cli: No such file or directory. " + "Is the lightning-rpc socket available to the API user?" + ) ) raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Unable to connect to lightning-cli: API Can't access lightning-cli.", + detail=( + "Unable to connect to lightning-cli: " + "API Can't access lightning-cli.", + ), ) logger.critical(f"Unable to connect to lightning-cli: {err}") raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Unable to connect to lightning-cli: Unknown error. Please consult the logs.", + detail=( + "Unable to connect to lightning-cli: " + "Unknown error. Please consult the logs." + ), ) return stdout, stderr @@ -108,7 +118,10 @@ class LnNodeCLNgRPC(LightningNodeBase): logger.info("Establishing a connection to the CLN daemon ...") if self._initialized: logger.warning( - "Connection already initialized. This function must not be called twice." + ( + "Connection already initialized. " + "This function must not be called twice." + ) ) yield InitLnRepoUpdate(state=LnInitState.DONE) @@ -241,7 +254,10 @@ class LnNodeCLNgRPC(LightningNodeBase): self, successful_only: bool, index_offset: int, max_tx: int, reversed: bool ) -> List[GenericTx]: logger.trace( - f"list_all_tx(successful_only={successful_only}, index_offset={index_offset}, max_tx={max_tx}, reversed={reversed})" + ( + f"list_all_tx(successful_only={successful_only}, " + f"index_offset={index_offset}, max_tx={max_tx}, reversed={reversed})" + ) ) list_invoice_req = ln.ListinvoicesRequest() @@ -300,12 +316,12 @@ class LnNodeCLNgRPC(LightningNodeBase): if reversed: tx.reverse() - l = len(tx) - for invoice in range(l): + tx_length = len(tx) + for invoice in range(tx_length): tx[invoice].index = invoice if max_tx == 0: - max_tx = l + max_tx = tx_length return tx[index_offset : index_offset + max_tx] except grpc.aio._call.AioRpcError as error: @@ -359,7 +375,9 @@ class LnNodeCLNgRPC(LightningNodeBase): if len(res) == 0: raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="No response from CLN while trying to list account income events", + detail=( + "No response from CLN while trying to list account income events" + ), ) decoded = res[0].decode() @@ -428,7 +446,11 @@ class LnNodeCLNgRPC(LightningNodeBase): reversed: bool, ): logger.trace( - f"list_payments(include_incomplete={include_incomplete}, index_offset{index_offset}, max_payments={max_payments}, reversed={reversed})" + ( + f"list_payments(include_incomplete={include_incomplete}, " + f"index_offset{index_offset}, max_payments={max_payments}, " + f"reversed={reversed})" + ) ) try: req = ln.ListpaysRequest() @@ -463,7 +485,10 @@ class LnNodeCLNgRPC(LightningNodeBase): is_keysend: bool = False, ) -> Invoice: logger.trace( - f"add_invoice(value_msat={value_msat}, memo={memo}, expiry={expiry}, is_keysend={is_keysend})" + ( + f"add_invoice(value_msat={value_msat}, memo={memo}, " + f"expiry={expiry}, is_keysend={is_keysend})" + ) ) if value_msat < 0: @@ -510,36 +535,34 @@ class LnNodeCLNgRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def decode_pay_request(self, pay_req: str) -> PaymentRequest: logger.trace(f"decode_pay_request(pay_req={pay_req})") - try: - res = await _make_local_call(f"decodepay bolt11={pay_req}") - if not res: - raise HTTPException( - status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Unknown CLN error decoding pay request", - ) + res = await _make_local_call(f"decodepay bolt11={pay_req}") - if len(res) == 0: - raise HTTPException( - status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="No response from CLN decoding pay request", - ) + if not res: + raise HTTPException( + status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Unknown CLN error decoding pay request", + ) - decoded = res[0].decode() + if len(res) == 0: + raise HTTPException( + status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="No response from CLN decoding pay request", + ) - if "Invalid bolt11: Bad bech32 string" in decoded: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail="Invalid bolt11: Bad bech32 string", - ) + decoded = res[0].decode() - return PaymentRequest.from_cln_json(json.loads(decoded)) - except e: - raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str()) + if "Invalid bolt11: Bad bech32 string" in decoded: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Invalid bolt11: Bad bech32 string", + ) + + return PaymentRequest.from_cln_json(json.loads(decoded)) @logger.catch(exclude=(HTTPException,)) async def get_fee_revenue(self) -> FeeRevenue: - logger.trace(f"get_fee_revenue()") + logger.trace("get_fee_revenue()") try: # status 1 == "settled" req = ln.ListforwardsRequest(status=1) @@ -575,13 +598,13 @@ class LnNodeCLNgRPC(LightningNodeBase): logger.trace(f"send_coins(input={input})") fee_rate: lnp.Feerate = None - if input.sat_per_vbyte != None and input.sat_per_vbyte > 0: + if input.sat_per_vbyte is not None and input.sat_per_vbyte > 0: fee_rate = lnp.Feerate(perkw=input.sat_per_vbyte) - elif input.target_conf != None and input.target_conf == 1: + elif input.target_conf is not None and input.target_conf == 1: fee_rate = lnp.Feerate(urgent=True) - elif input.target_conf != None and input.target_conf >= 2: + elif input.target_conf is not None and input.target_conf >= 2: fee_rate = lnp.Feerate(normal=True) - elif input.target_conf != None and input.target_conf >= 10: + elif input.target_conf is not None and input.target_conf >= 10: fee_rate = lnp.Feerate(slow=True) try: @@ -589,7 +612,9 @@ class LnNodeCLNgRPC(LightningNodeBase): if len(funds.outputs) == 0: raise HTTPException( status.HTTP_412_PRECONDITION_FAILED, - detail=f"Could not afford {input.amount}sat. No UTXOs available at all", + detail=( + f"Could not afford {input.amount}sat. No UTXOs available at all" + ), ) utxos = [] @@ -601,7 +626,12 @@ class LnNodeCLNgRPC(LightningNodeBase): if not input.send_all and max_amt <= input.amount: raise HTTPException( status.HTTP_412_PRECONDITION_FAILED, - detail=f"Could not afford {input.amount}sat. Not enough funds available", + detail=( + ( + f"Could not afford {input.amount}sat. " + "Not enough funds available" + ) + ), ) amt = lnp.AmountOrAll(amount=lnp.Amount(msat=input.amount * 1000)) @@ -626,7 +656,10 @@ class LnNodeCLNgRPC(LightningNodeBase): if details and details.find("Could not parse destination address") > -1: raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="Could not parse destination address, destination should be a valid address.", + detail=( + "Could not parse destination address, " + " destination should be a valid address." + ), ) elif ( details @@ -635,7 +668,10 @@ class LnNodeCLNgRPC(LightningNodeBase): ): raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Server tried to use a reserved UTXO. Please submit an issue to the BlitzAPI repository.", + detail=( + "Server tried to use a reserved UTXO. " + "Please submit an issue to the BlitzAPI repository." + ), ) elif details and details.find("insufficient funds available") > -1: raise HTTPException(status.HTTP_412_PRECONDITION_FAILED, detail=details) @@ -651,10 +687,13 @@ class LnNodeCLNgRPC(LightningNodeBase): amount_msat: Optional[int] = None, ) -> Payment: logger.trace( - f"send_payment(pay_req={pay_req}, timeout_seconds={timeout_seconds}, fee_limit_msat={fee_limit_msat}, amount_msat={amount_msat})" + ( + f"send_payment(pay_req={pay_req}, timeout_seconds={timeout_seconds}, " + f"fee_limit_msat={fee_limit_msat}, amount_msat={amount_msat})" + ) ) - amt = lnp.Amount(msat=amount_msat) if amount_msat != None else None + amt = lnp.Amount(msat=amount_msat) if amount_msat is not None else None fee_limit = lnp.Amount(msat=fee_limit_msat) req = ln.PayRequest( bolt11=pay_req, @@ -692,7 +731,12 @@ class LnNodeCLNgRPC(LightningNodeBase): if "amount_msat parameter unnecessary" in details: raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="amount must not be specified when paying a non-zero amount invoice", + detail=( + ( + "amount must not be specified when paying " + "a non-zero amount invoice" + ) + ), ) generic_grpc_error_handler(error) @@ -701,7 +745,7 @@ class LnNodeCLNgRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def get_ln_info(self) -> LnInfo: - logger.trace(f"get_ln_info()") + logger.trace("get_ln_info()") req = ln.GetinfoRequest() try: @@ -723,7 +767,7 @@ class LnNodeCLNgRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def unlock_wallet(self, password: str) -> bool: - logger.trace(f"unlock_wallet(password=wedontlogpasswords)") + logger.trace("unlock_wallet(password=wedontlogpasswords)") # Core Lightning doesn't lock wallets, # so we don't need to do anything here @@ -731,7 +775,7 @@ class LnNodeCLNgRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def listen_invoices(self) -> AsyncGenerator[Invoice, None]: - logger.trace(f"listen_invoices()") + logger.trace("listen_invoices()") try: lastpay_index = 0 invoices = await self.list_invoices( @@ -767,7 +811,7 @@ class LnNodeCLNgRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def listen_forward_events(self) -> ForwardSuccessEvent: - logger.trace(f"listen_forward_events()") + logger.trace("listen_forward_events()") # CLN has no subscription to forwarded events. # We must poll instead. @@ -796,7 +840,7 @@ class LnNodeCLNgRPC(LightningNodeBase): try: req = ln.ConnectRequest(id=uri) - res = await self._cln_stub.ConnectPeer(req) + await self._cln_stub.ConnectPeer(req) return True except grpc.aio._call.AioRpcError as error: @@ -860,7 +904,10 @@ class LnNodeCLNgRPC(LightningNodeBase): self, local_funding_amount: int, node_URI: str, target_confs: int ) -> str: logger.trace( - f"channel_open(local_funding_amount={local_funding_amount}, node_URI={node_URI}, target_confs={target_confs})" + ( + f"channel_open(local_funding_amount={local_funding_amount}, " + f"node_URI={node_URI}, target_confs={target_confs})" + ) ) await self.connect_peer(node_URI) @@ -901,14 +948,21 @@ class LnNodeCLNgRPC(LightningNodeBase): if "Unknown peer" in details: raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="We where able to connect to the peer but CLN can't find it when opening a channel.", + detail=( + "We where able to connect to the peer but CLN " + "can't find it when opening a channel." + ), ) if "Owning subdaemon openingd died" in details: # https://github.com/ElementsProject/lightning/issues/2798#issuecomment-511205719 raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="Likely the peer didn't like our channel opening proposal and disconnected from us.", + detail=( + "Likely the peer didn't like our channel " + "opening proposal and disconnected from us. More info:" + "https://github.com/ElementsProject/lightning/issues/2798#issuecomment-511205719" + ), ) if ( @@ -926,7 +980,7 @@ class LnNodeCLNgRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def channel_list(self) -> List[Channel]: - logger.trace(f"channel_list()") + logger.trace("channel_list()") try: res = await self._cln_stub.ListFunds(ln.ListfundsRequest()) diff --git a/app/lightning/impl/cln_jrpc.py b/app/lightning/impl/cln_jrpc.py index c300f15..23e84ea 100644 --- a/app/lightning/impl/cln_jrpc.py +++ b/app/lightning/impl/cln_jrpc.py @@ -2,20 +2,15 @@ import asyncio import json import os import sys -import time from typing import AsyncGenerator, Dict, List, Optional, Union import decouple -import grpc from decouple import config from fastapi.exceptions import HTTPException from loguru import logger from starlette import status -import app.lightning.impl.protos.cln.node_pb2 as ln -import app.lightning.impl.protos.cln.node_pb2_grpc as clnrpc -import app.lightning.impl.protos.cln.primitives_pb2 as lnp -from app.api.utils import SSE, broadcast_sse_msg, config_get_hex_str, next_push_id +from app.api.utils import SSE, broadcast_sse_msg, next_push_id from app.bitcoind.utils import bitcoin_rpc_async from app.lightning.exceptions import NodeNotFoundError from app.lightning.impl.cln_utils import ( @@ -35,7 +30,6 @@ from app.lightning.models import ( LnInfo, LnInitState, NewAddressInput, - OnchainAddressType, OnChainTransaction, Payment, PaymentRequest, @@ -44,7 +38,7 @@ from app.lightning.models import ( TxStatus, WalletBalance, ) -from app.lightning.utils import alias_or_empty, generic_grpc_error_handler +from app.lightning.utils import alias_or_empty _WAIT_ANY_INVOICE_ID = 0 _SOCKET_BUFFER_SIZE_LIMIT = 1024 * 1024 * 10 # 10 MB @@ -74,7 +68,10 @@ class LnNodeCLNjRPC(LightningNodeBase): logger.info("Initializing CLN JSON-RPC implementation.") if self._initialized: logger.warning( - "Connection already initialized. This function must not be called twice." + ( + "Connection already initialized. This function must not be " + "called twice." + ) ) yield InitLnRepoUpdate(state=LnInitState.DONE) @@ -85,7 +82,10 @@ class LnNodeCLNjRPC(LightningNodeBase): except decouple.UndefinedValueError as e: logger.debug(e) logger.error( - f"CLN JSON-RPC implementation set, but cln_jrpc_path is missing from the config file." + ( + "CLN JSON-RPC implementation set, but cln_jrpc_path is missing " + "from the config file." + ) ) sys.exit(1) @@ -123,7 +123,10 @@ class LnNodeCLNjRPC(LightningNodeBase): sys.exit(1) logger.success( - f"Connected to CLN node with alias {info.alias} and pubkey {info.identity_pubkey[:10]}...{info.identity_pubkey[-10:]}" + ( + f"Connected to CLN node with alias {info.alias} and " + f"pubkey {info.identity_pubkey[:10]}...{info.identity_pubkey[-10:]}" + ) ) yield InitLnRepoUpdate(state=LnInitState.DONE) @@ -260,12 +263,12 @@ class LnNodeCLNjRPC(LightningNodeBase): if reversed: tx.reverse() - l = len(tx) - for invoice in range(l): + num_tx = len(tx) + for invoice in range(num_tx): tx[invoice].index = invoice if max_tx == 0: - max_tx = l + max_tx = num_tx return tx[index_offset : index_offset + max_tx] @@ -278,7 +281,10 @@ class LnNodeCLNjRPC(LightningNodeBase): reversed: bool, ): logger.trace( - f"list_invoices({pending_only}, {index_offset}, {num_max_invoices}, {reversed})" + ( + f"list_invoices({pending_only}, {index_offset}, " + f"{num_max_invoices}, {reversed})" + ) ) res = await self._send_request("listinvoices") @@ -286,7 +292,7 @@ class LnNodeCLNjRPC(LightningNodeBase): if "error" in res: self._raise_internal_server_error("listing invoices", res) - if not "result" in res or not "invoices" in res["result"]: + if "result" not in res or "invoices" not in res["result"]: logger.error(f"Got no error and no invoices key result: {res}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -374,14 +380,15 @@ class LnNodeCLNjRPC(LightningNodeBase): reversed: bool, ): logger.trace( - f"list_payments({include_incomplete}, {index_offset}, {max_payments}, {reversed})" + f"list_payments({include_incomplete}, {index_offset}, " + f"{max_payments}, {reversed})" ) res = await self._send_request("listpays") if "error" in res: self._raise_internal_server_error("listing payments", res) - if not "result" in res or not "pays" in res["result"]: + if "result" not in res or "pays" not in res["result"]: logger.error(f"Got no error and no pays key result: {res}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -424,7 +431,7 @@ class LnNodeCLNjRPC(LightningNodeBase): params = [value_msat, pid, memo, expiry] res = await self._send_request("invoice", params) - if not "error" in res: + if "error" not in res: res = res["result"] return Invoice( payment_request=res["bolt11"], @@ -456,7 +463,7 @@ class LnNodeCLNjRPC(LightningNodeBase): params = [pay_req] res = await self._send_request("decodepay", params) - if not "error" in res: + if "error" not in res: res = res["result"] req = PaymentRequest.from_cln_json(res) self._bolt11_cache[pay_req] = req @@ -475,7 +482,7 @@ class LnNodeCLNjRPC(LightningNodeBase): params = ["settled"] # only list settled forwards res = await self._send_request("listforwards", params) - if not "error" in res: + if "error" not in res: res = res["result"] day, week, month, year, total = cln_classify_fee_revenue(res["forwards"]) @@ -493,7 +500,7 @@ class LnNodeCLNjRPC(LightningNodeBase): async def new_address(self, input: NewAddressInput) -> str: res = await self._send_request("newaddr") - if not "error" in res: + if "error" not in res: res = res["result"] return res["bech32"] @@ -514,14 +521,14 @@ class LnNodeCLNjRPC(LightningNodeBase): params = [input.address, amt, fee_rate] res = await self._send_request("withdraw", params) - if not "error" in res: + if "error" not in res: res = res["result"] r = SendCoinsResponse.from_cln_json(res, input) await broadcast_sse_msg(SSE.LN_ONCHAIN_PAYMENT_STATUS, r.dict()) return r - if not "message" in res["error"]: + if "message" not in res["error"]: raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Unknown error: {res}", @@ -533,7 +540,10 @@ class LnNodeCLNjRPC(LightningNodeBase): if details and details.find("Could not parse destination address") > -1: raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="Could not parse destination address, destination should be a valid address.", + detail=( + "Could not parse destination address, destination should be " + "a valid address." + ), ) elif ( details @@ -542,7 +552,10 @@ class LnNodeCLNjRPC(LightningNodeBase): ): raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Server tried to use a reserved UTXO. Please submit an issue to the BlitzAPI repository.", + detail=( + "Server tried to use a reserved UTXO. Please submit an " + "issue to the BlitzAPI repository." + ), ) elif details and details.find("Could not afford ") > -1: raise HTTPException(status.HTTP_412_PRECONDITION_FAILED, detail=details) @@ -591,7 +604,7 @@ class LnNodeCLNjRPC(LightningNodeBase): } res = await self._send_request("pay", params) - if not "error" in res: + if "error" not in res: res = res["result"] return Payment.from_cln_jrpc(res) @@ -622,7 +635,9 @@ class LnNodeCLNjRPC(LightningNodeBase): ): raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="amount must not be specified when paying a non-zero amount invoice", + detail=( + "amount must not be specified when paying a non-zero amount invoice" + ), ) logger.error(message) @@ -641,7 +656,7 @@ class LnNodeCLNjRPC(LightningNodeBase): @logger.catch(exclude=(HTTPException,)) async def unlock_wallet(self, password: str) -> bool: - logger.trace(f"unlock_wallet(password=wedontlogpasswords)") + logger.trace("unlock_wallet(password=wedontlogpasswords)") # Core Lightning doesn't lock wallets, # so we don't need to do anything here @@ -700,9 +715,13 @@ class LnNodeCLNjRPC(LightningNodeBase): self, local_funding_amount: int, node_URI: str, target_confs: int ) -> str: logger.trace( - f"channel_open(local_funding_amount={local_funding_amount}, node_URI={node_URI}, target_confs={target_confs})" + ( + f"channel_open(local_funding_amount={local_funding_amount}, " + f"node_URI={node_URI}, target_confs={target_confs})" + ) ) - # fundchannel id amount [feerate] [announce] [minconf] [utxos] [push_msat] [close_to] [request_amt] [compact_lease] [reserve] + # fundchannel id amount [feerate] [announce] [minconf] [utxos] [push_msat] + # [close_to] [request_amt] [compact_lease] [reserve] await self.connect_peer(node_URI) fee_rate = calc_fee_rate_str(None, target_confs) @@ -731,14 +750,20 @@ class LnNodeCLNjRPC(LightningNodeBase): if "Unknown peer" in message: raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="We where able to connect to the peer but CLN can't find it when opening a channel.", + detail=( + "We where able to connect to the peer but CLN can't find it " + "when opening a channel." + ), ) if "Owning subdaemon openingd died" in message: # https://github.com/ElementsProject/lightning/issues/2798#issuecomment-511205719 raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="Likely the peer didn't like our channel opening proposal and disconnected from us.", + detail=( + "Likely the peer didn't like our channel opening proposal " + "and disconnected from us." + ), ) if ( @@ -843,14 +868,12 @@ class LnNodeCLNjRPC(LightningNodeBase): res = await self._send_request("connect", [uri]) - if not "error" in res: + if "error" not in res: return True message = res["error"]["message"] if "All addresses failed" in message: - message = details.split('message: "')[1] - raise HTTPException( status.HTTP_400_BAD_REQUEST, detail=message, @@ -955,7 +978,10 @@ class LnNodeCLNjRPC(LightningNodeBase): if i.state is not InvoiceState.SETTLED: continue - if i.settle_index != None and i.settle_index < self.lastpay_index: + if ( + i.settle_index is not None + and i.settle_index < self.lastpay_index + ): break self.lastpay_index = i.settle_index diff --git a/app/lightning/impl/cln_utils.py b/app/lightning/impl/cln_utils.py index df601fe..0e1b79c 100644 --- a/app/lightning/impl/cln_utils.py +++ b/app/lightning/impl/cln_utils.py @@ -11,13 +11,13 @@ def calc_fee_rate_str(sat_per_vbyte, target_conf) -> str: # internal estimates: normal is the default. fee_rate: str = "" - if sat_per_vbyte != None and sat_per_vbyte > 0: + if sat_per_vbyte is not None and sat_per_vbyte > 0: fee_rate = f"{sat_per_vbyte}perkw" - elif target_conf != None and target_conf == 1: + elif target_conf is not None and target_conf == 1: fee_rate = "urgent" - elif target_conf != None and target_conf >= 2: + elif target_conf is not None and target_conf >= 2: fee_rate = "normal" - elif target_conf != None and target_conf >= 10: + elif target_conf is not None and target_conf >= 10: fee_rate = "slow" return fee_rate diff --git a/app/lightning/impl/lnd_grpc.py b/app/lightning/impl/lnd_grpc.py index 8823416..c33497e 100644 --- a/app/lightning/impl/lnd_grpc.py +++ b/app/lightning/impl/lnd_grpc.py @@ -42,9 +42,9 @@ from app.lightning.utils import alias_or_empty @logger.catch(exclude=(HTTPException,)) def _check_if_locked(error): - logger.debug(f"logger._check_if_locked()") + logger.debug("logger._check_if_locked()") - if error.details() != None and error.details().find("wallet locked") > -1: + if error.details() is not None and error.details().find("wallet locked") > -1: raise HTTPException( status.HTTP_423_LOCKED, detail="Wallet is locked. Unlock via /lightning/unlock-wallet", @@ -160,7 +160,10 @@ This will show more debug information. await self._init_queue.put( InitLnRepoUpdate( state=LnInitState.BOOTSTRAPPING, - msg="Connected but waiting to start, RPC services not available", + msg=( + "Connected but waiting to start, RPC services " + "not available" + ), ) ) await temp_channel.close() @@ -168,7 +171,10 @@ This will show more debug information. elif "wallet locked, unlock it to enable full RPC access" in details: if not wallet_locked_sent: logger.info( - "Wallet is locked. Unlock by calling /lightning/unlock-wallet" + ( + "Wallet is locked. Unlock by calling " + "/lightning/unlock-wallet" + ) ) wallet_locked_sent = True @@ -182,14 +188,17 @@ This will show more debug information. await temp_channel.close() temp_channel = None elif ( - "the RPC server is in the process of starting up, but not yet ready to accept calls" - in details - ): + "the RPC server is in the process of starting up, but not yet " + "ready to accept calls" + ) in details: # message from LND AFTER unlocking the wallet await self._init_queue.put( InitLnRepoUpdate( state=LnInitState.BOOTSTRAPPING_AFTER_UNLOCK, - msg="The RPC server is in the process of starting up, but not yet ready to accept calls", + msg=( + "The RPC server is in the process of starting up, " + "but not yet ready to accept calls" + ), ) ) else: @@ -207,7 +216,10 @@ This will show more debug information. if self._initialized: logger.warning( - "Connection already initialized. This function must not be called twice." + ( + "Connection already initialized. " + "This function must not be called twice." + ) ) yield InitLnRepoUpdate(state=LnInitState.DONE) @@ -248,7 +260,7 @@ This will show more debug information. ): task.cancel() - if self._channel == None: + if self._channel is None: # if res == _API_WALLET_UNLOCK_EVENT the endpoint function will have # created the channel for us. self._create_stubs() @@ -295,7 +307,10 @@ This will show more debug information. self, successful_only: bool, index_offset: int, max_tx: int, reversed: bool ) -> List[GenericTx]: logger.trace( - f"logger.list_all_tx(successful_only={successful_only}, index_offset={index_offset}, max_tx={max_tx}, reversed={reversed})" + ( + f"logger.list_all_tx(successful_only={successful_only}, " + f"index_offset={index_offset}, max_tx={max_tx}, reversed={reversed})" + ) ) # TODO: find a better caching strategy @@ -348,12 +363,12 @@ This will show more debug information. if reversed: tx.reverse() - l = len(tx) - for i in range(l): + current_tx = len(tx) + for i in range(current_tx): tx[i].index = i if max_tx == 0: - max_tx = l + max_tx = current_tx return tx[index_offset : index_offset + max_tx] except grpc.aio._call.AioRpcError as error: @@ -410,7 +425,11 @@ This will show more debug information. reversed: bool, ): logger.trace( - f"logger.list_payments(include_incomplete={include_incomplete}, index_offset{index_offset}, max_payments={max_payments}, reversed={reversed})" + ( + f"logger.list_payments(include_incomplete={include_incomplete}, " + f"index_offset{index_offset}, max_payments={max_payments}, " + f"reversed={reversed})" + ) ) try: @@ -437,7 +456,10 @@ This will show more debug information. is_keysend: bool = False, ) -> Invoice: logger.trace( - f"logger.add_invoice(value_msat={value_msat}, memo={memo}, expiry={expiry}, is_keysend={is_keysend})" + ( + f"logger.add_invoice(value_msat={value_msat}, memo={memo}, " + f"expiry={expiry}, is_keysend={is_keysend})" + ) ) try: @@ -482,7 +504,7 @@ This will show more debug information. except grpc.aio._call.AioRpcError as error: _check_if_locked(error) if ( - error.details() != None + error.details() is not None and error.details().find("checksum failed.") > -1 ): raise HTTPException( @@ -495,7 +517,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def get_fee_revenue(self) -> FeeRevenue: - logger.trace(f"logger.get_fee_revenue()") + logger.trace("logger.get_fee_revenue()") req = ln.FeeReportRequest() res = await self._lnd_stub.FeeReport(req) @@ -552,7 +574,10 @@ This will show more debug information. if details and details.find("invalid bech32 string") > -1: raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="Could not parse destination address, destination should be a valid address.", + detail=( + "Could not parse destination address, destination " + "should be a valid address." + ), ) elif details and details.find("insufficient funds available") > -1: raise HTTPException(status.HTTP_412_PRECONDITION_FAILED, detail=details) @@ -570,7 +595,11 @@ This will show more debug information. amount_msat: Optional[int] = None, ) -> Payment: logger.trace( - f"logger.send_payment(pay_req={pay_req}, timeout_seconds={timeout_seconds}, fee_limit_msat={fee_limit_msat}, amount_msat={amount_msat})" + ( + f"logger.send_payment(pay_req={pay_req}, " + f"timeout_seconds={timeout_seconds}, fee_limit_msat={fee_limit_msat}, " + f"amount_msat={amount_msat})" + ) ) try: @@ -589,14 +618,14 @@ This will show more debug information. except grpc.aio._call.AioRpcError as error: _check_if_locked(error) if ( - error.details() != None + error.details() is not None and error.details().find("invalid bech32 string") > -1 ): raise HTTPException( status.HTTP_400_BAD_REQUEST, detail="Invalid payment request string" ) elif ( - error.details() != None + error.details() is not None and error.details().find("OPENSSL_internal:CERTIFICATE_VERIFY_FAILED.") > -1 ): @@ -605,7 +634,7 @@ This will show more debug information. detail="Invalid LND credentials. SSL certificate verify failed.", ) elif ( - error.details() != None + error.details() is not None and error.details().find( "amount must be specified when paying a zero amount invoice" ) @@ -616,18 +645,21 @@ This will show more debug information. detail="amount must be specified when paying a zero amount invoice", ) elif ( - error.details() != None + error.details() is not None and error.details().find( - "amount must not be specified when paying a non-zero amount invoice" + "amount must not be specified when paying a non-zero amount invoice" ) > -1 ): raise HTTPException( status.HTTP_400_BAD_REQUEST, - detail="amount must not be specified when paying a non-zero amount invoice", + detail=( + "amount must not be specified when paying a non-zero " + "amount invoice" + ), ) elif ( - error.details() != None + error.details() is not None and error.details().find("invoice is already paid") > -1 ): raise HTTPException( @@ -640,7 +672,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def get_ln_info(self) -> LnInfo: - logger.trace(f"logger.get_ln_info()") + logger.trace("logger.get_ln_info()") if not self._initialized: raise HTTPException( @@ -659,7 +691,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def _wait_wallet_fully_ready(self): - logger.trace(f"logger._wait_wallet_fully_ready()") + logger.trace("logger._wait_wallet_fully_ready()") # This must only be called after unlocking the wallet. @@ -667,22 +699,28 @@ This will show more debug information. try: info = await self._lnd_stub.GetInfo(ln.GetInfoRequest()) - if info != None: + if info is not None: logger.debug( - f"logger._wait_wallet_fully_ready() breaking out of wait ready loop" + ( + "logger._wait_wallet_fully_ready() breaking out of " + "wait ready loop" + ) ) break except grpc.aio._call.AioRpcError as error: details = error.details() if ( - "the RPC server is in the process of starting up, but not yet ready to accept calls" - in details - ): + "the RPC server is in the process of starting up, but not yet " + "ready to accept calls" + ) in details: # message from LND AFTER unlocking the wallet await self._init_queue.put( InitLnRepoUpdate( state=LnInitState.BOOTSTRAPPING_AFTER_UNLOCK, - msg="The RPC server is in the process of starting up, but not yet ready to accept calls", + msg=( + "The RPC server is in the process of starting up, " + "but not yet ready to accept calls" + ), ) ) await asyncio.sleep(0.1) @@ -692,7 +730,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def unlock_wallet(self, password: str) -> bool: - logger.trace(f"logger.unlock_wallet(password=wedontlogpasswords)") + logger.trace("logger.unlock_wallet(password=wedontlogpasswords)") try: if self._channel is None: @@ -718,7 +756,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def listen_invoices(self) -> AsyncGenerator[Invoice, None]: - logger.trace(f"logger.listen_invoices()") + logger.trace("logger.listen_invoices()") request = ln.InvoiceSubscription() try: @@ -732,7 +770,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def listen_forward_events(self) -> ForwardSuccessEvent: - logger.trace(f"logger.listen_forward_events()") + logger.trace("logger.listen_forward_events()") request = router.SubscribeHtlcEventsRequest() try: @@ -744,7 +782,7 @@ This will show more debug information. evt = str(e) failed_event = "forward_fail_event" in evt or "link_fail_event" in evt - if not e.incoming_htlc_id in _fwd_cache and not failed_event: + if e.incoming_htlc_id not in _fwd_cache and not failed_event: _fwd_cache[e.incoming_htlc_id] = e elif e.incoming_htlc_id in _fwd_cache and not failed_event: if hasattr(e, "settle_event") and len(e.settle_event.preimage) > 0: @@ -775,7 +813,10 @@ This will show more debug information. self, local_funding_amount: int, node_URI: str, target_confs: int ) -> str: logger.trace( - f"logger.channel_open(local_funding_amount={local_funding_amount}, node_URI={node_URI}, target_confs={target_confs})" + ( + f"logger.channel_open(local_funding_amount={local_funding_amount}, " + f"node_URI={node_URI}, target_confs={target_confs})" + ) ) try: @@ -792,7 +833,7 @@ This will show more debug information. await self._lnd_stub.ConnectPeer(r) except grpc.aio._call.AioRpcError as error: if ( - error.details() != None + error.details() is not None and error.details().find("already connected to peer") > -1 ): logger.debug(f"already connected to peer {pubkey}") @@ -806,7 +847,6 @@ This will show more debug information. target_conf=target_confs, ) async for response in self._lnd_stub.OpenChannel(r): - # TODO: this is still some bytestring that needs correct conversion to a string txid (ok OK for now) return str(response.chan_pending.txid.hex()) except grpc.aio._call.AioRpcError as error: @@ -835,7 +875,7 @@ This will show more debug information. @logger.catch(exclude=(HTTPException,)) async def channel_list(self) -> List[Channel]: - logger.trace(f"logger.channel_list()") + logger.trace("logger.channel_list()") try: request = ln.ListChannelsRequest() @@ -871,7 +911,7 @@ This will show more debug information. f"logger.channel_close(channel_id={channel_id}, force_close={force_close})" ) - if not ":" in channel_id: + if ":" not in channel_id: raise ValueError("channel_id must contain : for lnd") try: @@ -886,7 +926,6 @@ This will show more debug information. target_conf=6, ) async for response in self._lnd_stub.CloseChannel(request): - # TODO: this is still some bytestring that needs correct conversion to a string txid (ok OK for now) return str(response.close_pending.txid.hex()) except grpc.aio._call.AioRpcError as error: diff --git a/app/lightning/impl/specializations/blitz_common.py b/app/lightning/impl/specializations/blitz_common.py index 3d5f6ba..afbba1f 100644 --- a/app/lightning/impl/specializations/blitz_common.py +++ b/app/lightning/impl/specializations/blitz_common.py @@ -1,6 +1,7 @@ import asyncio from fastapi.exceptions import HTTPException +from loguru import logger from starlette import status from app.api.utils import call_script2, redis_get @@ -22,8 +23,11 @@ async def blitz_cln_unlock(network: str, password: str) -> bool: ) if res.return_code == 0: - logging.debug( - f"CLN_GRPC_BLITZ: Unlock script successfully called via API. Waiting for Redis {key} to be set." + logger.debug( + ( + "CLN_GRPC_BLITZ: Unlock script successfully called via API. Waiting " + f"for Redis {key} to be set." + ) ) # success: exit 0 @@ -38,8 +42,12 @@ async def blitz_cln_unlock(network: str, password: str) -> bool: await asyncio.sleep(INTERVAL) total_wait_time += INTERVAL - logging.debug( - f"CLN_GRPC_BLITZ: Unlock script called successfully but redis key {key} indicates that RaspiBlitz is still locked. Stopped watching after polling for 60s for an unlock signal." + logger.debug( + ( + "CLN_GRPC_BLITZ: Unlock script called successfully but redis key " + f"{key} indicates that RaspiBlitz is still locked. Stopped watching " + "after polling for 60s for an unlock signal." + ) ) raise HTTPException( @@ -47,12 +55,14 @@ async def blitz_cln_unlock(network: str, password: str) -> bool: detail="Unknown error while trying to unlock.", ) elif res.return_code == 1: - logging.error("CLN_GRPC_BLITZ: Unknown error while trying to unlock.") - logging.error(f"CLN_GRPC_BLITZ: {res.__str__()}") + logger.error("CLN_GRPC_BLITZ: Unknown error while trying to unlock.") + logger.error(f"CLN_GRPC_BLITZ: {res.__str__()}") raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Unknown error while trying to unlock. See the API logs for more info.", + detail=( + "Unknown error while trying to unlock. See the API logs for more info." + ), ) elif res.return_code == 2: # wrong password: exit 2 diff --git a/app/lightning/impl/specializations/cln_grpc_blitz.py b/app/lightning/impl/specializations/cln_grpc_blitz.py index 5301dfe..0c4189f 100644 --- a/app/lightning/impl/specializations/cln_grpc_blitz.py +++ b/app/lightning/impl/specializations/cln_grpc_blitz.py @@ -6,7 +6,7 @@ from fastapi.exceptions import HTTPException from loguru import logger from starlette import status -from app.api.utils import call_script2, redis_get +from app.api.utils import redis_get from app.lightning.impl.cln_grpc import LnNodeCLNgRPC from app.lightning.impl.specializations.blitz_common import blitz_cln_unlock from app.lightning.models import ( @@ -29,7 +29,8 @@ from app.lightning.models import ( class LnNodeCLNgRPCBlitz(LnNodeCLNgRPC): - # RaspiBlitz implements a lock function on top of CLN, so we need to implement this on Blitz only. + # RaspiBlitz implements a lock function on top of CLN, so we need to implement + # this on Blitz only. _unlocked = False @@ -196,7 +197,7 @@ class LnNodeCLNgRPCBlitz(LnNodeCLNgRPC): return await super().channel_close(channel_id, force_close) def _check_if_locked(self): - logger.trace(f"_check_if_locked()") + logger.trace("_check_if_locked()") if not self._unlocked: raise HTTPException( diff --git a/app/lightning/impl/specializations/cln_jrpc_blitz.py b/app/lightning/impl/specializations/cln_jrpc_blitz.py index 4b3b056..cf67e54 100644 --- a/app/lightning/impl/specializations/cln_jrpc_blitz.py +++ b/app/lightning/impl/specializations/cln_jrpc_blitz.py @@ -6,7 +6,7 @@ from fastapi.exceptions import HTTPException from loguru import logger from starlette import status -from app.api.utils import call_script2, redis_get +from app.api.utils import redis_get from app.lightning.impl.cln_jrpc import LnNodeCLNjRPC from app.lightning.impl.specializations.blitz_common import blitz_cln_unlock from app.lightning.models import ( @@ -29,7 +29,8 @@ from app.lightning.models import ( class LnNodeCLNjRPCBlitz(LnNodeCLNjRPC): - # RaspiBlitz implements a lock function on top of CLN, so we need to implement this on Blitz only. + # RaspiBlitz implements a lock function on top of CLN, so we need to implement + # this on Blitz only. _unlocked = False @@ -209,7 +210,7 @@ class LnNodeCLNjRPCBlitz(LnNodeCLNjRPC): @logger.catch(exclude=(HTTPException,)) def _check_if_locked(self): - logger.debug(f"_check_if_locked()") + logger.debug("_check_if_locked()") if not self._unlocked: raise HTTPException( diff --git a/app/lightning/models.py b/app/lightning/models.py index ac6609a..d4730fb 100644 --- a/app/lightning/models.py +++ b/app/lightning/models.py @@ -106,11 +106,17 @@ class FeeRevenue(BaseModel): month: int = Query(..., description="Fee revenue earned in the last month") year: int = Query( None, - description="Fee revenue earned in the last year. Might be null if not implemented by backend.", + description=( + "Fee revenue earned in the last year." + "Might be null if not implemented by backend." + ), ) total: int = Query( None, - description="Fee revenue earned in the last year. Might be null if not implemented by backend", + description=( + "Fee revenue earned in the last year." + "Might be null if not implemented by backend" + ), ) @classmethod @@ -133,27 +139,43 @@ class FeeRevenue(BaseModel): class ForwardSuccessEvent(BaseModel): timestamp_ns: int = Query( ..., - description="The number of nanoseconds elapsed since January 1, 1970 UTC when this circuit was completed.", + description=( + "The number of nanoseconds elapsed since " + "January 1, 1970 UTC when this circuit was completed." + ), ) chan_id_in: str = Query( ..., - description="The incoming channel ID that carried the HTLC that created the circuit.", + description=( + "The incoming channel ID that carried the HTLC that created the circuit." + ), ) chan_id_out: str = Query( ..., - description="The outgoing channel ID that carried the preimage that completed the circuit.", + description=( + "The outgoing channel ID that carried the " + "preimage that completed the circuit." + ), ) amt_in_msat: int = Query( ..., - description="The total amount (in millisatoshis) of the incoming HTLC that created half the circuit.", + description=( + "The total amount (in millisatoshis) of the " + "incoming HTLC that created half the circuit." + ), ) amt_out_msat: str = Query( ..., - description="The total amount (in millisatoshis) of the outgoing HTLC that created the second half of the circuit.", + description=( + "The total amount (in millisatoshis) of the " + "outgoing HTLC that created the second half of the circuit." + ), ) fee_msat: int = Query( ..., - description="The total fee (in millisatoshis) that this payment circuit carried.", + description=( + "The total fee (in millisatoshis) that this payment circuit carried." + ), ) @classmethod @@ -307,16 +329,18 @@ class InvoiceHTLC(BaseModel): amp: Amp = Query( None, - description="Details relevant to AMP HTLCs, only populated if this is an AMP HTLC.", + description=( + "Details relevant to AMP HTLCs, only populated if this is an AMP HTLC." + ), ) @classmethod def from_lnd_grpc(cls, h) -> "InvoiceHTLC": def _crecords(recs): - l = [] + record_list = [] for r in recs: - l.append(CustomRecordsEntry.from_lnd_grpc(r)) - return l + record_list.append(CustomRecordsEntry.from_lnd_grpc(r)) + return record_list return cls( chan_id=h.chan_id, @@ -346,7 +370,10 @@ class HopHint(BaseModel): fee_proportional_millionths: int = Query( ..., - description="The fee rate of the channel for sending one satoshi across it denominated in msat", + description=( + "The fee rate of the channel for sending one" + "satoshi across it denominated in msat" + ), ) cltv_expiry_delta: int = Query( @@ -377,7 +404,10 @@ class HopHint(BaseModel): class RouteHint(BaseModel): hop_hints: List[HopHint] = Query( [], - description="A list of hop hints that when chained together can assist in reaching a specific destination.", + description=( + "A list of hop hints that when chained together can assist in " + "reaching a specific destination." + ), ) @classmethod @@ -406,7 +436,9 @@ class Channel(BaseModel): def from_lnd_grpc(cls, c) -> "Channel": return cls( active=c.active, - channel_id=c.channel_point, # use channel point as id because thats needed for closing the channel with lnd + # use channel point as id because thats needed + # for closing the channel with lnd + channel_id=c.channel_point, peer_publickey=c.remote_pubkey, peer_alias="n/a", balance_local=c.local_balance, @@ -418,7 +450,9 @@ class Channel(BaseModel): def from_lnd_grpc_pending(cls, c) -> "Channel": return cls( active=False, - channel_id=c.channel_point, # use channel point as id because thats needed for closing the channel with lnd + # use channel point as id because thats needed + # for closing the channel with lnd + channel_id=c.channel_point, peer_publickey=c.remote_node_pub, peer_alias="n/a", balance_local=-1, @@ -431,7 +465,9 @@ class Channel(BaseModel): # TODO: get alias and balance of the channel return cls( active=c.connected, - channel_id=c.short_channel_id, # use channel point as id because thats needed for closing the channel with lnd + # use channel point as id because thats needed + # for closing the channel with lnd + channel_id=c.short_channel_id, peer_publickey=c.peer_id.hex(), peer_alias=peer_alias, balance_local=c.our_amount_msat.msat, @@ -457,13 +493,20 @@ class Channel(BaseModel): class Invoice(BaseModel): memo: str = Query( None, - description="""Optional memo to attach along with the invoice. Used for record keeping purposes for the invoice's creator, - and will also be set in the description field of the encoded payment request if the description_hash field is not being used.""", + description=( + "Optional memo to attach along with the invoice. " + "Used for record keeping purposes for the invoice's creator, " + "and will also be set in the description field of the encoded payment " + "request if the description_hash field is not being used." + ), ) r_preimage: str = Query( None, - description="""The hex-encoded preimage(32 byte) which will allow settling an incoming HTLC payable to this preimage.""", + description=( + "The hex-encoded preimage(32 byte) which will allow settling " + "an incoming HTLC payable to this preimage." + ), ) r_hash: str = Query(None, description="The hash of the preimage.") @@ -481,25 +524,29 @@ class Invoice(BaseModel): settle_date: int = Query( None, - description="When this invoice was settled. Not available with pending invoices.", + description=( + "When this invoice was settled. " "Not available with pending invoices." + ), ) expiry_date: int = Query(None, description="The time at which this invoice expires") payment_request: str = Query( None, - description="""A bare-bones invoice for a payment within the - Lightning Network. With the details of the invoice, the sender has all the data necessary to - send a payment to the recipient. - """, + description=( + "A bare-bones invoice for a payment within the " + "Lightning Network. With the details of the invoice, the sender " + "has all the data necessary to send a payment to the recipient." + ), ) description_hash: str = Query( None, - description=""" - Hash(SHA-256) of a description of the payment. Used if the description of payment(memo) is too - long to naturally fit within the description field of an encoded payment request. - """, + description=( + "Hash(SHA-256) of a description of the payment. Used if the description of " + "payment(memo) is too long to naturally fit within the description field " + "of an encoded payment request." + ), ) expiry: int = Query( @@ -511,65 +558,76 @@ class Invoice(BaseModel): cltv_expiry: int = Query( None, - description="Delta to use for the time-lock of the CLTV extended to the final hop.", + description=( + "Delta to use for the time-lock of the CLTV extended to the final hop." + ), ) route_hints: List[RouteHint] = Query( None, - description=""" - Route hints that can each be individually used to assist in reaching the invoice's destination. - """, + description=( + "Route hints that can each be individually used to assist " + "in reaching the invoice's destination." + ), ) private: bool = Query( None, - description="Whether this invoice should include routing hints for private channels.", + description=( + "Whether this invoice should include routing hints for private channels." + ), ) add_index: str = Query( ..., - description=""" -The index of this invoice. Each newly created invoice will increment this index making it monotonically increasing. -CLN and LND handle ids differently. LND will generate an auto incremented integer id, while CLN will use a user supplied string id. -To unify both, we auto generate an id for CLN and use the add_index for LND. - -For `LND` this will be an `integer` in string form. This is auto generated by LND. - -For `CLN` this will be a `string`. If the invoice was generated by BlitzAPI, this will be a -[Firebase-like PushID](https://firebase.blog/posts/2015/02/the-2120-ways-to-ensure-unique_68). -If generated by some other method, it'll be the string supplied by the user at the time of creation of the invoice. -""", + description=( + "The index of this invoice. Each newly created invoice will increment this " + "index making it monotonically increasing. CLN and LND handle ids " + "differently. LND will generate an auto incremented integer id, while CLN " + "will use a user supplied string id. To unify both, we auto generate an id " + "for CLN and use the add_index for LND." + "" + "For `LND` this will be an `integer` in string form. This is auto " + "generated by LND. " + "" + "For `CLN` this will be a `string`. If the invoice was generated by " + "BlitzAPI, this will be a [Firebase-like PushID]" + "(https://firebase.blog/posts/2015/02/the-2120-ways-to-ensure-unique_68). " + "If generated by some other method, it'll be the string supplied by the " + "user at the time of creation of the invoice." + ), ) settle_index: int = Query( None, - description=""" - The "settle" index of this invoice. Each newly settled invoice will increment this index making it monotonically increasing. - """, + description=( + "The `settle` index of this invoice. Each newly settled invoice will " + "increment this index making it monotonically increasing. " + ), ) amt_paid_sat: int = Query( None, - description=""" - The amount that was accepted for this invoice, in satoshis. This - will ONLY be set if this invoice has been settled. We provide - this field as if the invoice was created with a zero value, - then we need to record what amount was ultimately accepted. - Additionally, it's possible that the sender paid MORE that - was specified in the original invoice. So we'll record that here as well. - """, + description=( + "The amount that was accepted for this invoice, in satoshis. This " + "will ONLY be set if this invoice has been settled. We provide " + "this field as if the invoice was created with a zero value, " + "then we need to record what amount was ultimately accepted. " + "Additionally, it's possible that the sender paid MORE that " + "was specified in the original invoice. So we'll record that here as well." + ), ) amt_paid_msat: int = Query( None, - description=""" - The amount that was accepted for this invoice, in millisatoshis. - This will ONLY be set if this invoice has been settled. We - provide this field as if the invoice was created with a zero value, - then we need to record what amount was ultimately accepted. Additionally, - it's possible that the sender paid MORE that was specified in the - original invoice. So we'll record that here as well. - """, + description=( + "The amount that was accepted for this invoice, in millisatoshis. " + "This will ONLY be set if this invoice has been settled. We " + "provide this field as if the invoice was created with a zero value, " + "then we need to record what amount was ultimately accepted. Additionally, " + "it's possible that the sender paid MORE that was specified in the " + "original invoice. So we'll record that here as well." + ), ) state: InvoiceState = Query(..., description="The state the invoice is in.") @@ -584,13 +642,19 @@ If generated by some other method, it'll be the string supplied by the user at t is_keysend: bool = Query( None, - description="[LND only] Indicates if this invoice was a spontaneous payment that arrived via keysend[EXPERIMENTAL].", + description=( + "[LND only] Indicates if this invoice was a spontaneous payment " + "that arrived via keysend[EXPERIMENTAL]." + ), ) payment_addr: str = Query( None, - description=""" The payment address of this invoice. This value will be used in MPP payments, - and also for newer invoices that always require the MPP payload for added end-to-end security.""", + description=( + "The payment address of this invoice. This value will be used " + "in MPP payments, and also for newer invoices that always require the MPP " + "payload for added end-to-end security." + ), ) is_amp: bool = Query( @@ -600,22 +664,13 @@ If generated by some other method, it'll be the string supplied by the user at t @classmethod def from_lnd_grpc(cls, i) -> "Invoice": def _route_hints(hints): - l = [] - for h in hints: - l.append(RouteHint.from_lnd_grpc((h))) - return l + return [RouteHint.from_lnd_grpc(h) for h in hints] def _htlcs(htlcs): - l = [] - for h in htlcs: - l.append(InvoiceHTLC.from_lnd_grpc(h)) - return l + return [InvoiceHTLC.from_lnd_grpc(h) for h in htlcs] def _features(features): - l = [] - for k in features: - l.append(FeaturesEntry.from_lnd_grpc(k, features[k])) - return l + return [FeaturesEntry.from_lnd_grpc(k, features[k]) for k in features] return cls( memo=i.memo, @@ -790,7 +845,8 @@ class PaymentFailureReason(str, Enum): class ChannelUpdate(BaseModel): - # The signature that validates the announced data and proves the ownership of node id. + # The signature that validates the announced data and proves the ownership + # of node id. signature: str # The target chain that this channel was opened within. This value should be the @@ -807,25 +863,29 @@ class ChannelUpdate(BaseModel): timestamp: int # The bitfield that describes whether optional fields are present in this update. - # Currently, the least-significant bit must be set to 1 if the optional field MaxHtlc is present. + # Currently, the least-significant bit must be set to 1 if the optional + # field MaxHtlc is present. message_flags: int - # The bitfield that describes additional meta-data concerning how the update is to be interpreted. - # Currently, the least-significant bit must be set to 0 if the creating node corresponds to the - # first node in the previously sent channel announcement and 1 otherwise. If the second bit is set, - # then the channel is set to be disabled. + # The bitfield that describes additional meta-data concerning how the update is to + # be interpreted. Currently, the least-significant bit must be set to 0 if the + # creating node corresponds to the first node in the previously sent channel + # announcement and 1 otherwise. If the second bit is set, then the channel is set + # to be disabled. channel_flags: int - # The minimum number of blocks this node requires to be added to the expiry of HTLCs. - # This is a security parameter determined by the node operator. This value represents the - # required gap between the time locks of the incoming and outgoing HTLC's set to this node. + # The minimum number of blocks this node requires to be added to the expiry + # of HTLCs. This is a security parameter determined by the node operator. + # This value represents the required gap between the time locks of the + # incoming and outgoing HTLC's set to this node. time_lock_delta: int # The minimum HTLC value which will be accepted. htlc_minimum_msat: int # The base fee that must be used for incoming HTLC's to this particular channel. - # This value will be tacked onto the required for a payment independent of the size of the payment. + # This value will be tacked onto the required for a payment independent of the + # size of the payment. base_fee: int # The fee rate that will be charged per millionth of a satoshi. @@ -834,10 +894,11 @@ class ChannelUpdate(BaseModel): # The maximum HTLC value which will be accepted. htlc_maximum_msat: int - # The set of data that was appended to this message, some of which we may not actually know how to - # iterate or parse. By holding onto this data, we ensure that we're able to properly validate the - # set of signatures that cover these new fields, and ensure we're able to make upgrades to the - # network in a forwards compatible manner. + # The set of data that was appended to this message, some of which we may not + # actually know how to iterate or parse. By holding onto this data, we ensure that + # we're able to properly validate the set of signatures that cover these new fields, + # and ensure we're able to make upgrades to the network in a forwards compatible + # manner. extra_opaque_data: str @classmethod @@ -875,9 +936,9 @@ class Hop(BaseModel): # the payment can be executed without relying on a copy of the channel graph. pub_key: str - # If set to true, then this hop will be encoded using the new variable length TLV format. - # Note that if any custom tlv_records below are specified, then this field MUST be set - # to true for them to be encoded properly. + # If set to true, then this hop will be encoded using the new variable length TLV + # format. Note that if any custom tlv_records below are specified, then this field + # MUST be set to true for them to be encoded properly. tlv_payload: bool @classmethod @@ -935,16 +996,10 @@ class Route(BaseModel): @classmethod def from_lnd_grpc(cls, r): def _crecords(recs): - l = [] - for r in recs: - l.append(CustomRecordsEntry(r)) - return l + return [CustomRecordsEntry.from_lnd_grpc(r) for r in recs] def _get_hops(hops) -> List[Hop]: - l = [] - for h in hops: - l.append(Hop.from_lnd_grpc(h)) - return l + return [Hop.from_lnd_grpc(h) for h in hops] mpp = None if hasattr(r, "mpp_record"): @@ -1116,10 +1171,7 @@ class Payment(BaseModel): @classmethod def from_lnd_grpc(cls, p) -> "Payment": def _get_attempts(attempts): - l = [] - for a in attempts: - l.append(HTLCAttempt.from_lnd_grpc(a)) - return l + return [HTLCAttempt.from_lnd_grpc(a) for a in attempts] return cls( payment_hash=p.payment_hash, @@ -1169,11 +1221,11 @@ class Payment(BaseModel): class NewAddressInput(BaseModel): type: OnchainAddressType = Query( ..., - description=""" -Address-types has to be one of: -* p2wkh: Pay to witness key hash (bech32) -* np2wkh: Pay to nested witness key hash - """, + description=( + "Address-types has to be one of: " + "* p2wkh: Pay to witness key hash (bech32) " + "* np2wkh: Pay to nested witness key hash" + ), ) @@ -1184,35 +1236,52 @@ class UnlockWalletInput(BaseModel): class SendCoinsInput(BaseModel): address: str = Query( ..., - description="The base58 or bech32 encoded bitcoin address to send coins to on-chain", + description=( + "The base58 or bech32 encoded bitcoin address to send coins to on-chain" + ), ) target_conf: int = Query( None, - description="The number of blocks that the transaction *should* confirm in, will be used for fee estimation", + description=( + "The number of blocks that the transaction *should* confirm in, " + "will be used for fee estimation" + ), ) sat_per_vbyte: int = Query( None, - description="A manual fee expressed in sat/vbyte that should be used when crafting the transaction (default: 0)", + description=( + "A manual fee expressed in sat/vbyte that should be used when " + "crafting the transaction (default: 0)" + ), ) min_confs: int = Query( 1, - description="The minimum number of confirmations each one of your outputs used for the transaction must satisfy", + description=( + "The minimum number of confirmations each one of your outputs " + "used for the transaction must satisfy" + ), ) label: str = Query( "", description="A label for the transaction. Ignored by CLN backend." ) send_all: bool = Query( False, - description="Send all available on-chain funds from the wallet. Will be executed `amount` is **0**", + description=( + "Send all available on-chain funds from the wallet. Will be " + "executed `amount` is **0**" + ), ) amount: conint(ge=0) = Query( 0, - description="The number of bitcoin denominated in satoshis to send. Must not be set when `send_all` is true.", + description=( + "The number of bitcoin denominated in satoshis to send. Must not " + "be set when `send_all` is true." + ), ) @validator("amount", pre=True, always=True) def check_amount_or_send_all(cls, amount, values): - if amount == None: + if amount is None: amount = 0 send_all = values.get("send_all") if "send_all" in values else False @@ -1223,7 +1292,10 @@ class SendCoinsInput(BaseModel): if amount == 0 and not send_all: # neither amount nor send_all is set raise ValueError( - "Either amount or send_all must be set. Please review the documentation." + ( + "Either amount or send_all must be set. " + "Please review the documentation." + ) ) if amount > 0 and not send_all: @@ -1233,7 +1305,10 @@ class SendCoinsInput(BaseModel): if amount > 0 and send_all: # amount is set and send_all is true raise ValueError( - "Amount and send_all must not be set at the same time. Please review the documentation." + ( + "Amount and send_all must not be set at the same time. " + "Please review the documentation." + ) ) if amount == 0 and send_all: @@ -1241,14 +1316,17 @@ class SendCoinsInput(BaseModel): return amount # normally this should never be reached - raise ValueError(f"Unknown input.") + raise ValueError("Unknown input.") class SendCoinsResponse(BaseModel): txid: str = Query(..., description="The transaction ID for this onchain payment") address: str = Query( ..., - description="The base58 or bech32 encoded bitcoin address where the onchain funds where sent to", + description=( + "The base58 or bech32 encoded bitcoin address where the onchain " + "funds where sent to" + ), ) amount: conint(ge=0) = Query( ..., @@ -1256,7 +1334,9 @@ class SendCoinsResponse(BaseModel): ) fees: conint(ge=0) = Query( None, - description="The number of bitcoin denominated in satoshis which where paid as fees", + description=( + "The number of bitcoin denominated in satoshis which where paid as fees" + ), ) label: str = Query( "", description="The label used for the transaction. Ignored by CLN backend." @@ -1268,7 +1348,7 @@ class SendCoinsResponse(BaseModel): @classmethod def from_lnd_grpc(cls, r, input: SendCoinsInput): - amount = input.amount if input.send_all == False else r.amount + amount = input.amount if input.send_all is False else r.amount return cls( txid=r.tx_hash, address=input.address, @@ -1345,27 +1425,42 @@ class LnInfo(BaseModel): block_height: int = Query( ..., - description="The node's current view of the height of the best block. Only available with LND.", + description=( + "The node's current view of the height of the best block. " + "Only available with LND." + ), ) block_hash: str = Query( "", - description="The node's current view of the hash of the best block. Only available with LND.", + description=( + "The node's current view of the hash of the best block. " + "Only available with LND." + ), ) best_header_timestamp: int = Query( None, - description="Timestamp of the block best known to the wallet. Only available with LND.", + description=( + "Timestamp of the block best known to the wallet. " + "Only available with LND." + ), ) synced_to_chain: bool = Query( None, - description="Whether the wallet's view is synced to the main chain. Only available with LND.", + description=( + "Whether the wallet's view is synced to the main chain. " + "Only available with LND." + ), ) synced_to_graph: bool = Query( None, - description="Whether we consider ourselves synced with the public channel graph. Only available with LND.", + description=( + "Whether we consider ourselves synced with the public channel " + "graph. Only available with LND." + ), ) chains: List[Chain] = Query( @@ -1376,7 +1471,10 @@ class LnInfo(BaseModel): features: List[FeaturesEntry] = Query( [], - description="Features that our node has advertised in our init message node announcements and invoices. Not yet implemented with CLN", + description=( + "Features that our node has advertised in our init message node " + "announcements and invoices. Not yet implemented with CLN" + ), ) def __eq__(self, other): @@ -1526,7 +1624,9 @@ class LightningInfoLite(BaseModel): ) synced_to_graph: bool = Query( None, - description="Whether we consider ourselves synced with the public channel graph.", + description=( + "Whether we consider ourselves synced with " "the public channel graph." + ), ) @classmethod @@ -1550,14 +1650,14 @@ class LightningInfoLite(BaseModel): class WalletBalance(BaseModel): onchain_confirmed_balance: int = Query( ..., - description="Confirmed onchain balance (more than three confirmations) in sat", + description="Confirmed onchain balance (more than 3 confirmations) in sat", ) onchain_total_balance: int = Query( ..., description="Total combined onchain balance in sat" ) onchain_unconfirmed_balance: int = Query( ..., - description="Unconfirmed onchain balance (less than three confirmations) in sat", + description="Unconfirmed onchain balance (less than 3 confirmations) in sat", ) channel_local_balance: int = Query( ..., description="Sum of channels local balances in msat" @@ -1647,10 +1747,6 @@ class PaymentRequest(BaseModel): features = [] # TODO: Map CLN's feature advertisements to LND's - # if "features" in r: - # features = [ - # FeaturesEntry.from_cln_json(k, r["features"][k]) for k in r["features"] - # ] return cls( currency="" if "currency" not in r else r["currency"], @@ -1683,10 +1779,6 @@ class PaymentRequest(BaseModel): features = [] # TODO: Map CLN's feature advertisements to LND's - # if "features" in r: - # features = [ - # FeaturesEntry.from_cln_json(k, r["features"][k]) for k in r["features"] - # ] dhash = "" if hasattr(r, "payment_hash"): @@ -1787,11 +1879,17 @@ class GenericTx(BaseModel): id: str = Query(..., description=docs.tx_id_desc) category: TxCategory = Query( ..., - description="Whether this is an onchain (**onchain**) or lightning (**ln**) transaction.", + description=( + "Whether this is an onchain (**onchain**) or lightning (**ln**) " + "transaction." + ), ) type: TxType = Query( ..., - description="Whether this is an outgoing (**send**) transaction or an incoming (**receive**) transaction.", + description=( + "Whether this is an outgoing (**send**) transaction or an " + "incoming(**receive**) transaction." + ), ) amount: int = Query(..., description=docs.tx_amount_desc) time_stamp: int = Query(..., description=docs.tx_time_stamp_desc) @@ -1799,11 +1897,16 @@ class GenericTx(BaseModel): status: TxStatus = Query(..., description=docs.tx_status_desc) block_height: int = Query( None, - description="Block height, if included in a block. Only applicable for category **onchain**.", + description=( + "Block height, if included in a block. Only applicable for " + "category **onchain**." + ), ) num_confs: Union[int, None] = Query( ge=0, - description="Number of confirmations. Only applicable for category **onchain**.", + description=( + "Number of confirmations. Only applicable for category **onchain**." + ), ) total_fees: int = Query(None, description="Total fees paid for this transaction") @@ -1841,7 +1944,9 @@ class GenericTx(BaseModel): if confs < 0: confs = 0 logging.warning( - f"Got negative confirmation count of for {tx.tx_hash}\nCalc:{current_block_height} - {tx.block_height} = {confs}" + f"""Got negative confirmation count of for {tx.tx_hash}\n + Calc:{current_block_height} - {tx.block_height} = {confs} + """ ) s = TxStatus.SUCCEEDED if confs > 0 else TxStatus.IN_FLIGHT diff --git a/app/lightning/router.py b/app/lightning/router.py index 08fd692..b00c8fa 100644 --- a/app/lightning/router.py +++ b/app/lightning/router.py @@ -54,7 +54,9 @@ router = APIRouter(prefix=f"/{_PREFIX}", tags=["Lightning"]) responses = { 423: { - "description": "LND only: Wallet is locked. Unlock via /lightning/unlock-wallet." + "description": ( + "LND only: Wallet is locked. Unlock via /lightning/unlock-wallet." + ) } } @@ -78,7 +80,7 @@ async def addinvoice( ): try: return await add_invoice(memo, value_msat, expiry, is_keysend) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -96,7 +98,7 @@ async def addinvoice( async def getwalletbalance(): try: return await get_wallet_balance() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -106,10 +108,11 @@ async def getwalletbalance(): "/get-fee-revenue", name=f"{_PREFIX}.get-fee-revenue", summary="Returns the daily, weekly and monthly fee revenue earned.", - description=""" -Currently, year and total fees are always null. Backends don't return these values by default. -Implementation in BlitzAPI is a [to-do](https://github.com/fusion44/blitz_api/issues/64). - """, + description=( + "Currently, year and total fees are always null. " + "Backends don't return these values by default. Implementation in BlitzAPI " + "remains a [to-do](https://github.com/fusion44/blitz_api/issues/64)." + ), dependencies=[Depends(JWTBearer())], response_model=FeeRevenue, responses=responses, @@ -117,7 +120,7 @@ Implementation in BlitzAPI is a [to-do](https://github.com/fusion44/blitz_api/is async def get_fee_revenue_path() -> FeeRevenue: try: return await get_fee_revenue() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -127,9 +130,11 @@ async def get_fee_revenue_path() -> FeeRevenue: "/list-all-tx", name=f"{_PREFIX}.list-all-tx", summary="Lists all on-chain transactions, payments and invoices in the wallet", - description="""Returns a list with all on-chain transaction, payments and invoices combined into one list. - The index of each tx is only valid for each identical set of parameters. - """, + description=( + "Returns a list with all on-chain transaction, payments and invoices " + "combined into one list. The index of each tx is only valid for each identical " + "set of parameters. " + ), dependencies=[Depends(JWTBearer())], response_model=List[GenericTx], responses=responses, @@ -137,24 +142,36 @@ async def get_fee_revenue_path() -> FeeRevenue: async def list_all_tx_path( successful_only: bool = Query( False, - description="If set, only successful transaction will be returned in the response.", + description=( + "If set, only successful transaction will be returned in the response." + ), ), index_offset: int = Query( 0, - description="The index of an transaction that will be used as either the start or end of a query to determine which invoices should be returned in the response.", + description=( + "The index of an transaction that will be used as either the " + "start or end of a query to determine which invoices should be returned in " + "the response." + ), ), max_tx: int = Query( 0, - description="The max number of transaction to return in the response to this query. Will return all transactions when set to 0 or null.", + description=( + "The max number of transaction to return in the response to " + "this query. Will return all transactions when set to 0 or null." + ), ), reversed: bool = Query( False, - description="If set, the transactions returned will result from seeking backwards from the specified index offset. This can be used to paginate backwards.", + description=( + "If set, the transactions returned will result from seeking backwards " + "from the specified index offset. This can be used to paginate backwards." + ), ), ): try: return await list_all_tx(successful_only, index_offset, max_tx, reversed) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -172,19 +189,31 @@ async def list_all_tx_path( async def list_invoices_path( pending_only: bool = Query( False, - description="If set, only invoices that are not settled and not canceled will be returned in the response.", + description=( + "If set, only invoices that are not settled and not canceled " + "will be returned in the response." + ), ), index_offset: int = Query( 0, - description="The index of an invoice that will be used as either the start or end of a query to determine which invoices should be returned in the response.", + description=( + "The index of an invoice that will be used as either the start or end " + "of a query to determine which invoices should be returned in the response." + ), ), num_max_invoices: int = Query( 0, - description="The max number of invoices to return in the response to this query. Will return all invoices when set to 0 or null.", + description=( + "The max number of invoices to return in the response to this query. " + "This will return all invoices when set to 0 or null." + ), ), reversed: bool = Query( False, - description="If set, the invoices returned will result from seeking backwards from the specified index offset. This can be used to paginate backwards.", + description=( + "If set, the invoices returned will result from seeking backwards " + "from the specified index offset. This can be used to paginate backwards." + ), ), ): try: @@ -194,7 +223,7 @@ async def list_invoices_path( num_max_invoices, reversed, ) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -212,7 +241,7 @@ async def list_invoices_path( async def list_on_chain_tx_path(): try: return await list_on_chain_tx() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -221,7 +250,9 @@ async def list_on_chain_tx_path(): @router.get( "/list-payments", name=f"{_PREFIX}.list-payments", - summary="Returns a list of all outgoing payments. Modeled after LND implementation.", + summary=( + "Returns a list of all outgoing payments. Modeled after LND implementation." + ), response_model=List[Payment], response_description="A list of all payments made.", dependencies=[Depends(JWTBearer())], @@ -230,26 +261,45 @@ async def list_on_chain_tx_path(): async def list_payments_path( include_incomplete: bool = Query( True, - description="If true, then return payments that have not yet fully completed. This means that pending payments, as well as failed payments will show up if this field is set to true. This flag doesn't change the meaning of the indices, which are tied to individual payments.", + description=( + "If true, then return payments that have not yet fully completed. " + "This means that pending payments, as well as failed payments will show up " + "if this field is set to true. This flag doesn't change the meaning of the " + "indices, which are tied to individual payments." + ), ), index_offset: int = Query( 0, - description="The index of a payment that will be used as either the start or end of a query to determine which payments should be returned in the response. The index_offset is exclusive. In the case of a zero index_offset, the query will start with the oldest payment when paginating forwards, or will end with the most recent payment when paginating backwards.", + description=( + "The index of a payment that will be used as either the start or " + "end of a query to determine which payments should be returned in the " + "response. The index_offset is exclusive. In the case of a zero " + "index_offset, the query will start with the oldest payment when " + "paginating forwards, or will end with the most recent payment when " + "paginating backwards." + ), ), max_payments: int = Query( 0, - description="The maximal number of payments returned in the response to this query.", + description=( + "The maximal number of payments returned in the response to this query." + ), ), reversed: bool = Query( False, - description="If set, the payments returned will result from seeking backwards from the specified index offset. This can be used to paginate backwards. The order of the returned payments is always oldest first (ascending index order).", + description=( + "If set, the payments returned will result from seeking backwards " + "from the specified index offset. This can be used to paginate backwards. " + "The order of the returned payments is always oldest first (ascending " + "index order)." + ), ), ): try: return await list_payments( include_incomplete, index_offset, max_payments, reversed ) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -268,7 +318,7 @@ async def list_payments_path( async def new_address_path(input: NewAddressInput): try: return await new_address(input) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -290,7 +340,7 @@ async def new_address_path(input: NewAddressInput): async def send_coins_path(input: SendCoinsInput): try: return await send_coins(input=input) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -314,7 +364,7 @@ async def open_channel_path( ): try: return await channel_open(local_funding_amount, node_URI, target_confs) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -334,7 +384,7 @@ async def open_channel_path( async def list_channels_path(): try: return await channel_list() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -354,7 +404,7 @@ async def list_channels_path(): async def close_channel_path(channel_id: str, force_close: bool): try: return await channel_close(channel_id, force_close) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -372,15 +422,19 @@ async def close_channel_path(channel_id: str, force_close: bool): response_model=Payment, responses={ 400: { - "description": """ -Possible error messages: -* invalid bech32 string -* amount must be specified when paying a zero amount invoice -* amount must not be specified when paying a non-zero amount invoice -""" + "description": ( + "Possible error messages:" + "* invalid bech32 string" + "* amount must be specified when paying a zero amount invoice" + "* amount must not be specified when paying a non-zero amount invoice" + ) }, 409: { - "description": "[LND only] When attempting to pay an already paid invoice. CLN will return the payment object of the previously paid invoice. Info: [GitHub](https://github.com/fusion44/blitz_api/issues/131)", + "description": ( + "[LND only] When attempting to pay an already paid invoice. " + "CLN will return the payment object of the previously paid invoice. " + "Info: [GitHub](https://github.com/fusion44/blitz_api/issues/131)" + ), }, 423: responses[423], }, @@ -393,7 +447,7 @@ async def sendpayment( ): try: return await send_payment(pay_req, timeout_seconds, fee_limit_msat, amount_msat) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -411,7 +465,7 @@ async def sendpayment( async def get_info(): try: return await get_ln_info() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -420,7 +474,10 @@ async def get_info(): @router.get( "/get-info-lite", name=f"{_PREFIX}.get-info-lite", - summary="Get lightweight current lightning info. Less verbose version of /lightning/get-info", + summary=( + "Get lightweight current lightning info. " + "Less verbose version of /lightning/get-info" + ), dependencies=[Depends(JWTBearer())], status_code=status.HTTP_200_OK, response_model=LightningInfoLite, @@ -429,7 +486,7 @@ async def get_info(): async def get_ln_info_lite_path(): try: return await get_ln_info_lite() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -438,9 +495,16 @@ async def get_ln_info_lite_path(): @router.get( "/decode-pay-req", name=f"{_PREFIX}.decode-pay-req", - summary="DecodePayReq takes an encoded payment request string and attempts to decode it, returning a full description of the conditions encoded within the payment request.", + summary=( + "DecodePayReq takes an encoded payment request string and attempts to " + "decode it, returning a full description of the conditions encoded within the " + "payment request." + ), response_model=PaymentRequest, - response_description="A fully decoded payment request or a HTTP status 400 if the payment request cannot be decoded.", + response_description=( + "A fully decoded payment request or a HTTP status 400 if the " + "payment request cannot be decoded." + ), dependencies=[Depends(JWTBearer())], responses=responses, ) @@ -449,7 +513,7 @@ async def get_decode_pay_request( ): try: return await decode_pay_request(pay_req) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -464,7 +528,10 @@ async def get_decode_pay_request( dependencies=[Depends(JWTBearer())], responses={ 401: { - "description": "Either JWT token is not ok OR wallet password is wrong, observe the detail message." + "description": ( + "Either JWT token is not ok OR wallet password is wrong, " + "observe the detail message." + ) }, 412: {"description": "Wallet already unlocked"}, }, @@ -472,7 +539,7 @@ async def get_decode_pay_request( async def unlock_wallet_path(input: UnlockWalletInput) -> bool: try: return await unlock_wallet(input.password) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) diff --git a/app/lightning/service.py b/app/lightning/service.py index c298cd8..17849f4 100644 --- a/app/lightning/service.py +++ b/app/lightning/service.py @@ -42,10 +42,10 @@ elif ln_node == "cln_grpc" and PLATFORM == APIPlatform.RASPIBLITZ: LnNodeCLNgRPCBlitz as LnNode, ) elif ln_node == "none": - logger.info(f"lightning was explicitly turned off") + logger.info("lightning was explicitly turned off") elif ln_node == "": ln_node = "none" - logger.info(f"lightning is not set yet") + logger.info("lightning is not set yet") else: logger.error(f"config: unknown lightning node: {ln_node}") raise RuntimeError(f"unknown lightning node type: {ln_node}") @@ -154,7 +154,7 @@ async def channel_open( if len(node_URI) == 0: raise ValueError("node_URI cant be empty") - if not "@" in node_URI: + if "@" not in node_URI: raise ValueError("node_URI must contain @ with node physical address") res = await ln.channel_open(local_funding_amount, node_URI, target_confs) @@ -252,9 +252,9 @@ async def _handle_forward_event_listener(): await asyncio.sleep(FWD_GATHER_INTERVAL) if len(_fwd_successes) > 0: - l = _fwd_successes + sending_successes = _fwd_successes _fwd_successes = [] - await broadcast_sse_msg(SSE.LN_FORWARD_SUCCESSES, l) + await broadcast_sse_msg(SSE.LN_FORWARD_SUCCESSES, sending_successes) _schedule_wallet_balance_update() rev = await get_fee_revenue() diff --git a/app/lightning/utils.py b/app/lightning/utils.py index 75484ea..896b66f 100644 --- a/app/lightning/utils.py +++ b/app/lightning/utils.py @@ -1,5 +1,5 @@ import grpc -from fastapi import HTTPException +from fastapi import HTTPException, status from loguru import logger from app.lightning.exceptions import NodeNotFoundError @@ -16,7 +16,7 @@ async def alias_or_empty(func, node_pub: str) -> str: logger.debug(f"alias_or_empty({node_pub})") if not node_pub: - logger.debug(f"alias_or_empty('') -> ''") + logger.debug("alias_or_empty('') -> ''") return "" diff --git a/app/main.py b/app/main.py index 8c920f1..97dc2cd 100644 --- a/app/main.py +++ b/app/main.py @@ -332,7 +332,8 @@ async def warmup_new_connections(): api_startup_status.bitcoin != StartupState.DONE and api_startup_status.lightning != StartupState.DONE ): - # send only the most minimal available data without Bitcoin Core and Lightning running + # send only the most minimal available data without + # Bitcoin Core and Lightning running res = await get_hardware_info() for id in new_connections: await _send_sse_event(id, SSE.HARDWARE_INFO, res), diff --git a/app/setup/impl/raspiblitz/router.py b/app/setup/impl/raspiblitz/router.py index f8a9e93..1dd51d6 100644 --- a/app/setup/impl/raspiblitz/router.py +++ b/app/setup/impl/raspiblitz/router.py @@ -37,7 +37,7 @@ async def get_status(): initialsync = "done" else: initialsync = "running" - except: + except Exception: initialsync = "" else: initialsync = "" @@ -51,7 +51,8 @@ async def get_status(): # if setupPhase!="done" && state="waitsetup" then # 'setup/setup_start_info' should be called -# We can do the "MIGRATION" option later - because it would need an additional step after formatting hdd +# We can do the "MIGRATION" option later - because it would need an additional step +# after formatting hdd # People that need to migrate can do for now by SSH option @@ -61,7 +62,7 @@ async def setup_start_info(): setupPhase = await redis_get("setupPhase") state = await redis_get("state") if state != "waitsetup": - logging.warning(f"/setup-start-info can only be called when nodes awaits setup") + logging.warning("/setup-start-info can only be called when nodes awaits setup") return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED) # get all the additional info needed to do setup dialog @@ -96,7 +97,8 @@ class StartDoneData(BaseModel): passwordC: str = "" -# With all this info the WebUi can run its own runs its dialogs and in the end makes a call to +# With all this info the WebUi can run its own runs its dialogs and in the end makes a +# call to @router.post("/setup-start-done") async def setup_start_done(data: StartDoneData): # first check that node is really in setup state @@ -105,36 +107,36 @@ async def setup_start_done(data: StartDoneData): hddGotBlockchain = await redis_get("hddBlocksBitcoin") if state != "waitsetup": - logging.warning(f"/setup-start-done can only be called when nodes awaits setup") + logging.warning("/setup-start-done can only be called when nodes awaits setup") return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) # check if a fresh setup is forced if data.forceFreshSetup: - logging.warning(f"forcing node to fresh setup") + logging.warning("forcing node to fresh setup") setupPhase = "setup" #### SETUP #### if setupPhase == "setup": - if name_valid(data.hostname) == False: - logging.warning(f"hostname is not valid") + if name_valid(data.hostname) is False: + logging.warning("hostname is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) if ( data.lightning != "lnd" and data.lightning != "cl" and data.lightning != "none" ): - logging.warning(f"lightning is not valid") - if password_valid(data.passwordA) == False: - logging.warning(f"passwordA is not valid") + logging.warning("lightning is not valid") + if password_valid(data.passwordA) is False: + logging.warning("passwordA is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) - if password_valid(data.passwordB) == False: - logging.warning(f"passwordB is not valid") + if password_valid(data.passwordB) is False: + logging.warning("passwordB is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) - if data.lightning != "none" and password_valid(data.passwordC) == False: - logging.warning(f"passwordC is not valid") + if data.lightning != "none" and password_valid(data.passwordC) is False: + logging.warning("passwordC is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) if hddGotBlockchain != "1" and data.keepBlockchain: - logging.warning(f"cannot keep blockchain that does not exists") + logging.warning("cannot keep blockchain that does not exists") return HTTPException(status.HTTP_400_BAD_REQUEST) if data.keepBlockchain: formatHDD = 0 @@ -163,9 +165,9 @@ async def setup_start_done(data: StartDoneData): #### RECOVERY #### elif setupPhase == "recovery" or setupPhase == "update": - logging.warning(f"check recovery data") - if password_valid(data.passwordA) == False: - logging.warning(f"passwordA is not valid") + logging.warning("check recovery data") + if password_valid(data.passwordA) is False: + logging.warning("passwordA is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) write_text_file( setupFilePath, ["setPasswordA=1", f"passwordA='{data.passwordA}'"] @@ -173,19 +175,19 @@ async def setup_start_done(data: StartDoneData): #### MIGRATION #### elif setupPhase == "migration": - logging.warning(f"check migration data") + logging.warning("check migration data") hddGotMigrationData = await redis_get("hddGotMigrationData") if hddGotMigrationData == "": - logging.warning(f"hddGotMigrationData is not available") + logging.warning("hddGotMigrationData is not available") return HTTPException(status.HTTP_400_BAD_REQUEST) - if password_valid(data.passwordA) == False: - logging.warning(f"passwordA is not valid") + if password_valid(data.passwordA) is False: + logging.warning("passwordA is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) - if password_valid(data.passwordB) == False: - logging.warning(f"passwordB is not valid") + if password_valid(data.passwordB) is False: + logging.warning("passwordB is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) - if password_valid(data.passwordC) == False: - logging.warning(f"passwordC is not valid") + if password_valid(data.passwordC) is False: + logging.warning("passwordC is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) write_text_file( setupFilePath, @@ -230,13 +232,14 @@ async def setup_start_done(data: StartDoneData): @router.get("/setup-final-info", dependencies=[Depends(JWTBearer())]) async def setup_final_info(): # TODO: return info on setup final - # during the process some data might be written to /var/cache/raspiblitz/temp/raspiblitz.setup + # during the process some data might be written to + # /var/cache/raspiblitz/temp/raspiblitz.setup # seedwordsNEW='${seedwords} # seedwords6x4NEW='${seedwords6x4} # syncProgressFull=[percent] = (later WebUi can offer sync from another RaspiBlitz) # first check that node is really in setup state - setupPhase = await redis_get("setupPhase") + await redis_get("setupPhase") state = await redis_get("state") if state != "waitfinal": logging.warning( @@ -250,7 +253,7 @@ async def setup_final_info(): data = parse_key_value_lines(result_lines) try: seedwordsNEW = data["seedwordsNEW"] - except: + except Exception: seedwordsNEW = "" return {"seedwordsNEW": seedwordsNEW} @@ -260,10 +263,10 @@ async def setup_final_info(): @router.post("/setup-final-done", dependencies=[Depends(JWTBearer())]) async def setup_final_done(): # first check that node is really in setup state - setupPhase = await redis_get("setupPhase") + await redis_get("setupPhase") state = await redis_get("state") if state != "waitfinal": - logging.warning(f"/setup-final-done can only be called when nodes awaits final") + logging.warning("/setup-final-done can only be called when nodes awaits final") return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) await call_script("/home/admin/_cache.sh set state donefinal") @@ -276,10 +279,10 @@ async def get_shutdown(): setupPhase = await redis_get("setupPhase") state = await redis_get("state") if setupPhase == "done": - logging.warning(f"can only be called when the nodes is not finalized yet") + logging.warning("can only be called when the nodes is not finalized yet") return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED) if state != "waitsetup": - logging.warning(f"can only be called when nodes awaits setup") + logging.warning("can only be called when nodes awaits setup") return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED) # do the shutdown @@ -293,7 +296,7 @@ async def setup_sync_info(): # first check that node is really in setup state setupPhase = await redis_get("setupPhase") if setupPhase != "done": - logging.warning(f"sync info not available yet") + logging.warning("sync info not available yet") return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) try: @@ -306,7 +309,7 @@ async def setup_sync_info(): btc_default_sync_percentage = await redis_get("btc_default_sync_percentage") btc_default_peers = await redis_get("btc_default_peers") system_count_start_blockchain = await redis_get("system_count_start_blockchain") - except: + except Exception: initialsync = "" btc_default_ready = "" btc_default_sync_percentage = "" @@ -317,7 +320,7 @@ async def setup_sync_info(): ln_default_ready = await redis_get("ln_default_ready") ln_default_locked = await redis_get("ln_default_locked") system_count_start_lightning = await redis_get("system_count_start_lightning") - except: + except Exception: ln_default = "" ln_default_ready = "" ln_default_locked = "" diff --git a/app/setup/router.py b/app/setup/router.py index e001e77..b8ad9cb 100644 --- a/app/setup/router.py +++ b/app/setup/router.py @@ -6,4 +6,4 @@ router = None _PLATFORM = config("platform", default=APIPlatform.RASPIBLITZ) if _PLATFORM == APIPlatform.RASPIBLITZ: - from app.setup.impl.raspiblitz.router import router + from app.setup.impl.raspiblitz.router import router # noqa: F401 diff --git a/app/system/docs.py b/app/system/docs.py index 6589114..39488d3 100644 --- a/app/system/docs.py +++ b/app/system/docs.py @@ -1,3 +1,5 @@ +# ruff: noqa: E501 + get_hw_info_json = """ ```JSON { diff --git a/app/system/impl/raspiblitz.py b/app/system/impl/raspiblitz.py index 9749757..8c9aa95 100644 --- a/app/system/impl/raspiblitz.py +++ b/app/system/impl/raspiblitz.py @@ -145,7 +145,7 @@ class RaspiBlitzSystem(SystemBase): data_lnd_zeus_connection_string = "" if lightning == "lnd": key_value_text = await call_script( - "/home/admin/config.scripts/bonus.lndconnect.sh zeus-android tor key-value" + "/home/admin/config.scripts/bonus.lndconnect.sh zeus-android tor key-value" # noqa: E501 ) key_value = parse_key_value_text(key_value_text) if "lndconnect" in key_value.keys(): @@ -208,7 +208,7 @@ class RaspiBlitzSystem(SystemBase): async def change_password(self, type: str, old_password: str, new_password: str): # check just allowed type values type = type.lower() - if not type in ["a", "b", "c"]: + if type not in ["a", "b", "c"]: raise HTTPException( status.HTTP_400_BAD_REQUEST, detail=f"unknown password type: {type}" ) @@ -225,7 +225,7 @@ class RaspiBlitzSystem(SystemBase): # first check if old password is correct result = await call_script( - f'/home/admin/config.scripts/blitz.passwords.sh check {type} "{old_password}"' + f'/home/admin/config.scripts/blitz.passwords.sh check {type} "{old_password}"' # noqa: E501 ) data = parse_key_value_text(result) if not data["correct"] == "1": @@ -239,7 +239,7 @@ class RaspiBlitzSystem(SystemBase): ) if type == "c": # will set password c of both lnd & core lightning if installed/activated - script_call = f'/home/admin/config.scripts/blitz.passwords.sh set c "{old_password}" "{new_password}"' + script_call = f'/home/admin/config.scripts/blitz.passwords.sh set c "{old_password}" "{new_password}"' # noqa: E501 result = await call_sudo_script(script_call) data = parse_key_value_text(result) @@ -298,8 +298,8 @@ class RaspiBlitzSystem(SystemBase): loads = (await redis_get("system_cpu_load")).split(",") iloads = [] total = 0 - for l in loads: - value = float(l) + for load in loads: + value = float(load) total += value iloads.append(value) info["cpu_overall_percent"] = round(total / len(loads), 2) diff --git a/app/system/models.py b/app/system/models.py index ee7ad4d..04a5f0a 100644 --- a/app/system/models.py +++ b/app/system/models.py @@ -3,7 +3,6 @@ from typing import Optional from decouple import config from fastapi import Query -from fastapi.param_functions import Query from pydantic import BaseModel from pydantic.types import constr @@ -66,7 +65,9 @@ class SystemInfo(BaseModel): # and the Lightning Implementation chain: str = Query( ..., - description="The current chain this node is connected to (mainnet, testnet or signet)", + description=( + "The current chain this node is connected to (mainnet, testnet or signet)" + ), ) diff --git a/app/system/router.py b/app/system/router.py index f727fd0..e4c021a 100644 --- a/app/system/router.py +++ b/app/system/router.py @@ -43,7 +43,7 @@ async def login_path(i: LoginInput, response: Response): token = await login(i) response.set_cookie("access_token", token) return token - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -67,12 +67,15 @@ def refresh_token(): response_description="if 200 OK - password change worked", dependencies=[Depends(JWTBearer())], ) -async def change_password( +async def change_password_impl( old_password: str, new_password: str, type: Optional[str] = Query( None, - description=' ℹ️ Used in **RaspiBlitz only**. Password A, B or C. Must be one of `["a", "b", "c"]`', + description=( + "ℹ️ Used in **RaspiBlitz only**. Password A, B or C. " + 'Must be one of `["a", "b", "c"]`' + ), ), ): return await change_password(type, old_password, new_password) @@ -91,7 +94,7 @@ async def change_password( async def get_system_info_path(): try: return await get_system_info() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -140,7 +143,10 @@ async def get_debug_logs_raw_route() -> RawDebugLogData: "/hardware-info-sub", name=f"{_PREFIX}.hardware-info-sub", summary="Subscribe to hardware status information.", - response_description=f"Yields a JSON string with hardware information every {HW_INFO_YIELD_TIME} seconds:\n" + response_description=( + "Yields a JSON string with hardware information " + f"every {HW_INFO_YIELD_TIME} seconds\n" + ) + get_hw_info_json, dependencies=[Depends(JWTBearer())], ) diff --git a/app/system/service.py b/app/system/service.py index a70d011..3664ebf 100644 --- a/app/system/service.py +++ b/app/system/service.py @@ -31,7 +31,7 @@ HW_INFO_YIELD_TIME = system.get_hardware_info_yield_time() async def change_password(type: Optional[str], old_password: str, new_password: str): try: return await system.change_password(type, old_password, new_password) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -40,7 +40,7 @@ async def change_password(type: Optional[str], old_password: str, new_password: async def get_system_info() -> SystemInfo: try: return await system.get_system_info() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -49,7 +49,7 @@ async def get_system_info() -> SystemInfo: async def get_hardware_info() -> map: try: return await system.get_hardware_info() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -58,7 +58,7 @@ async def get_hardware_info() -> map: async def get_connection_info() -> ConnectionInfo: try: return await system.get_connection_info() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -72,7 +72,7 @@ async def shutdown(reboot: bool) -> bool: try: return await system.shutdown(reboot=reboot) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -90,7 +90,7 @@ async def subscribe_hardware_info(request: Request): async def get_debug_logs_raw() -> RawDebugLogData: try: return await system.get_debug_logs_raw() - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) @@ -115,7 +115,7 @@ async def register_hardware_info_gatherer(): async def login(i: LoginInput) -> Dict[str, str]: try: return await system.login(i) - except HTTPException as r: + except HTTPException: raise except NotImplementedError as r: raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0]) diff --git a/gen_client_libs.py b/gen_client_libs.py index 1da3a24..e4c4585 100644 --- a/gen_client_libs.py +++ b/gen_client_libs.py @@ -9,9 +9,9 @@ from fastapi.openapi.utils import get_openapi # npm install @openapitools/openapi-generator-cli -g # sudo apt install default-jre -out_path = os.path.abspath(os.path.join("../../", f"blitz_api_client_libraries")) +out_path = os.path.abspath(os.path.join("../../", "blitz_api_client_libraries")) -mod = importlib.import_module(f"app.main") +mod = importlib.import_module("app.main") app = getattr(mod, "app") version = "v1" @@ -27,7 +27,7 @@ def _dart_dio_post_action(): # dart-dio has to generate some code after the initial generation. # flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs print("Post action for dart-dio") - p = os.path.abspath(os.path.join(out_path, f"clients/dart-dio")) + p = os.path.abspath(os.path.join(out_path, "clients/dart-dio")) print("Running flutter pub get") process = Popen(["flutter", "pub", "get"], stdout=PIPE, stderr=PIPE, cwd=p) @@ -96,7 +96,7 @@ def main(): routes=app.routes if app.routes else None, ) - with open(f"openapi.json", "w") as f: + with open("openapi.json", "w") as f: json.dump(specs, f, indent=2) dartOpts = [ diff --git a/poetry.lock b/poetry.lock index f36d223..d276684 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1617,6 +1617,33 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.0.267" +description = "An extremely fast Python linter, written in Rust." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.267-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:4adbbbe314d8fcc539a245065bad89446a3cef2e0c9cf70bf7bb9ed6fe31856d"}, + {file = "ruff-0.0.267-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:67254ae34c38cba109fdc52e4a70887de1f850fb3971e5eeef343db67305d1c1"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbe104f21a429b77eb5ac276bd5352fd8c0e1fbb580b4c772f77ee8c76825654"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:db33deef2a5e1cf528ca51cc59dd764122a48a19a6c776283b223d147041153f"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9adf1307fa9d840d1acaa477eb04f9702032a483214c409fca9dc46f5f157fe3"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0afca3633c8e2b6c0a48ad0061180b641b3b404d68d7e6736aab301c8024c424"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2972241065b1c911bce3db808837ed10f4f6f8a8e15520a4242d291083605ab6"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f731d81cb939e757b0335b0090f18ca2e9ff8bcc8e6a1cf909245958949b6e11"}, + {file = "ruff-0.0.267-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20c594eb56c19063ef5a57f89340e64c6550e169d6a29408a45130a8c3068adc"}, + {file = "ruff-0.0.267-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:45d61a2b01bdf61581a2ee039503a08aa603dc74a6bbe6fb5d1ce3052f5370e5"}, + {file = "ruff-0.0.267-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2107cec3699ca4d7bd41543dc1d475c97ae3a21ea9212238b5c2088fa8ee7722"}, + {file = "ruff-0.0.267-py3-none-musllinux_1_2_i686.whl", hash = "sha256:786de30723c71fc46b80a173c3313fc0dbe73c96bd9da8dd1212cbc2f84cdfb2"}, + {file = "ruff-0.0.267-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a898953949e37c109dd242cfcf9841e065319995ebb7cdfd213b446094a942f"}, + {file = "ruff-0.0.267-py3-none-win32.whl", hash = "sha256:d12ab329474c46b96d962e2bdb92e3ad2144981fe41b89c7770f370646c0101f"}, + {file = "ruff-0.0.267-py3-none-win_amd64.whl", hash = "sha256:d09aecc9f5845586ba90911d815f9772c5a6dcf2e34be58c6017ecb124534ac4"}, + {file = "ruff-0.0.267-py3-none-win_arm64.whl", hash = "sha256:7df7eb5f8d791566ba97cc0b144981b9c080a5b861abaf4bb35a26c8a77b83e9"}, + {file = "ruff-0.0.267.tar.gz", hash = "sha256:632cec7bbaf3c06fcf0a72a1dd029b7d8b7f424ba95a574aaa135f5d20a00af7"}, +] + [[package]] name = "setuptools" version = "67.6.1" @@ -1891,4 +1918,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "a99009743d866a958213c3a7cf886a56df2351948a949467c0f3cb40782582ae" +content-hash = "bc76c3a4abf311f344cf8f5c75a67d5ebda4318c7634a8cf2f38ee3fb075818f" diff --git a/pyproject.toml b/pyproject.toml index 23c9ff6..f3ed0ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ pyclean = "2.2.0" pre-commit = "2.20.0" isort = "5.10.1" pytest-asyncio = "^0.19.0" +ruff = "^0.0.267" [build-system] requires = ["poetry-core>=1.0.0"] @@ -43,3 +44,52 @@ build-backend = "poetry.core.masonry.api" [tool.isort] profile = "black" + + +[tool.ruff] +# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. +select = ["E", "F"] +ignore = [] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] +unfixable = [] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + "app/lightning/impl/protos/*" +] + +# Same as Black. +line-length = 88 + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# Assume Python 3.9. +target-version = "py39" + +[tool.ruff.mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 10 diff --git a/tests/models/test_bitcoind.py b/tests/models/test_bitcoind.py index 09b5d52..df66bba 100644 --- a/tests/models/test_bitcoind.py +++ b/tests/models/test_bitcoind.py @@ -1,4 +1,11 @@ -from app.bitcoind.models import * +from app.bitcoind.models import ( + Bip9Statistics, + BlockchainInfo, + BtcInfo, + BtcLocalAddress, + BtcNetwork, + NetworkInfo, +) blockhain_info = { "chain": "main", diff --git a/tests/repositories/system_impl/test_native_python.py b/tests/repositories/system_impl/test_native_python.py index 2819f37..f964d04 100644 --- a/tests/repositories/system_impl/test_native_python.py +++ b/tests/repositories/system_impl/test_native_python.py @@ -16,10 +16,10 @@ async def test_match_password(monkeypatch): ) res = await npy.match_password(ok) - assert res == True + assert res is True res = await npy.match_password(nok) - assert res == False + assert res is False with pytest.raises(ValidationError) as exc_info: # Should be 8 characters diff --git a/tests/repositories/system_impl/test_raspiblitz.py b/tests/repositories/system_impl/test_raspiblitz.py index 45e695b..4599264 100644 --- a/tests/repositories/system_impl/test_raspiblitz.py +++ b/tests/repositories/system_impl/test_raspiblitz.py @@ -4,5 +4,6 @@ import pytest @pytest.mark.asyncio async def test_match_password(monkeypatch): # TODO: implement this - # emulate what /home/admin/config.scripts/blitz.passwords.sh check a "{i.password}" does + # emulate what + # /home/admin/config.scripts/blitz.passwords.sh check a "{i.password}" does assert True diff --git a/tests/repositories/test_system.py b/tests/repositories/test_system.py index 0e856a8..ebb07ff 100644 --- a/tests/repositories/test_system.py +++ b/tests/repositories/test_system.py @@ -17,7 +17,7 @@ async def test_login(monkeypatch): res = await sys.login(i=LoginInput(password="12345678")) assert type(res) is dict - assert res.startswith("ey") == True + assert res.startswith("ey") is True async def fake_match_pw_negative(_): return False