diff --git a/Makefile b/Makefile index b2e688231..b1e2b4f68 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ openapi: HOST=0.0.0.0 \ PORT=5003 \ poetry run lnbits & - sleep 7 + sleep 15 curl -s http://0.0.0.0:5003/openapi.json | poetry run openapi-spec-validator --errors=all - # kill -9 %1 diff --git a/lnbits/server.py b/lnbits/server.py index d566702a6..72994b5fd 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -4,13 +4,10 @@ from pathlib import Path import click import uvicorn -import uvloop from uvicorn.supervisors import ChangeReload from lnbits.settings import set_cli_settings, settings -uvloop.install() - @click.command( context_settings=dict( diff --git a/lnbits/wallets/corelightning.py b/lnbits/wallets/corelightning.py index 7615222ed..66e7d20c7 100644 --- a/lnbits/wallets/corelightning.py +++ b/lnbits/wallets/corelightning.py @@ -1,7 +1,6 @@ import asyncio import random -from functools import partial, wraps -from typing import AsyncGenerator, Optional +from typing import Any, AsyncGenerator, Optional from loguru import logger from pyln.client import LightningRpc, RpcError @@ -19,23 +18,9 @@ from .base import ( ) -def async_wrap(func): - @wraps(func) - async def run(*args, loop=None, executor=None, **kwargs): - if loop is None: - loop = asyncio.get_event_loop() - partial_func = partial(func, *args, **kwargs) - return await loop.run_in_executor(executor, partial_func) - - return run - - -def _pay_invoice(ln, payload): - return ln.call("pay", payload) - - -def _paid_invoices_stream(ln, last_pay_index): - return ln.waitanyinvoice(last_pay_index) +async def run_sync(func) -> Any: + loop = asyncio.get_event_loop() + return await loop.run_in_executor(None, func) class CoreLightningWallet(Wallet): @@ -125,8 +110,7 @@ class CoreLightningWallet(Wallet): "exemptfee": 0, } try: - wrapped = async_wrap(_pay_invoice) - r = await wrapped(self.ln, payload) + r = await run_sync(lambda: self.ln.call("pay", payload)) except RpcError as exc: try: error_message = exc.error["attempts"][-1]["fail_reason"] # type: ignore @@ -193,10 +177,15 @@ class CoreLightningWallet(Wallet): async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: while True: try: - wrapped = async_wrap(_paid_invoices_stream) - paid = await wrapped(self.ln, self.last_pay_index) + paid = await run_sync( + lambda: self.ln.waitanyinvoice(self.last_pay_index, timeout=2) + ) self.last_pay_index = paid["pay_index"] yield paid["payment_hash"] + except RpcError as exc: + # only raise if not a timeout + if exc.error["code"] != 904: # type: ignore + raise except Exception as exc: logger.error( f"lost connection to corelightning invoices stream: '{exc}', " diff --git a/tests/conftest.py b/tests/conftest.py index ac9becd71..6ebd86456 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,15 @@ import asyncio +import uvloop + +uvloop.install() # noqa + import pytest import pytest_asyncio from fastapi.testclient import TestClient from httpx import AsyncClient from lnbits.app import create_app -from lnbits.commands import migrate_databases from lnbits.core.crud import create_account, create_wallet from lnbits.core.models import CreateInvoice from lnbits.core.services import update_wallet_balance @@ -28,17 +31,11 @@ def event_loop(): # use session scope to run once before and once after all tests @pytest_asyncio.fixture(scope="session") -def app(event_loop): +async def app(): app = create_app() - # use redefined version of the event loop for scope="session" - # loop = asyncio.get_event_loop() - loop = event_loop - loop.run_until_complete(migrate_databases()) + await app.router.startup() yield app - # # get the current event loop and gracefully stop any running tasks - # loop = event_loop - loop.run_until_complete(loop.shutdown_asyncgens()) - # loop.close() + await app.router.shutdown() @pytest_asyncio.fixture(scope="session") @@ -75,8 +72,10 @@ async def from_wallet(from_user): yield wallet -@pytest.fixture -def from_wallet_ws(from_wallet, test_client): +@pytest_asyncio.fixture +async def from_wallet_ws(from_wallet, test_client): + # wait a bit in order to avoid receiving topup notification + await asyncio.sleep(0.1) with test_client.websocket_connect(f"/api/v1/ws/{from_wallet.id}") as ws: yield ws @@ -98,8 +97,10 @@ async def to_wallet(to_user): yield wallet -@pytest.fixture -def to_wallet_ws(to_wallet, test_client): +@pytest_asyncio.fixture +async def to_wallet_ws(to_wallet, test_client): + # wait a bit in order to avoid receiving topup notification + await asyncio.sleep(0.1) with test_client.websocket_connect(f"/api/v1/ws/{to_wallet.id}") as ws: yield ws diff --git a/tests/core/views/test_api.py b/tests/core/views/test_api.py index 0546409a1..d2c88b019 100644 --- a/tests/core/views/test_api.py +++ b/tests/core/views/test_api.py @@ -132,9 +132,7 @@ async def test_create_invoice_custom_expiry(client, inkey_headers_to): # check POST /api/v1/payments: make payment @pytest.mark.asyncio -async def test_pay_invoice( - client, from_wallet_ws, to_wallet_ws, invoice, adminkey_headers_from -): +async def test_pay_invoice(client, from_wallet_ws, invoice, adminkey_headers_from): data = {"out": True, "bolt11": invoice["payment_request"]} response = await client.post( "/api/v1/payments", json=data, headers=adminkey_headers_from