[REFACTOR] core/__init__ to not have circular import issues (#1876)

* F541 fix

remove unused workflow and combine linters into one

add lnbits/static to ruff ignore
remote setupnode

ignore upgrades for mypy

ignore F401 for __init__ files

unused noqa

ignore upgrades for black

F821: undefine name

disabled and logged webhook_listener for opennode and lnpay because they are obvisouly not working

E402: module level import not at top of file

fixup

revert breaking changes wait for PR #1876

https://github.com/lnbits/lnbits/pull/1876

E721 fixes, only popped up for python3.9 not 3.10

[REFACTOR] core/__init__ to not have circular import issues

WIP

add db for backwards compat

fix pyright

make mypy happy again

pyright did not catch those, i think mypy got confused with relative imports. maybe we should use absolute ones everywhere

E402: module level import not at top of file

dont forget to add core_app

rebase on ruff pr

f

remo

format

* fix clnrest

* ignore E402 in conftest

* refactoring issues

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
This commit is contained in:
dni ⚡ 2023-09-12 12:25:05 +02:00 committed by GitHub
parent f19d59d429
commit fee40d7321
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 110 additions and 103 deletions

View file

@ -33,14 +33,11 @@ from lnbits.utils.cache import cache
from lnbits.wallets import get_wallet_class, set_wallet_class
from .commands import db_versions, load_disabled_extension_list, migrate_databases
from .core import (
add_installed_extension,
core_app,
core_app_extra,
update_installed_extension_state,
)
from .core import init_core_routers
from .core.db import core_app_extra
from .core.services import check_admin_settings, check_webpush_settings
from .core.views.generic import core_html_routes
from .core.views.api import add_installed_extension
from .core.views.generic import update_installed_extension_state
from .extension_manager import Extension, InstallableExtension, get_valid_extensions
from .helpers import template_renderer
from .middleware import (
@ -257,8 +254,7 @@ async def restore_installed_extension(app: FastAPI, ext: InstallableExtension):
def register_routes(app: FastAPI) -> None:
"""Register FastAPI routes / LNbits extensions."""
app.include_router(core_app)
app.include_router(core_html_routes)
init_core_routers(app)
for ext in get_valid_extensions():
try:

View file

@ -1,15 +1,20 @@
from fastapi.routing import APIRouter
from fastapi import APIRouter
from lnbits.core.models import CoreAppExtra
from lnbits.db import Database
from .db import core_app_extra, db
from .views.admin_api import admin_router
from .views.api import api_router
db = Database("database")
# this compat is needed for usermanager extension
from .views.generic import generic_router, update_user_extension
from .views.public_api import public_router
core_app: APIRouter = APIRouter(tags=["Core"])
# backwards compatibility for extensions
core_app = APIRouter(tags=["Core"])
core_app_extra: CoreAppExtra = CoreAppExtra()
from .views.admin_api import *
from .views.api import *
from .views.generic import *
from .views.public_api import *
def init_core_routers(app):
app.include_router(core_app)
app.include_router(generic_router)
app.include_router(public_router)
app.include_router(api_router)
app.include_router(admin_router)

View file

@ -7,6 +7,7 @@ from uuid import UUID, uuid4
import shortuuid
from lnbits import bolt11
from lnbits.core.db import db
from lnbits.core.models import WalletType
from lnbits.db import Connection, Database, Filters, Page
from lnbits.extension_manager import InstallableExtension
@ -18,7 +19,6 @@ from lnbits.settings import (
settings,
)
from . import db
from .models import (
BalanceCheck,
Payment,

5
lnbits/core/db.py Normal file
View file

@ -0,0 +1,5 @@
from lnbits.core.models import CoreAppExtra
from lnbits.db import Database
db = Database("database")
core_app_extra: CoreAppExtra = CoreAppExtra()

View file

@ -6,11 +6,11 @@ from uuid import UUID
import httpx
from loguru import logger
from lnbits.core.db import db as core_db
from lnbits.db import Connection
from lnbits.extension_manager import Extension
from lnbits.settings import settings
from . import db as core_db
from .crud import update_migration_version

View file

@ -15,6 +15,7 @@ from py_vapid import Vapid
from py_vapid.utils import b64urlencode
from lnbits import bolt11
from lnbits.core.db import db
from lnbits.db import Connection
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.helpers import url_for
@ -29,7 +30,6 @@ from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount
from lnbits.wallets import FAKE_WALLET, get_wallet_class, set_wallet_class
from lnbits.wallets.base import PaymentResponse, PaymentStatus
from . import db
from .crud import (
check_internal,
check_internal_pending,

View file

@ -4,6 +4,18 @@ from typing import Dict
import httpx
from loguru import logger
from lnbits.core.crud import (
get_balance_notify,
get_wallet,
get_webpush_subscriptions_for_user,
)
from lnbits.core.db import db
from lnbits.core.models import Payment
from lnbits.core.services import (
get_balance_delta,
send_payment_notification,
switch_to_voidwallet,
)
from lnbits.settings import get_wallet_class, settings
from lnbits.tasks import (
SseListenersDict,
@ -13,15 +25,6 @@ from lnbits.tasks import (
send_push_notification,
)
from . import db
from .crud import (
get_balance_notify,
get_wallet,
get_webpush_subscriptions_for_user,
)
from .models import Payment
from .services import get_balance_delta, send_payment_notification, switch_to_voidwallet
api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict(
"api_invoice_listeners"
)

View file

@ -6,7 +6,7 @@ from subprocess import Popen
from typing import Optional
from urllib.parse import urlparse
from fastapi import Depends
from fastapi import APIRouter, Depends
from fastapi.responses import FileResponse
from starlette.exceptions import HTTPException
@ -21,11 +21,13 @@ from lnbits.decorators import check_admin, check_super_user
from lnbits.server import server_restart
from lnbits.settings import AdminSettings, UpdateSettings, settings
from .. import core_app, core_app_extra
from .. import core_app_extra
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
admin_router = APIRouter()
@core_app.get(
@admin_router.get(
"/admin/api/v1/audit",
name="Audit",
description="show the current balance of the node and the LNbits database",
@ -46,7 +48,7 @@ async def api_auditor():
)
@core_app.get("/admin/api/v1/settings/")
@admin_router.get("/admin/api/v1/settings/")
async def api_get_settings(
user: User = Depends(check_admin),
) -> Optional[AdminSettings]:
@ -54,7 +56,7 @@ async def api_get_settings(
return admin_settings
@core_app.put(
@admin_router.put(
"/admin/api/v1/settings/",
status_code=HTTPStatus.OK,
)
@ -67,7 +69,7 @@ async def api_update_settings(data: UpdateSettings, user: User = Depends(check_a
return {"status": "Success"}
@core_app.delete(
@admin_router.delete(
"/admin/api/v1/settings/",
status_code=HTTPStatus.OK,
dependencies=[Depends(check_super_user)],
@ -77,7 +79,7 @@ async def api_delete_settings() -> None:
server_restart.set()
@core_app.get(
@admin_router.get(
"/admin/api/v1/restart/",
status_code=HTTPStatus.OK,
dependencies=[Depends(check_super_user)],
@ -87,7 +89,7 @@ async def api_restart_server() -> dict[str, str]:
return {"status": "Success"}
@core_app.put(
@admin_router.put(
"/admin/api/v1/topup/",
name="Topup",
status_code=HTTPStatus.OK,
@ -111,7 +113,7 @@ async def api_topup_balance(data: CreateTopup) -> dict[str, str]:
return {"status": "Success"}
@core_app.get(
@admin_router.get(
"/admin/api/v1/backup/",
status_code=HTTPStatus.OK,
dependencies=[Depends(check_super_user)],

View file

@ -11,6 +11,7 @@ from urllib.parse import ParseResult, parse_qs, unquote, urlencode, urlparse, ur
import httpx
import pyqrcode
from fastapi import (
APIRouter,
Body,
Depends,
Header,
@ -25,6 +26,7 @@ from sse_starlette.sse import EventSourceResponse
from starlette.responses import RedirectResponse, StreamingResponse
from lnbits import bolt11, lnurl
from lnbits.core.db import core_app_extra, db
from lnbits.core.helpers import (
migrate_extension_database,
stop_extension_background_work,
@ -68,7 +70,6 @@ from lnbits.utils.exchange_rates import (
satoshis_amount_as_fiat,
)
from .. import core_app, core_app_extra, db
from ..crud import (
add_installed_extension,
create_tinyurl,
@ -101,13 +102,15 @@ from ..services import (
)
from ..tasks import api_invoice_listeners
api_router = APIRouter()
@core_app.get("/api/v1/health", status_code=HTTPStatus.OK)
@api_router.get("/api/v1/health", status_code=HTTPStatus.OK)
async def health():
return
@core_app.get("/api/v1/wallet")
@api_router.get("/api/v1/wallet")
async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet_type == WalletType.admin:
return {
@ -119,7 +122,7 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
return {"name": wallet.wallet.name, "balance": wallet.wallet.balance_msat}
@core_app.put("/api/v1/wallet/{new_name}")
@api_router.put("/api/v1/wallet/{new_name}")
async def api_update_wallet_name(
new_name: str, wallet: WalletTypeInfo = Depends(require_admin_key)
):
@ -131,7 +134,7 @@ async def api_update_wallet_name(
}
@core_app.patch("/api/v1/wallet", response_model=Wallet)
@api_router.patch("/api/v1/wallet", response_model=Wallet)
async def api_update_wallet(
name: Optional[str] = Body(None),
currency: Optional[str] = Body(None),
@ -140,7 +143,7 @@ async def api_update_wallet(
return await update_wallet(wallet.wallet.id, name, currency)
@core_app.get(
@api_router.get(
"/api/v1/payments",
name="Payment List",
summary="get list of payments",
@ -170,7 +173,7 @@ async def api_payments(
)
@core_app.get(
@api_router.get(
"/api/v1/payments/paginated",
name="Payment List",
summary="get paginated list of payments",
@ -312,7 +315,7 @@ async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
}
@core_app.post(
@api_router.post(
"/api/v1/payments",
summary="Create or pay an invoice",
description="""
@ -348,7 +351,7 @@ async def api_payments_create(
)
@core_app.post("/api/v1/payments/lnurl")
@api_router.post("/api/v1/payments/lnurl")
async def api_payments_pay_lnurl(
data: CreateLnurl, wallet: WalletTypeInfo = Depends(require_admin_key)
):
@ -455,7 +458,7 @@ async def subscribe_wallet_invoices(request: Request, wallet: Wallet):
return
@core_app.get("/api/v1/payments/sse")
@api_router.get("/api/v1/payments/sse")
async def api_payments_sse(
request: Request, wallet: WalletTypeInfo = Depends(get_key_type)
):
@ -467,7 +470,7 @@ async def api_payments_sse(
# TODO: refactor this route into a public and admin one
@core_app.get("/api/v1/payments/{payment_hash}")
@api_router.get("/api/v1/payments/{payment_hash}")
async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
# We use X_Api_Key here because we want this call to work with and without keys
# If a valid key is given, we also return the field "details", otherwise not
@ -512,7 +515,7 @@ async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
return {"paid": not payment.pending, "preimage": payment.preimage}
@core_app.get("/api/v1/lnurlscan/{code}")
@api_router.get("/api/v1/lnurlscan/{code}")
async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type)):
try:
url = lnurl.decode(code)
@ -627,7 +630,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
return params
@core_app.post("/api/v1/payments/decode", status_code=HTTPStatus.OK)
@api_router.post("/api/v1/payments/decode", status_code=HTTPStatus.OK)
async def api_payments_decode(data: DecodePayment, response: Response):
payment_str = data.data
try:
@ -653,7 +656,7 @@ async def api_payments_decode(data: DecodePayment, response: Response):
return {"message": "Failed to decode"}
@core_app.post("/api/v1/lnurlauth")
@api_router.post("/api/v1/lnurlauth")
async def api_perform_lnurlauth(
data: CreateLnurlAuth, wallet: WalletTypeInfo = Depends(require_admin_key)
):
@ -665,7 +668,7 @@ async def api_perform_lnurlauth(
return ""
@core_app.get("/api/v1/currencies")
@api_router.get("/api/v1/currencies")
async def api_list_currencies_available():
if len(settings.lnbits_allowed_currencies) > 0:
return [
@ -676,7 +679,7 @@ async def api_list_currencies_available():
return list(currencies.keys())
@core_app.post("/api/v1/conversion")
@api_router.post("/api/v1/conversion")
async def api_fiat_as_sats(data: ConversionData):
output = {}
if data.from_ == "sat":
@ -694,7 +697,7 @@ async def api_fiat_as_sats(data: ConversionData):
return output
@core_app.get("/api/v1/qrcode/{data}", response_class=StreamingResponse)
@api_router.get("/api/v1/qrcode/{data}", response_class=StreamingResponse)
async def img(data):
qr = pyqrcode.create(data)
stream = BytesIO()
@ -715,7 +718,7 @@ async def img(data):
)
@core_app.websocket("/api/v1/ws/{item_id}")
@api_router.websocket("/api/v1/ws/{item_id}")
async def websocket_connect(websocket: WebSocket, item_id: str):
await websocketManager.connect(websocket, item_id)
try:
@ -725,7 +728,7 @@ async def websocket_connect(websocket: WebSocket, item_id: str):
websocketManager.disconnect(websocket)
@core_app.post("/api/v1/ws/{item_id}")
@api_router.post("/api/v1/ws/{item_id}")
async def websocket_update_post(item_id: str, data: str):
try:
await websocketUpdater(item_id, data)
@ -734,7 +737,7 @@ async def websocket_update_post(item_id: str, data: str):
return {"sent": False, "data": data}
@core_app.get("/api/v1/ws/{item_id}/{data}")
@api_router.get("/api/v1/ws/{item_id}/{data}")
async def websocket_update_get(item_id: str, data: str):
try:
await websocketUpdater(item_id, data)
@ -743,7 +746,7 @@ async def websocket_update_get(item_id: str, data: str):
return {"sent": False, "data": data}
@core_app.post("/api/v1/extension")
@api_router.post("/api/v1/extension")
async def api_install_extension(
data: CreateExtension, user: User = Depends(check_admin)
):
@ -802,7 +805,7 @@ async def api_install_extension(
)
@core_app.delete("/api/v1/extension/{ext_id}")
@api_router.delete("/api/v1/extension/{ext_id}")
async def api_uninstall_extension(ext_id: str, user: User = Depends(check_admin)):
installable_extensions = await InstallableExtension.get_installable_extensions()
@ -845,7 +848,7 @@ async def api_uninstall_extension(ext_id: str, user: User = Depends(check_admin)
)
@core_app.get(
@api_router.get(
"/api/v1/extension/{ext_id}/releases", dependencies=[Depends(check_admin)]
)
async def get_extension_releases(ext_id: str):
@ -862,7 +865,7 @@ async def get_extension_releases(ext_id: str):
)
@core_app.get(
@api_router.get(
"/api/v1/extension/release/{org}/{repo}/{tag_name}",
dependencies=[Depends(check_admin)],
)
@ -883,7 +886,7 @@ async def get_extension_release(org: str, repo: str, tag_name: str):
)
@core_app.delete(
@api_router.delete(
"/api/v1/extension/{ext_id}/db",
dependencies=[Depends(check_admin)],
)
@ -909,10 +912,7 @@ async def delete_extension_db(ext_id: str):
)
# TINYURL
@core_app.post(
@api_router.post(
"/api/v1/tinyurl",
name="Tinyurl",
description="creates a tinyurl",
@ -933,7 +933,7 @@ async def api_create_tinyurl(
)
@core_app.get(
@api_router.get(
"/api/v1/tinyurl/{tinyurl_id}",
name="Tinyurl",
description="get a tinyurl by id",
@ -955,7 +955,7 @@ async def api_get_tinyurl(
)
@core_app.delete(
@api_router.delete(
"/api/v1/tinyurl/{tinyurl_id}",
name="Tinyurl",
description="delete a tinyurl by id",
@ -978,7 +978,7 @@ async def api_delete_tinyurl(
)
@core_app.get(
@api_router.get(
"/t/{tinyurl_id}",
name="Tinyurl",
description="redirects a tinyurl by id",
@ -994,10 +994,7 @@ async def api_tinyurl(tinyurl_id: str):
)
############################WEBPUSH##################################
@core_app.post("/api/v1/webpush", status_code=HTTPStatus.CREATED)
@api_router.post("/api/v1/webpush", status_code=HTTPStatus.CREATED)
async def api_create_webpush_subscription(
request: Request,
data: CreateWebPushSubscription,
@ -1019,7 +1016,7 @@ async def api_create_webpush_subscription(
)
@core_app.delete("/api/v1/webpush", status_code=HTTPStatus.OK)
@api_router.delete("/api/v1/webpush", status_code=HTTPStatus.OK)
async def api_delete_webpush_subscription(
request: Request,
wallet: WalletTypeInfo = Depends(require_admin_key),

View file

@ -11,7 +11,7 @@ from loguru import logger
from pydantic.types import UUID4
from starlette.responses import HTMLResponse, JSONResponse
from lnbits.core import db
from lnbits.core.db import db
from lnbits.core.helpers import to_valid_user_id
from lnbits.core.models import User
from lnbits.decorators import check_admin, check_user_exists
@ -36,24 +36,24 @@ from ..crud import (
)
from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(
generic_router = APIRouter(
tags=["Core NON-API Website Routes"], include_in_schema=False
)
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
@generic_router.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
@core_html_routes.get("/", response_class=HTMLResponse)
@generic_router.get("/", response_class=HTMLResponse)
async def home(request: Request, lightning: str = ""):
return template_renderer().TemplateResponse(
"core/index.html", {"request": request, "lnurl": lightning}
)
@core_html_routes.get("/robots.txt", response_class=HTMLResponse)
@generic_router.get("/robots.txt", response_class=HTMLResponse)
async def robots():
data = """
User-agent: *
@ -62,7 +62,7 @@ async def robots():
return HTMLResponse(content=data, media_type="text/plain")
@core_html_routes.get(
@generic_router.get(
"/extensions", name="install.extensions", response_class=HTMLResponse
)
async def extensions_install(
@ -159,7 +159,7 @@ async def extensions_install(
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
@core_html_routes.get(
@generic_router.get(
"/wallet",
response_class=HTMLResponse,
description="""
@ -245,7 +245,7 @@ async def wallet(
)
@core_html_routes.get("/withdraw", response_class=JSONResponse)
@generic_router.get("/withdraw", response_class=JSONResponse)
async def lnurl_full_withdraw(request: Request):
user = await get_user(request.query_params.get("usr"))
if not user:
@ -268,7 +268,7 @@ async def lnurl_full_withdraw(request: Request):
}
@core_html_routes.get("/withdraw/cb", response_class=JSONResponse)
@generic_router.get("/withdraw/cb", response_class=JSONResponse)
async def lnurl_full_withdraw_callback(request: Request):
user = await get_user(request.query_params.get("usr"))
if not user:
@ -295,7 +295,7 @@ async def lnurl_full_withdraw_callback(request: Request):
return {"status": "OK"}
@core_html_routes.get("/deletewallet", response_class=RedirectResponse)
@generic_router.get("/deletewallet", response_class=RedirectResponse)
async def deletewallet(wal: str = Query(...), usr: str = Query(...)):
user = await get_user(usr)
if not user:
@ -321,14 +321,14 @@ async def deletewallet(wal: str = Query(...), usr: str = Query(...)):
)
@core_html_routes.get("/withdraw/notify/{service}")
@generic_router.get("/withdraw/notify/{service}")
async def lnurl_balance_notify(request: Request, service: str):
bc = await get_balance_check(request.query_params.get("wal"), service)
if bc:
await redeem_lnurl_withdraw(bc.wallet, bc.url)
@core_html_routes.get(
@generic_router.get(
"/lnurlwallet", response_class=RedirectResponse, name="core.lnurlwallet"
)
async def lnurlwallet(request: Request):
@ -354,12 +354,12 @@ async def lnurlwallet(request: Request):
)
@core_html_routes.get("/service-worker.js", response_class=FileResponse)
@generic_router.get("/service-worker.js", response_class=FileResponse)
async def service_worker():
return FileResponse("lnbits/core/static/js/service-worker.js")
@core_html_routes.get("/manifest/{usr}.webmanifest")
@generic_router.get("/manifest/{usr}.webmanifest")
async def manifest(request: Request, usr: str):
host = urlparse(str(request.url)).netloc
@ -400,7 +400,7 @@ async def manifest(request: Request, usr: str):
}
@core_html_routes.get("/admin", response_class=HTMLResponse)
@generic_router.get("/admin", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_admin)):
if not settings.lnbits_admin_ui:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
@ -420,7 +420,7 @@ async def index(request: Request, user: User = Depends(check_admin)):
)
@core_html_routes.get("/uuidv4/{hex_value}")
@generic_router.get("/uuidv4/{hex_value}")
async def hex_to_uuid4(hex_value: str):
try:
user_id = to_valid_user_id(hex_value).hex

View file

@ -2,17 +2,18 @@ import asyncio
import datetime
from http import HTTPStatus
from fastapi import HTTPException
from fastapi import APIRouter, HTTPException
from loguru import logger
from lnbits import bolt11
from .. import core_app
from ..crud import get_standalone_payment
from ..tasks import api_invoice_listeners
public_router = APIRouter()
@core_app.get("/public/v1/payment/{payment_hash}")
@public_router.get("/public/v1/payment/{payment_hash}")
async def api_public_payment_longpolling(payment_hash):
payment = await get_standalone_payment(payment_hash)

View file

@ -10,7 +10,7 @@ from slowapi.middleware import SlowAPIMiddleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.types import ASGIApp, Receive, Scope, Send
from lnbits.core import core_app_extra
from lnbits.core.db import core_app_extra
from lnbits.helpers import template_renderer
from lnbits.settings import settings

View file

@ -18,12 +18,11 @@ from lnbits.core.crud import (
get_payments,
get_standalone_payment,
)
from lnbits.core.db import db
from lnbits.core.services import redeem_lnurl_withdraw
from lnbits.settings import settings
from lnbits.wallets import get_wallet_class
from .core import db
tasks: List[asyncio.Task] = []

View file

@ -184,7 +184,7 @@ class CoreLightningRestWallet(Wallet):
return PaymentStatus(None)
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
from lnbits.core import get_standalone_payment
from lnbits.core.services import get_standalone_payment
payment = await get_standalone_payment(checking_id)
if not payment:

View file

@ -122,9 +122,7 @@ 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
]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]

View file

@ -1,3 +1,4 @@
# ruff: noqa: E402
import asyncio
import uvloop