feat: use uv instead of poetry for CI, docker and development (#3325)
Co-authored-by: arcbtc <ben@arc.wales>
This commit is contained in:
parent
15984fa49b
commit
5ba06d42d0
88 changed files with 4265 additions and 1303 deletions
22
.github/actions/prepare/action.yml
vendored
22
.github/actions/prepare/action.yml
vendored
|
|
@ -21,29 +21,17 @@ runs:
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
# cache poetry install via pip
|
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
|
|
||||||
- name: Set up Poetry
|
- name: Install uv
|
||||||
uses: abatilo/actions-poetry@v2
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|
||||||
- name: Setup a local virtual environment (if no poetry.toml file)
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
poetry config virtualenvs.create true --local
|
|
||||||
poetry config virtualenvs.in-project true --local
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
name: Define a cache for the virtual environment based on the dependencies lock file
|
|
||||||
with:
|
with:
|
||||||
path: ./.venv
|
enable-cache: true
|
||||||
key: venv-${{ hashFiles('poetry.lock') }}
|
python-version: ${{ inputs.python-version }}
|
||||||
|
|
||||||
- name: Install the project dependencies
|
- name: Install the project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: uv sync --locked --all-extras --dev
|
||||||
poetry env use python${{ inputs.python-version }}
|
|
||||||
poetry install --all-extras
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ inputs.node-version }}
|
- name: Use Node.js ${{ inputs.node-version }}
|
||||||
if: ${{ (inputs.npm == 'true') }}
|
if: ${{ (inputs.npm == 'true') }}
|
||||||
|
|
|
||||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/api"
|
custom-pytest: "uv run pytest tests/api"
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
db-url: ${{ matrix.db-url }}
|
db-url: ${{ matrix.db-url }}
|
||||||
secrets:
|
secrets:
|
||||||
|
|
@ -34,7 +34,7 @@ jobs:
|
||||||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/wallets"
|
custom-pytest: "uv run pytest tests/wallets"
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
db-url: ${{ matrix.db-url }}
|
db-url: ${{ matrix.db-url }}
|
||||||
secrets:
|
secrets:
|
||||||
|
|
@ -48,7 +48,7 @@ jobs:
|
||||||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/unit"
|
custom-pytest: "uv run pytest tests/unit"
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
db-url: ${{ matrix.db-url }}
|
db-url: ${{ matrix.db-url }}
|
||||||
secrets:
|
secrets:
|
||||||
|
|
@ -77,7 +77,7 @@ jobs:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.10"]
|
||||||
backend-wallet-class: ["LndRestWallet", "LndWallet", "CoreLightningWallet", "CoreLightningRestWallet", "LNbitsWallet", "EclairWallet"]
|
backend-wallet-class: ["LndRestWallet", "LndWallet", "CoreLightningWallet", "CoreLightningRestWallet", "LNbitsWallet", "EclairWallet"]
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/regtest"
|
custom-pytest: "uv run pytest tests/regtest"
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
backend-wallet-class: ${{ matrix.backend-wallet-class }}
|
backend-wallet-class: ${{ matrix.backend-wallet-class }}
|
||||||
secrets:
|
secrets:
|
||||||
|
|
|
||||||
2
.github/workflows/jmeter.yml
vendored
2
.github/workflows/jmeter.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
LNBITS_EXTENSIONS_DEFAULT_INSTALL: "watchonly, satspay, tipjar, tpos, lnurlp, withdraw"
|
LNBITS_EXTENSIONS_DEFAULT_INSTALL: "watchonly, satspay, tipjar, tpos, lnurlp, withdraw"
|
||||||
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
|
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
|
||||||
run: |
|
run: |
|
||||||
poetry run lnbits &
|
uv run lnbits &
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
- name: setup java version
|
- name: setup java version
|
||||||
|
|
|
||||||
5
.github/workflows/nix.yml
vendored
5
.github/workflows/nix.yml
vendored
|
|
@ -14,18 +14,17 @@ on:
|
||||||
- 'flake.nix'
|
- 'flake.nix'
|
||||||
- 'flake.lock'
|
- 'flake.lock'
|
||||||
- 'pyproject.toml'
|
- 'pyproject.toml'
|
||||||
- 'poetry.lock'
|
- 'uv.lock'
|
||||||
- '.github/workflows/nix.yml'
|
- '.github/workflows/nix.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'flake.nix'
|
- 'flake.nix'
|
||||||
- 'flake.lock'
|
- 'flake.lock'
|
||||||
- 'pyproject.toml'
|
- 'pyproject.toml'
|
||||||
- 'poetry.lock'
|
- 'uv.lock'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nix:
|
nix:
|
||||||
if: false # temporarly disable nix support until the `poetry2nix` issue is resolved
|
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
||||||
8
.github/workflows/regtest.yml
vendored
8
.github/workflows/regtest.yml
vendored
|
|
@ -32,6 +32,10 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
docker build -t lnbits/lnbits .
|
docker build -t lnbits/lnbits .
|
||||||
|
|
||||||
|
- uses: ./.github/actions/prepare
|
||||||
|
with:
|
||||||
|
python-version: ${{ inputs.python-version }}
|
||||||
|
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||||
|
|
@ -40,10 +44,6 @@ jobs:
|
||||||
./tests
|
./tests
|
||||||
sudo chmod -R a+rwx .
|
sudo chmod -R a+rwx .
|
||||||
|
|
||||||
- uses: ./.github/actions/prepare
|
|
||||||
with:
|
|
||||||
python-version: ${{ inputs.python-version }}
|
|
||||||
|
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
uses: pavelzw/pytest-action@v2
|
uses: pavelzw/pytest-action@v2
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
27
Dockerfile
27
Dockerfile
|
|
@ -2,26 +2,20 @@ FROM python:3.12-slim-bookworm AS builder
|
||||||
|
|
||||||
RUN apt-get clean
|
RUN apt-get clean
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y curl pkg-config build-essential libnss-myhostname
|
RUN apt-get install -y curl pkg-config build-essential libnss-myhostname automake
|
||||||
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
ENV PATH="/root/.local/bin:$PATH"
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Only copy the files required to install the dependencies
|
# Only copy the files required to install the dependencies
|
||||||
COPY pyproject.toml poetry.lock ./
|
COPY pyproject.toml uv.lock ./
|
||||||
RUN touch README.md
|
RUN touch README.md
|
||||||
|
|
||||||
RUN mkdir data
|
RUN mkdir data
|
||||||
|
|
||||||
ENV POETRY_NO_INTERACTION=1 \
|
RUN uv sync --all-extras
|
||||||
POETRY_VIRTUALENVS_IN_PROJECT=1 \
|
|
||||||
POETRY_VIRTUALENVS_CREATE=1 \
|
|
||||||
POETRY_CACHE_DIR=/tmp/poetry_cache
|
|
||||||
|
|
||||||
ARG POETRY_INSTALL_ARGS="--only main"
|
|
||||||
RUN poetry install --no-root ${POETRY_INSTALL_ARGS}
|
|
||||||
|
|
||||||
FROM python:3.12-slim-bookworm
|
FROM python:3.12-slim-bookworm
|
||||||
|
|
||||||
|
|
@ -34,26 +28,19 @@ RUN apt-get update && apt-get -y upgrade && \
|
||||||
apt-get -y install postgresql-client-14 postgresql-client-common && \
|
apt-get -y install postgresql-client-14 postgresql-client-common && \
|
||||||
apt-get clean all && rm -rf /var/lib/apt/lists/*
|
apt-get clean all && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
ENV PATH="/root/.local/bin:$PATH"
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
ENV POETRY_NO_INTERACTION=1 \
|
|
||||||
POETRY_VIRTUALENVS_IN_PROJECT=1 \
|
|
||||||
POETRY_VIRTUALENVS_CREATE=1 \
|
|
||||||
VIRTUAL_ENV=/app/.venv \
|
|
||||||
PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=builder /app/.venv .venv
|
COPY --from=builder /app/.venv .venv
|
||||||
|
|
||||||
ARG POETRY_INSTALL_ARGS="--only main"
|
RUN uv sync --all-extras
|
||||||
RUN poetry install ${POETRY_INSTALL_ARGS}
|
|
||||||
|
|
||||||
ENV LNBITS_PORT="5000"
|
ENV LNBITS_PORT="5000"
|
||||||
ENV LNBITS_HOST="0.0.0.0"
|
ENV LNBITS_HOST="0.0.0.0"
|
||||||
|
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
CMD ["sh", "-c", "poetry run lnbits --port $LNBITS_PORT --host $LNBITS_HOST --forwarded-allow-ips='*'"]
|
CMD ["sh", "-c", "uv run lnbits --port $LNBITS_PORT --host $LNBITS_HOST --forwarded-allow-ips='*'"]
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ RUN apt-get update && apt-get -y upgrade && \
|
||||||
apt-get install -y netcat-openbsd
|
apt-get install -y netcat-openbsd
|
||||||
|
|
||||||
# Reinstall dependencies just in case (needed for CMD usage)
|
# Reinstall dependencies just in case (needed for CMD usage)
|
||||||
ARG POETRY_INSTALL_ARGS="--only main"
|
RUN uv sync --all-extras
|
||||||
RUN poetry install ${POETRY_INSTALL_ARGS}
|
|
||||||
|
|
||||||
# LNbits + boltzd configuration
|
# LNbits + boltzd configuration
|
||||||
ENV LNBITS_PORT="5000"
|
ENV LNBITS_PORT="5000"
|
||||||
|
|
|
||||||
46
Makefile
46
Makefile
|
|
@ -9,34 +9,34 @@ check: mypy pyright checkblack checkruff checkprettier checkbundle
|
||||||
test: test-unit test-wallets test-api test-regtest
|
test: test-unit test-wallets test-api test-regtest
|
||||||
|
|
||||||
prettier:
|
prettier:
|
||||||
poetry run ./node_modules/.bin/prettier --write .
|
uv run ./node_modules/.bin/prettier --write .
|
||||||
|
|
||||||
pyright:
|
pyright:
|
||||||
poetry run ./node_modules/.bin/pyright
|
uv run ./node_modules/.bin/pyright
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
poetry run mypy
|
uv run mypy
|
||||||
|
|
||||||
black:
|
black:
|
||||||
poetry run black .
|
uv run black .
|
||||||
|
|
||||||
ruff:
|
ruff:
|
||||||
poetry run ruff check . --fix
|
uv run ruff check . --fix
|
||||||
|
|
||||||
checkruff:
|
checkruff:
|
||||||
poetry run ruff check .
|
uv run ruff check .
|
||||||
|
|
||||||
checkprettier:
|
checkprettier:
|
||||||
poetry run ./node_modules/.bin/prettier --check .
|
uv run ./node_modules/.bin/prettier --check .
|
||||||
|
|
||||||
checkblack:
|
checkblack:
|
||||||
poetry run black --check .
|
uv run black --check .
|
||||||
|
|
||||||
checkeditorconfig:
|
checkeditorconfig:
|
||||||
editorconfig-checker
|
editorconfig-checker
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
poetry run lnbits --reload
|
uv run lnbits --reload
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t lnbits/lnbits .
|
docker build -t lnbits/lnbits .
|
||||||
|
|
@ -46,27 +46,27 @@ test-wallets:
|
||||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
DEBUG=true \
|
DEBUG=true \
|
||||||
poetry run pytest tests/wallets
|
uv run pytest tests/wallets
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
DEBUG=true \
|
DEBUG=true \
|
||||||
poetry run pytest tests/unit
|
uv run pytest tests/unit
|
||||||
|
|
||||||
test-api:
|
test-api:
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
DEBUG=true \
|
DEBUG=true \
|
||||||
poetry run pytest tests/api
|
uv run pytest tests/api
|
||||||
|
|
||||||
test-regtest:
|
test-regtest:
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
DEBUG=true \
|
DEBUG=true \
|
||||||
poetry run pytest tests/regtest
|
uv run pytest tests/regtest
|
||||||
|
|
||||||
test-migration:
|
test-migration:
|
||||||
LNBITS_ADMIN_UI=True \
|
LNBITS_ADMIN_UI=True \
|
||||||
|
|
@ -74,18 +74,18 @@ test-migration:
|
||||||
HOST=0.0.0.0 \
|
HOST=0.0.0.0 \
|
||||||
PORT=5002 \
|
PORT=5002 \
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
timeout 5s uv run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
||||||
HOST=0.0.0.0 \
|
HOST=0.0.0.0 \
|
||||||
PORT=5002 \
|
PORT=5002 \
|
||||||
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
||||||
LNBITS_ADMIN_UI=False \
|
LNBITS_ADMIN_UI=False \
|
||||||
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
timeout 5s uv run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
||||||
poetry run python tools/conv.py
|
uv run python tools/conv.py
|
||||||
|
|
||||||
migration:
|
migration:
|
||||||
poetry run python tools/conv.py
|
uv run python tools/conv.py
|
||||||
|
|
||||||
openapi:
|
openapi:
|
||||||
LNBITS_ADMIN_UI=False \
|
LNBITS_ADMIN_UI=False \
|
||||||
|
|
@ -94,9 +94,9 @@ openapi:
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
HOST=0.0.0.0 \
|
HOST=0.0.0.0 \
|
||||||
PORT=5003 \
|
PORT=5003 \
|
||||||
poetry run lnbits &
|
uv run lnbits &
|
||||||
sleep 15
|
sleep 15
|
||||||
curl -s http://0.0.0.0:5003/openapi.json | poetry run openapi-spec-validator --errors=all -
|
curl -s http://0.0.0.0:5003/openapi.json | uv run openapi-spec-validator --errors=all -
|
||||||
# kill -9 %1
|
# kill -9 %1
|
||||||
|
|
||||||
bak:
|
bak:
|
||||||
|
|
@ -109,7 +109,7 @@ sass:
|
||||||
bundle:
|
bundle:
|
||||||
npm install
|
npm install
|
||||||
npm run bundle
|
npm run bundle
|
||||||
poetry run ./node_modules/.bin/prettier -w ./lnbits/static/vendor.json
|
uv run ./node_modules/.bin/prettier -w ./lnbits/static/vendor.json
|
||||||
|
|
||||||
checkbundle:
|
checkbundle:
|
||||||
cp lnbits/static/bundle.min.js lnbits/static/bundle.min.js.old
|
cp lnbits/static/bundle.min.js lnbits/static/bundle.min.js.old
|
||||||
|
|
@ -126,8 +126,8 @@ checkbundle:
|
||||||
|
|
||||||
install-pre-commit-hook:
|
install-pre-commit-hook:
|
||||||
@echo "Installing pre-commit hook to git"
|
@echo "Installing pre-commit hook to git"
|
||||||
@echo "Uninstall the hook with poetry run pre-commit uninstall"
|
@echo "Uninstall the hook with uv run pre-commit uninstall"
|
||||||
poetry run pre-commit install
|
uv run pre-commit install
|
||||||
|
|
||||||
pre-commit:
|
pre-commit:
|
||||||
poetry run pre-commit run --all-files
|
uv run pre-commit run --all-files
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,4 @@ if ! nc -z localhost 9002; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting LNbits on $LNBITS_HOST:$LNBITS_PORT..."
|
echo "Starting LNbits on $LNBITS_HOST:$LNBITS_PORT..."
|
||||||
exec poetry run lnbits --port "$LNBITS_PORT" --host "$LNBITS_HOST" --forwarded-allow-ips='*'
|
exec uv run lnbits --port "$LNBITS_PORT" --host "$LNBITS_HOST" --forwarded-allow-ips='*'
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ Thanks for contributing :)
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
|
|
||||||
Follow the [Basic installation: Option 1 (recommended): poetry](https://docs.lnbits.org/guide/installation.html#option-1-recommended-poetry)
|
Follow the [Option 2 (recommended): UV](https://docs.lnbits.org/guide/installation.html)
|
||||||
guide to install poetry and other dependencies.
|
guide to install uv and other dependencies.
|
||||||
|
|
||||||
Then you can start LNbits uvicorn server with:
|
Then you can start LNbits uvicorn server with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry run lnbits
|
uv run lnbits
|
||||||
```
|
```
|
||||||
|
|
||||||
Or you can use the following to start uvicorn with hot reloading enabled:
|
Or you can use the following to start uvicorn with hot reloading enabled:
|
||||||
|
|
@ -25,7 +25,7 @@ Or you can use the following to start uvicorn with hot reloading enabled:
|
||||||
```bash
|
```bash
|
||||||
make dev
|
make dev
|
||||||
# or
|
# or
|
||||||
poetry run lnbits --reload
|
uv run lnbits --reload
|
||||||
```
|
```
|
||||||
|
|
||||||
You might need the following extra dependencies on clean installation of Debian:
|
You might need the following extra dependencies on clean installation of Debian:
|
||||||
|
|
@ -50,7 +50,7 @@ make install-pre-commit-hook
|
||||||
This project has unit tests that help prevent regressions. Before you can run the tests, you must install a few dependencies:
|
This project has unit tests that help prevent regressions. Before you can run the tests, you must install a few dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry install
|
uv sync --all-extras --dev
|
||||||
npm i
|
npm i
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ make format
|
||||||
Run mypy checks:
|
Run mypy checks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry run mypy
|
make mypy
|
||||||
```
|
```
|
||||||
|
|
||||||
Run everything:
|
Run everything:
|
||||||
|
|
|
||||||
|
|
@ -42,14 +42,10 @@ mv templates/example templates/mysuperplugin # Rename templates folder.
|
||||||
|
|
||||||
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are available in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
|
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are available in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
|
||||||
|
|
||||||
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `poerty`:
|
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `poerty` or `uv`:
|
||||||
|
|
||||||
```sh
|
|
||||||
$ poetry add <package>
|
|
||||||
```
|
|
||||||
|
|
||||||
**But we need an extra step to make sure LNbits doesn't break in production.**
|
**But we need an extra step to make sure LNbits doesn't break in production.**
|
||||||
Dependencies need to be added to `pyproject.toml`, then tested by running on `poetry` compatibility can be tested with `nix build .#checks.x86_64-linux.vmTest`.
|
Dependencies need to be added to `pyproject.toml`, then tested by running on `uv` and `poetry` compatibility can be tested with `nix build .#checks.x86_64-linux.vmTest`.
|
||||||
|
|
||||||
## SQLite to PostgreSQL migration
|
## SQLite to PostgreSQL migration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ $ sudo nano .env
|
||||||
Now start LNbits once in the terminal window
|
Now start LNbits once in the terminal window
|
||||||
|
|
||||||
```
|
```
|
||||||
$ poetry run lnbits
|
$ uv run lnbits
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now `cat` the Super User ID:
|
You can now `cat` the Super User ID:
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,15 @@ LNBITS_ADMIN_UI=true HOST=0.0.0.0 PORT=5000 ./LNbits-latest.AppImage # most syst
|
||||||
|
|
||||||
LNbits will create a folder for db and extension files in the folder the AppImage runs from.
|
LNbits will create a folder for db and extension files in the folder the AppImage runs from.
|
||||||
|
|
||||||
## Option 2: Poetry (recommended for developers)
|
## Option 2: UV (recommended for developers)
|
||||||
|
|
||||||
It is recommended to use the latest version of Poetry. Make sure you have Python version `3.12` installed.
|
It is recommended to use the latest version of UV. Make sure you have Python version `3.12` installed.
|
||||||
|
|
||||||
### Install Python 3.12
|
### Install Python 3.12
|
||||||
|
|
||||||
## Option 2 (recommended): Poetry
|
## Option 2 (recommended): UV
|
||||||
|
|
||||||
It is recommended to use the latest version of Poetry. Make sure you have Python version 3.9 or higher installed.
|
It is recommended to use the latest version of UV. Make sure you have Python version 3.10 or higher installed.
|
||||||
|
|
||||||
### Verify Python version
|
### Verify Python version
|
||||||
|
|
||||||
|
|
@ -39,11 +39,19 @@ It is recommended to use the latest version of Poetry. Make sure you have Python
|
||||||
python3 --version
|
python3 --version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Poetry
|
### Install UV
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
### (old) Install Poetry
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# If path 'export PATH="$HOME/.local/bin:$PATH"' fails, use the path echoed by the install
|
# If path 'export PATH="$HOME/.local/bin:$PATH"' fails, use the path echoed by the install
|
||||||
curl -sSL https://install.python-poetry.org | python3 - && export PATH="$HOME/.local/bin:$PATH"
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
```
|
```
|
||||||
|
|
||||||
### install LNbits
|
### install LNbits
|
||||||
|
|
@ -51,9 +59,13 @@ curl -sSL https://install.python-poetry.org | python3 - && export PATH="$HOME/.l
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/lnbits/lnbits.git
|
git clone https://github.com/lnbits/lnbits.git
|
||||||
cd lnbits
|
cd lnbits
|
||||||
poetry env use 3.12
|
|
||||||
git checkout main
|
git checkout main
|
||||||
poetry install --only main
|
uv sync --all-extras
|
||||||
|
|
||||||
|
# or poetry
|
||||||
|
# poetry env use 3.12
|
||||||
|
# poetry install --only main
|
||||||
|
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Optional: to set funding source amongst other options via the env `nano .env`
|
# Optional: to set funding source amongst other options via the env `nano .env`
|
||||||
```
|
```
|
||||||
|
|
@ -61,8 +73,11 @@ cp .env.example .env
|
||||||
#### Running the server
|
#### Running the server
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
poetry run lnbits
|
uv run lnbits
|
||||||
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
|
# To change port/host pass 'uv run lnbits --port 9000 --host 0.0.0.0'
|
||||||
|
|
||||||
|
# or poetry
|
||||||
|
# poetry run lnbits
|
||||||
# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output
|
# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output
|
||||||
# Note that you have to add the line DEBUG=true in your .env file, too.
|
# Note that you have to add the line DEBUG=true in your .env file, too.
|
||||||
```
|
```
|
||||||
|
|
@ -71,7 +86,7 @@ poetry run lnbits
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# A very useful terminal client for getting the supersuer ID, updating extensions, etc
|
# A very useful terminal client for getting the supersuer ID, updating extensions, etc
|
||||||
poetry run lnbits-cli --help
|
uv run lnbits-cli --help
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Updating the server
|
#### Updating the server
|
||||||
|
|
@ -85,15 +100,18 @@ cd lnbits
|
||||||
git pull --rebase
|
git pull --rebase
|
||||||
|
|
||||||
# Check your poetry version with
|
# Check your poetry version with
|
||||||
poetry env list
|
# poetry env list
|
||||||
# If version is less 3.12, update it by running
|
# If version is less 3.12, update it by running
|
||||||
poetry env use python3.12
|
# poetry env use python3.12
|
||||||
poetry env remove python3.9
|
# poetry env remove python3.9
|
||||||
poetry env list
|
# poetry env list
|
||||||
|
|
||||||
# Run install and start LNbits with
|
# Run install and start LNbits with
|
||||||
poetry install --only main
|
# poetry install --only main
|
||||||
poetry run lnbits
|
# poetry run lnbits
|
||||||
|
|
||||||
|
uv sync --all-extras
|
||||||
|
uv run lnbits
|
||||||
|
|
||||||
# use LNbits admin UI Extensions page function "Update All" do get extensions onto proper level
|
# use LNbits admin UI Extensions page function "Update All" do get extensions onto proper level
|
||||||
```
|
```
|
||||||
|
|
@ -108,13 +126,13 @@ chmod +x lnbits.sh &&
|
||||||
|
|
||||||
Now visit `0.0.0.0:5000` to make a super-user account.
|
Now visit `0.0.0.0:5000` to make a super-user account.
|
||||||
|
|
||||||
`./lnbits.sh` can be used to run, but for more control `cd lnbits` and use `poetry run lnbits` (see previous option).
|
`./lnbits.sh` can be used to run, but for more control `cd lnbits` and use `uv run lnbits` (see previous option).
|
||||||
|
|
||||||
## Option 3: Nix
|
## Option 3: Nix
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Install nix. If you have installed via another manager, remove and use this install (from https://nixos.org/download)
|
# Install nix. If you have installed via another manager, remove and use this install (from https://nixos.org/download)
|
||||||
sh <(curl -L https://nixos.org/nix/install) --daemon
|
sh <(c&url -L https://nixos.org/nix/install) --daemon
|
||||||
|
|
||||||
# Enable nix-command and flakes experimental features for nix:
|
# Enable nix-command and flakes experimental features for nix:
|
||||||
echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf
|
echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf
|
||||||
|
|
@ -332,7 +350,7 @@ LNBITS_DATABASE_URL="postgres://postgres:postgres@localhost/lnbits"
|
||||||
|
|
||||||
# START LNbits
|
# START LNbits
|
||||||
# STOP LNbits
|
# STOP LNbits
|
||||||
poetry run python tools/conv.py
|
uv run python tools/conv.py
|
||||||
# or
|
# or
|
||||||
make migration
|
make migration
|
||||||
```
|
```
|
||||||
|
|
@ -357,8 +375,8 @@ Description=LNbits
|
||||||
[Service]
|
[Service]
|
||||||
# replace with the absolute path of your lnbits installation
|
# replace with the absolute path of your lnbits installation
|
||||||
WorkingDirectory=/home/lnbits/lnbits
|
WorkingDirectory=/home/lnbits/lnbits
|
||||||
# same here. run `which poetry` if you can't find the poetry binary
|
# same here. run `which uv` if you can't find the poetry binary
|
||||||
ExecStart=/home/lnbits/.local/bin/poetry run lnbits
|
ExecStart=/home/lnbits/.local/bin/uv run lnbits
|
||||||
# replace with the user that you're running lnbits on
|
# replace with the user that you're running lnbits on
|
||||||
User=lnbits
|
User=lnbits
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ You can also use an AES-encrypted macaroon (more info) instead by using
|
||||||
|
|
||||||
- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
||||||
|
|
||||||
To encrypt your macaroon, run `poetry run lnbits-cli encrypt macaroon`.
|
To encrypt your macaroon, run `uv run lnbits-cli encrypt macaroon`.
|
||||||
|
|
||||||
### LNbits
|
### LNbits
|
||||||
|
|
||||||
|
|
|
||||||
149
flake.lock
generated
149
flake.lock
generated
|
|
@ -1,15 +1,39 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"build-system-pkgs": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pyproject-nix": [
|
||||||
|
"pyproject-nix"
|
||||||
|
],
|
||||||
|
"uv2nix": "uv2nix"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1755484659,
|
||||||
|
"narHash": "sha256-2FfbqsaHVQd12XFFUAinIMAuGO3853LONmva1gT3vKw=",
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "build-system-pkgs",
|
||||||
|
"rev": "9778e87c2361810ff15e287ca5895c9da4a0e900",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "build-system-pkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -18,71 +42,49 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-github-actions": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"poetry2nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1703863825,
|
|
||||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nix-github-actions",
|
|
||||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nix-github-actions",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1735563628,
|
"lastModified": 1751274312,
|
||||||
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
|
"narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=",
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
|
"rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-24.05",
|
"ref": "nixos-24.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"poetry2nix": {
|
"pyproject-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nix-github-actions": "nix-github-actions",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
]
|
||||||
"systems": "systems_2",
|
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724134185,
|
"lastModified": 1754923840,
|
||||||
"narHash": "sha256-nDqpGjz7cq3ThdC98BPe1ANCNlsJds/LLZ3/MdIXjA0=",
|
"narHash": "sha256-QSKpYg+Ts9HYF155ltlj40iBex39c05cpOF8gjoE2EM=",
|
||||||
"owner": "nix-community",
|
"owner": "pyproject-nix",
|
||||||
"repo": "poetry2nix",
|
"repo": "pyproject.nix",
|
||||||
"rev": "5ee730a8752264e463c0eaf06cc060fd07f6dae9",
|
"rev": "023cd4be230eacae52635be09eef100c37ef78da",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "pyproject-nix",
|
||||||
"repo": "poetry2nix",
|
"repo": "pyproject.nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"build-system-pkgs": "build-system-pkgs",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"poetry2nix": "poetry2nix"
|
"pyproject-nix": "pyproject-nix",
|
||||||
|
"uv2nix": "uv2nix_2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
|
|
@ -100,38 +102,51 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems_2": {
|
"uv2nix": {
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "systems",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"treefmt-nix": {
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"poetry2nix",
|
"build-system-pkgs",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pyproject-nix": [
|
||||||
|
"build-system-pkgs",
|
||||||
|
"pyproject-nix"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1719749022,
|
"lastModified": 1755210905,
|
||||||
"narHash": "sha256-ddPKHcqaKCIFSFc/cvxS14goUhCOAwsM1PbMr0ZtHMg=",
|
"narHash": "sha256-WnoFEk79ysjL85TNP7bvImzhxvQw9B6uNtnLd4oJntw=",
|
||||||
"owner": "numtide",
|
"owner": "pyproject-nix",
|
||||||
"repo": "treefmt-nix",
|
"repo": "uv2nix",
|
||||||
"rev": "8df5ff62195d4e67e2264df0b7f5e8c9995fd0bd",
|
"rev": "87bcba013ef304bbfd67c8e8a257aee634ed5a4c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "pyproject-nix",
|
||||||
"repo": "treefmt-nix",
|
"repo": "uv2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uv2nix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pyproject-nix": [
|
||||||
|
"pyproject-nix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1755485731,
|
||||||
|
"narHash": "sha256-k8kxwVs8Oze6q/jAaRa3RvZbb50I/K0b5uptlsh0HXI=",
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "uv2nix",
|
||||||
|
"rev": "bebbd80bf56110fcd20b425589814af28f1939eb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "uv2nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
195
flake.nix
195
flake.nix
|
|
@ -1,70 +1,149 @@
|
||||||
{
|
{
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system";
|
description = "LNbits, free and open-source Lightning wallet and accounts system (uv2nix)";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||||
poetry2nix = {
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
url = "github:nix-community/poetry2nix";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
pyproject-nix.url = "github:pyproject-nix/pyproject.nix";
|
||||||
};
|
uv2nix.url = "github:pyproject-nix/uv2nix";
|
||||||
|
build-system-pkgs.url = "github:pyproject-nix/build-system-pkgs";
|
||||||
|
|
||||||
|
pyproject-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
uv2nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
build-system-pkgs.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
uv2nix.inputs.pyproject-nix.follows = "pyproject-nix";
|
||||||
|
build-system-pkgs.inputs.pyproject-nix.follows = "pyproject-nix";
|
||||||
};
|
};
|
||||||
outputs = { self, nixpkgs, poetry2nix }@inputs:
|
|
||||||
let
|
outputs = { self, nixpkgs, flake-utils, uv2nix, pyproject-nix, build-system-pkgs, ... }:
|
||||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]
|
||||||
forSystems = systems: f:
|
(system:
|
||||||
nixpkgs.lib.genAttrs systems
|
let
|
||||||
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlays.default self.overlays.default ]; }));
|
pkgs = import nixpkgs { inherit system; };
|
||||||
forAllSystems = forSystems supportedSystems;
|
lib = pkgs.lib;
|
||||||
projectName = "lnbits";
|
|
||||||
in
|
python = pkgs.python312;
|
||||||
{
|
|
||||||
overlays = {
|
# Read uv.lock / pyproject via uv2nix
|
||||||
default = final: prev: {
|
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
|
||||||
${projectName} = self.packages.${prev.stdenv.hostPlatform.system}.${projectName};
|
|
||||||
};
|
# Prefer wheels when available
|
||||||
};
|
uvLockedOverlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; };
|
||||||
packages = forAllSystems (system: pkgs: {
|
|
||||||
default = self.packages.${system}.${projectName};
|
# Helper for extending lists safely (works if a is null)
|
||||||
${projectName} = pkgs.poetry2nix.mkPoetryApplication {
|
plus = a: b: lib.unique (((if a == null then [] else a)) ++ b);
|
||||||
projectDir = ./.;
|
|
||||||
meta.rev = self.dirtyRev or self.rev;
|
# Extra build inputs for troublesome sdists
|
||||||
meta.mainProgram = projectName;
|
myOverrides = (final: prev: {
|
||||||
overrides = pkgs.poetry2nix.overrides.withDefaults (final: prev: {
|
# embit needs setuptools at build time
|
||||||
coincurve = prev.coincurve.override { preferWheel = true; };
|
embit = prev.embit.overrideAttrs (old: {
|
||||||
protobuf = prev.protobuf.override { preferWheel = true; };
|
nativeBuildInputs = plus (old.nativeBuildInputs or []) [ prev.setuptools ];
|
||||||
ruff = prev.ruff.override { preferWheel = true; };
|
|
||||||
wallycore = prev.wallycore.override { preferWheel = true; };
|
|
||||||
tlv8 = prev.tlv8.overrideAttrs (old: {
|
|
||||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
|
|
||||||
prev.setuptools
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
pynostr = prev.pynostr.overrideAttrs (old: {
|
|
||||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
|
# http-ece (pywebpush dep) needs setuptools
|
||||||
prev.setuptools-scm
|
"http-ece" = prev."http-ece".overrideAttrs (old: {
|
||||||
|
nativeBuildInputs = plus (old.nativeBuildInputs or []) [ prev.setuptools ];
|
||||||
|
});
|
||||||
|
|
||||||
|
# pyqrcode needs setuptools
|
||||||
|
pyqrcode = prev.pyqrcode.overrideAttrs (old: {
|
||||||
|
nativeBuildInputs = plus (old.nativeBuildInputs or []) [ prev.setuptools ];
|
||||||
|
});
|
||||||
|
|
||||||
|
# tlv8 needs setuptools
|
||||||
|
tlv8 = prev.tlv8.overrideAttrs (old: {
|
||||||
|
nativeBuildInputs = plus (old.nativeBuildInputs or []) [ prev.setuptools ];
|
||||||
|
});
|
||||||
|
|
||||||
|
# secp256k1 Python binding:
|
||||||
|
# - setuptools, pkg-config
|
||||||
|
# - cffi + pycparser
|
||||||
|
# - system libsecp256k1 for headers/libs
|
||||||
|
secp256k1 = prev.secp256k1.overrideAttrs (old: {
|
||||||
|
nativeBuildInputs = plus (old.nativeBuildInputs or []) [
|
||||||
|
prev.setuptools
|
||||||
|
pkgs.pkg-config
|
||||||
|
prev.cffi
|
||||||
|
prev.pycparser
|
||||||
];
|
];
|
||||||
|
buildInputs = plus (old.buildInputs or []) [ pkgs.secp256k1 ];
|
||||||
|
propagatedBuildInputs = plus (old.propagatedBuildInputs or []) [ prev.cffi prev.pycparser ];
|
||||||
|
env = (old.env or { }) // { PKG_CONFIG = "${pkgs.pkg-config}/bin/pkg-config"; };
|
||||||
|
});
|
||||||
|
|
||||||
|
# pynostr uses setuptools-scm for versioning
|
||||||
|
pynostr = prev.pynostr.overrideAttrs (old: {
|
||||||
|
nativeBuildInputs = plus (old.nativeBuildInputs or []) [ prev.setuptools-scm ];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
|
||||||
});
|
# Compose Python package set honoring uv.lock
|
||||||
nixosModules = {
|
pythonSet =
|
||||||
default = { pkgs, lib, config, ... }: {
|
(pkgs.callPackage pyproject-nix.build.packages { inherit python; })
|
||||||
imports = [ "${./nix/modules/${projectName}-service.nix}" ];
|
.overrideScope (lib.composeManyExtensions [
|
||||||
nixpkgs.overlays = [ self.overlays.default ];
|
build-system-pkgs.overlays.default
|
||||||
};
|
uvLockedOverlay
|
||||||
};
|
myOverrides
|
||||||
checks = forAllSystems (system: pkgs:
|
]);
|
||||||
let
|
|
||||||
vmTests = import ./nix/tests {
|
projectName = "lnbits";
|
||||||
makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest;
|
|
||||||
inherit inputs pkgs;
|
# Build a venv from the locked spec (this installs the resolved wheels)
|
||||||
|
runtimeVenv = pythonSet.mkVirtualEnv "${projectName}-env" workspace.deps.default;
|
||||||
|
|
||||||
|
# Wrapper so `nix run` behaves like `uv run` (use local source tree for templates/static/extensions)
|
||||||
|
lnbitsApp = pkgs.writeShellApplication {
|
||||||
|
name = "lnbits";
|
||||||
|
text = ''
|
||||||
|
export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
|
||||||
|
export REQUESTS_CA_BUNDLE=$SSL_CERT_FILE
|
||||||
|
export PYTHONPATH="$PWD:${PYTHONPATH:-}"
|
||||||
|
exec ${runtimeVenv}/bin/lnbits "$@"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
lnbitsCliApp = pkgs.writeShellApplication {
|
||||||
|
name = "lnbits-cli";
|
||||||
|
text = ''
|
||||||
|
export PYTHONPATH="$PWD:${PYTHONPATH:-}"
|
||||||
|
exec ${runtimeVenv}/bin/lnbits-cli "$@"
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.lib.optionalAttrs pkgs.stdenv.isLinux vmTests # vmTests can only be ran on Linux, so append them only if on Linux.
|
|
||||||
//
|
|
||||||
{
|
{
|
||||||
# Other checks here...
|
# nix build → produces the venv in ./result
|
||||||
}
|
packages.default = runtimeVenv;
|
||||||
);
|
packages.${projectName} = runtimeVenv;
|
||||||
};
|
|
||||||
|
# nix run . → launches via wrapper that imports from source tree
|
||||||
|
apps.default = { type = "app"; program = "${lnbitsApp}/bin/lnbits"; };
|
||||||
|
apps.${projectName} = self.apps.${system}.default;
|
||||||
|
apps."${projectName}-cli" = { type = "app"; program = "${lnbitsCliApp}/bin/lnbits-cli"; };
|
||||||
|
|
||||||
|
# dev shell with locked deps + tools
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
runtimeVenv
|
||||||
|
pkgs.uv
|
||||||
|
pkgs.ruff
|
||||||
|
pkgs.black
|
||||||
|
pkgs.mypy
|
||||||
|
pkgs.pre-commit
|
||||||
|
pkgs.openapi-generator-cli
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
overlays.default = final: prev: {
|
||||||
|
${projectName} = self.packages.${final.stdenv.hostPlatform.system}.${projectName};
|
||||||
|
replaceVars = prev.replaceVars or (path: vars: prev.substituteAll ({ src = path; } // vars));
|
||||||
|
};
|
||||||
|
|
||||||
|
nixosModules.default = { pkgs, lib, config, ... }: {
|
||||||
|
imports = [ "${./nix/modules/lnbits-service.nix}" ];
|
||||||
|
nixpkgs.overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = { };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
lnbits.sh
13
lnbits.sh
|
|
@ -13,10 +13,9 @@ if [ ! -d lnbits/data ]; then
|
||||||
# Install Python 3.10 and distutils non-interactively
|
# Install Python 3.10 and distutils non-interactively
|
||||||
sudo apt install -y python3.10 python3.10-distutils
|
sudo apt install -y python3.10 python3.10-distutils
|
||||||
|
|
||||||
# Install Poetry
|
# Install UV
|
||||||
curl -sSL https://install.python-poetry.org | python3.10 -
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
|
||||||
# Add Poetry to PATH for the current session
|
|
||||||
export PATH="/home/$USER/.local/bin:$PATH"
|
export PATH="/home/$USER/.local/bin:$PATH"
|
||||||
|
|
||||||
if [ ! -d lnbits/wallets ]; then
|
if [ ! -d lnbits/wallets ]; then
|
||||||
|
|
@ -42,13 +41,13 @@ elif [ ! -d lnbits/wallets ]; then
|
||||||
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
|
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install the dependencies using Poetry
|
# Install the dependencies using UV
|
||||||
poetry env use python3.9
|
uv sync --all-extras
|
||||||
poetry install --only main
|
|
||||||
|
|
||||||
# Set environment variables for LNbits
|
# Set environment variables for LNbits
|
||||||
export LNBITS_ADMIN_UI=true
|
export LNBITS_ADMIN_UI=true
|
||||||
export HOST=0.0.0.0
|
export HOST=0.0.0.0
|
||||||
|
|
||||||
# Run LNbits
|
# Run LNbits
|
||||||
poetry run lnbits
|
uv run lnbits
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Optional
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
@ -280,7 +280,7 @@ async def check_installed_extensions(app: FastAPI):
|
||||||
|
|
||||||
|
|
||||||
async def build_all_installed_extensions_list( # noqa: C901
|
async def build_all_installed_extensions_list( # noqa: C901
|
||||||
include_deactivated: Optional[bool] = True,
|
include_deactivated: bool | None = True,
|
||||||
) -> list[InstallableExtension]:
|
) -> list[InstallableExtension]:
|
||||||
"""
|
"""
|
||||||
Returns a list of all the installed extensions plus the extensions that
|
Returns a list of all the installed extensions plus the extensions that
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import time
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
@ -96,7 +95,7 @@ def decrypt():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_super_user() -> Optional[str]:
|
def get_super_user() -> str | None:
|
||||||
"""Get the superuser"""
|
"""Get the superuser"""
|
||||||
superuser_file = Path(settings.lnbits_data_folder, ".super_user")
|
superuser_file = Path(settings.lnbits_data_folder, ".super_user")
|
||||||
if not superuser_file.exists() or not superuser_file.is_file():
|
if not superuser_file.exists() or not superuser_file.is_file():
|
||||||
|
|
@ -155,7 +154,7 @@ async def db_versions():
|
||||||
@db.command("cleanup-wallets")
|
@db.command("cleanup-wallets")
|
||||||
@click.argument("days", type=int, required=False)
|
@click.argument("days", type=int, required=False)
|
||||||
@coro
|
@coro
|
||||||
async def database_cleanup_wallets(days: Optional[int] = None):
|
async def database_cleanup_wallets(days: int | None = None):
|
||||||
"""Delete all wallets that never had any transaction"""
|
"""Delete all wallets that never had any transaction"""
|
||||||
async with core_db.connect() as conn:
|
async with core_db.connect() as conn:
|
||||||
delta = days or settings.cleanup_wallets_days
|
delta = days or settings.cleanup_wallets_days
|
||||||
|
|
@ -212,10 +211,10 @@ async def database_revert_payment(checking_id: str):
|
||||||
@click.option("-v", "--verbose", is_flag=True, help="Detailed log.")
|
@click.option("-v", "--verbose", is_flag=True, help="Detailed log.")
|
||||||
@coro
|
@coro
|
||||||
async def check_invalid_payments(
|
async def check_invalid_payments(
|
||||||
days: Optional[int] = None,
|
days: int | None = None,
|
||||||
limit: Optional[int] = None,
|
limit: int | None = None,
|
||||||
wallet: Optional[str] = None,
|
wallet: str | None = None,
|
||||||
verbose: Optional[bool] = False,
|
verbose: bool | None = False,
|
||||||
):
|
):
|
||||||
"""Check payments that are settled in the DB but pending on the Funding Source"""
|
"""Check payments that are settled in the DB but pending on the Funding Source"""
|
||||||
await check_admin_settings()
|
await check_admin_settings()
|
||||||
|
|
@ -303,7 +302,7 @@ async def create_user(username: str, password: str):
|
||||||
@users.command("cleanup-accounts")
|
@users.command("cleanup-accounts")
|
||||||
@click.argument("days", type=int, required=False)
|
@click.argument("days", type=int, required=False)
|
||||||
@coro
|
@coro
|
||||||
async def database_cleanup_accounts(days: Optional[int] = None):
|
async def database_cleanup_accounts(days: int | None = None):
|
||||||
"""Delete all accounts that have no wallets"""
|
"""Delete all accounts that have no wallets"""
|
||||||
async with core_db.connect() as conn:
|
async with core_db.connect() as conn:
|
||||||
delta = days or settings.cleanup_wallets_days
|
delta = days or settings.cleanup_wallets_days
|
||||||
|
|
@ -353,12 +352,12 @@ async def extensions_list():
|
||||||
)
|
)
|
||||||
@coro
|
@coro
|
||||||
async def extensions_update( # noqa: C901
|
async def extensions_update( # noqa: C901
|
||||||
extension: Optional[str] = None,
|
extension: str | None = None,
|
||||||
all_extensions: Optional[bool] = False,
|
all_extensions: bool | None = False,
|
||||||
repo_index: Optional[str] = None,
|
repo_index: str | None = None,
|
||||||
source_repo: Optional[str] = None,
|
source_repo: str | None = None,
|
||||||
url: Optional[str] = None,
|
url: str | None = None,
|
||||||
admin_user: Optional[str] = None,
|
admin_user: str | None = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Update extension to the latest version.
|
Update extension to the latest version.
|
||||||
|
|
@ -443,10 +442,10 @@ async def extensions_update( # noqa: C901
|
||||||
@coro
|
@coro
|
||||||
async def extensions_install(
|
async def extensions_install(
|
||||||
extension: str,
|
extension: str,
|
||||||
repo_index: Optional[str] = None,
|
repo_index: str | None = None,
|
||||||
source_repo: Optional[str] = None,
|
source_repo: str | None = None,
|
||||||
url: Optional[str] = None,
|
url: str | None = None,
|
||||||
admin_user: Optional[str] = None,
|
admin_user: str | None = None,
|
||||||
):
|
):
|
||||||
"""Install a extension"""
|
"""Install a extension"""
|
||||||
click.echo(f"Installing {extension}... {repo_index}")
|
click.echo(f"Installing {extension}... {repo_index}")
|
||||||
|
|
@ -473,7 +472,7 @@ async def extensions_install(
|
||||||
)
|
)
|
||||||
@coro
|
@coro
|
||||||
async def extensions_uninstall(
|
async def extensions_uninstall(
|
||||||
extension: str, url: Optional[str] = None, admin_user: Optional[str] = None
|
extension: str, url: str | None = None, admin_user: str | None = None
|
||||||
):
|
):
|
||||||
"""Uninstall a extension"""
|
"""Uninstall a extension"""
|
||||||
click.echo(f"Uninstalling '{extension}'...")
|
click.echo(f"Uninstalling '{extension}'...")
|
||||||
|
|
@ -562,10 +561,10 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
async def install_extension(
|
async def install_extension(
|
||||||
extension: str,
|
extension: str,
|
||||||
repo_index: Optional[str] = None,
|
repo_index: str | None = None,
|
||||||
source_repo: Optional[str] = None,
|
source_repo: str | None = None,
|
||||||
url: Optional[str] = None,
|
url: str | None = None,
|
||||||
admin_user: Optional[str] = None,
|
admin_user: str | None = None,
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
release = await _select_release(extension, repo_index, source_repo)
|
release = await _select_release(extension, repo_index, source_repo)
|
||||||
|
|
@ -591,10 +590,10 @@ async def install_extension(
|
||||||
|
|
||||||
async def update_extension(
|
async def update_extension(
|
||||||
extension: str,
|
extension: str,
|
||||||
repo_index: Optional[str] = None,
|
repo_index: str | None = None,
|
||||||
source_repo: Optional[str] = None,
|
source_repo: str | None = None,
|
||||||
url: Optional[str] = None,
|
url: str | None = None,
|
||||||
admin_user: Optional[str] = None,
|
admin_user: str | None = None,
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
click.echo(f"Updating '{extension}' extension.")
|
click.echo(f"Updating '{extension}' extension.")
|
||||||
|
|
@ -644,9 +643,9 @@ async def update_extension(
|
||||||
|
|
||||||
async def _select_release(
|
async def _select_release(
|
||||||
extension: str,
|
extension: str,
|
||||||
repo_index: Optional[str] = None,
|
repo_index: str | None = None,
|
||||||
source_repo: Optional[str] = None,
|
source_repo: str | None = None,
|
||||||
) -> Optional[ExtensionRelease]:
|
) -> ExtensionRelease | None:
|
||||||
all_releases = await InstallableExtension.get_extension_releases(extension)
|
all_releases = await InstallableExtension.get_extension_releases(extension)
|
||||||
if len(all_releases) == 0:
|
if len(all_releases) == 0:
|
||||||
click.echo(f"No repository found for extension '{extension}'.")
|
click.echo(f"No repository found for extension '{extension}'.")
|
||||||
|
|
@ -706,7 +705,7 @@ def _get_latest_release_per_repo(all_releases):
|
||||||
|
|
||||||
|
|
||||||
async def _call_install_extension(
|
async def _call_install_extension(
|
||||||
data: CreateExtension, url: Optional[str], user_id: Optional[str] = None
|
data: CreateExtension, url: str | None, user_id: str | None = None
|
||||||
):
|
):
|
||||||
if url:
|
if url:
|
||||||
user_id = user_id or get_super_user()
|
user_id = user_id or get_super_user()
|
||||||
|
|
@ -720,7 +719,7 @@ async def _call_install_extension(
|
||||||
|
|
||||||
|
|
||||||
async def _call_uninstall_extension(
|
async def _call_uninstall_extension(
|
||||||
extension: str, url: Optional[str], user_id: Optional[str] = None
|
extension: str, url: str | None, user_id: str | None = None
|
||||||
):
|
):
|
||||||
if url:
|
if url:
|
||||||
user_id = user_id or get_super_user()
|
user_id = user_id or get_super_user()
|
||||||
|
|
@ -756,7 +755,7 @@ async def _can_run_operation(url) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def _is_lnbits_started(url: Optional[str]):
|
async def _is_lnbits_started(url: str | None):
|
||||||
try:
|
try:
|
||||||
url = url or f"http://{settings.host}:{settings.port}/api/v1/health"
|
url = url or f"http://{settings.host}:{settings.port}/api/v1/health"
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
from lnbits.core.models import AuditEntry, AuditFilters
|
from lnbits.core.models import AuditEntry, AuditFilters
|
||||||
from lnbits.core.models.audit import AuditCountStat
|
from lnbits.core.models.audit import AuditCountStat
|
||||||
|
|
@ -8,14 +6,14 @@ from lnbits.db import Connection, Filters, Page
|
||||||
|
|
||||||
async def create_audit_entry(
|
async def create_audit_entry(
|
||||||
entry: AuditEntry,
|
entry: AuditEntry,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).insert("audit", entry)
|
await (conn or db).insert("audit", entry)
|
||||||
|
|
||||||
|
|
||||||
async def get_audit_entries(
|
async def get_audit_entries(
|
||||||
filters: Optional[Filters[AuditFilters]] = None,
|
filters: Filters[AuditFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Page[AuditEntry]:
|
) -> Page[AuditEntry]:
|
||||||
return await (conn or db).fetch_page(
|
return await (conn or db).fetch_page(
|
||||||
"SELECT * from audit",
|
"SELECT * from audit",
|
||||||
|
|
@ -27,7 +25,7 @@ async def get_audit_entries(
|
||||||
|
|
||||||
|
|
||||||
async def delete_expired_audit_entries(
|
async def delete_expired_audit_entries(
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
):
|
):
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
# Timestamp placeholder is safe from SQL injection (not user input)
|
# Timestamp placeholder is safe from SQL injection (not user input)
|
||||||
|
|
@ -40,8 +38,8 @@ async def delete_expired_audit_entries(
|
||||||
|
|
||||||
async def get_count_stats(
|
async def get_count_stats(
|
||||||
field: str,
|
field: str,
|
||||||
filters: Optional[Filters[AuditFilters]] = None,
|
filters: Filters[AuditFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> list[AuditCountStat]:
|
) -> list[AuditCountStat]:
|
||||||
if field not in ["request_method", "component", "response_code"]:
|
if field not in ["request_method", "component", "response_code"]:
|
||||||
return []
|
return []
|
||||||
|
|
@ -67,8 +65,8 @@ async def get_count_stats(
|
||||||
|
|
||||||
|
|
||||||
async def get_long_duration_stats(
|
async def get_long_duration_stats(
|
||||||
filters: Optional[Filters[AuditFilters]] = None,
|
filters: Filters[AuditFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> list[AuditCountStat]:
|
) -> list[AuditCountStat]:
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = Filters()
|
filters = Filters()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
from lnbits.db import Connection
|
from lnbits.db import Connection
|
||||||
|
|
||||||
|
|
@ -7,8 +5,8 @@ from ..models import DbVersion
|
||||||
|
|
||||||
|
|
||||||
async def get_db_version(
|
async def get_db_version(
|
||||||
ext_id: str, conn: Optional[Connection] = None
|
ext_id: str, conn: Connection | None = None
|
||||||
) -> Optional[DbVersion]:
|
) -> DbVersion | None:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM dbversions WHERE db = :ext_id",
|
"SELECT * FROM dbversions WHERE db = :ext_id",
|
||||||
{"ext_id": ext_id},
|
{"ext_id": ext_id},
|
||||||
|
|
@ -16,7 +14,7 @@ async def get_db_version(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_db_versions(conn: Optional[Connection] = None) -> list[DbVersion]:
|
async def get_db_versions(conn: Connection | None = None) -> list[DbVersion]:
|
||||||
return await (conn or db).fetchall("SELECT * FROM dbversions", model=DbVersion)
|
return await (conn or db).fetchall("SELECT * FROM dbversions", model=DbVersion)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,7 +28,7 @@ async def update_migration_version(conn, db_name, version):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def delete_dbversion(*, ext_id: str, conn: Optional[Connection] = None) -> None:
|
async def delete_dbversion(*, ext_id: str, conn: Connection | None = None) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
DELETE FROM dbversions WHERE db = :ext
|
DELETE FROM dbversions WHERE db = :ext
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
from lnbits.core.models.extensions import (
|
from lnbits.core.models.extensions import (
|
||||||
InstallableExtension,
|
InstallableExtension,
|
||||||
|
|
@ -10,20 +8,20 @@ from lnbits.db import Connection, Database
|
||||||
|
|
||||||
async def create_installed_extension(
|
async def create_installed_extension(
|
||||||
ext: InstallableExtension,
|
ext: InstallableExtension,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).insert("installed_extensions", ext)
|
await (conn or db).insert("installed_extensions", ext)
|
||||||
|
|
||||||
|
|
||||||
async def update_installed_extension(
|
async def update_installed_extension(
|
||||||
ext: InstallableExtension,
|
ext: InstallableExtension,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).update("installed_extensions", ext)
|
await (conn or db).update("installed_extensions", ext)
|
||||||
|
|
||||||
|
|
||||||
async def update_installed_extension_state(
|
async def update_installed_extension_state(
|
||||||
*, ext_id: str, active: bool, conn: Optional[Connection] = None
|
*, ext_id: str, active: bool, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -34,7 +32,7 @@ async def update_installed_extension_state(
|
||||||
|
|
||||||
|
|
||||||
async def delete_installed_extension(
|
async def delete_installed_extension(
|
||||||
*, ext_id: str, conn: Optional[Connection] = None
|
*, ext_id: str, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -44,7 +42,7 @@ async def delete_installed_extension(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def drop_extension_db(ext_id: str, conn: Optional[Connection] = None) -> None:
|
async def drop_extension_db(ext_id: str, conn: Connection | None = None) -> None:
|
||||||
row: dict = await (conn or db).fetchone(
|
row: dict = await (conn or db).fetchone(
|
||||||
"SELECT * FROM dbversions WHERE db = :id",
|
"SELECT * FROM dbversions WHERE db = :id",
|
||||||
{"id": ext_id},
|
{"id": ext_id},
|
||||||
|
|
@ -65,8 +63,8 @@ async def drop_extension_db(ext_id: str, conn: Optional[Connection] = None) -> N
|
||||||
|
|
||||||
|
|
||||||
async def get_installed_extension(
|
async def get_installed_extension(
|
||||||
ext_id: str, conn: Optional[Connection] = None
|
ext_id: str, conn: Connection | None = None
|
||||||
) -> Optional[InstallableExtension]:
|
) -> InstallableExtension | None:
|
||||||
extension = await (conn or db).fetchone(
|
extension = await (conn or db).fetchone(
|
||||||
"SELECT * FROM installed_extensions WHERE id = :id",
|
"SELECT * FROM installed_extensions WHERE id = :id",
|
||||||
{"id": ext_id},
|
{"id": ext_id},
|
||||||
|
|
@ -76,8 +74,8 @@ async def get_installed_extension(
|
||||||
|
|
||||||
|
|
||||||
async def get_installed_extensions(
|
async def get_installed_extensions(
|
||||||
active: Optional[bool] = None,
|
active: bool | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> list[InstallableExtension]:
|
) -> list[InstallableExtension]:
|
||||||
query = "SELECT * FROM installed_extensions"
|
query = "SELECT * FROM installed_extensions"
|
||||||
if active is not None:
|
if active is not None:
|
||||||
|
|
@ -93,8 +91,8 @@ async def get_installed_extensions(
|
||||||
|
|
||||||
|
|
||||||
async def get_user_extension(
|
async def get_user_extension(
|
||||||
user_id: str, extension: str, conn: Optional[Connection] = None
|
user_id: str, extension: str, conn: Connection | None = None
|
||||||
) -> Optional[UserExtension]:
|
) -> UserExtension | None:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM extensions
|
SELECT * FROM extensions
|
||||||
|
|
@ -106,7 +104,7 @@ async def get_user_extension(
|
||||||
|
|
||||||
|
|
||||||
async def get_user_extensions(
|
async def get_user_extensions(
|
||||||
user_id: str, conn: Optional[Connection] = None
|
user_id: str, conn: Connection | None = None
|
||||||
) -> list[UserExtension]:
|
) -> list[UserExtension]:
|
||||||
return await (conn or db).fetchall(
|
return await (conn or db).fetchall(
|
||||||
"""SELECT * FROM extensions WHERE "user" = :user""",
|
"""SELECT * FROM extensions WHERE "user" = :user""",
|
||||||
|
|
@ -116,20 +114,20 @@ async def get_user_extensions(
|
||||||
|
|
||||||
|
|
||||||
async def create_user_extension(
|
async def create_user_extension(
|
||||||
user_extension: UserExtension, conn: Optional[Connection] = None
|
user_extension: UserExtension, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).insert("extensions", user_extension)
|
await (conn or db).insert("extensions", user_extension)
|
||||||
|
|
||||||
|
|
||||||
async def update_user_extension(
|
async def update_user_extension(
|
||||||
user_extension: UserExtension, conn: Optional[Connection] = None
|
user_extension: UserExtension, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
where = """WHERE extension = :extension AND "user" = :user"""
|
where = """WHERE extension = :extension AND "user" = :user"""
|
||||||
await (conn or db).update("extensions", user_extension, where)
|
await (conn or db).update("extensions", user_extension, where)
|
||||||
|
|
||||||
|
|
||||||
async def get_user_active_extensions_ids(
|
async def get_user_active_extensions_ids(
|
||||||
user_id: str, conn: Optional[Connection] = None
|
user_id: str, conn: Connection | None = None
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
exts = await (conn or db).fetchall(
|
exts = await (conn or db).fetchall(
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from lnbits.core.crud.wallets import get_total_balance, get_wallet, get_wallets_ids
|
from lnbits.core.crud.wallets import get_total_balance, get_wallet, get_wallets_ids
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
|
|
@ -23,7 +23,7 @@ def update_payment_extra():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Payment:
|
async def get_payment(checking_id: str, conn: Connection | None = None) -> Payment:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM apipayments WHERE checking_id = :checking_id",
|
"SELECT * FROM apipayments WHERE checking_id = :checking_id",
|
||||||
{"checking_id": checking_id},
|
{"checking_id": checking_id},
|
||||||
|
|
@ -33,10 +33,10 @@ async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Pa
|
||||||
|
|
||||||
async def get_standalone_payment(
|
async def get_standalone_payment(
|
||||||
checking_id_or_hash: str,
|
checking_id_or_hash: str,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
incoming: Optional[bool] = False,
|
incoming: bool | None = False,
|
||||||
wallet_id: Optional[str] = None,
|
wallet_id: str | None = None,
|
||||||
) -> Optional[Payment]:
|
) -> Payment | None:
|
||||||
clause: str = "checking_id = :checking_id OR payment_hash = :hash"
|
clause: str = "checking_id = :checking_id OR payment_hash = :hash"
|
||||||
values = {
|
values = {
|
||||||
"wallet_id": wallet_id,
|
"wallet_id": wallet_id,
|
||||||
|
|
@ -64,8 +64,8 @@ async def get_standalone_payment(
|
||||||
|
|
||||||
|
|
||||||
async def get_wallet_payment(
|
async def get_wallet_payment(
|
||||||
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
|
wallet_id: str, payment_hash: str, conn: Connection | None = None
|
||||||
) -> Optional[Payment]:
|
) -> Payment | None:
|
||||||
payment = await (conn or db).fetchone(
|
payment = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
@ -102,17 +102,17 @@ async def get_latest_payments_by_extension(
|
||||||
|
|
||||||
async def get_payments_paginated( # noqa: C901
|
async def get_payments_paginated( # noqa: C901
|
||||||
*,
|
*,
|
||||||
wallet_id: Optional[str] = None,
|
wallet_id: str | None = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: str | None = None,
|
||||||
complete: bool = False,
|
complete: bool = False,
|
||||||
pending: bool = False,
|
pending: bool = False,
|
||||||
failed: bool = False,
|
failed: bool = False,
|
||||||
outgoing: bool = False,
|
outgoing: bool = False,
|
||||||
incoming: bool = False,
|
incoming: bool = False,
|
||||||
since: Optional[int] = None,
|
since: int | None = None,
|
||||||
exclude_uncheckable: bool = False,
|
exclude_uncheckable: bool = False,
|
||||||
filters: Optional[Filters[PaymentFilters]] = None,
|
filters: Filters[PaymentFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Page[Payment]:
|
) -> Page[Payment]:
|
||||||
"""
|
"""
|
||||||
Filters payments to be returned by:
|
Filters payments to be returned by:
|
||||||
|
|
@ -176,17 +176,17 @@ async def get_payments_paginated( # noqa: C901
|
||||||
|
|
||||||
async def get_payments(
|
async def get_payments(
|
||||||
*,
|
*,
|
||||||
wallet_id: Optional[str] = None,
|
wallet_id: str | None = None,
|
||||||
complete: bool = False,
|
complete: bool = False,
|
||||||
pending: bool = False,
|
pending: bool = False,
|
||||||
outgoing: bool = False,
|
outgoing: bool = False,
|
||||||
incoming: bool = False,
|
incoming: bool = False,
|
||||||
since: Optional[int] = None,
|
since: int | None = None,
|
||||||
exclude_uncheckable: bool = False,
|
exclude_uncheckable: bool = False,
|
||||||
filters: Optional[Filters[PaymentFilters]] = None,
|
filters: Filters[PaymentFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
limit: Optional[int] = None,
|
limit: int | None = None,
|
||||||
offset: Optional[int] = None,
|
offset: int | None = None,
|
||||||
) -> list[Payment]:
|
) -> list[Payment]:
|
||||||
"""
|
"""
|
||||||
Filters payments to be returned by complete | pending | outgoing | incoming.
|
Filters payments to be returned by complete | pending | outgoing | incoming.
|
||||||
|
|
@ -230,7 +230,7 @@ async def get_payments_status_count() -> PaymentsStatusCount:
|
||||||
|
|
||||||
|
|
||||||
async def delete_expired_invoices(
|
async def delete_expired_invoices(
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# first we delete all invoices older than one month
|
# first we delete all invoices older than one month
|
||||||
|
|
||||||
|
|
@ -259,7 +259,7 @@ async def create_payment(
|
||||||
checking_id: str,
|
checking_id: str,
|
||||||
data: CreatePayment,
|
data: CreatePayment,
|
||||||
status: PaymentState = PaymentState.PENDING,
|
status: PaymentState = PaymentState.PENDING,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
# we don't allow the creation of the same invoice twice
|
# we don't allow the creation of the same invoice twice
|
||||||
# note: this can be removed if the db uniqueness constraints are set appropriately
|
# note: this can be removed if the db uniqueness constraints are set appropriately
|
||||||
|
|
@ -290,7 +290,7 @@ async def create_payment(
|
||||||
|
|
||||||
|
|
||||||
async def update_payment_checking_id(
|
async def update_payment_checking_id(
|
||||||
checking_id: str, new_checking_id: str, conn: Optional[Connection] = None
|
checking_id: str, new_checking_id: str, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"UPDATE apipayments SET checking_id = :new_id WHERE checking_id = :old_id",
|
"UPDATE apipayments SET checking_id = :new_id WHERE checking_id = :old_id",
|
||||||
|
|
@ -300,8 +300,8 @@ async def update_payment_checking_id(
|
||||||
|
|
||||||
async def update_payment(
|
async def update_payment(
|
||||||
payment: Payment,
|
payment: Payment,
|
||||||
new_checking_id: Optional[str] = None,
|
new_checking_id: str | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).update(
|
await (conn or db).update(
|
||||||
"apipayments", payment, "WHERE checking_id = :checking_id"
|
"apipayments", payment, "WHERE checking_id = :checking_id"
|
||||||
|
|
@ -311,9 +311,9 @@ async def update_payment(
|
||||||
|
|
||||||
|
|
||||||
async def get_payments_history(
|
async def get_payments_history(
|
||||||
wallet_id: Optional[str] = None,
|
wallet_id: str | None = None,
|
||||||
group: DateTrunc = "day",
|
group: DateTrunc = "day",
|
||||||
filters: Optional[Filters] = None,
|
filters: Filters | None = None,
|
||||||
) -> list[PaymentHistoryPoint]:
|
) -> list[PaymentHistoryPoint]:
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = Filters()
|
filters = Filters()
|
||||||
|
|
@ -376,9 +376,9 @@ async def get_payments_history(
|
||||||
|
|
||||||
async def get_payment_count_stats(
|
async def get_payment_count_stats(
|
||||||
field: PaymentCountField,
|
field: PaymentCountField,
|
||||||
filters: Optional[Filters[PaymentFilters]] = None,
|
filters: Filters[PaymentFilters] | None = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: str | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> list[PaymentCountStat]:
|
) -> list[PaymentCountStat]:
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
|
|
@ -409,9 +409,9 @@ async def get_payment_count_stats(
|
||||||
|
|
||||||
|
|
||||||
async def get_daily_stats(
|
async def get_daily_stats(
|
||||||
filters: Optional[Filters[PaymentFilters]] = None,
|
filters: Filters[PaymentFilters] | None = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: str | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
|
) -> tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
|
|
@ -459,9 +459,9 @@ async def get_daily_stats(
|
||||||
|
|
||||||
|
|
||||||
async def get_wallets_stats(
|
async def get_wallets_stats(
|
||||||
filters: Optional[Filters[PaymentFilters]] = None,
|
filters: Filters[PaymentFilters] | None = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: str | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> list[PaymentWalletStats]:
|
) -> list[PaymentWalletStats]:
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
|
|
@ -508,7 +508,7 @@ async def get_wallets_stats(
|
||||||
|
|
||||||
|
|
||||||
async def delete_wallet_payment(
|
async def delete_wallet_payment(
|
||||||
checking_id: str, wallet_id: str, conn: Optional[Connection] = None
|
checking_id: str, wallet_id: str, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"DELETE FROM apipayments WHERE checking_id = :checking_id AND wallet = :wallet",
|
"DELETE FROM apipayments WHERE checking_id = :checking_id AND wallet = :wallet",
|
||||||
|
|
@ -517,8 +517,8 @@ async def delete_wallet_payment(
|
||||||
|
|
||||||
|
|
||||||
async def check_internal(
|
async def check_internal(
|
||||||
payment_hash: str, conn: Optional[Connection] = None
|
payment_hash: str, conn: Connection | None = None
|
||||||
) -> Optional[Payment]:
|
) -> Payment | None:
|
||||||
"""
|
"""
|
||||||
Returns the checking_id of the internal payment if it exists,
|
Returns the checking_id of the internal payment if it exists,
|
||||||
otherwise None
|
otherwise None
|
||||||
|
|
@ -534,7 +534,7 @@ async def check_internal(
|
||||||
|
|
||||||
|
|
||||||
async def is_internal_status_success(
|
async def is_internal_status_success(
|
||||||
payment_hash: str, conn: Optional[Connection] = None
|
payment_hash: str, conn: Connection | None = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if the internal payment was found and is successful,
|
Returns True if the internal payment was found and is successful,
|
||||||
|
|
@ -563,7 +563,7 @@ async def mark_webhook_sent(payment_hash: str, status: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
async def _only_user_wallets_statement(
|
async def _only_user_wallets_statement(
|
||||||
user_id: str, conn: Optional[Connection] = None
|
user_id: str, conn: Connection | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
wallet_ids = await get_wallets_ids(user_id=user_id, conn=conn) or [
|
wallet_ids = await get_wallets_ids(user_id=user_id, conn=conn) or [
|
||||||
"no-wallets-for-user"
|
"no-wallets-for-user"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import json
|
import json
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ from lnbits.settings import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_super_settings() -> Optional[SuperSettings]:
|
async def get_super_settings() -> SuperSettings | None:
|
||||||
data = await get_settings_by_tag("core")
|
data = await get_settings_by_tag("core")
|
||||||
if data:
|
if data:
|
||||||
super_user = await get_settings_field("super_user")
|
super_user = await get_settings_field("super_user")
|
||||||
|
|
@ -24,7 +24,7 @@ async def get_super_settings() -> Optional[SuperSettings]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSettings]:
|
async def get_admin_settings(is_super_user: bool = False) -> AdminSettings | None:
|
||||||
sets = await get_super_settings()
|
sets = await get_super_settings()
|
||||||
if not sets:
|
if not sets:
|
||||||
return None
|
return None
|
||||||
|
|
@ -41,7 +41,7 @@ async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSetti
|
||||||
|
|
||||||
|
|
||||||
async def update_admin_settings(
|
async def update_admin_settings(
|
||||||
data: EditableSettings, tag: Optional[str] = "core"
|
data: EditableSettings, tag: str | None = "core"
|
||||||
) -> None:
|
) -> None:
|
||||||
editable_settings = await get_settings_by_tag("core") or {}
|
editable_settings = await get_settings_by_tag("core") or {}
|
||||||
editable_settings.update(data.dict(exclude_unset=True))
|
editable_settings.update(data.dict(exclude_unset=True))
|
||||||
|
|
@ -61,7 +61,7 @@ async def update_super_user(super_user: str) -> SuperSettings:
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
async def delete_admin_settings(tag: Optional[str] = "core") -> None:
|
async def delete_admin_settings(tag: str | None = "core") -> None:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"DELETE FROM system_settings WHERE tag = :tag",
|
"DELETE FROM system_settings WHERE tag = :tag",
|
||||||
{"tag": tag},
|
{"tag": tag},
|
||||||
|
|
@ -93,8 +93,8 @@ async def create_admin_settings(super_user: str, new_settings: dict) -> SuperSet
|
||||||
|
|
||||||
|
|
||||||
async def get_settings_field(
|
async def get_settings_field(
|
||||||
id_: str, tag: Optional[str] = "core"
|
id_: str, tag: str | None = "core"
|
||||||
) -> Optional[SettingsField]:
|
) -> SettingsField | None:
|
||||||
|
|
||||||
row: dict = await db.fetchone(
|
row: dict = await db.fetchone(
|
||||||
"""
|
"""
|
||||||
|
|
@ -108,9 +108,7 @@ async def get_settings_field(
|
||||||
return SettingsField(id=row["id"], value=json.loads(row["value"]), tag=row["tag"])
|
return SettingsField(id=row["id"], value=json.loads(row["value"]), tag=row["tag"])
|
||||||
|
|
||||||
|
|
||||||
async def set_settings_field(
|
async def set_settings_field(id_: str, value: Any | None, tag: str | None = "core"):
|
||||||
id_: str, value: Optional[Any], tag: Optional[str] = "core"
|
|
||||||
):
|
|
||||||
value = json.dumps(value) if value is not None else None
|
value = json.dumps(value) if value is not None else None
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -122,7 +120,7 @@ async def set_settings_field(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_settings_by_tag(tag: str) -> Optional[dict[str, Any]]:
|
async def get_settings_by_tag(tag: str) -> dict[str, Any] | None:
|
||||||
rows: list[dict] = await db.fetchall(
|
rows: list[dict] = await db.fetchall(
|
||||||
"SELECT * FROM system_settings WHERE tag = :tag", {"tag": tag}
|
"SELECT * FROM system_settings WHERE tag = :tag", {"tag": tag}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import shortuuid
|
import shortuuid
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
|
|
@ -19,7 +17,7 @@ async def create_tinyurl(domain: str, endless: bool, wallet: str):
|
||||||
return await get_tinyurl(tinyurl_id)
|
return await get_tinyurl(tinyurl_id)
|
||||||
|
|
||||||
|
|
||||||
async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]:
|
async def get_tinyurl(tinyurl_id: str) -> TinyURL | None:
|
||||||
return await db.fetchone(
|
return await db.fetchone(
|
||||||
"SELECT * FROM tiny_url WHERE id = :tinyurl",
|
"SELECT * FROM tiny_url WHERE id = :tinyurl",
|
||||||
{"tinyurl": tinyurl_id},
|
{"tinyurl": tinyurl_id},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from lnbits.core.crud.extensions import get_user_active_extensions_ids
|
from lnbits.core.crud.extensions import get_user_active_extensions_ids
|
||||||
|
|
@ -18,8 +18,8 @@ from ..models import (
|
||||||
|
|
||||||
|
|
||||||
async def create_account(
|
async def create_account(
|
||||||
account: Optional[Account] = None,
|
account: Account | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Account:
|
) -> Account:
|
||||||
if account:
|
if account:
|
||||||
account.validate_fields()
|
account.validate_fields()
|
||||||
|
|
@ -36,7 +36,7 @@ async def update_account(account: Account) -> Account:
|
||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
||||||
async def delete_account(user_id: str, conn: Optional[Connection] = None) -> None:
|
async def delete_account(user_id: str, conn: Connection | None = None) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"DELETE from accounts WHERE id = :user",
|
"DELETE from accounts WHERE id = :user",
|
||||||
{"user": user_id},
|
{"user": user_id},
|
||||||
|
|
@ -44,8 +44,8 @@ async def delete_account(user_id: str, conn: Optional[Connection] = None) -> Non
|
||||||
|
|
||||||
|
|
||||||
async def get_accounts(
|
async def get_accounts(
|
||||||
filters: Optional[Filters[AccountFilters]] = None,
|
filters: Filters[AccountFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Page[AccountOverview]:
|
) -> Page[AccountOverview]:
|
||||||
where_clauses = []
|
where_clauses = []
|
||||||
values: dict[str, Any] = {}
|
values: dict[str, Any] = {}
|
||||||
|
|
@ -92,9 +92,7 @@ async def get_accounts(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_account(
|
async def get_account(user_id: str, conn: Connection | None = None) -> Account | None:
|
||||||
user_id: str, conn: Optional[Connection] = None
|
|
||||||
) -> Optional[Account]:
|
|
||||||
if len(user_id) == 0:
|
if len(user_id) == 0:
|
||||||
return None
|
return None
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
|
|
@ -106,7 +104,7 @@ async def get_account(
|
||||||
|
|
||||||
async def delete_accounts_no_wallets(
|
async def delete_accounts_no_wallets(
|
||||||
time_delta: int,
|
time_delta: int,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
delta = int(time()) - time_delta
|
delta = int(time()) - time_delta
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
|
|
@ -125,8 +123,8 @@ async def delete_accounts_no_wallets(
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_username(
|
async def get_account_by_username(
|
||||||
username: str, conn: Optional[Connection] = None
|
username: str, conn: Connection | None = None
|
||||||
) -> Optional[Account]:
|
) -> Account | None:
|
||||||
if len(username) == 0:
|
if len(username) == 0:
|
||||||
return None
|
return None
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
|
|
@ -137,8 +135,8 @@ async def get_account_by_username(
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_pubkey(
|
async def get_account_by_pubkey(
|
||||||
pubkey: str, conn: Optional[Connection] = None
|
pubkey: str, conn: Connection | None = None
|
||||||
) -> Optional[Account]:
|
) -> Account | None:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM accounts WHERE LOWER(pubkey) = :pubkey",
|
"SELECT * FROM accounts WHERE LOWER(pubkey) = :pubkey",
|
||||||
{"pubkey": pubkey.lower()},
|
{"pubkey": pubkey.lower()},
|
||||||
|
|
@ -147,8 +145,8 @@ async def get_account_by_pubkey(
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_email(
|
async def get_account_by_email(
|
||||||
email: str, conn: Optional[Connection] = None
|
email: str, conn: Connection | None = None
|
||||||
) -> Optional[Account]:
|
) -> Account | None:
|
||||||
if len(email) == 0:
|
if len(email) == 0:
|
||||||
return None
|
return None
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
|
|
@ -159,8 +157,8 @@ async def get_account_by_email(
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_username_or_email(
|
async def get_account_by_username_or_email(
|
||||||
username_or_email: str, conn: Optional[Connection] = None
|
username_or_email: str, conn: Connection | None = None
|
||||||
) -> Optional[Account]:
|
) -> Account | None:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM accounts
|
SELECT * FROM accounts
|
||||||
|
|
@ -171,7 +169,7 @@ async def get_account_by_username_or_email(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
|
async def get_user(user_id: str, conn: Connection | None = None) -> User | None:
|
||||||
account = await get_account(user_id, conn)
|
account = await get_account(user_id, conn)
|
||||||
if not account:
|
if not account:
|
||||||
return None
|
return None
|
||||||
|
|
@ -179,8 +177,8 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
|
||||||
|
|
||||||
|
|
||||||
async def get_user_from_account(
|
async def get_user_from_account(
|
||||||
account: Account, conn: Optional[Connection] = None
|
account: Account, conn: Connection | None = None
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
extensions = await get_user_active_extensions_ids(account.id, conn)
|
extensions = await get_user_active_extensions_ids(account.id, conn)
|
||||||
wallets = await get_wallets(account.id, False, conn=conn)
|
wallets = await get_wallets(account.id, False, conn=conn)
|
||||||
return User(
|
return User(
|
||||||
|
|
@ -207,7 +205,7 @@ async def update_user_access_control_list(user_acls: UserAcls):
|
||||||
|
|
||||||
|
|
||||||
async def get_user_access_control_lists(
|
async def get_user_access_control_lists(
|
||||||
user_id: str, conn: Optional[Connection] = None
|
user_id: str, conn: Connection | None = None
|
||||||
) -> UserAcls:
|
) -> UserAcls:
|
||||||
user_acls = await (conn or db).fetchone(
|
user_acls = await (conn or db).fetchone(
|
||||||
"SELECT id, access_control_list FROM accounts WHERE id = :id",
|
"SELECT id, access_control_list FROM accounts WHERE id = :id",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Optional
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
|
|
@ -14,8 +13,8 @@ from ..models import Wallet
|
||||||
async def create_wallet(
|
async def create_wallet(
|
||||||
*,
|
*,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
wallet_name: Optional[str] = None,
|
wallet_name: str | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Wallet:
|
) -> Wallet:
|
||||||
wallet_id = uuid4().hex
|
wallet_id = uuid4().hex
|
||||||
wallet = Wallet(
|
wallet = Wallet(
|
||||||
|
|
@ -32,7 +31,7 @@ async def create_wallet(
|
||||||
|
|
||||||
async def update_wallet(
|
async def update_wallet(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Wallet:
|
) -> Wallet:
|
||||||
wallet.updated_at = datetime.now(timezone.utc)
|
wallet.updated_at = datetime.now(timezone.utc)
|
||||||
await (conn or db).update("wallets", wallet)
|
await (conn or db).update("wallets", wallet)
|
||||||
|
|
@ -44,7 +43,7 @@ async def delete_wallet(
|
||||||
user_id: str,
|
user_id: str,
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
deleted: bool = True,
|
deleted: bool = True,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
now = int(time())
|
now = int(time())
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
|
|
@ -58,9 +57,7 @@ async def delete_wallet(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def force_delete_wallet(
|
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
|
||||||
wallet_id: str, conn: Optional[Connection] = None
|
|
||||||
) -> None:
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"DELETE FROM wallets WHERE id = :wallet",
|
"DELETE FROM wallets WHERE id = :wallet",
|
||||||
{"wallet": wallet_id},
|
{"wallet": wallet_id},
|
||||||
|
|
@ -68,8 +65,8 @@ async def force_delete_wallet(
|
||||||
|
|
||||||
|
|
||||||
async def delete_wallet_by_id(
|
async def delete_wallet_by_id(
|
||||||
wallet_id: str, conn: Optional[Connection] = None
|
wallet_id: str, conn: Connection | None = None
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
now = int(time())
|
now = int(time())
|
||||||
result = await (conn or db).execute(
|
result = await (conn or db).execute(
|
||||||
# Timestamp placeholder is safe from SQL injection (not user input)
|
# Timestamp placeholder is safe from SQL injection (not user input)
|
||||||
|
|
@ -83,13 +80,13 @@ async def delete_wallet_by_id(
|
||||||
return result.rowcount
|
return result.rowcount
|
||||||
|
|
||||||
|
|
||||||
async def remove_deleted_wallets(conn: Optional[Connection] = None) -> None:
|
async def remove_deleted_wallets(conn: Connection | None = None) -> None:
|
||||||
await (conn or db).execute("DELETE FROM wallets WHERE deleted = true")
|
await (conn or db).execute("DELETE FROM wallets WHERE deleted = true")
|
||||||
|
|
||||||
|
|
||||||
async def delete_unused_wallets(
|
async def delete_unused_wallets(
|
||||||
time_delta: int,
|
time_delta: int,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
delta = int(time()) - time_delta
|
delta = int(time()) - time_delta
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
|
|
@ -107,8 +104,8 @@ async def delete_unused_wallets(
|
||||||
|
|
||||||
|
|
||||||
async def get_wallet(
|
async def get_wallet(
|
||||||
wallet_id: str, deleted: Optional[bool] = None, conn: Optional[Connection] = None
|
wallet_id: str, deleted: bool | None = None, conn: Connection | None = None
|
||||||
) -> Optional[Wallet]:
|
) -> Wallet | None:
|
||||||
query = """
|
query = """
|
||||||
SELECT *, COALESCE((
|
SELECT *, COALESCE((
|
||||||
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
||||||
|
|
@ -125,7 +122,7 @@ async def get_wallet(
|
||||||
|
|
||||||
|
|
||||||
async def get_wallets(
|
async def get_wallets(
|
||||||
user_id: str, deleted: Optional[bool] = None, conn: Optional[Connection] = None
|
user_id: str, deleted: bool | None = None, conn: Connection | None = None
|
||||||
) -> list[Wallet]:
|
) -> list[Wallet]:
|
||||||
query = """
|
query = """
|
||||||
SELECT *, COALESCE((
|
SELECT *, COALESCE((
|
||||||
|
|
@ -144,9 +141,9 @@ async def get_wallets(
|
||||||
|
|
||||||
async def get_wallets_paginated(
|
async def get_wallets_paginated(
|
||||||
user_id: str,
|
user_id: str,
|
||||||
deleted: Optional[bool] = None,
|
deleted: bool | None = None,
|
||||||
filters: Optional[Filters[WalletsFilters]] = None,
|
filters: Filters[WalletsFilters] | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Page[Wallet]:
|
) -> Page[Wallet]:
|
||||||
if deleted is None:
|
if deleted is None:
|
||||||
deleted = False
|
deleted = False
|
||||||
|
|
@ -166,7 +163,7 @@ async def get_wallets_paginated(
|
||||||
|
|
||||||
|
|
||||||
async def get_wallets_ids(
|
async def get_wallets_ids(
|
||||||
user_id: str, deleted: Optional[bool] = None, conn: Optional[Connection] = None
|
user_id: str, deleted: bool | None = None, conn: Connection | None = None
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
query = """SELECT id FROM wallets WHERE "user" = :user"""
|
query = """SELECT id FROM wallets WHERE "user" = :user"""
|
||||||
if deleted is not None:
|
if deleted is not None:
|
||||||
|
|
@ -186,8 +183,8 @@ async def get_wallets_count():
|
||||||
|
|
||||||
async def get_wallet_for_key(
|
async def get_wallet_for_key(
|
||||||
key: str,
|
key: str,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Optional[Wallet]:
|
) -> Wallet | None:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT *, COALESCE((
|
SELECT *, COALESCE((
|
||||||
|
|
@ -201,7 +198,7 @@ async def get_wallet_for_key(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_total_balance(conn: Optional[Connection] = None):
|
async def get_total_balance(conn: Connection | None = None):
|
||||||
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
||||||
row = result.mappings().first()
|
row = result.mappings().first()
|
||||||
return row.get("balance", 0) or 0
|
return row.get("balance", 0) or 0
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
|
|
||||||
from ..models import WebPushSubscription
|
from ..models import WebPushSubscription
|
||||||
|
|
@ -7,7 +5,7 @@ from ..models import WebPushSubscription
|
||||||
|
|
||||||
async def get_webpush_subscription(
|
async def get_webpush_subscription(
|
||||||
endpoint: str, user: str
|
endpoint: str, user: str
|
||||||
) -> Optional[WebPushSubscription]:
|
) -> WebPushSubscription | None:
|
||||||
return await db.fetchone(
|
return await db.fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM webpush_subscriptions
|
SELECT * FROM webpush_subscriptions
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import importlib
|
import importlib
|
||||||
import re
|
import re
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
async def migrate_extension_database(
|
async def migrate_extension_database(
|
||||||
ext: InstallableExtension, current_version: Optional[DbVersion] = None
|
ext: InstallableExtension, current_version: DbVersion | None = None
|
||||||
):
|
):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -38,7 +38,7 @@ async def run_migration(
|
||||||
db: Connection,
|
db: Connection,
|
||||||
migrations_module: Any,
|
migrations_module: Any,
|
||||||
db_name: str,
|
db_name: str,
|
||||||
current_version: Optional[DbVersion] = None,
|
current_version: DbVersion | None = None,
|
||||||
):
|
):
|
||||||
matcher = re.compile(r"^m(\d\d\d)_")
|
matcher = re.compile(r"^m(\d\d\d)_")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lnurl import LnAddress, Lnurl, LnurlPayResponse
|
from lnurl import LnAddress, Lnurl, LnurlPayResponse
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
@ -9,9 +8,9 @@ class CreateLnurlPayment(BaseModel):
|
||||||
res: LnurlPayResponse | None = None
|
res: LnurlPayResponse | None = None
|
||||||
lnurl: Lnurl | LnAddress | None = None
|
lnurl: Lnurl | LnAddress | None = None
|
||||||
amount: int
|
amount: int
|
||||||
comment: Optional[str] = None
|
comment: str | None = None
|
||||||
unit: Optional[str] = None
|
unit: str | None = None
|
||||||
internal_memo: Optional[str] = None
|
internal_memo: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class CreateLnurlWithdraw(BaseModel):
|
class CreateLnurlWithdraw(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import importlib
|
import importlib
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
@ -145,7 +144,7 @@ async def start_extension_background_work(ext_id: str) -> bool:
|
||||||
|
|
||||||
|
|
||||||
async def get_valid_extensions(
|
async def get_valid_extensions(
|
||||||
include_deactivated: Optional[bool] = True,
|
include_deactivated: bool | None = True,
|
||||||
) -> list[Extension]:
|
) -> list[Extension]:
|
||||||
installed_extensions = await get_installed_extensions()
|
installed_extensions = await get_installed_extensions()
|
||||||
valid_extensions = [Extension.from_installable_ext(e) for e in installed_extensions]
|
valid_extensions = [Extension.from_installable_ext(e) for e in installed_extensions]
|
||||||
|
|
@ -164,8 +163,8 @@ async def get_valid_extensions(
|
||||||
|
|
||||||
|
|
||||||
async def get_valid_extension(
|
async def get_valid_extension(
|
||||||
ext_id: str, include_deactivated: Optional[bool] = True
|
ext_id: str, include_deactivated: bool | None = True
|
||||||
) -> Optional[Extension]:
|
) -> Extension | None:
|
||||||
ext = await get_installed_extension(ext_id)
|
ext = await get_installed_extension(ext_id)
|
||||||
if not ext:
|
if not ext:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
@ -15,7 +14,7 @@ from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
async def handle_fiat_payment_confirmation(
|
async def handle_fiat_payment_confirmation(
|
||||||
payment: Payment, conn: Optional[Connection] = None
|
payment: Payment, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
await _credit_fiat_service_fee_wallet(payment, conn=conn)
|
await _credit_fiat_service_fee_wallet(payment, conn=conn)
|
||||||
|
|
@ -29,7 +28,7 @@ async def handle_fiat_payment_confirmation(
|
||||||
|
|
||||||
|
|
||||||
async def _credit_fiat_service_fee_wallet(
|
async def _credit_fiat_service_fee_wallet(
|
||||||
payment: Payment, conn: Optional[Connection] = None
|
payment: Payment, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
fiat_provider_name = payment.fiat_provider
|
fiat_provider_name = payment.fiat_provider
|
||||||
if not fiat_provider_name:
|
if not fiat_provider_name:
|
||||||
|
|
@ -66,7 +65,7 @@ async def _credit_fiat_service_fee_wallet(
|
||||||
|
|
||||||
|
|
||||||
async def _debit_fiat_service_faucet_wallet(
|
async def _debit_fiat_service_faucet_wallet(
|
||||||
payment: Payment, conn: Optional[Connection] = None
|
payment: Payment, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
fiat_provider_name = payment.fiat_provider
|
fiat_provider_name = payment.fiat_provider
|
||||||
if not fiat_provider_name:
|
if not fiat_provider_name:
|
||||||
|
|
@ -129,8 +128,8 @@ async def handle_stripe_event(event: dict):
|
||||||
|
|
||||||
def check_stripe_signature(
|
def check_stripe_signature(
|
||||||
payload: bytes,
|
payload: bytes,
|
||||||
sig_header: Optional[str],
|
sig_header: str | None,
|
||||||
secret: Optional[str],
|
secret: str | None,
|
||||||
tolerance_seconds=300,
|
tolerance_seconds=300,
|
||||||
):
|
):
|
||||||
if not sig_header:
|
if not sig_header:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import smtplib
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -71,7 +70,7 @@ async def process_next_notification() -> None:
|
||||||
|
|
||||||
async def send_admin_notification(
|
async def send_admin_notification(
|
||||||
message: str,
|
message: str,
|
||||||
message_type: Optional[str] = None,
|
message_type: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
return await send_notification(
|
return await send_notification(
|
||||||
settings.lnbits_telegram_notifications_chat_id,
|
settings.lnbits_telegram_notifications_chat_id,
|
||||||
|
|
@ -85,7 +84,7 @@ async def send_admin_notification(
|
||||||
async def send_user_notification(
|
async def send_user_notification(
|
||||||
user_notifications: UserNotifications,
|
user_notifications: UserNotifications,
|
||||||
message: str,
|
message: str,
|
||||||
message_type: Optional[str] = None,
|
message_type: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
email_address = (
|
email_address = (
|
||||||
|
|
@ -110,7 +109,7 @@ async def send_notification(
|
||||||
nostr_identifiers: list[str] | None,
|
nostr_identifiers: list[str] | None,
|
||||||
email_addresses: list[str] | None,
|
email_addresses: list[str] | None,
|
||||||
message: str,
|
message: str,
|
||||||
message_type: Optional[str] = None,
|
message_type: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
if telegram_chat_id and settings.is_telegram_notifications_configured():
|
if telegram_chat_id and settings.is_telegram_notifications_configured():
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bolt11 import Bolt11, MilliSatoshi, Tags
|
from bolt11 import Bolt11, MilliSatoshi, Tags
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
|
|
@ -58,11 +57,11 @@ async def pay_invoice(
|
||||||
*,
|
*,
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
payment_request: str,
|
payment_request: str,
|
||||||
max_sat: Optional[int] = None,
|
max_sat: int | None = None,
|
||||||
extra: Optional[dict] = None,
|
extra: dict | None = None,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
tag: str = "",
|
tag: str = "",
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
if settings.lnbits_only_allow_incoming_payments:
|
if settings.lnbits_only_allow_incoming_payments:
|
||||||
raise PaymentError("Only incoming payments allowed.", status="failed")
|
raise PaymentError("Only incoming payments allowed.", status="failed")
|
||||||
|
|
@ -110,7 +109,7 @@ async def create_payment_request(
|
||||||
|
|
||||||
|
|
||||||
async def create_fiat_invoice(
|
async def create_fiat_invoice(
|
||||||
wallet_id: str, invoice_data: CreateInvoice, conn: Optional[Connection] = None
|
wallet_id: str, invoice_data: CreateInvoice, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
fiat_provider_name = invoice_data.fiat_provider
|
fiat_provider_name = invoice_data.fiat_provider
|
||||||
if not fiat_provider_name:
|
if not fiat_provider_name:
|
||||||
|
|
@ -231,16 +230,16 @@ async def create_invoice(
|
||||||
*,
|
*,
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
amount: float,
|
amount: float,
|
||||||
currency: Optional[str] = "sat",
|
currency: str | None = "sat",
|
||||||
memo: str,
|
memo: str,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
expiry: Optional[int] = None,
|
expiry: int | None = None,
|
||||||
extra: Optional[dict] = None,
|
extra: dict | None = None,
|
||||||
webhook: Optional[str] = None,
|
webhook: str | None = None,
|
||||||
internal: Optional[bool] = False,
|
internal: bool | None = False,
|
||||||
payment_hash: str | None = None,
|
payment_hash: str | None = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
if not amount > 0:
|
if not amount > 0:
|
||||||
raise InvoiceError("Amountless invoices not supported.", status="failed")
|
raise InvoiceError("Amountless invoices not supported.", status="failed")
|
||||||
|
|
@ -427,7 +426,7 @@ def service_fee_fiat(amount_msat: int, fiat_provider_name: str) -> int:
|
||||||
async def update_wallet_balance(
|
async def update_wallet_balance(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
amount: int,
|
amount: int,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
):
|
):
|
||||||
if amount == 0:
|
if amount == 0:
|
||||||
raise ValueError("Amount cannot be 0.")
|
raise ValueError("Amount cannot be 0.")
|
||||||
|
|
@ -486,14 +485,14 @@ async def update_wallet_balance(
|
||||||
|
|
||||||
|
|
||||||
async def check_wallet_limits(
|
async def check_wallet_limits(
|
||||||
wallet_id: str, amount_msat: int, conn: Optional[Connection] = None
|
wallet_id: str, amount_msat: int, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
await check_time_limit_between_transactions(wallet_id, conn)
|
await check_time_limit_between_transactions(wallet_id, conn)
|
||||||
await check_wallet_daily_withdraw_limit(wallet_id, amount_msat, conn)
|
await check_wallet_daily_withdraw_limit(wallet_id, amount_msat, conn)
|
||||||
|
|
||||||
|
|
||||||
async def check_time_limit_between_transactions(
|
async def check_time_limit_between_transactions(
|
||||||
wallet_id: str, conn: Optional[Connection] = None
|
wallet_id: str, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
limit = settings.lnbits_wallet_limit_secs_between_trans
|
limit = settings.lnbits_wallet_limit_secs_between_trans
|
||||||
if not limit or limit <= 0:
|
if not limit or limit <= 0:
|
||||||
|
|
@ -513,7 +512,7 @@ async def check_time_limit_between_transactions(
|
||||||
|
|
||||||
|
|
||||||
async def check_wallet_daily_withdraw_limit(
|
async def check_wallet_daily_withdraw_limit(
|
||||||
wallet_id: str, amount_msat: int, conn: Optional[Connection] = None
|
wallet_id: str, amount_msat: int, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
limit = settings.lnbits_wallet_limit_daily_max_withdraw
|
limit = settings.lnbits_wallet_limit_daily_max_withdraw
|
||||||
if not limit:
|
if not limit:
|
||||||
|
|
@ -546,8 +545,8 @@ async def check_wallet_daily_withdraw_limit(
|
||||||
async def calculate_fiat_amounts(
|
async def calculate_fiat_amounts(
|
||||||
amount: float,
|
amount: float,
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
currency: Optional[str] = None,
|
currency: str | None = None,
|
||||||
extra: Optional[dict] = None,
|
extra: dict | None = None,
|
||||||
) -> tuple[int, dict]:
|
) -> tuple[int, dict]:
|
||||||
wallet_currency = wallet.currency or settings.lnbits_default_accounting_currency
|
wallet_currency = wallet.currency or settings.lnbits_default_accounting_currency
|
||||||
fiat_amounts: dict = extra or {}
|
fiat_amounts: dict = extra or {}
|
||||||
|
|
@ -582,9 +581,9 @@ async def calculate_fiat_amounts(
|
||||||
|
|
||||||
|
|
||||||
async def check_transaction_status(
|
async def check_transaction_status(
|
||||||
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
|
wallet_id: str, payment_hash: str, conn: Connection | None = None
|
||||||
) -> PaymentStatus:
|
) -> PaymentStatus:
|
||||||
payment: Optional[Payment] = await get_wallet_payment(
|
payment: Payment | None = await get_wallet_payment(
|
||||||
wallet_id, payment_hash, conn=conn
|
wallet_id, payment_hash, conn=conn
|
||||||
)
|
)
|
||||||
if not payment:
|
if not payment:
|
||||||
|
|
@ -598,7 +597,7 @@ async def check_transaction_status(
|
||||||
|
|
||||||
async def get_payments_daily_stats(
|
async def get_payments_daily_stats(
|
||||||
filters: Filters[PaymentFilters],
|
filters: Filters[PaymentFilters],
|
||||||
user_id: Optional[str] = None,
|
user_id: str | None = None,
|
||||||
) -> list[PaymentDailyStats]:
|
) -> list[PaymentDailyStats]:
|
||||||
data_in, data_out = await get_daily_stats(filters, user_id=user_id)
|
data_in, data_out = await get_daily_stats(filters, user_id=user_id)
|
||||||
balance_total: float = 0
|
balance_total: float = 0
|
||||||
|
|
@ -647,7 +646,7 @@ async def get_payments_daily_stats(
|
||||||
async def _pay_invoice(
|
async def _pay_invoice(
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
create_payment_model: CreatePayment,
|
create_payment_model: CreatePayment,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
):
|
):
|
||||||
async with payment_lock:
|
async with payment_lock:
|
||||||
if wallet_id not in wallets_payments_lock:
|
if wallet_id not in wallets_payments_lock:
|
||||||
|
|
@ -670,8 +669,8 @@ async def _pay_invoice(
|
||||||
async def _pay_internal_invoice(
|
async def _pay_internal_invoice(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
create_payment_model: CreatePayment,
|
create_payment_model: CreatePayment,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Optional[Payment]:
|
) -> Payment | None:
|
||||||
"""
|
"""
|
||||||
Pay an internal payment.
|
Pay an internal payment.
|
||||||
returns None if the payment is not internal.
|
returns None if the payment is not internal.
|
||||||
|
|
@ -738,7 +737,7 @@ async def _pay_internal_invoice(
|
||||||
async def _pay_external_invoice(
|
async def _pay_external_invoice(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
create_payment_model: CreatePayment,
|
create_payment_model: CreatePayment,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
checking_id = create_payment_model.payment_hash
|
checking_id = create_payment_model.payment_hash
|
||||||
amount_msat = create_payment_model.amount_msat
|
amount_msat = create_payment_model.amount_msat
|
||||||
|
|
@ -807,7 +806,7 @@ async def _pay_external_invoice(
|
||||||
async def update_payment_success_status(
|
async def update_payment_success_status(
|
||||||
payment: Payment,
|
payment: Payment,
|
||||||
status: PaymentStatus,
|
status: PaymentStatus,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
if status.success:
|
if status.success:
|
||||||
service_fee_msat = service_fee(payment.amount, internal=False)
|
service_fee_msat = service_fee(payment.amount, internal=False)
|
||||||
|
|
@ -831,7 +830,7 @@ async def _fundingsource_pay_invoice(
|
||||||
|
|
||||||
|
|
||||||
async def _verify_external_payment(
|
async def _verify_external_payment(
|
||||||
payment: Payment, conn: Optional[Connection] = None
|
payment: Payment, conn: Connection | None = None
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
# fail on pending payments
|
# fail on pending payments
|
||||||
if payment.pending:
|
if payment.pending:
|
||||||
|
|
@ -862,7 +861,7 @@ async def _check_wallet_for_payment(
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
tag: str,
|
tag: str,
|
||||||
amount_msat: int,
|
amount_msat: int,
|
||||||
conn: Optional[Connection] = None,
|
conn: Connection | None = None,
|
||||||
):
|
):
|
||||||
wallet = await get_wallet(wallet_id, conn=conn)
|
wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
if not wallet:
|
if not wallet:
|
||||||
|
|
@ -878,7 +877,7 @@ async def _check_wallet_for_payment(
|
||||||
|
|
||||||
|
|
||||||
def _validate_payment_request(
|
def _validate_payment_request(
|
||||||
payment_request: str, max_sat: Optional[int] = None
|
payment_request: str, max_sat: int | None = None
|
||||||
) -> Bolt11:
|
) -> Bolt11:
|
||||||
try:
|
try:
|
||||||
invoice = bolt11_decode(payment_request)
|
invoice = bolt11_decode(payment_request)
|
||||||
|
|
@ -901,7 +900,7 @@ def _validate_payment_request(
|
||||||
|
|
||||||
|
|
||||||
async def _credit_service_fee_wallet(
|
async def _credit_service_fee_wallet(
|
||||||
wallet: Wallet, payment: Payment, conn: Optional[Connection] = None
|
wallet: Wallet, payment: Payment, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
|
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
|
||||||
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
|
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
|
||||||
|
|
@ -927,7 +926,7 @@ async def _credit_service_fee_wallet(
|
||||||
|
|
||||||
|
|
||||||
async def _check_fiat_invoice_limits(
|
async def _check_fiat_invoice_limits(
|
||||||
amount_sat: int, fiat_provider_name: str, conn: Optional[Connection] = None
|
amount_sat: int, fiat_provider_name: str, conn: Connection | None = None
|
||||||
):
|
):
|
||||||
limits = settings.get_fiat_provider_limits(fiat_provider_name)
|
limits = settings.get_fiat_provider_limits(fiat_provider_name)
|
||||||
if not limits:
|
if not limits:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -37,7 +36,7 @@ from .settings import update_cached_settings
|
||||||
|
|
||||||
|
|
||||||
async def create_user_account(
|
async def create_user_account(
|
||||||
account: Optional[Account] = None, wallet_name: Optional[str] = None
|
account: Account | None = None, wallet_name: str | None = None
|
||||||
) -> User:
|
) -> User:
|
||||||
if not settings.new_accounts_allowed:
|
if not settings.new_accounts_allowed:
|
||||||
raise ValueError("Account creation is disabled.")
|
raise ValueError("Account creation is disabled.")
|
||||||
|
|
@ -46,9 +45,9 @@ async def create_user_account(
|
||||||
|
|
||||||
|
|
||||||
async def create_user_account_no_ckeck(
|
async def create_user_account_no_ckeck(
|
||||||
account: Optional[Account] = None,
|
account: Account | None = None,
|
||||||
wallet_name: Optional[str] = None,
|
wallet_name: str | None = None,
|
||||||
default_exts: Optional[list[str]] = None,
|
default_exts: list[str] | None = None,
|
||||||
) -> User:
|
) -> User:
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
|
|
@ -165,12 +164,12 @@ async def check_admin_settings():
|
||||||
settings.first_install = True
|
settings.first_install = True
|
||||||
|
|
||||||
logger.success(
|
logger.success(
|
||||||
"✔️ Admin UI is enabled. run `poetry run lnbits-cli superuser` "
|
"✔️ Admin UI is enabled. run `uv run lnbits-cli superuser` "
|
||||||
"to get the superuser."
|
"to get the superuser."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
|
async def init_admin_settings(super_user: str | None = None) -> SuperSettings:
|
||||||
account = None
|
account = None
|
||||||
if super_user:
|
if super_user:
|
||||||
account = await get_account(super_user)
|
account = await get_account(super_user)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
from collections.abc import Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||||
from shutil import make_archive, move
|
from shutil import make_archive, move
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import IO, Optional
|
from typing import IO
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import filetype
|
import filetype
|
||||||
|
|
@ -71,10 +71,10 @@ async def api_test_email():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/api/v1/settings", response_model=Optional[AdminSettings])
|
@admin_router.get("/api/v1/settings")
|
||||||
async def api_get_settings(
|
async def api_get_settings(
|
||||||
user: User = Depends(check_admin),
|
user: User = Depends(check_admin),
|
||||||
) -> Optional[AdminSettings]:
|
) -> AdminSettings | None:
|
||||||
admin_settings = await get_admin_settings(user.super_user)
|
admin_settings = await get_admin_settings(user.super_user)
|
||||||
return admin_settings
|
return admin_settings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import base64
|
import base64
|
||||||
import importlib
|
import importlib
|
||||||
import json
|
import json
|
||||||
|
from collections.abc import Callable
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Callable, Optional
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
|
|
@ -238,7 +238,7 @@ async def api_delete_user_api_token(
|
||||||
|
|
||||||
@auth_router.get("/{provider}", description="SSO Provider")
|
@auth_router.get("/{provider}", description="SSO Provider")
|
||||||
async def login_with_sso_provider(
|
async def login_with_sso_provider(
|
||||||
request: Request, provider: str, user_id: Optional[str] = None
|
request: Request, provider: str, user_id: str | None = None
|
||||||
):
|
):
|
||||||
provider_sso = _new_sso(provider)
|
provider_sso = _new_sso(provider)
|
||||||
if not provider_sso:
|
if not provider_sso:
|
||||||
|
|
@ -319,7 +319,7 @@ async def update_pubkey(
|
||||||
data: UpdateUserPubkey,
|
data: UpdateUserPubkey,
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
payload: AccessTokenPayload = Depends(access_token_payload),
|
payload: AccessTokenPayload = Depends(access_token_payload),
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
if data.user_id != user.id:
|
if data.user_id != user.id:
|
||||||
raise ValueError("Invalid user ID.")
|
raise ValueError("Invalid user ID.")
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ async def update_password(
|
||||||
data: UpdateUserPassword,
|
data: UpdateUserPassword,
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
payload: AccessTokenPayload = Depends(access_token_payload),
|
payload: AccessTokenPayload = Depends(access_token_payload),
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
_validate_auth_timeout(payload.auth_time)
|
_validate_auth_timeout(payload.auth_time)
|
||||||
if data.user_id != user.id:
|
if data.user_id != user.id:
|
||||||
raise ValueError("Invalid user ID.")
|
raise ValueError("Invalid user ID.")
|
||||||
|
|
@ -419,7 +419,7 @@ async def reset_password(data: ResetUserPassword) -> JSONResponse:
|
||||||
@auth_router.put("/update")
|
@auth_router.put("/update")
|
||||||
async def update(
|
async def update(
|
||||||
data: UpdateUser, user: User = Depends(check_user_exists)
|
data: UpdateUser, user: User = Depends(check_user_exists)
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
if data.user_id != user.id:
|
if data.user_id != user.id:
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid user ID.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid user ID.")
|
||||||
if data.username and not is_valid_username(data.username):
|
if data.username and not is_valid_username(data.username):
|
||||||
|
|
@ -461,7 +461,7 @@ async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
|
||||||
return _auth_success_response(account.username, account.id, account.email)
|
return _auth_success_response(account.username, account.id, account.email)
|
||||||
|
|
||||||
|
|
||||||
async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] = None):
|
async def _handle_sso_login(userinfo: OpenID, verified_user_id: str | None = None):
|
||||||
email = userinfo.email
|
email = userinfo.email
|
||||||
if not email or not is_valid_email_address(email):
|
if not email or not is_valid_email_address(email):
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
|
||||||
|
|
@ -490,9 +490,9 @@ async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] =
|
||||||
|
|
||||||
|
|
||||||
def _auth_success_response(
|
def _auth_success_response(
|
||||||
username: Optional[str] = None,
|
username: str | None = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: str | None = None,
|
||||||
email: Optional[str] = None,
|
email: str | None = None,
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
payload = AccessTokenPayload(
|
payload = AccessTokenPayload(
|
||||||
sub=username or "", usr=user_id, email=email, auth_time=int(time())
|
sub=username or "", usr=user_id, email=email, auth_time=int(time())
|
||||||
|
|
@ -533,7 +533,7 @@ def _auth_redirect_response(path: str, email: str) -> RedirectResponse:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _new_sso(provider: str) -> Optional[SSOBase]:
|
def _new_sso(provider: str) -> SSOBase | None:
|
||||||
try:
|
try:
|
||||||
if not settings.is_auth_method_allowed(AuthMethods(f"{provider}-auth")):
|
if not settings.is_auth_method_allowed(AuthMethods(f"{provider}-auth")):
|
||||||
return None
|
return None
|
||||||
|
|
@ -610,7 +610,7 @@ def _nostr_nip98_event(request: Request) -> dict:
|
||||||
|
|
||||||
|
|
||||||
def _check_nostr_event_tags(event: dict):
|
def _check_nostr_event_tags(event: dict):
|
||||||
method: Optional[str] = next((v for k, v in event["tags"] if k == "method"), None)
|
method: str | None = next((v for k, v in event["tags"] if k == "method"), None)
|
||||||
if not method:
|
if not method:
|
||||||
raise ValueError("Tag 'method' is missing.")
|
raise ValueError("Tag 'method' is missing.")
|
||||||
if not method.upper() == "POST":
|
if not method.upper() == "POST":
|
||||||
|
|
@ -625,7 +625,7 @@ def _check_nostr_event_tags(event: dict):
|
||||||
raise ValueError(f"Invalid value for tag 'u': '{url}'.")
|
raise ValueError(f"Invalid value for tag 'u': '{url}'.")
|
||||||
|
|
||||||
|
|
||||||
def _validate_auth_timeout(auth_time: Optional[int] = 0):
|
def _validate_auth_timeout(auth_time: int | None = 0):
|
||||||
if abs(time() - (auth_time or 0)) > settings.auth_credetials_update_threshold:
|
if abs(time() - (auth_time or 0)) > settings.auth_credetials_update_threshold:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, Optional, Union
|
from typing import Annotated
|
||||||
from urllib.parse import urlencode, urlparse
|
from urllib.parse import urlencode, urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -161,9 +161,9 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
||||||
)
|
)
|
||||||
async def wallet(
|
async def wallet(
|
||||||
request: Request,
|
request: Request,
|
||||||
lnbits_last_active_wallet: Annotated[Union[str, None], Cookie()] = None,
|
lnbits_last_active_wallet: Annotated[str | None, Cookie()] = None,
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
wal: Optional[UUID4] = Query(None),
|
wal: UUID4 | None = Query(None),
|
||||||
):
|
):
|
||||||
if wal:
|
if wal:
|
||||||
wallet = await get_wallet(wal.hex)
|
wallet = await get_wallet(wal.hex)
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ async def _handle(lnurl: str) -> LnurlResponseModel:
|
||||||
)
|
)
|
||||||
async def api_lnurlscan(code: str) -> LnurlResponseModel:
|
async def api_lnurlscan(code: str) -> LnurlResponseModel:
|
||||||
res = await _handle(code)
|
res = await _handle(code)
|
||||||
if isinstance(res, (LnurlPayResponse, LnurlWithdrawResponse, LnurlAuthResponse)):
|
if isinstance(res, LnurlPayResponse | LnurlWithdrawResponse | LnurlAuthResponse):
|
||||||
check_callback_url(res.callback)
|
check_callback_url(res.callback)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ async def api_payment_pay_with_nfc(
|
||||||
except (LnurlResponseException, Exception) as exc:
|
except (LnurlResponseException, Exception) as exc:
|
||||||
logger.warning(exc)
|
logger.warning(exc)
|
||||||
return LnurlErrorResponse(reason=str(exc))
|
return LnurlErrorResponse(reason=str(exc))
|
||||||
if not isinstance(res2, (LnurlSuccessResponse, LnurlErrorResponse)):
|
if not isinstance(res2, LnurlSuccessResponse | LnurlErrorResponse):
|
||||||
return LnurlErrorResponse(reason="Invalid LNURL-withdraw response.")
|
return LnurlErrorResponse(reason="Invalid LNURL-withdraw response.")
|
||||||
|
|
||||||
return res2
|
return res2
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||||
|
|
@ -89,14 +88,14 @@ async def api_get_public_info(node: Node = Depends(require_node)) -> PublicNodeI
|
||||||
@node_router.get("/info")
|
@node_router.get("/info")
|
||||||
async def api_get_info(
|
async def api_get_info(
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
) -> Optional[NodeInfoResponse]:
|
) -> NodeInfoResponse | None:
|
||||||
return await node.get_info()
|
return await node.get_info()
|
||||||
|
|
||||||
|
|
||||||
@node_router.get("/channels")
|
@node_router.get("/channels")
|
||||||
async def api_get_channels(
|
async def api_get_channels(
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
) -> Optional[list[NodeChannel]]:
|
) -> list[NodeChannel] | None:
|
||||||
return await node.get_channels()
|
return await node.get_channels()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,7 +103,7 @@ async def api_get_channels(
|
||||||
async def api_get_channel(
|
async def api_get_channel(
|
||||||
channel_id: str,
|
channel_id: str,
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
) -> Optional[NodeChannel]:
|
) -> NodeChannel | None:
|
||||||
return await node.get_channel(channel_id)
|
return await node.get_channel(channel_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -113,20 +112,20 @@ async def api_create_channel(
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
peer_id: str = Body(),
|
peer_id: str = Body(),
|
||||||
funding_amount: int = Body(),
|
funding_amount: int = Body(),
|
||||||
push_amount: Optional[int] = Body(None),
|
push_amount: int | None = Body(None),
|
||||||
fee_rate: Optional[int] = Body(None),
|
fee_rate: int | None = Body(None),
|
||||||
):
|
):
|
||||||
return await node.open_channel(peer_id, funding_amount, push_amount, fee_rate)
|
return await node.open_channel(peer_id, funding_amount, push_amount, fee_rate)
|
||||||
|
|
||||||
|
|
||||||
@super_node_router.delete("/channels")
|
@super_node_router.delete("/channels")
|
||||||
async def api_delete_channel(
|
async def api_delete_channel(
|
||||||
short_id: Optional[str],
|
short_id: str | None,
|
||||||
funding_txid: Optional[str],
|
funding_txid: str | None,
|
||||||
output_index: Optional[int],
|
output_index: int | None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
) -> Optional[list[NodeChannel]]:
|
) -> list[NodeChannel] | None:
|
||||||
return await node.close_channel(
|
return await node.close_channel(
|
||||||
short_id,
|
short_id,
|
||||||
(
|
(
|
||||||
|
|
@ -152,7 +151,7 @@ async def api_set_channel_fees(
|
||||||
async def api_get_payments(
|
async def api_get_payments(
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
filters: Filters = Depends(parse_filters(NodePaymentsFilters)),
|
filters: Filters = Depends(parse_filters(NodePaymentsFilters)),
|
||||||
) -> Optional[Page[NodePayment]]:
|
) -> Page[NodePayment] | None:
|
||||||
if not settings.lnbits_node_ui_transactions:
|
if not settings.lnbits_node_ui_transactions:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
HTTP_503_SERVICE_UNAVAILABLE,
|
HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
|
@ -165,7 +164,7 @@ async def api_get_payments(
|
||||||
async def api_get_invoices(
|
async def api_get_invoices(
|
||||||
node: Node = Depends(require_node),
|
node: Node = Depends(require_node),
|
||||||
filters: Filters = Depends(parse_filters(NodeInvoiceFilters)),
|
filters: Filters = Depends(parse_filters(NodeInvoiceFilters)),
|
||||||
) -> Optional[Page[NodeInvoice]]:
|
) -> Page[NodeInvoice] | None:
|
||||||
if not settings.lnbits_node_ui_transactions:
|
if not settings.lnbits_node_ui_transactions:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
HTTP_503_SERVICE_UNAVAILABLE,
|
HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
|
@ -192,25 +191,25 @@ async def api_disconnect_peer(peer_id: str, node: Node = Depends(require_node)):
|
||||||
|
|
||||||
|
|
||||||
class NodeRank(BaseModel):
|
class NodeRank(BaseModel):
|
||||||
capacity: Optional[int]
|
capacity: int | None
|
||||||
channelcount: Optional[int]
|
channelcount: int | None
|
||||||
age: Optional[int]
|
age: int | None
|
||||||
growth: Optional[int]
|
growth: int | None
|
||||||
availability: Optional[int]
|
availability: int | None
|
||||||
|
|
||||||
|
|
||||||
# Same for public and private api
|
# Same for public and private api
|
||||||
@node_router.get(
|
@node_router.get(
|
||||||
"/rank",
|
"/rank",
|
||||||
description="Retrieve node ranks from https://1ml.com",
|
description="Retrieve node ranks from https://1ml.com",
|
||||||
response_model=Optional[NodeRank],
|
response_model=NodeRank | None,
|
||||||
)
|
)
|
||||||
@public_node_router.get(
|
@public_node_router.get(
|
||||||
"/rank",
|
"/rank",
|
||||||
description="Retrieve node ranks from https://1ml.com",
|
description="Retrieve node ranks from https://1ml.com",
|
||||||
response_model=Optional[NodeRank],
|
response_model=NodeRank | None,
|
||||||
)
|
)
|
||||||
async def api_get_1ml_stats(node: Node = Depends(require_node)) -> Optional[NodeRank]:
|
async def api_get_1ml_stats(node: Node = Depends(require_node)) -> NodeRank | None:
|
||||||
node_id = await node.get_id()
|
node_id = await node.get_id()
|
||||||
headers = {"User-Agent": settings.user_agent}
|
headers = {"User-Agent": settings.user_agent}
|
||||||
async with httpx.AsyncClient(headers=headers) as client:
|
async with httpx.AsyncClient(headers=headers) as client:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
APIRouter,
|
APIRouter,
|
||||||
|
|
@ -275,7 +274,7 @@ async def api_payments_fee_reserve(invoice: str = Query("invoice")) -> JSONRespo
|
||||||
|
|
||||||
# TODO: refactor this route into a public and admin one
|
# TODO: refactor this route into a public and admin one
|
||||||
@payment_router.get("/{payment_hash}")
|
@payment_router.get("/{payment_hash}")
|
||||||
async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
async def api_payment(payment_hash, x_api_key: str | None = Header(None)):
|
||||||
# We use X_Api_Key here because we want this call to work with and without keys
|
# 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
|
# If a valid key is given, we also return the field "details", otherwise not
|
||||||
wallet = await get_wallet_for_key(x_api_key) if isinstance(x_api_key, str) else None
|
wallet = await get_wallet_for_key(x_api_key) if isinstance(x_api_key, str) else None
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import base64
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import shortuuid
|
import shortuuid
|
||||||
|
|
@ -223,7 +222,7 @@ async def api_users_get_user_wallet(user_id: str) -> list[Wallet]:
|
||||||
|
|
||||||
@users_router.post("/user/{user_id}/wallet", name="Create a new wallet for user")
|
@users_router.post("/user/{user_id}/wallet", name="Create a new wallet for user")
|
||||||
async def api_users_create_user_wallet(
|
async def api_users_create_user_wallet(
|
||||||
user_id: str, name: Optional[str] = Body(None), currency: Optional[str] = Body(None)
|
user_id: str, name: str | None = Body(None), currency: str | None = Body(None)
|
||||||
):
|
):
|
||||||
if currency and currency not in allowed_currencies():
|
if currency and currency not in allowed_currencies():
|
||||||
raise ValueError(f"Currency '{currency}' not allowed.")
|
raise ValueError(f"Currency '{currency}' not allowed.")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
|
|
@ -110,11 +109,11 @@ async def api_put_stored_paylinks(
|
||||||
|
|
||||||
@wallet_router.patch("")
|
@wallet_router.patch("")
|
||||||
async def api_update_wallet(
|
async def api_update_wallet(
|
||||||
name: Optional[str] = Body(None),
|
name: str | None = Body(None),
|
||||||
icon: Optional[str] = Body(None),
|
icon: str | None = Body(None),
|
||||||
color: Optional[str] = Body(None),
|
color: str | None = Body(None),
|
||||||
currency: Optional[str] = Body(None),
|
currency: str | None = Body(None),
|
||||||
pinned: Optional[bool] = Body(None),
|
pinned: bool | None = Body(None),
|
||||||
key_info: WalletTypeInfo = Depends(require_admin_key),
|
key_info: WalletTypeInfo = Depends(require_admin_key),
|
||||||
) -> Wallet:
|
) -> Wallet:
|
||||||
wallet = await get_wallet(key_info.wallet.id)
|
wallet = await get_wallet(key_info.wallet.id)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, Literal, Optional, Union
|
from typing import Annotated, Literal
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
from fastapi import Cookie, Depends, Query, Request, Security
|
from fastapi import Cookie, Depends, Query, Request, Security
|
||||||
|
|
@ -55,8 +55,8 @@ api_key_query = APIKeyQuery(
|
||||||
class KeyChecker(SecurityBase):
|
class KeyChecker(SecurityBase):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
api_key: Optional[str] = None,
|
api_key: str | None = None,
|
||||||
expected_key_type: Optional[KeyType] = None,
|
expected_key_type: KeyType | None = None,
|
||||||
):
|
):
|
||||||
self.auto_error: bool = True
|
self.auto_error: bool = True
|
||||||
self.expected_key_type = expected_key_type
|
self.expected_key_type = expected_key_type
|
||||||
|
|
@ -137,17 +137,17 @@ async def require_invoice_key(
|
||||||
|
|
||||||
|
|
||||||
async def check_access_token(
|
async def check_access_token(
|
||||||
header_access_token: Annotated[Union[str, None], Depends(oauth2_scheme)],
|
header_access_token: Annotated[str | None, Depends(oauth2_scheme)],
|
||||||
cookie_access_token: Annotated[Union[str, None], Cookie()] = None,
|
cookie_access_token: Annotated[str | None, Cookie()] = None,
|
||||||
bearer_access_token: Annotated[Union[str, None], Depends(http_bearer)] = None,
|
bearer_access_token: Annotated[str | None, Depends(http_bearer)] = None,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
return header_access_token or cookie_access_token or bearer_access_token
|
return header_access_token or cookie_access_token or bearer_access_token
|
||||||
|
|
||||||
|
|
||||||
async def check_user_exists(
|
async def check_user_exists(
|
||||||
r: Request,
|
r: Request,
|
||||||
access_token: Annotated[Optional[str], Depends(check_access_token)],
|
access_token: Annotated[str | None, Depends(check_access_token)],
|
||||||
usr: Optional[UUID4] = None,
|
usr: UUID4 | None = None,
|
||||||
) -> User:
|
) -> User:
|
||||||
if access_token:
|
if access_token:
|
||||||
account = await _get_account_from_token(access_token, r["path"], r["method"])
|
account = await _get_account_from_token(access_token, r["path"], r["method"])
|
||||||
|
|
@ -176,9 +176,9 @@ async def check_user_exists(
|
||||||
|
|
||||||
async def optional_user_id(
|
async def optional_user_id(
|
||||||
r: Request,
|
r: Request,
|
||||||
access_token: Annotated[Optional[str], Depends(check_access_token)],
|
access_token: Annotated[str | None, Depends(check_access_token)],
|
||||||
usr: Optional[UUID4] = None,
|
usr: UUID4 | None = None,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
if usr and settings.is_auth_method_allowed(AuthMethods.user_id_only):
|
if usr and settings.is_auth_method_allowed(AuthMethods.user_id_only):
|
||||||
return usr.hex
|
return usr.hex
|
||||||
if access_token:
|
if access_token:
|
||||||
|
|
@ -189,7 +189,7 @@ async def optional_user_id(
|
||||||
|
|
||||||
|
|
||||||
async def access_token_payload(
|
async def access_token_payload(
|
||||||
access_token: Annotated[Optional[str], Depends(check_access_token)],
|
access_token: Annotated[str | None, Depends(check_access_token)],
|
||||||
) -> AccessTokenPayload:
|
) -> AccessTokenPayload:
|
||||||
if not access_token:
|
if not access_token:
|
||||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Missing access token.")
|
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Missing access token.")
|
||||||
|
|
@ -231,11 +231,11 @@ def parse_filters(model: type[TFilterModel]):
|
||||||
|
|
||||||
def dependency(
|
def dependency(
|
||||||
request: Request,
|
request: Request,
|
||||||
limit: Optional[int] = None,
|
limit: int | None = None,
|
||||||
offset: Optional[int] = None,
|
offset: int | None = None,
|
||||||
sortby: Optional[str] = None,
|
sortby: str | None = None,
|
||||||
direction: Optional[Literal["asc", "desc"]] = None,
|
direction: Literal["asc", "desc"] | None = None,
|
||||||
search: Optional[str] = Query(None, description="Text based search"),
|
search: str | None = Query(None, description="Text based search"),
|
||||||
):
|
):
|
||||||
params = request.query_params
|
params = request.query_params
|
||||||
filters = []
|
filters = []
|
||||||
|
|
@ -259,7 +259,7 @@ def parse_filters(model: type[TFilterModel]):
|
||||||
|
|
||||||
|
|
||||||
async def check_user_extension_access(
|
async def check_user_extension_access(
|
||||||
user_id: str, ext_id: str, conn: Optional[Connection] = None
|
user_id: str, ext_id: str, conn: Connection | None = None
|
||||||
) -> SimpleStatus:
|
) -> SimpleStatus:
|
||||||
"""
|
"""
|
||||||
Check if the user has access to a particular extension.
|
Check if the user has access to a particular extension.
|
||||||
|
|
@ -292,7 +292,7 @@ async def _check_user_extension_access(user_id: str, path: str):
|
||||||
|
|
||||||
async def _get_account_from_token(
|
async def _get_account_from_token(
|
||||||
access_token: str, path: str, method: str
|
access_token: str, path: str, method: str
|
||||||
) -> Optional[Account]:
|
) -> Account | None:
|
||||||
try:
|
try:
|
||||||
payload: dict = jwt.decode(access_token, settings.auth_secret_key, ["HS256"])
|
payload: dict = jwt.decode(access_token, settings.auth_secret_key, ["HS256"])
|
||||||
return await _get_account_from_jwt_payload(
|
return await _get_account_from_jwt_payload(
|
||||||
|
|
@ -310,7 +310,7 @@ async def _get_account_from_token(
|
||||||
|
|
||||||
async def _get_account_from_jwt_payload(
|
async def _get_account_from_jwt_payload(
|
||||||
payload: AccessTokenPayload, path: str, method: str
|
payload: AccessTokenPayload, path: str, method: str
|
||||||
) -> Optional[Account]:
|
) -> Account | None:
|
||||||
account = None
|
account = None
|
||||||
if payload.sub:
|
if payload.sub:
|
||||||
account = await get_account_by_username(payload.sub)
|
account = await get_account_by_username(payload.sub)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Request
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
|
|
@ -30,7 +29,7 @@ class UnsupportedError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def render_html_error(request: Request, exc: Exception) -> Optional[Response]:
|
def render_html_error(request: Request, exc: Exception) -> Response | None:
|
||||||
# Only the browser sends "text/html" request
|
# Only the browser sends "text/html" request
|
||||||
# not fail proof, but everything else get's a JSON response
|
# not fail proof, but everything else get's a JSON response
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -49,7 +48,7 @@ class StripeWallet(FiatProvider):
|
||||||
logger.warning(f"Error closing stripe wallet connection: {e}")
|
logger.warning(f"Error closing stripe wallet connection: {e}")
|
||||||
|
|
||||||
async def status(
|
async def status(
|
||||||
self, only_check_settings: Optional[bool] = False
|
self, only_check_settings: bool | None = False
|
||||||
) -> FiatStatusResponse:
|
) -> FiatStatusResponse:
|
||||||
if only_check_settings:
|
if only_check_settings:
|
||||||
if self._settings_fields != self._settings_connection_fields():
|
if self._settings_fields != self._settings_connection_fields():
|
||||||
|
|
@ -76,7 +75,7 @@ class StripeWallet(FiatProvider):
|
||||||
amount: float,
|
amount: float,
|
||||||
payment_hash: str,
|
payment_hash: str,
|
||||||
currency: str,
|
currency: str,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> FiatInvoiceResponse:
|
) -> FiatInvoiceResponse:
|
||||||
amount_cents = int(amount * 100)
|
amount_cents = int(amount * 100)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import json
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
from urllib import request
|
from urllib import request
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ def urlsafe_short_hash() -> str:
|
||||||
return shortuuid.uuid()
|
return shortuuid.uuid()
|
||||||
|
|
||||||
|
|
||||||
def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> str:
|
def url_for(endpoint: str, external: bool | None = False, **params: Any) -> str:
|
||||||
base = f"http://{settings.host}:{settings.port}" if external else ""
|
base = f"http://{settings.host}:{settings.port}" if external else ""
|
||||||
url_params = "?"
|
url_params = "?"
|
||||||
for key, value in params.items():
|
for key, value in params.items():
|
||||||
|
|
@ -52,7 +52,7 @@ def static_url_for(static: str, path: str) -> str:
|
||||||
return f"/{static}/{path}?v={settings.server_startup_time}"
|
return f"/{static}/{path}?v={settings.server_startup_time}"
|
||||||
|
|
||||||
|
|
||||||
def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templates:
|
def template_renderer(additional_folders: list | None = None) -> Jinja2Templates:
|
||||||
folders = ["lnbits/templates", "lnbits/core/templates"]
|
folders = ["lnbits/templates", "lnbits/core/templates"]
|
||||||
if additional_folders:
|
if additional_folders:
|
||||||
additional_folders += [
|
additional_folders += [
|
||||||
|
|
@ -211,7 +211,7 @@ def is_valid_pubkey(pubkey: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(data: dict, token_expire_minutes: Optional[int] = None) -> str:
|
def create_access_token(data: dict, token_expire_minutes: int | None = None) -> str:
|
||||||
minutes = token_expire_minutes or settings.auth_token_expire_minutes
|
minutes = token_expire_minutes or settings.auth_token_expire_minutes
|
||||||
expire = datetime.now(timezone.utc) + timedelta(minutes=minutes)
|
expire = datetime.now(timezone.utc) + timedelta(minutes=minutes)
|
||||||
to_encode = {k: v for k, v in data.items() if v is not None}
|
to_encode = {k: v for k, v in data.items() if v is not None}
|
||||||
|
|
@ -219,9 +219,7 @@ def create_access_token(data: dict, token_expire_minutes: Optional[int] = None)
|
||||||
return jwt.encode(to_encode, settings.auth_secret_key, "HS256")
|
return jwt.encode(to_encode, settings.auth_secret_key, "HS256")
|
||||||
|
|
||||||
|
|
||||||
def encrypt_internal_message(
|
def encrypt_internal_message(m: str | None = None, urlsafe: bool = False) -> str | None:
|
||||||
m: Optional[str] = None, urlsafe: bool = False
|
|
||||||
) -> Optional[str]:
|
|
||||||
"""
|
"""
|
||||||
Encrypt message with the internal secret key
|
Encrypt message with the internal secret key
|
||||||
|
|
||||||
|
|
@ -234,9 +232,7 @@ def encrypt_internal_message(
|
||||||
return AESCipher(key=settings.auth_secret_key).encrypt(m.encode(), urlsafe=urlsafe)
|
return AESCipher(key=settings.auth_secret_key).encrypt(m.encode(), urlsafe=urlsafe)
|
||||||
|
|
||||||
|
|
||||||
def decrypt_internal_message(
|
def decrypt_internal_message(m: str | None = None, urlsafe: bool = False) -> str | None:
|
||||||
m: Optional[str] = None, urlsafe: bool = False
|
|
||||||
) -> Optional[str]:
|
|
||||||
"""
|
"""
|
||||||
Decrypt message with the internal secret key
|
Decrypt message with the internal secret key
|
||||||
|
|
||||||
|
|
@ -249,7 +245,7 @@ def decrypt_internal_message(
|
||||||
return AESCipher(key=settings.auth_secret_key).decrypt(m, urlsafe=urlsafe)
|
return AESCipher(key=settings.auth_secret_key).decrypt(m, urlsafe=urlsafe)
|
||||||
|
|
||||||
|
|
||||||
def filter_dict_keys(data: dict, filter_keys: Optional[list[str]]) -> dict:
|
def filter_dict_keys(data: dict, filter_keys: list[str] | None) -> dict:
|
||||||
if not filter_keys:
|
if not filter_keys:
|
||||||
# return shallow clone of the dict even if there are no filters
|
# return shallow clone of the dict even if there are no filters
|
||||||
return {**data}
|
return {**data}
|
||||||
|
|
@ -270,7 +266,7 @@ def version_parse(v: str):
|
||||||
|
|
||||||
|
|
||||||
def is_lnbits_version_ok(
|
def is_lnbits_version_ok(
|
||||||
min_lnbits_version: Optional[str], max_lnbits_version: Optional[str]
|
min_lnbits_version: str | None, max_lnbits_version: str | None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if min_lnbits_version and (
|
if min_lnbits_version and (
|
||||||
version_parse(min_lnbits_version) > version_parse(settings.version)
|
version_parse(min_lnbits_version) > version_parse(settings.version)
|
||||||
|
|
@ -347,7 +343,7 @@ def path_segments(path: str) -> list[str]:
|
||||||
return segments[0:]
|
return segments[0:]
|
||||||
|
|
||||||
|
|
||||||
def normalize_path(path: Optional[str]) -> str:
|
def normalize_path(path: str | None) -> str:
|
||||||
path = path or ""
|
path = path or ""
|
||||||
return "/" + "/".join(path_segments(path))
|
return "/" + "/".join(path_segments(path))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, Optional, Union
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, Response
|
from fastapi import FastAPI, Request, Response
|
||||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||||
|
|
@ -62,7 +62,7 @@ class InstalledExtensionMiddleware:
|
||||||
|
|
||||||
def _response_by_accepted_type(
|
def _response_by_accepted_type(
|
||||||
self, scope: Scope, headers: list[Any], msg: str, status_code: HTTPStatus
|
self, scope: Scope, headers: list[Any], msg: str, status_code: HTTPStatus
|
||||||
) -> Union[HTMLResponse, JSONResponse]:
|
) -> HTMLResponse | JSONResponse:
|
||||||
"""
|
"""
|
||||||
Build an HTTP response containing the `msg` as HTTP body and the `status_code`
|
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
|
as HTTP code. If the `accept` HTTP header is present int the request and
|
||||||
|
|
@ -127,7 +127,7 @@ class AuditMiddleware(BaseHTTPMiddleware):
|
||||||
async def dispatch(self, request: Request, call_next) -> Response:
|
async def dispatch(self, request: Request, call_next) -> Response:
|
||||||
start_time = datetime.now(timezone.utc)
|
start_time = datetime.now(timezone.utc)
|
||||||
request_details = await self._request_details(request)
|
request_details = await self._request_details(request)
|
||||||
response: Optional[Response] = None
|
response: Response | None = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await call_next(request)
|
response = await call_next(request)
|
||||||
|
|
@ -140,13 +140,13 @@ class AuditMiddleware(BaseHTTPMiddleware):
|
||||||
async def _log_audit(
|
async def _log_audit(
|
||||||
self,
|
self,
|
||||||
request: Request,
|
request: Request,
|
||||||
response: Optional[Response],
|
response: Response | None,
|
||||||
duration: float,
|
duration: float,
|
||||||
request_details: Optional[str],
|
request_details: str | None,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
http_method = request.scope.get("method", None)
|
http_method = request.scope.get("method", None)
|
||||||
path: Optional[str] = getattr(request.scope.get("route", {}), "path", None)
|
path: str | None = getattr(request.scope.get("route", {}), "path", None)
|
||||||
response_code = str(response.status_code) if response else None
|
response_code = str(response.status_code) if response else None
|
||||||
if not settings.audit_http_request(http_method, path, response_code):
|
if not settings.audit_http_request(http_method, path, response_code):
|
||||||
return None
|
return None
|
||||||
|
|
@ -178,7 +178,7 @@ class AuditMiddleware(BaseHTTPMiddleware):
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
|
|
||||||
async def _request_details(self, request: Request) -> Optional[str]:
|
async def _request_details(self, request: Request) -> str | None:
|
||||||
if not settings.audit_http_request_details():
|
if not settings.audit_http_request_details():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ def main(
|
||||||
ssl_certfile: str,
|
ssl_certfile: str,
|
||||||
reload: bool,
|
reload: bool,
|
||||||
):
|
):
|
||||||
"""Launched with `poetry run lnbits` at root level"""
|
"""Launched with `uv run lnbits` at root level"""
|
||||||
|
|
||||||
# create data dir if it does not exist
|
# create data dir if it does not exist
|
||||||
Path(settings.lnbits_data_folder).mkdir(parents=True, exist_ok=True)
|
Path(settings.lnbits_data_folder).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
from typing import (
|
|
||||||
Callable,
|
|
||||||
Optional,
|
|
||||||
)
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
@ -84,7 +80,7 @@ invoice_listeners: dict[str, asyncio.Queue] = {}
|
||||||
|
|
||||||
# TODO: name should not be optional
|
# TODO: name should not be optional
|
||||||
# some extensions still dont use a name, but they should
|
# some extensions still dont use a name, but they should
|
||||||
def register_invoice_listener(send_chan: asyncio.Queue, name: Optional[str] = None):
|
def register_invoice_listener(send_chan: asyncio.Queue, name: str | None = None):
|
||||||
"""
|
"""
|
||||||
A method intended for extensions (and core/tasks.py) to call when they want to be
|
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.
|
notified about new invoice payments incoming. Will emit all incoming payments.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from base64 import b64decode, b64encode, urlsafe_b64decode, urlsafe_b64encode
|
from base64 import b64decode, b64encode, urlsafe_b64decode, urlsafe_b64encode
|
||||||
from hashlib import md5, pbkdf2_hmac, sha256
|
from hashlib import md5, pbkdf2_hmac, sha256
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from Cryptodome import Random
|
from Cryptodome import Random
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
|
|
@ -40,7 +39,7 @@ class AESCipher:
|
||||||
AES.decrypt(encrypted, password).toString(Utf8);
|
AES.decrypt(encrypted, password).toString(Utf8);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, key: Union[bytes, str], block_size: int = 16):
|
def __init__(self, key: bytes | str, block_size: int = 16):
|
||||||
self.block_size = block_size
|
self.block_size = block_size
|
||||||
if isinstance(key, bytes):
|
if isinstance(key, bytes):
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import statistics
|
import statistics
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import jsonpath_ng.ext as jpx
|
import jsonpath_ng.ext as jpx
|
||||||
|
|
@ -250,7 +249,7 @@ async def btc_rates(currency: str) -> list[tuple[str, float]]:
|
||||||
|
|
||||||
async def fetch_price(
|
async def fetch_price(
|
||||||
provider: ExchangeRateProvider,
|
provider: ExchangeRateProvider,
|
||||||
) -> Optional[tuple[str, float]]:
|
) -> tuple[str, float] | None:
|
||||||
if currency.lower() in provider.exclude_to:
|
if currency.lower() in provider.exclude_to:
|
||||||
logger.warning(f"Provider {provider.name} does not support {currency}.")
|
logger.warning(f"Provider {provider.name} does not support {currency}.")
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from collections.abc import Callable
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import secp256k1
|
import secp256k1
|
||||||
|
|
@ -149,7 +148,7 @@ def sign_event(
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
def json_dumps(data: Union[dict, list]) -> str:
|
def json_dumps(data: dict | list) -> str:
|
||||||
"""
|
"""
|
||||||
Converts a Python dictionary to a JSON string with compact encoding.
|
Converts a Python dictionary to a JSON string with compact encoding.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -71,9 +70,9 @@ class AlbyWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
# https://api.getalby.com/invoices
|
# https://api.getalby.com/invoices
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -102,9 +101,9 @@ class BlinkWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
# https://dev.blink.sv/api/btc-ln-receive
|
# https://dev.blink.sv/api/btc-ln-receive
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bolt11.decode import decode
|
from bolt11.decode import decode
|
||||||
from grpc.aio import AioRpcError
|
from grpc.aio import AioRpcError
|
||||||
|
|
@ -107,9 +106,9 @@ class BoltzWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
pair = boltzrpc_pb2.Pair(to=boltzrpc_pb2.LBTC)
|
pair = boltzrpc_pb2.Pair(to=boltzrpc_pb2.LBTC)
|
||||||
|
|
@ -254,7 +253,7 @@ class BoltzWallet(Wallet):
|
||||||
)
|
)
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
async def _fetch_wallet(self, wallet_name: str) -> Optional[str]:
|
async def _fetch_wallet(self, wallet_name: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
request = boltzrpc_pb2.GetWalletRequest(name=wallet_name)
|
request = boltzrpc_pb2.GetWalletRequest(name=wallet_name)
|
||||||
response = await self.rpc.GetWallet(request, metadata=self.metadata)
|
response = await self.rpc.GetWallet(request, metadata=self.metadata)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
|
|
||||||
wget https://raw.githubusercontent.com/BoltzExchange/boltz-client/refs/heads/master/pkg/boltzrpc/boltzrpc.proto -O boltz_grpc_files/boltzrpc.proto
|
wget https://raw.githubusercontent.com/BoltzExchange/boltz-client/refs/heads/master/pkg/boltzrpc/boltzrpc.proto -O boltz_grpc_files/boltzrpc.proto
|
||||||
poetry run python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. --pyi_out=. boltz_grpc_files/boltzrpc.proto
|
uv run python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. --pyi_out=. boltz_grpc_files/boltzrpc.proto
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,13 @@ if not find_spec("breez_sdk"):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Breez SDK is not installed. "
|
"Breez SDK is not installed. "
|
||||||
"Ask admin to run `poetry install -E breez` to install it."
|
"Ask admin to run `uv sync --extra breez` to install it."
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bolt11 import Bolt11Exception
|
from bolt11 import Bolt11Exception
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
|
|
@ -66,7 +65,7 @@ else:
|
||||||
):
|
):
|
||||||
breez_incoming_queue.put_nowait(e.details)
|
breez_incoming_queue.put_nowait(e.details)
|
||||||
|
|
||||||
def load_bytes(source: str, extension: str) -> Optional[bytes]:
|
def load_bytes(source: str, extension: str) -> bytes | None:
|
||||||
# first check if it can be read from a file
|
# first check if it can be read from a file
|
||||||
if source.split(".")[-1] == extension:
|
if source.split(".")[-1] == extension:
|
||||||
with open(source, "rb") as f:
|
with open(source, "rb") as f:
|
||||||
|
|
@ -85,7 +84,7 @@ else:
|
||||||
logger.debug(exc)
|
logger.debug(exc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def load_greenlight_credentials() -> Optional[GreenlightCredentials]:
|
def load_greenlight_credentials() -> GreenlightCredentials | None:
|
||||||
if (
|
if (
|
||||||
settings.breez_greenlight_device_key
|
settings.breez_greenlight_device_key
|
||||||
and settings.breez_greenlight_device_cert
|
and settings.breez_greenlight_device_cert
|
||||||
|
|
@ -168,9 +167,9 @@ else:
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
# if description_hash or unhashed_description:
|
# if description_hash or unhashed_description:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ if not find_spec("breez_sdk_liquid"):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Breez Liquid SDK is not installed. "
|
"Breez Liquid SDK is not installed. "
|
||||||
"Ask admin to run `poetry add -E breez` to install it."
|
"Ask admin to run `uv sync --extra breez` to install it."
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -16,7 +16,6 @@ else:
|
||||||
from asyncio import Queue
|
from asyncio import Queue
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
from breez_sdk_liquid import (
|
from breez_sdk_liquid import (
|
||||||
|
|
@ -128,9 +127,9 @@ else:
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websocket import create_connection
|
from websocket import create_connection
|
||||||
|
|
@ -53,9 +52,9 @@ class ClicheWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
if unhashed_description or description_hash:
|
if unhashed_description or description_hash:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import os
|
||||||
import ssl
|
import ssl
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -174,9 +173,9 @@ class CLNRestWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from bolt11.decode import decode as bolt11_decode
|
from bolt11.decode import decode as bolt11_decode
|
||||||
from bolt11.exceptions import Bolt11Exception
|
from bolt11.exceptions import Bolt11Exception
|
||||||
|
|
@ -92,9 +92,9 @@ class CoreLightningWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
label = kwargs.get("label", f"lbl{token_urlsafe(16)}")
|
label = kwargs.get("label", f"lbl{token_urlsafe(16)}")
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from bolt11 import Bolt11Exception
|
from bolt11 import Bolt11Exception
|
||||||
|
|
@ -106,9 +105,9 @@ class CoreLightningRestWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
label = kwargs.get("label", f"lbl{token_urlsafe(16)}")
|
label = kwargs.get("label", f"lbl{token_urlsafe(16)}")
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -84,9 +84,9 @@ class EclairWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict[str, Any] = {
|
data: dict[str, Any] = {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from collections.abc import AsyncGenerator
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from os import urandom
|
from os import urandom
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bolt11 import (
|
from bolt11 import (
|
||||||
Bolt11,
|
Bolt11,
|
||||||
|
|
@ -53,11 +52,11 @@ class FakeWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
expiry: Optional[int] = None,
|
expiry: int | None = None,
|
||||||
payment_secret: Optional[bytes] = None,
|
payment_secret: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
tags = Tags()
|
tags = Tags()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -71,9 +70,9 @@ class LNbitsWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict = {"out": False, "amount": amount, "memo": memo or ""}
|
data: dict = {"out": False, "amount": amount, "memo": memo or ""}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import base64
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from os import environ
|
from os import environ
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -123,9 +122,9 @@ class LndWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict = {
|
data: dict = {
|
||||||
|
|
@ -312,9 +311,9 @@ class LndWallet(Wallet):
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
payment_hash: str,
|
payment_hash: str,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict = {
|
data: dict = {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -103,9 +103,9 @@ class LndRestWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
_data: dict = {
|
_data: dict = {
|
||||||
|
|
@ -327,9 +327,9 @@ class LndRestWallet(Wallet):
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
payment_hash: str,
|
payment_hash: str,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict = {
|
data: dict = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -75,9 +74,9 @@ class LNPayWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict = {"num_satoshis": f"{amount}"}
|
data: dict = {"num_satoshis": f"{amount}"}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -69,9 +68,9 @@ class LnTipsWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: dict = {"amount": amount, "description_hash": "", "memo": memo or ""}
|
data: dict = {"amount": amount, "description_hash": "", "memo": memo or ""}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import base64
|
import base64
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
@ -8,8 +7,8 @@ from lnbits.utils.crypto import AESCipher
|
||||||
|
|
||||||
|
|
||||||
def load_macaroon(
|
def load_macaroon(
|
||||||
macaroon: Optional[str] = None,
|
macaroon: str | None = None,
|
||||||
encrypted_macaroon: Optional[str] = None,
|
encrypted_macaroon: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Returns hex version of a macaroon encoded in base64 or the file path."""
|
"""Returns hex version of a macaroon encoded in base64 or the file path."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import json
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional, Union, cast
|
from typing import cast
|
||||||
from urllib.parse import parse_qs, unquote, urlparse
|
from urllib.parse import parse_qs, unquote, urlparse
|
||||||
|
|
||||||
import secp256k1
|
import secp256k1
|
||||||
|
|
@ -130,9 +130,9 @@ class NWCWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
desc = ""
|
desc = ""
|
||||||
|
|
@ -360,7 +360,7 @@ class NWCConnection:
|
||||||
"""
|
"""
|
||||||
return self.shutdown or not settings.lnbits_running
|
return self.shutdown or not settings.lnbits_running
|
||||||
|
|
||||||
async def _send(self, data: list[Union[str, dict]]):
|
async def _send(self, data: list[str | dict]):
|
||||||
"""
|
"""
|
||||||
Sends data to the NWC relay.
|
Sends data to the NWC relay.
|
||||||
|
|
||||||
|
|
@ -396,7 +396,7 @@ class NWCConnection:
|
||||||
|
|
||||||
async def _close_subscription_by_subid(
|
async def _close_subscription_by_subid(
|
||||||
self, sub_id: str, send_event: bool = True
|
self, sub_id: str, send_event: bool = True
|
||||||
) -> Optional[dict]:
|
) -> dict | None:
|
||||||
"""
|
"""
|
||||||
Closes a subscription by its sub_id.
|
Closes a subscription by its sub_id.
|
||||||
|
|
||||||
|
|
@ -427,7 +427,7 @@ class NWCConnection:
|
||||||
|
|
||||||
async def _close_subscription_by_eventid(
|
async def _close_subscription_by_eventid(
|
||||||
self, event_id, send_event=True
|
self, event_id, send_event=True
|
||||||
) -> Optional[dict]:
|
) -> dict | None:
|
||||||
"""
|
"""
|
||||||
Closes a subscription associated to an event_id.
|
Closes a subscription associated to an event_id.
|
||||||
|
|
||||||
|
|
@ -514,7 +514,7 @@ class NWCConnection:
|
||||||
if subscription: # Check if the subscription exists first
|
if subscription: # Check if the subscription exists first
|
||||||
subscription["future"].set_exception(Exception(info))
|
subscription["future"].set_exception(Exception(info))
|
||||||
|
|
||||||
async def _on_event_message(self, msg: list[Union[str, dict]]): # noqa: C901
|
async def _on_event_message(self, msg: list[str | dict]): # noqa: C901
|
||||||
"""
|
"""
|
||||||
Handles EVENT messages from the relay.
|
Handles EVENT messages from the relay.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -71,9 +70,9 @@ class OpenNodeWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
if description_hash or unhashed_description:
|
if description_hash or unhashed_description:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from httpx import RequestError, TimeoutException
|
from httpx import RequestError, TimeoutException
|
||||||
|
|
@ -96,9 +96,9 @@ class PhoenixdWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -111,9 +110,9 @@ class SparkWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
label = f"lbs{token_urlsafe(16)}"
|
label = f"lbs{token_urlsafe(16)}"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import asyncio
|
||||||
import time
|
import time
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -116,7 +116,7 @@ class StrikeWallet(Wallet):
|
||||||
self.failed_payments: dict[str, str] = {}
|
self.failed_payments: dict[str, str] = {}
|
||||||
|
|
||||||
# balance cache
|
# balance cache
|
||||||
self._cached_balance: Optional[int] = None
|
self._cached_balance: int | None = None
|
||||||
self._cached_balance_ts: float = 0.0
|
self._cached_balance_ts: float = 0.0
|
||||||
self._cache_ttl = 30 # seconds
|
self._cache_ttl = 30 # seconds
|
||||||
|
|
||||||
|
|
@ -199,8 +199,8 @@ class StrikeWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: bytes | None = None, # Add this parameter
|
unhashed_description: bytes | None = None, # Add this parameter
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
|
|
@ -424,10 +424,10 @@ class StrikeWallet(Wallet):
|
||||||
|
|
||||||
async def get_invoices(
|
async def get_invoices(
|
||||||
self,
|
self,
|
||||||
filters: Optional[str] = None,
|
filters: str | None = None,
|
||||||
orderby: Optional[str] = None,
|
orderby: str | None = None,
|
||||||
skip: Optional[int] = None,
|
skip: int | None = None,
|
||||||
top: Optional[int] = None,
|
top: int | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
params: dict[str, Any] = {}
|
params: dict[str, Any] = {}
|
||||||
|
|
@ -450,7 +450,7 @@ class StrikeWallet(Wallet):
|
||||||
|
|
||||||
async def _get_payment_status_by_quote_id(
|
async def _get_payment_status_by_quote_id(
|
||||||
self, checking_id: str, quote_id: str
|
self, checking_id: str, quote_id: str
|
||||||
) -> Optional[PaymentStatus]:
|
) -> PaymentStatus | None:
|
||||||
resp = await self._get(f"/payment-quotes/{quote_id}")
|
resp = await self._get(f"/payment-quotes/{quote_id}")
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
|
|
@ -60,9 +59,9 @@ class ZBDWallet(Wallet):
|
||||||
async def create_invoice(
|
async def create_invoice(
|
||||||
self,
|
self,
|
||||||
amount: int,
|
amount: int,
|
||||||
memo: Optional[str] = None,
|
memo: str | None = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: bytes | None = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: bytes | None = None,
|
||||||
**_,
|
**_,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
# https://api.zebedee.io/v0/charges
|
# https://api.zebedee.io/v0/charges
|
||||||
|
|
|
||||||
1011
poetry.lock
generated
1011
poetry.lock
generated
File diff suppressed because it is too large
Load diff
167
pyproject.toml
167
pyproject.toml
|
|
@ -1,78 +1,98 @@
|
||||||
[tool.poetry]
|
[project]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.3.0-rc4"
|
version = "1.3.0-rc4"
|
||||||
|
requires-python = ">=3.10,<3.13"
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||||
|
urls = { Homepage = "https://lnbits.com", Repository = "https://github.com/lnbits/lnbits" }
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/lnbits/lnbits"
|
dependencies = [
|
||||||
homepage = "https://lnbits.com"
|
"bech32==1.2.0",
|
||||||
|
"click==8.2.1",
|
||||||
|
"ecdsa==0.19.1",
|
||||||
|
"fastapi==0.116.1",
|
||||||
|
"starlette==0.47.1",
|
||||||
|
"httpx==0.27.0",
|
||||||
|
"jinja2==3.1.6",
|
||||||
|
"lnurl==0.7.3",
|
||||||
|
"pydantic==1.10.22",
|
||||||
|
"pyqrcode==1.2.1",
|
||||||
|
"shortuuid==1.0.13",
|
||||||
|
"sse-starlette==2.3.6",
|
||||||
|
"typing-extensions==4.14.0",
|
||||||
|
"uvicorn==0.34.3",
|
||||||
|
"sqlalchemy==1.4.54",
|
||||||
|
"aiosqlite==0.21.0",
|
||||||
|
"asyncpg==0.30.0",
|
||||||
|
"uvloop==0.21.0",
|
||||||
|
"websockets==15.0.1",
|
||||||
|
"loguru==0.7.3",
|
||||||
|
"grpcio==1.69.0",
|
||||||
|
"protobuf==5.29.5",
|
||||||
|
"pyln-client==25.5",
|
||||||
|
"pywebpush==2.0.3",
|
||||||
|
"slowapi==0.1.9",
|
||||||
|
"websocket-client==1.8.0",
|
||||||
|
"pycryptodomex==3.23.0",
|
||||||
|
"packaging==25.0",
|
||||||
|
"bolt11==2.1.1",
|
||||||
|
"pyjwt==2.10.1",
|
||||||
|
"itsdangerous==2.2.0",
|
||||||
|
"fastapi-sso==0.18.0",
|
||||||
|
# needed for boltz, lnurldevice, watchonly extensions
|
||||||
|
"embit==0.8.0",
|
||||||
|
# needed for lnurlp, nostrclient, nostrmarket
|
||||||
|
"secp256k1==0.14.0",
|
||||||
|
# keep for backwards compatibility with lnurlp
|
||||||
|
"environs==14.2.0",
|
||||||
|
# needed for scheduler extension
|
||||||
|
"python-crontab==3.2.0",
|
||||||
|
"pynostr==0.6.2",
|
||||||
|
"python-multipart==0.0.20",
|
||||||
|
"filetype==1.2.0",
|
||||||
|
"nostr-sdk==0.42.1",
|
||||||
|
"bcrypt==4.3.0",
|
||||||
|
"jsonpath-ng==1.7.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
lnbits = "lnbits.server:main"
|
||||||
|
lnbits-cli = "lnbits.commands:main"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
breez = ["breez-sdk==0.8.0", "breez-sdk-liquid==0.9.1"]
|
||||||
|
liquid = ["wallycore==1.4.0"]
|
||||||
|
migration = ["psycopg2-binary==2.9.10"]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = [
|
||||||
|
"black>=25.1.0,<26.0.0",
|
||||||
|
"mypy>=1.11.2,<2.0.0",
|
||||||
|
"types-protobuf>=6.30.2.20250516,<7.0.0",
|
||||||
|
"pre-commit>=4.2.0,<5.0.0",
|
||||||
|
"openapi-spec-validator>=0.7.1,<1.0.0",
|
||||||
|
"ruff>=0.12.0,<1.0.0",
|
||||||
|
"types-passlib>=1.7.7.20240327,<2.0.0",
|
||||||
|
"openai>=1.39.0,<2.0.0",
|
||||||
|
"json5>=0.12.0,<1.0.0",
|
||||||
|
"asgi-lifespan>=2.1.0,<3.0.0",
|
||||||
|
"anyio>=4.7.0,<5.0.0",
|
||||||
|
"pytest>=8.3.4,<9.0.0",
|
||||||
|
"pytest-cov>=6.0.0,<7.0.0",
|
||||||
|
"pytest-md>=0.2.0,<0.3.0",
|
||||||
|
"pytest-httpserver>=1.1.0,<2.0.0",
|
||||||
|
"pytest-mock>=3.14.0,<4.0.0",
|
||||||
|
"types-mock>=5.1.0.20240425,<6.0.0",
|
||||||
|
"mock>=5.1.0,<6.0.0",
|
||||||
|
"grpcio-tools>=1.69.0,<2.0.0"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
packages = [
|
packages = [
|
||||||
{include = "lnbits"},
|
{include = "lnbits"},
|
||||||
{include = "lnbits/py.typed"},
|
{include = "lnbits/py.typed"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "~3.12 | ~3.11 | ~3.10"
|
|
||||||
bech32 = "1.2.0"
|
|
||||||
click = "8.2.1"
|
|
||||||
ecdsa = "0.19.1"
|
|
||||||
fastapi = "0.116.1"
|
|
||||||
starlette = "0.47.1"
|
|
||||||
httpx = "0.27.0"
|
|
||||||
jinja2 = "3.1.6"
|
|
||||||
lnurl = "0.7.3"
|
|
||||||
pydantic = "1.10.22"
|
|
||||||
pyqrcode = "1.2.1"
|
|
||||||
shortuuid = "1.0.13"
|
|
||||||
sse-starlette = "2.3.6"
|
|
||||||
typing-extensions = "4.14.0"
|
|
||||||
uvicorn = "0.34.3"
|
|
||||||
sqlalchemy = "1.4.54"
|
|
||||||
aiosqlite = "0.21.0"
|
|
||||||
asyncpg = "0.30.0"
|
|
||||||
uvloop = "0.21.0"
|
|
||||||
websockets = "15.0.1"
|
|
||||||
loguru = "0.7.3"
|
|
||||||
grpcio = "1.69.0"
|
|
||||||
protobuf = "5.29.5"
|
|
||||||
pyln-client = "25.5"
|
|
||||||
pywebpush = "2.0.3"
|
|
||||||
slowapi = "0.1.9"
|
|
||||||
websocket-client = "1.8.0"
|
|
||||||
pycryptodomex = "3.23.0"
|
|
||||||
packaging = "25.0"
|
|
||||||
bolt11 = "2.1.1"
|
|
||||||
pyjwt = "2.10.1"
|
|
||||||
itsdangerous = "2.2.0"
|
|
||||||
fastapi-sso = "0.18.0"
|
|
||||||
# needed for boltz, lnurldevice, watchonly extensions
|
|
||||||
embit = "0.8.0"
|
|
||||||
# needed for cashu, lnurlp, nostrclient, nostrmarket, nostrrelay extensions
|
|
||||||
secp256k1 = "0.14.0"
|
|
||||||
# keep for backwards compatibility with lnurlp and cashu
|
|
||||||
environs = "14.2.0"
|
|
||||||
# needed for scheduler extension
|
|
||||||
python-crontab = "3.2.0"
|
|
||||||
# needed for liquid support boltz
|
|
||||||
wallycore = {version = "1.4.0", optional = true}
|
|
||||||
# needed for breez funding source
|
|
||||||
breez-sdk = {version = "0.8.0", optional = true}
|
|
||||||
breez-sdk-liquid = {version = "0.9.1", optional = true}
|
|
||||||
# needed for migration tests
|
|
||||||
psycopg2-binary = {version = "2.9.10", optional = true}
|
|
||||||
|
|
||||||
jsonpath-ng = "^1.7.0"
|
|
||||||
pynostr = "^0.6.2"
|
|
||||||
python-multipart = "^0.0.20"
|
|
||||||
filetype = "^1.2.0"
|
|
||||||
nostr-sdk = "^0.42.1"
|
|
||||||
bcrypt = "^4.3.0"
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
|
||||||
breez = ["breez-sdk", "breez-sdk-liquid"]
|
|
||||||
liquid = ["wallycore"]
|
|
||||||
migration = ["psycopg2-binary"]
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^25.1.0"
|
black = "^25.1.0"
|
||||||
mypy = "^1.17.1"
|
mypy = "^1.17.1"
|
||||||
|
|
@ -94,14 +114,6 @@ types-mock = "^5.1.0.20240425"
|
||||||
mock = "^5.1.0"
|
mock = "^5.1.0"
|
||||||
grpcio-tools = "^1.69.0"
|
grpcio-tools = "^1.69.0"
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core>=1.0.0"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
|
||||||
lnbits = "lnbits.server:main"
|
|
||||||
lnbits-cli = "lnbits.commands:main"
|
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
include = [
|
include = [
|
||||||
"lnbits",
|
"lnbits",
|
||||||
|
|
@ -255,3 +267,10 @@ extend-immutable-calls = [
|
||||||
"fastapi.Body",
|
"fastapi.Body",
|
||||||
"lnbits.decorators.parse_filters"
|
"lnbits.decorators.parse_filters"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["lnbits"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
@ -14,13 +13,13 @@ class FakeError(Exception):
|
||||||
class DbTestModel(BaseModel):
|
class DbTestModel(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
value: Optional[str] = None
|
value: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class DbTestModel2(BaseModel):
|
class DbTestModel2(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
label: str
|
label: str
|
||||||
description: Optional[str] = None
|
description: str | None = None
|
||||||
child: DbTestModel
|
child: DbTestModel
|
||||||
child_list: list[DbTestModel]
|
child_list: list[DbTestModel]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,28 @@
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class FundingSourceConfig(BaseModel):
|
class FundingSourceConfig(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
skip: Optional[bool]
|
skip: bool | None
|
||||||
wallet_class: str
|
wallet_class: str
|
||||||
settings: dict
|
settings: dict
|
||||||
mock_settings: Optional[dict]
|
mock_settings: dict | None
|
||||||
|
|
||||||
|
|
||||||
class FunctionMock(BaseModel):
|
class FunctionMock(BaseModel):
|
||||||
uri: Optional[str]
|
uri: str | None
|
||||||
query_params: Optional[dict]
|
query_params: dict | None
|
||||||
headers: Optional[dict]
|
headers: dict | None
|
||||||
method: Optional[str]
|
method: str | None
|
||||||
|
|
||||||
|
|
||||||
class TestMock(BaseModel):
|
class TestMock(BaseModel):
|
||||||
skip: Optional[bool]
|
skip: bool | None
|
||||||
description: Optional[str]
|
description: str | None
|
||||||
request_type: Optional[str]
|
request_type: str | None
|
||||||
request_body: Optional[dict]
|
request_body: dict | None
|
||||||
response_type: str
|
response_type: str
|
||||||
response: Union[str, dict, list]
|
response: str | dict | list
|
||||||
|
|
||||||
|
|
||||||
class Mock(FunctionMock, TestMock):
|
class Mock(FunctionMock, TestMock):
|
||||||
|
|
@ -62,13 +60,13 @@ class FunctionData(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class WalletTest(BaseModel):
|
class WalletTest(BaseModel):
|
||||||
skip: Optional[bool]
|
skip: bool | None
|
||||||
function: str
|
function: str
|
||||||
description: str
|
description: str
|
||||||
funding_source: FundingSourceConfig
|
funding_source: FundingSourceConfig
|
||||||
call_params: Optional[dict] = {}
|
call_params: dict | None = {}
|
||||||
expect: Optional[dict]
|
expect: dict | None
|
||||||
expect_error: Optional[dict]
|
expect_error: dict | None
|
||||||
mocks: list[Mock] = []
|
mocks: list[Mock] = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import json
|
import json
|
||||||
from typing import Union
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -59,7 +58,7 @@ async def test_rest_wallet(httpserver: HTTPServer, test_data: WalletTest):
|
||||||
|
|
||||||
|
|
||||||
def _apply_mock(httpserver: HTTPServer, mock: Mock):
|
def _apply_mock(httpserver: HTTPServer, mock: Mock):
|
||||||
request_data: dict[str, Union[str, dict, list]] = {}
|
request_data: dict[str, str | dict | list] = {}
|
||||||
request_type = getattr(mock.dict(), "request_type", None)
|
request_type = getattr(mock.dict(), "request_type", None)
|
||||||
# request_type = mock.request_type <--- this des not work for whatever reason!!!
|
# request_type = mock.request_type <--- this des not work for whatever reason!!!
|
||||||
|
|
||||||
|
|
@ -81,7 +80,7 @@ def _apply_mock(httpserver: HTTPServer, mock: Mock):
|
||||||
**request_data, # type: ignore
|
**request_data, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
server_response: Union[str, dict, list, Response] = mock.response
|
server_response: str | dict | list | Response = mock.response
|
||||||
response_type = mock.response_type
|
response_type = mock.response_type
|
||||||
if response_type == "response":
|
if response_type == "response":
|
||||||
assert isinstance(server_response, dict), "server response must be JSON"
|
assert isinstance(server_response, dict), "server response must be JSON"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import importlib
|
import importlib
|
||||||
from typing import Optional
|
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -167,7 +166,7 @@ def _mock_field(field):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _eval_dict(data: Optional[dict]) -> Optional[dict]:
|
def _eval_dict(data: dict | None) -> dict | None:
|
||||||
fn_prefix = "__eval__:"
|
fn_prefix = "__eval__:"
|
||||||
if not data:
|
if not data:
|
||||||
return data
|
return data
|
||||||
|
|
@ -190,7 +189,7 @@ def _eval_dict(data: Optional[dict]) -> Optional[dict]:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def _dict_to_object(data: Optional[dict]) -> Optional[DataObject]:
|
def _dict_to_object(data: dict | None) -> DataObject | None:
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
# if isinstance(data, list):
|
# if isinstance(data, list):
|
||||||
|
|
@ -213,7 +212,7 @@ def _data_mock(data: dict) -> Mock:
|
||||||
return Mock(return_value=_dict_to_object(data))
|
return Mock(return_value=_dict_to_object(data))
|
||||||
|
|
||||||
|
|
||||||
def _raise(error: Optional[dict]):
|
def _raise(error: dict | None):
|
||||||
if not error:
|
if not error:
|
||||||
return Exception()
|
return Exception()
|
||||||
data = error["data"] if "data" in error else None
|
data = error["data"] if "data" in error else None
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import argparse
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
@ -108,7 +107,7 @@ def insert_to_pg(query, data):
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
def migrate_core(file: str, exclude_tables: Optional[list[str]] = None):
|
def migrate_core(file: str, exclude_tables: list[str] | None = None):
|
||||||
if exclude_tables is None:
|
if exclude_tables is None:
|
||||||
exclude_tables = []
|
exclude_tables = []
|
||||||
print(f"Migrating core: {file}")
|
print(f"Migrating core: {file}")
|
||||||
|
|
@ -124,7 +123,7 @@ def migrate_ext(file: str):
|
||||||
print(f"✅ Migrated ext: {schema}")
|
print(f"✅ Migrated ext: {schema}")
|
||||||
|
|
||||||
|
|
||||||
def migrate_db(file: str, schema: str, exclude_tables: Optional[list[str]] = None):
|
def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None):
|
||||||
# first we check if this file exists:
|
# first we check if this file exists:
|
||||||
if exclude_tables is None:
|
if exclude_tables is None:
|
||||||
exclude_tables = []
|
exclude_tables = []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue