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
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
# cache poetry install via pip
|
||||
cache: "pip"
|
||||
|
||||
- name: Set up Poetry
|
||||
uses: abatilo/actions-poetry@v2
|
||||
|
||||
- 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
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
path: ./.venv
|
||||
key: venv-${{ hashFiles('poetry.lock') }}
|
||||
enable-cache: true
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Install the project dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
poetry env use python${{ inputs.python-version }}
|
||||
poetry install --all-extras
|
||||
run: uv sync --locked --all-extras --dev
|
||||
|
||||
- name: Use Node.js ${{ inputs.node-version }}
|
||||
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"]
|
||||
uses: ./.github/workflows/tests.yml
|
||||
with:
|
||||
custom-pytest: "poetry run pytest tests/api"
|
||||
custom-pytest: "uv run pytest tests/api"
|
||||
python-version: ${{ matrix.python-version }}
|
||||
db-url: ${{ matrix.db-url }}
|
||||
secrets:
|
||||
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||
uses: ./.github/workflows/tests.yml
|
||||
with:
|
||||
custom-pytest: "poetry run pytest tests/wallets"
|
||||
custom-pytest: "uv run pytest tests/wallets"
|
||||
python-version: ${{ matrix.python-version }}
|
||||
db-url: ${{ matrix.db-url }}
|
||||
secrets:
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||
uses: ./.github/workflows/tests.yml
|
||||
with:
|
||||
custom-pytest: "poetry run pytest tests/unit"
|
||||
custom-pytest: "uv run pytest tests/unit"
|
||||
python-version: ${{ matrix.python-version }}
|
||||
db-url: ${{ matrix.db-url }}
|
||||
secrets:
|
||||
|
|
@ -77,7 +77,7 @@ jobs:
|
|||
python-version: ["3.10"]
|
||||
backend-wallet-class: ["LndRestWallet", "LndWallet", "CoreLightningWallet", "CoreLightningRestWallet", "LNbitsWallet", "EclairWallet"]
|
||||
with:
|
||||
custom-pytest: "poetry run pytest tests/regtest"
|
||||
custom-pytest: "uv run pytest tests/regtest"
|
||||
python-version: ${{ matrix.python-version }}
|
||||
backend-wallet-class: ${{ matrix.backend-wallet-class }}
|
||||
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_BACKEND_WALLET_CLASS: FakeWallet
|
||||
run: |
|
||||
poetry run lnbits &
|
||||
uv run lnbits &
|
||||
sleep 10
|
||||
|
||||
- name: setup java version
|
||||
|
|
|
|||
5
.github/workflows/nix.yml
vendored
5
.github/workflows/nix.yml
vendored
|
|
@ -14,18 +14,17 @@ on:
|
|||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
- 'uv.lock'
|
||||
- '.github/workflows/nix.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
- 'uv.lock'
|
||||
|
||||
jobs:
|
||||
nix:
|
||||
if: false # temporarly disable nix support until the `poetry2nix` issue is resolved
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
|||
8
.github/workflows/regtest.yml
vendored
8
.github/workflows/regtest.yml
vendored
|
|
@ -32,6 +32,10 @@ jobs:
|
|||
run: |
|
||||
docker build -t lnbits/lnbits .
|
||||
|
||||
- uses: ./.github/actions/prepare
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Setup Regtest
|
||||
run: |
|
||||
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||
|
|
@ -40,10 +44,6 @@ jobs:
|
|||
./tests
|
||||
sudo chmod -R a+rwx .
|
||||
|
||||
- uses: ./.github/actions/prepare
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Run pytest
|
||||
uses: pavelzw/pytest-action@v2
|
||||
env:
|
||||
|
|
|
|||
27
Dockerfile
27
Dockerfile
|
|
@ -2,26 +2,20 @@ FROM python:3.12-slim-bookworm AS builder
|
|||
|
||||
RUN apt-get clean
|
||||
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"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Only copy the files required to install the dependencies
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN touch README.md
|
||||
|
||||
RUN mkdir data
|
||||
|
||||
ENV POETRY_NO_INTERACTION=1 \
|
||||
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}
|
||||
RUN uv sync --all-extras
|
||||
|
||||
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 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 POETRY_NO_INTERACTION=1 \
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=1 \
|
||||
POETRY_VIRTUALENVS_CREATE=1 \
|
||||
VIRTUAL_ENV=/app/.venv \
|
||||
PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
COPY --from=builder /app/.venv .venv
|
||||
|
||||
ARG POETRY_INSTALL_ARGS="--only main"
|
||||
RUN poetry install ${POETRY_INSTALL_ARGS}
|
||||
RUN uv sync --all-extras
|
||||
|
||||
ENV LNBITS_PORT="5000"
|
||||
ENV LNBITS_HOST="0.0.0.0"
|
||||
|
||||
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
|
||||
|
||||
# Reinstall dependencies just in case (needed for CMD usage)
|
||||
ARG POETRY_INSTALL_ARGS="--only main"
|
||||
RUN poetry install ${POETRY_INSTALL_ARGS}
|
||||
RUN uv sync --all-extras
|
||||
|
||||
# LNbits + boltzd configuration
|
||||
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
|
||||
|
||||
prettier:
|
||||
poetry run ./node_modules/.bin/prettier --write .
|
||||
uv run ./node_modules/.bin/prettier --write .
|
||||
|
||||
pyright:
|
||||
poetry run ./node_modules/.bin/pyright
|
||||
uv run ./node_modules/.bin/pyright
|
||||
|
||||
mypy:
|
||||
poetry run mypy
|
||||
uv run mypy
|
||||
|
||||
black:
|
||||
poetry run black .
|
||||
uv run black .
|
||||
|
||||
ruff:
|
||||
poetry run ruff check . --fix
|
||||
uv run ruff check . --fix
|
||||
|
||||
checkruff:
|
||||
poetry run ruff check .
|
||||
uv run ruff check .
|
||||
|
||||
checkprettier:
|
||||
poetry run ./node_modules/.bin/prettier --check .
|
||||
uv run ./node_modules/.bin/prettier --check .
|
||||
|
||||
checkblack:
|
||||
poetry run black --check .
|
||||
uv run black --check .
|
||||
|
||||
checkeditorconfig:
|
||||
editorconfig-checker
|
||||
|
||||
dev:
|
||||
poetry run lnbits --reload
|
||||
uv run lnbits --reload
|
||||
|
||||
docker:
|
||||
docker build -t lnbits/lnbits .
|
||||
|
|
@ -46,27 +46,27 @@ test-wallets:
|
|||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
poetry run pytest tests/wallets
|
||||
uv run pytest tests/wallets
|
||||
|
||||
test-unit:
|
||||
LNBITS_DATA_FOLDER="./tests/data" \
|
||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
poetry run pytest tests/unit
|
||||
uv run pytest tests/unit
|
||||
|
||||
test-api:
|
||||
LNBITS_DATA_FOLDER="./tests/data" \
|
||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
poetry run pytest tests/api
|
||||
uv run pytest tests/api
|
||||
|
||||
test-regtest:
|
||||
LNBITS_DATA_FOLDER="./tests/data" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
poetry run pytest tests/regtest
|
||||
uv run pytest tests/regtest
|
||||
|
||||
test-migration:
|
||||
LNBITS_ADMIN_UI=True \
|
||||
|
|
@ -74,18 +74,18 @@ test-migration:
|
|||
HOST=0.0.0.0 \
|
||||
PORT=5002 \
|
||||
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 \
|
||||
PORT=5002 \
|
||||
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
||||
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_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
||||
poetry run python tools/conv.py
|
||||
uv run python tools/conv.py
|
||||
|
||||
migration:
|
||||
poetry run python tools/conv.py
|
||||
uv run python tools/conv.py
|
||||
|
||||
openapi:
|
||||
LNBITS_ADMIN_UI=False \
|
||||
|
|
@ -94,9 +94,9 @@ openapi:
|
|||
PYTHONUNBUFFERED=1 \
|
||||
HOST=0.0.0.0 \
|
||||
PORT=5003 \
|
||||
poetry run lnbits &
|
||||
uv run lnbits &
|
||||
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
|
||||
|
||||
bak:
|
||||
|
|
@ -109,7 +109,7 @@ sass:
|
|||
bundle:
|
||||
npm install
|
||||
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:
|
||||
cp lnbits/static/bundle.min.js lnbits/static/bundle.min.js.old
|
||||
|
|
@ -126,8 +126,8 @@ checkbundle:
|
|||
|
||||
install-pre-commit-hook:
|
||||
@echo "Installing pre-commit hook to git"
|
||||
@echo "Uninstall the hook with poetry run pre-commit uninstall"
|
||||
poetry run pre-commit install
|
||||
@echo "Uninstall the hook with uv run pre-commit uninstall"
|
||||
uv run pre-commit install
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Follow the [Basic installation: Option 1 (recommended): poetry](https://docs.lnbits.org/guide/installation.html#option-1-recommended-poetry)
|
||||
guide to install poetry and other dependencies.
|
||||
Follow the [Option 2 (recommended): UV](https://docs.lnbits.org/guide/installation.html)
|
||||
guide to install uv and other dependencies.
|
||||
|
||||
Then you can start LNbits uvicorn server with:
|
||||
|
||||
```bash
|
||||
poetry run lnbits
|
||||
uv run lnbits
|
||||
```
|
||||
|
||||
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
|
||||
make dev
|
||||
# or
|
||||
poetry run lnbits --reload
|
||||
uv run lnbits --reload
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
uv sync --all-extras --dev
|
||||
npm i
|
||||
```
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ make format
|
|||
Run mypy checks:
|
||||
|
||||
```bash
|
||||
poetry run mypy
|
||||
make mypy
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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`:
|
||||
|
||||
```sh
|
||||
$ poetry add <package>
|
||||
```
|
||||
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`:
|
||||
|
||||
**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
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ $ sudo nano .env
|
|||
Now start LNbits once in the terminal window
|
||||
|
||||
```
|
||||
$ poetry run lnbits
|
||||
$ uv run lnbits
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
|
|
@ -39,11 +39,19 @@ It is recommended to use the latest version of Poetry. Make sure you have Python
|
|||
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
|
||||
# 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
|
||||
|
|
@ -51,9 +59,13 @@ curl -sSL https://install.python-poetry.org | python3 - && export PATH="$HOME/.l
|
|||
```sh
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
poetry env use 3.12
|
||||
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
|
||||
# Optional: to set funding source amongst other options via the env `nano .env`
|
||||
```
|
||||
|
|
@ -61,8 +73,11 @@ cp .env.example .env
|
|||
#### Running the server
|
||||
|
||||
```sh
|
||||
poetry run lnbits
|
||||
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
|
||||
uv run lnbits
|
||||
# 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
|
||||
# Note that you have to add the line DEBUG=true in your .env file, too.
|
||||
```
|
||||
|
|
@ -71,7 +86,7 @@ poetry run lnbits
|
|||
|
||||
```sh
|
||||
# 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
|
||||
|
|
@ -85,15 +100,18 @@ cd lnbits
|
|||
git pull --rebase
|
||||
|
||||
# Check your poetry version with
|
||||
poetry env list
|
||||
# poetry env list
|
||||
# If version is less 3.12, update it by running
|
||||
poetry env use python3.12
|
||||
poetry env remove python3.9
|
||||
poetry env list
|
||||
# poetry env use python3.12
|
||||
# poetry env remove python3.9
|
||||
# poetry env list
|
||||
|
||||
# Run install and start LNbits with
|
||||
poetry install --only main
|
||||
poetry run lnbits
|
||||
# poetry install --only main
|
||||
# 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
|
||||
```
|
||||
|
|
@ -108,13 +126,13 @@ chmod +x lnbits.sh &&
|
|||
|
||||
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
|
||||
|
||||
```sh
|
||||
# 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:
|
||||
echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf
|
||||
|
|
@ -332,7 +350,7 @@ LNBITS_DATABASE_URL="postgres://postgres:postgres@localhost/lnbits"
|
|||
|
||||
# START LNbits
|
||||
# STOP LNbits
|
||||
poetry run python tools/conv.py
|
||||
uv run python tools/conv.py
|
||||
# or
|
||||
make migration
|
||||
```
|
||||
|
|
@ -357,8 +375,8 @@ Description=LNbits
|
|||
[Service]
|
||||
# replace with the absolute path of your lnbits installation
|
||||
WorkingDirectory=/home/lnbits/lnbits
|
||||
# same here. run `which poetry` if you can't find the poetry binary
|
||||
ExecStart=/home/lnbits/.local/bin/poetry run lnbits
|
||||
# same here. run `which uv` if you can't find the poetry binary
|
||||
ExecStart=/home/lnbits/.local/bin/uv run lnbits
|
||||
# replace with the user that you're running lnbits on
|
||||
User=lnbits
|
||||
Restart=always
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ You can also use an AES-encrypted macaroon (more info) instead by using
|
|||
|
||||
- `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
|
||||
|
||||
|
|
|
|||
149
flake.lock
generated
149
flake.lock
generated
|
|
@ -1,15 +1,39 @@
|
|||
{
|
||||
"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": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -18,71 +42,49 @@
|
|||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1735563628,
|
||||
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
|
||||
"owner": "nixos",
|
||||
"lastModified": 1751274312,
|
||||
"narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
|
||||
"rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-24.05",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"pyproject-nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems_2",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724134185,
|
||||
"narHash": "sha256-nDqpGjz7cq3ThdC98BPe1ANCNlsJds/LLZ3/MdIXjA0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "5ee730a8752264e463c0eaf06cc060fd07f6dae9",
|
||||
"lastModified": 1754923840,
|
||||
"narHash": "sha256-QSKpYg+Ts9HYF155ltlj40iBex39c05cpOF8gjoE2EM=",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "pyproject.nix",
|
||||
"rev": "023cd4be230eacae52635be09eef100c37ef78da",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "pyproject.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"build-system-pkgs": "build-system-pkgs",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix"
|
||||
"pyproject-nix": "pyproject-nix",
|
||||
"uv2nix": "uv2nix_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
|
@ -100,38 +102,51 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "systems",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"uv2nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"build-system-pkgs",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pyproject-nix": [
|
||||
"build-system-pkgs",
|
||||
"pyproject-nix"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1719749022,
|
||||
"narHash": "sha256-ddPKHcqaKCIFSFc/cvxS14goUhCOAwsM1PbMr0ZtHMg=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "8df5ff62195d4e67e2264df0b7f5e8c9995fd0bd",
|
||||
"lastModified": 1755210905,
|
||||
"narHash": "sha256-WnoFEk79ysjL85TNP7bvImzhxvQw9B6uNtnLd4oJntw=",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "uv2nix",
|
||||
"rev": "87bcba013ef304bbfd67c8e8a257aee634ed5a4c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"owner": "pyproject-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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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 = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
|
||||
poetry2nix = {
|
||||
url = "github:nix-community/poetry2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
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
|
||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
forSystems = systems: f:
|
||||
nixpkgs.lib.genAttrs systems
|
||||
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlays.default self.overlays.default ]; }));
|
||||
forAllSystems = forSystems supportedSystems;
|
||||
projectName = "lnbits";
|
||||
in
|
||||
{
|
||||
overlays = {
|
||||
default = final: prev: {
|
||||
${projectName} = self.packages.${prev.stdenv.hostPlatform.system}.${projectName};
|
||||
};
|
||||
};
|
||||
packages = forAllSystems (system: pkgs: {
|
||||
default = self.packages.${system}.${projectName};
|
||||
${projectName} = pkgs.poetry2nix.mkPoetryApplication {
|
||||
projectDir = ./.;
|
||||
meta.rev = self.dirtyRev or self.rev;
|
||||
meta.mainProgram = projectName;
|
||||
overrides = pkgs.poetry2nix.overrides.withDefaults (final: prev: {
|
||||
coincurve = prev.coincurve.override { preferWheel = true; };
|
||||
protobuf = prev.protobuf.override { preferWheel = true; };
|
||||
ruff = prev.ruff.override { preferWheel = true; };
|
||||
wallycore = prev.wallycore.override { preferWheel = true; };
|
||||
tlv8 = prev.tlv8.overrideAttrs (old: {
|
||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
|
||||
prev.setuptools
|
||||
];
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, uv2nix, pyproject-nix, build-system-pkgs, ... }:
|
||||
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]
|
||||
(system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
lib = pkgs.lib;
|
||||
|
||||
python = pkgs.python312;
|
||||
|
||||
# Read uv.lock / pyproject via uv2nix
|
||||
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
|
||||
|
||||
# Prefer wheels when available
|
||||
uvLockedOverlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; };
|
||||
|
||||
# Helper for extending lists safely (works if a is null)
|
||||
plus = a: b: lib.unique (((if a == null then [] else a)) ++ b);
|
||||
|
||||
# Extra build inputs for troublesome sdists
|
||||
myOverrides = (final: prev: {
|
||||
# embit needs setuptools at build time
|
||||
embit = prev.embit.overrideAttrs (old: {
|
||||
nativeBuildInputs = plus (old.nativeBuildInputs or []) [ prev.setuptools ];
|
||||
});
|
||||
pynostr = prev.pynostr.overrideAttrs (old: {
|
||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
|
||||
prev.setuptools-scm
|
||||
|
||||
# http-ece (pywebpush dep) needs setuptools
|
||||
"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 ];
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
nixosModules = {
|
||||
default = { pkgs, lib, config, ... }: {
|
||||
imports = [ "${./nix/modules/${projectName}-service.nix}" ];
|
||||
nixpkgs.overlays = [ self.overlays.default ];
|
||||
};
|
||||
};
|
||||
checks = forAllSystems (system: pkgs:
|
||||
let
|
||||
vmTests = import ./nix/tests {
|
||||
makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest;
|
||||
inherit inputs pkgs;
|
||||
|
||||
# Compose Python package set honoring uv.lock
|
||||
pythonSet =
|
||||
(pkgs.callPackage pyproject-nix.build.packages { inherit python; })
|
||||
.overrideScope (lib.composeManyExtensions [
|
||||
build-system-pkgs.overlays.default
|
||||
uvLockedOverlay
|
||||
myOverrides
|
||||
]);
|
||||
|
||||
projectName = "lnbits";
|
||||
|
||||
# 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
|
||||
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
|
||||
sudo apt install -y python3.10 python3.10-distutils
|
||||
|
||||
# Install Poetry
|
||||
curl -sSL https://install.python-poetry.org | python3.10 -
|
||||
# Install UV
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Add Poetry to PATH for the current session
|
||||
export PATH="/home/$USER/.local/bin:$PATH"
|
||||
|
||||
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; }
|
||||
fi
|
||||
|
||||
# Install the dependencies using Poetry
|
||||
poetry env use python3.9
|
||||
poetry install --only main
|
||||
# Install the dependencies using UV
|
||||
uv sync --all-extras
|
||||
|
||||
|
||||
# Set environment variables for LNbits
|
||||
export LNBITS_ADMIN_UI=true
|
||||
export HOST=0.0.0.0
|
||||
|
||||
# Run LNbits
|
||||
poetry run lnbits
|
||||
uv run lnbits
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import os
|
|||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
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
|
||||
include_deactivated: Optional[bool] = True,
|
||||
include_deactivated: bool | None = True,
|
||||
) -> list[InstallableExtension]:
|
||||
"""
|
||||
Returns a list of all the installed extensions plus the extensions that
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import time
|
|||
from functools import wraps
|
||||
from getpass import getpass
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
import click
|
||||
|
|
@ -96,7 +95,7 @@ def decrypt():
|
|||
"""
|
||||
|
||||
|
||||
def get_super_user() -> Optional[str]:
|
||||
def get_super_user() -> str | None:
|
||||
"""Get the superuser"""
|
||||
superuser_file = Path(settings.lnbits_data_folder, ".super_user")
|
||||
if not superuser_file.exists() or not superuser_file.is_file():
|
||||
|
|
@ -155,7 +154,7 @@ async def db_versions():
|
|||
@db.command("cleanup-wallets")
|
||||
@click.argument("days", type=int, required=False)
|
||||
@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"""
|
||||
async with core_db.connect() as conn:
|
||||
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.")
|
||||
@coro
|
||||
async def check_invalid_payments(
|
||||
days: Optional[int] = None,
|
||||
limit: Optional[int] = None,
|
||||
wallet: Optional[str] = None,
|
||||
verbose: Optional[bool] = False,
|
||||
days: int | None = None,
|
||||
limit: int | None = None,
|
||||
wallet: str | None = None,
|
||||
verbose: bool | None = False,
|
||||
):
|
||||
"""Check payments that are settled in the DB but pending on the Funding Source"""
|
||||
await check_admin_settings()
|
||||
|
|
@ -303,7 +302,7 @@ async def create_user(username: str, password: str):
|
|||
@users.command("cleanup-accounts")
|
||||
@click.argument("days", type=int, required=False)
|
||||
@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"""
|
||||
async with core_db.connect() as conn:
|
||||
delta = days or settings.cleanup_wallets_days
|
||||
|
|
@ -353,12 +352,12 @@ async def extensions_list():
|
|||
)
|
||||
@coro
|
||||
async def extensions_update( # noqa: C901
|
||||
extension: Optional[str] = None,
|
||||
all_extensions: Optional[bool] = False,
|
||||
repo_index: Optional[str] = None,
|
||||
source_repo: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
admin_user: Optional[str] = None,
|
||||
extension: str | None = None,
|
||||
all_extensions: bool | None = False,
|
||||
repo_index: str | None = None,
|
||||
source_repo: str | None = None,
|
||||
url: str | None = None,
|
||||
admin_user: str | None = None,
|
||||
):
|
||||
"""
|
||||
Update extension to the latest version.
|
||||
|
|
@ -443,10 +442,10 @@ async def extensions_update( # noqa: C901
|
|||
@coro
|
||||
async def extensions_install(
|
||||
extension: str,
|
||||
repo_index: Optional[str] = None,
|
||||
source_repo: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
admin_user: Optional[str] = None,
|
||||
repo_index: str | None = None,
|
||||
source_repo: str | None = None,
|
||||
url: str | None = None,
|
||||
admin_user: str | None = None,
|
||||
):
|
||||
"""Install a extension"""
|
||||
click.echo(f"Installing {extension}... {repo_index}")
|
||||
|
|
@ -473,7 +472,7 @@ async def extensions_install(
|
|||
)
|
||||
@coro
|
||||
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"""
|
||||
click.echo(f"Uninstalling '{extension}'...")
|
||||
|
|
@ -562,10 +561,10 @@ if __name__ == "__main__":
|
|||
|
||||
async def install_extension(
|
||||
extension: str,
|
||||
repo_index: Optional[str] = None,
|
||||
source_repo: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
admin_user: Optional[str] = None,
|
||||
repo_index: str | None = None,
|
||||
source_repo: str | None = None,
|
||||
url: str | None = None,
|
||||
admin_user: str | None = None,
|
||||
) -> tuple[bool, str]:
|
||||
try:
|
||||
release = await _select_release(extension, repo_index, source_repo)
|
||||
|
|
@ -591,10 +590,10 @@ async def install_extension(
|
|||
|
||||
async def update_extension(
|
||||
extension: str,
|
||||
repo_index: Optional[str] = None,
|
||||
source_repo: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
admin_user: Optional[str] = None,
|
||||
repo_index: str | None = None,
|
||||
source_repo: str | None = None,
|
||||
url: str | None = None,
|
||||
admin_user: str | None = None,
|
||||
) -> tuple[bool, str]:
|
||||
try:
|
||||
click.echo(f"Updating '{extension}' extension.")
|
||||
|
|
@ -644,9 +643,9 @@ async def update_extension(
|
|||
|
||||
async def _select_release(
|
||||
extension: str,
|
||||
repo_index: Optional[str] = None,
|
||||
source_repo: Optional[str] = None,
|
||||
) -> Optional[ExtensionRelease]:
|
||||
repo_index: str | None = None,
|
||||
source_repo: str | None = None,
|
||||
) -> ExtensionRelease | None:
|
||||
all_releases = await InstallableExtension.get_extension_releases(extension)
|
||||
if len(all_releases) == 0:
|
||||
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(
|
||||
data: CreateExtension, url: Optional[str], user_id: Optional[str] = None
|
||||
data: CreateExtension, url: str | None, user_id: str | None = None
|
||||
):
|
||||
if url:
|
||||
user_id = user_id or get_super_user()
|
||||
|
|
@ -720,7 +719,7 @@ async def _call_install_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:
|
||||
user_id = user_id or get_super_user()
|
||||
|
|
@ -756,7 +755,7 @@ async def _can_run_operation(url) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
async def _is_lnbits_started(url: Optional[str]):
|
||||
async def _is_lnbits_started(url: str | None):
|
||||
try:
|
||||
url = url or f"http://{settings.host}:{settings.port}/api/v1/health"
|
||||
async with httpx.AsyncClient() as client:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from lnbits.core.db import db
|
||||
from lnbits.core.models import AuditEntry, AuditFilters
|
||||
from lnbits.core.models.audit import AuditCountStat
|
||||
|
|
@ -8,14 +6,14 @@ from lnbits.db import Connection, Filters, Page
|
|||
|
||||
async def create_audit_entry(
|
||||
entry: AuditEntry,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
await (conn or db).insert("audit", entry)
|
||||
|
||||
|
||||
async def get_audit_entries(
|
||||
filters: Optional[Filters[AuditFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[AuditFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Page[AuditEntry]:
|
||||
return await (conn or db).fetch_page(
|
||||
"SELECT * from audit",
|
||||
|
|
@ -27,7 +25,7 @@ async def get_audit_entries(
|
|||
|
||||
|
||||
async def delete_expired_audit_entries(
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
):
|
||||
await (conn or db).execute(
|
||||
# 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(
|
||||
field: str,
|
||||
filters: Optional[Filters[AuditFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[AuditFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[AuditCountStat]:
|
||||
if field not in ["request_method", "component", "response_code"]:
|
||||
return []
|
||||
|
|
@ -67,8 +65,8 @@ async def get_count_stats(
|
|||
|
||||
|
||||
async def get_long_duration_stats(
|
||||
filters: Optional[Filters[AuditFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[AuditFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[AuditCountStat]:
|
||||
if not filters:
|
||||
filters = Filters()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from lnbits.core.db import db
|
||||
from lnbits.db import Connection
|
||||
|
||||
|
|
@ -7,8 +5,8 @@ from ..models import DbVersion
|
|||
|
||||
|
||||
async def get_db_version(
|
||||
ext_id: str, conn: Optional[Connection] = None
|
||||
) -> Optional[DbVersion]:
|
||||
ext_id: str, conn: Connection | None = None
|
||||
) -> DbVersion | None:
|
||||
return await (conn or db).fetchone(
|
||||
"SELECT * FROM dbversions WHERE db = :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)
|
||||
|
||||
|
||||
|
|
@ -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(
|
||||
"""
|
||||
DELETE FROM dbversions WHERE db = :ext
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from lnbits.core.db import db
|
||||
from lnbits.core.models.extensions import (
|
||||
InstallableExtension,
|
||||
|
|
@ -10,20 +8,20 @@ from lnbits.db import Connection, Database
|
|||
|
||||
async def create_installed_extension(
|
||||
ext: InstallableExtension,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
await (conn or db).insert("installed_extensions", ext)
|
||||
|
||||
|
||||
async def update_installed_extension(
|
||||
ext: InstallableExtension,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
await (conn or db).update("installed_extensions", ext)
|
||||
|
||||
|
||||
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:
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
|
|
@ -34,7 +32,7 @@ async def update_installed_extension_state(
|
|||
|
||||
|
||||
async def delete_installed_extension(
|
||||
*, ext_id: str, conn: Optional[Connection] = None
|
||||
*, ext_id: str, conn: Connection | None = None
|
||||
) -> None:
|
||||
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(
|
||||
"SELECT * FROM dbversions WHERE db = :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(
|
||||
ext_id: str, conn: Optional[Connection] = None
|
||||
) -> Optional[InstallableExtension]:
|
||||
ext_id: str, conn: Connection | None = None
|
||||
) -> InstallableExtension | None:
|
||||
extension = await (conn or db).fetchone(
|
||||
"SELECT * FROM installed_extensions WHERE id = :id",
|
||||
{"id": ext_id},
|
||||
|
|
@ -76,8 +74,8 @@ async def get_installed_extension(
|
|||
|
||||
|
||||
async def get_installed_extensions(
|
||||
active: Optional[bool] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
active: bool | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[InstallableExtension]:
|
||||
query = "SELECT * FROM installed_extensions"
|
||||
if active is not None:
|
||||
|
|
@ -93,8 +91,8 @@ async def get_installed_extensions(
|
|||
|
||||
|
||||
async def get_user_extension(
|
||||
user_id: str, extension: str, conn: Optional[Connection] = None
|
||||
) -> Optional[UserExtension]:
|
||||
user_id: str, extension: str, conn: Connection | None = None
|
||||
) -> UserExtension | None:
|
||||
return await (conn or db).fetchone(
|
||||
"""
|
||||
SELECT * FROM extensions
|
||||
|
|
@ -106,7 +104,7 @@ async def get_user_extension(
|
|||
|
||||
|
||||
async def get_user_extensions(
|
||||
user_id: str, conn: Optional[Connection] = None
|
||||
user_id: str, conn: Connection | None = None
|
||||
) -> list[UserExtension]:
|
||||
return await (conn or db).fetchall(
|
||||
"""SELECT * FROM extensions WHERE "user" = :user""",
|
||||
|
|
@ -116,20 +114,20 @@ async def get_user_extensions(
|
|||
|
||||
|
||||
async def create_user_extension(
|
||||
user_extension: UserExtension, conn: Optional[Connection] = None
|
||||
user_extension: UserExtension, conn: Connection | None = None
|
||||
) -> None:
|
||||
await (conn or db).insert("extensions", user_extension)
|
||||
|
||||
|
||||
async def update_user_extension(
|
||||
user_extension: UserExtension, conn: Optional[Connection] = None
|
||||
user_extension: UserExtension, conn: Connection | None = None
|
||||
) -> None:
|
||||
where = """WHERE extension = :extension AND "user" = :user"""
|
||||
await (conn or db).update("extensions", user_extension, where)
|
||||
|
||||
|
||||
async def get_user_active_extensions_ids(
|
||||
user_id: str, conn: Optional[Connection] = None
|
||||
user_id: str, conn: Connection | None = None
|
||||
) -> list[str]:
|
||||
exts = await (conn or db).fetchall(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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.db import db
|
||||
|
|
@ -23,7 +23,7 @@ def update_payment_extra():
|
|||
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(
|
||||
"SELECT * FROM apipayments WHERE 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(
|
||||
checking_id_or_hash: str,
|
||||
conn: Optional[Connection] = None,
|
||||
incoming: Optional[bool] = False,
|
||||
wallet_id: Optional[str] = None,
|
||||
) -> Optional[Payment]:
|
||||
conn: Connection | None = None,
|
||||
incoming: bool | None = False,
|
||||
wallet_id: str | None = None,
|
||||
) -> Payment | None:
|
||||
clause: str = "checking_id = :checking_id OR payment_hash = :hash"
|
||||
values = {
|
||||
"wallet_id": wallet_id,
|
||||
|
|
@ -64,8 +64,8 @@ async def get_standalone_payment(
|
|||
|
||||
|
||||
async def get_wallet_payment(
|
||||
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Payment]:
|
||||
wallet_id: str, payment_hash: str, conn: Connection | None = None
|
||||
) -> Payment | None:
|
||||
payment = await (conn or db).fetchone(
|
||||
"""
|
||||
SELECT *
|
||||
|
|
@ -102,17 +102,17 @@ async def get_latest_payments_by_extension(
|
|||
|
||||
async def get_payments_paginated( # noqa: C901
|
||||
*,
|
||||
wallet_id: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
wallet_id: str | None = None,
|
||||
user_id: str | None = None,
|
||||
complete: bool = False,
|
||||
pending: bool = False,
|
||||
failed: bool = False,
|
||||
outgoing: bool = False,
|
||||
incoming: bool = False,
|
||||
since: Optional[int] = None,
|
||||
since: int | None = None,
|
||||
exclude_uncheckable: bool = False,
|
||||
filters: Optional[Filters[PaymentFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[PaymentFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Page[Payment]:
|
||||
"""
|
||||
Filters payments to be returned by:
|
||||
|
|
@ -176,17 +176,17 @@ async def get_payments_paginated( # noqa: C901
|
|||
|
||||
async def get_payments(
|
||||
*,
|
||||
wallet_id: Optional[str] = None,
|
||||
wallet_id: str | None = None,
|
||||
complete: bool = False,
|
||||
pending: bool = False,
|
||||
outgoing: bool = False,
|
||||
incoming: bool = False,
|
||||
since: Optional[int] = None,
|
||||
since: int | None = None,
|
||||
exclude_uncheckable: bool = False,
|
||||
filters: Optional[Filters[PaymentFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
limit: Optional[int] = None,
|
||||
offset: Optional[int] = None,
|
||||
filters: Filters[PaymentFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
limit: int | None = None,
|
||||
offset: int | None = None,
|
||||
) -> list[Payment]:
|
||||
"""
|
||||
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(
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
# first we delete all invoices older than one month
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ async def create_payment(
|
|||
checking_id: str,
|
||||
data: CreatePayment,
|
||||
status: PaymentState = PaymentState.PENDING,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Payment:
|
||||
# we don't allow the creation of the same invoice twice
|
||||
# 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(
|
||||
checking_id: str, new_checking_id: str, conn: Optional[Connection] = None
|
||||
checking_id: str, new_checking_id: str, conn: Connection | None = None
|
||||
) -> None:
|
||||
await (conn or db).execute(
|
||||
"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(
|
||||
payment: Payment,
|
||||
new_checking_id: Optional[str] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
new_checking_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
await (conn or db).update(
|
||||
"apipayments", payment, "WHERE checking_id = :checking_id"
|
||||
|
|
@ -311,9 +311,9 @@ async def update_payment(
|
|||
|
||||
|
||||
async def get_payments_history(
|
||||
wallet_id: Optional[str] = None,
|
||||
wallet_id: str | None = None,
|
||||
group: DateTrunc = "day",
|
||||
filters: Optional[Filters] = None,
|
||||
filters: Filters | None = None,
|
||||
) -> list[PaymentHistoryPoint]:
|
||||
if not filters:
|
||||
filters = Filters()
|
||||
|
|
@ -376,9 +376,9 @@ async def get_payments_history(
|
|||
|
||||
async def get_payment_count_stats(
|
||||
field: PaymentCountField,
|
||||
filters: Optional[Filters[PaymentFilters]] = None,
|
||||
user_id: Optional[str] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[PaymentFilters] | None = None,
|
||||
user_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[PaymentCountStat]:
|
||||
|
||||
if not filters:
|
||||
|
|
@ -409,9 +409,9 @@ async def get_payment_count_stats(
|
|||
|
||||
|
||||
async def get_daily_stats(
|
||||
filters: Optional[Filters[PaymentFilters]] = None,
|
||||
user_id: Optional[str] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[PaymentFilters] | None = None,
|
||||
user_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
|
||||
|
||||
if not filters:
|
||||
|
|
@ -459,9 +459,9 @@ async def get_daily_stats(
|
|||
|
||||
|
||||
async def get_wallets_stats(
|
||||
filters: Optional[Filters[PaymentFilters]] = None,
|
||||
user_id: Optional[str] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[PaymentFilters] | None = None,
|
||||
user_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[PaymentWalletStats]:
|
||||
|
||||
if not filters:
|
||||
|
|
@ -508,7 +508,7 @@ async def get_wallets_stats(
|
|||
|
||||
|
||||
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:
|
||||
await (conn or db).execute(
|
||||
"DELETE FROM apipayments WHERE checking_id = :checking_id AND wallet = :wallet",
|
||||
|
|
@ -517,8 +517,8 @@ async def delete_wallet_payment(
|
|||
|
||||
|
||||
async def check_internal(
|
||||
payment_hash: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Payment]:
|
||||
payment_hash: str, conn: Connection | None = None
|
||||
) -> Payment | None:
|
||||
"""
|
||||
Returns the checking_id of the internal payment if it exists,
|
||||
otherwise None
|
||||
|
|
@ -534,7 +534,7 @@ async def check_internal(
|
|||
|
||||
|
||||
async def is_internal_status_success(
|
||||
payment_hash: str, conn: Optional[Connection] = None
|
||||
payment_hash: str, conn: Connection | None = None
|
||||
) -> bool:
|
||||
"""
|
||||
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(
|
||||
user_id: str, conn: Optional[Connection] = None
|
||||
user_id: str, conn: Connection | None = None
|
||||
) -> str:
|
||||
wallet_ids = await get_wallets_ids(user_id=user_id, conn=conn) or [
|
||||
"no-wallets-for-user"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
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")
|
||||
if data:
|
||||
super_user = await get_settings_field("super_user")
|
||||
|
|
@ -24,7 +24,7 @@ async def get_super_settings() -> Optional[SuperSettings]:
|
|||
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()
|
||||
if not sets:
|
||||
return None
|
||||
|
|
@ -41,7 +41,7 @@ async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSetti
|
|||
|
||||
|
||||
async def update_admin_settings(
|
||||
data: EditableSettings, tag: Optional[str] = "core"
|
||||
data: EditableSettings, tag: str | None = "core"
|
||||
) -> None:
|
||||
editable_settings = await get_settings_by_tag("core") or {}
|
||||
editable_settings.update(data.dict(exclude_unset=True))
|
||||
|
|
@ -61,7 +61,7 @@ async def update_super_user(super_user: str) -> SuperSettings:
|
|||
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(
|
||||
"DELETE FROM system_settings WHERE 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(
|
||||
id_: str, tag: Optional[str] = "core"
|
||||
) -> Optional[SettingsField]:
|
||||
id_: str, tag: str | None = "core"
|
||||
) -> SettingsField | None:
|
||||
|
||||
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"])
|
||||
|
||||
|
||||
async def set_settings_field(
|
||||
id_: str, value: Optional[Any], tag: Optional[str] = "core"
|
||||
):
|
||||
async def set_settings_field(id_: str, value: Any | None, tag: str | None = "core"):
|
||||
value = json.dumps(value) if value is not None else None
|
||||
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(
|
||||
"SELECT * FROM system_settings WHERE tag = :tag", {"tag": tag}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
import shortuuid
|
||||
|
||||
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)
|
||||
|
||||
|
||||
async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]:
|
||||
async def get_tinyurl(tinyurl_id: str) -> TinyURL | None:
|
||||
return await db.fetchone(
|
||||
"SELECT * FROM tiny_url WHERE id = :tinyurl",
|
||||
{"tinyurl": tinyurl_id},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from datetime import datetime, timezone
|
||||
from time import time
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from lnbits.core.crud.extensions import get_user_active_extensions_ids
|
||||
|
|
@ -18,8 +18,8 @@ from ..models import (
|
|||
|
||||
|
||||
async def create_account(
|
||||
account: Optional[Account] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
account: Account | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Account:
|
||||
if account:
|
||||
account.validate_fields()
|
||||
|
|
@ -36,7 +36,7 @@ async def update_account(account: Account) -> 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(
|
||||
"DELETE from accounts WHERE id = :user",
|
||||
{"user": user_id},
|
||||
|
|
@ -44,8 +44,8 @@ async def delete_account(user_id: str, conn: Optional[Connection] = None) -> Non
|
|||
|
||||
|
||||
async def get_accounts(
|
||||
filters: Optional[Filters[AccountFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
filters: Filters[AccountFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Page[AccountOverview]:
|
||||
where_clauses = []
|
||||
values: dict[str, Any] = {}
|
||||
|
|
@ -92,9 +92,7 @@ async def get_accounts(
|
|||
)
|
||||
|
||||
|
||||
async def get_account(
|
||||
user_id: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
async def get_account(user_id: str, conn: Connection | None = None) -> Account | None:
|
||||
if len(user_id) == 0:
|
||||
return None
|
||||
return await (conn or db).fetchone(
|
||||
|
|
@ -106,7 +104,7 @@ async def get_account(
|
|||
|
||||
async def delete_accounts_no_wallets(
|
||||
time_delta: int,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
delta = int(time()) - time_delta
|
||||
await (conn or db).execute(
|
||||
|
|
@ -125,8 +123,8 @@ async def delete_accounts_no_wallets(
|
|||
|
||||
|
||||
async def get_account_by_username(
|
||||
username: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
username: str, conn: Connection | None = None
|
||||
) -> Account | None:
|
||||
if len(username) == 0:
|
||||
return None
|
||||
return await (conn or db).fetchone(
|
||||
|
|
@ -137,8 +135,8 @@ async def get_account_by_username(
|
|||
|
||||
|
||||
async def get_account_by_pubkey(
|
||||
pubkey: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
pubkey: str, conn: Connection | None = None
|
||||
) -> Account | None:
|
||||
return await (conn or db).fetchone(
|
||||
"SELECT * FROM accounts WHERE LOWER(pubkey) = :pubkey",
|
||||
{"pubkey": pubkey.lower()},
|
||||
|
|
@ -147,8 +145,8 @@ async def get_account_by_pubkey(
|
|||
|
||||
|
||||
async def get_account_by_email(
|
||||
email: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
email: str, conn: Connection | None = None
|
||||
) -> Account | None:
|
||||
if len(email) == 0:
|
||||
return None
|
||||
return await (conn or db).fetchone(
|
||||
|
|
@ -159,8 +157,8 @@ async def get_account_by_email(
|
|||
|
||||
|
||||
async def get_account_by_username_or_email(
|
||||
username_or_email: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
username_or_email: str, conn: Connection | None = None
|
||||
) -> Account | None:
|
||||
return await (conn or db).fetchone(
|
||||
"""
|
||||
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)
|
||||
if not account:
|
||||
return None
|
||||
|
|
@ -179,8 +177,8 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
|
|||
|
||||
|
||||
async def get_user_from_account(
|
||||
account: Account, conn: Optional[Connection] = None
|
||||
) -> Optional[User]:
|
||||
account: Account, conn: Connection | None = None
|
||||
) -> User | None:
|
||||
extensions = await get_user_active_extensions_ids(account.id, conn)
|
||||
wallets = await get_wallets(account.id, False, conn=conn)
|
||||
return User(
|
||||
|
|
@ -207,7 +205,7 @@ async def update_user_access_control_list(user_acls: UserAcls):
|
|||
|
||||
|
||||
async def get_user_access_control_lists(
|
||||
user_id: str, conn: Optional[Connection] = None
|
||||
user_id: str, conn: Connection | None = None
|
||||
) -> UserAcls:
|
||||
user_acls = await (conn or db).fetchone(
|
||||
"SELECT id, access_control_list FROM accounts WHERE id = :id",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from datetime import datetime, timezone
|
||||
from time import time
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from lnbits.core.db import db
|
||||
|
|
@ -14,8 +13,8 @@ from ..models import Wallet
|
|||
async def create_wallet(
|
||||
*,
|
||||
user_id: str,
|
||||
wallet_name: Optional[str] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
wallet_name: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Wallet:
|
||||
wallet_id = uuid4().hex
|
||||
wallet = Wallet(
|
||||
|
|
@ -32,7 +31,7 @@ async def create_wallet(
|
|||
|
||||
async def update_wallet(
|
||||
wallet: Wallet,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Wallet:
|
||||
wallet.updated_at = datetime.now(timezone.utc)
|
||||
await (conn or db).update("wallets", wallet)
|
||||
|
|
@ -44,7 +43,7 @@ async def delete_wallet(
|
|||
user_id: str,
|
||||
wallet_id: str,
|
||||
deleted: bool = True,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
now = int(time())
|
||||
await (conn or db).execute(
|
||||
|
|
@ -58,9 +57,7 @@ async def delete_wallet(
|
|||
)
|
||||
|
||||
|
||||
async def force_delete_wallet(
|
||||
wallet_id: str, conn: Optional[Connection] = None
|
||||
) -> None:
|
||||
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
|
||||
await (conn or db).execute(
|
||||
"DELETE FROM wallets WHERE id = :wallet",
|
||||
{"wallet": wallet_id},
|
||||
|
|
@ -68,8 +65,8 @@ async def force_delete_wallet(
|
|||
|
||||
|
||||
async def delete_wallet_by_id(
|
||||
wallet_id: str, conn: Optional[Connection] = None
|
||||
) -> Optional[int]:
|
||||
wallet_id: str, conn: Connection | None = None
|
||||
) -> int | None:
|
||||
now = int(time())
|
||||
result = await (conn or db).execute(
|
||||
# Timestamp placeholder is safe from SQL injection (not user input)
|
||||
|
|
@ -83,13 +80,13 @@ async def delete_wallet_by_id(
|
|||
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")
|
||||
|
||||
|
||||
async def delete_unused_wallets(
|
||||
time_delta: int,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> None:
|
||||
delta = int(time()) - time_delta
|
||||
await (conn or db).execute(
|
||||
|
|
@ -107,8 +104,8 @@ async def delete_unused_wallets(
|
|||
|
||||
|
||||
async def get_wallet(
|
||||
wallet_id: str, deleted: Optional[bool] = None, conn: Optional[Connection] = None
|
||||
) -> Optional[Wallet]:
|
||||
wallet_id: str, deleted: bool | None = None, conn: Connection | None = None
|
||||
) -> Wallet | None:
|
||||
query = """
|
||||
SELECT *, COALESCE((
|
||||
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
||||
|
|
@ -125,7 +122,7 @@ async def get_wallet(
|
|||
|
||||
|
||||
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]:
|
||||
query = """
|
||||
SELECT *, COALESCE((
|
||||
|
|
@ -144,9 +141,9 @@ async def get_wallets(
|
|||
|
||||
async def get_wallets_paginated(
|
||||
user_id: str,
|
||||
deleted: Optional[bool] = None,
|
||||
filters: Optional[Filters[WalletsFilters]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
deleted: bool | None = None,
|
||||
filters: Filters[WalletsFilters] | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Page[Wallet]:
|
||||
if deleted is None:
|
||||
deleted = False
|
||||
|
|
@ -166,7 +163,7 @@ async def get_wallets_paginated(
|
|||
|
||||
|
||||
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]:
|
||||
query = """SELECT id FROM wallets WHERE "user" = :user"""
|
||||
if deleted is not None:
|
||||
|
|
@ -186,8 +183,8 @@ async def get_wallets_count():
|
|||
|
||||
async def get_wallet_for_key(
|
||||
key: str,
|
||||
conn: Optional[Connection] = None,
|
||||
) -> Optional[Wallet]:
|
||||
conn: Connection | None = None,
|
||||
) -> Wallet | None:
|
||||
return await (conn or db).fetchone(
|
||||
"""
|
||||
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")
|
||||
row = result.mappings().first()
|
||||
return row.get("balance", 0) or 0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from lnbits.core.db import db
|
||||
|
||||
from ..models import WebPushSubscription
|
||||
|
|
@ -7,7 +5,7 @@ from ..models import WebPushSubscription
|
|||
|
||||
async def get_webpush_subscription(
|
||||
endpoint: str, user: str
|
||||
) -> Optional[WebPushSubscription]:
|
||||
) -> WebPushSubscription | None:
|
||||
return await db.fetchone(
|
||||
"""
|
||||
SELECT * FROM webpush_subscriptions
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import importlib
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
from uuid import UUID
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ from lnbits.settings import settings
|
|||
|
||||
|
||||
async def migrate_extension_database(
|
||||
ext: InstallableExtension, current_version: Optional[DbVersion] = None
|
||||
ext: InstallableExtension, current_version: DbVersion | None = None
|
||||
):
|
||||
|
||||
try:
|
||||
|
|
@ -38,7 +38,7 @@ async def run_migration(
|
|||
db: Connection,
|
||||
migrations_module: Any,
|
||||
db_name: str,
|
||||
current_version: Optional[DbVersion] = None,
|
||||
current_version: DbVersion | None = None,
|
||||
):
|
||||
matcher = re.compile(r"^m(\d\d\d)_")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from time import time
|
||||
from typing import Optional
|
||||
|
||||
from lnurl import LnAddress, Lnurl, LnurlPayResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
|
@ -9,9 +8,9 @@ class CreateLnurlPayment(BaseModel):
|
|||
res: LnurlPayResponse | None = None
|
||||
lnurl: Lnurl | LnAddress | None = None
|
||||
amount: int
|
||||
comment: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
internal_memo: Optional[str] = None
|
||||
comment: str | None = None
|
||||
unit: str | None = None
|
||||
internal_memo: str | None = None
|
||||
|
||||
|
||||
class CreateLnurlWithdraw(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import asyncio
|
||||
import importlib
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -145,7 +144,7 @@ async def start_extension_background_work(ext_id: str) -> bool:
|
|||
|
||||
|
||||
async def get_valid_extensions(
|
||||
include_deactivated: Optional[bool] = True,
|
||||
include_deactivated: bool | None = True,
|
||||
) -> list[Extension]:
|
||||
installed_extensions = await get_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(
|
||||
ext_id: str, include_deactivated: Optional[bool] = True
|
||||
) -> Optional[Extension]:
|
||||
ext_id: str, include_deactivated: bool | None = True
|
||||
) -> Extension | None:
|
||||
ext = await get_installed_extension(ext_id)
|
||||
if not ext:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import hashlib
|
||||
import hmac
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -15,7 +14,7 @@ from lnbits.settings import settings
|
|||
|
||||
|
||||
async def handle_fiat_payment_confirmation(
|
||||
payment: Payment, conn: Optional[Connection] = None
|
||||
payment: Payment, conn: Connection | None = None
|
||||
):
|
||||
try:
|
||||
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(
|
||||
payment: Payment, conn: Optional[Connection] = None
|
||||
payment: Payment, conn: Connection | None = None
|
||||
):
|
||||
fiat_provider_name = payment.fiat_provider
|
||||
if not fiat_provider_name:
|
||||
|
|
@ -66,7 +65,7 @@ async def _credit_fiat_service_fee_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
|
||||
if not fiat_provider_name:
|
||||
|
|
@ -129,8 +128,8 @@ async def handle_stripe_event(event: dict):
|
|||
|
||||
def check_stripe_signature(
|
||||
payload: bytes,
|
||||
sig_header: Optional[str],
|
||||
secret: Optional[str],
|
||||
sig_header: str | None,
|
||||
secret: str | None,
|
||||
tolerance_seconds=300,
|
||||
):
|
||||
if not sig_header:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import smtplib
|
|||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -71,7 +70,7 @@ async def process_next_notification() -> None:
|
|||
|
||||
async def send_admin_notification(
|
||||
message: str,
|
||||
message_type: Optional[str] = None,
|
||||
message_type: str | None = None,
|
||||
) -> None:
|
||||
return await send_notification(
|
||||
settings.lnbits_telegram_notifications_chat_id,
|
||||
|
|
@ -85,7 +84,7 @@ async def send_admin_notification(
|
|||
async def send_user_notification(
|
||||
user_notifications: UserNotifications,
|
||||
message: str,
|
||||
message_type: Optional[str] = None,
|
||||
message_type: str | None = None,
|
||||
) -> None:
|
||||
|
||||
email_address = (
|
||||
|
|
@ -110,7 +109,7 @@ async def send_notification(
|
|||
nostr_identifiers: list[str] | None,
|
||||
email_addresses: list[str] | None,
|
||||
message: str,
|
||||
message_type: Optional[str] = None,
|
||||
message_type: str | None = None,
|
||||
) -> None:
|
||||
try:
|
||||
if telegram_chat_id and settings.is_telegram_notifications_configured():
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
from bolt11 import Bolt11, MilliSatoshi, Tags
|
||||
from bolt11 import decode as bolt11_decode
|
||||
|
|
@ -58,11 +57,11 @@ async def pay_invoice(
|
|||
*,
|
||||
wallet_id: str,
|
||||
payment_request: str,
|
||||
max_sat: Optional[int] = None,
|
||||
extra: Optional[dict] = None,
|
||||
max_sat: int | None = None,
|
||||
extra: dict | None = None,
|
||||
description: str = "",
|
||||
tag: str = "",
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Payment:
|
||||
if settings.lnbits_only_allow_incoming_payments:
|
||||
raise PaymentError("Only incoming payments allowed.", status="failed")
|
||||
|
|
@ -110,7 +109,7 @@ async def create_payment_request(
|
|||
|
||||
|
||||
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
|
||||
if not fiat_provider_name:
|
||||
|
|
@ -231,16 +230,16 @@ async def create_invoice(
|
|||
*,
|
||||
wallet_id: str,
|
||||
amount: float,
|
||||
currency: Optional[str] = "sat",
|
||||
currency: str | None = "sat",
|
||||
memo: str,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
expiry: Optional[int] = None,
|
||||
extra: Optional[dict] = None,
|
||||
webhook: Optional[str] = None,
|
||||
internal: Optional[bool] = False,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
expiry: int | None = None,
|
||||
extra: dict | None = None,
|
||||
webhook: str | None = None,
|
||||
internal: bool | None = False,
|
||||
payment_hash: str | None = None,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Payment:
|
||||
if not amount > 0:
|
||||
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(
|
||||
wallet: Wallet,
|
||||
amount: int,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
):
|
||||
if amount == 0:
|
||||
raise ValueError("Amount cannot be 0.")
|
||||
|
|
@ -486,14 +485,14 @@ async def update_wallet_balance(
|
|||
|
||||
|
||||
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_wallet_daily_withdraw_limit(wallet_id, amount_msat, conn)
|
||||
|
||||
|
||||
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
|
||||
if not limit or limit <= 0:
|
||||
|
|
@ -513,7 +512,7 @@ async def check_time_limit_between_transactions(
|
|||
|
||||
|
||||
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
|
||||
if not limit:
|
||||
|
|
@ -546,8 +545,8 @@ async def check_wallet_daily_withdraw_limit(
|
|||
async def calculate_fiat_amounts(
|
||||
amount: float,
|
||||
wallet: Wallet,
|
||||
currency: Optional[str] = None,
|
||||
extra: Optional[dict] = None,
|
||||
currency: str | None = None,
|
||||
extra: dict | None = None,
|
||||
) -> tuple[int, dict]:
|
||||
wallet_currency = wallet.currency or settings.lnbits_default_accounting_currency
|
||||
fiat_amounts: dict = extra or {}
|
||||
|
|
@ -582,9 +581,9 @@ async def calculate_fiat_amounts(
|
|||
|
||||
|
||||
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:
|
||||
payment: Optional[Payment] = await get_wallet_payment(
|
||||
payment: Payment | None = await get_wallet_payment(
|
||||
wallet_id, payment_hash, conn=conn
|
||||
)
|
||||
if not payment:
|
||||
|
|
@ -598,7 +597,7 @@ async def check_transaction_status(
|
|||
|
||||
async def get_payments_daily_stats(
|
||||
filters: Filters[PaymentFilters],
|
||||
user_id: Optional[str] = None,
|
||||
user_id: str | None = None,
|
||||
) -> list[PaymentDailyStats]:
|
||||
data_in, data_out = await get_daily_stats(filters, user_id=user_id)
|
||||
balance_total: float = 0
|
||||
|
|
@ -647,7 +646,7 @@ async def get_payments_daily_stats(
|
|||
async def _pay_invoice(
|
||||
wallet_id: str,
|
||||
create_payment_model: CreatePayment,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
):
|
||||
async with payment_lock:
|
||||
if wallet_id not in wallets_payments_lock:
|
||||
|
|
@ -670,8 +669,8 @@ async def _pay_invoice(
|
|||
async def _pay_internal_invoice(
|
||||
wallet: Wallet,
|
||||
create_payment_model: CreatePayment,
|
||||
conn: Optional[Connection] = None,
|
||||
) -> Optional[Payment]:
|
||||
conn: Connection | None = None,
|
||||
) -> Payment | None:
|
||||
"""
|
||||
Pay an internal payment.
|
||||
returns None if the payment is not internal.
|
||||
|
|
@ -738,7 +737,7 @@ async def _pay_internal_invoice(
|
|||
async def _pay_external_invoice(
|
||||
wallet: Wallet,
|
||||
create_payment_model: CreatePayment,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Payment:
|
||||
checking_id = create_payment_model.payment_hash
|
||||
amount_msat = create_payment_model.amount_msat
|
||||
|
|
@ -807,7 +806,7 @@ async def _pay_external_invoice(
|
|||
async def update_payment_success_status(
|
||||
payment: Payment,
|
||||
status: PaymentStatus,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
) -> Payment:
|
||||
if status.success:
|
||||
service_fee_msat = service_fee(payment.amount, internal=False)
|
||||
|
|
@ -831,7 +830,7 @@ async def _fundingsource_pay_invoice(
|
|||
|
||||
|
||||
async def _verify_external_payment(
|
||||
payment: Payment, conn: Optional[Connection] = None
|
||||
payment: Payment, conn: Connection | None = None
|
||||
) -> Payment:
|
||||
# fail on pending payments
|
||||
if payment.pending:
|
||||
|
|
@ -862,7 +861,7 @@ async def _check_wallet_for_payment(
|
|||
wallet_id: str,
|
||||
tag: str,
|
||||
amount_msat: int,
|
||||
conn: Optional[Connection] = None,
|
||||
conn: Connection | None = None,
|
||||
):
|
||||
wallet = await get_wallet(wallet_id, conn=conn)
|
||||
if not wallet:
|
||||
|
|
@ -878,7 +877,7 @@ async def _check_wallet_for_payment(
|
|||
|
||||
|
||||
def _validate_payment_request(
|
||||
payment_request: str, max_sat: Optional[int] = None
|
||||
payment_request: str, max_sat: int | None = None
|
||||
) -> Bolt11:
|
||||
try:
|
||||
invoice = bolt11_decode(payment_request)
|
||||
|
|
@ -901,7 +900,7 @@ def _validate_payment_request(
|
|||
|
||||
|
||||
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)
|
||||
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(
|
||||
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)
|
||||
if not limits:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from loguru import logger
|
||||
|
|
@ -37,7 +36,7 @@ from .settings import update_cached_settings
|
|||
|
||||
|
||||
async def create_user_account(
|
||||
account: Optional[Account] = None, wallet_name: Optional[str] = None
|
||||
account: Account | None = None, wallet_name: str | None = None
|
||||
) -> User:
|
||||
if not settings.new_accounts_allowed:
|
||||
raise ValueError("Account creation is disabled.")
|
||||
|
|
@ -46,9 +45,9 @@ async def create_user_account(
|
|||
|
||||
|
||||
async def create_user_account_no_ckeck(
|
||||
account: Optional[Account] = None,
|
||||
wallet_name: Optional[str] = None,
|
||||
default_exts: Optional[list[str]] = None,
|
||||
account: Account | None = None,
|
||||
wallet_name: str | None = None,
|
||||
default_exts: list[str] | None = None,
|
||||
) -> User:
|
||||
|
||||
if account:
|
||||
|
|
@ -165,12 +164,12 @@ async def check_admin_settings():
|
|||
settings.first_install = True
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
|
||||
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
|
||||
async def init_admin_settings(super_user: str | None = None) -> SuperSettings:
|
||||
account = None
|
||||
if super_user:
|
||||
account = await get_account(super_user)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import traceback
|
||||
from collections.abc import Coroutine
|
||||
from typing import Callable
|
||||
from collections.abc import Callable, Coroutine
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||
from shutil import make_archive, move
|
||||
from subprocess import Popen
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import IO, Optional
|
||||
from typing import IO
|
||||
from urllib.parse import urlparse
|
||||
|
||||
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(
|
||||
user: User = Depends(check_admin),
|
||||
) -> Optional[AdminSettings]:
|
||||
) -> AdminSettings | None:
|
||||
admin_settings = await get_admin_settings(user.super_user)
|
||||
return admin_settings
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import base64
|
||||
import importlib
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
from time import time
|
||||
from typing import Callable, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
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")
|
||||
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)
|
||||
if not provider_sso:
|
||||
|
|
@ -319,7 +319,7 @@ async def update_pubkey(
|
|||
data: UpdateUserPubkey,
|
||||
user: User = Depends(check_user_exists),
|
||||
payload: AccessTokenPayload = Depends(access_token_payload),
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
if data.user_id != user.id:
|
||||
raise ValueError("Invalid user ID.")
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ async def update_password(
|
|||
data: UpdateUserPassword,
|
||||
user: User = Depends(check_user_exists),
|
||||
payload: AccessTokenPayload = Depends(access_token_payload),
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
_validate_auth_timeout(payload.auth_time)
|
||||
if data.user_id != user.id:
|
||||
raise ValueError("Invalid user ID.")
|
||||
|
|
@ -419,7 +419,7 @@ async def reset_password(data: ResetUserPassword) -> JSONResponse:
|
|||
@auth_router.put("/update")
|
||||
async def update(
|
||||
data: UpdateUser, user: User = Depends(check_user_exists)
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
if data.user_id != user.id:
|
||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid user ID.")
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
if not email or not is_valid_email_address(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(
|
||||
username: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
email: Optional[str] = None,
|
||||
username: str | None = None,
|
||||
user_id: str | None = None,
|
||||
email: str | None = None,
|
||||
) -> JSONResponse:
|
||||
payload = AccessTokenPayload(
|
||||
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
|
||||
|
||||
|
||||
def _new_sso(provider: str) -> Optional[SSOBase]:
|
||||
def _new_sso(provider: str) -> SSOBase | None:
|
||||
try:
|
||||
if not settings.is_auth_method_allowed(AuthMethods(f"{provider}-auth")):
|
||||
return None
|
||||
|
|
@ -610,7 +610,7 @@ def _nostr_nip98_event(request: Request) -> 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:
|
||||
raise ValueError("Tag 'method' is missing.")
|
||||
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}'.")
|
||||
|
||||
|
||||
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:
|
||||
raise HTTPException(
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Annotated, Optional, Union
|
||||
from typing import Annotated
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
import httpx
|
||||
|
|
@ -161,9 +161,9 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
|||
)
|
||||
async def wallet(
|
||||
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),
|
||||
wal: Optional[UUID4] = Query(None),
|
||||
wal: UUID4 | None = Query(None),
|
||||
):
|
||||
if wal:
|
||||
wallet = await get_wallet(wal.hex)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ async def _handle(lnurl: str) -> LnurlResponseModel:
|
|||
)
|
||||
async def api_lnurlscan(code: str) -> LnurlResponseModel:
|
||||
res = await _handle(code)
|
||||
if isinstance(res, (LnurlPayResponse, LnurlWithdrawResponse, LnurlAuthResponse)):
|
||||
if isinstance(res, LnurlPayResponse | LnurlWithdrawResponse | LnurlAuthResponse):
|
||||
check_callback_url(res.callback)
|
||||
return res
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ async def api_payment_pay_with_nfc(
|
|||
except (LnurlResponseException, Exception) as exc:
|
||||
logger.warning(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 res2
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
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")
|
||||
async def api_get_info(
|
||||
node: Node = Depends(require_node),
|
||||
) -> Optional[NodeInfoResponse]:
|
||||
) -> NodeInfoResponse | None:
|
||||
return await node.get_info()
|
||||
|
||||
|
||||
@node_router.get("/channels")
|
||||
async def api_get_channels(
|
||||
node: Node = Depends(require_node),
|
||||
) -> Optional[list[NodeChannel]]:
|
||||
) -> list[NodeChannel] | None:
|
||||
return await node.get_channels()
|
||||
|
||||
|
||||
|
|
@ -104,7 +103,7 @@ async def api_get_channels(
|
|||
async def api_get_channel(
|
||||
channel_id: str,
|
||||
node: Node = Depends(require_node),
|
||||
) -> Optional[NodeChannel]:
|
||||
) -> NodeChannel | None:
|
||||
return await node.get_channel(channel_id)
|
||||
|
||||
|
||||
|
|
@ -113,20 +112,20 @@ async def api_create_channel(
|
|||
node: Node = Depends(require_node),
|
||||
peer_id: str = Body(),
|
||||
funding_amount: int = Body(),
|
||||
push_amount: Optional[int] = Body(None),
|
||||
fee_rate: Optional[int] = Body(None),
|
||||
push_amount: int | None = Body(None),
|
||||
fee_rate: int | None = Body(None),
|
||||
):
|
||||
return await node.open_channel(peer_id, funding_amount, push_amount, fee_rate)
|
||||
|
||||
|
||||
@super_node_router.delete("/channels")
|
||||
async def api_delete_channel(
|
||||
short_id: Optional[str],
|
||||
funding_txid: Optional[str],
|
||||
output_index: Optional[int],
|
||||
short_id: str | None,
|
||||
funding_txid: str | None,
|
||||
output_index: int | None,
|
||||
force: bool = False,
|
||||
node: Node = Depends(require_node),
|
||||
) -> Optional[list[NodeChannel]]:
|
||||
) -> list[NodeChannel] | None:
|
||||
return await node.close_channel(
|
||||
short_id,
|
||||
(
|
||||
|
|
@ -152,7 +151,7 @@ async def api_set_channel_fees(
|
|||
async def api_get_payments(
|
||||
node: Node = Depends(require_node),
|
||||
filters: Filters = Depends(parse_filters(NodePaymentsFilters)),
|
||||
) -> Optional[Page[NodePayment]]:
|
||||
) -> Page[NodePayment] | None:
|
||||
if not settings.lnbits_node_ui_transactions:
|
||||
raise HTTPException(
|
||||
HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
|
@ -165,7 +164,7 @@ async def api_get_payments(
|
|||
async def api_get_invoices(
|
||||
node: Node = Depends(require_node),
|
||||
filters: Filters = Depends(parse_filters(NodeInvoiceFilters)),
|
||||
) -> Optional[Page[NodeInvoice]]:
|
||||
) -> Page[NodeInvoice] | None:
|
||||
if not settings.lnbits_node_ui_transactions:
|
||||
raise HTTPException(
|
||||
HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
|
@ -192,25 +191,25 @@ async def api_disconnect_peer(peer_id: str, node: Node = Depends(require_node)):
|
|||
|
||||
|
||||
class NodeRank(BaseModel):
|
||||
capacity: Optional[int]
|
||||
channelcount: Optional[int]
|
||||
age: Optional[int]
|
||||
growth: Optional[int]
|
||||
availability: Optional[int]
|
||||
capacity: int | None
|
||||
channelcount: int | None
|
||||
age: int | None
|
||||
growth: int | None
|
||||
availability: int | None
|
||||
|
||||
|
||||
# Same for public and private api
|
||||
@node_router.get(
|
||||
"/rank",
|
||||
description="Retrieve node ranks from https://1ml.com",
|
||||
response_model=Optional[NodeRank],
|
||||
response_model=NodeRank | None,
|
||||
)
|
||||
@public_node_router.get(
|
||||
"/rank",
|
||||
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()
|
||||
headers = {"User-Agent": settings.user_agent}
|
||||
async with httpx.AsyncClient(headers=headers) as client:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from hashlib import sha256
|
||||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import (
|
||||
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
|
||||
@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
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import base64
|
|||
import json
|
||||
import time
|
||||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
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")
|
||||
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():
|
||||
raise ValueError(f"Currency '{currency}' not allowed.")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import (
|
||||
|
|
@ -110,11 +109,11 @@ async def api_put_stored_paylinks(
|
|||
|
||||
@wallet_router.patch("")
|
||||
async def api_update_wallet(
|
||||
name: Optional[str] = Body(None),
|
||||
icon: Optional[str] = Body(None),
|
||||
color: Optional[str] = Body(None),
|
||||
currency: Optional[str] = Body(None),
|
||||
pinned: Optional[bool] = Body(None),
|
||||
name: str | None = Body(None),
|
||||
icon: str | None = Body(None),
|
||||
color: str | None = Body(None),
|
||||
currency: str | None = Body(None),
|
||||
pinned: bool | None = Body(None),
|
||||
key_info: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> Wallet:
|
||||
wallet = await get_wallet(key_info.wallet.id)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Annotated, Literal, Optional, Union
|
||||
from typing import Annotated, Literal
|
||||
|
||||
import jwt
|
||||
from fastapi import Cookie, Depends, Query, Request, Security
|
||||
|
|
@ -55,8 +55,8 @@ api_key_query = APIKeyQuery(
|
|||
class KeyChecker(SecurityBase):
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
expected_key_type: Optional[KeyType] = None,
|
||||
api_key: str | None = None,
|
||||
expected_key_type: KeyType | None = None,
|
||||
):
|
||||
self.auto_error: bool = True
|
||||
self.expected_key_type = expected_key_type
|
||||
|
|
@ -137,17 +137,17 @@ async def require_invoice_key(
|
|||
|
||||
|
||||
async def check_access_token(
|
||||
header_access_token: Annotated[Union[str, None], Depends(oauth2_scheme)],
|
||||
cookie_access_token: Annotated[Union[str, None], Cookie()] = None,
|
||||
bearer_access_token: Annotated[Union[str, None], Depends(http_bearer)] = None,
|
||||
) -> Optional[str]:
|
||||
header_access_token: Annotated[str | None, Depends(oauth2_scheme)],
|
||||
cookie_access_token: Annotated[str | None, Cookie()] = None,
|
||||
bearer_access_token: Annotated[str | None, Depends(http_bearer)] = None,
|
||||
) -> str | None:
|
||||
return header_access_token or cookie_access_token or bearer_access_token
|
||||
|
||||
|
||||
async def check_user_exists(
|
||||
r: Request,
|
||||
access_token: Annotated[Optional[str], Depends(check_access_token)],
|
||||
usr: Optional[UUID4] = None,
|
||||
access_token: Annotated[str | None, Depends(check_access_token)],
|
||||
usr: UUID4 | None = None,
|
||||
) -> User:
|
||||
if access_token:
|
||||
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(
|
||||
r: Request,
|
||||
access_token: Annotated[Optional[str], Depends(check_access_token)],
|
||||
usr: Optional[UUID4] = None,
|
||||
) -> Optional[str]:
|
||||
access_token: Annotated[str | None, Depends(check_access_token)],
|
||||
usr: UUID4 | None = None,
|
||||
) -> str | None:
|
||||
if usr and settings.is_auth_method_allowed(AuthMethods.user_id_only):
|
||||
return usr.hex
|
||||
if access_token:
|
||||
|
|
@ -189,7 +189,7 @@ async def optional_user_id(
|
|||
|
||||
|
||||
async def access_token_payload(
|
||||
access_token: Annotated[Optional[str], Depends(check_access_token)],
|
||||
access_token: Annotated[str | None, Depends(check_access_token)],
|
||||
) -> AccessTokenPayload:
|
||||
if not access_token:
|
||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Missing access token.")
|
||||
|
|
@ -231,11 +231,11 @@ def parse_filters(model: type[TFilterModel]):
|
|||
|
||||
def dependency(
|
||||
request: Request,
|
||||
limit: Optional[int] = None,
|
||||
offset: Optional[int] = None,
|
||||
sortby: Optional[str] = None,
|
||||
direction: Optional[Literal["asc", "desc"]] = None,
|
||||
search: Optional[str] = Query(None, description="Text based search"),
|
||||
limit: int | None = None,
|
||||
offset: int | None = None,
|
||||
sortby: str | None = None,
|
||||
direction: Literal["asc", "desc"] | None = None,
|
||||
search: str | None = Query(None, description="Text based search"),
|
||||
):
|
||||
params = request.query_params
|
||||
filters = []
|
||||
|
|
@ -259,7 +259,7 @@ def parse_filters(model: type[TFilterModel]):
|
|||
|
||||
|
||||
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:
|
||||
"""
|
||||
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(
|
||||
access_token: str, path: str, method: str
|
||||
) -> Optional[Account]:
|
||||
) -> Account | None:
|
||||
try:
|
||||
payload: dict = jwt.decode(access_token, settings.auth_secret_key, ["HS256"])
|
||||
return await _get_account_from_jwt_payload(
|
||||
|
|
@ -310,7 +310,7 @@ async def _get_account_from_token(
|
|||
|
||||
async def _get_account_from_jwt_payload(
|
||||
payload: AccessTokenPayload, path: str, method: str
|
||||
) -> Optional[Account]:
|
||||
) -> Account | None:
|
||||
account = None
|
||||
if payload.sub:
|
||||
account = await get_account_by_username(payload.sub)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import sys
|
||||
import traceback
|
||||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
|
|
@ -30,7 +29,7 @@ class UnsupportedError(Exception):
|
|||
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
|
||||
# not fail proof, but everything else get's a JSON response
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import asyncio
|
|||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import httpx
|
||||
|
|
@ -49,7 +48,7 @@ class StripeWallet(FiatProvider):
|
|||
logger.warning(f"Error closing stripe wallet connection: {e}")
|
||||
|
||||
async def status(
|
||||
self, only_check_settings: Optional[bool] = False
|
||||
self, only_check_settings: bool | None = False
|
||||
) -> FiatStatusResponse:
|
||||
if only_check_settings:
|
||||
if self._settings_fields != self._settings_connection_fields():
|
||||
|
|
@ -76,7 +75,7 @@ class StripeWallet(FiatProvider):
|
|||
amount: float,
|
||||
payment_hash: str,
|
||||
currency: str,
|
||||
memo: Optional[str] = None,
|
||||
memo: str | None = None,
|
||||
**kwargs,
|
||||
) -> FiatInvoiceResponse:
|
||||
amount_cents = int(amount * 100)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import json
|
|||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
from urllib import request
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ def urlsafe_short_hash() -> str:
|
|||
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 ""
|
||||
url_params = "?"
|
||||
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}"
|
||||
|
||||
|
||||
def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templates:
|
||||
def template_renderer(additional_folders: list | None = None) -> Jinja2Templates:
|
||||
folders = ["lnbits/templates", "lnbits/core/templates"]
|
||||
if additional_folders:
|
||||
additional_folders += [
|
||||
|
|
@ -211,7 +211,7 @@ def is_valid_pubkey(pubkey: str) -> bool:
|
|||
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
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=minutes)
|
||||
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")
|
||||
|
||||
|
||||
def encrypt_internal_message(
|
||||
m: Optional[str] = None, urlsafe: bool = False
|
||||
) -> Optional[str]:
|
||||
def encrypt_internal_message(m: str | None = None, urlsafe: bool = False) -> str | None:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
def decrypt_internal_message(
|
||||
m: Optional[str] = None, urlsafe: bool = False
|
||||
) -> Optional[str]:
|
||||
def decrypt_internal_message(m: str | None = None, urlsafe: bool = False) -> str | None:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
# return shallow clone of the dict even if there are no filters
|
||||
return {**data}
|
||||
|
|
@ -270,7 +266,7 @@ def version_parse(v: str):
|
|||
|
||||
|
||||
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:
|
||||
if min_lnbits_version and (
|
||||
version_parse(min_lnbits_version) > version_parse(settings.version)
|
||||
|
|
@ -347,7 +343,7 @@ def path_segments(path: str) -> list[str]:
|
|||
return segments[0:]
|
||||
|
||||
|
||||
def normalize_path(path: Optional[str]) -> str:
|
||||
def normalize_path(path: str | None) -> str:
|
||||
path = path or ""
|
||||
return "/" + "/".join(path_segments(path))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import asyncio
|
|||
import json
|
||||
from datetime import datetime, timezone
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Request, Response
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
|
|
@ -62,7 +62,7 @@ class InstalledExtensionMiddleware:
|
|||
|
||||
def _response_by_accepted_type(
|
||||
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`
|
||||
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:
|
||||
start_time = datetime.now(timezone.utc)
|
||||
request_details = await self._request_details(request)
|
||||
response: Optional[Response] = None
|
||||
response: Response | None = None
|
||||
|
||||
try:
|
||||
response = await call_next(request)
|
||||
|
|
@ -140,13 +140,13 @@ class AuditMiddleware(BaseHTTPMiddleware):
|
|||
async def _log_audit(
|
||||
self,
|
||||
request: Request,
|
||||
response: Optional[Response],
|
||||
response: Response | None,
|
||||
duration: float,
|
||||
request_details: Optional[str],
|
||||
request_details: str | None,
|
||||
):
|
||||
try:
|
||||
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
|
||||
if not settings.audit_http_request(http_method, path, response_code):
|
||||
return None
|
||||
|
|
@ -178,7 +178,7 @@ class AuditMiddleware(BaseHTTPMiddleware):
|
|||
except Exception as 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():
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def main(
|
|||
ssl_certfile: str,
|
||||
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
|
||||
Path(settings.lnbits_data_folder).mkdir(parents=True, exist_ok=True)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import asyncio
|
||||
import traceback
|
||||
import uuid
|
||||
from collections.abc import Coroutine
|
||||
from typing import (
|
||||
Callable,
|
||||
Optional,
|
||||
)
|
||||
from collections.abc import Callable, Coroutine
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -84,7 +80,7 @@ invoice_listeners: dict[str, asyncio.Queue] = {}
|
|||
|
||||
# TODO: name should not be optional
|
||||
# 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
|
||||
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 hashlib import md5, pbkdf2_hmac, sha256
|
||||
from typing import Union
|
||||
|
||||
from Cryptodome import Random
|
||||
from Cryptodome.Cipher import AES
|
||||
|
|
@ -40,7 +39,7 @@ class AESCipher:
|
|||
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
|
||||
if isinstance(key, bytes):
|
||||
self.key = key
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import asyncio
|
||||
import statistics
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
import jsonpath_ng.ext as jpx
|
||||
|
|
@ -250,7 +249,7 @@ async def btc_rates(currency: str) -> list[tuple[str, float]]:
|
|||
|
||||
async def fetch_price(
|
||||
provider: ExchangeRateProvider,
|
||||
) -> Optional[tuple[str, float]]:
|
||||
) -> tuple[str, float] | None:
|
||||
if currency.lower() in provider.exclude_to:
|
||||
logger.warning(f"Provider {provider.name} does not support {currency}.")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import base64
|
|||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from typing import Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import secp256k1
|
||||
|
|
@ -149,7 +148,7 @@ def sign_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.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import asyncio
|
|||
import hashlib
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -71,9 +70,9 @@ class AlbyWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
# https://api.getalby.com/invoices
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import asyncio
|
|||
import hashlib
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -102,9 +101,9 @@ class BlinkWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
# https://dev.blink.sv/api/btc-ln-receive
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
from bolt11.decode import decode
|
||||
from grpc.aio import AioRpcError
|
||||
|
|
@ -107,9 +106,9 @@ class BoltzWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
pair = boltzrpc_pb2.Pair(to=boltzrpc_pb2.LBTC)
|
||||
|
|
@ -254,7 +253,7 @@ class BoltzWallet(Wallet):
|
|||
)
|
||||
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:
|
||||
request = boltzrpc_pb2.GetWalletRequest(name=wallet_name)
|
||||
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
|
||||
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):
|
||||
raise RuntimeError(
|
||||
"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:
|
||||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
|
|
@ -66,7 +65,7 @@ else:
|
|||
):
|
||||
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
|
||||
if source.split(".")[-1] == extension:
|
||||
with open(source, "rb") as f:
|
||||
|
|
@ -85,7 +84,7 @@ else:
|
|||
logger.debug(exc)
|
||||
return None
|
||||
|
||||
def load_greenlight_credentials() -> Optional[GreenlightCredentials]:
|
||||
def load_greenlight_credentials() -> GreenlightCredentials | None:
|
||||
if (
|
||||
settings.breez_greenlight_device_key
|
||||
and settings.breez_greenlight_device_cert
|
||||
|
|
@ -168,9 +167,9 @@ else:
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
# if description_hash or unhashed_description:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ if not find_spec("breez_sdk_liquid"):
|
|||
def __init__(self):
|
||||
raise RuntimeError(
|
||||
"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:
|
||||
|
|
@ -16,7 +16,6 @@ else:
|
|||
from asyncio import Queue
|
||||
from collections.abc import AsyncGenerator
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from breez_sdk_liquid import (
|
||||
|
|
@ -128,9 +127,9 @@ else:
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import asyncio
|
|||
import hashlib
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
from websocket import create_connection
|
||||
|
|
@ -53,9 +52,9 @@ class ClicheWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
if unhashed_description or description_hash:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import os
|
|||
import ssl
|
||||
import uuid
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
|
|
@ -174,9 +173,9 @@ class CLNRestWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from secrets import token_urlsafe
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from bolt11.decode import decode as bolt11_decode
|
||||
from bolt11.exceptions import Bolt11Exception
|
||||
|
|
@ -92,9 +92,9 @@ class CoreLightningWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
label = kwargs.get("label", f"lbl{token_urlsafe(16)}")
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import asyncio
|
|||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from secrets import token_urlsafe
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
|
|
@ -106,9 +105,9 @@ class CoreLightningRestWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
label = kwargs.get("label", f"lbl{token_urlsafe(16)}")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import json
|
|||
import urllib.parse
|
||||
from collections.abc import AsyncGenerator
|
||||
from decimal import Decimal
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -84,9 +84,9 @@ class EclairWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: dict[str, Any] = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ from collections.abc import AsyncGenerator
|
|||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
from os import urandom
|
||||
from typing import Optional
|
||||
|
||||
from bolt11 import (
|
||||
Bolt11,
|
||||
|
|
@ -53,11 +52,11 @@ class FakeWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
expiry: Optional[int] = None,
|
||||
payment_secret: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
expiry: int | None = None,
|
||||
payment_secret: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
tags = Tags()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -71,9 +70,9 @@ class LNbitsWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: dict = {"out": False, "amount": amount, "memo": memo or ""}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import base64
|
|||
from collections.abc import AsyncGenerator
|
||||
from hashlib import sha256
|
||||
from os import environ
|
||||
from typing import Optional
|
||||
|
||||
import grpc
|
||||
from loguru import logger
|
||||
|
|
@ -123,9 +122,9 @@ class LndWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: dict = {
|
||||
|
|
@ -312,9 +311,9 @@ class LndWallet(Wallet):
|
|||
self,
|
||||
amount: int,
|
||||
payment_hash: str,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: dict = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import base64
|
|||
import hashlib
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -103,9 +103,9 @@ class LndRestWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
_data: dict = {
|
||||
|
|
@ -327,9 +327,9 @@ class LndRestWallet(Wallet):
|
|||
self,
|
||||
amount: int,
|
||||
payment_hash: str,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
data: dict = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -75,9 +74,9 @@ class LNPayWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
data: dict = {"num_satoshis": f"{amount}"}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import hashlib
|
|||
import json
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -69,9 +68,9 @@ class LnTipsWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
data: dict = {"amount": amount, "description_hash": "", "memo": memo or ""}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import base64
|
||||
from getpass import getpass
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -8,8 +7,8 @@ from lnbits.utils.crypto import AESCipher
|
|||
|
||||
|
||||
def load_macaroon(
|
||||
macaroon: Optional[str] = None,
|
||||
encrypted_macaroon: Optional[str] = None,
|
||||
macaroon: str | None = None,
|
||||
encrypted_macaroon: str | None = None,
|
||||
) -> str:
|
||||
"""Returns hex version of a macaroon encoded in base64 or the file path."""
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import json
|
|||
import random
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional, Union, cast
|
||||
from typing import cast
|
||||
from urllib.parse import parse_qs, unquote, urlparse
|
||||
|
||||
import secp256k1
|
||||
|
|
@ -130,9 +130,9 @@ class NWCWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
desc = ""
|
||||
|
|
@ -360,7 +360,7 @@ class NWCConnection:
|
|||
"""
|
||||
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.
|
||||
|
||||
|
|
@ -396,7 +396,7 @@ class NWCConnection:
|
|||
|
||||
async def _close_subscription_by_subid(
|
||||
self, sub_id: str, send_event: bool = True
|
||||
) -> Optional[dict]:
|
||||
) -> dict | None:
|
||||
"""
|
||||
Closes a subscription by its sub_id.
|
||||
|
||||
|
|
@ -427,7 +427,7 @@ class NWCConnection:
|
|||
|
||||
async def _close_subscription_by_eventid(
|
||||
self, event_id, send_event=True
|
||||
) -> Optional[dict]:
|
||||
) -> dict | None:
|
||||
"""
|
||||
Closes a subscription associated to an event_id.
|
||||
|
||||
|
|
@ -514,7 +514,7 @@ class NWCConnection:
|
|||
if subscription: # Check if the subscription exists first
|
||||
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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -71,9 +70,9 @@ class OpenNodeWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
if description_hash or unhashed_description:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import hashlib
|
|||
import json
|
||||
import urllib.parse
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from httpx import RequestError, TimeoutException
|
||||
|
|
@ -96,9 +96,9 @@ class PhoenixdWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import hashlib
|
|||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
from secrets import token_urlsafe
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -111,9 +110,9 @@ class SparkWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
label = f"lbs{token_urlsafe(16)}"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import asyncio
|
|||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from decimal import Decimal
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
|
@ -116,7 +116,7 @@ class StrikeWallet(Wallet):
|
|||
self.failed_payments: dict[str, str] = {}
|
||||
|
||||
# balance cache
|
||||
self._cached_balance: Optional[int] = None
|
||||
self._cached_balance: int | None = None
|
||||
self._cached_balance_ts: float = 0.0
|
||||
self._cache_ttl = 30 # seconds
|
||||
|
||||
|
|
@ -199,8 +199,8 @@ class StrikeWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None, # Add this parameter
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
|
|
@ -424,10 +424,10 @@ class StrikeWallet(Wallet):
|
|||
|
||||
async def get_invoices(
|
||||
self,
|
||||
filters: Optional[str] = None,
|
||||
orderby: Optional[str] = None,
|
||||
skip: Optional[int] = None,
|
||||
top: Optional[int] = None,
|
||||
filters: str | None = None,
|
||||
orderby: str | None = None,
|
||||
skip: int | None = None,
|
||||
top: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
params: dict[str, Any] = {}
|
||||
|
|
@ -450,7 +450,7 @@ class StrikeWallet(Wallet):
|
|||
|
||||
async def _get_payment_status_by_quote_id(
|
||||
self, checking_id: str, quote_id: str
|
||||
) -> Optional[PaymentStatus]:
|
||||
) -> PaymentStatus | None:
|
||||
resp = await self._get(f"/payment-quotes/{quote_id}")
|
||||
resp.raise_for_status()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from bolt11 import decode as bolt11_decode
|
||||
|
|
@ -60,9 +59,9 @@ class ZBDWallet(Wallet):
|
|||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**_,
|
||||
) -> InvoiceResponse:
|
||||
# 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"
|
||||
version = "1.3.0-rc4"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
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"
|
||||
repository = "https://github.com/lnbits/lnbits"
|
||||
homepage = "https://lnbits.com"
|
||||
dependencies = [
|
||||
"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 = [
|
||||
{include = "lnbits"},
|
||||
{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]
|
||||
black = "^25.1.0"
|
||||
mypy = "^1.17.1"
|
||||
|
|
@ -94,14 +114,6 @@ types-mock = "^5.1.0.20240425"
|
|||
mock = "^5.1.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]
|
||||
include = [
|
||||
"lnbits",
|
||||
|
|
@ -255,3 +267,10 @@ extend-immutable-calls = [
|
|||
"fastapi.Body",
|
||||
"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 string
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -14,13 +13,13 @@ class FakeError(Exception):
|
|||
class DbTestModel(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
value: Optional[str] = None
|
||||
value: str | None = None
|
||||
|
||||
|
||||
class DbTestModel2(BaseModel):
|
||||
id: int
|
||||
label: str
|
||||
description: Optional[str] = None
|
||||
description: str | None = None
|
||||
child: DbTestModel
|
||||
child_list: list[DbTestModel]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,28 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class FundingSourceConfig(BaseModel):
|
||||
name: str
|
||||
skip: Optional[bool]
|
||||
skip: bool | None
|
||||
wallet_class: str
|
||||
settings: dict
|
||||
mock_settings: Optional[dict]
|
||||
mock_settings: dict | None
|
||||
|
||||
|
||||
class FunctionMock(BaseModel):
|
||||
uri: Optional[str]
|
||||
query_params: Optional[dict]
|
||||
headers: Optional[dict]
|
||||
method: Optional[str]
|
||||
uri: str | None
|
||||
query_params: dict | None
|
||||
headers: dict | None
|
||||
method: str | None
|
||||
|
||||
|
||||
class TestMock(BaseModel):
|
||||
skip: Optional[bool]
|
||||
description: Optional[str]
|
||||
request_type: Optional[str]
|
||||
request_body: Optional[dict]
|
||||
skip: bool | None
|
||||
description: str | None
|
||||
request_type: str | None
|
||||
request_body: dict | None
|
||||
response_type: str
|
||||
response: Union[str, dict, list]
|
||||
response: str | dict | list
|
||||
|
||||
|
||||
class Mock(FunctionMock, TestMock):
|
||||
|
|
@ -62,13 +60,13 @@ class FunctionData(BaseModel):
|
|||
|
||||
|
||||
class WalletTest(BaseModel):
|
||||
skip: Optional[bool]
|
||||
skip: bool | None
|
||||
function: str
|
||||
description: str
|
||||
funding_source: FundingSourceConfig
|
||||
call_params: Optional[dict] = {}
|
||||
expect: Optional[dict]
|
||||
expect_error: Optional[dict]
|
||||
call_params: dict | None = {}
|
||||
expect: dict | None
|
||||
expect_error: dict | None
|
||||
mocks: list[Mock] = []
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import json
|
||||
from typing import Union
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import pytest
|
||||
|
|
@ -59,7 +58,7 @@ async def test_rest_wallet(httpserver: HTTPServer, test_data: WalletTest):
|
|||
|
||||
|
||||
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 = 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
|
||||
)
|
||||
|
||||
server_response: Union[str, dict, list, Response] = mock.response
|
||||
server_response: str | dict | list | Response = mock.response
|
||||
response_type = mock.response_type
|
||||
if response_type == "response":
|
||||
assert isinstance(server_response, dict), "server response must be JSON"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import importlib
|
||||
from typing import Optional
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
|
|
@ -167,7 +166,7 @@ def _mock_field(field):
|
|||
return response
|
||||
|
||||
|
||||
def _eval_dict(data: Optional[dict]) -> Optional[dict]:
|
||||
def _eval_dict(data: dict | None) -> dict | None:
|
||||
fn_prefix = "__eval__:"
|
||||
if not data:
|
||||
return data
|
||||
|
|
@ -190,7 +189,7 @@ def _eval_dict(data: Optional[dict]) -> Optional[dict]:
|
|||
return d
|
||||
|
||||
|
||||
def _dict_to_object(data: Optional[dict]) -> Optional[DataObject]:
|
||||
def _dict_to_object(data: dict | None) -> DataObject | None:
|
||||
if not data:
|
||||
return None
|
||||
# if isinstance(data, list):
|
||||
|
|
@ -213,7 +212,7 @@ def _data_mock(data: dict) -> Mock:
|
|||
return Mock(return_value=_dict_to_object(data))
|
||||
|
||||
|
||||
def _raise(error: Optional[dict]):
|
||||
def _raise(error: dict | None):
|
||||
if not error:
|
||||
return Exception()
|
||||
data = error["data"] if "data" in error else None
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import argparse
|
|||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
|
@ -108,7 +107,7 @@ def insert_to_pg(query, data):
|
|||
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:
|
||||
exclude_tables = []
|
||||
print(f"Migrating core: {file}")
|
||||
|
|
@ -124,7 +123,7 @@ def migrate_ext(file: str):
|
|||
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:
|
||||
if exclude_tables is None:
|
||||
exclude_tables = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue