Browse Source

migrate to trio so c-lightning sockets stop hanging.

atmext
fiatjaf 4 years ago
parent
commit
9994e61615
  1. 4
      Pipfile
  2. 127
      Pipfile.lock
  3. 23
      lnbits/app.py
  4. 47
      lnbits/core/tasks.py
  5. 2
      lnbits/core/views/generic.py
  6. 8
      lnbits/db.py
  7. 4
      lnbits/proxy_fix.py
  8. 11
      lnbits/wallets/clightning.py
  9. 4
      lnbits/wallets/lnbits.py
  10. 11
      lnbits/wallets/lnpay.py
  11. 4
      lnbits/wallets/lntxbot.py
  12. 13
      lnbits/wallets/opennode.py
  13. 2
      pytest.ini
  14. 1
      tests/conftest.py
  15. 1
      tests/core/test_views.py

4
Pipfile

@ -21,10 +21,12 @@ quart-compress = "*"
secure = "*" secure = "*"
typing-extensions = "*" typing-extensions = "*"
httpx = "*" httpx = "*"
quart-trio = "*"
trio = "*"
[dev-packages] [dev-packages]
black = "==20.8b1" black = "==20.8b1"
pytest = "*" pytest = "*"
pytest-cov = "*" pytest-cov = "*"
pytest-asyncio = "*"
mypy = "==0.761" mypy = "==0.761"
pytest-trio = "*"

127
Pipfile.lock

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "6f7d14aa2e3bc6a1319c7f0e2873151cefa741792fccc249567932a3a94263e3" "sha256": "894690d75d6558f0aa98eed8c5f54bdfe79c2a1bfd736507f930bf07c775a89e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -23,6 +23,22 @@
], ],
"version": "==0.5.0" "version": "==0.5.0"
}, },
"async-generator": {
"hashes": [
"sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
"sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
],
"markers": "python_version >= '3.5'",
"version": "==1.10"
},
"attrs": {
"hashes": [
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.2.0"
},
"bech32": { "bech32": {
"hashes": [ "hashes": [
"sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899", "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899",
@ -149,13 +165,16 @@
}, },
"httpx": { "httpx": {
"hashes": [ "hashes": [
"sha256:4c81dbf98a29cb4f51f415140df56542f9d4860798d713e336642e953cddd1db", "sha256:02326f2d3c61133db31e4b88dd3432479b434e52a68d813eab6db930f13611ea",
"sha256:7b3c07bfdcdadd92020dd4c07b15932abdcf1c898422a4e98de3d19b2223310b" "sha256:254b371e3880a8e2387bf9ead6949bac797bd557fda26eba19a6153a0c06bd2b"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.15.4" "version": "==0.15.5"
}, },
"hypercorn": { "hypercorn": {
"extras": [
"trio"
],
"hashes": [ "hashes": [
"sha256:6540faeba9dd44f7e74c7cc1beae3a438a7efb5f77323d1199457da46d32c2c2", "sha256:6540faeba9dd44f7e74c7cc1beae3a438a7efb5f77323d1199457da46d32c2c2",
"sha256:b5c479023757e279f954b46a4ec9dd85e58a2bcbf4d959d5601cbced593e711d" "sha256:b5c479023757e279f954b46a4ec9dd85e58a2bcbf4d959d5601cbced593e711d"
@ -242,6 +261,14 @@
], ],
"version": "==3.8.0" "version": "==3.8.0"
}, },
"outcome": {
"hashes": [
"sha256:ee46c5ce42780cde85d55a61819d0e6b8cb490f1dbd749ba75ff2629771dcd2d",
"sha256:fc7822068ba7dd0fc2532743611e8a73246708d3564e29a39f93d6ab3701b66f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.0.1"
},
"priority": { "priority": {
"hashes": [ "hashes": [
"sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe", "sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe",
@ -309,6 +336,14 @@
"index": "pypi", "index": "pypi",
"version": "==0.3.0" "version": "==0.3.0"
}, },
"quart-trio": {
"hashes": [
"sha256:00f3b20f8d82ce7e81ead61db4efba38ed7653c7e28199defded46b663ab2595",
"sha256:dafc8f0440d4b70fa60d24122a161d2373894d2bfa9f713d9f1df1fd508f0834"
],
"index": "pypi",
"version": "==0.5.1"
},
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
@ -354,6 +389,13 @@
], ],
"version": "==1.1.0" "version": "==1.1.0"
}, },
"sortedcontainers": {
"hashes": [
"sha256:4e73a757831fc3ca4de2859c422564239a31d8213d09a2a666e375807034d2ba",
"sha256:c633ebde8580f241f274c1f8994a665c0e54a17724fecd0cae2f079e09c36d3f"
],
"version": "==2.2.2"
},
"toml": { "toml": {
"hashes": [ "hashes": [
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
@ -361,6 +403,14 @@
], ],
"version": "==0.10.1" "version": "==0.10.1"
}, },
"trio": {
"hashes": [
"sha256:e85cf9858e445465dfbb0e3fdf36efe92082d2df87bfe9d62585eedd6e8e9d7d",
"sha256:fc70c74e8736d1105b3c05cc2e49b30c58755733740f9c51ae6d88a4d6d0a291"
],
"index": "pypi",
"version": "==0.17.0"
},
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
@ -400,6 +450,14 @@
], ],
"version": "==1.4.4" "version": "==1.4.4"
}, },
"async-generator": {
"hashes": [
"sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
"sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
],
"markers": "python_version >= '3.5'",
"version": "==1.10"
},
"attrs": { "attrs": {
"hashes": [ "hashes": [
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
@ -460,6 +518,14 @@
], ],
"version": "==5.3" "version": "==5.3"
}, },
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
@ -494,6 +560,14 @@
], ],
"version": "==0.4.3" "version": "==0.4.3"
}, },
"outcome": {
"hashes": [
"sha256:ee46c5ce42780cde85d55a61819d0e6b8cb490f1dbd749ba75ff2629771dcd2d",
"sha256:fc7822068ba7dd0fc2532743611e8a73246708d3564e29a39f93d6ab3701b66f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.0.1"
},
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
@ -531,19 +605,11 @@
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:1cd09785c0a50f9af72220dd12aa78cfa49cbffc356c61eab009ca189e018a33", "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9",
"sha256:d010e24666435b39a4cf48740b039885642b6c273a3f77be3e7e03554d2806b7" "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.1.0" "version": "==6.1.1"
},
"pytest-asyncio": {
"hashes": [
"sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d",
"sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"
],
"index": "pypi",
"version": "==0.14.0"
}, },
"pytest-cov": { "pytest-cov": {
"hashes": [ "hashes": [
@ -553,6 +619,14 @@
"index": "pypi", "index": "pypi",
"version": "==2.10.1" "version": "==2.10.1"
}, },
"pytest-trio": {
"hashes": [
"sha256:3f48cc1df66d279d705af38ad38d1639c2e2380ddffcdc3a45bb81758de61f03",
"sha256:9bf0a490fd177a33617e8709242293fae47934de2b51f8209eb2c0545b6ca8fe"
],
"index": "pypi",
"version": "==0.6.0"
},
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef", "sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef",
@ -586,6 +660,21 @@
], ],
"version": "==1.15.0" "version": "==1.15.0"
}, },
"sniffio": {
"hashes": [
"sha256:20ed6d5b46f8ae136d00b9dcb807615d83ed82ceea6b2058cecb696765246da5",
"sha256:8e3810100f69fe0edd463d02ad407112542a11ffdc29f67db2bf3771afb87a21"
],
"markers": "python_version >= '3.5'",
"version": "==1.1.0"
},
"sortedcontainers": {
"hashes": [
"sha256:4e73a757831fc3ca4de2859c422564239a31d8213d09a2a666e375807034d2ba",
"sha256:c633ebde8580f241f274c1f8994a665c0e54a17724fecd0cae2f079e09c36d3f"
],
"version": "==2.2.2"
},
"toml": { "toml": {
"hashes": [ "hashes": [
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
@ -593,6 +682,14 @@
], ],
"version": "==0.10.1" "version": "==0.10.1"
}, },
"trio": {
"hashes": [
"sha256:e85cf9858e445465dfbb0e3fdf36efe92082d2df87bfe9d62585eedd6e8e9d7d",
"sha256:fc70c74e8736d1105b3c05cc2e49b30c58755733740f9c51ae6d88a4d6d0a291"
],
"index": "pypi",
"version": "==0.17.0"
},
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",

