[CHORE] string formatting default length 88 (#1887)

* [CHORE] string formatting default length 88

uses blacks default off 88 and enabled autostringformatting

* formatting

* nitpicks jackstar

fix
This commit is contained in:
dni ⚡ 2023-08-24 11:26:09 +02:00 committed by GitHub
parent 2ab18544c3
commit 4e6f229db2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 245 additions and 160 deletions

View File

@ -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 ]

View File

@ -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"
)
)

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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(
"""

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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}."
),
),
)

View File

@ -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<br>
just **user_id**: return the first user wallet or create one if none found (with default wallet_name)<br>
**user_id** and **wallet_name**: create a new wallet for user with wallet_name<br>
**user_id** and **wallet_id**: return that wallet if user is the owner<br>
nothing: create everything<br>
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",
}

View File

@ -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]] = []

View File

@ -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

View File

@ -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(

View File

@ -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 = []

View File

@ -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:

View File

@ -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("=")

View File

@ -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)}"
)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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)

View File

@ -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":

View File

@ -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)

View File

@ -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.

View File

@ -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()

View File

@ -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:

View File

@ -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,
)