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
This commit is contained in:
dni ⚡ 2024-04-17 13:11:51 +02:00 committed by GitHub
parent e13a37c193
commit 98ec59df96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 226 additions and 169 deletions

View file

@ -137,8 +137,8 @@ def create_app() -> FastAPI:
) )
# Allow registering new extensions routes without direct access to the `app` object # 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)) 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_ratelimiter = register_new_ratelimiter(app)
# register static files # register static files
static_path = Path("lnbits", "static") static_path = Path("lnbits", "static")

View file

@ -18,11 +18,11 @@ async def migrate_extension_database(ext: Extension, current_version):
try: try:
ext_migrations = importlib.import_module(f"{ext.module_name}.migrations") ext_migrations = importlib.import_module(f"{ext.module_name}.migrations")
ext_db = importlib.import_module(ext.module_name).db ext_db = importlib.import_module(ext.module_name).db
except ImportError as e: except ImportError as exc:
logger.error(e) logger.error(exc)
raise ImportError( raise ImportError(
f"Please make sure that the extension `{ext.code}` has a migrations file." f"Please make sure that the extension `{ext.code}` has a migrations file."
) ) from exc
async with ext_db.connect() as ext_conn: async with ext_db.connect() as ext_conn:
await run_migration(ext_conn, ext_migrations, ext.code, current_version) 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") raise ValueError("User ID must have at least 128 bits")
try: try:
int(user_id, 16) int(user_id, 16)
except Exception: except Exception as exc:
raise ValueError("Invalid hex string for User ID.") raise ValueError("Invalid hex string for User ID.") from exc
return UUID(hex=user_id[:32], version=4) return UUID(hex=user_id[:32], version=4)

View file

@ -201,8 +201,8 @@ async def pay_invoice(
""" """
try: try:
invoice = bolt11_decode(payment_request) invoice = bolt11_decode(payment_request)
except Exception: except Exception as exc:
raise InvoiceError("Bolt11 decoding failed.") raise InvoiceError("Bolt11 decoding failed.") from exc
if not invoice.amount_msat or not invoice.amount_msat > 0: if not invoice.amount_msat or not invoice.amount_msat > 0:
raise InvoiceError("Amountless invoices not supported.") raise InvoiceError("Amountless invoices not supported.")
@ -286,10 +286,10 @@ async def pay_invoice(
conn=conn, conn=conn,
**payment_kwargs, **payment_kwargs,
) )
except Exception as e: except Exception as exc:
logger.error(f"could not create temporary payment: {e}") logger.error(f"could not create temporary payment: {exc}")
# happens if the same wallet tries to pay an invoice twice # 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 # do the balance check
wallet = await get_wallet(wallet_id, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
@ -727,7 +727,7 @@ def update_cached_settings(sets_dict: dict):
except Exception: except Exception:
logger.warning(f"Failed overriding setting: {key}, value: {value}") logger.warning(f"Failed overriding setting: {key}, value: {value}")
if "super_user" in sets_dict: 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: async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:

View file

@ -43,11 +43,11 @@ async def api_auditor():
"node_balance_msats": int(node_balance), "node_balance_msats": int(node_balance),
"lnbits_balance_msats": int(total_balance), "lnbits_balance_msats": int(total_balance),
} }
except Exception: except Exception as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Could not audit balance.", detail="Could not audit balance.",
) ) from exc
@admin_router.get( @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]: async def api_topup_balance(data: CreateTopup) -> dict[str, str]:
try: try:
await get_wallet(data.id) await get_wallet(data.id)
except Exception: except Exception as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist." status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
) ) from exc
if settings.lnbits_backend_wallet_class == "VoidWallet": if settings.lnbits_backend_wallet_class == "VoidWallet":
raise HTTPException( raise HTTPException(

View file

@ -79,7 +79,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
try: try:
url = str(lnurl_decode(code)) url = str(lnurl_decode(code))
domain = urlparse(url).netloc domain = urlparse(url).netloc
except Exception: except Exception as exc:
# parse internet identifier (user@domain.com) # parse internet identifier (user@domain.com)
name_domain = code.split("@") name_domain = code.split("@")
if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2: 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: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl" status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl"
) ) from exc
# params is what will be returned to the client # params is what will be returned to the client
params: Dict = {"domain": domain} params: Dict = {"domain": domain}
@ -119,14 +119,14 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
try: try:
data = json.loads(r.text) data = json.loads(r.text)
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE, status_code=HTTPStatus.SERVICE_UNAVAILABLE,
detail={ detail={
"domain": domain, "domain": domain,
"message": f"got invalid response '{r.text[:200]}'", "message": f"got invalid response '{r.text[:200]}'",
}, },
) ) from exc
try: try:
tag: str = data.get("tag") tag: str = data.get("tag")
@ -185,7 +185,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
"domain": domain, "domain": domain,
"message": f"lnurl JSON response invalid: {exc}", "message": f"lnurl JSON response invalid: {exc}",
}, },
) ) from exc
return params return params