23
lnbits/app.py

@ -1,7 +1,8 @@
import asyncio import trio # type: ignore
import importlib import importlib
from quart import Quart, g from quart import g
from quart_trio import QuartTrio
from quart_cors import cors # type: ignore from quart_cors import cors # type: ignore
from quart_compress import Compress # type: ignore from quart_compress import Compress # type: ignore
from secure import SecureHeaders # type: ignore from secure import SecureHeaders # type: ignore
@ -15,11 +16,11 @@ from .proxy_fix import ASGIProxyFix
secure_headers = SecureHeaders(hsts=False) secure_headers = SecureHeaders(hsts=False)
def create_app(config_object="lnbits.settings") -> Quart: def create_app(config_object="lnbits.settings") -> QuartTrio:
"""Create application factory. """Create application factory.
:param config_object: The configuration object to use. :param config_object: The configuration object to use.
""" """
app = Quart(__name__, static_folder="static") app = QuartTrio(__name__, static_folder="static")
app.config.from_object(config_object) app.config.from_object(config_object)
app.asgi_http_class = ASGIProxyFix app.asgi_http_class = ASGIProxyFix
@ -36,7 +37,7 @@ def create_app(config_object="lnbits.settings") -> Quart:
return app return app
def register_blueprints(app: Quart) -> None: def register_blueprints(app: QuartTrio) -> None:
"""Register Flask blueprints / LNbits extensions.""" """Register Flask blueprints / LNbits extensions."""
app.register_blueprint(core_app) app.register_blueprint(core_app)
@ -58,13 +59,13 @@ def register_blueprints(app: Quart) -> None:
raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.") raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.")
def register_commands(app: Quart): def register_commands(app: QuartTrio):
"""Register Click commands.""" """Register Click commands."""
app.cli.add_command(db_migrate) app.cli.add_command(db_migrate)
app.cli.add_command(handle_assets) app.cli.add_command(handle_assets)
def register_assets(app: Quart): def register_assets(app: QuartTrio):
"""Serve each vendored asset separately or a bundle.""" """Serve each vendored asset separately or a bundle."""
@app.before_request @app.before_request
@ -77,13 +78,13 @@ def register_assets(app: Quart):
g.VENDORED_CSS = ["/static/bundle.css"] g.VENDORED_CSS = ["/static/bundle.css"]
def register_filters(app: Quart): def register_filters(app: QuartTrio):
"""Jinja filters.""" """Jinja filters."""
app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"] app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"]
app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions() app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions()
def register_request_hooks(app: Quart): def register_request_hooks(app: QuartTrio):
"""Open the core db for each request so everything happens in a big transaction""" """Open the core db for each request so everything happens in a big transaction"""
@app.before_request @app.before_request
@ -109,8 +110,8 @@ def register_async_tasks(app):
@app.before_serving @app.before_serving
async def listeners(): async def listeners():
loop = asyncio.get_running_loop() app.nursery.start_soon(invoice_listener)
loop.create_task(invoice_listener()) print("started invoice_listener")
@app.after_serving @app.after_serving
async def stop_listeners(): async def stop_listeners():

47
lnbits/core/tasks.py

