feat: code quality (#26)

* feat: code quality

* fixes
This commit is contained in:
dni ⚡ 2024-08-02 10:05:26 +02:00 committed by GitHub
commit 32bf4ae1d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 2822 additions and 61 deletions

10
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,10 @@
name: lint
on:
push:
branches:
- main
pull_request:
jobs:
lint:
uses: lnbits/lnbits/.github/workflows/lint.yml@dev

View file

@ -1,10 +1,9 @@
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
release:
runs-on: ubuntu-latest
steps:
@ -34,12 +33,12 @@ jobs:
- name: Create pull request in extensions repo
env:
GH_TOKEN: ${{ secrets.EXT_GITHUB }}
repo_name: "${{ github.event.repository.name }}"
tag: "${{ github.ref_name }}"
branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}"
title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}"
body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}"
archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip"
repo_name: '${{ github.event.repository.name }}'
tag: '${{ github.ref_name }}'
branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}'
title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}'
body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}'
archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip'
run: |
cd lnbits-extensions
git checkout -b $branch

3
.gitignore vendored
View file

@ -1 +1,4 @@
__pycache__
node_modules
.mypy_cache
.venv

12
.prettierrc Normal file
View file

@ -0,0 +1,12 @@
{
"semi": false,
"arrowParens": "avoid",
"insertPragma": false,
"printWidth": 80,
"proseWrap": "preserve",
"singleQuote": true,
"trailingComma": "none",
"useTabs": false,
"bracketSameLine": false,
"bracketSpacing": false
}

47
Makefile Normal file
View file

@ -0,0 +1,47 @@
all: format check
format: prettier black ruff
check: mypy pyright checkblack checkruff checkprettier
prettier:
poetry run ./node_modules/.bin/prettier --write .
pyright:
poetry run ./node_modules/.bin/pyright
mypy:
poetry run mypy .
black:
poetry run black .
ruff:
poetry run ruff check . --fix
checkruff:
poetry run ruff check .
checkprettier:
poetry run ./node_modules/.bin/prettier --check .
checkblack:
poetry run black --check .
checkeditorconfig:
editorconfig-checker
test:
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest
install-pre-commit-hook:
@echo "Installing pre-commit hook to git"
@echo "Uninstall the hook with poetry run pre-commit uninstall"
poetry run pre-commit install
pre-commit:
poetry run pre-commit run --all-files
checkbundle:
@echo "skipping checkbundle"

View file