View file

@ -68,11 +68,11 @@ async def login(data: LoginUsernamePassword) -> JSONResponse:
raise HTTPException(HTTP_401_UNAUTHORIZED, "Invalid credentials.") raise HTTPException(HTTP_401_UNAUTHORIZED, "Invalid credentials.")
return _auth_success_response(user.username, user.id) return _auth_success_response(user.username, user.id)
except HTTPException as e: except HTTPException as exc:
raise e raise exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc
@auth_router.post("/usr", description="Login via the User ID") @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.") raise HTTPException(HTTP_401_UNAUTHORIZED, "User ID does not exist.")
return _auth_success_response(user.username or "", user.id) return _auth_success_response(user.username or "", user.id)
except HTTPException as e: except HTTPException as exc:
raise e raise exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc
@auth_router.get("/{provider}", description="SSO Provider") @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) user_id = decrypt_internal_message(provider_sso.state)
request.session.pop("user", None) request.session.pop("user", None)
return await _handle_sso_login(userinfo, user_id) return await _handle_sso_login(userinfo, user_id)
except HTTPException as e: except HTTPException as exc:
raise e raise exc
except ValueError as e: except ValueError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(e)) raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException( raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, HTTP_500_INTERNAL_SERVER_ERROR,
f"Cannot authenticate user with {provider} Auth.", f"Cannot authenticate user with {provider} Auth.",
) ) from exc
@auth_router.post("/logout") @auth_router.post("/logout")
@ -169,11 +169,13 @@ async def register(data: CreateUser) -> JSONResponse:
user = await create_user(data) user = await create_user(data)
return _auth_success_response(user.username) return _auth_success_response(user.username)
except ValueError as e: except ValueError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(e)) raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot create user.") raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot create user."
) from exc
@auth_router.put("/password") @auth_router.put("/password")
@ -189,13 +191,13 @@ async def update_password(
try: try:
return await update_user_password(data) return await update_user_password(data)
except AssertionError as e: except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(e)) raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException( raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password." HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password."
) ) from exc
@auth_router.put("/update") @auth_router.put("/update")
@ -211,11 +213,13 @@ async def update(
try: try:
return await update_account(user.id, data.username, None, data.config) return await update_account(user.id, data.username, None, data.config)
except AssertionError as e: except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(e)) raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user.") raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user."
) from exc
@auth_router.put("/first_install") @auth_router.put("/first_install")
@ -237,13 +241,13 @@ async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
await update_user_password(super_user) await update_user_password(super_user)
settings.first_install = False settings.first_install = False
return _auth_success_response(username=super_user.username) return _auth_success_response(username=super_user.username)
except AssertionError as e: except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(e)) raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as e: except Exception as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException( raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password." HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password."
) ) from exc
async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] = None): async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] = None):

View file