@ -1,7 +1,8 @@
import asyncio import trio # type: ignore
from http import HTTPStatus from http import HTTPStatus
from typing import Optional, Tuple, List, Callable, Awaitable from typing import Optional, Tuple, List, Callable, Awaitable
from quart import Quart, Request, g from quart import Request, g
from quart_trio import QuartTrio
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from lnbits.db import open_db, open_ext_db from lnbits.db import open_db, open_ext_db
@ -10,7 +11,7 @@ from lnbits.settings import WALLET
from .models import Payment from .models import Payment
from .crud import get_standalone_payment from .crud import get_standalone_payment
main_app: Optional[Quart] = None main_app: Optional[QuartTrio] = None
def grab_app_for_later(state): def grab_app_for_later(state):
@ -18,24 +19,30 @@ def grab_app_for_later(state):
main_app = state.app main_app = state.app
def run_on_pseudo_request(awaitable: Awaitable): async def send_push_promise(a, b) -> None:
async def run(awaitable): pass
fk = Request(
"GET",
"http", async def run_on_pseudo_request(func: Callable, *args):
"/background/pseudo", fk = Request(
b"", "GET",
Headers([("host", "lnbits.background")]), "http",
"", "/background/pseudo",
"1.1", b"",
send_push_promise=lambda x, h: None, Headers([("host", "lnbits.background")]),
) "",
"1.1",
send_push_promise=send_push_promise,
)
assert main_app
async def run():
async with main_app.request_context(fk): async with main_app.request_context(fk):
with open_db() as g.db: with open_db() as g.db: # type: ignore
await awaitable await func(*args)
loop = asyncio.get_event_loop() async with trio.open_nursery() as nursery:
loop.create_task(run(awaitable)) nursery.start_soon(run)
invoice_listeners: List[Tuple[str, Callable[[Payment], Awaitable[None]]]] = [] invoice_listeners: List[Tuple[str, Callable[[Payment], Awaitable[None]]]] = []
@ -59,7 +66,7 @@ async def webhook_handler():
async def invoice_listener(): async def invoice_listener():
async for checking_id in WALLET.paid_invoices_stream(): async for checking_id in WALLET.paid_invoices_stream():
run_on_pseudo_request(invoice_callback_dispatcher(checking_id)) await run_on_pseudo_request(invoice_callback_dispatcher, checking_id)
async def invoice_callback_dispatcher(checking_id: str): async def invoice_callback_dispatcher(checking_id: str):

2
lnbits/core/views/generic.py

@ -123,6 +123,6 @@ async def lnurlwallet():
user = get_user(account.id) user = get_user(account.id)
wallet = create_wallet(user_id=user.id) wallet = create_wallet(user_id=user.id)
run_on_pseudo_request(redeem_lnurl_withdraw(wallet.id, withdraw_res, "LNbits initial funding: voucher redeem.")) run_on_pseudo_request(redeem_lnurl_withdraw, wallet.id, withdraw_res, "LNbits initial funding: voucher redeem.")
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id)) return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))

8
lnbits/db.py

@ -10,20 +10,26 @@ class Database:
self.connection = sqlite3.connect(db_path) self.connection = sqlite3.connect(db_path)
self.connection.row_factory = sqlite3.Row self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
self.closed = False
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
if self.closed:
return
if exc_val: if exc_val:
self.connection.rollback() self.connection.rollback()
self.cursor.close() self.cursor.close()
self.cursor.close() self.connection.close()
else: else:
self.connection.commit() self.connection.commit()
self.cursor.close() self.cursor.close()
self.connection.close() self.connection.close()
self.closed = True
def commit(self): def commit(self):
self.connection.commit() self.connection.commit()

4
lnbits/proxy_fix.py

@ -5,10 +5,10 @@ from urllib.parse import urlparse
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from quart import Request from quart import Request
from quart.asgi import ASGIHTTPConnection from quart_trio.asgi import TrioASGIHTTPConnection
class ASGIProxyFix(ASGIHTTPConnection): class ASGIProxyFix(TrioASGIHTTPConnection):
def _create_request_from_scope(self, send: Callable) -> Request: def _create_request_from_scope(self, send: Callable) -> Request:
headers = Headers() headers = Headers()
headers["Remote-Addr"] = (self.scope.get("client") or ["<local>"])[0] headers["Remote-Addr"] = (self.scope.get("client") or ["<local>"])[0]

11
lnbits/wallets/clightning.py

@ -3,7 +3,7 @@ try:
except ImportError: # pragma: nocover except ImportError: # pragma: nocover
LightningRpc = None LightningRpc = None
import asyncio import trio # type: ignore
import random import random
import json import json
@ -86,7 +86,7 @@ class CLightningWallet(Wallet):
raise KeyError("supplied an invalid checking_id") raise KeyError("supplied an invalid checking_id")
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
reader, writer = await asyncio.open_unix_connection(self.rpc) stream = await trio.open_unix_socket(self.rpc)
i = 0 i = 0
while True: while True:
@ -98,12 +98,9 @@ class CLightningWallet(Wallet):
} }
) )
print(call) await stream.send_all(call.encode("utf-8"))
writer.write(call.encode("ascii"))
await writer.drain()
data = await reader.readuntil(b"\n\n") data = await stream.receive_some()
print(data)
paid = json.loads(data.decode("ascii")) paid = json.loads(data.decode("ascii"))
paid = self.ln.waitanyinvoice(self.last_pay_index) paid = self.ln.waitanyinvoice(self.last_pay_index)

