From 98ec59df96766539cb274bb648ca889a60dcd01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 17 Apr 2024 13:11:51 +0200 Subject: [PATCH] feat: adhere to ruff's `B` rules (#2423) * feat: adhere to ruff's `B` rules last of the ruff checks. closes #2308 * B904 * B008 * B005 * B025 * cleanup on fake --- lnbits/app.py | 4 +- lnbits/core/helpers.py | 10 ++-- lnbits/core/services.py | 12 ++--- lnbits/core/views/admin_api.py | 8 +-- lnbits/core/views/api.py | 10 ++-- lnbits/core/views/auth_api.py | 78 ++++++++++++++++-------------- lnbits/core/views/extension_api.py | 46 +++++++++--------- lnbits/core/views/generic.py | 14 ++++-- lnbits/core/views/node_api.py | 6 ++- lnbits/core/views/payment_api.py | 32 ++++++------ lnbits/core/views/public_api.py | 4 +- lnbits/core/views/tinyurl_api.py | 12 ++--- lnbits/decorators.py | 14 +++--- lnbits/extension_manager.py | 12 +++-- lnbits/nodes/cln.py | 43 ++++++++-------- lnbits/nodes/lndrest.py | 18 ++++--- lnbits/server.py | 6 ++- lnbits/utils/crypto.py | 4 +- lnbits/wallets/base.py | 6 ++- lnbits/wallets/cliche.py | 3 ++ lnbits/wallets/corelightning.py | 3 ++ lnbits/wallets/fake.py | 3 ++ lnbits/wallets/lndgrpc.py | 3 ++ lnbits/wallets/spark.py | 8 +-- lnbits/wallets/void.py | 4 ++ pyproject.toml | 12 ++++- tests/wallets/helpers.py | 4 +- tools/conv.py | 16 +++--- 28 files changed, 226 insertions(+), 169 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index b4970421f..1923e3234 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -137,8 +137,8 @@ def create_app() -> FastAPI: ) # Allow registering new extensions routes without direct access to the `app` object - setattr(core_app_extra, "register_new_ext_routes", register_new_ext_routes(app)) - setattr(core_app_extra, "register_new_ratelimiter", register_new_ratelimiter(app)) + core_app_extra.register_new_ext_routes = register_new_ext_routes(app) + core_app_extra.register_new_ratelimiter = register_new_ratelimiter(app) # register static files static_path = Path("lnbits", "static") diff --git a/lnbits/core/helpers.py b/lnbits/core/helpers.py index 839d58b71..0c1982eea 100644 --- a/lnbits/core/helpers.py +++ b/lnbits/core/helpers.py @@ -18,11 +18,11 @@ async def migrate_extension_database(ext: Extension, current_version): try: ext_migrations = importlib.import_module(f"{ext.module_name}.migrations") ext_db = importlib.import_module(ext.module_name).db - except ImportError as e: - logger.error(e) + except ImportError as exc: + logger.error(exc) raise ImportError( f"Please make sure that the extension `{ext.code}` has a migrations file." - ) + ) from exc async with ext_db.connect() as ext_conn: await run_migration(ext_conn, ext_migrations, ext.code, current_version) @@ -113,7 +113,7 @@ def to_valid_user_id(user_id: str) -> UUID: raise ValueError("User ID must have at least 128 bits") try: int(user_id, 16) - except Exception: - raise ValueError("Invalid hex string for User ID.") + except Exception as exc: + raise ValueError("Invalid hex string for User ID.") from exc return UUID(hex=user_id[:32], version=4) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 9154fdec1..625ac759c 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -201,8 +201,8 @@ async def pay_invoice( """ try: invoice = bolt11_decode(payment_request) - except Exception: - raise InvoiceError("Bolt11 decoding failed.") + except Exception as exc: + raise InvoiceError("Bolt11 decoding failed.") from exc if not invoice.amount_msat or not invoice.amount_msat > 0: raise InvoiceError("Amountless invoices not supported.") @@ -286,10 +286,10 @@ async def pay_invoice( conn=conn, **payment_kwargs, ) - except Exception as e: - logger.error(f"could not create temporary payment: {e}") + except Exception as exc: + logger.error(f"could not create temporary payment: {exc}") # happens if the same wallet tries to pay an invoice twice - raise PaymentError("Could not make payment.") + raise PaymentError("Could not make payment.") from exc # do the balance check wallet = await get_wallet(wallet_id, conn=conn) @@ -727,7 +727,7 @@ def update_cached_settings(sets_dict: dict): except Exception: logger.warning(f"Failed overriding setting: {key}, value: {value}") if "super_user" in sets_dict: - setattr(settings, "super_user", sets_dict["super_user"]) + settings.super_user = sets_dict["super_user"] async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings: diff --git a/lnbits/core/views/admin_api.py b/lnbits/core/views/admin_api.py index d9ad8d0d3..b2286e16c 100644 --- a/lnbits/core/views/admin_api.py +++ b/lnbits/core/views/admin_api.py @@ -43,11 +43,11 @@ async def api_auditor(): "node_balance_msats": int(node_balance), "lnbits_balance_msats": int(total_balance), } - except Exception: + except Exception as exc: raise HTTPException( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Could not audit balance.", - ) + ) from exc @admin_router.get( @@ -113,10 +113,10 @@ async def api_restart_server() -> dict[str, str]: async def api_topup_balance(data: CreateTopup) -> dict[str, str]: try: await get_wallet(data.id) - except Exception: + except Exception as exc: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist." - ) + ) from exc if settings.lnbits_backend_wallet_class == "VoidWallet": raise HTTPException( diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index f865ae891..b78995b19 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -79,7 +79,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type try: url = str(lnurl_decode(code)) domain = urlparse(url).netloc - except Exception: + except Exception as exc: # parse internet identifier (user@domain.com) name_domain = code.split("@") if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2: @@ -94,7 +94,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type else: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl" - ) + ) from exc # params is what will be returned to the client params: Dict = {"domain": domain} @@ -119,14 +119,14 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type try: data = json.loads(r.text) - except json.decoder.JSONDecodeError: + except json.decoder.JSONDecodeError as exc: raise HTTPException( status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail={ "domain": domain, "message": f"got invalid response '{r.text[:200]}'", }, - ) + ) from exc try: tag: str = data.get("tag") @@ -185,7 +185,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type "domain": domain, "message": f"lnurl JSON response invalid: {exc}", }, - ) + ) from exc return params diff --git a/lnbits/core/views/auth_api.py b/lnbits/core/views/auth_api.py index 05d5ff443..ec4bec133 100644 --- a/lnbits/core/views/auth_api.py +++ b/lnbits/core/views/auth_api.py @@ -68,11 +68,11 @@ async def login(data: LoginUsernamePassword) -> JSONResponse: raise HTTPException(HTTP_401_UNAUTHORIZED, "Invalid credentials.") return _auth_success_response(user.username, user.id) - except HTTPException as e: - raise e - except Exception as e: - logger.debug(e) - raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") + except HTTPException as exc: + raise exc + except Exception as exc: + logger.debug(exc) + raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc @auth_router.post("/usr", description="Login via the User ID") @@ -86,11 +86,11 @@ async def login_usr(data: LoginUsr) -> JSONResponse: raise HTTPException(HTTP_401_UNAUTHORIZED, "User ID does not exist.") return _auth_success_response(user.username or "", user.id) - except HTTPException as e: - raise e - except Exception as e: - logger.debug(e) - raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") + except HTTPException as exc: + raise exc + except Exception as exc: + logger.debug(exc) + raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc @auth_router.get("/{provider}", description="SSO Provider") @@ -124,16 +124,16 @@ async def handle_oauth_token(request: Request, provider: str) -> RedirectRespons user_id = decrypt_internal_message(provider_sso.state) request.session.pop("user", None) return await _handle_sso_login(userinfo, user_id) - except HTTPException as e: - raise e - except ValueError as e: - raise HTTPException(HTTP_403_FORBIDDEN, str(e)) - except Exception as e: - logger.debug(e) + except HTTPException as exc: + raise exc + except ValueError as exc: + raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc + except Exception as exc: + logger.debug(exc) raise HTTPException( HTTP_500_INTERNAL_SERVER_ERROR, f"Cannot authenticate user with {provider} Auth.", - ) + ) from exc @auth_router.post("/logout") @@ -169,11 +169,13 @@ async def register(data: CreateUser) -> JSONResponse: user = await create_user(data) return _auth_success_response(user.username) - except ValueError as e: - raise HTTPException(HTTP_403_FORBIDDEN, str(e)) - except Exception as e: - logger.debug(e) - raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot create user.") + except ValueError as exc: + raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc + except Exception as exc: + logger.debug(exc) + raise HTTPException( + HTTP_500_INTERNAL_SERVER_ERROR, "Cannot create user." + ) from exc @auth_router.put("/password") @@ -189,13 +191,13 @@ async def update_password( try: return await update_user_password(data) - except AssertionError as e: - raise HTTPException(HTTP_403_FORBIDDEN, str(e)) - except Exception as e: - logger.debug(e) + except AssertionError as exc: + raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc + except Exception as exc: + logger.debug(exc) raise HTTPException( HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password." - ) + ) from exc @auth_router.put("/update") @@ -211,11 +213,13 @@ async def update( try: return await update_account(user.id, data.username, None, data.config) - except AssertionError as e: - raise HTTPException(HTTP_403_FORBIDDEN, str(e)) - except Exception as e: - logger.debug(e) - raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user.") + except AssertionError as exc: + raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc + except Exception as exc: + logger.debug(exc) + raise HTTPException( + HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user." + ) from exc @auth_router.put("/first_install") @@ -237,13 +241,13 @@ async def first_install(data: UpdateSuperuserPassword) -> JSONResponse: await update_user_password(super_user) settings.first_install = False return _auth_success_response(username=super_user.username) - except AssertionError as e: - raise HTTPException(HTTP_403_FORBIDDEN, str(e)) - except Exception as e: - logger.debug(e) + except AssertionError as exc: + raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc + except Exception as exc: + logger.debug(exc) raise HTTPException( HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password." - ) + ) from exc async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] = None): diff --git a/lnbits/core/views/extension_api.py b/lnbits/core/views/extension_api.py index fa9e12116..853cc9b77 100644 --- a/lnbits/core/views/extension_api.py +++ b/lnbits/core/views/extension_api.py @@ -104,10 +104,10 @@ async def api_install_extension( ext_info.notify_upgrade() return extension - except AssertionError as e: - raise HTTPException(HTTPStatus.BAD_REQUEST, str(e)) - except Exception as ex: - logger.warning(ex) + except AssertionError as exc: + raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc + except Exception as exc: + logger.warning(exc) ext_info.clean_extension_files() raise HTTPException( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, @@ -115,7 +115,7 @@ async def api_install_extension( f"Failed to install extension {ext_info.id} " f"({ext_info.installed_version})." ), - ) + ) from exc @extension_router.delete("/{ext_id}") @@ -159,10 +159,10 @@ async def api_uninstall_extension( await delete_installed_extension(ext_id=ext_info.id) logger.success(f"Extension '{ext_id}' uninstalled.") - except Exception as ex: + except Exception as exc: raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(ex) - ) + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc) + ) from exc @extension_router.get("/{ext_id}/releases", dependencies=[Depends(check_admin)]) @@ -183,10 +183,10 @@ async def get_extension_releases(ext_id: str): return extension_releases - except Exception as ex: + except Exception as exc: raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(ex) - ) + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc) + ) from exc @extension_router.put("/invoice", dependencies=[Depends(check_admin)]) @@ -216,11 +216,13 @@ async def get_extension_invoice(data: CreateExtension): return payment_info - except AssertionError as e: - raise HTTPException(HTTPStatus.BAD_REQUEST, str(e)) - except Exception as ex: - logger.warning(ex) - raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot request invoice") + except AssertionError as exc: + raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc + except Exception as exc: + logger.warning(exc) + raise HTTPException( + HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot request invoice" + ) from exc @extension_router.get( @@ -238,10 +240,10 @@ async def get_extension_release(org: str, repo: str, tag_name: str): "is_version_compatible": config.is_version_compatible(), "warning": config.warning, } - except Exception as ex: + except Exception as exc: raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(ex) - ) + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc) + ) from exc @extension_router.delete( @@ -262,9 +264,9 @@ async def delete_extension_db(ext_id: str): except HTTPException as ex: logger.error(ex) raise ex - except Exception as ex: - logger.error(ex) + except Exception as exc: + logger.error(exc) raise HTTPException( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=f"Cannot delete data for extension '{ext_id}'", - ) + ) from exc diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 3509fbda5..822945000 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -171,9 +171,11 @@ async def extensions_install( "extensions": extensions, }, ) - except Exception as e: - logger.warning(e) - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) + except Exception as exc: + logger.warning(exc) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc) + ) from exc @generic_router.get( @@ -396,8 +398,10 @@ async def hex_to_uuid4(hex_value: str): try: user_id = to_valid_user_id(hex_value).hex return RedirectResponse(url=f"/wallet?usr={user_id}") - except Exception as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) + except Exception as exc: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail=str(exc) + ) from exc async def toggle_extension(extension_to_enable, extension_to_disable, user_id): diff --git a/lnbits/core/views/node_api.py b/lnbits/core/views/node_api.py index c4c1297c4..caff8f382 100644 --- a/lnbits/core/views/node_api.py +++ b/lnbits/core/views/node_api.py @@ -195,5 +195,7 @@ async def api_get_1ml_stats(node: Node = Depends(require_node)) -> Optional[Node try: r.raise_for_status() return r.json()["noderank"] - except httpx.HTTPStatusError: - raise HTTPException(status_code=404, detail="Node not found on 1ml.com") + except httpx.HTTPStatusError as exc: + raise HTTPException( + status_code=404, detail="Node not found on 1ml.com" + ) from exc diff --git a/lnbits/core/views/payment_api.py b/lnbits/core/views/payment_api.py index 4b827fe20..d09e02f9d 100644 --- a/lnbits/core/views/payment_api.py +++ b/lnbits/core/views/payment_api.py @@ -13,6 +13,7 @@ from fastapi import ( Depends, Header, HTTPException, + Query, Request, ) from fastapi.responses import JSONResponse @@ -28,7 +29,6 @@ from lnbits.core.models import ( Payment, PaymentFilters, PaymentHistoryPoint, - Query, Wallet, WalletType, ) @@ -133,19 +133,19 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet): if data.description_hash: try: description_hash = bytes.fromhex(data.description_hash) - except ValueError: + except ValueError as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="'description_hash' must be a valid hex string", - ) + ) from exc if data.unhashed_description: try: unhashed_description = bytes.fromhex(data.unhashed_description) - except ValueError: + except ValueError as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="'unhashed_description' must be a valid hex string", - ) + ) from exc # do not save memo if description_hash or unhashed_description is set memo = "" @@ -170,8 +170,8 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet): payment_db = await get_standalone_payment(payment_hash, conn=conn) assert payment_db is not None, "payment not found" checking_id = payment_db.checking_id - except InvoiceError as e: - raise HTTPException(status_code=520, detail=str(e)) + except InvoiceError as exc: + raise HTTPException(status_code=520, detail=str(exc)) from exc except Exception as exc: raise exc @@ -192,12 +192,14 @@ async def api_payments_pay_invoice( payment_hash = await pay_invoice( wallet_id=wallet.id, payment_request=bolt11, extra=extra ) - except ValueError as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) - except PermissionError as e: - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e)) - except PaymentError as e: - raise HTTPException(status_code=520, detail=str(e)) + except ValueError as exc: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail=str(exc) + ) from exc + except PermissionError as exc: + raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(exc)) from exc + except PaymentError as exc: + raise HTTPException(status_code=520, detail=str(exc)) from exc except Exception as exc: raise exc @@ -282,11 +284,11 @@ async def api_payments_pay_lnurl( if r.is_error: raise httpx.ConnectError("LNURL callback connection error") r.raise_for_status() - except (httpx.ConnectError, httpx.RequestError): + except (httpx.ConnectError, httpx.RequestError) as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail=f"Failed to connect to {domain}.", - ) + ) from exc params = json.loads(r.text) if params.get("status") == "ERROR": diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 371775754..106eb0326 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -27,10 +27,10 @@ async def api_public_payment_longpolling(payment_hash): invoice = bolt11.decode(payment.bolt11) if invoice.has_expired(): return {"status": "expired"} - except Exception: + except Exception as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="Invalid bolt11 invoice." - ) + ) from exc payment_queue = asyncio.Queue(0) diff --git a/lnbits/core/views/tinyurl_api.py b/lnbits/core/views/tinyurl_api.py index 6e1a3ce5a..deb90eefd 100644 --- a/lnbits/core/views/tinyurl_api.py +++ b/lnbits/core/views/tinyurl_api.py @@ -38,10 +38,10 @@ async def api_create_tinyurl( if tinyurl.wallet == wallet.wallet.id: return tinyurl return await create_tinyurl(url, endless, wallet.wallet.id) - except Exception: + except Exception as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="Unable to create tinyurl" - ) + ) from exc @tinyurl_router.get( @@ -60,10 +60,10 @@ async def api_get_tinyurl( raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided." ) - except Exception: + except Exception as exc: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Unable to fetch tinyurl" - ) + ) from exc @tinyurl_router.delete( @@ -83,10 +83,10 @@ async def api_delete_tinyurl( raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided." ) - except Exception: + except Exception as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="Unable to delete" - ) + ) from exc @tinyurl_router.get( diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 6770f4c43..862ff2656 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -69,10 +69,10 @@ class KeyChecker(SecurityBase): detail="Invalid key or wallet.", ) self.wallet = wallet - except KeyError: + except KeyError as exc: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="`X-API-KEY` header missing." - ) + ) from exc class WalletInvoiceKeyChecker(KeyChecker): @@ -324,10 +324,10 @@ async def _get_account_from_token(access_token): return await get_account_by_email(str(payload.get("email"))) raise HTTPException(HTTPStatus.UNAUTHORIZED, "Data missing for access token.") - except ExpiredSignatureError: + except ExpiredSignatureError as exc: raise HTTPException( HTTPStatus.UNAUTHORIZED, "Session expired.", {"token-expired": "true"} - ) - except JWTError as e: - logger.debug(e) - raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid access token.") + ) from exc + except JWTError as exc: + logger.debug(exc) + raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid access token.") from exc diff --git a/lnbits/extension_manager.py b/lnbits/extension_manager.py index 62224896e..e0d3d3106 100644 --- a/lnbits/extension_manager.py +++ b/lnbits/extension_manager.py @@ -428,9 +428,9 @@ class InstallableExtension(BaseModel): self._remember_payment_info() - except Exception as ex: - logger.warning(ex) - raise AssertionError("Cannot fetch extension archive file") + except Exception as exc: + logger.warning(exc) + raise AssertionError("Cannot fetch extension archive file") from exc archive_hash = file_hash(ext_zip_file) if self.installed_release.hash and self.installed_release.hash != archive_hash: @@ -560,7 +560,11 @@ class InstallableExtension(BaseModel): return ext @classmethod - def from_rows(cls, rows: List[Any] = []) -> List["InstallableExtension"]: + def from_rows( + cls, rows: Optional[List[Any]] = None + ) -> List["InstallableExtension"]: + if rows is None: + rows = [] return [InstallableExtension.from_row(row) for row in rows] @classmethod diff --git a/lnbits/nodes/cln.py b/lnbits/nodes/cln.py index 58a21183b..09ace6f56 100644 --- a/lnbits/nodes/cln.py +++ b/lnbits/nodes/cln.py @@ -44,11 +44,12 @@ def catch_rpc_errors(f): async def wrapper(*args, **kwargs): try: return await f(*args, **kwargs) - except RpcError as e: - if e.error["code"] == -32602: - raise HTTPException(status_code=400, detail=e.error["message"]) + except RpcError as exc: + msg = exc.error["message"] + if exc.error["code"] == -32602: + raise HTTPException(status_code=400, detail=msg) from exc else: - raise HTTPException(status_code=500, detail=e.error["message"]) + raise HTTPException(status_code=500, detail=msg) from exc return wrapper @@ -66,9 +67,11 @@ class CoreLightningNode(Node): # https://docs.corelightning.org/reference/lightning-connect try: await self.ln_rpc("connect", uri) - except RpcError as e: - if e.error["code"] == 400: - raise HTTPException(HTTPStatus.BAD_REQUEST, detail=e.error["message"]) + except RpcError as exc: + if exc.error["code"] == 400: + raise HTTPException( + HTTPStatus.BAD_REQUEST, detail=exc.error["message"] + ) from exc else: raise @@ -76,12 +79,12 @@ class CoreLightningNode(Node): async def disconnect_peer(self, peer_id: str): try: await self.ln_rpc("disconnect", peer_id) - except RpcError as e: - if e.error["code"] == -1: + except RpcError as exc: + if exc.error["code"] == -1: raise HTTPException( HTTPStatus.BAD_REQUEST, - detail=e.error["message"], - ) + detail=exc.error["message"], + ) from exc else: raise @@ -105,14 +108,14 @@ class CoreLightningNode(Node): funding_txid=result["txid"], output_index=result["outnum"], ) - except RpcError as e: - message = e.error["message"] + except RpcError as exc: + message = exc.error["message"] if "amount: should be a satoshi amount" in message: raise HTTPException( HTTPStatus.BAD_REQUEST, detail="The amount is not a valid satoshi amount.", - ) + ) from exc if "Unknown peer" in message: raise HTTPException( @@ -121,7 +124,7 @@ class CoreLightningNode(Node): "We where able to connect to the peer but CLN " "can't find it when opening a channel." ), - ) + ) from exc if "Owning subdaemon openingd died" in message: # https://github.com/ElementsProject/lightning/issues/2798#issuecomment-511205719 @@ -131,14 +134,14 @@ class CoreLightningNode(Node): "Likely the peer didn't like our channel opening " "proposal and disconnected from us." ), - ) + ) from exc if ( "Number of pending channels exceed maximum" in message or "exceeds maximum chan size of 10 BTC" in message or "Could not afford" in message ): - raise HTTPException(HTTPStatus.BAD_REQUEST, detail=message) + raise HTTPException(HTTPStatus.BAD_REQUEST, detail=message) from exc raise @catch_rpc_errors @@ -152,13 +155,13 @@ class CoreLightningNode(Node): raise HTTPException(status_code=400, detail="Short id required") try: await self.ln_rpc("close", short_id) - except RpcError as e: - message = e.error["message"] + except RpcError as exc: + message = exc.error["message"] if ( "Short channel ID not active:" in message or "Short channel ID not found" in message ): - raise HTTPException(HTTPStatus.BAD_REQUEST, detail=message) + raise HTTPException(HTTPStatus.BAD_REQUEST, detail=message) from exc else: raise diff --git a/lnbits/nodes/lndrest.py b/lnbits/nodes/lndrest.py index 6547d60e0..d6d079719 100644 --- a/lnbits/nodes/lndrest.py +++ b/lnbits/nodes/lndrest.py @@ -60,11 +60,13 @@ class LndRestNode(Node): ) try: response.raise_for_status() - except HTTPStatusError as e: - json = e.response.json() + except HTTPStatusError as exc: + json = exc.response.json() if json: error = json.get("error") or json - raise HTTPException(e.response.status_code, detail=error.get("message")) + raise HTTPException( + exc.response.status_code, detail=error.get("message") + ) from exc return response.json() def get(self, path: str, **kwargs): @@ -81,8 +83,8 @@ class LndRestNode(Node): async def connect_peer(self, uri: str): try: pubkey, host = uri.split("@") - except ValueError: - raise HTTPException(400, detail="Invalid peer URI") + except ValueError as exc: + raise HTTPException(400, detail="Invalid peer URI") from exc await self.request( "POST", "/v1/peers", @@ -96,11 +98,11 @@ class LndRestNode(Node): async def disconnect_peer(self, peer_id: str): try: await self.request("DELETE", "/v1/peers/" + peer_id) - except HTTPException as e: - if "unable to disconnect" in e.detail: + except HTTPException as exc: + if "unable to disconnect" in exc.detail: raise HTTPException( HTTPStatus.BAD_REQUEST, detail="Peer is not connected" - ) + ) from exc raise async def _get_peer_info(self, peer_id: str) -> NodePeerInfo: diff --git a/lnbits/server.py b/lnbits/server.py index 30ac6c3f9..54f33f987 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -48,18 +48,20 @@ def main( # this beautiful beast parses all command line arguments and # passes them to the uvicorn server + # TODO: why is this needed? it would be better only to rely on the commands options d = {} for a in ctx.args: item = a.split("=") if len(item) > 1: # argument like --key=value print(a, item) - d[item[0].strip("--").replace("-", "_")] = ( + d[item[0].strip("--").replace("-", "_")] = ( # noqa: B005 int(item[1]) # need to convert to int if it's a number if item[1].isdigit() else item[1] ) else: - d[a.strip("--")] = True # argument like --key + # argument like --key + d[a.strip("--")] = True # noqa: B005 while True: config = uvicorn.Config( diff --git a/lnbits/utils/crypto.py b/lnbits/utils/crypto.py index f60c47832..f1ed4ebf3 100644 --- a/lnbits/utils/crypto.py +++ b/lnbits/utils/crypto.py @@ -60,8 +60,8 @@ class AESCipher: aes = AES.new(key, AES.MODE_CBC, iv) try: return self.unpad(aes.decrypt(encrypted[16:])).decode() # type: ignore - except UnicodeDecodeError: - raise ValueError("Wrong passphrase") + except UnicodeDecodeError as exc: + raise ValueError("Wrong passphrase") from exc def encrypt(self, message: bytes) -> str: passphrase = self.passphrase diff --git a/lnbits/wallets/base.py b/lnbits/wallets/base.py index fea45e4f3..34b31102c 100644 --- a/lnbits/wallets/base.py +++ b/lnbits/wallets/base.py @@ -93,11 +93,13 @@ class PaymentPendingStatus(PaymentStatus): class Wallet(ABC): - async def cleanup(self): - pass __node_cls__: Optional[type[Node]] = None + @abstractmethod + async def cleanup(self): + pass + @abstractmethod def status(self) -> Coroutine[None, None, StatusResponse]: pass diff --git a/lnbits/wallets/cliche.py b/lnbits/wallets/cliche.py index b9c56a644..d7bd99d46 100644 --- a/lnbits/wallets/cliche.py +++ b/lnbits/wallets/cliche.py @@ -27,6 +27,9 @@ class ClicheWallet(Wallet): self.endpoint = self.normalize_endpoint(settings.cliche_endpoint) + async def cleanup(self): + pass + async def status(self) -> StatusResponse: try: ws = create_connection(self.endpoint) diff --git a/lnbits/wallets/corelightning.py b/lnbits/wallets/corelightning.py index 1b22768e7..430d2c2f2 100644 --- a/lnbits/wallets/corelightning.py +++ b/lnbits/wallets/corelightning.py @@ -31,6 +31,9 @@ async def run_sync(func) -> Any: class CoreLightningWallet(Wallet): __node_cls__ = CoreLightningNode + async def cleanup(self): + pass + def __init__(self): rpc = settings.corelightning_rpc or settings.clightning_rpc if not rpc: diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 965656a67..99010c760 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -42,6 +42,9 @@ class FakeWallet(Wallet): 32, ).hex() + async def cleanup(self): + pass + async def status(self) -> StatusResponse: logger.info( "FakeWallet funding source is for using LNbits as a centralised," diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index 54776e8e0..dfca68344 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -109,6 +109,9 @@ class LndWallet(Wallet): def metadata_callback(self, _, callback): callback([("macaroon", self.macaroon)], None) + async def cleanup(self): + pass + async def status(self) -> StatusResponse: try: resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest()) diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py index 1276744a8..4557699f4 100644 --- a/lnbits/wallets/spark.py +++ b/lnbits/wallets/spark.py @@ -77,12 +77,12 @@ class SparkWallet(Wallet): httpx.HTTPError, httpx.TimeoutException, ) as exc: - raise UnknownError(f"error connecting to spark: {exc}") + raise UnknownError(f"error connecting to spark: {exc}") from exc try: data = r.json() - except Exception: - raise UnknownError(r.text) + except Exception as exc: + raise UnknownError(r.text) from exc if r.is_error: if r.status_code == 401: @@ -171,7 +171,7 @@ class SparkWallet(Wallet): raise SparkError( f"listpays({payment_hash}) returned an unexpected response:" f" {listpays}" - ) + ) from exc if pay["status"] == "failed": return PaymentResponse(False, None, None, None, str(exc)) diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py index 0ecfed770..6b130c2de 100644 --- a/lnbits/wallets/void.py +++ b/lnbits/wallets/void.py @@ -13,6 +13,10 @@ from .base import ( class VoidWallet(Wallet): + + async def cleanup(self): + pass + async def create_invoice(self, *_, **__) -> InvoiceResponse: return InvoiceResponse( ok=False, error_message="VoidWallet cannot create invoices." diff --git a/pyproject.toml b/pyproject.toml index 36af1dd3b..d621c6d9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,7 +181,8 @@ extend-exclude = [ # N - naming # UP - pyupgrade # RUF - ruff specific rules -select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF"] +# B - bugbear +select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"] # UP007: pyupgrade: use X | Y instead of Optional. (python3.10) # RUF012: mutable-class-default ignore = ["UP007", "RUF012"] @@ -206,3 +207,12 @@ classmethod-decorators = [ [tool.ruff.lint.mccabe] # TODO: Decrease this to 10. max-complexity = 16 + +[tool.ruff.lint.flake8-bugbear] +# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. +extend-immutable-calls = [ + "fastapi.Depends", + "fastapi.Query", + "fastapi.Body", + "lnbits.decorators.parse_filters" +] diff --git a/tests/wallets/helpers.py b/tests/wallets/helpers.py index 6d6734b03..11b6c5ab2 100644 --- a/tests/wallets/helpers.py +++ b/tests/wallets/helpers.py @@ -62,7 +62,7 @@ def load_funding_source(funding_source: FundingSourceConfig) -> BaseWallet: custom_settings = funding_source.settings original_settings = {} - settings = getattr(wallets_module, "settings") + settings = wallets_module.settings for s in custom_settings: original_settings[s] = getattr(settings, s) @@ -93,7 +93,7 @@ async def check_assertions(wallet, _test_data: WalletTest): elif "expect_error" in test_data: await _assert_error(wallet, tested_func, call_params, _test_data.expect_error) else: - assert False, "Expected outcome not specified" + raise AssertionError("Expected outcome not specified") async def _assert_data(wallet, tested_func, call_params, expect): diff --git a/tools/conv.py b/tools/conv.py index 7595861ad..292312b31 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -7,7 +7,7 @@ import argparse import os import sqlite3 import sys -from typing import List +from typing import List, Optional import psycopg2 @@ -90,21 +90,23 @@ def insert_to_pg(query, data): for d in data: try: cursor.execute(query, d) - except Exception as e: + except Exception as exc: if args.ignore_errors: - print(e) + print(exc) print(f"Failed to insert {d}") else: print("query:", query) print("data:", d) - raise ValueError(f"Failed to insert {d}") + raise ValueError(f"Failed to insert {d}") from exc connection.commit() cursor.close() connection.close() -def migrate_core(file: str, exclude_tables: List[str] = []): +def migrate_core(file: str, exclude_tables: Optional[List[str]] = None): + if exclude_tables is None: + exclude_tables = [] print(f"Migrating core: {file}") migrate_db(file, "public", exclude_tables) print("✅ Migrated core") @@ -118,8 +120,10 @@ def migrate_ext(file: str): print(f"✅ Migrated ext: {schema}") -def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): +def migrate_db(file: str, schema: str, exclude_tables: Optional[List[str]] = None): # first we check if this file exists: + if exclude_tables is None: + exclude_tables = [] assert os.path.isfile(file), f"{file} does not exist!" cursor = get_sqlite_cursor(file)