@ -104,10 +104,10 @@ async def api_install_extension(
ext_info.notify_upgrade() ext_info.notify_upgrade()
return extension return extension
except AssertionError as e: except AssertionError as exc:
raise HTTPException(HTTPStatus.BAD_REQUEST, str(e)) raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc
except Exception as ex: except Exception as exc:
logger.warning(ex) logger.warning(exc)
ext_info.clean_extension_files() ext_info.clean_extension_files()
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
@ -115,7 +115,7 @@ async def api_install_extension(
f"Failed to install extension {ext_info.id} " f"Failed to install extension {ext_info.id} "
f"({ext_info.installed_version})." f"({ext_info.installed_version})."
), ),
) ) from exc
@extension_router.delete("/{ext_id}") @extension_router.delete("/{ext_id}")
@ -159,10 +159,10 @@ async def api_uninstall_extension(
await delete_installed_extension(ext_id=ext_info.id) await delete_installed_extension(ext_id=ext_info.id)
logger.success(f"Extension '{ext_id}' uninstalled.") logger.success(f"Extension '{ext_id}' uninstalled.")
except Exception as ex: except Exception as exc:
raise HTTPException( 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)]) @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 return extension_releases
except Exception as ex: except Exception as exc:
raise HTTPException( 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)]) @extension_router.put("/invoice", dependencies=[Depends(check_admin)])
@ -216,11 +216,13 @@ async def get_extension_invoice(data: CreateExtension):
return payment_info return payment_info
except AssertionError as e: except AssertionError as exc:
raise HTTPException(HTTPStatus.BAD_REQUEST, str(e)) raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc
except Exception as ex: except Exception as exc:
logger.warning(ex) logger.warning(exc)
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot request invoice") raise HTTPException(
HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot request invoice"
) from exc
@extension_router.get( @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(), "is_version_compatible": config.is_version_compatible(),
"warning": config.warning, "warning": config.warning,
} }
except Exception as ex: except Exception as exc:
raise HTTPException( 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( @extension_router.delete(
@ -262,9 +264,9 @@ async def delete_extension_db(ext_id: str):
except HTTPException as ex: except HTTPException as ex:
logger.error(ex) logger.error(ex)
raise ex raise ex
except Exception as ex: except Exception as exc:
logger.error(ex) logger.error(exc)
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Cannot delete data for extension '{ext_id}'", detail=f"Cannot delete data for extension '{ext_id}'",
) ) from exc

View file

@ -171,9 +171,11 @@ async def extensions_install(
"extensions": extensions, "extensions": extensions,
}, },
) )
except Exception as e: except Exception as exc:
logger.warning(e) logger.warning(exc)
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
) from exc
@generic_router.get( @generic_router.get(
@ -396,8 +398,10 @@ async def hex_to_uuid4(hex_value: str):
try: try:
user_id = to_valid_user_id(hex_value).hex user_id = to_valid_user_id(hex_value).hex
return RedirectResponse(url=f"/wallet?usr={user_id}") return RedirectResponse(url=f"/wallet?usr={user_id}")
except Exception as e: except Exception as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
) from exc
async def toggle_extension(extension_to_enable, extension_to_disable, user_id): async def toggle_extension(extension_to_enable, extension_to_disable, user_id):

View file

@ -195,5 +195,7 @@ async def api_get_1ml_stats(node: Node = Depends(require_node)) -> Optional[Node
try: try:
r.raise_for_status() r.raise_for_status()
return r.json()["noderank"] return r.json()["noderank"]
except httpx.HTTPStatusError: except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=404, detail="Node not found on 1ml.com") raise HTTPException(
status_code=404, detail="Node not found on 1ml.com"
) from exc

View file

@ -13,6 +13,7 @@ from fastapi import (
Depends, Depends,
Header, Header,
HTTPException, HTTPException,
Query,
Request, Request,
) )
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@ -28,7 +29,6 @@ from lnbits.core.models import (
Payment, Payment,
PaymentFilters, PaymentFilters,
PaymentHistoryPoint, PaymentHistoryPoint,
Query,
Wallet, Wallet,
WalletType, WalletType,
) )
@ -133,19 +133,19 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
if data.description_hash: if data.description_hash:
try: try:
description_hash = bytes.fromhex(data.description_hash) description_hash = bytes.fromhex(data.description_hash)
except ValueError: except ValueError as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail="'description_hash' must be a valid hex string", detail="'description_hash' must be a valid hex string",
) ) from exc
if data.unhashed_description: if data.unhashed_description:
try: try:
unhashed_description = bytes.fromhex(data.unhashed_description) unhashed_description = bytes.fromhex(data.unhashed_description)
except ValueError: except ValueError as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail="'unhashed_description' must be a valid hex string", detail="'unhashed_description' must be a valid hex string",
) ) from exc
# do not save memo if description_hash or unhashed_description is set # do not save memo if description_hash or unhashed_description is set
memo = "" 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) payment_db = await get_standalone_payment(payment_hash, conn=conn)
assert payment_db is not None, "payment not found" assert payment_db is not None, "payment not found"
checking_id = payment_db.checking_id checking_id = payment_db.checking_id
except InvoiceError as e: except InvoiceError as exc:
raise HTTPException(status_code=520, detail=str(e)) raise HTTPException(status_code=520, detail=str(exc)) from exc
except Exception as exc: except Exception as exc:
raise exc raise exc
@ -192,12 +192,14 @@ async def api_payments_pay_invoice(
payment_hash = await pay_invoice( payment_hash = await pay_invoice(
wallet_id=wallet.id, payment_request=bolt11, extra=extra wallet_id=wallet.id, payment_request=bolt11, extra=extra
) )
except ValueError as e: except ValueError as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) raise HTTPException(
except PermissionError as e: status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e)) ) from exc
except PaymentError as e: except PermissionError as exc:
raise HTTPException(status_code=520, detail=str(e)) 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: except Exception as exc:
raise exc raise exc
@ -282,11 +284,11 @@ async def api_payments_pay_lnurl(
if r.is_error: if r.is_error:
raise httpx.ConnectError("LNURL callback connection error") raise httpx.ConnectError("LNURL callback connection error")
r.raise_for_status() r.raise_for_status()
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError) as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"Failed to connect to {domain}.", detail=f"Failed to connect to {domain}.",
) ) from exc
params = json.loads(r.text) params = json.loads(r.text)
if params.get("status") == "ERROR": if params.get("status") == "ERROR":