4
lnbits/wallets/lnbits.py

@ -1,4 +1,4 @@
import asyncio import trio # type: ignore
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from requests import get, post from requests import get, post
@ -68,5 +68,5 @@ class LNbitsWallet(Wallet):
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
print("lnbits does not support paid invoices stream yet") print("lnbits does not support paid invoices stream yet")
await asyncio.sleep(5) await trio.sleep(5)
yield "" yield ""

11
lnbits/wallets/lnpay.py

@ -1,5 +1,5 @@
import json import json
import asyncio import trio # type: ignore
import httpx import httpx
from os import getenv from os import getenv
from http import HTTPStatus from http import HTTPStatus
@ -77,10 +77,9 @@ class LNPayWallet(Wallet):
return PaymentStatus(statuses[r.json()["settled"]]) return PaymentStatus(statuses[r.json()["settled"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue() self.send, receive = trio.open_memory_channel(0)
while True: async for value in receive:
yield await self.queue.get() yield value
self.queue.task_done()
async def webhook_listener(self): async def webhook_listener(self):
text: str = await request.get_data() text: str = await request.get_data()
@ -96,6 +95,6 @@ class LNPayWallet(Wallet):
) )
data = r.json() data = r.json()
if data["settled"]: if data["settled"]:
self.queue.put_nowait(lntx_id) self.send.send(lntx_id)
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT

4
lnbits/wallets/lntxbot.py

@ -1,4 +1,4 @@
import asyncio import trio # type: ignore
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from requests import post from requests import post
@ -79,5 +79,5 @@ class LntxbotWallet(Wallet):
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
print("lntxbot does not support paid invoices stream yet") print("lntxbot does not support paid invoices stream yet")
await asyncio.sleep(5) await trio.sleep(5)
yield "" yield ""

13
lnbits/wallets/opennode.py

@ -1,5 +1,5 @@
import json import json
import asyncio import trio # type: ignore
import hmac import hmac
import httpx import httpx
from http import HTTPStatus from http import HTTPStatus
@ -77,15 +77,12 @@ class OpenNodeWallet(Wallet):
return PaymentStatus(statuses[r.json()["data"]["status"]]) return PaymentStatus(statuses[r.json()["data"]["status"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue() self.send, receive = trio.open_memory_channel(0)
while True: async for value in receive:
yield await self.queue.get() yield value
self.queue.task_done()
async def webhook_listener(self): async def webhook_listener(self):
print("a request!")
text: str = await request.get_data() text: str = await request.get_data()
print("text", text)
data = json.loads(text) data = json.loads(text)
if type(data) is not dict or "event" not in data or data["event"].get("name") != "wallet_receive": if type(data) is not dict or "event" not in data or data["event"].get("name") != "wallet_receive":
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT
@ -100,5 +97,5 @@ class OpenNodeWallet(Wallet):
print("invalid webhook, not from opennode") print("invalid webhook, not from opennode")
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT
self.queue.put_nowait(charge_id) self.send.send(charge_id)
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT

2
pytest.ini

@ -0,0 +1,2 @@
[pytest]
trio_mode = true

1
tests/conftest.py

@ -4,7 +4,6 @@ from lnbits.app import create_app
@pytest.fixture @pytest.fixture
@pytest.mark.asyncio
async def client(): async def client():
app = create_app() app = create_app()
app.config["TESTING"] = True app.config["TESTING"] = True

1
tests/core/test_views.py

@ -1,7 +1,6 @@
import pytest import pytest
@pytest.mark.asyncio
async def test_homepage(client): async def test_homepage(client):
r = await client.get("/") r = await client.get("/")
assert b"Add a new wallet" in await r.get_data() assert b"Add a new wallet" in await r.get_data()

Loading…
Cancel
Save