diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e391a186b..1a64c9c57 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,14 +17,14 @@ repos:
rev: 23.7.0
hooks:
- id: black
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.0.283
+ hooks:
+ - id: ruff
+ args: [ --fix, --exit-non-zero-on-fix ]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: '50c5478ed9e10bf360335449280cf2a67f4edb7a'
hooks:
- id: prettier
types_or: [css, javascript, html, json]
args: ['lnbits']
- - repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.0.283
- hooks:
- - id: ruff
- args: [ --fix, --exit-non-zero-on-fix ]
diff --git a/lnbits/app.py b/lnbits/app.py
index 6fceb9364..e27824cfb 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -413,9 +413,11 @@ def get_db_vendor_name():
return (
"PostgreSQL"
if db_url and db_url.startswith("postgres://")
- else "CockroachDB"
- if db_url and db_url.startswith("cockroachdb://")
- else "SQLite"
+ else (
+ "CockroachDB"
+ if db_url and db_url.startswith("cockroachdb://")
+ else "SQLite"
+ )
)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 03ab04f1b..a8471fe17 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -61,7 +61,8 @@ async def migrate_databases():
)
elif conn.type in {POSTGRES, COCKROACH}:
exists = await conn.fetchone(
- "SELECT * FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'dbversions'"
+ "SELECT * FROM information_schema.tables WHERE table_schema = 'public'"
+ " AND table_name = 'dbversions'"
)
if not exists:
diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py
index dc8991f61..10b9a02fd 100644
--- a/lnbits/core/crud.py
+++ b/lnbits/core/crud.py
@@ -58,7 +58,9 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
)
wallets = await (conn or db).fetchall(
"""
- SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
+ SELECT *, COALESCE((
+ SELECT balance FROM balances WHERE wallet = wallets.id
+ ), 0) AS balance_msat
FROM wallets
WHERE "user" = ?
""",
@@ -89,9 +91,9 @@ async def add_installed_extension(
conn: Optional[Connection] = None,
) -> None:
meta = {
- "installed_release": dict(ext.installed_release)
- if ext.installed_release
- else None,
+ "installed_release": (
+ dict(ext.installed_release) if ext.installed_release else None
+ ),
"dependencies": ext.dependencies,
}
@@ -99,9 +101,11 @@ async def add_installed_extension(
await (conn or db).execute(
"""
- INSERT INTO installed_extensions (id, version, name, short_description, icon, stars, meta) VALUES (?, ?, ?, ?, ?, ?, ?)
- ON CONFLICT (id) DO
- UPDATE SET (version, name, active, short_description, icon, stars, meta) = (?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO installed_extensions
+ (id, version, name, short_description, icon, stars, meta)
+ VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (id) DO UPDATE SET
+ (version, name, active, short_description, icon, stars, meta) =
+ (?, ?, ?, ?, ?, ?, ?)
""",
(
ext.id,
@@ -270,9 +274,8 @@ async def get_wallet(
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
"""
- SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
- FROM wallets
- WHERE id = ?
+ SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0)
+ AS balance_msat FROM wallets WHERE id = ?
""",
(wallet_id,),
)
@@ -287,9 +290,8 @@ async def get_wallet_for_key(
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
"""
- SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
- FROM wallets
- WHERE adminkey = ? OR inkey = ?
+ SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0)
+ AS balance_msat FROM wallets WHERE adminkey = ? OR inkey = ?
""",
(key, key),
)
@@ -544,9 +546,11 @@ async def create_payment(
pending,
memo,
fee,
- json.dumps(extra)
- if extra and extra != {} and type(extra) is dict
- else None,
+ (
+ json.dumps(extra)
+ if extra and extra != {} and type(extra) is dict
+ else None
+ ),
webhook,
db.datetime_to_timestamp(expiration_date),
),
@@ -608,7 +612,8 @@ async def update_payment_extra(
) -> None:
"""
Only update the `extra` field for the payment.
- Old values in the `extra` JSON object will be kept unless the new `extra` overwrites them.
+ Old values in the `extra` JSON object will be kept
+ unless the new `extra` overwrites them.
"""
amount_clause = "AND amount < 0" if outgoing else "AND amount > 0"
@@ -662,7 +667,10 @@ async def check_internal(
async def check_internal_pending(
payment_hash: str, conn: Optional[Connection] = None
) -> bool:
- """Returns False if the internal payment is not pending anymore (and thus paid), otherwise True"""
+ """
+ Returns False if the internal payment is not pending anymore
+ (and thus paid), otherwise True
+ """
row = await (conn or db).fetchone(
"""
SELECT pending FROM apipayments
diff --git a/lnbits/core/helpers.py b/lnbits/core/helpers.py
index a5502a506..12eb97a1c 100644
--- a/lnbits/core/helpers.py
+++ b/lnbits/core/helpers.py
@@ -51,7 +51,8 @@ async def stop_extension_background_work(ext_id: str, user: str):
"""
Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
Extensions SHOULD expose a DELETE enpoint at the root level of their API.
- This function tries first to call the endpoint using `http` and if if fails it tries using `https`.
+ This function tries first to call the endpoint using `http`
+ and if it fails it tries using `https`.
"""
async with httpx.AsyncClient() as client:
try:
diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py
index 09f930a0d..6c3743cb0 100644
--- a/lnbits/core/migrations.py
+++ b/lnbits/core/migrations.py
@@ -239,7 +239,8 @@ async def m007_set_invoice_expiries(db):
invoice.date + invoice.expiry
)
logger.info(
- f"Migration: {i+1}/{len(rows)} setting expiry of invoice {invoice.payment_hash} to {expiration_date}"
+ f"Migration: {i+1}/{len(rows)} setting expiry of invoice"
+ f" {invoice.payment_hash} to {expiration_date}"
)
await db.execute(
"""
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 3b9f1b1dd..c12525653 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -115,16 +115,17 @@ async def pay_invoice(
) -> str:
"""
Pay a Lightning invoice.
- First, we create a temporary payment in the database with fees set to the reserve fee.
- We then check whether the balance of the payer would go negative.
- We then attempt to pay the invoice through the backend.
- If the payment is successful, we update the payment in the database with the payment details.
+ First, we create a temporary payment in the database with fees set to the reserve
+ fee. We then check whether the balance of the payer would go negative.
+ We then attempt to pay the invoice through the backend. If the payment is
+ successful, we update the payment in the database with the payment details.
If the payment is unsuccessful, we delete the temporary payment.
- If the payment is still in flight, we hope that some other process will regularly check for the payment.
+ If the payment is still in flight, we hope that some other process
+ will regularly check for the payment.
"""
invoice = bolt11.decode(payment_request)
fee_reserve_msat = fee_reserve(invoice.amount_msat)
- async with (db.reuse_conn(conn) if conn else db.connect()) as conn:
+ async with db.reuse_conn(conn) if conn else db.connect() as conn:
temp_id = invoice.payment_hash
internal_id = f"internal_{invoice.payment_hash}"
@@ -151,11 +152,13 @@ async def pay_invoice(
extra=extra,
)
- # we check if an internal invoice exists that has already been paid (not pending anymore)
+ # we check if an internal invoice exists that has already been paid
+ # (not pending anymore)
if not await check_internal_pending(invoice.payment_hash, conn=conn):
raise PaymentFailure("Internal invoice already paid.")
- # check_internal() returns the checking_id of the invoice we're waiting for (pending only)
+ # check_internal() returns the checking_id of the invoice we're waiting for
+ # (pending only)
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn)
if internal_checking_id:
# perform additional checks on the internal payment
@@ -202,7 +205,8 @@ async def pay_invoice(
logger.debug("balance is too low, deleting temporary payment")
if not internal_checking_id and wallet.balance_msat > -fee_reserve_msat:
raise PaymentFailure(
- f"You must reserve at least ({round(fee_reserve_msat/1000)} sat) to cover potential routing fees."
+ f"You must reserve at least ({round(fee_reserve_msat/1000)} sat) to"
+ " cover potential routing fees."
)
raise PermissionError("Insufficient balance.")
@@ -232,7 +236,8 @@ async def pay_invoice(
if payment.checking_id and payment.checking_id != temp_id:
logger.warning(
- f"backend sent unexpected checking_id (expected: {temp_id} got: {payment.checking_id})"
+ f"backend sent unexpected checking_id (expected: {temp_id} got:"
+ f" {payment.checking_id})"
)
logger.debug(f"backend: pay_invoice finished {temp_id}")
@@ -267,7 +272,8 @@ async def pay_invoice(
)
else:
logger.warning(
- f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
+ "didn't receive checking_id from backend, payment may be stuck in"
+ f" database: {temp_id}"
)
return invoice.payment_hash
@@ -301,7 +307,8 @@ async def redeem_lnurl_withdraw(
)
except Exception:
logger.warning(
- f"failed to create invoice on redeem_lnurl_withdraw from {lnurl}. params: {res}"
+ f"failed to create invoice on redeem_lnurl_withdraw "
+ f"from {lnurl}. params: {res}"
)
return None
@@ -420,7 +427,8 @@ async def check_transaction_status(
return status
-# WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ
+# WARN: this same value must be used for balance check and passed to
+# WALLET.pay_invoice(), it may cause a vulnerability if the values differ
def fee_reserve(amount_msat: int) -> int:
reserve_min = settings.lnbits_reserve_fee_min
reserve_percent = settings.lnbits_reserve_fee_percent
diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py
index 5f7f67bb6..f2a8ce6ad 100644
--- a/lnbits/core/tasks.py
+++ b/lnbits/core/tasks.py
@@ -48,7 +48,8 @@ async def killswitch_task():
await switch_to_voidwallet()
except (httpx.ConnectError, httpx.RequestError):
logger.error(
- f"Cannot fetch lnbits status manifest. {settings.lnbits_status_manifest}"
+ "Cannot fetch lnbits status manifest."
+ f" {settings.lnbits_status_manifest}"
)
await asyncio.sleep(settings.lnbits_killswitch_interval * 60)
@@ -80,8 +81,8 @@ async def watchdog_task():
def register_task_listeners():
"""
- Registers an invoice listener queue for the core tasks.
- Incoming payaments in this queue will eventually trigger the signals sent to all other extensions
+ Registers an invoice listener queue for the core tasks. Incoming payments in this
+ queue will eventually trigger the signals sent to all other extensions
and fulfill other core tasks such as dispatching webhooks.
"""
invoice_paid_queue = asyncio.Queue(5)
@@ -93,7 +94,8 @@ def register_task_listeners():
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
"""
- This worker dispatches events to all extensions, dispatches webhooks and balance notifys.
+ This worker dispatches events to all extensions,
+ dispatches webhooks and balance notifys.
"""
while True:
payment = await invoice_paid_queue.get()
@@ -135,11 +137,15 @@ async def dispatch_webhook(payment: Payment):
"""
Dispatches the webhook to the webhook url.
"""
+ logger.debug("sending webhook", payment.webhook)
+
+ if not payment.webhook:
+ return await mark_webhook_sent(payment, -1)
+
async with httpx.AsyncClient() as client:
data = payment.dict()
try:
- logger.debug("sending webhook", payment.webhook)
- r = await client.post(payment.webhook, json=data, timeout=40) # type: ignore
+ r = await client.post(payment.webhook, json=data, timeout=40)
await mark_webhook_sent(payment, r.status_code)
except (httpx.ConnectError, httpx.RequestError):
await mark_webhook_sent(payment, -1)
diff --git a/lnbits/core/views/admin_api.py b/lnbits/core/views/admin_api.py
index ea37fc6f5..a14583b9b 100644
--- a/lnbits/core/views/admin_api.py
+++ b/lnbits/core/views/admin_api.py
@@ -126,10 +126,10 @@ async def api_download_backup() -> FileResponse:
p = urlparse(db_url)
command = (
f"pg_dump --host={p.hostname} "
- f'--dbname={p.path.replace("/", "")} '
+ f"--dbname={p.path.replace('/', '')} "
f"--username={p.username} "
- f"--no-password "
- f"--format=c "
+ "--no-password "
+ "--format=c "
f"--file={pg_backup_filename}"
)
proc = Popen(
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 704428a2f..b48f3da65 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -234,8 +234,9 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
internal=data.internal,
conn=conn,
)
- # NOTE: we get the checking_id with a seperate query because create_invoice does not return it
- # and it would be a big hustle to change its return type (used across extensions)
+ # NOTE: we get the checking_id with a seperate query because create_invoice
+ # does not return it and it would be a big hustle to change its return type
+ # (used across extensions)
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
@@ -309,12 +310,13 @@ async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
"/api/v1/payments",
summary="Create or pay an invoice",
description="""
-This endpoint can be used both to generate and pay a BOLT11 invoice.
-To generate a new invoice for receiving funds into the authorized account,
-specify at least the first four fields in the POST body: `out: false`, `amount`, `unit`, and `memo`.
-To pay an arbitrary invoice from the funds already in the authorized account,
-specify `out: true` and use the `bolt11` field to supply the BOLT11 invoice to be paid.
-""",
+ This endpoint can be used both to generate and pay a BOLT11 invoice.
+ To generate a new invoice for receiving funds into the authorized account,
+ specify at least the first four fields in the POST body: `out: false`,
+ `amount`, `unit`, and `memo`. To pay an arbitrary invoice from the funds
+ already in the authorized account, specify `out: true` and use the `bolt11`
+ field to supply the BOLT11 invoice to be paid.
+ """,
status_code=HTTPStatus.CREATED,
)
async def api_payments_create(
@@ -379,8 +381,10 @@ async def api_payments_pay_lnurl(
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=(
- f"{domain} returned an invalid invoice. Expected {data.amount} msat, "
- f"got {invoice.amount_msat}.",
+ (
+ f"{domain} returned an invalid invoice. Expected"
+ f" {data.amount} msat, got {invoice.amount_msat}."
+ ),
),
)
@@ -388,8 +392,10 @@ async def api_payments_pay_lnurl(
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=(
- f"{domain} returned an invalid invoice. Expected description_hash == "
- f"{data.description_hash}, got {invoice.description_hash}.",
+ (
+ f"{domain} returned an invalid invoice. Expected description_hash"
+ f" == {data.description_hash}, got {invoice.description_hash}."
+ ),
),
)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 7b36b4b7b..b9694f0e7 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -132,12 +132,12 @@ async def extensions_install(
"isAvailable": ext.id in all_extensions,
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
"isActive": ext.id not in inactive_extensions,
- "latestRelease": dict(ext.latest_release)
- if ext.latest_release
- else None,
- "installedRelease": dict(ext.installed_release)
- if ext.installed_release
- else None,
+ "latestRelease": (
+ dict(ext.latest_release) if ext.latest_release else None
+ ),
+ "installedRelease": (
+ dict(ext.installed_release) if ext.installed_release else None
+ ),
},
installable_exts,
)
@@ -160,13 +160,13 @@ async def extensions_install(
"/wallet",
response_class=HTMLResponse,
description="""
-Args:
-
-just **wallet_name**: create a new user, then create a new wallet for user with wallet_name
-just **user_id**: return the first user wallet or create one if none found (with default wallet_name)
-**user_id** and **wallet_name**: create a new wallet for user with wallet_name
-**user_id** and **wallet_id**: return that wallet if user is the owner
-nothing: create everything
+just **wallet_name**: create a new user, then create a new wallet
+ for user with wallet_name
+just **user_id**: return the first user wallet or create one if none found
+ (with default wallet_name)
+**user_id** and **wallet_name**: create a new wallet for user with wallet_name
+**user_id** and **wallet_id**: return that wallet if user is the owner
+nothing: create everything
""",
)
async def wallet(
@@ -210,7 +210,8 @@ async def wallet(
else:
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
logger.info(
- f"Created new wallet {wallet_name if wallet_name else '(no name)'} for user {user.id}"
+ f"Created new wallet {wallet_name if wallet_name else '(no name)'} for"
+ f" user {user.id}"
)
return RedirectResponse(
@@ -219,7 +220,9 @@ async def wallet(
)
logger.debug(
- f"Access {'user '+ user.id + ' ' if user else ''} {'wallet ' + wallet_name if wallet_name else ''}"
+ "Access "
+ f"{'user '+ user.id + ' ' if user else ''} "
+ f"{'wallet ' + wallet_name if wallet_name else ''}"
)
userwallet = user.get_wallet(wallet_id)
if not userwallet:
@@ -255,7 +258,9 @@ async def lnurl_full_withdraw(request: Request):
"k1": "0",
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
"maxWithdrawable": wallet.withdrawable_balance,
- "defaultDescription": f"{settings.lnbits_site_title} balance withdraw from {wallet.id[0:5]}",
+ "defaultDescription": (
+ f"{settings.lnbits_site_title} balance withdraw from {wallet.id[0:5]}"
+ ),
"balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
}
@@ -362,9 +367,11 @@ async def manifest(usr: str):
"name": settings.lnbits_site_title + " Wallet",
"icons": [
{
- "src": settings.lnbits_custom_logo
- if settings.lnbits_custom_logo
- else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
+ "src": (
+ settings.lnbits_custom_logo
+ if settings.lnbits_custom_logo
+ else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png"
+ ),
"type": "image/png",
"sizes": "900x900",
}
diff --git a/lnbits/db.py b/lnbits/db.py
index 9c692f519..281188c0d 100644
--- a/lnbits/db.py
+++ b/lnbits/db.py
@@ -421,8 +421,8 @@ class Filters(BaseModel, Generic[TFilterModel]):
Generic helper class for filtering and sorting data.
For usage in an api endpoint, use the `parse_filters` dependency.
- When constructing this class manually always make sure to pass a model so that the values can be validated.
- Otherwise, make sure to validate the inputs manually.
+ When constructing this class manually always make sure to pass a model so that
+ the values can be validated. Otherwise, make sure to validate the inputs manually.
"""
filters: List[Filter[TFilterModel]] = []
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 70f653d46..c23f03a7b 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -49,16 +49,16 @@ class KeyChecker(SecurityBase):
if self._api_key
else request.headers.get("X-API-KEY") or request.query_params["api-key"]
)
- # FIXME: Find another way to validate the key. A fetch from DB should be avoided here.
- # Also, we should not return the wallet here - thats silly.
- # Possibly store it in a Redis DB
- self.wallet = await get_wallet_for_key(key_value, self._key_type) # type: ignore
- if not self.wallet:
+ # FIXME: Find another way to validate the key. A fetch from DB should be
+ # avoided here. Also, we should not return the wallet here - thats
+ # silly. Possibly store it in a Redis DB
+ wallet = await get_wallet_for_key(key_value, self._key_type)
+ self.wallet = wallet # type: ignore
+ if not wallet:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED,
detail="Invalid key or expired key.",
)
-
except KeyError:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="`X-API-KEY` header missing."
@@ -156,7 +156,8 @@ async def get_key_type(
if exc.status_code == HTTPStatus.BAD_REQUEST:
raise
elif exc.status_code == HTTPStatus.UNAUTHORIZED:
- # we pass this in case it is not an invoice key, nor an admin key, and then return NOT_FOUND at the end of this block
+ # we pass this in case it is not an invoice key, nor an admin key,
+ # and then return NOT_FOUND at the end of this block
pass
else:
raise
diff --git a/lnbits/extension_manager.py b/lnbits/extension_manager.py
index 957037474..d4a17a202 100644
--- a/lnbits/extension_manager.py
+++ b/lnbits/extension_manager.py
@@ -426,7 +426,10 @@ class InstallableExtension(BaseModel):
logger.success(f"Extension {self.name} ({self.installed_version}) installed.")
def nofiy_upgrade(self) -> None:
- """Update the list of upgraded extensions. The middleware will perform redirects based on this"""
+ """
+ Update the list of upgraded extensions. The middleware will perform
+ redirects based on this
+ """
clean_upgraded_exts = list(
filter(
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index 065d0d9b0..59252d795 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -93,9 +93,11 @@ def get_current_extension_name() -> str:
def generate_filter_params_openapi(model: Type[FilterModel], keep_optional=False):
"""
- Generate openapi documentation for Filters. This is intended to be used along parse_filters (see example)
+ Generate openapi documentation for Filters. This is intended to be used along
+ parse_filters (see example)
:param model: Filter model
- :param keep_optional: If false, all parameters will be optional, otherwise inferred from model
+ :param keep_optional: If false, all parameters will be optional,
+ otherwise inferred from model
"""
fields = list(model.__fields__.values())
params = []
diff --git a/lnbits/middleware.py b/lnbits/middleware.py
index 2944702e4..83b2dcd1b 100644
--- a/lnbits/middleware.py
+++ b/lnbits/middleware.py
@@ -18,7 +18,8 @@ from lnbits.settings import settings
class InstalledExtensionMiddleware:
# This middleware class intercepts calls made to the extensions API and:
# - it blocks the calls if the extension has been disabled or uninstalled.
- # - it redirects the calls to the latest version of the extension if the extension has been upgraded.
+ # - it redirects the calls to the latest version of the extension
+ # if the extension has been upgraded.
# - otherwise it has no effect
def __init__(self, app: ASGIApp) -> None:
self.app = app
@@ -89,9 +90,10 @@ class InstalledExtensionMiddleware:
self, headers: List[Any], msg: str, status_code: HTTPStatus
) -> Union[HTMLResponse, JSONResponse]:
"""
- Build an HTTP response containing the `msg` as HTTP body and the `status_code` as HTTP code.
- If the `accept` HTTP header is present int the request and contains the value of `text/html`
- then return an `HTMLResponse`, otherwise return an `JSONResponse`.
+ Build an HTTP response containing the `msg` as HTTP body and the `status_code`
+ as HTTP code. If the `accept` HTTP header is present int the request and
+ contains the value of `text/html` then return an `HTMLResponse`,
+ otherwise return an `JSONResponse`.
"""
accept_header: str = next(
(
@@ -129,8 +131,8 @@ class CustomGZipMiddleware(GZipMiddleware):
class ExtensionsRedirectMiddleware:
- # Extensions are allowed to specify redirect paths.
- # A call to a path outside the scope of the extension can be redirected to one of the extension's endpoints.
+ # Extensions are allowed to specify redirect paths. A call to a path outside the
+ # scope of the extension can be redirected to one of the extension's endpoints.
# Eg: redirect `GET /.well-known` to `GET /lnurlp/api/v1/well-known`
def __init__(self, app: ASGIApp) -> None:
@@ -231,7 +233,8 @@ def add_ip_block_middleware(app: FastAPI):
status_code=403, # Forbidden
content={"detail": "IP is blocked"},
)
- # this try: except: block is not needed on latest FastAPI (await call_next(request) is enough)
+ # this try: except: block is not needed on latest FastAPI
+ # (await call_next(request) is enough)
# https://stackoverflow.com/questions/71222144/runtimeerror-no-response-returned-in-fastapi-when-refresh-request
# TODO: remove after https://github.com/lnbits/lnbits/pull/1609 is merged
try:
diff --git a/lnbits/server.py b/lnbits/server.py
index c000d4dd5..d566702a6 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -46,7 +46,8 @@ def main(
set_cli_settings(host=host, port=port, forwarded_allow_ips=forwarded_allow_ips)
- # this beautiful beast parses all command line arguments and passes them to the uvicorn server
+ # this beautiful beast parses all command line arguments and
+ # passes them to the uvicorn server
d = dict()
for a in ctx.args:
item = a.split("=")
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 1c5db56dc..9315c46f0 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -113,7 +113,9 @@ class SecuritySettings(LNbitsSettings):
lnbits_watchdog_interval: int = Field(default=60)
lnbits_watchdog_delta: int = Field(default=1_000_000)
lnbits_status_manifest: str = Field(
- default="https://raw.githubusercontent.com/lnbits/lnbits-status/main/manifest.json"
+ default=(
+ "https://raw.githubusercontent.com/lnbits/lnbits-status/main/manifest.json"
+ )
)
@@ -376,7 +378,8 @@ def send_admin_user_to_saas():
logger.success("sent super_user to saas application")
except Exception as e:
logger.error(
- f"error sending super_user to saas: {settings.lnbits_saas_callback}. Error: {str(e)}"
+ "error sending super_user to saas:"
+ f" {settings.lnbits_saas_callback}. Error: {str(e)}"
)
diff --git a/lnbits/tasks.py b/lnbits/tasks.py
index 2f95a42be..db4c1f1a8 100644
--- a/lnbits/tasks.py
+++ b/lnbits/tasks.py
@@ -87,8 +87,8 @@ invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict("invoice_listener
def register_invoice_listener(send_chan: asyncio.Queue, name: Optional[str] = None):
"""
- A method intended for extensions (and core/tasks.py) to call when they want to be notified about
- new invoice payments incoming. Will emit all incoming payments.
+ A method intended for extensions (and core/tasks.py) to call when they want to be
+ notified about new invoice payments incoming. Will emit all incoming payments.
"""
name_unique = f"{name or 'no_name'}_{str(uuid.uuid4())[:8]}"
logger.trace(f"sse: registering invoice listener {name_unique}")
@@ -147,7 +147,8 @@ async def check_pending_payments():
while True:
async with db.connect() as conn:
logger.info(
- f"Task: checking all pending payments (incoming={incoming}, outgoing={outgoing}) of last 15 days"
+ f"Task: checking all pending payments (incoming={incoming},"
+ f" outgoing={outgoing}) of last 15 days"
)
start_time = time.time()
pending_payments = await get_payments(
@@ -163,7 +164,8 @@ async def check_pending_payments():
await payment.check_status(conn=conn)
logger.info(
- f"Task: pending check finished for {len(pending_payments)} payments (took {time.time() - start_time:0.3f} s)"
+ f"Task: pending check finished for {len(pending_payments)} payments"
+ f" (took {time.time() - start_time:0.3f} s)"
)
# we delete expired invoices once upon the first pending check
if incoming:
@@ -171,7 +173,8 @@ async def check_pending_payments():
start_time = time.time()
await delete_expired_invoices(conn=conn)
logger.info(
- f"Task: expired invoice deletion finished (took {time.time() - start_time:0.3f} s)"
+ "Task: expired invoice deletion finished (took"
+ f" {time.time() - start_time:0.3f} s)"
)
# after the first check we will only check outgoing, not incoming
diff --git a/lnbits/utils/exchange_rates.py b/lnbits/utils/exchange_rates.py
index 2801146b5..b1ceb26fd 100644
--- a/lnbits/utils/exchange_rates.py
+++ b/lnbits/utils/exchange_rates.py
@@ -260,12 +260,15 @@ async def btc_price(currency: str) -> float:
rate = float(provider.getter(data, replacements))
await send_channel.put(rate)
except (
- TypeError, # CoinMate returns HTTPStatus 200 but no data when a currency pair is not found
- KeyError, # Kraken's response dictionary doesn't include keys we look up for
+ # CoinMate returns HTTPStatus 200 but no data when a pair is not found
+ TypeError,
+ # Kraken's response dictionary doesn't include keys we look up for
+ KeyError,
httpx.ConnectTimeout,
httpx.ConnectError,
httpx.ReadTimeout,
- httpx.HTTPStatusError, # Some providers throw a 404 when a currency pair is not found
+ # Some providers throw a 404 when a currency pair is not found
+ httpx.HTTPStatusError,
):
await send_channel.put(None)
diff --git a/lnbits/wallets/cliche.py b/lnbits/wallets/cliche.py
index e6d6af7cb..36894d0ee 100644
--- a/lnbits/wallets/cliche.py
+++ b/lnbits/wallets/cliche.py
@@ -55,13 +55,16 @@ class ClicheWallet(Wallet):
description_hash_str = (
description_hash.hex()
if description_hash
- else hashlib.sha256(unhashed_description).hexdigest()
- if unhashed_description
- else None
+ else (
+ hashlib.sha256(unhashed_description).hexdigest()
+ if unhashed_description
+ else None
+ )
)
ws = create_connection(self.endpoint)
ws.send(
- f"create-invoice --msatoshi {amount*1000} --description_hash {description_hash_str}"
+ f"create-invoice --msatoshi {amount*1000} --description_hash"
+ f" {description_hash_str}"
)
r = ws.recv()
else:
@@ -172,7 +175,8 @@ class ClicheWallet(Wallet):
continue
except Exception as exc:
logger.error(
- f"lost connection to cliche's invoices stream: '{exc}', retrying in 5 seconds"
+ f"lost connection to cliche's invoices stream: '{exc}', retrying in"
+ " 5 seconds"
)
await asyncio.sleep(5)
continue
diff --git a/lnbits/wallets/corelightning.py b/lnbits/wallets/corelightning.py
index e29b08094..7615222ed 100644
--- a/lnbits/wallets/corelightning.py
+++ b/lnbits/wallets/corelightning.py
@@ -44,9 +44,8 @@ class CoreLightningWallet(Wallet):
self.ln = LightningRpc(self.rpc)
# check if description_hash is supported (from corelightning>=v0.11.0)
- self.supports_description_hash = (
- "deschashonly" in self.ln.help("invoice")["help"][0]["command"] # type: ignore
- )
+ command = self.ln.help("invoice")["help"][0]["command"] # type: ignore
+ self.supports_description_hash = "deschashonly" in command
# check last payindex so we can listen from that point on
self.last_pay_index = 0
@@ -79,20 +78,21 @@ class CoreLightningWallet(Wallet):
try:
if description_hash and not unhashed_description:
raise Unsupported(
- "'description_hash' unsupported by CoreLightning, provide 'unhashed_description'"
+ "'description_hash' unsupported by CoreLightning, provide"
+ " 'unhashed_description'"
)
if unhashed_description and not self.supports_description_hash:
raise Unsupported("unhashed_description")
r: dict = self.ln.invoice( # type: ignore
msatoshi=msat,
label=label,
- description=unhashed_description.decode()
- if unhashed_description
- else memo,
+ description=(
+ unhashed_description.decode() if unhashed_description else memo
+ ),
exposeprivatechannels=True,
- deschashonly=True
- if unhashed_description
- else False, # we can't pass None here
+ deschashonly=(
+ True if unhashed_description else False
+ ), # we can't pass None here
expiry=kwargs.get("expiry"),
)
@@ -101,7 +101,10 @@ class CoreLightningWallet(Wallet):
return InvoiceResponse(True, r["payment_hash"], r["bolt11"], "")
except RpcError as exc:
- error_message = f"CoreLightning method '{exc.method}' failed with '{exc.error.get('message') or exc.error}'." # type: ignore
+ error_message = (
+ f"CoreLightning method '{exc.method}' failed with"
+ f" '{exc.error.get('message') or exc.error}'." # type: ignore
+ )
return InvoiceResponse(False, None, None, error_message)
except Exception as e:
return InvoiceResponse(False, None, None, str(e))
@@ -114,11 +117,12 @@ class CoreLightningWallet(Wallet):
return PaymentResponse(False, None, None, None, "invoice already paid")
fee_limit_percent = fee_limit_msat / invoice.amount_msat * 100
-
+ # so fee_limit_percent is applied even on payments with fee < 5000 millisatoshi
+ # (which is default value of exemptfee)
payload = {
"bolt11": bolt11,
"maxfeepercent": f"{fee_limit_percent:.11}",
- "exemptfee": 0, # so fee_limit_percent is applied even on payments with fee < 5000 millisatoshi (which is default value of exemptfee)
+ "exemptfee": 0,
}
try:
wrapped = async_wrap(_pay_invoice)
@@ -127,7 +131,10 @@ class CoreLightningWallet(Wallet):
try:
error_message = exc.error["attempts"][-1]["fail_reason"] # type: ignore
except Exception:
- error_message = f"CoreLightning method '{exc.method}' failed with '{exc.error.get('message') or exc.error}'." # type: ignore
+ error_message = (
+ f"CoreLightning method '{exc.method}' failed with"
+ f" '{exc.error.get('message') or exc.error}'." # type: ignore
+ )
return PaymentResponse(False, None, None, None, error_message)
except Exception as exc:
return PaymentResponse(False, None, None, None, str(exc))
@@ -192,6 +199,7 @@ class CoreLightningWallet(Wallet):
yield paid["payment_hash"]
except Exception as exc:
logger.error(
- f"lost connection to corelightning invoices stream: '{exc}', retrying in 5 seconds"
+ f"lost connection to corelightning invoices stream: '{exc}', "
+ "retrying in 5 seconds"
)
await asyncio.sleep(5)
diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py
index 62f1b5ccc..ac6c5e42b 100644
--- a/lnbits/wallets/eclair.py
+++ b/lnbits/wallets/eclair.py
@@ -225,6 +225,7 @@ class EclairWallet(Wallet):
except Exception as exc:
logger.error(
- f"lost connection to eclair invoices stream: '{exc}', retrying in 5 seconds"
+ f"lost connection to eclair invoices stream: '{exc}'"
+ "retrying in 5 seconds"
)
await asyncio.sleep(5)
diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py
index 62f2bc44c..9fcc2e913 100644
--- a/lnbits/wallets/fake.py
+++ b/lnbits/wallets/fake.py
@@ -31,7 +31,8 @@ class FakeWallet(Wallet):
async def status(self) -> StatusResponse:
logger.info(
- "FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr."
+ "FakeWallet funding source is for using LNbits as a centralised,"
+ " stand-alone payment system with brrrrrr."
)
return StatusResponse(None, 1000000000)
diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py
index 0cbe7dde8..b74211904 100644
--- a/lnbits/wallets/lndgrpc.py
+++ b/lnbits/wallets/lndgrpc.py
@@ -100,7 +100,8 @@ class LndWallet(Wallet):
def __init__(self):
if not imports_ok: # pragma: nocover
raise ImportError(
- "The `grpcio` and `protobuf` library must be installed to use `GRPC LndWallet`. Alternatively try using the LndRESTWallet."
+ "The `grpcio` and `protobuf` library must be installed to use `GRPC"
+ " LndWallet`. Alternatively try using the LndRESTWallet."
)
endpoint = settings.lnd_grpc_endpoint
@@ -305,6 +306,7 @@ class LndWallet(Wallet):
yield checking_id
except Exception as exc:
logger.error(
- f"lost connection to lnd invoices stream: '{exc}', retrying in 5 seconds"
+ f"lost connection to lnd invoices stream: '{exc}', "
+ "retrying in 5 seconds"
)
await asyncio.sleep(5)
diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py
index d8d51fd64..119fa10ee 100644
--- a/lnbits/wallets/lndrest.py
+++ b/lnbits/wallets/lndrest.py
@@ -48,7 +48,8 @@ class LndRestWallet(Wallet):
if not cert:
logger.warning(
- "no certificate for lndrest provided, this only works if you have a publicly issued certificate"
+ "no certificate for lndrest provided, this only works if you have a"
+ " publicly issued certificate"
)
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
@@ -223,6 +224,7 @@ class LndRestWallet(Wallet):
yield payment_hash
except Exception as exc:
logger.error(
- f"lost connection to lnd invoices stream: '{exc}', retrying in 5 seconds"
+ f"lost connection to lnd invoices stream: '{exc}', retrying in 5"
+ " seconds"
)
await asyncio.sleep(5)
diff --git a/lnbits/wallets/lnpay.py b/lnbits/wallets/lnpay.py
index 919af8f78..9c7e3a0e7 100644
--- a/lnbits/wallets/lnpay.py
+++ b/lnbits/wallets/lnpay.py
@@ -50,7 +50,8 @@ class LNPayWallet(Wallet):
data = r.json()
if data["statusType"]["name"] != "active":
return StatusResponse(
- f"Wallet {data['user_label']} (data['id']) not active, but {data['statusType']['name']}",
+ f"Wallet {data['user_label']} (data['id']) not active, but"
+ f" {data['statusType']['name']}",
0,
)
diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py
index f19f9c4fd..048727061 100644
--- a/lnbits/wallets/lntips.py
+++ b/lnbits/wallets/lntips.py
@@ -164,6 +164,7 @@ class LnTipsWallet(Wallet):
# since the backend is expected to drop the connection after 90s
if last_connected is None or time.time() - last_connected < 10:
logger.error(
- f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying in 5 seconds"
+ f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying"
+ " in 5 seconds"
)
await asyncio.sleep(5)
diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py
index 2e3121094..a7c51ce42 100644
--- a/lnbits/wallets/spark.py
+++ b/lnbits/wallets/spark.py
@@ -46,7 +46,8 @@ class SparkWallet(Wallet):
async def call(*args, **kwargs):
if args and kwargs:
raise TypeError(
- f"must supply either named arguments or a list of arguments, not both: {args} {kwargs}"
+ "must supply either named arguments or a list of arguments, not"
+ f" both: {args} {kwargs}"
)
elif args:
params = args
@@ -161,7 +162,8 @@ class SparkWallet(Wallet):
if len(pays) > 1:
raise SparkError(
- f"listpays({payment_hash}) returned an unexpected response: {listpays}"
+ f"listpays({payment_hash}) returned an unexpected response:"
+ f" {listpays}"
)
if pay["status"] == "failed":
diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py
index 1d0ff90a9..22eba7695 100644
--- a/lnbits/wallets/void.py
+++ b/lnbits/wallets/void.py
@@ -19,10 +19,9 @@ class VoidWallet(Wallet):
async def status(self) -> StatusResponse:
logger.warning(
- (
- "This backend does nothing, it is here just as a placeholder, you must configure an "
- "actual backend before being able to do anything useful with LNbits."
- )
+ "This backend does nothing, it is here just as a placeholder, you must"
+ " configure an actual backend before being able to do anything useful with"
+ " LNbits."
)
return StatusResponse(None, 0)
diff --git a/pyproject.toml b/pyproject.toml
index b0c4ae8d9..f454da7f0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -103,11 +103,9 @@ testpaths = [
]
[tool.black]
-# line-length = 150
-# previously experimental-string-processing = true
-# this should autoformat string poperly but does not work
+line-length = 88
+# use upcoming new features
# preview = true
-target-versions = ["py39"]
extend-exclude = """(
lnbits/static
| lnbits/extensions
@@ -116,14 +114,14 @@ extend-exclude = """(
)"""
[tool.ruff]
-# Same as Black.
-line-length = 150
+# Same as Black. + 10% rule of black
+line-length = 88
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
+# (`I`) is for `isort`.
select = ["E", "F", "I"]
ignore = [
"E402", # Module level import not at top of file
- "E501", # Line length
]
# Allow autofix for all enabled rules (when `--fix`) is provided.
diff --git a/tests/core/views/test_api.py b/tests/core/views/test_api.py
index 7ff95c7e9..009b71e50 100644
--- a/tests/core/views/test_api.py
+++ b/tests/core/views/test_api.py
@@ -210,10 +210,10 @@ async def test_pay_invoice_adminkey(client, invoice, adminkey_headers_from):
@pytest.mark.asyncio
async def test_get_payments(client, from_wallet, adminkey_headers_from):
- # Because sqlite only stores timestamps with milliseconds we have to wait a second to ensure
- # a different timestamp than previous invoices
- # due to this limitation both payments (normal and paginated) are tested at the same time as they are almost
- # identical anyways
+ # Because sqlite only stores timestamps with milliseconds we have to wait a second
+ # to ensure a different timestamp than previous invoices due to this limitation
+ # both payments (normal and paginated) are tested at the same time as they are
+ # almost identical anyways
if DB_TYPE == SQLITE:
await asyncio.sleep(1)
ts = time()
diff --git a/tests/helpers.py b/tests/helpers.py
index 15754189e..4a3b3da9d 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -34,7 +34,10 @@ docker_lightning = f"{docker_cmd} {docker_prefix}-lnd-1-1"
docker_lightning_cli = f"{docker_lightning} lncli --network regtest --rpcserver=lnd-1"
docker_bitcoin = f"{docker_cmd} {docker_prefix}-bitcoind-1-1"
-docker_bitcoin_cli = f"{docker_bitcoin} bitcoin-cli -rpcuser={docker_bitcoin_rpc} -rpcpassword={docker_bitcoin_rpc} -regtest"
+docker_bitcoin_cli = (
+ f"{docker_bitcoin} bitcoin-cli"
+ f" -rpcuser={docker_bitcoin_rpc} -rpcpassword={docker_bitcoin_rpc} -regtest"
+)
def run_cmd(cmd: str) -> str:
diff --git a/tools/conv.py b/tools/conv.py
index 36bf0a808..1d1a42483 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -55,7 +55,8 @@ def check_db_versions(sqdb):
version = dbpost[key]
if value != version:
raise Exception(
- f"sqlite database version ({value}) of {key} doesn't match postgres database version {version}"
+ f"sqlite database version ({value}) of {key} doesn't match postgres"
+ f" database version {version}"
)
connection = postgres.connection
@@ -174,7 +175,10 @@ parser.add_argument(
dest="sqlite_path",
const=True,
nargs="?",
- help=f"SQLite DB folder *or* single extension db file to migrate. Default: {sqfolder}",
+ help=(
+ "SQLite DB folder *or* single extension db file to migrate. Default:"
+ f" {sqfolder}"
+ ),
default=sqfolder,
type=str,
)