View file

@ -27,10 +27,10 @@ async def api_public_payment_longpolling(payment_hash):
invoice = bolt11.decode(payment.bolt11) invoice = bolt11.decode(payment.bolt11)
if invoice.has_expired(): if invoice.has_expired():
return {"status": "expired"} return {"status": "expired"}
except Exception: except Exception as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Invalid bolt11 invoice." status_code=HTTPStatus.BAD_REQUEST, detail="Invalid bolt11 invoice."
) ) from exc
payment_queue = asyncio.Queue(0) payment_queue = asyncio.Queue(0)

View file

@ -38,10 +38,10 @@ async def api_create_tinyurl(
if tinyurl.wallet == wallet.wallet.id: if tinyurl.wallet == wallet.wallet.id:
return tinyurl return tinyurl
return await create_tinyurl(url, endless, wallet.wallet.id) return await create_tinyurl(url, endless, wallet.wallet.id)
except Exception: except Exception as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Unable to create tinyurl" status_code=HTTPStatus.BAD_REQUEST, detail="Unable to create tinyurl"
) ) from exc
@tinyurl_router.get( @tinyurl_router.get(
@ -60,10 +60,10 @@ async def api_get_tinyurl(
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided." status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided."
) )
except Exception: except Exception as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Unable to fetch tinyurl" status_code=HTTPStatus.NOT_FOUND, detail="Unable to fetch tinyurl"
) ) from exc
@tinyurl_router.delete( @tinyurl_router.delete(
@ -83,10 +83,10 @@ async def api_delete_tinyurl(
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided." status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided."
) )
except Exception: except Exception as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Unable to delete" status_code=HTTPStatus.BAD_REQUEST, detail="Unable to delete"
) ) from exc
@tinyurl_router.get( @tinyurl_router.get(

View file

@ -69,10 +69,10 @@ class KeyChecker(SecurityBase):
detail="Invalid key or wallet.", detail="Invalid key or wallet.",
) )
self.wallet = wallet self.wallet = wallet
except KeyError: except KeyError as exc:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="`X-API-KEY` header missing." status_code=HTTPStatus.BAD_REQUEST, detail="`X-API-KEY` header missing."
) ) from exc
class WalletInvoiceKeyChecker(KeyChecker): 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"))) return await get_account_by_email(str(payload.get("email")))
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Data missing for access token.") raise HTTPException(HTTPStatus.UNAUTHORIZED, "Data missing for access token.")
except ExpiredSignatureError: except ExpiredSignatureError as exc:
raise HTTPException( raise HTTPException(
HTTPStatus.UNAUTHORIZED, "Session expired.", {"token-expired": "true"} HTTPStatus.UNAUTHORIZED, "Session expired.", {"token-expired": "true"}
) ) from exc
except JWTError as e: except JWTError as exc:
logger.debug(e) logger.debug(exc)
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid access token.") raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid access token.") from exc

View file

@ -428,9 +428,9 @@ class InstallableExtension(BaseModel):
self._remember_payment_info() self._remember_payment_info()
except Exception as ex: except Exception as exc:
logger.warning(ex) logger.warning(exc)
raise AssertionError("Cannot fetch extension archive file") raise AssertionError("Cannot fetch extension archive file") from exc
archive_hash = file_hash(ext_zip_file) archive_hash = file_hash(ext_zip_file)
if self.installed_release.hash and self.installed_release.hash != archive_hash: if self.installed_release.hash and self.installed_release.hash != archive_hash:
@ -560,7 +560,11 @@ class InstallableExtension(BaseModel):
return ext return ext
@classmethod @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] return [InstallableExtension.from_row(row) for row in rows]
@classmethod @classmethod

View file

@ -44,11 +44,12 @@ def catch_rpc_errors(f):
async def wrapper(*args, **kwargs): async def wrapper(*args, **kwargs):
try: try:
return await f(*args, **kwargs) return await f(*args, **kwargs)
except RpcError as e: except RpcError as exc:
if e.error["code"] == -32602: msg = exc.error["message"]
raise HTTPException(status_code=400, detail=e.error["message"]) if exc.error["code"] == -32602:
raise HTTPException(status_code=400, detail=msg) from exc
else: else:
raise HTTPException(status_code=500, detail=e.error["message"]) raise HTTPException(status_code=500, detail=msg) from exc
return wrapper return wrapper
@ -66,9 +67,11 @@ class CoreLightningNode(Node):
# https://docs.corelightning.org/reference/lightning-connect # https://docs.corelightning.org/reference/lightning-connect
try: try:
await self.ln_rpc("connect", uri) await self.ln_rpc("connect", uri)
except RpcError as e: except RpcError as exc:
if e.error["code"] == 400: if exc.error["code"] == 400:
raise HTTPException(HTTPStatus.BAD_REQUEST, detail=e.error["message"]) raise HTTPException(
HTTPStatus.BAD_REQUEST, detail=exc.error["message"]
) from exc
else: else:
raise raise
@ -76,12 +79,12 @@ class CoreLightningNode(Node):
async def disconnect_peer(self, peer_id: str): async def disconnect_peer(self, peer_id: str):
try: try:
await self.ln_rpc("disconnect", peer_id) await self.ln_rpc("disconnect", peer_id)
except RpcError as e: except RpcError as exc:
if e.error["code"] == -1: if exc.error["code"] == -1:
raise HTTPException( raise HTTPException(
HTTPStatus.BAD_REQUEST, HTTPStatus.BAD_REQUEST,
detail=e.error["message"], detail=exc.error["message"],
) ) from exc
else: else:
raise raise
@ -105,14 +108,14 @@ class CoreLightningNode(Node):
funding_txid=result["txid"], funding_txid=result["txid"],
output_index=result["outnum"], output_index=result["outnum"],
) )
except RpcError as e: except RpcError as exc:
message = e.error["message"] message = exc.error["message"]
if "amount: should be a satoshi amount" in message: if "amount: should be a satoshi amount" in message:
raise HTTPException( raise HTTPException(
HTTPStatus.BAD_REQUEST, HTTPStatus.BAD_REQUEST,
detail="The amount is not a valid satoshi amount.", detail="The amount is not a valid satoshi amount.",
) ) from exc
if "Unknown peer" in message: if "Unknown peer" in message:
raise HTTPException( raise HTTPException(
@ -121,7 +124,7 @@ class CoreLightningNode(Node):
"We where able to connect to the peer but CLN " "We where able to connect to the peer but CLN "
"can't find it when opening a channel." "can't find it when opening a channel."
), ),
) ) from exc
if "Owning subdaemon openingd died" in message: if "Owning subdaemon openingd died" in message:
# https://github.com/ElementsProject/lightning/issues/2798#issuecomment-511205719 # 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 " "Likely the peer didn't like our channel opening "
"proposal and disconnected from us." "proposal and disconnected from us."
), ),
) ) from exc
if ( if (
"Number of pending channels exceed maximum" in message "Number of pending channels exceed maximum" in message
or "exceeds maximum chan size of 10 BTC" in message or "exceeds maximum chan size of 10 BTC" in message
or "Could not afford" 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 raise
@catch_rpc_errors @catch_rpc_errors
@ -152,13 +155,13 @@ class CoreLightningNode(Node):
raise HTTPException(status_code=400, detail="Short id required") raise HTTPException(status_code=400, detail="Short id required")
try: try:
await self.ln_rpc("close", short_id) await self.ln_rpc("close", short_id)
except RpcError as e: except RpcError as exc:
message = e.error["message"] message = exc.error["message"]
if ( if (
"Short channel ID not active:" in message "Short channel ID not active:" in message
or "Short channel ID not found" 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: else:
raise raise

View file

@ -60,11 +60,13 @@ class LndRestNode(Node):
) )
try: try:
response.raise_for_status() response.raise_for_status()
except HTTPStatusError as e: except HTTPStatusError as exc:
json = e.response.json() json = exc.response.json()
if json: if json:
error = json.get("error") or 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() return response.json()
def get(self, path: str, **kwargs): def get(self, path: str, **kwargs):
@ -81,8 +83,8 @@ class LndRestNode(Node):
async def connect_peer(self, uri: str): async def connect_peer(self, uri: str):
try: try:
pubkey, host = uri.split("@") pubkey, host = uri.split("@")
except ValueError: except ValueError as exc:
raise HTTPException(400, detail="Invalid peer URI") raise HTTPException(400, detail="Invalid peer URI") from exc
await self.request( await self.request(
"POST", "POST",
"/v1/peers", "/v1/peers",
@ -96,11 +98,11 @@ class LndRestNode(Node):
async def disconnect_peer(self, peer_id: str): async def disconnect_peer(self, peer_id: str):
try: try:
await self.request("DELETE", "/v1/peers/" + peer_id) await self.request("DELETE", "/v1/peers/" + peer_id)
except HTTPException as e: except HTTPException as exc:
if "unable to disconnect" in e.detail: if "unable to disconnect" in exc.detail:
raise HTTPException( raise HTTPException(
HTTPStatus.BAD_REQUEST, detail="Peer is not connected" HTTPStatus.BAD_REQUEST, detail="Peer is not connected"
) ) from exc
raise raise
async def _get_peer_info(self, peer_id: str) -> NodePeerInfo: async def _get_peer_info(self, peer_id: str) -> NodePeerInfo:

View file

@ -48,18 +48,20 @@ def main(
# this beautiful beast parses all command line arguments and # this beautiful beast parses all command line arguments and
# passes them to the uvicorn server # passes them to the uvicorn server
# TODO: why is this needed? it would be better only to rely on the commands options
d = {} d = {}
for a in ctx.args: for a in ctx.args:
item = a.split("=") item = a.split("=")
if len(item) > 1: # argument like --key=value if len(item) > 1: # argument like --key=value
print(a, item) 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 int(item[1]) # need to convert to int if it's a number
if item[1].isdigit() if item[1].isdigit()
else item[1] else item[1]
) )
else: else:
d[a.strip("--")] = True # argument like --key # argument like --key
d[a.strip("--")] = True # noqa: B005
while True: while True:
config = uvicorn.Config( config = uvicorn.Config(

View file

@ -60,8 +60,8 @@ class AESCipher:
aes = AES.new(key, AES.MODE_CBC, iv) aes = AES.new(key, AES.MODE_CBC, iv)
try: try:
return self.unpad(aes.decrypt(encrypted[16:])).decode() # type: ignore return self.unpad(aes.decrypt(encrypted[16:])).decode() # type: ignore
except UnicodeDecodeError: except UnicodeDecodeError as exc:
raise ValueError("Wrong passphrase") raise ValueError("Wrong passphrase") from exc
def encrypt(self, message: bytes) -> str: def encrypt(self, message: bytes) -> str:
passphrase = self.passphrase passphrase = self.passphrase

View file

@ -93,11 +93,13 @@ class PaymentPendingStatus(PaymentStatus):
class Wallet(ABC): class Wallet(ABC):
async def cleanup(self):
pass
__node_cls__: Optional[type[Node]] = None __node_cls__: Optional[type[Node]] = None
@abstractmethod
async def cleanup(self):
pass
@abstractmethod @abstractmethod
def status(self) -> Coroutine[None, None, StatusResponse]: def status(self) -> Coroutine[None, None, StatusResponse]:
pass pass

View file

@ -27,6 +27,9 @@ class ClicheWallet(Wallet):
self.endpoint = self.normalize_endpoint(settings.cliche_endpoint) self.endpoint = self.normalize_endpoint(settings.cliche_endpoint)
async def cleanup(self):
pass
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
try: try:
ws = create_connection(self.endpoint) ws = create_connection(self.endpoint)

View file

@ -31,6 +31,9 @@ async def run_sync(func) -> Any:
class CoreLightningWallet(Wallet): class CoreLightningWallet(Wallet):
__node_cls__ = CoreLightningNode __node_cls__ = CoreLightningNode
async def cleanup(self):
pass
def __init__(self): def __init__(self):
rpc = settings.corelightning_rpc or settings.clightning_rpc rpc = settings.corelightning_rpc or settings.clightning_rpc
if not rpc: if not rpc:

View file

@ -42,6 +42,9 @@ class FakeWallet(Wallet):
32, 32,
).hex() ).hex()
async def cleanup(self):
pass
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
logger.info( logger.info(
"FakeWallet funding source is for using LNbits as a centralised," "FakeWallet funding source is for using LNbits as a centralised,"

View file

@ -109,6 +109,9 @@ class LndWallet(Wallet):
def metadata_callback(self, _, callback): def metadata_callback(self, _, callback):
callback([("macaroon", self.macaroon)], None) callback([("macaroon", self.macaroon)], None)
async def cleanup(self):
pass
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
try: try:
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest()) resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())

View file

@ -77,12 +77,12 @@ class SparkWallet(Wallet):
httpx.HTTPError, httpx.HTTPError,
httpx.TimeoutException, httpx.TimeoutException,
) as exc: ) as exc:
raise UnknownError(f"error connecting to spark: {exc}") raise UnknownError(f"error connecting to spark: {exc}") from exc
try: try:
data = r.json() data = r.json()
except Exception: except Exception as exc:
raise UnknownError(r.text) raise UnknownError(r.text) from exc
if r.is_error: if r.is_error:
if r.status_code == 401: if r.status_code == 401:
@ -171,7 +171,7 @@ class SparkWallet(Wallet):
raise SparkError( raise SparkError(
f"listpays({payment_hash}) returned an unexpected response:" f"listpays({payment_hash}) returned an unexpected response:"
f" {listpays}" f" {listpays}"
) ) from exc
if pay["status"] == "failed": if pay["status"] == "failed":
return PaymentResponse(False, None, None, None, str(exc)) return PaymentResponse(False, None, None, None, str(exc))

View file

@ -13,6 +13,10 @@ from .base import (
class VoidWallet(Wallet): class VoidWallet(Wallet):
async def cleanup(self):
pass
async def create_invoice(self, *_, **__) -> InvoiceResponse: async def create_invoice(self, *_, **__) -> InvoiceResponse:
return InvoiceResponse( return InvoiceResponse(
ok=False, error_message="VoidWallet cannot create invoices." ok=False, error_message="VoidWallet cannot create invoices."

View file

@ -181,7 +181,8 @@ extend-exclude = [
# N - naming # N - naming
# UP - pyupgrade # UP - pyupgrade
# RUF - ruff specific rules # 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) # UP007: pyupgrade: use X | Y instead of Optional. (python3.10)
# RUF012: mutable-class-default # RUF012: mutable-class-default
ignore = ["UP007", "RUF012"] ignore = ["UP007", "RUF012"]
@ -206,3 +207,12 @@ classmethod-decorators = [
[tool.ruff.lint.mccabe] [tool.ruff.lint.mccabe]
# TODO: Decrease this to 10. # TODO: Decrease this to 10.
max-complexity = 16 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"
]

View file

@ -62,7 +62,7 @@ def load_funding_source(funding_source: FundingSourceConfig) -> BaseWallet:
custom_settings = funding_source.settings custom_settings = funding_source.settings
original_settings = {} original_settings = {}
settings = getattr(wallets_module, "settings") settings = wallets_module.settings
for s in custom_settings: for s in custom_settings:
original_settings[s] = getattr(settings, s) original_settings[s] = getattr(settings, s)
@ -93,7 +93,7 @@ async def check_assertions(wallet, _test_data: WalletTest):
elif "expect_error" in test_data: elif "expect_error" in test_data:
await _assert_error(wallet, tested_func, call_params, _test_data.expect_error) await _assert_error(wallet, tested_func, call_params, _test_data.expect_error)
else: else:
assert False, "Expected outcome not specified" raise AssertionError("Expected outcome not specified")
async def _assert_data(wallet, tested_func, call_params, expect): async def _assert_data(wallet, tested_func, call_params, expect):

View file

@ -7,7 +7,7 @@ import argparse
import os import os
import sqlite3 import sqlite3
import sys import sys
from typing import List from typing import List, Optional
import psycopg2 import psycopg2
@ -90,21 +90,23 @@ def insert_to_pg(query, data):
for d in data: for d in data:
try: try:
cursor.execute(query, d) cursor.execute(query, d)
except Exception as e: except Exception as exc:
if args.ignore_errors: if args.ignore_errors:
print(e) print(exc)
print(f"Failed to insert {d}") print(f"Failed to insert {d}")
else: else:
print("query:", query) print("query:", query)
print("data:", d) print("data:", d)
raise ValueError(f"Failed to insert {d}") raise ValueError(f"Failed to insert {d}") from exc
connection.commit() connection.commit()
cursor.close() cursor.close()
connection.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}") print(f"Migrating core: {file}")
migrate_db(file, "public", exclude_tables) migrate_db(file, "public", exclude_tables)
print("✅ Migrated core") print("✅ Migrated core")
@ -118,8 +120,10 @@ def migrate_ext(file: str):
print(f"✅ Migrated ext: {schema}") 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: # first we check if this file exists:
if exclude_tables is None:
exclude_tables = []
assert os.path.isfile(file), f"{file} does not exist!" assert os.path.isfile(file), f"{file} does not exist!"
cursor = get_sqlite_cursor(file) cursor = get_sqlite_cursor(file)