@ -1,13 +1,12 @@
import asyncio
from loguru import logger
from fastapi import APIRouter
from loguru import logger
from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import create_permanent_unique_task
db = Database("ext_splitpayments")
from .crud import db
from .tasks import wait_for_paid_invoices
from .views import splitpayments_generic_router
from .views_api import splitpayments_api_router
splitpayments_static_files = [
{
@ -18,19 +17,12 @@ splitpayments_static_files = [
splitpayments_ext: APIRouter = APIRouter(
prefix="/splitpayments", tags=["splitpayments"]
)
def splitpayments_renderer():
return template_renderer(["splitpayments/templates"])
from .tasks import wait_for_paid_invoices
from .views import * # noqa: F401,F403
from .views_api import * # noqa: F401,F403
splitpayments_ext.include_router(splitpayments_generic_router)
splitpayments_ext.include_router(splitpayments_api_router)
scheduled_tasks: list[asyncio.Task] = []
def splitpayments_stop():
for task in scheduled_tasks:
try:
@ -38,6 +30,18 @@ def splitpayments_stop():
except Exception as ex:
logger.warning(ex)
def splitpayments_start():
from lnbits.tasks import create_permanent_unique_task
task = create_permanent_unique_task("ext_splitpayments", wait_for_paid_invoices)
scheduled_tasks.append(task)
__all__ = [
"db",
"splitpayments_ext",
"splitpayments_static_files",
"splitpayments_start",
"splitpayments_stop",
]

View file

@ -1,10 +1,12 @@
from typing import List
from lnbits.db import Database
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import Target
db = Database("ext_splitpayments")
async def get_targets(source_wallet: str) -> List[Target]:
rows = await db.fetchall(

View file

@ -2,11 +2,12 @@ Split Payments across multiple wallets/lnaddresses/lnurlps seamlessly!
Once configured, it continuously splits your payments across different wallets.
Usage:
* Enable the Extension: Start by enabling the Split Payments extension.
* Select the Source Wallet: Identify and select the wallet that will receive and subsequently distribute the payments.
* Add Wallet Information for Payment Splitting: Enter the details of the wallets where the payments will be split. This could include LNURLp, LNaddress, wallet ID, or an invoice key from a different wallet. Wallet details can be found under the API Info section on each wallet's page. Optionally, assign an alias to each wallet for easier identification.
* Set Distribution Percentages: Specify the percentage of each payment that each wallet should receive. Ensure the total distribution does not exceed 100%.
* Save Your Settings: After adding or deleting wallet information, click “SAVE TARGETS” to activate the payment splits.
- Enable the Extension: Start by enabling the Split Payments extension.
- Select the Source Wallet: Identify and select the wallet that will receive and subsequently distribute the payments.
- Add Wallet Information for Payment Splitting: Enter the details of the wallets where the payments will be split. This could include LNURLp, LNaddress, wallet ID, or an invoice key from a different wallet. Wallet details can be found under the API Info section on each wallet's page. Optionally, assign an alias to each wallet for easier identification.
- Set Distribution Percentages: Specify the percentage of each payment that each wallet should receive. Ensure the total distribution does not exceed 100%.
- Save Your Settings: After adding or deleting wallet information, click “SAVE TARGETS” to activate the payment splits.
Note:
You can distribute payments to multiple wallets as long as their combined percentage is at or below 100%. Distribution can only total exactly 100% if all target wallets are internal.

59
package-lock.json generated Normal file
View file

@ -0,0 +1,59 @@
{
"name": "splitpayments",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "splitpayments",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"prettier": "^3.2.5",
"pyright": "^1.1.358"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pyright": {
"version": "1.1.374",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.374.tgz",
"integrity": "sha512-ISbC1YnYDYrEatoKKjfaA5uFIp0ddC/xw9aSlN/EkmwupXUMVn41Jl+G6wHEjRhC+n4abHZeGpEvxCUus/K9dA==",
"bin": {
"pyright": "index.js",
"pyright-langserver": "langserver.index.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
}
}
}

15
package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "splitpayments",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"prettier": "^3.2.5",
"pyright": "^1.1.358"
}
}

2492
poetry.lock generated Normal file

File diff suppressed because it is too large Load diff

94
pyproject.toml Normal file
View file

@ -0,0 +1,94 @@
[tool.poetry]
name = "lnbits-splitpayments"
version = "0.0.0"
description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = ["Alan Bits <alan@lnbits.com>"]
[tool.poetry.dependencies]
python = "^3.10 | ^3.9"
lnbits = "*"
[tool.poetry.group.dev.dependencies]
black = "^24.3.0"
pytest-asyncio = "^0.21.0"
pytest = "^7.3.2"
mypy = "^1.5.1"
pre-commit = "^3.2.2"
ruff = "^0.3.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.mypy]
exclude = "(nostr/*)"
[[tool.mypy.overrides]]
module = [
"lnbits.*",
"lnurl.*",
"loguru.*",
"fastapi.*",
"pydantic.*",
"pyqrcode.*",
"shortuuid.*",
"httpx.*",
]
ignore_missing_imports = "True"
[tool.pytest.ini_options]
log_cli = false
testpaths = [
"tests"
]
[tool.black]
line-length = 88
[tool.ruff]
# Same as Black. + 10% rule of black
line-length = 88
exclude = [
"nostr",
]
[tool.ruff.lint]
# Enable:
# F - pyflakes
# E - pycodestyle errors
# W - pycodestyle warnings
# I - isort
# A - flake8-builtins
# C - mccabe
# N - naming
# UP - pyupgrade
# RUF - ruff
# B - bugbear
select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"]
ignore = ["C901"]
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# needed for pydantic
[tool.ruff.lint.pep8-naming]
classmethod-decorators = [
"root_validator",
]
# Ignore unused imports in __init__.py files.
# [tool.ruff.lint.extend-per-file-ignores]
# "__init__.py" = ["F401", "F403"]
# [tool.ruff.lint.mccabe]
# max-complexity = 10
[tool.ruff.lint.flake8-bugbear]
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.
extend-immutable-calls = [
"fastapi.Depends",
"fastapi.Query",
]

View file

@ -34,7 +34,7 @@ new Vue({
},
methods: {
clearTarget(index) {
if(this.targets.length == 1) {
if (this.targets.length == 1) {
return this.deleteTargets()
}
this.targets.splice(index, 1)

View file

@ -3,15 +3,14 @@ import json
from math import floor
from typing import Optional
import bolt11
import httpx
from loguru import logger
from lnbits import bolt11
from lnbits.core.crud import get_standalone_payment
from lnbits.core.models import Payment
from lnbits.core.services import create_invoice, fee_reserve, pay_invoice
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
from loguru import logger
from .crud import get_targets
@ -102,7 +101,7 @@ async def get_lnurl_invoice(
)
return None
except Exception as exc:
logger.error(f"splitting LNURL failed: {str(exc)}.")
logger.error(f"splitting LNURL failed: {exc!s}.")
return None
params = json.loads(r.text)
@ -115,12 +114,15 @@ async def get_lnurl_invoice(
lnurlp_payment = await get_standalone_payment(invoice.payment_hash)
if lnurlp_payment and lnurlp_payment.wallet_id == wallet_id:
logger.error(f"split failed. cannot split payments to yourself via LNURL.")
logger.error("split failed. cannot split payments to yourself via LNURL.")
return None
if invoice.amount_msat != rounded_amount:
logger.error(
f"{data['callback']} returned an invalid invoice. Expected {amount_msat} msat, got {invoice.amount_msat}."
f"""
{data['callback']} returned an invalid invoice.
Expected {amount_msat} msat, got {invoice.amount_msat}.
"""
)
return None

0
tests/__init__.py Normal file
View file

11
tests/test_init.py Normal file
View file

@ -0,0 +1,11 @@
import pytest
from fastapi import APIRouter
from .. import splitpayments_ext
# just import router and add it to a test router
@pytest.mark.asyncio
async def test_router():
router = APIRouter()
router.include_router(splitpayments_ext)

7
toc.md
View file

@ -1,22 +1,29 @@
# Terms and Conditions for LNbits Extension
## 1. Acceptance of Terms
By installing and using the LNbits extension ("Extension"), you agree to be bound by these terms and conditions ("Terms"). If you do not agree to these Terms, do not use the Extension.
## 2. License
The Extension is free and open-source software, released under [specify the FOSS license here, e.g., GPL-3.0, MIT, etc.]. You are permitted to use, copy, modify, and distribute the Extension under the terms of that license.
## 3. No Warranty
The Extension is provided "as is" and with all faults, and the developer expressly disclaims all warranties of any kind, whether express, implied, statutory, or otherwise, including but not limited to warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranties arising out of course of dealing or usage of trade. No advice or information, whether oral or written, obtained from the developer or elsewhere will create any warranty not expressly stated in this Terms.
## 4. Limitation of Liability
In no event will the developer be liable to you or any third party for any direct, indirect, incidental, special, consequential, or punitive damages, including lost profit, lost revenue, loss of data, or other damages arising out of or in connection with your use of the Extension, even if the developer has been advised of the possibility of such damages. The foregoing limitation of liability shall apply to the fullest extent permitted by law in the applicable jurisdiction.
## 5. Modification of Terms
The developer reserves the right to modify these Terms at any time. You are advised to review these Terms periodically for any changes. Changes to these Terms are effective when they are posted on the appropriate location within or associated with the Extension.
## 6. General Provisions
If any provision of these Terms is held to be invalid or unenforceable, that provision will be enforced to the maximum extent permissible, and the other provisions of these Terms will remain in full force and effect. These Terms constitute the entire agreement between you and the developer regarding the use of the Extension.
## 7. Contact Information
If you have any questions about these Terms, please contact the developer at [developer's contact information].

View file

@ -1,16 +1,19 @@
from fastapi import Depends, Request
from fastapi import APIRouter, Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import splitpayments_ext, splitpayments_renderer
from lnbits.helpers import template_renderer
from starlette.responses import HTMLResponse
templates = Jinja2Templates(directory="templates")
splitpayments_generic_router = APIRouter()
@splitpayments_ext.get("/", response_class=HTMLResponse)
def splitpayments_renderer():
return template_renderer(["splitpayments/templates"])
@splitpayments_generic_router.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return splitpayments_renderer().TemplateResponse(
"splitpayments/index.html", {"request": request, "user": user.dict()}

View file

@ -1,39 +1,38 @@
from http import HTTPStatus
from typing import List
from fastapi import Depends
from loguru import logger
from starlette.exceptions import HTTPException
from fastapi import APIRouter, Depends, HTTPException
from lnbits.core.crud import get_wallet, get_wallet_for_key
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.core.models import WalletTypeInfo
from lnbits.decorators import require_admin_key
from loguru import logger
from . import splitpayments_ext
from .crud import get_targets, set_targets
from .models import Target, TargetPutList
splitpayments_api_router = APIRouter()
@splitpayments_ext.get("/api/v1/targets")
@splitpayments_api_router.get("/api/v1/targets")
async def api_targets_get(
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> List[Target]:
) -> list[Target]:
targets = await get_targets(wallet.wallet.id)
return targets or []
@splitpayments_ext.put("/api/v1/targets", status_code=HTTPStatus.OK)
@splitpayments_api_router.put("/api/v1/targets", status_code=HTTPStatus.OK)
async def api_targets_set(
target_put: TargetPutList,
source_wallet: WalletTypeInfo = Depends(require_admin_key),
) -> None:
try:
targets: List[Target] = []
targets: list[Target] = []
for entry in target_put.targets:
if entry.wallet.find("@") < 0 and entry.wallet.find("LNURL") < 0:
wallet = await get_wallet(entry.wallet)
if not wallet:
wallet = await get_wallet_for_key(entry.wallet, "invoice")
wallet = await get_wallet_for_key(entry.wallet)
if not wallet:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@ -42,7 +41,8 @@ async def api_targets_set(
if wallet.id == source_wallet.wallet.id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Can't split to itself."
status_code=HTTPStatus.BAD_REQUEST,
detail="Can't split to itself.",
)
if entry.percent <= 0:
@ -73,10 +73,10 @@ async def api_targets_set(
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot set targets.",
)
) from ex
@splitpayments_ext.delete("/api/v1/targets", status_code=HTTPStatus.OK)
@splitpayments_api_router.delete("/api/v1/targets", status_code=HTTPStatus.OK)
async def api_targets_delete(
source_wallet: WalletTypeInfo = Depends(require_admin_key),
) -> None: