Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f46545dea | ||
|
|
37d2a695a7 | ||
|
|
63458ced1e | ||
|
|
cb3e340a78 | ||
|
|
ae24f7e43c | ||
|
|
609808f6a2 | ||
|
|
c760e6f63d | ||
|
|
991ac4d7fe | ||
|
|
c1c622524e | ||
|
|
ccc784c8fc | ||
|
|
7c72766bbd | ||
|
|
416d170996 | ||
|
|
94ebc22dcc |
+1
-1
@@ -1 +1 @@
|
||||
custom: https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg
|
||||
custom: https://demo.lnbits.com/lnurlp/link/fH59GD
|
||||
|
||||
@@ -1,42 +1,27 @@
|
||||
name: Build LNbits AppImage
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'The tag name for the release'
|
||||
required: true
|
||||
type: string
|
||||
upload_url:
|
||||
description: 'The upload URL for the release'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'The tag name for the release'
|
||||
required: true
|
||||
type: string
|
||||
upload_url:
|
||||
description: 'The upload URL for the release'
|
||||
required: true
|
||||
type: string
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
|
||||
build-linux-package:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Step 1: Checkout the repository
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Step 2: Set up Python (uv will still use this toolchain)
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
# Step 3: Install system deps (fuse) + uv
|
||||
- name: Install system deps and uv
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -45,6 +30,7 @@ jobs:
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
# Optional: Cache uv + venv to speed up CI
|
||||
- name: Cache uv and venv
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -53,6 +39,7 @@ jobs:
|
||||
.venv
|
||||
key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock', 'pyproject.toml') }}
|
||||
|
||||
# Step 4: Prepare packaging tree and clone LNbits
|
||||
- name: Prepare packaging & clone LNbits
|
||||
run: |
|
||||
mv .github/packaging packaging
|
||||
@@ -60,11 +47,18 @@ jobs:
|
||||
git clone https://github.com/lnbits/lnbits.git packaging/linux/AppDir/usr/lnbits
|
||||
shell: bash
|
||||
|
||||
# Step 5: Build the LNbits binary with uv + PyInstaller
|
||||
- name: Build LNbits binary (uv + PyInstaller)
|
||||
run: |
|
||||
cd packaging/linux/AppDir/usr/lnbits
|
||||
uv sync --all-extras --no-dev
|
||||
|
||||
# Install project deps into .venv using uv
|
||||
uv sync
|
||||
|
||||
# Install PyInstaller into the same environment
|
||||
uv pip install pyinstaller
|
||||
|
||||
# Build the LNbits binary
|
||||
uv run pyinstaller \
|
||||
--onefile \
|
||||
--name lnbits \
|
||||
@@ -72,10 +66,6 @@ jobs:
|
||||
--collect-all embit \
|
||||
--collect-all lnbits \
|
||||
--collect-all sqlalchemy \
|
||||
--collect-all breez_sdk \
|
||||
--collect-binaries breez_sdk \
|
||||
--collect-all breez_sdk_liquid \
|
||||
--collect-binaries breez_sdk_liquid \
|
||||
--collect-all aiosqlite \
|
||||
--hidden-import=passlib.handlers.bcrypt \
|
||||
"$(uv run which lnbits)"
|
||||
@@ -85,7 +75,7 @@ jobs:
|
||||
chmod +x packaging/linux/AppDir/lnbits.desktop
|
||||
chmod +x packaging/linux/AppDir/usr/lnbits/dist/lnbits
|
||||
|
||||
# keep AppDir slim
|
||||
# Clean out non-dist content from the app dir to keep AppImage slim
|
||||
find packaging/linux/AppDir/usr/lnbits -mindepth 1 -maxdepth 1 \
|
||||
! -name 'dist' \
|
||||
! -name 'lnbits' \
|
||||
@@ -94,25 +84,20 @@ jobs:
|
||||
# Build AppImage
|
||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
TAG_NAME=${{ inputs.tag_name }}
|
||||
TAG_NAME=${{ github.event.release.tag_name }}
|
||||
APPIMAGE_NAME="LNbits-${TAG_NAME}.AppImage"
|
||||
./appimagetool-x86_64.AppImage \
|
||||
--updateinformation "gh-releases-zsync|lnbits|lnbits|latest|*.AppImage.zsync" \
|
||||
packaging/linux/AppDir "$APPIMAGE_NAME"
|
||||
chmod +x "$APPIMAGE_NAME"
|
||||
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
|
||||
|
||||
# 🔎 quick audit: show glibc versions referenced by the binary
|
||||
echo "Runner glibc:"
|
||||
ldd --version | head -n1 || true
|
||||
echo "Symbols needed by lnbits:"
|
||||
strings "$APPIMAGE_NAME" | grep -o 'GLIBC_[0-9.]*' | sort -u || true
|
||||
shell: bash
|
||||
|
||||
# Step 6: Upload Linux Release Asset
|
||||
- name: Upload Linux Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ inputs.upload_url }}
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ${{ env.APPIMAGE_NAME }}
|
||||
asset_name: ${{ env.APPIMAGE_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
@@ -13,8 +13,6 @@ jobs:
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Create github release
|
||||
@@ -22,8 +20,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref_name }}
|
||||
run: |
|
||||
upload_url=$(gh release create "$tag" --generate-notes --draft --json upload_url -q '.upload_url')
|
||||
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
|
||||
gh release create "$tag" --generate-notes --draft
|
||||
|
||||
docker:
|
||||
needs: [ release ]
|
||||
@@ -55,10 +52,3 @@ jobs:
|
||||
uses: JRubics/poetry-publish@v1.15
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
|
||||
appimage:
|
||||
needs: [ release ]
|
||||
uses: ./.github/workflows/appimage.yml
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
|
||||
@@ -1,45 +1,38 @@
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
<picture >
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png" style="width:300px">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" style="width:300px">
|
||||
</picture>
|
||||
|
||||
 [![license-badge]](LICENSE) [![docs-badge]][docs]  [](https://extensions.lnbits.com/) [](https://shop.lnbits.com/) [<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits) [<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||
<img width="2000" height="203" alt="lnbits_head" src="https://github.com/user-attachments/assets/77669718-ac10-43c7-ae95-6ce236c77401" />
|
||||
 [![license-badge]](LICENSE) [![docs-badge]][docs]  [<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits) [<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||

|
||||
|
||||
# LNbits — The most powerful Bitcoin & Lightning toolkit
|
||||
# The world's most powerful suite of bitcoin tools.
|
||||
|
||||
> Run it for yourself, for your community, or as part of a larger stack.
|
||||
## Run for yourself, for others, or as part of a stack.
|
||||
|
||||
## What is LNbits?
|
||||
LNbits is beta, for responsible disclosure of any concerns please contact an admin in the community chat.
|
||||
|
||||
LNbits is a lightweight Python server that sits on top of your Lightning funding source. It gives you safe, isolated wallets, a clean API, and an extension system for rapidly adding features - without locking you into a single node implementation. The Inspiration for LNBits came from ideas pioneered by **OpenNode** and **LNPay** — both today work as funding sources for LNbits.
|
||||
LNbits is a Python server that sits on top of any funding source. It can be used as:
|
||||
|
||||
## What you can do with LNbits
|
||||
- Accounts system to mitigate the risk of exposing applications to your full balance via unique API keys for each wallet
|
||||
- Extendable platform for exploring Lightning network functionality via the LNbits extension framework
|
||||
- Part of a development stack via LNbits API
|
||||
- Fallback wallet for the LNURL scheme
|
||||
- Instant wallet for LN demonstrations
|
||||
|
||||
- **Harden app security:** Create per-wallet API keys so individual apps never touch your full balance.
|
||||
- **Extend functionality fast:** Install extensions to explore and ship Lightning features with minimal code.
|
||||
- **Build into your stack:** Use the LNbits HTTP API to integrate payments, wallets, and accounting.
|
||||
- **Cover LNURL flows:** Use LNbits as a reliable fallback wallet for LNURL.
|
||||
- **Demo in minutes:** Spin up instant wallets for workshops, proofs-of-concept, and user testing.
|
||||
LNbits can run on top of almost all Lightning funding sources.
|
||||
|
||||
## Funding sources
|
||||
See [LNbits manual](https://docs.lnbits.org/guide/wallets.html) for more detailed documentation about each funding source.
|
||||
|
||||
LNbits runs on top of most Lightning backends. Choose the one you already operate - or swap later without changing your app architecture.
|
||||
Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series.
|
||||
|
||||
- Read the [funding source guide](https://docs.lnbits.org/guide/wallets.html)
|
||||
|
||||
## Learn more
|
||||
|
||||
- Video series on [Youtube](https://www.youtube.com/@lnbits)
|
||||
- Introduction Video [LNBits V1](https://www.youtube.com/watch?v=PFAHKxvgI9Y&t=19s)
|
||||
LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as funding sources for LNbits.
|
||||
|
||||
## Running LNbits
|
||||
|
||||
See the [install guide](https://github.com/lnbits/lnbits/blob/main/docs/guide/installation.md) for details on installation and setup.
|
||||
Test on our demo server [demo.lnbits.com](https://demo.lnbits.com), or on [lnbits.com](https://lnbits.com) software as a service, where you can spin up an LNbits instance for 21sats per hr.
|
||||
|
||||
Get yourself familiar and test on our demo server [demo.lnbits.com](https://demo.lnbits.com), or on [lnbits.com](https://lnbits.com) software as a service, where you can spin up an LNbits instance for 21sats per hr.
|
||||
See the [install guide](https://github.com/lnbits/lnbits/blob/main/docs/guide/installation.md) for details on installation and setup.
|
||||
|
||||
## LNbits account system
|
||||
|
||||
@@ -73,17 +66,9 @@ As well as working great in a browser, LNbits has native IoS and Android apps as
|
||||
|
||||
<img src="https://i.imgur.com/J96EbRf.png" style="width:800px">
|
||||
|
||||
## Powered by LNbits
|
||||
## Tip us
|
||||
|
||||
LNbits empowers everyone with modular, open-source tools for building Bitcoin-based systems — fast, free, and extendable.
|
||||
|
||||
If you like this project [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visit our [Shop](https://shop.lnbits.de)
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://my.lnbits.com/login)
|
||||
[](https://news.lnbits.com/)
|
||||
[](https://extensions.lnbits.com/)
|
||||
If you like this project [send some tip love](https://demo.lnbits.com/lnurlp/link/fH59GD)!
|
||||
|
||||
[docs]: https://github.com/lnbits/lnbits/wiki
|
||||
[docs-badge]: https://img.shields.io/badge/docs-lnbits.org-673ab7.svg
|
||||
|
||||
+49
-93
@@ -1,122 +1,78 @@
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
---
|
||||
layout: default
|
||||
title: Admin UI
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits)
|
||||
[<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||
# Admin UI
|
||||
|
||||
# LNBits Admin UI
|
||||
The LNbits Admin UI lets you change LNbits settings via the LNbits frontend.
|
||||
It is disabled by default and the first time you set the environment variable `LNBITS_ADMIN_UI=true`
|
||||
the settings are initialized and saved to the database and will be used from there as long the UI is enabled.
|
||||
From there on the settings from the database are used.
|
||||
|
||||
We introduced the Admin UI as the new default to make setup simpler and more straightforward. Instead of hand editing the `.env` file, you configure key server settings directly in the frontend with clear labels and guardrails.
|
||||
# Super User
|
||||
|
||||
<ins>On a fresh install the Admin UI is enabled by default</ins>, and at first launch you are prompted to create **Super User** credentials so that sensitive operations, such as switching funding sources, remain in trusted hands. When the Admin UI is enabled, configuration is written to and read from the database; for all settings managed by the UI, the parameters in `.env` are largely no longer used. If you disable the Admin UI, the `.env` file becomes the single source of truth again.
|
||||
With the Admin UI we introduced the super user, it is created with the initialisation of the Admin UI and will be shown with a success message in the server logs.
|
||||
The super user has access to the server and can change settings that may crash the server and make it unresponsive via the frontend and api, like changing funding sources.
|
||||
|
||||
For privileged actions and role details see **[Super User](./super_user.md)** & [User Roles](./user_roles.md)
|
||||
For a complete reference of legacy variables consult **[.env.example](../../.env.example)**.
|
||||
Also only the super user can brrrr satoshis to different wallets.
|
||||
|
||||
<img width="900" height="640" alt="grafik" src="https://github.com/user-attachments/assets/d8852b4b-21be-446f-a1e7-d3eb794d3505" />
|
||||
The super user is only stored inside the settings table of the database and after the settings are "reset to defaults" and a restart happened,
|
||||
a new super user is created.
|
||||
|
||||
> [!WARNING]
|
||||
> Some settings remain `.env` only. Use **[.env.example](../../.env.example#L3-L87)** as the authoritative reference for those variables.
|
||||
The super user is never sent over the api and the frontend only receives a bool if you are super user or not.
|
||||
|
||||
## What you can do with the Admin UI
|
||||
We also added a decorator for the API routes to check for super user.
|
||||
|
||||
- Switch funding sources and other server level settings
|
||||
- Manage who can access LNbits (**[Allowed Users](#allowed-users)**)
|
||||
- Promote or demote Admin Users
|
||||
- Gate extensions to Admins only or disable them globally
|
||||
- Adjust balances with credit or debit
|
||||
- Adjust site customization
|
||||
There is also the possibility of posting the super user via webhook to another service when it is created. you can look it up here https://github.com/lnbits/lnbits/blob/main/lnbits/settings.py `class SaaSSettings`
|
||||
|
||||
> [!NOTE]
|
||||
> See **[Super User](./super_user.md)** for the role and permission differences compared to Admin Users.
|
||||
# Admin Users
|
||||
|
||||
## Enabling or disabling the Admin UI
|
||||
environment variable: `LNBITS_ADMIN_USERS`, comma-separated list of user ids
|
||||
Admin Users can change settings in the admin ui as well, with the exception of funding source settings, because they require e server restart and could potentially make the server inaccessible. Also they have access to all the extension defined in `LNBITS_ADMIN_EXTENSIONS`.
|
||||
|
||||
The Admin UI is enabled by default on new installs. To change the state:
|
||||
# Allowed Users
|
||||
|
||||
1. Stop LNbits
|
||||
environment variable: `LNBITS_ALLOWED_USERS`, comma-separated list of user ids
|
||||
By defining this users, LNbits will no longer be usable by the public, only defined users and admins can then access the LNbits frontend.
|
||||
|
||||
```bash
|
||||
sudo systemctl stop lnbits.service
|
||||
```
|
||||
Setting this environment variable also disables account creation.
|
||||
Account creation can be also disabled by setting `LNBITS_ALLOW_NEW_ACCOUNTS=false`
|
||||
|
||||
2. Edit your `.env`
|
||||
# How to activate
|
||||
|
||||
```
|
||||
cd ~/lnbits
|
||||
sudo nano .env
|
||||
```
|
||||
```
|
||||
$ sudo systemctl stop lnbits.service
|
||||
$ cd ~/lnbits
|
||||
$ sudo nano .env
|
||||
```
|
||||
|
||||
3. Set one of
|
||||
-> set: `LNBITS_ADMIN_UI=true`
|
||||
|
||||
```
|
||||
# Enable Admin UI
|
||||
LNBITS_ADMIN_UI=true
|
||||
Now start LNbits once in the terminal window
|
||||
|
||||
# Disable Admin UI
|
||||
LNBITS_ADMIN_UI=false
|
||||
```
|
||||
```
|
||||
$ uv run lnbits
|
||||
```
|
||||
|
||||
4. Start LNbits
|
||||
You can now `cat` the Super User ID:
|
||||
|
||||
```
|
||||
sudo systemctl start lnbits.service
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> With the Admin UI enabled, config is DB-backed and UI-managed settings ignore .env. Disable it to revert to [.env](../../.env.example) as the single source of truth.
|
||||
|
||||
## Reset to defaults
|
||||
|
||||
Using `Reset to defaults` in the Admin UI wipes stored settings. After a restart, a new `Super User` is created and the old one is no longer valid.
|
||||
|
||||
## First run and Super User ID
|
||||
|
||||
On first start with the Admin UI enabled you will be prompted to generate a Super User. If you need to read it from disk later:
|
||||
|
||||
```bash
|
||||
cat /lnbits/data/.super_user
|
||||
# example
|
||||
```
|
||||
$ cat data/.super_user
|
||||
123de4bfdddddbbeb48c8bc8382fe123
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> For security reasons, Super Users and Admin users must authenticate with credentials (username and password).
|
||||
You can access your super user account at `/wallet?usr=super_user_id`. You just have to append it to your normal LNbits web domain.
|
||||
|
||||
After login you will see **Settings** and **Users** in the sidebar between **Wallets** and **Extensions**, plus a role badge in the top left.
|
||||
After that you will find the **`Admin` / `Manage Server`** between `Wallets` and `Extensions`
|
||||
|
||||
<img width="1353" height="914" alt="grafik" src="https://github.com/user-attachments/assets/06bb4f36-a23a-4058-87ec-60440d322c25" />
|
||||
Here you can design the interface, it has credit/debit to change wallets balances and you can restrict access rights to extensions only for admins or generally deactivated for everyone. You can make users admins or set up Allowed Users if you want to restrict access. And of course the classic settings of the .env file, e.g. to change the funding source wallet or set a charge fee.
|
||||
|
||||
## Allowed Users
|
||||
Do not forget
|
||||
|
||||
When set **at least one**, LNbits becomes private: only the listed users and Admins can access the frontend. Account creation is disabled automatically. You can also disable account creation explicitly.
|
||||
```
|
||||
sudo systemctl start lnbits.service
|
||||
```
|
||||
|
||||
<img width="1889" height="870" alt="grafik" src="https://github.com/user-attachments/assets/89011b75-a267-44ea-971a-1517968b7af5" />
|
||||
|
||||
> [!WARNING]
|
||||
> Assign your own account first when enabling **Allowed Users** to avoid locking yourself out. If you do get locked out, use your Super User to recover access.
|
||||
|
||||
## Additional Guides
|
||||
|
||||
- **[Backend Wallets](./wallets.md)** — Explore options to fund your LNbits instance.
|
||||
- **[User Roles](./User_Roles.md)** — Overview of existing roles in LNbits.
|
||||
- **[Funding sources](./funding-sources_table.md)** — What is available and how to configure each.
|
||||
- **[Install LNBits](./installation.md)** — Choose your prefared way to install LNBits.
|
||||
|
||||
## Powered by LNbits
|
||||
|
||||
LNbits empowers everyone with modular, open source tools for building Bitcoin based systems — fast, free, and extendable.
|
||||
|
||||
If you like this project, [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visit our [Shop](https://shop.lnbits.de)
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://my.lnbits.com/login)
|
||||
[](https://news.lnbits.com/)
|
||||
[](https://extensions.lnbits.com/)
|
||||
A little hint, if you set `RESET TO DEFAULTS`, then a new Super User Account will also be created. The old one is then no longer valid.
|
||||
|
||||
@@ -1,79 +1,30 @@
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
# LNbits Funding Sources Comparison Table
|
||||
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits)
|
||||
[<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||
LNbits can use a number of different Lightning Network funding source.
|
||||
|
||||
# Backend Wallet Comparison Table
|
||||
There may be trade-offs between the funding sources used, for example funding LNbits using Strike requires the user to KYC themselves and has some
|
||||
privacy compromises versus running your own LND node. However the technical barrier to entry of using Strike is lower than using LND.
|
||||
|
||||
LNbits lets you choose **how your wallets are funded** — from fully self-custodial nodes to simple hosted services. You can switch the funding source **without touching your apps, users, or extensions**. That means you can start fast, learn, and later upgrade to more control and privacy when you are ready.
|
||||
|
||||
**Why this matters**
|
||||
|
||||
- **Flexibility:** Pick the backend that fits your skills and constraints today, change it later with minimal friction.
|
||||
- **Speed to ship:** Use a hosted option to get live quickly; move to a node when you need more control.
|
||||
- **Scalability:** Match cost and maintenance to your stage — from hobby to production.
|
||||
- **Privacy and compliance:** Choose between self-custody and provider-managed options depending on your requirements.
|
||||
|
||||
Below is a side-by-side comparison of Lightning funding sources you can use with LNbits.
|
||||
|
||||
> [!NOTE]
|
||||
> “Backend Wallet” and “Funding Source” mean the same thing — the wallet or service that funds your LNbits.
|
||||
The table below offers a comparison of the different Lightning Network funding sources that can be used with LNbits.
|
||||
|
||||
## LNbits Lightning Network Funding Sources Comparison Table
|
||||
|
||||
| **Funding Source** | **Custodial Type** | **KYC Required** | **Technical Knowledge Needed** | **Node Hosting Required** | **Privacy Level** | **Liquidity Management** | **Ease of Setup** | **Maintenance Effort** | **Cost Implications** | **Scalability** | **Notes** |
|
||||
| ------------------------------ | ------------------------ | ------------------- | ------------------------------ | ------------------------- | ----------------- | ------------------------ | ----------------- | ---------------------- | -------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------ |
|
||||
| **LND (gRPC)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | gRPC interface for LND; suitable for advanced integrations. |
|
||||
| **CoreLightning (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Requires setting up and managing your own CLN node. |
|
||||
| **Phoenixd** | Self-custodial | ❌ | Medium | ❌ | Medium | Automatic | Moderate | Low | Minimal fees | Medium | Mobile wallet backend; suitable for mobile integrations. |
|
||||
| **Nostr Wallet Connect (NWC)** | Custodial | Depends on provider | Low | ❌ | Variable | Provider-managed | Easy | Low | May incur fees | Medium | Connects via Nostr protocol; depends on provider's policies. |
|
||||
| **Boltz** | Self-custodial | ❌ | Medium | ❌ | Medium | Provider-managed | Moderate | Moderate | Minimal fees | Medium | Uses submarine swaps; connects to Boltz client. |
|
||||
| **LND (REST)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | REST interface for LND; suitable for web integrations. |
|
||||
| **CoreLightning REST** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | REST interface for CLN; suitable for web integrations. |
|
||||
| **LNbits (another instance)** | Custodial | Depends on host | Low | ❌ | Variable | Provider-managed | Easy | Low | May incur hosting fees | Medium | Connects to another LNbits instance; depends on host's policies. |
|
||||
| **Alby** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Browser extension wallet; suitable for web users. |
|
||||
| **Breez SDK** | Self-custodial | ❌ | Medium | ❌ | High | Automatic | Moderate | Low | Minimal fees | Medium | SDK for integrating Breez wallet functionalities. |
|
||||
| **OpenNode** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for merchants. |
|
||||
| **Blink** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. |
|
||||
| **ZBD** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. |
|
||||
| **Spark (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. |
|
||||
| **Cliche Wallet** | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. |
|
||||
| **Strike** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||
| **LNPay** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||
| **Eclair (ACINQ)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Connects via API; you run and manage your Eclair node. |
|
||||
| **LN.tips** | Custodial/Self-Custodial | Depends on provider | Medium | ❌ | Low | Provider-managed | Moderate | Low | Transaction fees may apply | Medium | Simple hosted service; use LN.tips API as your backend. |
|
||||
| **Fake Wallet** | Testing (simulated) | ❌ | Low | ❌ | N/A | N/A | Easy | Low | None (test only) | N/A | For testing only; mints accounting units in LNbits (no real sats, unit name configurable). |
|
||||
|
||||
---
|
||||
|
||||
### Notes for readers
|
||||
|
||||
- These are typical characteristics; your exact experience may vary by configuration and provider policy.
|
||||
- Pick based on your constraints: compliance (KYC), privacy, ops effort, and time-to-ship.
|
||||
|
||||
---
|
||||
|
||||
## Additional Guides
|
||||
|
||||
- **[Admin UI](./admin_ui.md)** — Manage server settings via a clean UI (avoid editing `.env` by hand).
|
||||
- **[User Roles](./User_Roles.md)** — Quick Overview of existing Roles in LNBits.
|
||||
- **[Funding sources](./funding-sources_table.md)** — What’s available and how to enable/configure each.
|
||||
|
||||
## Powered by LNbits
|
||||
|
||||
LNbits empowers everyone with modular, open-source tools for building Bitcoin-based systems — fast, free, and extendable.
|
||||
|
||||
If you like this project, [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visit our [Shop](https://shop.lnbits.de)
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://my.lnbits.com/login)
|
||||
[](https://news.lnbits.com/)
|
||||
[](https://extensions.lnbits.com/)
|
||||
| **Funding Source** | **Custodial Type** | **KYC Required** | **Technical Knowledge Needed** | **Node Hosting Required** | **Privacy Level** | **Liquidity Management** | **Ease of Setup** | **Maintenance Effort** | **Cost Implications** | **Scalability** | **Notes** |
|
||||
| -------------------------- | ------------------ | ------------------- | ------------------------------ | ------------------------- | ----------------- | ------------------------ | ----------------- | ---------------------- | -------------------------------------------- | --------------- | ---------------------------------------------------------------- |
|
||||
| LND (gRPC) | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | gRPC interface for LND; suitable for advanced integrations. |
|
||||
| CoreLightning (CLN) | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Requires setting up and managing your own CLN node. |
|
||||
| Phoenixd | Self-custodial | ❌ | Medium | ❌ | Medium | Automatic | Moderate | Low | Minimal fees | Medium | Mobile wallet backend; suitable for mobile integrations. |
|
||||
| Nostr Wallet Connect (NWC) | Custodial | Depends on provider | Low | ❌ | Variable | Provider-managed | Easy | Low | May incur fees | Medium | Connects via Nostr protocol; depends on provider's policies. |
|
||||
| Boltz | Self-custodial | ❌ | Medium | ❌ | Medium | Provider-managed | Moderate | Moderate | Minimal fees | Medium | Uses submarine swaps; connects to Boltz client. |
|
||||
| LND (REST) | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | REST interface for LND; suitable for web integrations. |
|
||||
| CoreLightning REST | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | REST interface for CLN; suitable for web integrations. |
|
||||
| LNbits (another instance) | Custodial | Depends on host | Low | ❌ | Variable | Provider-managed | Easy | Low | May incur hosting fees | Medium | Connects to another LNbits instance; depends on host's policies. |
|
||||
| Alby | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Browser extension wallet; suitable for web users. |
|
||||
| Breez SDK | Self-custodial | ❌ | Medium | ❌ | High | Automatic | Moderate | Low | Minimal fees | Medium | SDK for integrating Breez wallet functionalities. |
|
||||
| OpenNode | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for merchants. |
|
||||
| Blink | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. |
|
||||
| ZBD | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. |
|
||||
| Spark (CLN) | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. |
|
||||
| Cliche Wallet | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. |
|
||||
| Strike | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||
| LNPay | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||
|
||||
+195
-455
@@ -1,66 +1,39 @@
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
   [](https://extensions.lnbits.com/) [<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits) <img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">
|
||||
---
|
||||
layout: default
|
||||
title: Basic installation
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Basic installation
|
||||
|
||||
> [!NOTE]
|
||||
> **Default DB:** LNbits uses SQLite by default (simple & effective). You can switch to PostgreSQL — see the section below.
|
||||
Note that by default LNbits uses SQLite as its database, which is simple and effective but you can configure it to use PostgreSQL instead which is also described in a section below.
|
||||
|
||||
## Table of contents
|
||||
## Option 1: AppImage (LInux)
|
||||
|
||||
- [Option 1: AppImage (Linux)](#option-1-appimage-linux)
|
||||
- [Option 2: UV (recommended for developers)](#option-2-uv-recommended-for-developers)
|
||||
- [Option 2a (Legacy): Poetry — Replaced by UV](#option-2a-legacy-poetry--replaced-by-uv)
|
||||
- [Option 3: Install script (Debian/Ubuntu)](#option-3-install-script-debianubuntu)
|
||||
- [Option 4: Nix](#option-4-nix)
|
||||
- [Option 5: Docker](#option-5-docker)
|
||||
- [Option 6: Fly.io](#option-6-flyio)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Optional: PostgreSQL database](#optional-postgresql-database)
|
||||
- [Using LNbits](#using-lnbits)
|
||||
- [Additional guides](#additional-guides)
|
||||
- [Update LNbits (all methods)](#update-lnbits-all-methods)
|
||||
- [SQLite → PostgreSQL migration](#sqlite--postgresql-migration)
|
||||
- [LNbits as a systemd service](#lnbits-as-a-systemd-service)
|
||||
- [Reverse proxy with automatic HTTPS (Caddy)](#reverse-proxy-with-automatic-https-caddy)
|
||||
- [Apache2 reverse proxy over HTTPS](#apache2-reverse-proxy-over-https)
|
||||
- [Nginx reverse proxy over HTTPS](#nginx-reverse-proxy-over-https)
|
||||
- [HTTPS without a reverse proxy (self-signed)](#https-without-a-reverse-proxy-self-signed)
|
||||
- [LNbits on Umbrel behind Tor](#lnbits-on-umbrel-behind-tor)
|
||||
- [FreeBSD notes](#freebsd-notes)
|
||||
### AppImage (Linux)
|
||||
|
||||
## Option 1: AppImage (Linux)
|
||||
|
||||
**Quickstart**
|
||||
|
||||
1. Download latest AppImage from [releases](https://github.com/lnbits/lnbits/releases) **or** run:
|
||||
Go to [releases](https://github.com/lnbits/lnbits/releases) and pull latest AppImage, or:
|
||||
|
||||
```sh
|
||||
sudo apt-get install jq libfuse2
|
||||
sudo apt-get install libfuse2
|
||||
wget $(curl -s https://api.github.com/repos/lnbits/lnbits/releases/latest | jq -r '.assets[] | select(.name | endswith(".AppImage")) | .browser_download_url') -O LNbits-latest.AppImage
|
||||
chmod +x LNbits-latest.AppImage
|
||||
LNBITS_ADMIN_UI=true HOST=0.0.0.0 PORT=5000 ./LNbits-latest.AppImage # most system settings are now in the admin UI, but pass additional .env variables here
|
||||
```
|
||||
|
||||
- LNbits will create a folder for DB and extension files **in the same directory** as the AppImage.
|
||||
|
||||
> [!NOTE]
|
||||
> **Next steps**
|
||||
> Install complete → **[Running LNbits](#run-the-server)**
|
||||
> Update LNBits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
LNbits will create a folder for db and extension files in the folder the AppImage runs from.
|
||||
|
||||
## Option 2: UV (recommended for developers)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **It is recommended to use the latest version of UV & 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.
|
||||
|
||||
### Verify Python
|
||||
### Install Python 3.12
|
||||
|
||||
## Option 2 (recommended): UV
|
||||
|
||||
It is recommended to use the latest version of UV. Make sure you have Python version 3.10 or higher installed.
|
||||
|
||||
### Verify Python version
|
||||
|
||||
```sh
|
||||
python3 --version
|
||||
@@ -73,86 +46,26 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
### Install LNbits
|
||||
|
||||
```sh
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
git checkout main
|
||||
uv sync --all-extras
|
||||
|
||||
cp .env.example .env
|
||||
# Optional: set funding source and other options in .env (e.g., `nano .env`)
|
||||
```
|
||||
|
||||
### Run the server
|
||||
|
||||
```sh
|
||||
uv run lnbits
|
||||
# To change port/host: uv run lnbits --port 9000 --host 0.0.0.0
|
||||
# Add --debug to the command above and set DEBUG=true in .env for verbose output
|
||||
```
|
||||
|
||||
### LNbits CLI
|
||||
|
||||
```sh
|
||||
# Useful for superuser ID, updating extensions, etc.
|
||||
uv run lnbits-cli --help
|
||||
```
|
||||
|
||||
### Update LNbits
|
||||
|
||||
```sh
|
||||
cd lnbits
|
||||
# Stop LNbits with Ctrl + X or your service manager
|
||||
# sudo systemctl stop lnbits
|
||||
|
||||
# Update code
|
||||
git pull --rebase
|
||||
|
||||
uv sync --all-extras
|
||||
uv run lnbits
|
||||
```
|
||||
|
||||
#### Use Admin UI → Extensions → "Update All" to bring extensions up to the proper level
|
||||
|
||||
> [!NOTE]
|
||||
> **Next steps**
|
||||
> Install complete → **[Running LNbits](#run-the-server)**
|
||||
> Update LNBits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
|
||||
## Option 2a (Legacy): Poetry — _Replaced by UV_
|
||||
|
||||
<details>
|
||||
<summary><strong>Poetry install and update (legacy workflow)</summary>
|
||||
|
||||
This legacy section is preserved for older environments.
|
||||
**UV is the recommended (and faster) tool** for new installs. Use Poetry only if you have personal preferences or must support an older workflow.
|
||||
|
||||
> 
|
||||
> **It is recommended to use the latest version of Poetry & Make sure you have Python version 3.12 installed.**
|
||||
|
||||
### Verify Python version
|
||||
|
||||
```sh
|
||||
python3 --version
|
||||
```
|
||||
|
||||
### Install Poetry
|
||||
### (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
|
||||
### install LNbits
|
||||
|
||||
```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`
|
||||
```
|
||||
@@ -160,53 +73,50 @@ cp .env.example .env
|
||||
#### Running the server
|
||||
|
||||
```sh
|
||||
poetry run lnbits
|
||||
# To change port/host: poetry run lnbits --port 9000 --host 0.0.0.0
|
||||
# Add --debug to help troubleshooting (also set DEBUG=true in .env)
|
||||
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.
|
||||
```
|
||||
|
||||
#### LNbits CLI
|
||||
#### LNbits-cli
|
||||
|
||||
```sh
|
||||
# A very useful terminal client for getting the superuser ID, updating extensions, etc.
|
||||
poetry run lnbits-cli --help
|
||||
# A very useful terminal client for getting the supersuer ID, updating extensions, etc
|
||||
uv run lnbits-cli --help
|
||||
```
|
||||
|
||||
#### Updating the server
|
||||
|
||||
```sh
|
||||
cd lnbits
|
||||
# Stop LNbits with Ctrl + X or with your service manager
|
||||
# Stop LNbits with `ctrl + x` or with service manager
|
||||
# sudo systemctl stop lnbits
|
||||
|
||||
# Update LNbits
|
||||
git pull --rebase
|
||||
|
||||
# Check your Poetry Python version
|
||||
poetry env list
|
||||
# If version is less than 3.12, update it:
|
||||
poetry env use python3.12
|
||||
poetry env remove python3.X
|
||||
poetry env list
|
||||
# Check your poetry version with
|
||||
# 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
|
||||
|
||||
# Reinstall and start
|
||||
poetry install --only main
|
||||
poetry run lnbits
|
||||
# Run install and start LNbits with
|
||||
# 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
|
||||
```
|
||||
|
||||
#### Use Admin UI → Extensions → "Update All" to bring extensions up to the proper level
|
||||
|
||||
> 
|
||||
> **Next steps**
|
||||
> Install complete → **[Running LNbits](#run-the-server)**
|
||||
> Update LNBits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
|
||||
</details>
|
||||
|
||||
## Option 3: Install script (Debian/Ubuntu)
|
||||
|
||||
<details>
|
||||
<summary><strong>Show install script</strong> (one-line setup)</summary>
|
||||
## Option 2: Install script (on Debian/Ubuntu)
|
||||
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits.sh &&
|
||||
@@ -214,23 +124,15 @@ chmod +x lnbits.sh &&
|
||||
./lnbits.sh
|
||||
```
|
||||
|
||||
- You can use `./lnbits.sh` to run, but for more control: `cd lnbits` and use `uv run lnbits` (see Option 2).
|
||||
Now visit `0.0.0.0:5000` to make a super-user account.
|
||||
|
||||
> 
|
||||
> **Next steps**
|
||||
> Install complete → **[Running LNbits](#run-the-server)**
|
||||
> Update LNBits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
`./lnbits.sh` can be used to run, but for more control `cd lnbits` and use `uv run lnbits` (see previous option).
|
||||
|
||||
</details>
|
||||
|
||||
## Option 4: Nix
|
||||
|
||||
<details>
|
||||
<summary><strong>Show Nix instructions</strong> (flakes, cachix, run)</summary>
|
||||
## 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 --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yes
|
||||
sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon
|
||||
|
||||
# Enable nix-command and flakes experimental features for nix:
|
||||
grep -qxF 'experimental-features = nix-command flakes' /etc/nix/nix.conf || \
|
||||
@@ -243,29 +145,16 @@ echo "trusted-users = root $USER" | sudo tee -a /etc/nix/nix.conf
|
||||
# Restart daemon so changes apply
|
||||
sudo systemctl restart nix-daemon
|
||||
|
||||
# Clone and build LNbits
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
|
||||
# Make data directory and persist data/extension folders
|
||||
mkdir data
|
||||
PROJECT_DIR="$(pwd)"
|
||||
{
|
||||
echo "export PYTHONPATH=\"$PROJECT_DIR/ns:\$PYTHONPATH\""
|
||||
echo "export LNBITS_DATA_FOLDER=\"$PROJECT_DIR/data\""
|
||||
echo "export LNBITS_EXTENSIONS_PATH=\"$PROJECT_DIR\""
|
||||
} >> ~/.bashrc
|
||||
grep -qxF '. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ~/.bashrc || \
|
||||
echo '. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' >> ~/.bashrc
|
||||
. ~/.bashrc
|
||||
|
||||
# Add cachix for cached binaries
|
||||
nix-env -iA cachix -f https://cachix.org/api/v1/install
|
||||
cachix use lnbits
|
||||
|
||||
# Build LNbits
|
||||
# Clone and build LNbits
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
nix build
|
||||
|
||||
mkdir data
|
||||
```
|
||||
|
||||
#### Running the server
|
||||
@@ -285,36 +174,26 @@ LNBITS_ADMIN_UI=true ./result/bin/lnbits --port 9000 --host 0.0.0.0
|
||||
SUPER_USER=be54db7f245346c8833eaa430e1e0405 LNBITS_ADMIN_UI=true ./result/bin/lnbits --port 9000
|
||||
```
|
||||
|
||||
> 
|
||||
> **Next steps**
|
||||
> Update LNBits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
## Option 4: Docker
|
||||
|
||||
</details>
|
||||
|
||||
## Option 5: Docker
|
||||
|
||||
<details>
|
||||
<summary><strong>Show Docker instructions</strong> (official image, volumes, extensions)</summary>
|
||||
|
||||
**Use latest image**
|
||||
Use latest version from Docker Hub.
|
||||
|
||||
```sh
|
||||
docker pull lnbits/lnbits
|
||||
wget https://raw.githubusercontent.com/lnbits/lnbits/main/.env.example -O .env
|
||||
mkdir data
|
||||
docker run --detach --publish 5000:5000 --name lnbits \
|
||||
--volume ${PWD}/.env:/app/.env \
|
||||
--volume ${PWD}/data/:/app/data \
|
||||
lnbits/lnbits
|
||||
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits/lnbits
|
||||
```
|
||||
|
||||
- The LNbits Docker image ships **without any extensions**; by default, any extensions you install are stored **inside the container** and will be **lost** when the container is removed, so you should set `LNBITS_EXTENSIONS_PATH` to a directory that’s **mapped to a persistent host volume** so extensions **survive rebuilds/recreates**—for example:
|
||||
The LNbits Docker image comes with no extensions installed. User-installed extensions will be stored by default in a container directory.
|
||||
It is recommended to point the `LNBITS_EXTENSIONS_PATH` environment variable to a directory that is mapped to a Docker volume. This way, the extensions will not be reinstalled when the container is destroyed.
|
||||
Example:
|
||||
|
||||
```sh
|
||||
docker run ... -e "LNBITS_EXTENSIONS_PATH='/app/data/extensions'" --volume ${PWD}/data/:/app/data ...
|
||||
```
|
||||
|
||||
**Build image yourself**
|
||||
Build the image yourself.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
@@ -322,39 +201,24 @@ cd lnbits
|
||||
docker build -t lnbits/lnbits .
|
||||
cp .env.example .env
|
||||
mkdir data
|
||||
docker run --detach --publish 5000:5000 --name lnbits \
|
||||
--volume ${PWD}/.env:/app/.env \
|
||||
--volume ${PWD}/data/:/app/data \
|
||||
lnbits/lnbits
|
||||
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits/lnbits
|
||||
```
|
||||
|
||||
You can optionally override the install extras for both **Poetry** and **UV** to include optional features during build or setup:
|
||||
|
||||
- with Poetry, pass extras via the `POETRY_INSTALL_ARGS` Docker build-arg (e.g., to enable the **Breez** funding source: `docker build --build-arg POETRY_INSTALL_ARGS="-E breez" -t lnbits/lnbits .`);
|
||||
- with UV, enable extras during environment sync (e.g., locally run `uv sync --extra breez` or `uv sync --all-extras`), and—**if your Dockerfile supports it**—you can mirror the same at build time via a build-arg such as `UV_SYNC_ARGS` (example pattern: `docker build --build-arg UV_SYNC_ARGS="--extra breez" -t lnbits/lnbits .`).
|
||||
|
||||
**Enable Breez funding source at build**
|
||||
You can optionally override the arguments that are passed to `poetry install` during the build process by setting the Docker build argument named `POETRY_INSTALL_ARGS`. For example, to enable the Breez funding source, build the Docker image with the command:
|
||||
|
||||
```sh
|
||||
docker build --build-arg POETRY_INSTALL_ARGS="-E breez" -t lnbits/lnbits .
|
||||
```
|
||||
|
||||
> 
|
||||
> **Next steps**
|
||||
> Install complete → **[Running LNbits](#run-the-server)**
|
||||
> Update LNBits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
## Option 5: Fly.io
|
||||
|
||||
</details>
|
||||
Fly.io is a docker container hosting platform that has a generous free tier. You can host LNbits for free on Fly.io for personal use.
|
||||
|
||||
## Option 6: Fly.io
|
||||
First, sign up for an account at [Fly.io](https://fly.io) (no credit card required).
|
||||
|
||||
<details>
|
||||
<summary><strong>Deploy LNbits on Fly.io (free tier friendly)</summary>
|
||||
Then, install the Fly.io CLI onto your device [here](https://fly.io/docs/getting-started/installing-flyctl/).
|
||||
|
||||
**Fly.io is a docker container hosting platform that has a generous free tier. You can host LNbits for free on Fly.io for personal use.**
|
||||
|
||||
1. Create an account at [Fly.io](https://fly.io).
|
||||
2. Install the Fly.io CLI ([guide](https://fly.io/docs/getting-started/installing-flyctl/)).
|
||||
After install is complete, the command will output a command you should copy/paste/run to get `fly` into your `$PATH`. Something like:
|
||||
|
||||
```
|
||||
flyctl was installed successfully to /home/ubuntu/.fly/bin/flyctl
|
||||
@@ -363,9 +227,9 @@ Manually add the directory to your $HOME/.bash_profile (or similar)
|
||||
export PATH="$FLYCTL_INSTALL/bin:$PATH"
|
||||
```
|
||||
|
||||
3. You can either run those commands, then `source ~/.bash_profile` or, if you don't, you'll have to call Fly from `~/.fly/bin/flyctl`.
|
||||
You can either run those commands, then `source ~/.bash_profile` or, if you don't, you'll have to call Fly from `~/.fly/bin/flyctl`.
|
||||
|
||||
- Once installed, run the following commands.
|
||||
Once installed, run the following commands.
|
||||
|
||||
```
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
@@ -379,16 +243,9 @@ You'll be prompted to enter an app name, region, postgres (choose no), deploy no
|
||||
|
||||
You'll now find a file in the directory called `fly.toml`. Open that file and modify/add the following settings.
|
||||
|
||||
> 
|
||||
> Be sure to replace `${PUT_YOUR_LNBITS_ENV_VARS_HERE}` with all relevant environment variables in `.env` or `.env.example`.
|
||||
> Environment variable strings should be quoted here. For example, if `.env` has
|
||||
> `LNBITS_ENDPOINT=https://demo.lnbits.com`, then in `fly.toml` use
|
||||
> `LNBITS_ENDPOINT="https://demo.lnbits.com"`.
|
||||
Note: Be sure to replace `${PUT_YOUR_LNBITS_ENV_VARS_HERE}` with all relevant environment variables in `.env` or `.env.example`. Environment variable strings should be quoted here, so if in `.env` you have `LNBITS_ENDPOINT=https://demo.lnbits.com` in `fly.toml` you should have `LNBITS_ENDPOINT="https://demo.lnbits.com"`.
|
||||
|
||||
> 
|
||||
> Don't enter secret environment variables here. Fly.io offers **secrets** (via `fly secrets`) that are exposed as env vars at runtime.
|
||||
> Example (LND REST funding source):
|
||||
> `fly secrets set LND_REST_MACAROON=<hex_macaroon_data>`
|
||||
Note: Don't enter secret environment variables here. Fly.io offers secrets (via the `fly secrets` command) that are exposed as environment variables in your runtime. So, for example, if using the LND_REST funding source, you can run `fly secrets set LND_REST_MACAROON=<hex_macaroon_data>`.
|
||||
|
||||
```
|
||||
...
|
||||
@@ -443,49 +300,26 @@ sudo apt install python3.10-dev gcc build-essential
|
||||
poetry add setuptools wheel
|
||||
```
|
||||
|
||||
> 
|
||||
>
|
||||
> **Next steps**
|
||||
> Install complete → **[Running LNbits](#run-the-server)**
|
||||
> Update LNbits → **[Update LNbits (all methods)](#update-lnbits-all-methods)**
|
||||
### Optional: PostgreSQL database
|
||||
|
||||
## Troubleshooting
|
||||
If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and setup a database for LNbits:
|
||||
|
||||
```sh
|
||||
sudo apt install pkg-config libffi-dev libpq-dev
|
||||
# on debian/ubuntu 'sudo apt-get -y install postgresql'
|
||||
# or follow instructions at https://www.postgresql.org/download/linux/
|
||||
|
||||
# build essentials (Debian/Ubuntu)
|
||||
sudo apt install python3.10-dev gcc build-essential
|
||||
|
||||
# if secp256k1 build fails and you used poetry
|
||||
poetry add setuptools wheel
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
</details>
|
||||
|
||||
## Optional: PostgreSQL database
|
||||
|
||||
> [!TIP]
|
||||
> If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and set up a database for LNbits.
|
||||
|
||||
```sh
|
||||
# Debian/Ubuntu: sudo apt-get -y install postgresql
|
||||
# or see https://www.postgresql.org/download/linux/
|
||||
|
||||
# Create a password for the postgres user
|
||||
# Postgres doesn't have a default password, so we'll create one.
|
||||
sudo -i -u postgres
|
||||
psql
|
||||
# in psql
|
||||
ALTER USER postgres PASSWORD 'myPassword';
|
||||
# on psql
|
||||
ALTER USER postgres PASSWORD 'myPassword'; # choose whatever password you want
|
||||
\q
|
||||
# back as postgres user
|
||||
# on postgres user
|
||||
createdb lnbits
|
||||
exit
|
||||
```
|
||||
|
||||
**Configure LNbits**
|
||||
You need to edit the `.env` file.
|
||||
|
||||
```sh
|
||||
# add the database connection string to .env 'nano .env' LNBITS_DATABASE_URL=
|
||||
@@ -496,189 +330,44 @@ LNBITS_DATABASE_URL="postgres://postgres:postgres@localhost:5432/lnbits"
|
||||
|
||||
# Using LNbits
|
||||
|
||||
Visit **[http://localhost:5000/](http://localhost:5000/)** (or `0.0.0.0:5000`).
|
||||
Now you can visit your LNbits at http://localhost:5000/.
|
||||
|
||||
### Option A — First-run setup in the Browser (UI)
|
||||
Now modify the `.env` file with any settings you prefer and add a proper [funding source](./wallets.md) by modifying the value of `LNBITS_BACKEND_WALLET_CLASS` and providing the extra information and credentials related to the chosen funding source.
|
||||
|
||||
1. On the **first start**, LNbits will **prompt you to Setup a SuperUser**.
|
||||
2. After creating it, you’ll be **redirected to the Admin UI as SuperUser**.
|
||||
3. In the Admin UI, **set your funding source** (backend wallet) and other preferences.
|
||||
4. **Restart LNbits** if prompted or after changing critical settings.
|
||||
Then you can restart it and it will be using the new settings.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Use the **SuperUser only** for initial setup and instance settings (funding source, configuration, Topup).
|
||||
> For maintenance, create a separate **Admin** account. For everyday usage (payments, wallets, etc.), **do not use the SuperUser** — use admin or regular user accounts instead. Its a bad behaviour.
|
||||
> Read more about [SuperUser](./super_user.md) and [Admin UI](./admin_ui.md)
|
||||
You might also need to install additional packages or perform additional setup steps, depending on the chosen backend. See [the short guide](./wallets.md) on each different funding source.
|
||||
|
||||
### Option B — Configure via `.env`
|
||||
|
||||
1. Edit your `.env` with preferred settings (funding, base URL, etc.).
|
||||
2. Set a funding source by configuring:
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`
|
||||
- plus the required credentials for your chosen backend (see **[wallets.md](./wallets.md)**).
|
||||
|
||||
3. **Restart LNbits** to apply changes.
|
||||
|
||||
---
|
||||
|
||||
> [!NOTE]
|
||||
> **Paths overview**
|
||||
>
|
||||
> - **SuperUser file:** `<lnbits_root>/data/.super_user`
|
||||
> Example: `~/lnbits/data/.super_user` • View: `cat ~/lnbits/data/.super_user`
|
||||
> - **Environment file:** `<lnbits_root>/.env` (for bare-metal installs)
|
||||
> - **Docker:** bind a host directory to `/app/data`.
|
||||
> On the host the SuperUser file is at `<host_data_dir>/.super_user`.
|
||||
> The container reads `/app/.env` (usually bind-mounted from your project root).
|
||||
|
||||
> [!TIP]
|
||||
> **Local Lightning test network**
|
||||
> Use **Polar** to spin up a safe local Lightning environment and test LNbits without touching your live setup.
|
||||
> https://lightningpolar.com/
|
||||
|
||||
> [!TIP]
|
||||
> **API comparison before updates**
|
||||
> Use **TableTown** to diff your LNbits instance against another (dev vs prod) or the upstream dev branch. Spot endpoint changes before updating.
|
||||
> Crafted by [Arbadacarbayk](https://github.com/arbadacarbaYK) - a standout contribution that makes pre-release reviews fast and reliable.
|
||||
> https://arbadacarbayk.github.io/LNbits_TableTown/
|
||||
Take a look at [Polar](https://lightningpolar.com/) for an excellent way of spinning up a Lightning Network dev environment.
|
||||
|
||||
# Additional guides
|
||||
|
||||
## Update LNbits (all methods)
|
||||
## SQLite to PostgreSQL migration
|
||||
|
||||
> After updating, open **Admin UI → Extensions → “Update All”** to make sure extensions match the core version.
|
||||
If you already have LNbits installed and running, on an SQLite database, we **highly** recommend you migrate to postgres if you are planning to run LNbits on scale.
|
||||
|
||||
<details>
|
||||
<summary><strong>UV (recommended)</strong></summary>
|
||||
|
||||
```sh
|
||||
cd lnbits
|
||||
git pull --rebase
|
||||
uv sync --all-extras
|
||||
# restart (dev)
|
||||
uv run lnbits
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Poetry (legacy)</strong></summary>
|
||||
|
||||
```sh
|
||||
cd lnbits
|
||||
git pull --rebase
|
||||
# Optional: ensure Python 3.12
|
||||
poetry env list
|
||||
poetry env use python3.12
|
||||
poetry install --only main
|
||||
# restart (dev)
|
||||
poetry run lnbits
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>AppImage</strong></summary>
|
||||
|
||||
Download the latest AppImage from Releases and replace your old file **in the same directory** to keep the `./data` folder (DB, extensions).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Install script (Debian/Ubuntu)</strong></summary>
|
||||
|
||||
```sh
|
||||
# If you installed via lnbits.sh:
|
||||
cd lnbits
|
||||
git pull --rebase
|
||||
# then use your chosen runner (UV recommended)
|
||||
uv sync --all-extras
|
||||
uv run lnbits
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Nix</strong></summary>
|
||||
|
||||
```sh
|
||||
cd lnbits
|
||||
git pull --rebase
|
||||
nix build
|
||||
# restart
|
||||
nix run
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Docker (official image)</strong></summary>
|
||||
|
||||
```sh
|
||||
docker pull lnbits/lnbits
|
||||
docker stop lnbits && docker rm lnbits
|
||||
docker run --detach --publish 5000:5000 --name lnbits \
|
||||
--volume ${PWD}/.env:/app/.env \
|
||||
--volume ${PWD}/data/:/app/data \
|
||||
lnbits/lnbits
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Docker (build yourself)</strong></summary>
|
||||
|
||||
```sh
|
||||
cd lnbits
|
||||
git pull --rebase
|
||||
docker build -t lnbits/lnbits .
|
||||
docker stop lnbits && docker rm lnbits
|
||||
docker run --detach --publish 5000:5000 --name lnbits \
|
||||
--volume ${PWD}/.env:/app/.env \
|
||||
--volume ${PWD}/data/:/app/data \
|
||||
lnbits/lnbits
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Fly.io</strong></summary>
|
||||
|
||||
```sh
|
||||
# If using Dockerfile in repo (recommended)
|
||||
cd lnbits
|
||||
git pull --rebase
|
||||
fly deploy
|
||||
# Logs & shell if needed
|
||||
fly logs
|
||||
fly ssh console
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## SQLite → PostgreSQL migration
|
||||
|
||||
> [!TIP]
|
||||
> If you run on SQLite and plan to scale, migrate to Postgres.
|
||||
There's a script included that can do the migration easy. You should have Postgres already installed and there should be a password for the user (see Postgres install guide above). Additionally, your LNbits instance should run once on postgres to implement the database schema before the migration works:
|
||||
|
||||
```sh
|
||||
# STOP LNbits
|
||||
|
||||
# Edit .env with Postgres URL
|
||||
# add the database connection string to .env 'nano .env' LNBITS_DATABASE_URL=
|
||||
# postgres://<user>:<password>@<host>/<database> - alter line bellow with your user, password and db name
|
||||
LNBITS_DATABASE_URL="postgres://postgres:postgres@localhost/lnbits"
|
||||
# save and exit
|
||||
|
||||
# START then STOP LNbits once to apply schema
|
||||
# START LNbits
|
||||
# STOP LNbits
|
||||
uv run python tools/conv.py
|
||||
# or
|
||||
make migration
|
||||
```
|
||||
|
||||
- Launch LNbits again and verify.
|
||||
Hopefully, everything works and get migrated... Launch LNbits again and check if everything is working properly.
|
||||
|
||||
## LNbits as a systemd service
|
||||
|
||||
Create `/etc/systemd/system/lnbits.service`:
|
||||
Systemd is great for taking care of your LNbits instance. It will start it on boot and restart it in case it crashes. If you want to run LNbits as a systemd service on your Debian/Ubuntu/Raspbian server, create a file at `/etc/systemd/system/lnbits.service` with the following content:
|
||||
|
||||
```
|
||||
# Systemd unit for lnbits
|
||||
@@ -686,14 +375,17 @@ Create `/etc/systemd/system/lnbits.service`:
|
||||
|
||||
[Unit]
|
||||
Description=LNbits
|
||||
# Optional: start after your backend
|
||||
# you can uncomment these lines if you know what you're doing
|
||||
# it will make sure that lnbits starts after lnd (replace with your own backend service)
|
||||
#Wants=lnd.service
|
||||
#After=lnd.service
|
||||
|
||||
[Service]
|
||||
# replace with the absolute path of your lnbits installation
|
||||
WorkingDirectory=/home/lnbits/lnbits
|
||||
# Find uv path via `which uv`
|
||||
# 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
|
||||
TimeoutSec=120
|
||||
@@ -704,23 +396,33 @@ Environment=PYTHONUNBUFFERED=1
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable & start:
|
||||
Save the file and run the following commands:
|
||||
|
||||
```sh
|
||||
sudo systemctl enable lnbits.service
|
||||
sudo systemctl start lnbits.service
|
||||
```
|
||||
|
||||
## Reverse proxy with automatic HTTPS (Caddy)
|
||||
## Reverse proxy with automatic HTTPS using Caddy
|
||||
|
||||
Point your domain A-record to your server IP. Install Caddy: [Caddy install guide](https://caddyserver.com/docs/install#debian-ubuntu-raspbian)
|
||||
Use Caddy to make your LNbits install accessible over clearnet with a domain and https cert.
|
||||
|
||||
```sh
|
||||
Point your domain at the IP of the server you're running LNbits on, by making an `A` record.
|
||||
|
||||
Install Caddy on the server
|
||||
https://caddyserver.com/docs/install#debian-ubuntu-raspbian
|
||||
|
||||
```
|
||||
sudo caddy stop
|
||||
```
|
||||
|
||||
Create a Caddyfile
|
||||
|
||||
```
|
||||
sudo nano Caddyfile
|
||||
```
|
||||
|
||||
Add:
|
||||
Assuming your LNbits is running on port `5000` add:
|
||||
|
||||
```
|
||||
yourdomain.com {
|
||||
@@ -730,21 +432,28 @@ yourdomain.com {
|
||||
}
|
||||
```
|
||||
|
||||
Save (Ctrl+X) and start:
|
||||
Save and exit `CTRL + x`
|
||||
|
||||
```sh
|
||||
```
|
||||
sudo caddy start
|
||||
```
|
||||
|
||||
## Apache2 reverse proxy over HTTPS
|
||||
## Running behind an Apache2 reverse proxy over HTTPS
|
||||
|
||||
Install Apache2 and enable Apache2 mods:
|
||||
|
||||
```sh
|
||||
apt-get install apache2 certbot
|
||||
a2enmod headers ssl proxy proxy_http
|
||||
```
|
||||
|
||||
Create a SSL certificate with LetsEncrypt:
|
||||
|
||||
```sh
|
||||
certbot certonly --webroot --agree-tos --non-interactive --webroot-path /var/www/html -d lnbits.org
|
||||
```
|
||||
|
||||
Create `/etc/apache2/sites-enabled/lnbits.conf`:
|
||||
Create an Apache2 vhost at: `/etc/apache2/sites-enabled/lnbits.conf`:
|
||||
|
||||
```sh
|
||||
cat <<EOF > /etc/apache2/sites-enabled/lnbits.conf
|
||||
@@ -771,20 +480,27 @@ cat <<EOF > /etc/apache2/sites-enabled/lnbits.conf
|
||||
EOF
|
||||
```
|
||||
|
||||
Restart:
|
||||
Restart Apache2:
|
||||
|
||||
```sh
|
||||
service apache2 restart
|
||||
```
|
||||
|
||||
## Nginx reverse proxy over HTTPS
|
||||
## Running behind an Nginx reverse proxy over HTTPS
|
||||
|
||||
Install nginx:
|
||||
|
||||
```sh
|
||||
apt-get install nginx certbot
|
||||
```
|
||||
|
||||
Create a SSL certificate with LetsEncrypt:
|
||||
|
||||
```sh
|
||||
certbot certonly --nginx --agree-tos -d lnbits.org
|
||||
```
|
||||
|
||||
Create `/etc/nginx/sites-enabled/lnbits.org`:
|
||||
Create an nginx vhost at `/etc/nginx/sites-enabled/lnbits.org`:
|
||||
|
||||
```sh
|
||||
cat <<EOF > /etc/nginx/sites-enabled/lnbits.org
|
||||
@@ -818,22 +534,23 @@ server {
|
||||
EOF
|
||||
```
|
||||
|
||||
Restart:
|
||||
Restart nginx:
|
||||
|
||||
```sh
|
||||
service nginx restart
|
||||
```
|
||||
|
||||
---
|
||||
## Using https without reverse proxy
|
||||
|
||||
## HTTPS without a reverse proxy (self-signed)
|
||||
The most common way of using LNbits via https is to use a reverse proxy such as Caddy, nginx, or ngriok. However, you can also run LNbits via https without additional software. This is useful for development purposes or if you want to use LNbits in your local network.
|
||||
|
||||
Create a self-signed cert (useful for local/dev). Browsers won’t trust it by default.
|
||||
We have to create a self-signed certificate using `mkcert`. Note that this certificate is not "trusted" by most browsers but that's fine (since you know that you have created it) and encryption is always better than clear text.
|
||||
|
||||
### Install mkcert
|
||||
#### Install mkcert
|
||||
|
||||
- Install instructions: [mkcert README](https://github.com/FiloSottile/mkcert)
|
||||
- Ubuntu example:
|
||||
You can find the install instructions for `mkcert` [here](https://github.com/FiloSottile/mkcert).
|
||||
|
||||
Install mkcert on Ubuntu:
|
||||
|
||||
```sh
|
||||
sudo apt install libnss3-tools
|
||||
@@ -842,47 +559,70 @@ chmod +x mkcert-v*-linux-amd64
|
||||
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
|
||||
```
|
||||
|
||||
### Create certificate
|
||||
#### Create certificate
|
||||
|
||||
**OpenSSL**
|
||||
To create a certificate, first `cd` into your LNbits folder and execute the following command on Linux:
|
||||
|
||||
```sh
|
||||
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out cert.pem -keyout key.pem
|
||||
```
|
||||
|
||||
**mkcert** (alternative)
|
||||
This will create two new files (`key.pem` and `cert.pem `).
|
||||
|
||||
Alternatively, you can use mkcert ([more info](https://kifarunix.com/how-to-create-self-signed-ssl-certificate-with-mkcert-on-ubuntu-18-04/)):
|
||||
|
||||
```sh
|
||||
# include your local IP (e.g., 192.x.x.x) if needed
|
||||
# add your local IP (192.x.x.x) as well if you want to use it in your local network
|
||||
mkcert localhost 127.0.0.1 ::1
|
||||
```
|
||||
|
||||
**Run with certs**
|
||||
You can then pass the certificate files to uvicorn when you start LNbits:
|
||||
|
||||
```sh
|
||||
poetry run uvicorn lnbits.__main__:app --host 0.0.0.0 --port 5000 --ssl-keyfile ./key.pem --ssl-certfile ./cert.pem
|
||||
```
|
||||
|
||||
## LNbits on Umbrel behind Tor
|
||||
## LNbits running on Umbrel behind Tor
|
||||
|
||||
See this community [guide](https://community.getumbrel.com/t/guide-lnbits-without-tor/604).
|
||||
If you want to run LNbits on your Umbrel but want it to be reached through clearnet, _Uxellodunum_ made an extensive [guide](https://community.getumbrel.com/t/guide-lnbits-without-tor/604) on how to do it.
|
||||
|
||||
## FreeBSD notes
|
||||
## Docker installation
|
||||
|
||||
Issue with secp256k1 0.14.0 on FreeBSD (thanks @GitKalle):
|
||||
To install using docker you first need to build the docker image as:
|
||||
|
||||
1. Install `py311-secp256k1` with `pkg install py311-secp256k1`.
|
||||
2. Change version in `pyproject.toml` from `0.14.0` to `0.13.2`.
|
||||
3. Rewrite `poetry.lock` with `poetry lock`.
|
||||
4. Follow install instructions with Poetry.
|
||||
```
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
docker build -t lnbits/lnbits .
|
||||
```
|
||||
|
||||
---
|
||||
You can launch the docker in a different directory, but make sure to copy `.env.example` from lnbits there
|
||||
|
||||
## Powered by LNbits
|
||||
```
|
||||
cp <lnbits_repo>/.env.example .env
|
||||
```
|
||||
|
||||
LNbits empowers everyone with modular, open-source tools for building Bitcoin-based systems — fast, free, and extendable.
|
||||
and change the configuration in `.env` as required.
|
||||
|
||||
If you like this project [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visiting our [Shop](https://shop.lnbits.com)
|
||||
Then create the data directory
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/) [](https://my.lnbits.com/login) [](https://news.lnbits.com/) [](https://extensions.lnbits.com/)
|
||||
```
|
||||
mkdir data
|
||||
```
|
||||
|
||||
Then the image can be run as:
|
||||
|
||||
```
|
||||
docker run --detach --publish 5000:5000 --name lnbits -e "LNBITS_BACKEND_WALLET_CLASS='FakeWallet'" --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits
|
||||
```
|
||||
|
||||
Finally you can access your lnbits on your machine at port 5000.
|
||||
|
||||
### FreeBSD notes
|
||||
|
||||
Currently there is an issue with secp256k1 0.14.0 on FreeBSD. Thanks to @GitKalle
|
||||
|
||||
1. Install package `py311-secp256k1` with `pkg install py311-secp256k1`
|
||||
2. Change version in `pyproject.toml` from 0.14.0 to 0.13.2
|
||||
3. Rewrite `poetry.lock` file with command `poetry lock`
|
||||
4. Follow install instruction with Poetry
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits)
|
||||
[<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||
|
||||
# LNbits Super User (SU)
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [What is the Super User?](#what-is-the-super-user)
|
||||
- [When is the Super User created?](#when-is-the-super-user-created)
|
||||
- [Disabeling the Admin UI](#disabeling-the-admin-ui)
|
||||
- [Super User identity and storage](#super-user-identity-and-storage)
|
||||
- [Security model since v1](#security-model-since-v1)
|
||||
- [Admin vs Super User](#admin-vs-super-user)
|
||||
- [Operational guidance](#operational-guidance)
|
||||
- [Additional guides](#additional-guides)
|
||||
|
||||
<details>
|
||||
<summary><strong>TLDR</strong></summary>
|
||||
|
||||
- **No Admin UI → No Super User.** The Super User (SU) exists only when `LNBITS_ADMIN_UI=true`.
|
||||
- **Why SU exists:** SU can do a few high impact actions regular admins cannot, like **changing the funding source**, **restarting the server from the UI**, and **crediting or debiting accounts**.
|
||||
- **Login changes since v1:** Logging in by **user ID** for SU and admins is **disabled**. On first visit after enabling the Admin UI you will be prompted to set a **username and password** for the SU.
|
||||
- **Trust model:** Admins and the SU share about **99 percent of the same powers**, but the SU is the one trusted with funding source control and cannot be demoted by regular admins.
|
||||
|
||||
</details>
|
||||
|
||||
## What is the Super User?
|
||||
|
||||
The **Super User** is the owner-operator account of an LNbits instance. Think of it as your “break glass” operator with a few capabilities that are intentionally reserved for the person ultimately responsible for the server and the funding rails.
|
||||
|
||||
The SU is created alongside the [Admin UI](./admin_ui.md) and is meant to keep enviroment operations pleasant in the UI while keeping the most sensitive knobs in trusted hands.
|
||||
|
||||
**Key SU capabilities**
|
||||
|
||||
- **Change the funding source** for the instance
|
||||
- **Restart the LNbits server** from the web UI
|
||||
- **Credit or debit accounts** for operational corrections
|
||||
|
||||
> Note
|
||||
> These are separated from regular admin tasks on purpose. It helps maintain least privilege and reduces the chance of accidental or malicious changes.
|
||||
|
||||
## Admin vs Super User
|
||||
|
||||
| Capability | Admin | Super User |
|
||||
| ------------------------ | ---------- | ---------- |
|
||||
| View Admin UI | If enabled | If enabled |
|
||||
| Change funding source | — | ✓ |
|
||||
| Credit or debit accounts | — | ✓ |
|
||||
| Restart server from UI | — | ✓ |
|
||||
| Manage users and wallets | ✓ | ✓ |
|
||||
| Instance-level settings | ✓ | ✓ |
|
||||
| Manage notifications | ✓ | ✓ |
|
||||
| Exchange rates | ✓ | ✓ |
|
||||
| View all Payments | ✓ | ✓ |
|
||||
|
||||
**Why both roles?**
|
||||
In many teams the person running the server prefers to **delegate day-to-day admin work** while keeping funding and final authority safe. Admins can do almost everything; the SU retains the last few high risk powers.
|
||||
|
||||
## When is the Super User created?
|
||||
|
||||
- The SU is created **only** when you enable the Admin UI: `LNBITS_ADMIN_UI=true`.
|
||||
- If the Admin UI is **disabled**, there is **no SU** and all SU-only UI is hidden.
|
||||
|
||||
## Disabeling the Admin UI
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Read the [Admin UI guide](./admin_ui.md) before Disabeling. You are turning on a management surface; do it deliberately.
|
||||
|
||||
Set the environment variable in your deployment:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
LNBITS_ADMIN_UI=false
|
||||
```
|
||||
|
||||
## Super User identity and storage
|
||||
|
||||
LNbits stores the **Super User ID** at:
|
||||
|
||||
```
|
||||
/lnbits/data/.super_user
|
||||
```
|
||||
|
||||
- Back this up along with the rest of `/lnbits/data` as part of your secure backup routine.
|
||||
- **Changing who is the SU** can only be done by someone with **CLI access to the host OS** where LNbits runs. **Regular admins cannot revoke or replace the SU in the Admin UI.**
|
||||
|
||||
## Security model since v1
|
||||
|
||||
- **User-ID logins are disabled** for SU and admin roles.
|
||||
- **Credentialed login is required:** set a **username and password** for the SU at first run of the Admin UI.
|
||||
- **SU secrecy:** Regular users and admins **cannot discover the SU user ID** through normal UI flows.
|
||||
|
||||
## Operational guidance
|
||||
|
||||
These are practical tips for running a safe and friendly instance.
|
||||
|
||||
- It is normal to **delegate admin** duties to trusted people. Admins have about **99 percent** of SU powers for day-to-day work.
|
||||
- Keep the **SU** reserved for the person legally or operationally responsible for the **funding source**.
|
||||
- Use admin roles for regular day-to-day management and keep the SU for reserved SU tasks only.
|
||||
|
||||
## Additional guides
|
||||
|
||||
- **[Admin UI](./admin_ui.md)** — Manage server settings in the browser instead of editing `.env` or using the CLI for routine tasks.
|
||||
- **[User Roles](./User_Roles.md)** — Overview of roles and what they can do.
|
||||
- **[Funding sources](./funding-sources_table.md)** — Available options and how to enable and configure them.
|
||||
- **[Install LNBits](./installation.md)** — Choose your prefared way to install LNBits.
|
||||
|
||||
## Powered by LNbits
|
||||
|
||||
LNbits empowers everyone with modular, open source tools for building Bitcoin-based systems — fast, free, and extendable.
|
||||
|
||||
If you like this project, [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visit our [Shop](https://shop.lnbits.de)
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://my.lnbits.com/login)
|
||||
[](https://news.lnbits.com/)
|
||||
[](https://extensions.lnbits.com/)
|
||||
@@ -1,96 +0,0 @@
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits)
|
||||
[<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||
|
||||
# LNbits Roles: A Quick Overview
|
||||
|
||||
### Understand **who can do what** in seconds: `Super User`, `Admin`, and `Regular User`.
|
||||
|
||||
**Jump to:**
|
||||
[Roles at a Glance](#roles-at-a-glance) •
|
||||
[Super User](#super-user--master-control) •
|
||||
[Admin](#admin--day-to-day-manager) •
|
||||
[Regular User](#regular-user--everyday-use) •
|
||||
[Best Practices](#best-practices) •
|
||||
[Additional Guides](#additional-guides)
|
||||
|
||||
---
|
||||
|
||||
## Roles at a Glance
|
||||
|
||||
| Capability | **Super User** (owner) | **Admin** (manager) | **Regular User** (end user) |
|
||||
| -------------------------------- | :--------------------: | :-----------------: | :-------------------------: |
|
||||
| Change **funding source** | ✅ | ❌ | ❌ |
|
||||
| Credit/Debit any wallet | ✅ | ❌ | ❌ |
|
||||
| Manage Admins & Users | ✅ | ✅ | ❌ |
|
||||
| Enable/disable extensions | ✅ | ✅ | ❌ |
|
||||
| Use wallets & allowed extensions | ✅ | ✅ | ✅ |
|
||||
|
||||
> **Plain talk:** **Super User** = Owner • **Admin** = Trusted manager • **Regular User** = End user
|
||||
|
||||
## Role Snapshots
|
||||
|
||||
### Super User — Master Control
|
||||
|
||||
For initial setup and rare, high-impact changes.
|
||||
|
||||
- Configure **server-level settings** (e.g., funding source).
|
||||
- **Credit/Debit** any wallet.
|
||||
- Create and manage initial **Admin(s)**.
|
||||
|
||||
> **Sign-in:** username + password (v1+). The old query-string login is retired.
|
||||
|
||||
### Admin — Day-to-Day Manager
|
||||
|
||||
For running the service without touching the most sensitive knobs.
|
||||
|
||||
- Manage **Users**, **Admins**, and **Extensions** in the Admin UI.
|
||||
- Adjust security-related settings (e.g., `rate_limiter`, `ip_blocker`).
|
||||
- Handle operations settings (e.g., `service_fee`, `invoice_expiry`).
|
||||
- Build brand design in **Site Customization**.
|
||||
- Update user accounts.
|
||||
|
||||
**Typical tasks:** onboarding users, enabling extensions, tidying wallets, reviewing activity.
|
||||
|
||||
> **Sign-in:** username + password (v1+). The old query-string login is retired.
|
||||
|
||||
### Regular User — Everyday Use
|
||||
|
||||
For using LNbits, not administering it.
|
||||
|
||||
- Access **personal wallets** and **allowed extensions**.
|
||||
- No server/admin privileges.
|
||||
|
||||
**Typical tasks:** receive and send payments, use enabled extensions.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Minimize risk:** Reserve **Super User** for rare, sensitive actions (funding source, debit/credit). Use **Admin** for daily operations.
|
||||
- **Keep access tidy:** Review your Admin list occasionally; remove unused accounts.
|
||||
- **Change management:** Test risky changes (like funding) in a staging setup first.
|
||||
|
||||
## Additional Guides
|
||||
|
||||
- **[Admin UI](./admin_ui.md)** — Manage server settings via a clean UI (avoid editing `.env` by hand).
|
||||
- **[Super User](./super_user.md)** — Deep dive on responsibilities and safe usage patterns.
|
||||
- **[Funding sources](./funding-sources_table.md)** — What’s available and how to enable/configure each.
|
||||
|
||||
## Powered by LNbits
|
||||
|
||||
LNbits empowers everyone with modular, open-source tools for building Bitcoin-based systems—fast, free, and extendable.
|
||||
|
||||
If you like this project, [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visit our [Shop](https://shop.lnbits.de)
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://my.lnbits.com/login)
|
||||
[](https://news.lnbits.com/)
|
||||
[](https://extensions.lnbits.com/)
|
||||
+110
-300
@@ -4,367 +4,177 @@ title: Backend wallets
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits)
|
||||
[<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||
|
||||
# Backend wallets
|
||||
|
||||
**LNbits is modular**: You can switch the funding source (backend wallet) **without changing anything else** in your setup. Keep your extensions, apps, users, and config as-is — just point LNbits to a different backend via environment variables.
|
||||
LNbits can run on top of many Lightning Network funding sources with more being added regularly.
|
||||
|
||||
**What stays the same when you switch backends**
|
||||
A backend wallet can be configured using the following LNbits environment variables:
|
||||
|
||||
- Your LNbits setup and extensions
|
||||
- Your API keys and endpoints
|
||||
- Your server and deployment setup
|
||||
|
||||
A backend wallet is selected and configured entirely through LNbits environment variables. See the options and variables below, and compare them here: [Funding-Source-Table.md](funding-sources-table.md)
|
||||
|
||||
> [!NOTE]
|
||||
> **Terminology:** “Backend Wallet” and “Funding Source” mean the same thing — the wallet or service that funds your LNbits.
|
||||
|
||||
## Funding Sources
|
||||
|
||||
## Funding Sources
|
||||
|
||||
| | | |
|
||||
| ----------------------------------------------- | ------------------------------------- | ------------------------------------------------- |
|
||||
| [CLNRest (runes)](#clnrest-runes) | [LND (REST)](#lnd-rest) | [OpenNode](#opennode) |
|
||||
| [CoreLightning](#corelightning) | [LND (gRPC)](#lnd-grpc) | [Blink](#blink) |
|
||||
| [CoreLightning REST](#corelightning-rest) | [LNbits](#lnbits) | [Alby](#alby) |
|
||||
| [Spark (Core Lightning)](#spark-core-lightning) | [LNPay](#lnpay) | [Boltz](#boltz) |
|
||||
| [Cliche Wallet](#cliche-wallet) | [ZBD](#zbd) | [Phoenixd](#phoenixd) |
|
||||
| [Breez SDK](#breez-sdk) | [Breez Liquid SDK](#breez-liquid-sdk) | [Nostr Wallet Connect](#nostr-wallet-connect-nwc) |
|
||||
| [Strike](#strike) | [Eclair (ACINQ)](#eclair-acinq) | [LN.tips](#lntips) |
|
||||
| [Fake Wallet](#fake-wallet) | | |
|
||||
|
||||
---
|
||||
|
||||
<a id="clnrest-runes"></a>
|
||||
You can [compare the LNbits compatible Lightning Network funding sources here](wallets.md).
|
||||
|
||||
### CLNRest (using [runes](https://docs.corelightning.org/reference/lightning-createrune))
|
||||
|
||||
[Core Lightning REST API docs](https://docs.corelightning.org/docs/rest)
|
||||
[Core lightning Rest API docs](https://docs.corelightning.org/docs/rest)
|
||||
|
||||
Should also work with the [Rust version of CLNRest](https://github.com/daywalker90/clnrest-rs)
|
||||
|
||||
**Environment variables**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `CLNRestWallet`
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **CLNRestWallet**
|
||||
- `CLNREST_URL`: `https://127.0.0.1:3010`
|
||||
- `CLNREST_CA`: `/home/lightningd/.lightning/bitcoin/ca.pem` (or the content of the file)
|
||||
- `CLNREST_CERT`: `/home/lightningd/.lightning/bitcoin/server.pem` (or the content of the file)
|
||||
- `CLNREST_LAST_PAY_INDEX`: `lightning-cli listinvoices | jq -r '.invoices | map(.created_index) | max'`
|
||||
- `CLNREST_CA`: `/home/lightningd/.lightning/bitcoin/ca.pem` (or the content of the `ca.pem` file)
|
||||
- `CLNREST_CERT`: `/home/lightningd/.lightning/bitcoin/server.pem` (or the content of the `server.pem` file)
|
||||
- `CLNREST_READONLY_RUNE`: `lightning-cli createrune restrictions='[["method=listfunds", "method=listpays", "method=listinvoices", "method=getinfo", "method=summary", "method=waitanyinvoice"]]' | jq -r .rune`
|
||||
- `CLNREST_INVOICE_RUNE`: `lightning-cli createrune restrictions='[["method=invoice"], ["pnameamount_msat<1000001"], ["pnamelabel^LNbits"], ["rate=60"]]' | jq -r .rune`
|
||||
- `CLNREST_PAY_RUNE`: `lightning-cli createrune restrictions='[["method=pay"], ["pinvbolt11_amount<1001"], ["pnamelabel^LNbits"], ["rate=1"]]' | jq -r .rune`
|
||||
- `CLNREST_RENEPAY_RUNE`: `lightning-cli createrune restrictions='[["method=renepay"], ["pinvinvstring_amount<1001"], ["pnamelabel^LNbits"], ["rate=1"]]' | jq -r .rune`
|
||||
- `CLNREST_LAST_PAY_INDEX`: `lightning-cli listinvoices | jq -r '.invoices | map(.created_index) | max' `
|
||||
- `CLNREST_NODEID`: `lightning-cli getinfo | jq -r .id` (only required for v23.08)
|
||||
|
||||
**Create runes (copy/paste)**
|
||||
### CoreLightning
|
||||
|
||||
```bash
|
||||
# Read-only: funds, pays, invoices, info, summary, and invoice listener
|
||||
lightning-cli createrune \
|
||||
restrictions='[["method=listfunds","method=listpays","method=listinvoices","method=getinfo","method=summary","method=waitanyinvoice"]]' \
|
||||
| jq -r .rune
|
||||
```
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet**
|
||||
- `CORELIGHTNING_RPC`: /file/path/lightning-rpc
|
||||
|
||||
```bash
|
||||
# Invoice: max 1,000,001 msat, label must start with "LNbits", 60 req/min
|
||||
lightning-cli createrune \
|
||||
restrictions='[["method=invoice"], ["pnameamount_msat<1000001"], ["pnamelabel^LNbits"], ["rate=60"]]' \
|
||||
| jq -r .rune
|
||||
```
|
||||
### CoreLightning REST
|
||||
|
||||
```bash
|
||||
# Pay: bolt11 amount < 1001 (msat), label must start with "LNbits", 1 req/min
|
||||
lightning-cli createrune \
|
||||
restrictions='[["method=pay"], ["pinvbolt11_amount<1001"], ["pnamelabel^LNbits"], ["rate=1"]]' \
|
||||
| jq -r .rune
|
||||
```
|
||||
This is the old REST interface that uses [Ride The Lightning/c-lightning-REST](https://github.com/Ride-The-Lightning/c-lightning-REST)
|
||||
|
||||
```bash
|
||||
# Renepay: invstring amount < 1001 (msat), label must start with "LNbits", 1 req/min
|
||||
lightning-cli createrune \
|
||||
restrictions='[["method=renepay"], ["pinvinvstring_amount<1001"], ["pnamelabel^LNbits"], ["rate=1"]]' \
|
||||
| jq -r .rune
|
||||
```
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningRestWallet**
|
||||
- `CORELIGHTNING_REST_URL`: http://127.0.0.1:8185/
|
||||
- `CORELIGHTNING_REST_MACAROON`: /file/path/admin.macaroon or Base64/Hex
|
||||
- `CORELIGHTNING_REST_CERT`: /home/lightning/clnrest/tls.cert
|
||||
|
||||
Set the resulting values into:
|
||||
### Spark (Core Lightning)
|
||||
|
||||
- `CLNREST_READONLY_RUNE`
|
||||
- `CLNREST_INVOICE_RUNE`
|
||||
- `CLNREST_PAY_RUNE`
|
||||
- `CLNREST_RENEPAY_RUNE`
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **SparkWallet**
|
||||
- `SPARK_URL`: http://10.147.17.230:9737/rpc
|
||||
- `SPARK_TOKEN`: secret_access_key
|
||||
|
||||
## CoreLightning
|
||||
### LND (REST)
|
||||
|
||||
**Required env vars**
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LndRestWallet**
|
||||
- `LND_REST_ENDPOINT`: http://10.147.17.230:8080/
|
||||
- `LND_REST_CERT`: /file/path/tls.cert
|
||||
- `LND_REST_MACAROON`: /file/path/admin.macaroon or Base64/Hex
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `CoreLightningWallet`
|
||||
- `CORELIGHTNING_RPC`: `/file/path/lightning-rpc`
|
||||
or
|
||||
|
||||
## CoreLightning REST
|
||||
- `LND_REST_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
||||
|
||||
Old REST interface using [RTL c-lightning-REST](https://github.com/Ride-The-Lightning/c-lightning-REST)
|
||||
### LND (gRPC)
|
||||
|
||||
**Required env vars**
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet**
|
||||
- `LND_GRPC_ENDPOINT`: ip_address
|
||||
- `LND_GRPC_PORT`: port
|
||||
- `LND_GRPC_CERT`: /file/path/tls.cert
|
||||
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon or Base64/Hex
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `CoreLightningRestWallet`
|
||||
- `CORELIGHTNING_REST_URL`: `http://127.0.0.1:8185/`
|
||||
- `CORELIGHTNING_REST_MACAROON`: `/file/path/admin.macaroon` or Base64/Hex
|
||||
- `CORELIGHTNING_REST_CERT`: `/home/lightning/clnrest/tls.cert`
|
||||
You can also use an AES-encrypted macaroon (more info) instead by using
|
||||
|
||||
## Spark (Core Lightning)
|
||||
- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
||||
|
||||
**Required env vars**
|
||||
To encrypt your macaroon, run `uv run lnbits-cli encrypt macaroon`.
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `SparkWallet`
|
||||
- `SPARK_URL`: `http://10.147.17.230:9737/rpc`
|
||||
- `SPARK_TOKEN`: `secret_access_key`
|
||||
### LNbits
|
||||
|
||||
## LND (REST)
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LNbitsWallet**
|
||||
- `LNBITS_ENDPOINT`: e.g. https://lnbits.com
|
||||
- `LNBITS_KEY`: lnbitsAdminKey
|
||||
|
||||
**Required env vars**
|
||||
### LNPay
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `LndRestWallet`
|
||||
- `LND_REST_ENDPOINT`: `http://10.147.17.230:8080/`
|
||||
- `LND_REST_CERT`: `/file/path/tls.cert`
|
||||
- `LND_REST_MACAROON`: `/file/path/admin.macaroon` or Base64/Hex
|
||||
For the invoice listener to work you have a publicly accessible URL in your LNbits and must set up [LNPay webhooks](https://dashboard.lnpay.co/webhook/) pointing to `<your LNbits host>/wallet/webhook` with the "Wallet Receive" event and no secret. For example, `https://mylnbits/wallet/webhook` will be the Endpoint Url that gets notified about the payment.
|
||||
|
||||
or:
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LNPayWallet**
|
||||
- `LNPAY_API_ENDPOINT`: https://api.lnpay.co/v1/
|
||||
- `LNPAY_API_KEY`: sak_apiKey
|
||||
- `LNPAY_WALLET_KEY`: waka_apiKey
|
||||
|
||||
- `LND_REST_MACAROON_ENCRYPTED`: `eNcRyPtEdMaCaRoOn`
|
||||
### OpenNode
|
||||
|
||||
## LND (gRPC)
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary.
|
||||
|
||||
**Required env vars**
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **OpenNodeWallet**
|
||||
- `OPENNODE_API_ENDPOINT`: https://api.opennode.com/
|
||||
- `OPENNODE_KEY`: opennodeAdminApiKey
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `LndWallet`
|
||||
- `LND_GRPC_ENDPOINT`: `ip_address`
|
||||
- `LND_GRPC_PORT`: `port`
|
||||
- `LND_GRPC_CERT`: `/file/path/tls.cert`
|
||||
- `LND_GRPC_MACAROON`: `/file/path/admin.macaroon` or Base64/Hex
|
||||
### Blink
|
||||
|
||||
You can also use an AES-encrypted macaroon instead:
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate a Blink API key after logging in or creating a new Blink account at: https://dashboard.blink.sv. For more info visit: https://dev.blink.sv/api/auth#create-an-api-key```
|
||||
|
||||
- `LND_GRPC_MACAROON_ENCRYPTED`: `eNcRyPtEdMaCaRoOn`
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **BlinkWallet**
|
||||
- `BLINK_API_ENDPOINT`: https://api.blink.sv/graphql
|
||||
- `BLINK_WS_ENDPOINT`: wss://ws.blink.sv/graphql
|
||||
- `BLINK_TOKEN`: BlinkToken
|
||||
|
||||
To encrypt your macaroon:
|
||||
### Alby
|
||||
|
||||
```bash
|
||||
uv run lnbits-cli encrypt macaroon
|
||||
```
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate an alby access token here: https://getalby.com/developer/access_tokens/new
|
||||
|
||||
## LNbits
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **AlbyWallet**
|
||||
- `ALBY_API_ENDPOINT`: https://api.getalby.com/
|
||||
- `ALBY_ACCESS_TOKEN`: AlbyAccessToken
|
||||
|
||||
**Required env vars**
|
||||
### Boltz
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `LNbitsWallet`
|
||||
- `LNBITS_ENDPOINT`: for example `https://lnbits.com`
|
||||
- `LNBITS_KEY`: `lnbitsAdminKey`
|
||||
This funding source connects to a running [boltz-client](https://docs.boltz.exchange/v/boltz-client) and handles all lightning payments through submarine swaps on the liquid network.
|
||||
You can configure the daemon to run in standalone mode by `standalone = True` in the config file or using the cli flag (`boltzd --standalone`).
|
||||
Once running, you can create a liquid wallet using `boltzcli wallet create lnbits lbtc`.
|
||||
|
||||
## LNPay
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **BoltzWallet**
|
||||
- `BOLTZ_CLIENT_ENDPOINT`: 127.0.0.1:9002
|
||||
- `BOLTZ_CLIENT_MACAROON`: /home/bob/.boltz/macaroons/admin.macaroon or Base64/Hex
|
||||
- `BOLTZ_CLIENT_CERT`: /home/bob/.boltz/tls.cert or Base64/Hex
|
||||
- `BOLTZ_CLIENT_WALLET`: lnbits
|
||||
|
||||
For the invoice listener to work you must have a publicly accessible URL in your LNbits and set up [LNPay webhooks](https://dashboard.lnpay.co/webhook/) pointing to `<your LNbits host>/wallet/webhook` with the event **Wallet Receive** and no secret. Example: [https://mylnbits/wallet/webhook](`https://mylnbits/wallet/webhook).
|
||||
### ZBD
|
||||
|
||||
**Required env vars**
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate an ZBD API Key here: https://zbd.dev/docs/dashboard/projects/api
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `LNPayWallet`
|
||||
- `LNPAY_API_ENDPOINT`: `https://api.lnpay.co/v1/`
|
||||
- `LNPAY_API_KEY`: `sak_apiKey`
|
||||
- `LNPAY_WALLET_KEY`: `waka_apiKey`
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **ZBDWallet**
|
||||
- `ZBD_API_ENDPOINT`: https://api.zebedee.io/v0/
|
||||
- `ZBD_API_KEY`: ZBDApiKey
|
||||
|
||||
## OpenNode
|
||||
### Phoenixd
|
||||
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook configuration required.
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. You can get a phoenixd API key from the install
|
||||
~/.phoenix/phoenix.conf, also see the documentation for phoenixd.
|
||||
|
||||
**Required env vars**
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **PhoenixdWallet**
|
||||
- `PHOENIXD_API_ENDPOINT`: http://localhost:9740/
|
||||
- `PHOENIXD_API_PASSWORD`: PhoenixdApiPassword
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `OpenNodeWallet`
|
||||
- `OPENNODE_API_ENDPOINT`: `https://api.opennode.com/`
|
||||
- `OPENNODE_KEY`: `opennodeAdminApiKey`
|
||||
### Breez SDK
|
||||
|
||||
## Blink
|
||||
A Greenlight invite code or Greenlight partner certificate/key can be used to register a new node with Greenlight. If the Greenlight node already exists, neither are required.
|
||||
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook configuration required.
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **BreezSdkWallet**
|
||||
- `BREEZ_API_KEY`: ...
|
||||
- `BREEZ_GREENLIGHT_SEED`: ...
|
||||
- `BREEZ_GREENLIGHT_INVITE_CODE`: ...
|
||||
- `BREEZ_GREENLIGHT_DEVICE_KEY`: /path/to/breezsdk/device.pem or Base64/Hex
|
||||
- `BREEZ_GREENLIGHT_DEVICE_CERT`: /path/to/breezsdk/device.crt or Base64/Hex
|
||||
|
||||
You can generate a Blink API key at [https://dashboard.blink.sv](https://dashboard.blink.sv). More info: [https://dev.blink.sv/api/auth#create-an-api-key](https://dev.blink.sv/api/auth#create-an-api-key)
|
||||
### Breez Liquid SDK
|
||||
|
||||
**Required env vars**
|
||||
This funding source leverages the [Breez SDK - Liquid](https://sdk-doc-liquid.breez.technology/) to manage all Lightning payments via submarine swaps on the Liquid network. To get started, simply provide a mnemonic seed phrase. The easiest way to generate one is by using a liquid wallet, such as [Blockstream Green](https://blockstream.com/green/). Once generated, you can copy the seed to your environment variable or enter it in the admin UI.
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `BlinkWallet`
|
||||
- `BLINK_API_ENDPOINT`: `https://api.blink.sv/graphql`
|
||||
- `BLINK_WS_ENDPOINT`: `wss://ws.blink.sv/graphql`
|
||||
- `BLINK_TOKEN`: `BlinkToken`
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **BreezLiquidSdkWallet**
|
||||
- `BREEZ_LIQUID_SEED`: ...
|
||||
|
||||
## Alby
|
||||
Each submarine swap incurs service and on-chain fees. To account for these, you may need to increase the reserve fee in the admin UI by navigating to **Settings -> Funding**, or by setting the following environment variables:
|
||||
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook configuration required.
|
||||
- `LNBITS_RESERVE_FEE_MIN`: ...
|
||||
- `LNBITS_RESERVE_FEE_PERCENT`: ...
|
||||
|
||||
Generate an Alby access token here: [https://getalby.com/developer/access_tokens/new](https://getalby.com/developer/access_tokens/new)
|
||||
### Cliche Wallet
|
||||
|
||||
**Required env vars**
|
||||
- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `AlbyWallet`
|
||||
- `ALBY_API_ENDPOINT`: `https://api.getalby.com/`
|
||||
- `ALBY_ACCESS_TOKEN`: `AlbyAccessToken`
|
||||
### Nostr Wallet Connect (NWC)
|
||||
|
||||
## Boltz
|
||||
To use NWC as funding source in LNbits you'll need a pairing URL (also known as pairing secret) from a NWC service provider. You can find a list of providers [here](https://github.com/getAlby/awesome-nwc?tab=readme-ov-file#nwc-wallets).
|
||||
|
||||
This connects to a running [boltz-client](https://docs.boltz.exchange/v/boltz-client) and handles Lightning payments through submarine swaps on the Liquid network.
|
||||
You can configure Nostr Wallet Connect in the admin ui or using the following environment variables:
|
||||
|
||||
You can run the daemon in standalone mode via `standalone = True` in the config or `boltzd --standalone`. Create a Liquid wallet with:
|
||||
|
||||
```bash
|
||||
boltzcli wallet create lnbits lbtc
|
||||
```
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `BoltzWallet`
|
||||
- `BOLTZ_CLIENT_ENDPOINT`: `127.0.0.1:9002`
|
||||
- `BOLTZ_CLIENT_MACAROON`: `/home/bob/.boltz/macaroons/admin.macaroon` or Base64/Hex
|
||||
- `BOLTZ_CLIENT_CERT`: `/home/bob/.boltz/tls.cert` or Base64/Hex
|
||||
- `BOLTZ_CLIENT_WALLET`: `lnbits`
|
||||
|
||||
## ZBD
|
||||
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook configuration required.
|
||||
|
||||
Generate a ZBD API key here: [https://zbd.dev/docs/dashboard/projects/api](https://zbd.dev/docs/dashboard/projects/api)
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `ZBDWallet`
|
||||
- `ZBD_API_ENDPOINT`: `https://api.zebedee.io/v0/`
|
||||
- `ZBD_API_KEY`: `ZBDApiKey`
|
||||
|
||||
## Phoenixd
|
||||
|
||||
For the invoice to work you must have a publicly accessible URL in your LNbits.
|
||||
|
||||
You can get a phoenixd API key from `~/.phoenix/phoenix.conf`. See the phoenixd documentation for details.
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `PhoenixdWallet`
|
||||
- `PHOENIXD_API_ENDPOINT`: `http://localhost:9740/`
|
||||
- `PHOENIXD_API_PASSWORD`: `PhoenixdApiPassword`
|
||||
|
||||
## Eclair (ACINQ)
|
||||
|
||||
<a id="eclair-acinq"></a>
|
||||
|
||||
Connect to an existing Eclair node so your backend handles invoices and payments.
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `EclairWallet`
|
||||
- `ECLAIR_URL`: `http://127.0.0.1:8283`
|
||||
- `ECLAIR_PASSWORD`: `eclairpw`
|
||||
|
||||
## Fake Wallet
|
||||
|
||||
<a id="fake-wallet"></a>
|
||||
|
||||
A testing-only backend that mints accounting units inside LNbits accounting (no real sats).
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `FakeWallet`
|
||||
- `FAKE_SECRET`: `ToTheMoon1`
|
||||
- `FAKE_UNIT`: `sats`
|
||||
|
||||
## LN.tips
|
||||
|
||||
<a id="lntips"></a>
|
||||
|
||||
As the initinal LN.tips bot is no longer active the code still exists and is widly used. Connect one of custodial services as your backend to create and pay Lightning invoices through their API or selfhost this service and run it as funding source.
|
||||
Resources: https://github.com/massmux/SatsMobiBot
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `LNTipsWallet`
|
||||
- `LNTIPS_API_ENDPOINT`: `https://ln.tips`
|
||||
- `LNTIPS_API_KEY`: `LNTIPS_ADMIN_KEY`
|
||||
|
||||
## Breez SDK
|
||||
|
||||
A Greenlight invite code or Greenlight partner certificate/key can register a new node with Greenlight. If the Greenlight node already exists, neither is required.
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `BreezSdkWallet`
|
||||
- `BREEZ_API_KEY`: `...`
|
||||
- `BREEZ_GREENLIGHT_SEED`: `...`
|
||||
- `BREEZ_GREENLIGHT_INVITE_CODE`: `...`
|
||||
- `BREEZ_GREENLIGHT_DEVICE_KEY`: `/path/to/breezsdk/device.pem` or Base64/Hex
|
||||
- `BREEZ_GREENLIGHT_DEVICE_CERT`: `/path/to/breezsdk/device.crt` or Base64/Hex
|
||||
|
||||
## Breez Liquid SDK
|
||||
|
||||
This uses the [Breez SDK - Liquid](https://sdk-doc-liquid.breez.technology/) to manage Lightning payments via submarine swaps on the Liquid network. Provide a mnemonic seed phrase (for example, generate one with a Liquid wallet like [Blockstream Green](https://blockstream.com/green/)) and set it in the environment or admin UI.
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `BreezLiquidSdkWallet`
|
||||
- `BREEZ_LIQUID_SEED`: `...`
|
||||
|
||||
Fees apply for each submarine swap. You may need to increase the reserve fee under **Settings → Funding** or via:
|
||||
|
||||
- `LNBITS_RESERVE_FEE_MIN`: `...`
|
||||
- `LNBITS_RESERVE_FEE_PERCENT`: `...`
|
||||
|
||||
## Cliche Wallet
|
||||
|
||||
**Required env vars**
|
||||
|
||||
- `CLICHE_ENDPOINT`: `ws://127.0.0.1:12000`
|
||||
|
||||
## Nostr Wallet Connect (NWC)
|
||||
|
||||
To use NWC as a funding source you need a pairing URL (pairing secret) from an NWC provider. See providers here:
|
||||
[https://github.com/getAlby/awesome-nwc?tab=readme-ov-file#nwc-wallets](https://github.com/getAlby/awesome-nwc?tab=readme-ov-file#nwc-wallets)
|
||||
|
||||
Configure in the admin UI or via env vars:
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: `NWCWallet`
|
||||
- `NWC_PAIRING_URL`: `nostr+walletconnect://...your...pairing...secret...`
|
||||
|
||||
<a id="strike"></a>
|
||||
|
||||
## Strike (alpha)
|
||||
|
||||
Custodial provider integrated via **Strike OAuth Connect** (OAuth 2.0 / OIDC). Authenticate a Strike user in your app, then call Strike APIs on the user’s behalf once scopes are granted. Requires a Strike business account, registered OAuth client, minimal scopes, and login/logout redirect URLs.
|
||||
|
||||
Get more info here [https://docs.strike.me/strike-oauth-connect/](https://docs.strike.me/strike-oauth-connect/)
|
||||
|
||||
**Integration endpoints**
|
||||
|
||||
- `STRIKE_API_ENDPOINT`: `https://api.strike.me/v1`
|
||||
- `STRIKE_API_KEY`: `YOUR_STRIKE_API_KEY`
|
||||
|
||||
---
|
||||
|
||||
## Additional Guides
|
||||
|
||||
- **[Admin UI](./admin_ui.md)** — Manage server settings via a clean UI (avoid editing `.env` by hand).
|
||||
- **[User Roles](./User_Roles.md)** — Quick Overview of existing Roles in LNBits.
|
||||
- **[Funding sources](./funding-sources_table.md)** — What’s available and how to enable/configure each.
|
||||
|
||||
## Powered by LNbits
|
||||
|
||||
LNbits empowers everyone with modular, open-source tools for building Bitcoin-based systems — fast, free, and extendable.
|
||||
|
||||
If you like this project, [send some tip love](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg) or visit our [Shop](https://shop.lnbits.de)
|
||||
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://shop.lnbits.com/)
|
||||
[](https://my.lnbits.com/login)
|
||||
[](https://news.lnbits.com/)
|
||||
[](https://extensions.lnbits.com/)
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **NWCWallet**
|
||||
- `NWC_PAIRING_URL`: **nostr+walletconnect://...your...pairing...secret...**
|
||||
|
||||
@@ -145,7 +145,6 @@
|
||||
nixpkgs.overlays = [ self.overlays.${system}.default ];
|
||||
};
|
||||
|
||||
checks =
|
||||
import ./nix/tests { inherit pkgs; flake = self; };
|
||||
checks = { };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,75 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
#!/bin/bash
|
||||
|
||||
# --- Config you might tweak ---
|
||||
REPO_URL="https://github.com/lnbits/lnbits.git"
|
||||
BRANCH="main"
|
||||
APP_DIR="${PWD}/lnbits"
|
||||
HOST="${HOST:-0.0.0.0}"
|
||||
PORT="${PORT:-5000}"
|
||||
ADMIN_UI="${LNBITS_ADMIN_UI:-true}"
|
||||
# -------------------------------
|
||||
# Check install has not already run
|
||||
if [ ! -d lnbits/data ]; then
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
# Update package list and install prerequisites non-interactively
|
||||
sudo apt update -y
|
||||
sudo apt install -y software-properties-common
|
||||
|
||||
# Ensure basic tooling
|
||||
if ! command -v curl >/dev/null 2>&1 || ! command -v git >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y curl git
|
||||
fi
|
||||
# Add the deadsnakes PPA repository non-interactively
|
||||
sudo add-apt-repository -y ppa:deadsnakes/ppa
|
||||
|
||||
# System build deps and secp headers
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
pkg-config \
|
||||
build-essential \
|
||||
libsecp256k1-dev \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
m4
|
||||
fi
|
||||
# Install Python 3.10 and distutils non-interactively
|
||||
sudo apt install -y python3.10 python3.10-distutils
|
||||
|
||||
# Install uv (if missing)
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
# Install UV
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
# Ensure PATH for current session
|
||||
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
export PATH="/home/$USER/.local/bin:$PATH"
|
||||
|
||||
if [ ! -d lnbits/wallets ]; then
|
||||
# Clone the LNbits repository
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to clone the repository ... FAIL"
|
||||
exit 1
|
||||
fi
|
||||
# Ensure we are in the lnbits directory
|
||||
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
|
||||
fi
|
||||
|
||||
git checkout main
|
||||
# Make data folder
|
||||
mkdir data
|
||||
|
||||
# Copy the .env.example to .env
|
||||
cp .env.example .env
|
||||
|
||||
elif [ ! -d lnbits/wallets ]; then
|
||||
# cd into lnbits
|
||||
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
|
||||
fi
|
||||
|
||||
# Clone or reuse repo
|
||||
if [[ ! -d "$APP_DIR/.git" ]]; then
|
||||
git clone "$REPO_URL" "$APP_DIR"
|
||||
fi
|
||||
# Install the dependencies using UV
|
||||
uv sync --all-extras
|
||||
|
||||
cd "$APP_DIR"
|
||||
git fetch --all --prune
|
||||
git checkout "$BRANCH"
|
||||
git pull --ff-only || true
|
||||
|
||||
# First-run setup
|
||||
mkdir -p data
|
||||
[[ -f .env ]] || cp .env.example .env || true
|
||||
# Set environment variables for LNbits
|
||||
export LNBITS_ADMIN_UI=true
|
||||
export HOST=0.0.0.0
|
||||
|
||||
# Prefer system libsecp256k1 (avoid autotools path)
|
||||
export SECP_BUNDLED=0
|
||||
|
||||
# Sync dependencies with Python 3.12
|
||||
uv sync --python 3.12 --all-extras --no-dev
|
||||
|
||||
# Environment
|
||||
export LNBITS_ADMIN_UI="$ADMIN_UI"
|
||||
export HOST="$HOST"
|
||||
export PORT="$PORT"
|
||||
|
||||
# Open firewall (optional)
|
||||
if command -v ufw >/dev/null 2>&1; then
|
||||
sudo ufw allow "$PORT"/tcp || true
|
||||
fi
|
||||
|
||||
# Run LNbits with Python 3.12 via uv
|
||||
exec uv run --python 3.12 lnbits
|
||||
# Run LNbits
|
||||
uv run lnbits
|
||||
|
||||
@@ -104,7 +104,7 @@ async def delete_unused_wallets(
|
||||
|
||||
|
||||
async def get_wallet(
|
||||
wallet_id: str, deleted: bool | None = False, conn: Connection | None = None
|
||||
wallet_id: str, deleted: bool | None = None, conn: Connection | None = None
|
||||
) -> Wallet | None:
|
||||
query = """
|
||||
SELECT *, COALESCE((
|
||||
@@ -122,7 +122,7 @@ async def get_wallet(
|
||||
|
||||
|
||||
async def get_wallets(
|
||||
user_id: str, deleted: bool | None = False, conn: Connection | None = None
|
||||
user_id: str, deleted: bool | None = None, conn: Connection | None = None
|
||||
) -> list[Wallet]:
|
||||
query = """
|
||||
SELECT *, COALESCE((
|
||||
@@ -163,11 +163,11 @@ async def get_wallets_paginated(
|
||||
|
||||
|
||||
async def get_wallets_ids(
|
||||
user_id: str, deleted: bool | None = False, conn: Connection | None = 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:
|
||||
query += " AND deleted = :deleted"
|
||||
query += "AND deleted = :deleted"
|
||||
result: list[dict] = await (conn or db).fetchall(
|
||||
query,
|
||||
{"user": user_id, "deleted": deleted},
|
||||
|
||||
@@ -39,7 +39,6 @@ class ExplicitRelease(BaseModel):
|
||||
info_notification: str | None
|
||||
critical_notification: str | None
|
||||
details_link: str | None
|
||||
paid_features: str | None
|
||||
pay_link: str | None
|
||||
|
||||
def is_version_compatible(self):
|
||||
@@ -188,7 +187,6 @@ class ExtensionRelease(BaseModel):
|
||||
icon: str | None = None
|
||||
details_link: str | None = None
|
||||
|
||||
paid_features: str | None = None
|
||||
pay_link: str | None = None
|
||||
cost_sats: int | None = None
|
||||
paid_sats: int | None = 0
|
||||
@@ -258,7 +256,6 @@ class ExtensionRelease(BaseModel):
|
||||
html_url=e.html_url,
|
||||
details_link=e.details_link,
|
||||
pay_link=e.pay_link,
|
||||
paid_features=e.paid_features,
|
||||
repo=e.repo,
|
||||
icon=e.icon,
|
||||
)
|
||||
@@ -311,9 +308,6 @@ class ExtensionMeta(BaseModel):
|
||||
dependencies: list[str] = []
|
||||
archive: str | None = None
|
||||
featured: bool = False
|
||||
paid_features: str | None = None
|
||||
has_paid_release: bool = False
|
||||
has_free_release: bool = False
|
||||
|
||||
|
||||
class InstallableExtension(BaseModel):
|
||||
@@ -457,23 +451,9 @@ class InstallableExtension(BaseModel):
|
||||
|
||||
shutil.rmtree(self.ext_upgrade_dir, True)
|
||||
|
||||
def check_release_updates(self, release: ExtensionRelease | None):
|
||||
self._check_latest_version(release)
|
||||
self._check_payment_link(release)
|
||||
|
||||
def find_existing_payment(self, pay_link: str | None) -> ReleasePaymentInfo | None:
|
||||
if not pay_link or not self.meta or not self.meta.payments:
|
||||
return None
|
||||
return next(
|
||||
(p for p in self.meta.payments if p.pay_link == pay_link),
|
||||
None,
|
||||
)
|
||||
|
||||
def _check_latest_version(self, release: ExtensionRelease | None):
|
||||
def check_latest_version(self, release: ExtensionRelease | None):
|
||||
if not release:
|
||||
return
|
||||
if not release.is_version_compatible:
|
||||
return
|
||||
if not self.meta or not self.meta.latest_release:
|
||||
meta = self.meta or ExtensionMeta()
|
||||
meta.latest_release = release
|
||||
@@ -484,19 +464,13 @@ class InstallableExtension(BaseModel):
|
||||
):
|
||||
self.meta.latest_release = release
|
||||
|
||||
def _check_payment_link(self, release: ExtensionRelease | None):
|
||||
if not release:
|
||||
return
|
||||
if not release.is_version_compatible:
|
||||
return
|
||||
if not self.meta:
|
||||
self.meta = ExtensionMeta()
|
||||
if release.pay_link:
|
||||
self.meta.has_paid_release = True
|
||||
else:
|
||||
self.meta.has_free_release = True
|
||||
if release.paid_features:
|
||||
self.meta.paid_features = release.paid_features
|
||||
def find_existing_payment(self, pay_link: str | None) -> ReleasePaymentInfo | None:
|
||||
if not pay_link or not self.meta or not self.meta.payments:
|
||||
return None
|
||||
return next(
|
||||
(p for p in self.meta.payments if p.pay_link == pay_link),
|
||||
None,
|
||||
)
|
||||
|
||||
def _restore_payment_info(self):
|
||||
if (
|
||||
@@ -622,7 +596,7 @@ class InstallableExtension(BaseModel):
|
||||
(ee for ee in extension_list if ee.id == r.id), None
|
||||
)
|
||||
if existing_ext and ext.meta:
|
||||
existing_ext.check_release_updates(ext.meta.latest_release)
|
||||
existing_ext.check_latest_version(ext.meta.latest_release)
|
||||
continue
|
||||
|
||||
meta = ext.meta or ExtensionMeta()
|
||||
@@ -636,10 +610,10 @@ class InstallableExtension(BaseModel):
|
||||
(ee for ee in extension_list if ee.id == e.id), None
|
||||
)
|
||||
if existing_ext:
|
||||
existing_ext.check_release_updates(release)
|
||||
existing_ext.check_latest_version(release)
|
||||
continue
|
||||
ext = InstallableExtension.from_explicit_release(e)
|
||||
ext.check_release_updates(release)
|
||||
ext.check_latest_version(release)
|
||||
meta = ext.meta or ExtensionMeta()
|
||||
meta.featured = ext.id in manifest.featured
|
||||
ext.meta = meta
|
||||
|
||||
@@ -68,7 +68,7 @@ class Payment(BaseModel):
|
||||
amount: int
|
||||
fee: int
|
||||
bolt11: str
|
||||
payment_request: str | None = Field(default=None, no_database=True)
|
||||
# payment_request: str | None
|
||||
fiat_provider: str | None = None
|
||||
status: str = PaymentState.PENDING
|
||||
memo: str | None = None
|
||||
@@ -83,13 +83,6 @@ class Payment(BaseModel):
|
||||
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
extra: dict = {}
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
if "fiat_payment_request" in self.extra:
|
||||
self.payment_request = self.extra["fiat_payment_request"]
|
||||
else:
|
||||
self.payment_request = self.bolt11
|
||||
|
||||
@property
|
||||
def pending(self) -> bool:
|
||||
return self.status == PaymentState.PENDING.value
|
||||
|
||||
@@ -5,7 +5,7 @@ import time
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.crud import get_wallet
|
||||
from lnbits.core.crud.payments import create_payment
|
||||
from lnbits.core.crud.payments import create_payment, get_standalone_payment
|
||||
from lnbits.core.models import CreatePayment, Payment, PaymentState
|
||||
from lnbits.core.models.misc import SimpleStatus
|
||||
from lnbits.db import Connection
|
||||
@@ -27,73 +27,6 @@ async def handle_fiat_payment_confirmation(
|
||||
logger.warning(e)
|
||||
|
||||
|
||||
def check_stripe_signature(
|
||||
payload: bytes,
|
||||
sig_header: str | None,
|
||||
secret: str | None,
|
||||
tolerance_seconds=300,
|
||||
):
|
||||
if not sig_header:
|
||||
logger.warning("Stripe-Signature header is missing.")
|
||||
raise ValueError("Stripe-Signature header is missing.")
|
||||
|
||||
if not secret:
|
||||
logger.warning("Stripe webhook signing secret is not set.")
|
||||
raise ValueError("Stripe webhook cannot be verified.")
|
||||
|
||||
# Split the Stripe-Signature header
|
||||
items = dict(i.split("=") for i in sig_header.split(","))
|
||||
timestamp = int(items["t"])
|
||||
signature = items["v1"]
|
||||
|
||||
# Check timestamp tolerance
|
||||
if abs(time.time() - timestamp) > tolerance_seconds:
|
||||
logger.warning("Timestamp outside tolerance.")
|
||||
logger.debug(
|
||||
f"Current time: {time.time()}, "
|
||||
f"Timestamp: {timestamp}, "
|
||||
f"Tolerance: {tolerance_seconds} seconds"
|
||||
)
|
||||
|
||||
raise ValueError("Timestamp outside tolerance." f"Timestamp: {timestamp}")
|
||||
|
||||
signed_payload = f"{timestamp}.{payload.decode()}"
|
||||
|
||||
# Compute HMAC SHA256 using the webhook secret
|
||||
computed_signature = hmac.new(
|
||||
key=secret.encode(), msg=signed_payload.encode(), digestmod=hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
# Compare signatures using constant time comparison
|
||||
if hmac.compare_digest(computed_signature, signature) is not True:
|
||||
logger.warning("Stripe signature verification failed.")
|
||||
raise ValueError("Stripe signature verification failed.")
|
||||
|
||||
|
||||
async def test_connection(provider: str) -> SimpleStatus:
|
||||
"""
|
||||
Test the connection to Stripe by checking if the API key is valid.
|
||||
This function should be called when setting up or testing the Stripe integration.
|
||||
"""
|
||||
fiat_provider = await get_fiat_provider(provider)
|
||||
if not fiat_provider:
|
||||
return SimpleStatus(
|
||||
success=False,
|
||||
message=f"Fiat provider '{provider}' not found.",
|
||||
)
|
||||
status = await fiat_provider.status()
|
||||
if status.error_message:
|
||||
return SimpleStatus(
|
||||
success=False,
|
||||
message=f"Cconnection test failed: {status.error_message}",
|
||||
)
|
||||
|
||||
return SimpleStatus(
|
||||
success=True,
|
||||
message="Connection test successful." f" Balance: {status.balance}.",
|
||||
)
|
||||
|
||||
|
||||
async def _credit_fiat_service_fee_wallet(
|
||||
payment: Payment, conn: Connection | None = None
|
||||
):
|
||||
@@ -171,3 +104,90 @@ async def _debit_fiat_service_faucet_wallet(
|
||||
status=PaymentState.SUCCESS,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
|
||||
async def handle_stripe_event(event: dict):
|
||||
event_id = event.get("id")
|
||||
event_object = event.get("data", {}).get("object", {})
|
||||
object_type = event_object.get("object")
|
||||
payment_hash = event_object.get("metadata", {}).get("payment_hash")
|
||||
logger.debug(
|
||||
f"Handling Stripe event: '{event_id}'. Type: '{object_type}'."
|
||||
f" Payment hash: '{payment_hash}'."
|
||||
)
|
||||
if not payment_hash:
|
||||
logger.warning("Stripe event does not contain a payment hash.")
|
||||
return
|
||||
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
if not payment:
|
||||
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
||||
return
|
||||
await payment.check_fiat_status()
|
||||
|
||||
|
||||
def check_stripe_signature(
|
||||
payload: bytes,
|
||||
sig_header: str | None,
|
||||
secret: str | None,
|
||||
tolerance_seconds=300,
|
||||
):
|
||||
if not sig_header:
|
||||
logger.warning("Stripe-Signature header is missing.")
|
||||
raise ValueError("Stripe-Signature header is missing.")
|
||||
|
||||
if not secret:
|
||||
logger.warning("Stripe webhook signing secret is not set.")
|
||||
raise ValueError("Stripe webhook cannot be verified.")
|
||||
|
||||
# Split the Stripe-Signature header
|
||||
items = dict(i.split("=") for i in sig_header.split(","))
|
||||
timestamp = int(items["t"])
|
||||
signature = items["v1"]
|
||||
|
||||
# Check timestamp tolerance
|
||||
if abs(time.time() - timestamp) > tolerance_seconds:
|
||||
logger.warning("Timestamp outside tolerance.")
|
||||
logger.debug(
|
||||
f"Current time: {time.time()}, "
|
||||
f"Timestamp: {timestamp}, "
|
||||
f"Tolerance: {tolerance_seconds} seconds"
|
||||
)
|
||||
|
||||
raise ValueError("Timestamp outside tolerance." f"Timestamp: {timestamp}")
|
||||
|
||||
signed_payload = f"{timestamp}.{payload.decode()}"
|
||||
|
||||
# Compute HMAC SHA256 using the webhook secret
|
||||
computed_signature = hmac.new(
|
||||
key=secret.encode(), msg=signed_payload.encode(), digestmod=hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
# Compare signatures using constant time comparison
|
||||
if hmac.compare_digest(computed_signature, signature) is not True:
|
||||
logger.warning("Stripe signature verification failed.")
|
||||
raise ValueError("Stripe signature verification failed.")
|
||||
|
||||
|
||||
async def test_connection(provider: str) -> SimpleStatus:
|
||||
"""
|
||||
Test the connection to Stripe by checking if the API key is valid.
|
||||
This function should be called when setting up or testing the Stripe integration.
|
||||
"""
|
||||
fiat_provider = await get_fiat_provider(provider)
|
||||
if not fiat_provider:
|
||||
return SimpleStatus(
|
||||
success=False,
|
||||
message=f"Fiat provider '{provider}' not found.",
|
||||
)
|
||||
status = await fiat_provider.status()
|
||||
if status.error_message:
|
||||
return SimpleStatus(
|
||||
success=False,
|
||||
message=f"Cconnection test failed: {status.error_message}",
|
||||
)
|
||||
|
||||
return SimpleStatus(
|
||||
success=True,
|
||||
message="Connection test successful." f" Balance: {status.balance}.",
|
||||
)
|
||||
|
||||
@@ -110,7 +110,7 @@ async def create_payment_request(
|
||||
|
||||
async def create_fiat_invoice(
|
||||
wallet_id: str, invoice_data: CreateInvoice, conn: Connection | None = None
|
||||
) -> Payment:
|
||||
):
|
||||
fiat_provider_name = invoice_data.fiat_provider
|
||||
if not fiat_provider_name:
|
||||
raise ValueError("Fiat provider is required for fiat invoices.")
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from collections.abc import Callable, Coroutine
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -25,6 +27,7 @@ from lnbits.core.services.notifications import (
|
||||
)
|
||||
from lnbits.db import Filters
|
||||
from lnbits.settings import settings
|
||||
from lnbits.tasks import create_unique_task
|
||||
from lnbits.utils.exchange_rates import btc_rates
|
||||
|
||||
audit_queue: asyncio.Queue[AuditEntry] = asyncio.Queue()
|
||||
@@ -159,3 +162,14 @@ async def collect_exchange_rates_data() -> None:
|
||||
else:
|
||||
sleep_time = 60
|
||||
await asyncio.sleep(sleep_time)
|
||||
|
||||
|
||||
def _create_unique_task(name: str, func: Callable):
|
||||
async def _to_coro(func: Callable[[], Coroutine]) -> Coroutine:
|
||||
return await func()
|
||||
|
||||
try:
|
||||
create_unique_task(name, _to_coro(func))
|
||||
except Exception as e:
|
||||
logger.error(f"Error in {name} task", e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</h6>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8">
|
||||
<div class="col-md-8 col-sm-12">
|
||||
<div class="q-pa-sm">
|
||||
<canvas
|
||||
style="
|
||||
@@ -17,7 +17,7 @@
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.lnbits_exchange_history_refresh_interval_seconds"
|
||||
@@ -74,104 +74,100 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-auto">
|
||||
<q-table
|
||||
row-key="name"
|
||||
:rows="formData.lnbits_exchange_rate_providers"
|
||||
:columns="exchangesTable.columns"
|
||||
v-model:pagination="exchangesTable.pagination"
|
||||
:grid="$q.screen.xs"
|
||||
:dense="$q.screen.lt.md"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width></q-th>
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span v-text="col.label"></span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td>
|
||||
<q-btn
|
||||
@click="removeExchangeProvider(props.row)"
|
||||
round
|
||||
icon="delete"
|
||||
size="sm"
|
||||
color="negative"
|
||||
class="q-ml-xs"
|
||||
>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="props.row.name"
|
||||
@update:model-value="touchSettings()"
|
||||
type="text"
|
||||
>
|
||||
</q-input>
|
||||
</q-td>
|
||||
<q-td full-width>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="props.row.api_url"
|
||||
@update:model-value="touchSettings()"
|
||||
type="text"
|
||||
>
|
||||
</q-input
|
||||
></q-td>
|
||||
<q-td>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="props.row.path"
|
||||
@update:model-value="touchSettings()"
|
||||
type="text"
|
||||
>
|
||||
</q-input>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="props.row.exclude_to"
|
||||
@update:model-value="touchSettings()"
|
||||
multiple
|
||||
:options="{{ currencies | safe }}"
|
||||
></q-select>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-btn
|
||||
@click="showTickerConversionDialog(props.row)"
|
||||
round
|
||||
icon="add"
|
||||
size="sm"
|
||||
color="gray"
|
||||
class="q-ml-xs"
|
||||
>
|
||||
</q-btn>
|
||||
<q-chip
|
||||
v-for="ticker, index in props.row.ticker_conversion"
|
||||
:key="ticker"
|
||||
removable
|
||||
dense
|
||||
filled
|
||||
@remove="removeExchangeTickerConversion(props.row, ticker)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
:label="ticker"
|
||||
class="ellipsis"
|
||||
>
|
||||
</q-chip>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<q-table
|
||||
row-key="name"
|
||||
:rows="formData.lnbits_exchange_rate_providers"
|
||||
:columns="exchangesTable.columns"
|
||||
v-model:pagination="exchangesTable.pagination"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width></q-th>
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span v-text="col.label"></span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td>
|
||||
<q-btn
|
||||
@click="removeExchangeProvider(props.row)"
|
||||
round
|
||||
icon="delete"
|
||||
size="sm"
|
||||
color="negative"
|
||||
class="q-ml-xs"
|
||||
>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="props.row.name"
|
||||
@update:model-value="touchSettings()"
|
||||
type="text"
|
||||
>
|
||||
</q-input>
|
||||
</q-td>
|
||||
<q-td full-width>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="props.row.api_url"
|
||||
@update:model-value="touchSettings()"
|
||||
type="text"
|
||||
>
|
||||
</q-input
|
||||
></q-td>
|
||||
<q-td>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="props.row.path"
|
||||
@update:model-value="touchSettings()"
|
||||
type="text"
|
||||
>
|
||||
</q-input>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="props.row.exclude_to"
|
||||
@update:model-value="touchSettings()"
|
||||
multiple
|
||||
:options="{{ currencies | safe }}"
|
||||
></q-select>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-btn
|
||||
@click="showTickerConversionDialog(props.row)"
|
||||
round
|
||||
icon="add"
|
||||
size="sm"
|
||||
color="gray"
|
||||
class="q-ml-xs"
|
||||
>
|
||||
</q-btn>
|
||||
<q-chip
|
||||
v-for="ticker, index in props.row.ticker_conversion"
|
||||
:key="ticker"
|
||||
removable
|
||||
dense
|
||||
filled
|
||||
@remove="removeExchangeTickerConversion(props.row, ticker)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
:label="ticker"
|
||||
class="ellipsis"
|
||||
>
|
||||
</q-chip>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
<ul>
|
||||
<li>
|
||||
<code>API URL</code> and <code>JSON Path</code> fields can use the
|
||||
|
||||
@@ -103,10 +103,10 @@
|
||||
<q-card-section>
|
||||
<span v-text="$t('webhook_events_list')"></span>
|
||||
<ul>
|
||||
<li><code>checkout.session.async_payment_failed</code></li>
|
||||
<li><code>checkout.session.async_payment_succeeded</code></li>
|
||||
<li><code>checkout.session.completed</code></li>
|
||||
- the user completed the checkout process
|
||||
<li><code>invoice.paid</code></li>
|
||||
- the invoice was successfully paid (for subscriptions)
|
||||
<li><code>checkout.session.expired</code></li>
|
||||
</ul>
|
||||
</q-card-section>
|
||||
</q-expansion-item>
|
||||
|
||||
@@ -106,28 +106,6 @@
|
||||
:form-data="formData"
|
||||
:allowed-funding-sources="settings.lnbits_allowed_funding_sources"
|
||||
/>
|
||||
<div class="row q-col-gutter-md q-my-md">
|
||||
<div class="col-12 col-sm-8">
|
||||
<q-item tag="div">
|
||||
<q-item-section>
|
||||
<q-item-label
|
||||
v-text="$t('funding_source_retries')"
|
||||
></q-item-label>
|
||||
<q-item-label
|
||||
caption
|
||||
v-text="$t('funding_source_retries_desc')"
|
||||
></q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.funding_source_max_retries"
|
||||
type="number"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator></q-separator>
|
||||
<h6 class="q-mt-lg q-mb-sm">
|
||||
|
||||
@@ -76,25 +76,19 @@
|
||||
icon="add"
|
||||
></q-btn>
|
||||
</q-input>
|
||||
<div>
|
||||
<q-chip
|
||||
v-for="identifier in formData.lnbits_nostr_notifications_identifiers"
|
||||
:key="identifier"
|
||||
removable
|
||||
@remove="removeNostrNotificationIdentifier(identifier)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
><span class="ellipsis" v-text="identifier"></span
|
||||
></q-chip>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<div>
|
||||
<q-chip
|
||||
v-for="identifier in formData.lnbits_nostr_notifications_identifiers"
|
||||
:key="identifier"
|
||||
removable
|
||||
@remove="removeNostrNotificationIdentifier(identifier)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
class="ellipsis"
|
||||
:label="identifier"
|
||||
><q-tooltip
|
||||
v-if="identifier"
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
><span v-text="identifier"></span></q-tooltip
|
||||
></q-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<q-card-section class="q-pa-none">
|
||||
<h6 class="q-my-none"><span v-text="$t('server_management')"></span></h6>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-md-6">
|
||||
<p><span v-text="$t('base_url')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
@@ -16,7 +16,7 @@
|
||||
<span v-text="$t('authentication')"></span>
|
||||
</h6>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-sm-6">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.auth_token_expire_minutes"
|
||||
@@ -26,7 +26,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-sm-6">
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.auth_allowed_methods"
|
||||
@@ -34,7 +34,6 @@
|
||||
:hint="$t('auth_allowed_methods_hint')"
|
||||
:label="$t('auth_allowed_methods_label')"
|
||||
:options="formData.auth_all_methods"
|
||||
:option-label="option => option.length > 25 ? option.substring(0, 22) + '...' : option"
|
||||
></q-select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +79,7 @@
|
||||
<strong class="q-my-none q-mb-sm">Google Auth</strong>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 q-pr-sm">
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.google_client_id"
|
||||
@@ -89,7 +88,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.google_client_secret"
|
||||
@@ -107,7 +106,7 @@
|
||||
<strong class="q-my-none q-mb-sm">GitHub Auth</strong>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 q-pr-sm">
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.github_client_id"
|
||||
@@ -116,7 +115,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.github_client_secret"
|
||||
@@ -134,7 +133,7 @@
|
||||
<strong class="q-my-none q-mb-sm">Keycloak Auth</strong>
|
||||
|
||||
<div class="row q-col-gutter-sm q-col-gutter-y-md">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.keycloak_discovery_url"
|
||||
@@ -142,7 +141,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.keycloak_client_id"
|
||||
@@ -151,7 +150,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.keycloak_client_secret"
|
||||
@@ -160,7 +159,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.keycloak_client_custom_org"
|
||||
@@ -168,7 +167,7 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-8">
|
||||
<div class="col-md-8 col-sm-12">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.keycloak_client_custom_icon"
|
||||
@@ -214,7 +213,7 @@
|
||||
<div class="col-12 col-md-12">
|
||||
<p v-text="$t('ip_blocker')"></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formBlockedIPs"
|
||||
@@ -244,7 +243,7 @@
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
filled
|
||||
v-model="formAllowedIPs"
|
||||
@@ -280,7 +279,7 @@
|
||||
<div class="col-12 col-md-12">
|
||||
<p v-text="$t('rate_limiter')"></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -288,7 +287,7 @@
|
||||
:label="$t('number_of_requests')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-6">
|
||||
<q-select
|
||||
filled
|
||||
:options="[$t('second'),$t('minute'),$t('hour')]"
|
||||
|
||||
@@ -76,30 +76,7 @@ import window_vars with context %} {% block scripts %} {{ window_vars(user) }}
|
||||
<div class="row q-col-gutter-md justify-center">
|
||||
<div class="col q-gutter-y-md">
|
||||
<q-card>
|
||||
<!-- Mobile: Horizontal tabs at top -->
|
||||
<q-tabs
|
||||
v-if="$q.screen.lt.md"
|
||||
@update:model-value="showExchangeProvidersTab"
|
||||
v-model="tab"
|
||||
dense
|
||||
active-color="primary"
|
||||
inline-label
|
||||
class="text-primary"
|
||||
>
|
||||
<q-tab name="funding" icon="account_balance_wallet" label="Fund" />
|
||||
<q-tab name="security" icon="security" label="Sec" />
|
||||
<q-tab name="server" icon="settings" label="Srv" />
|
||||
<q-tab name="exchange_providers" icon="swap_horiz" label="Exch" />
|
||||
<q-tab name="fiat_providers" icon="account_balance" label="Fiat" />
|
||||
<q-tab name="extensions" icon="extension" label="Ext" />
|
||||
<q-tab name="notifications" icon="notifications" label="Not" />
|
||||
<q-tab name="audit" icon="receipt_long" label="Aud" />
|
||||
<q-tab name="site_customisation" icon="language" label="Site" />
|
||||
<q-tab name="library" icon="image" label="Lib" />
|
||||
</q-tabs>
|
||||
|
||||
<!-- Desktop: Vertical sidebar with splitter -->
|
||||
<q-splitter v-if="$q.screen.gt.sm">
|
||||
<q-splitter>
|
||||
<template v-slot:before>
|
||||
<q-tabs
|
||||
@update:model-value="showExchangeProvidersTab"
|
||||
@@ -205,6 +182,7 @@ import window_vars with context %} {% block scripts %} {{ window_vars(user) }}
|
||||
<q-tab-panels
|
||||
v-model="tab"
|
||||
animated
|
||||
swipeable
|
||||
vertical
|
||||
scroll
|
||||
transition-prev="jump-up"
|
||||
@@ -224,35 +202,6 @@ import window_vars with context %} {% block scripts %} {{ window_vars(user) }}
|
||||
</q-form>
|
||||
</template>
|
||||
</q-splitter>
|
||||
|
||||
<!-- Mobile: Full-width content without sidebar -->
|
||||
<q-form
|
||||
v-if="$q.screen.lt.md"
|
||||
name="settings_form"
|
||||
id="settings_form_mobile"
|
||||
>
|
||||
<q-scroll-area style="height: 70vh">
|
||||
<q-tab-panels
|
||||
v-model="tab"
|
||||
animated
|
||||
vertical
|
||||
scroll
|
||||
transition-prev="jump-up"
|
||||
transition-next="jump-up"
|
||||
style="overflow-x: hidden; padding: 8px"
|
||||
>
|
||||
{% include "admin/_tab_funding.html" %} {% include
|
||||
"admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {%
|
||||
include "admin/_tab_exchange_providers.html" %}{% include
|
||||
"admin/_tab_fiat_providers.html" %} {% include
|
||||
"admin/_tab_extensions.html" %} {% include
|
||||
"admin/_tab_notifications.html" %} {% include
|
||||
"admin/_tab_security.html" %} {% include "admin/_tab_theme.html"
|
||||
%}{% include "admin/_tab_audit.html"%}{% include
|
||||
"admin/_tab_library.html"%}
|
||||
</q-tab-panels>
|
||||
</q-scroll-area>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -137,40 +137,8 @@
|
||||
@click="showExtensionDetails(extension.id, extension.details_link)"
|
||||
v-text="extension.name"
|
||||
></div>
|
||||
<div style="justify-content: space-between; display: flex">
|
||||
<div>
|
||||
<lnbits-extension-rating :rating="0" />
|
||||
<q-btn-group size="xs" style="margin: 5px 0">
|
||||
<q-btn
|
||||
v-if="extension.hasFreeRelease"
|
||||
color="green"
|
||||
size="xs"
|
||||
:label="$t('free')"
|
||||
>
|
||||
<q-tooltip>
|
||||
<span v-text="$t('extension_has_free_release')"></span>
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="extension.hasPaidRelease || extension.paidFeatures"
|
||||
color="primary"
|
||||
size="xs"
|
||||
:label="$t('paid')"
|
||||
>
|
||||
<q-tooltip>
|
||||
<span
|
||||
v-if="extension.hasPaidRelease"
|
||||
v-text="$t('extension_has_paid_release')"
|
||||
></span>
|
||||
<br
|
||||
v-if="extension.hasPaidRelease && extension.paidFeatures"
|
||||
/>
|
||||
<span
|
||||
v-if="extension.paidFeatures"
|
||||
v-text="extension.paidFeatures"
|
||||
></span>
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-btn-group>
|
||||
</div>
|
||||
<div style="justify-content: space-between; display: flex">
|
||||
<q-toggle
|
||||
@@ -948,6 +916,7 @@
|
||||
:href="selectedExtensionDetails.repo"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="q-pr-xs"
|
||||
><q-tooltip>repository</q-tooltip></q-btn
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!---->
|
||||
{% block scripts %} {{ window_vars(user, wallet) }}{% endblock %} {% block page
|
||||
%}
|
||||
<div class="row q-col-gutter-md" style="margin-bottom: 6rem">
|
||||
<div class="row q-col-gutter-md">
|
||||
{% if HIDE_API and AD_SPACE_ENABLED and AD_SPACE %}
|
||||
<div class="col-12 col-md-8 q-gutter-y-md">
|
||||
{% elif HIDE_API %}
|
||||
@@ -877,6 +877,11 @@
|
||||
></q-avatar>
|
||||
<span v-text="$t('nfc_supported')"></span>
|
||||
</q-chip>
|
||||
<span
|
||||
v-else
|
||||
class="text-caption text-grey"
|
||||
v-text="$t('nfc_not_supported')"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-lg">
|
||||
|
||||
@@ -116,6 +116,19 @@
|
||||
>
|
||||
<q-tooltip>Delete Wallet</q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
icon="link"
|
||||
size="sm"
|
||||
flat
|
||||
class="cursor-pointer q-mr-xs"
|
||||
@click="copyWalletLink(props.row.id)"
|
||||
>
|
||||
<q-tooltip>Copy Wallet Link</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<span v-text="props.row.name"></span>
|
||||
<q-btn
|
||||
round
|
||||
v-if="props.row.deleted"
|
||||
@@ -127,42 +140,6 @@
|
||||
>
|
||||
<q-tooltip>Undelete Wallet</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
icon="link"
|
||||
size="sm"
|
||||
flat
|
||||
class="cursor-pointer q-mr-xs"
|
||||
@click="copyWalletLink(props.row.id)"
|
||||
>
|
||||
<q-tooltip>Copy Wallet Link</q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td auto-width>
|
||||
<span
|
||||
v-text="props.row.name"
|
||||
v-if="!props.row.editable"
|
||||
:class="props.row.deleted ? 'text-strike' : 'cursor-pointer'"
|
||||
@click="props.row.editable = true && !props.row.deleted"
|
||||
></span>
|
||||
<q-input
|
||||
v-else
|
||||
@keydown.enter="updateWallet(props.row)"
|
||||
v-model="props.row.name"
|
||||
size="xs"
|
||||
flat
|
||||
dense
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-btn
|
||||
@click="updateWallet(props.row)"
|
||||
round
|
||||
dense
|
||||
size="xs"
|
||||
flat
|
||||
icon="send"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-td>
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
|
||||
@@ -113,9 +113,8 @@ async def api_fiat_as_sats(data: ConversionData):
|
||||
return output
|
||||
|
||||
|
||||
@api_router.get("/api/v1/qrcode", response_class=StreamingResponse)
|
||||
@api_router.get("/api/v1/qrcode/{data}", response_class=StreamingResponse)
|
||||
async def img(data: str):
|
||||
async def img(data):
|
||||
qr = pyqrcode.create(data)
|
||||
stream = BytesIO()
|
||||
qr.svg(stream, scale=3)
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.crud.payments import (
|
||||
get_standalone_payment,
|
||||
)
|
||||
from lnbits.core.models.misc import SimpleStatus
|
||||
from lnbits.core.models.payments import CreateInvoice
|
||||
from lnbits.core.services.fiat_providers import (
|
||||
check_stripe_signature,
|
||||
handle_stripe_event,
|
||||
)
|
||||
from lnbits.core.services.payments import create_fiat_invoice
|
||||
from lnbits.fiat.base import FiatSubscriptionPaymentOptions
|
||||
from lnbits.settings import settings
|
||||
|
||||
callback_router = APIRouter(prefix="/api/v1/callback", tags=["callback"])
|
||||
@@ -41,105 +33,3 @@ async def api_generic_webhook_handler(
|
||||
success=False,
|
||||
message=f"Unknown fiat provider '{provider_name}'.",
|
||||
)
|
||||
|
||||
|
||||
async def handle_stripe_event(event: dict):
|
||||
event_id = event.get("id")
|
||||
event_type = event.get("type")
|
||||
if event_type == "checkout.session.completed":
|
||||
await _handle_stripe_checkout_session_completed(event)
|
||||
elif event_type == "invoice.paid":
|
||||
await _handle_stripe_subscription_invoice_paid(event)
|
||||
else:
|
||||
logger.info(
|
||||
f"Unhandled Stripe event type: '{event_type}'." f" Event ID: '{event_id}'."
|
||||
)
|
||||
|
||||
|
||||
async def _handle_stripe_checkout_session_completed(event: dict):
|
||||
event_id = event.get("id")
|
||||
event_object = event.get("data", {}).get("object", {})
|
||||
object_type = event_object.get("object")
|
||||
payment_hash = event_object.get("metadata", {}).get("payment_hash")
|
||||
lnbits_action = event_object.get("metadata", {}).get("lnbits_action")
|
||||
logger.debug(
|
||||
f"Handling Stripe event: '{event_id}'. Type: '{object_type}'."
|
||||
f" Payment hash: '{payment_hash}'."
|
||||
)
|
||||
if lnbits_action != "invoice":
|
||||
logger.warning(f"Stripe event is not an invoice: '{lnbits_action}'.")
|
||||
return
|
||||
|
||||
if not payment_hash:
|
||||
raise ValueError("Stripe event does not contain a payment hash.")
|
||||
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
if not payment:
|
||||
raise ValueError(f"No payment found for hash: '{payment_hash}'.")
|
||||
await payment.check_fiat_status()
|
||||
|
||||
|
||||
async def _handle_stripe_subscription_invoice_paid(event: dict):
|
||||
invoice = event.get("data", {}).get("object", {})
|
||||
parent = invoice.get("parent", {})
|
||||
|
||||
currency = invoice.get("currency", "").upper()
|
||||
if not currency:
|
||||
raise ValueError("Stripe invoice.paid event missing 'currency'.")
|
||||
|
||||
amount_paid = invoice.get("amount_paid")
|
||||
if not amount_paid:
|
||||
raise ValueError("Stripe invoice.paid event missing 'amount_paid'.")
|
||||
|
||||
payment_options = await _get_stripe_subscription_payment_options(parent)
|
||||
if not payment_options.wallet_id:
|
||||
raise ValueError("Stripe invoice.paid event missing 'wallet_id' in metadata.")
|
||||
|
||||
memo = " | ".join(
|
||||
[i.get("description", "") for i in invoice.get("lines", {}).get("data", [])]
|
||||
+ [payment_options.memo or "", invoice.get("customer_email", "")]
|
||||
)
|
||||
|
||||
extra = {
|
||||
**(payment_options.extra or {}),
|
||||
"fiat_method": "subscription",
|
||||
"tag": payment_options.tag,
|
||||
"subscription": {
|
||||
"checking_id": invoice.get("id"),
|
||||
"payment_request": invoice.get("hosted_invoice_url"),
|
||||
},
|
||||
}
|
||||
|
||||
payment = await create_fiat_invoice(
|
||||
wallet_id=payment_options.wallet_id,
|
||||
invoice_data=CreateInvoice(
|
||||
unit=currency,
|
||||
amount=amount_paid / 100, # convert cents to dollars
|
||||
memo=memo,
|
||||
extra=extra,
|
||||
fiat_provider="stripe",
|
||||
),
|
||||
)
|
||||
|
||||
await payment.check_fiat_status()
|
||||
|
||||
|
||||
async def _get_stripe_subscription_payment_options(
|
||||
parent: dict,
|
||||
) -> FiatSubscriptionPaymentOptions:
|
||||
if not parent or not parent.get("type") == "subscription_details":
|
||||
raise ValueError("Stripe invoice.paid event does not contain a subscription.")
|
||||
|
||||
metadata = parent.get("subscription_details", {}).get("metadata", {})
|
||||
|
||||
if metadata.get("lnbits_action") != "subscription":
|
||||
raise ValueError("Stripe invoice.paid metadata action is not 'subscription'.")
|
||||
|
||||
if "extra" in metadata:
|
||||
try:
|
||||
metadata["extra"] = json.loads(metadata["extra"])
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.warning(exc)
|
||||
metadata["extra"] = {}
|
||||
|
||||
return FiatSubscriptionPaymentOptions(**metadata)
|
||||
|
||||
@@ -61,10 +61,9 @@ async def api_install_extension(data: CreateExtension):
|
||||
data.ext_id, data.source_repo, data.archive, data.version
|
||||
)
|
||||
if not release:
|
||||
raise HTTPException(HTTPStatus.NOT_FOUND, "Release not found")
|
||||
|
||||
if not release.is_version_compatible:
|
||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Incompatible extension version.")
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Release not found"
|
||||
)
|
||||
|
||||
release.payment_hash = data.payment_hash
|
||||
ext_meta = ExtensionMeta(installed_release=release)
|
||||
|
||||
@@ -3,11 +3,9 @@ from http import HTTPStatus
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from lnbits.core.models.misc import SimpleStatus
|
||||
from lnbits.core.models.wallets import WalletTypeInfo
|
||||
from lnbits.core.services.fiat_providers import test_connection
|
||||
from lnbits.decorators import check_admin, require_admin_key
|
||||
from lnbits.decorators import check_admin
|
||||
from lnbits.fiat import StripeWallet, get_fiat_provider
|
||||
from lnbits.fiat.base import CreateFiatSubscription, FiatSubscriptionResponse
|
||||
|
||||
fiat_router = APIRouter(tags=["Fiat API"], prefix="/api/v1/fiat")
|
||||
|
||||
@@ -21,67 +19,20 @@ async def api_test_fiat_provider(provider: str) -> SimpleStatus:
|
||||
return await test_connection(provider)
|
||||
|
||||
|
||||
@fiat_router.post(
|
||||
"/{provider}/subscription",
|
||||
status_code=HTTPStatus.OK,
|
||||
)
|
||||
async def create_subscription(
|
||||
provider: str,
|
||||
data: CreateFiatSubscription,
|
||||
key_type: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> FiatSubscriptionResponse:
|
||||
fiat_provider = await get_fiat_provider(provider)
|
||||
if not fiat_provider:
|
||||
raise HTTPException(404, "Fiat provider not found")
|
||||
|
||||
wallet_id = data.payment_options.wallet_id
|
||||
|
||||
if wallet_id and wallet_id != key_type.wallet.id:
|
||||
raise HTTPException(
|
||||
403,
|
||||
"Wallet id does not match your API key."
|
||||
"Leave it empty to use your key's wallet.",
|
||||
)
|
||||
|
||||
data.payment_options.wallet_id = key_type.wallet.id
|
||||
subscription_response = await fiat_provider.create_subscription(
|
||||
data.subscription_id, data.quantity, data.payment_options
|
||||
)
|
||||
return subscription_response
|
||||
|
||||
|
||||
@fiat_router.delete(
|
||||
"/{provider}/subscription/{subscription_id}",
|
||||
status_code=HTTPStatus.OK,
|
||||
)
|
||||
async def cancel_subscription(
|
||||
provider: str,
|
||||
subscription_id: str,
|
||||
key_type: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> FiatSubscriptionResponse:
|
||||
fiat_provider = await get_fiat_provider(provider)
|
||||
if not fiat_provider:
|
||||
raise HTTPException(404, "Fiat provider not found")
|
||||
|
||||
resp = await fiat_provider.cancel_subscription(subscription_id, key_type.wallet.id)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@fiat_router.post(
|
||||
"/{provider}/connection_token",
|
||||
status_code=HTTPStatus.OK,
|
||||
dependencies=[Depends(check_admin)],
|
||||
)
|
||||
async def connection_token(provider: str):
|
||||
fiat_provider = await get_fiat_provider(provider)
|
||||
provider_wallet = await get_fiat_provider(provider)
|
||||
if provider == "stripe":
|
||||
if not isinstance(fiat_provider, StripeWallet):
|
||||
if not isinstance(provider_wallet, StripeWallet):
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Stripe wallet/provider not configured"
|
||||
)
|
||||
try:
|
||||
tok = await fiat_provider.create_terminal_connection_token()
|
||||
tok = await provider_wallet.create_terminal_connection_token()
|
||||
secret = tok.get("secret")
|
||||
if not secret:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -48,96 +48,6 @@ async def home(request: Request, lightning: str = ""):
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get(
|
||||
"/account",
|
||||
response_class=HTMLResponse,
|
||||
description="show account page",
|
||||
)
|
||||
async def account(
|
||||
request: Request,
|
||||
user: User = Depends(check_user_exists),
|
||||
):
|
||||
nostr_configured = settings.is_nostr_notifications_configured()
|
||||
telegram_configured = settings.is_telegram_notifications_configured()
|
||||
return template_renderer().TemplateResponse(
|
||||
request,
|
||||
"core/account.html",
|
||||
{
|
||||
"user": user.json(),
|
||||
"nostr_configured": nostr_configured,
|
||||
"telegram_configured": telegram_configured,
|
||||
"ajax": _is_ajax_request(request),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get(
|
||||
"/wallet",
|
||||
response_class=HTMLResponse,
|
||||
description="show wallet page",
|
||||
)
|
||||
async def get_user_wallet(
|
||||
request: Request,
|
||||
lnbits_last_active_wallet: Annotated[str | None, Cookie()] = None,
|
||||
user: User = Depends(check_user_exists),
|
||||
wal: UUID4 | None = Query(None),
|
||||
):
|
||||
if wal:
|
||||
wallet = await get_wallet(wal.hex)
|
||||
elif len(user.wallets) == 0:
|
||||
wallet = await create_wallet(user_id=user.id)
|
||||
user.wallets.append(wallet)
|
||||
elif lnbits_last_active_wallet and user.get_wallet(lnbits_last_active_wallet):
|
||||
wallet = await get_wallet(lnbits_last_active_wallet)
|
||||
else:
|
||||
wallet = user.wallets[0]
|
||||
|
||||
if not wallet or wallet.deleted:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Wallet not found",
|
||||
)
|
||||
if wallet.user != user.id:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Not your wallet.",
|
||||
)
|
||||
context = {
|
||||
"user": user.json(),
|
||||
"wallet": wallet.json(),
|
||||
"wallet_name": wallet.name,
|
||||
"currencies": allowed_currencies(),
|
||||
"service_fee": settings.lnbits_service_fee,
|
||||
"service_fee_max": settings.lnbits_service_fee_max,
|
||||
"web_manifest": f"/manifest/{user.id}.webmanifest",
|
||||
}
|
||||
|
||||
return template_renderer().TemplateResponse(
|
||||
request,
|
||||
"core/wallet.html",
|
||||
{**context, "ajax": _is_ajax_request(request)},
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get(
|
||||
"/wallets",
|
||||
response_class=HTMLResponse,
|
||||
description="show wallets page",
|
||||
)
|
||||
async def wallets(
|
||||
request: Request,
|
||||
user: User = Depends(check_user_exists),
|
||||
):
|
||||
return template_renderer().TemplateResponse(
|
||||
request,
|
||||
"core/wallets.html",
|
||||
{
|
||||
"user": user.json(),
|
||||
"ajax": _is_ajax_request(request),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get("/first_install", response_class=HTMLResponse)
|
||||
async def first_install(request: Request):
|
||||
if not settings.first_install:
|
||||
@@ -216,9 +126,6 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
||||
if ext.meta and ext.meta.latest_release
|
||||
else None
|
||||
),
|
||||
"hasPaidRelease": ext.meta.has_paid_release if ext.meta else False,
|
||||
"hasFreeRelease": ext.meta.has_free_release if ext.meta else False,
|
||||
"paidFeatures": ext.meta.paid_features if ext.meta else False,
|
||||
"installedRelease": (
|
||||
dict(ext.meta.installed_release)
|
||||
if ext.meta and ext.meta.installed_release
|
||||
@@ -333,6 +240,91 @@ async def extensions_builder_preview(
|
||||
return response
|
||||
|
||||
|
||||
@generic_router.get(
|
||||
"/wallet",
|
||||
response_class=HTMLResponse,
|
||||
description="show wallet page",
|
||||
)
|
||||
async def wallet(
|
||||
request: Request,
|
||||
lnbits_last_active_wallet: Annotated[str | None, Cookie()] = None,
|
||||
user: User = Depends(check_user_exists),
|
||||
wal: UUID4 | None = Query(None),
|
||||
):
|
||||
if wal:
|
||||
wallet = await get_wallet(wal.hex)
|
||||
elif len(user.wallets) == 0:
|
||||
wallet = await create_wallet(user_id=user.id)
|
||||
user.wallets.append(wallet)
|
||||
elif lnbits_last_active_wallet and user.get_wallet(lnbits_last_active_wallet):
|
||||
wallet = await get_wallet(lnbits_last_active_wallet)
|
||||
else:
|
||||
wallet = user.wallets[0]
|
||||
|
||||
if not wallet or wallet.deleted:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Wallet not found",
|
||||
)
|
||||
context = {
|
||||
"user": user.json(),
|
||||
"wallet": wallet.json(),
|
||||
"wallet_name": wallet.name,
|
||||
"currencies": allowed_currencies(),
|
||||
"service_fee": settings.lnbits_service_fee,
|
||||
"service_fee_max": settings.lnbits_service_fee_max,
|
||||
"web_manifest": f"/manifest/{user.id}.webmanifest",
|
||||
}
|
||||
|
||||
return template_renderer().TemplateResponse(
|
||||
request,
|
||||
"core/wallet.html",
|
||||
{**context, "ajax": _is_ajax_request(request)},
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get(
|
||||
"/account",
|
||||
response_class=HTMLResponse,
|
||||
description="show account page",
|
||||
)
|
||||
async def account(
|
||||
request: Request,
|
||||
user: User = Depends(check_user_exists),
|
||||
):
|
||||
nostr_configured = settings.is_nostr_notifications_configured()
|
||||
telegram_configured = settings.is_telegram_notifications_configured()
|
||||
return template_renderer().TemplateResponse(
|
||||
request,
|
||||
"core/account.html",
|
||||
{
|
||||
"user": user.json(),
|
||||
"nostr_configured": nostr_configured,
|
||||
"telegram_configured": telegram_configured,
|
||||
"ajax": _is_ajax_request(request),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get(
|
||||
"/wallets",
|
||||
response_class=HTMLResponse,
|
||||
description="show wallets page",
|
||||
)
|
||||
async def wallets(
|
||||
request: Request,
|
||||
user: User = Depends(check_user_exists),
|
||||
):
|
||||
return template_renderer().TemplateResponse(
|
||||
request,
|
||||
"core/wallets.html",
|
||||
{
|
||||
"user": user.json(),
|
||||
"ajax": _is_ajax_request(request),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@generic_router.get("/service-worker.js")
|
||||
async def service_worker(request: Request):
|
||||
return template_renderer().TemplateResponse(
|
||||
|
||||
@@ -6,17 +6,16 @@ from fastapi import (
|
||||
Depends,
|
||||
HTTPException,
|
||||
)
|
||||
from lnurl import (
|
||||
LnurlAuthResponse,
|
||||
LnurlErrorResponse,
|
||||
LnurlException,
|
||||
LnurlPayResponse,
|
||||
LnurlResponseException,
|
||||
LnurlWithdrawResponse,
|
||||
)
|
||||
from lnurl import LnurlResponseException
|
||||
from lnurl import execute_login as lnurlauth
|
||||
from lnurl import handle as lnurl_handle
|
||||
from lnurl.models import LnurlResponseModel
|
||||
from lnurl.models import (
|
||||
LnurlAuthResponse,
|
||||
LnurlErrorResponse,
|
||||
LnurlPayResponse,
|
||||
LnurlResponseModel,
|
||||
LnurlWithdrawResponse,
|
||||
)
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.models import Payment
|
||||
@@ -39,7 +38,7 @@ async def _handle(lnurl: str) -> LnurlResponseModel:
|
||||
res = await lnurl_handle(lnurl, user_agent=settings.user_agent, timeout=5)
|
||||
if isinstance(res, LnurlErrorResponse):
|
||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=res.reason)
|
||||
except LnurlException as exc:
|
||||
except LnurlResponseException as exc:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
@@ -296,9 +296,7 @@ async def api_payment(payment_hash, x_api_key: str | None = Header(None)):
|
||||
return {"paid": True, "preimage": payment.preimage}
|
||||
|
||||
if payment.failed:
|
||||
if wallet and wallet.id == payment.wallet_id:
|
||||
return {"paid": False, "status": "failed", "details": payment}
|
||||
return {"paid": False, "status": "failed"}
|
||||
return {"paid": False, "status": "failed", "details": payment}
|
||||
|
||||
try:
|
||||
status = await payment.check_status()
|
||||
|
||||
+2
-8
@@ -424,7 +424,6 @@ class Operator(Enum):
|
||||
LE = "le"
|
||||
INCLUDE = "in"
|
||||
EXCLUDE = "ex"
|
||||
LIKE = "like"
|
||||
|
||||
@property
|
||||
def as_sql(self):
|
||||
@@ -444,8 +443,6 @@ class Operator(Enum):
|
||||
return ">="
|
||||
elif self == Operator.LE:
|
||||
return "<="
|
||||
elif self == Operator.LIKE:
|
||||
return "LIKE"
|
||||
else:
|
||||
raise ValueError("Unknown SQL Operator")
|
||||
|
||||
@@ -502,7 +499,7 @@ class Filter(BaseModel, Generic[TFilterModel]):
|
||||
return cls(field=field, op=op, values=values, model=model)
|
||||
|
||||
@property
|
||||
def statement(self) -> str:
|
||||
def statement(self):
|
||||
stmt = []
|
||||
for key in self.values.keys() if self.values else []:
|
||||
clean_key = key.split("__")[0]
|
||||
@@ -585,10 +582,7 @@ class Filters(BaseModel, Generic[TFilterModel]):
|
||||
for page_filter in self.filters:
|
||||
if page_filter.values:
|
||||
for key, value in page_filter.values.items():
|
||||
if page_filter.op == Operator.LIKE:
|
||||
values[key] = f"%{value}%"
|
||||
else:
|
||||
values[key] = value
|
||||
values[key] = value
|
||||
if self.search and self.model:
|
||||
values["search"] = f"%{self.search.lower()}%"
|
||||
return values
|
||||
|
||||
@@ -4,8 +4,6 @@ from abc import ABC, abstractmethod
|
||||
from collections.abc import AsyncGenerator, Coroutine
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
@@ -78,54 +76,6 @@ class FiatPaymentStatus(NamedTuple):
|
||||
return "pending"
|
||||
|
||||
|
||||
class FiatSubscriptionPaymentOptions(BaseModel):
|
||||
|
||||
memo: str | None = Field(
|
||||
default=None,
|
||||
description="Payments created by the recurring subscription"
|
||||
" will have this memo.",
|
||||
)
|
||||
wallet_id: str | None = Field(
|
||||
default=None,
|
||||
description="Payments created by the recurring subscription"
|
||||
" will be made to this wallet.",
|
||||
)
|
||||
subscription_request_id: str | None = Field(
|
||||
default=None,
|
||||
description="Unique ID that can be used to identify the subscription request."
|
||||
"If not provided, one will be generated.",
|
||||
)
|
||||
tag: str | None = Field(
|
||||
default=None,
|
||||
description="Payments created by the recurring subscription"
|
||||
" will have this tag. Admin only.",
|
||||
)
|
||||
extra: dict[str, Any] | None = Field(
|
||||
default=None,
|
||||
description="Payments created by the recurring subscription"
|
||||
" will merge this extra data to the payment extra. Admin only.",
|
||||
)
|
||||
|
||||
success_url: str | None = Field(
|
||||
default="https://my.lnbits.com",
|
||||
description="The URL to redirect the user to after the"
|
||||
" subscription is successfully created.",
|
||||
)
|
||||
|
||||
|
||||
class CreateFiatSubscription(BaseModel):
|
||||
subscription_id: str
|
||||
quantity: int
|
||||
payment_options: FiatSubscriptionPaymentOptions
|
||||
|
||||
|
||||
class FiatSubscriptionResponse(BaseModel):
|
||||
ok: bool = True
|
||||
subscription_request_id: str | None = None
|
||||
checkout_session_url: str | None = None
|
||||
error_message: str | None = None
|
||||
|
||||
|
||||
class FiatPaymentSuccessStatus(FiatPaymentStatus):
|
||||
paid = True
|
||||
|
||||
@@ -161,32 +111,6 @@ class FiatProvider(ABC):
|
||||
) -> Coroutine[None, None, FiatInvoiceResponse]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_subscription(
|
||||
self,
|
||||
subscription_id: str,
|
||||
quantity: int,
|
||||
payment_options: FiatSubscriptionPaymentOptions,
|
||||
**kwargs,
|
||||
) -> Coroutine[None, None, FiatSubscriptionResponse]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_subscription(
|
||||
self,
|
||||
subscription_id: str,
|
||||
correlation_id: str,
|
||||
**kwargs,
|
||||
) -> Coroutine[None, None, FiatSubscriptionResponse]:
|
||||
"""
|
||||
Cancel a subscription.
|
||||
Args:
|
||||
subscription_id: The ID of the subscription to cancel.
|
||||
correlation_id: An identifier used to verify that the subscription belongs
|
||||
to the user that made the request. Usually the wallet ID.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pay_invoice(
|
||||
self,
|
||||
|
||||
+236
-163
@@ -1,16 +1,16 @@
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from collections.abc import AsyncGenerator
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Literal
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import httpx
|
||||
from httpx import HTTPStatusError
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from lnbits.helpers import normalize_endpoint, urlsafe_short_hash
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
@@ -22,11 +22,37 @@ from .base import (
|
||||
FiatPaymentSuccessStatus,
|
||||
FiatProvider,
|
||||
FiatStatusResponse,
|
||||
FiatSubscriptionPaymentOptions,
|
||||
FiatSubscriptionResponse,
|
||||
)
|
||||
|
||||
FiatMethod = Literal["checkout", "terminal", "subscription"]
|
||||
FiatMethod = Literal["checkout", "terminal"]
|
||||
|
||||
# ---- NEW: normalized subscription status type ----
|
||||
StripeStatus = Literal[
|
||||
"active",
|
||||
"trialing",
|
||||
"past_due",
|
||||
"unpaid",
|
||||
"canceled",
|
||||
"incomplete",
|
||||
"incomplete_expired",
|
||||
"paused",
|
||||
"not_found",
|
||||
"pending",
|
||||
"error",
|
||||
"unknown",
|
||||
]
|
||||
|
||||
# Typed map to ensure mypy sees return values as StripeStatus (not plain str)
|
||||
_STRIPE_STATUS_MAP: dict[str, StripeStatus] = {
|
||||
"active": "active",
|
||||
"trialing": "trialing",
|
||||
"past_due": "past_due",
|
||||
"unpaid": "unpaid",
|
||||
"canceled": "canceled",
|
||||
"incomplete": "incomplete",
|
||||
"incomplete_expired": "incomplete_expired",
|
||||
"paused": "paused",
|
||||
}
|
||||
|
||||
|
||||
class StripeTerminalOptions(BaseModel):
|
||||
@@ -46,12 +72,20 @@ class StripeCheckoutOptions(BaseModel):
|
||||
line_item_name: str | None = None
|
||||
|
||||
|
||||
class StripeSubscriptionOptions(BaseModel):
|
||||
# === Direct-debit subscription options ===
|
||||
class StripeRecurringOptions(BaseModel):
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
||||
checking_id: str | None = None
|
||||
payment_request: str | None = None
|
||||
price_id: str | None = None
|
||||
price_lookup_key: str | None = None
|
||||
payment_method_types: list[str] = Field(default_factory=lambda: ["bacs_debit"])
|
||||
|
||||
success_url: str | None = None
|
||||
cancel_url: str | None = None
|
||||
metadata: dict[str, str] = Field(default_factory=dict)
|
||||
customer_email: str | None = None
|
||||
trial_days: int | None = None
|
||||
|
||||
|
||||
class StripeCreateInvoiceOptions(BaseModel):
|
||||
@@ -61,7 +95,7 @@ class StripeCreateInvoiceOptions(BaseModel):
|
||||
fiat_method: FiatMethod = "checkout"
|
||||
terminal: StripeTerminalOptions | None = None
|
||||
checkout: StripeCheckoutOptions | None = None
|
||||
subscription: StripeSubscriptionOptions | None = None
|
||||
recurring: StripeRecurringOptions | None = None
|
||||
|
||||
|
||||
class StripeWallet(FiatProvider):
|
||||
@@ -101,12 +135,10 @@ class StripeWallet(FiatProvider):
|
||||
r = await self.client.get(url="/v1/balance", timeout=15)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
|
||||
available = data.get("available") or []
|
||||
available_balance = 0
|
||||
if available and isinstance(available, list):
|
||||
available_balance = int(available[0].get("amount", 0))
|
||||
|
||||
return FiatStatusResponse(balance=available_balance)
|
||||
except json.JSONDecodeError:
|
||||
return FiatStatusResponse("Server error: 'invalid json response'", 0)
|
||||
@@ -128,127 +160,24 @@ class StripeWallet(FiatProvider):
|
||||
if not opts:
|
||||
return FiatInvoiceResponse(ok=False, error_message="Invalid Stripe options")
|
||||
|
||||
if opts.recurring is not None:
|
||||
return await self._create_subscription_checkout_session(
|
||||
payment_hash, memo, opts
|
||||
)
|
||||
|
||||
if opts.fiat_method == "checkout":
|
||||
return await self._create_checkout_invoice(
|
||||
amount_cents, currency, payment_hash, memo, opts.checkout
|
||||
amount_cents, currency, payment_hash, memo, opts
|
||||
)
|
||||
if opts.fiat_method == "terminal":
|
||||
return await self._create_terminal_invoice(
|
||||
amount_cents, currency, payment_hash, opts.terminal
|
||||
amount_cents, currency, payment_hash, opts
|
||||
)
|
||||
|
||||
if opts.fiat_method == "subscription":
|
||||
return self._create_subscription_invoice(opts.subscription)
|
||||
|
||||
return FiatInvoiceResponse(
|
||||
ok=False, error_message=f"Unsupported fiat_method: {opts.fiat_method}"
|
||||
)
|
||||
|
||||
async def create_subscription(
|
||||
self,
|
||||
subscription_id: str,
|
||||
quantity: int,
|
||||
payment_options: FiatSubscriptionPaymentOptions,
|
||||
**kwargs,
|
||||
) -> FiatSubscriptionResponse:
|
||||
success_url = (
|
||||
payment_options.success_url
|
||||
or settings.stripe_payment_success_url
|
||||
or "https://lnbits.com"
|
||||
)
|
||||
|
||||
if not payment_options.subscription_request_id:
|
||||
payment_options.subscription_request_id = str(uuid.uuid4())
|
||||
payment_options.extra = payment_options.extra or {}
|
||||
payment_options.extra["subscription_request_id"] = (
|
||||
payment_options.subscription_request_id
|
||||
)
|
||||
|
||||
form_data: list[tuple[str, str]] = [
|
||||
("mode", "subscription"),
|
||||
("success_url", success_url),
|
||||
("line_items[0][price]", subscription_id),
|
||||
("line_items[0][quantity]", f"{quantity}"),
|
||||
]
|
||||
subscription_data = {**payment_options.dict(), "lnbits_action": "subscription"}
|
||||
subscription_data["extra"] = json.dumps(subscription_data.get("extra") or {})
|
||||
|
||||
form_data += self._encode_metadata(
|
||||
"subscription_data[metadata]",
|
||||
subscription_data,
|
||||
)
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
"/v1/checkout/sessions",
|
||||
headers=self._build_headers_form(),
|
||||
content=urlencode(form_data),
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
|
||||
url = data.get("url")
|
||||
if not url:
|
||||
return FiatSubscriptionResponse(
|
||||
ok=False, error_message="Server error: missing url"
|
||||
)
|
||||
return FiatSubscriptionResponse(
|
||||
ok=True,
|
||||
checkout_session_url=url,
|
||||
subscription_request_id=payment_options.subscription_request_id,
|
||||
)
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.warning(exc)
|
||||
return FiatSubscriptionResponse(
|
||||
ok=False, error_message="Server error: invalid json response"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return FiatSubscriptionResponse(
|
||||
ok=False, error_message=f"Unable to connect to {self.endpoint}."
|
||||
)
|
||||
|
||||
async def cancel_subscription(
|
||||
self,
|
||||
subscription_id: str,
|
||||
correlation_id: str,
|
||||
**kwargs,
|
||||
) -> FiatSubscriptionResponse:
|
||||
try:
|
||||
params = {
|
||||
"query": f"metadata['wallet_id']:'{correlation_id}'"
|
||||
" AND "
|
||||
f"metadata['subscription_request_id']:'{subscription_id}'"
|
||||
}
|
||||
r = await self.client.get(
|
||||
"/v1/subscriptions/search",
|
||||
params=params,
|
||||
)
|
||||
r.raise_for_status()
|
||||
search_result = r.json()
|
||||
data = search_result.get("data") or []
|
||||
if not data or len(data) == 0:
|
||||
return FiatSubscriptionResponse(
|
||||
ok=False, error_message="Subscription not found."
|
||||
)
|
||||
|
||||
subscription = data[0]
|
||||
subscription_id = subscription.get("id")
|
||||
if not subscription_id:
|
||||
return FiatSubscriptionResponse(
|
||||
ok=False, error_message="Subscription ID not found."
|
||||
)
|
||||
|
||||
r = await self.client.delete(f"/v1/subscriptions/{subscription_id}")
|
||||
r.raise_for_status()
|
||||
|
||||
return FiatSubscriptionResponse(ok=True)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return FiatSubscriptionResponse(
|
||||
ok=False, error_message="Unable to un subscribe."
|
||||
)
|
||||
|
||||
async def pay_invoice(self, payment_request: str) -> FiatPaymentResponse:
|
||||
raise NotImplementedError("Stripe does not support paying invoices directly.")
|
||||
|
||||
@@ -266,11 +195,6 @@ class StripeWallet(FiatProvider):
|
||||
r.raise_for_status()
|
||||
return self._status_from_payment_intent(r.json())
|
||||
|
||||
if stripe_id.startswith("in_"):
|
||||
r = await self.client.get(f"/v1/invoices/{stripe_id}")
|
||||
r.raise_for_status()
|
||||
return self._status_from_invoice(r.json())
|
||||
|
||||
logger.debug(f"Unknown Stripe id prefix: {checking_id}")
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
@@ -295,15 +219,16 @@ class StripeWallet(FiatProvider):
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
# ---------- One-off Checkout ----------
|
||||
async def _create_checkout_invoice(
|
||||
self,
|
||||
amount_cents: int,
|
||||
currency: str,
|
||||
payment_hash: str,
|
||||
memo: str | None,
|
||||
opts: StripeCheckoutOptions | None = None,
|
||||
opts: StripeCreateInvoiceOptions,
|
||||
) -> FiatInvoiceResponse:
|
||||
co = opts or StripeCheckoutOptions()
|
||||
co = opts.checkout or StripeCheckoutOptions()
|
||||
success_url = (
|
||||
co.success_url
|
||||
or settings.stripe_payment_success_url
|
||||
@@ -315,7 +240,6 @@ class StripeWallet(FiatProvider):
|
||||
("mode", "payment"),
|
||||
("success_url", success_url),
|
||||
("metadata[payment_hash]", payment_hash),
|
||||
("metadata[lnbits_action]", "invoice"),
|
||||
("line_items[0][price_data][currency]", currency.lower()),
|
||||
("line_items[0][price_data][product_data][name]", line_item_name),
|
||||
("line_items[0][price_data][unit_amount]", str(amount_cents)),
|
||||
@@ -349,14 +273,15 @@ class StripeWallet(FiatProvider):
|
||||
ok=False, error_message=f"Unable to connect to {self.endpoint}."
|
||||
)
|
||||
|
||||
# ---------- Terminal ----------
|
||||
async def _create_terminal_invoice(
|
||||
self,
|
||||
amount_cents: int,
|
||||
currency: str,
|
||||
payment_hash: str,
|
||||
opts: StripeTerminalOptions | None = None,
|
||||
opts: StripeCreateInvoiceOptions,
|
||||
) -> FiatInvoiceResponse:
|
||||
term = opts or StripeTerminalOptions()
|
||||
term = opts.terminal or StripeTerminalOptions()
|
||||
data: dict[str, str] = {
|
||||
"amount": str(amount_cents),
|
||||
"currency": currency.lower(),
|
||||
@@ -391,20 +316,189 @@ class StripeWallet(FiatProvider):
|
||||
ok=False, error_message=f"Unable to connect to {self.endpoint}."
|
||||
)
|
||||
|
||||
def _create_subscription_invoice(
|
||||
# ---------- Subscription Checkout ----------
|
||||
async def _create_subscription_checkout_session(
|
||||
self,
|
||||
opts: StripeSubscriptionOptions | None = None,
|
||||
payment_hash: str,
|
||||
memo: str | None,
|
||||
opts: StripeCreateInvoiceOptions,
|
||||
) -> FiatInvoiceResponse:
|
||||
term = opts or StripeSubscriptionOptions()
|
||||
rc = opts.recurring or StripeRecurringOptions()
|
||||
try:
|
||||
price_id = rc.price_id
|
||||
if not price_id and rc.price_lookup_key:
|
||||
price_id = await self._get_price_id_by_lookup_key(rc.price_lookup_key)
|
||||
if not price_id:
|
||||
return FiatInvoiceResponse(
|
||||
ok=False,
|
||||
error_message="Stripe: missing price_id or price_lookup_key",
|
||||
)
|
||||
|
||||
return FiatInvoiceResponse(
|
||||
ok=True,
|
||||
checking_id=term.checking_id or urlsafe_short_hash(),
|
||||
payment_request=term.payment_request or "",
|
||||
)
|
||||
success_url = (
|
||||
rc.success_url
|
||||
or (opts.checkout.success_url if opts.checkout else None)
|
||||
or settings.stripe_payment_success_url
|
||||
or "https://lnbits.com"
|
||||
)
|
||||
cancel_url = rc.cancel_url or success_url
|
||||
|
||||
form_data: list[tuple[str, str]] = [
|
||||
("mode", "subscription"),
|
||||
("success_url", success_url),
|
||||
("cancel_url", cancel_url),
|
||||
("payment_method_collection", "always"),
|
||||
("metadata[payment_hash]", payment_hash),
|
||||
("line_items[0][price]", price_id),
|
||||
("line_items[0][quantity]", "1"),
|
||||
]
|
||||
|
||||
if rc.trial_days:
|
||||
form_data.append(
|
||||
("subscription_data[trial_period_days]", str(rc.trial_days))
|
||||
)
|
||||
|
||||
if rc.customer_email:
|
||||
form_data.append(("customer_email", rc.customer_email))
|
||||
|
||||
form_data += self._encode_metadata("metadata", rc.metadata)
|
||||
|
||||
r = await self.client.post(
|
||||
"/v1/checkout/sessions",
|
||||
headers=self._build_headers_form(),
|
||||
content=urlencode(form_data),
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
session_id, url = data.get("id"), data.get("url")
|
||||
if not session_id or not url:
|
||||
return FiatInvoiceResponse(
|
||||
ok=False,
|
||||
error_message="Server error: missing id or url (subscription)",
|
||||
)
|
||||
return FiatInvoiceResponse(
|
||||
ok=True, checking_id=session_id, payment_request=url
|
||||
)
|
||||
|
||||
except HTTPStatusError as e:
|
||||
body = e.response.text if e.response is not None else "<no body>"
|
||||
logger.warning(f"Stripe subscription 400: {body}")
|
||||
return FiatInvoiceResponse(ok=False, error_message=body)
|
||||
except json.JSONDecodeError:
|
||||
return FiatInvoiceResponse(
|
||||
ok=False, error_message="Server error: invalid json response"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return FiatInvoiceResponse(
|
||||
ok=False, error_message=f"Unable to connect to {self.endpoint}."
|
||||
)
|
||||
|
||||
# ---------- Subscription status helpers (NEW) ----------
|
||||
async def get_subscription_status(self, sub_or_session_id: str) -> StripeStatus:
|
||||
"""
|
||||
Accepts either a 'sub_...' or 'cs_...' id. If it's a 'cs_...',
|
||||
returns 'pending' until the subscription exists; once it does,
|
||||
returns the mapped subscription status.
|
||||
"""
|
||||
sid = self._normalize_stripe_id(sub_or_session_id)
|
||||
try:
|
||||
if sid.startswith("sub_"):
|
||||
r = await self.client.get(f"/v1/subscriptions/{sid}")
|
||||
if r.status_code == 404:
|
||||
return "not_found"
|
||||
r.raise_for_status()
|
||||
return self._status_from_subscription(r.json())
|
||||
|
||||
if sid.startswith("cs_"):
|
||||
r = await self.client.get(f"/v1/checkout/sessions/{sid}")
|
||||
if r.status_code == 404:
|
||||
return "not_found"
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
subscription_id = data.get("subscription")
|
||||
if not subscription_id:
|
||||
return "pending"
|
||||
r2 = await self.client.get(f"/v1/subscriptions/{subscription_id}")
|
||||
if r2.status_code == 404:
|
||||
return "not_found"
|
||||
r2.raise_for_status()
|
||||
return self._status_from_subscription(r2.json())
|
||||
|
||||
return "unknown"
|
||||
|
||||
except httpx.HTTPStatusError:
|
||||
return "error"
|
||||
except Exception:
|
||||
return "error"
|
||||
|
||||
async def get_subscription_status_and_promote(
|
||||
self, sub_or_session_id: str
|
||||
) -> tuple[StripeStatus, str]:
|
||||
"""
|
||||
Returns (status, effective_id). If given a 'cs_...' and the Checkout
|
||||
Session has created a subscription, returns the subscription status
|
||||
AND the promoted 'sub_...' id so you can persist it. If given a 'sub_...',
|
||||
returns its status and the same id.
|
||||
"""
|
||||
sid = self._normalize_stripe_id(sub_or_session_id)
|
||||
try:
|
||||
if sid.startswith("sub_"):
|
||||
r = await self.client.get(f"/v1/subscriptions/{sid}")
|
||||
if r.status_code == 404:
|
||||
return ("not_found", sid)
|
||||
r.raise_for_status()
|
||||
return (self._status_from_subscription(r.json()), sid)
|
||||
|
||||
if sid.startswith("cs_"):
|
||||
r = await self.client.get(f"/v1/checkout/sessions/{sid}")
|
||||
if r.status_code == 404:
|
||||
return ("not_found", sid)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
subscription_id = data.get("subscription")
|
||||
if not subscription_id:
|
||||
return ("pending", sid)
|
||||
|
||||
# Promote to the subscription id
|
||||
r2 = await self.client.get(f"/v1/subscriptions/{subscription_id}")
|
||||
if r2.status_code == 404:
|
||||
return ("not_found", subscription_id)
|
||||
r2.raise_for_status()
|
||||
return (self._status_from_subscription(r2.json()), subscription_id)
|
||||
|
||||
return ("unknown", sid)
|
||||
|
||||
except httpx.HTTPStatusError:
|
||||
return ("error", sid)
|
||||
except Exception:
|
||||
return ("error", sid)
|
||||
|
||||
def _status_from_subscription(self, sub: dict) -> StripeStatus:
|
||||
status = (sub or {}).get("status")
|
||||
if not status:
|
||||
return "unknown"
|
||||
return _STRIPE_STATUS_MAP.get(str(status).lower().strip(), "unknown")
|
||||
|
||||
# ---------- Helpers ----------
|
||||
async def _get_price_id_by_lookup_key(self, lookup_key: str) -> str | None:
|
||||
params = {"active": "true", "expand[]": "data.product", "limit": "1"}
|
||||
qs = urlencode(params) + f"&lookup_keys[]={lookup_key}"
|
||||
r = await self.client.get(f"/v1/prices?{qs}")
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
items = (data or {}).get("data") or []
|
||||
if not items:
|
||||
return None
|
||||
return items[0].get("id")
|
||||
|
||||
async def list_prices_for_product(self, product_id: str) -> list[dict]:
|
||||
qs = urlencode({"product": product_id, "active": "true", "limit": "100"})
|
||||
r = await self.client.get(f"/v1/prices?{qs}")
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
return (data or {}).get("data") or []
|
||||
|
||||
def _normalize_stripe_id(self, checking_id: str) -> str:
|
||||
"""Remove our internal prefix so Stripe sees a real id."""
|
||||
return (
|
||||
checking_id.replace("fiat_stripe_", "", 1)
|
||||
if checking_id.startswith("fiat_stripe_")
|
||||
@@ -412,11 +506,9 @@ class StripeWallet(FiatProvider):
|
||||
)
|
||||
|
||||
def _status_from_checkout_session(self, data: dict) -> FiatPaymentStatus:
|
||||
"""Map a Checkout Session to LNbits fiat status."""
|
||||
if data.get("payment_status") == "paid":
|
||||
return FiatPaymentSuccessStatus()
|
||||
|
||||
# Consider an expired session a fail (existing 24h rule).
|
||||
expires_at = data.get("expires_at")
|
||||
_24h_ago = datetime.now(timezone.utc) - timedelta(hours=24)
|
||||
if expires_at and float(expires_at) < _24h_ago.timestamp():
|
||||
@@ -425,37 +517,18 @@ class StripeWallet(FiatProvider):
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
def _status_from_payment_intent(self, pi: dict) -> FiatPaymentStatus:
|
||||
"""Map a PaymentIntent to LNbits fiat status (card_present friendly)."""
|
||||
status = pi.get("status")
|
||||
|
||||
if status == "succeeded":
|
||||
return FiatPaymentSuccessStatus()
|
||||
|
||||
if status in ("canceled", "payment_failed"):
|
||||
return FiatPaymentFailedStatus()
|
||||
|
||||
if status == "requires_payment_method":
|
||||
if pi.get("last_payment_error"):
|
||||
return FiatPaymentFailedStatus()
|
||||
|
||||
now_ts = datetime.now(timezone.utc).timestamp()
|
||||
created_ts = float(pi.get("created") or now_ts)
|
||||
is_stale = (now_ts - created_ts) > 300
|
||||
if is_stale:
|
||||
if (now_ts - created_ts) > 300:
|
||||
return FiatPaymentFailedStatus()
|
||||
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
def _status_from_invoice(self, invoice: dict) -> FiatPaymentStatus:
|
||||
"""Map an Invoice to LNbits fiat status."""
|
||||
status = invoice.get("status")
|
||||
|
||||
if status == "paid":
|
||||
return FiatPaymentSuccessStatus()
|
||||
|
||||
if status in ["uncollectible", "void"]:
|
||||
return FiatPaymentFailedStatus()
|
||||
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
def _build_headers_form(self) -> dict[str, str]:
|
||||
@@ -466,7 +539,7 @@ class StripeWallet(FiatProvider):
|
||||
) -> list[tuple[str, str]]:
|
||||
out: list[tuple[str, str]] = []
|
||||
for k, v in (md or {}).items():
|
||||
out.append((f"{prefix}[{k}]", str(v or "")))
|
||||
out.append((f"{prefix}[{k}]", str(v)))
|
||||
return out
|
||||
|
||||
def _parse_create_opts(
|
||||
|
||||
+1
-1
@@ -664,10 +664,10 @@ class FundingSourcesSettings(
|
||||
# How long to wait for the payment to be confirmed before returning a pending status
|
||||
# It will not fail the payment, it will make it return pending after the timeout
|
||||
lnbits_funding_source_pay_invoice_wait_seconds: int = Field(default=5, ge=0)
|
||||
funding_source_max_retries: int = Field(default=4, ge=0)
|
||||
|
||||
|
||||
class FiatProvidersSettings(StripeFiatProvider):
|
||||
|
||||
def is_fiat_provider_enabled(self, provider: str | None) -> bool:
|
||||
"""
|
||||
Checks if a specific fiat provider is enabled.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -72,10 +72,6 @@ window.localisation.en = {
|
||||
donate: 'Donate',
|
||||
view_github: 'View on GitHub',
|
||||
voidwallet_active: 'VoidWallet is active! Payments disabled',
|
||||
voidwallet_active_user:
|
||||
'Funding source unavailable. Please contact your admin to configure.',
|
||||
voidwallet_active_admin:
|
||||
'Funding source unavailable. Click here to configure.',
|
||||
service_fee_badge: 'Service fee: {amount} % per transaction',
|
||||
service_fee_max_badge:
|
||||
'Service fee: {amount} % per transaction (max {max} {denom})',
|
||||
@@ -172,8 +168,6 @@ window.localisation.en = {
|
||||
'Only admin accounts can create extensions',
|
||||
admin_only: 'Admin Only',
|
||||
new_version: 'New Version',
|
||||
extension_has_free_release: 'Has free releases',
|
||||
extension_has_paid_release: 'Has paid releases',
|
||||
extension_depends_on: 'Depends on:',
|
||||
extension_rating_soon: 'Ratings coming soon',
|
||||
extension_installed_version: 'Installed version',
|
||||
@@ -669,12 +663,5 @@ window.localisation.en = {
|
||||
callback_success_url_hint:
|
||||
'The user will be redirected to this URL after the payment is successful',
|
||||
connected: 'Connected',
|
||||
not_connected: 'Not Connected',
|
||||
free: 'Free',
|
||||
paid: 'Paid',
|
||||
funding_source_retries: 'Max Retries',
|
||||
funding_source_retries_desc:
|
||||
'Maximum number of retries for funding sources, before it falls back to VoidWallet.',
|
||||
ws_connection_warning:
|
||||
'Lost connection to the websocket. Wait while page tries to reconnect.'
|
||||
not_connected: 'Not Connected'
|
||||
}
|
||||
|
||||
@@ -468,10 +468,7 @@ window.AdminPageLogic = {
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
},
|
||||
formatDate(date) {
|
||||
return moment
|
||||
.utc(date * 1000)
|
||||
.local()
|
||||
.fromNow()
|
||||
return moment.utc(date * 1000).fromNow()
|
||||
},
|
||||
sendTestEmail() {
|
||||
LNbits.api
|
||||
|
||||
+10
-129
@@ -179,79 +179,14 @@ window.LNbits = {
|
||||
},
|
||||
events: {
|
||||
onInvoicePaid(wallet, cb) {
|
||||
const reconnectInterval = 5000 // 5 seconds
|
||||
const maxRetries = 12 // ~60 seconds
|
||||
const websocketUrlFull = `${websocketUrl}/${wallet.inkey}`
|
||||
|
||||
if (!g.activeWebsockets) g.activeWebsockets = {}
|
||||
if (!g.disconnectedWallets) g.disconnectedWallets = new Set()
|
||||
|
||||
const updateConnectionWarning = () => {
|
||||
g.connectionWarning = g.disconnectedWallets.size > 0
|
||||
}
|
||||
|
||||
const connect = (retryCount = 0) => {
|
||||
try {
|
||||
const ws = new WebSocket(websocketUrlFull)
|
||||
g.activeWebsockets[wallet.id] = ws
|
||||
console.debug(
|
||||
`Connecting WebSocket for wallet ${wallet.name}... (attempt ${retryCount + 1})`
|
||||
)
|
||||
|
||||
ws.onopen = () => {
|
||||
console.debug(`WebSocket connected for wallet ${wallet.name}`)
|
||||
g.disconnectedWallets.delete(wallet.id)
|
||||
updateConnectionWarning()
|
||||
}
|
||||
|
||||
ws.onmessage = ev => {
|
||||
try {
|
||||
const data = JSON.parse(ev.data)
|
||||
if (data.payment) cb(data)
|
||||
} catch (err) {
|
||||
console.error('Error parsing WebSocket message:', err)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = err => {
|
||||
console.error(`WebSocket error for wallet ${wallet.name}:`, err)
|
||||
ws.close()
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
g.disconnectedWallets.add(wallet.id)
|
||||
updateConnectionWarning()
|
||||
|
||||
if (retryCount < maxRetries) {
|
||||
console.warn(
|
||||
`WebSocket closed for wallet ${wallet.name}. Reconnecting in ${reconnectInterval / 1000}s...`
|
||||
)
|
||||
setTimeout(() => connect(retryCount + 1), reconnectInterval)
|
||||
} else {
|
||||
console.error(
|
||||
`WebSocket for wallet ${wallet.name} failed after ${maxRetries} attempts. Stopping retries.`
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`WebSocket connection failed for wallet ${wallet.name}:`,
|
||||
err
|
||||
)
|
||||
g.disconnectedWallets.add(wallet.id)
|
||||
updateConnectionWarning()
|
||||
|
||||
if (retryCount < maxRetries) {
|
||||
setTimeout(() => connect(retryCount + 1), reconnectInterval)
|
||||
} else {
|
||||
console.error(
|
||||
`[LNbits] Stopped retrying wallet ${wallet.name} after ${maxRetries} attempts.`
|
||||
)
|
||||
}
|
||||
ws = new WebSocket(`${websocketUrl}/${wallet.inkey}`)
|
||||
ws.onmessage = ev => {
|
||||
const data = JSON.parse(ev.data)
|
||||
if (data.payment) {
|
||||
cb(data)
|
||||
}
|
||||
}
|
||||
|
||||
connect()
|
||||
return ws.onclose
|
||||
}
|
||||
},
|
||||
map: {
|
||||
@@ -332,10 +267,10 @@ window.LNbits = {
|
||||
}
|
||||
|
||||
obj.date = moment.utc(data.created_at).local().format(window.dateFormat)
|
||||
obj.dateFrom = moment.utc(data.created_at).local().fromNow()
|
||||
obj.dateFrom = moment.utc(data.created_at).fromNow()
|
||||
|
||||
obj.expirydate = moment.utc(obj.expiry).local().format(window.dateFormat)
|
||||
obj.expirydateFrom = moment.utc(obj.expiry).local().fromNow()
|
||||
obj.expirydateFrom = moment.utc(obj.expiry).fromNow()
|
||||
obj.msat = obj.amount
|
||||
obj.sat = obj.msat / 1000
|
||||
obj.tag = obj.extra?.tag
|
||||
@@ -524,9 +459,7 @@ if (!window.g) {
|
||||
langs: [],
|
||||
walletEventListeners: [],
|
||||
updatePayments: false,
|
||||
updatePaymentsHash: '',
|
||||
wsDisconnectionNotification: false,
|
||||
wsReconnectedNotification: false
|
||||
updatePaymentsHash: ''
|
||||
})
|
||||
}
|
||||
|
||||
@@ -561,8 +494,7 @@ window.windowMixin = {
|
||||
bgimageChoice: this.$q.localStorage.has('lnbits.backgroundImage')
|
||||
? this.$q.localStorage.getItem('lnbits.backgroundImage')
|
||||
: USE_DEFAULT_BGIMAGE,
|
||||
...WINDOW_SETTINGS,
|
||||
silentConnectionCleanup: false
|
||||
...WINDOW_SETTINGS
|
||||
}
|
||||
},
|
||||
|
||||
@@ -870,57 +802,6 @@ window.windowMixin = {
|
||||
mounted() {
|
||||
if (this.g.user) {
|
||||
this.paymentEvents()
|
||||
|
||||
// Retry timer reference (for cleanup)
|
||||
this.connectionRetryTimer = setInterval(() => {
|
||||
if (this.g.disconnectedWallets && this.g.disconnectedWallets.size > 0) {
|
||||
console.warn('Some wallets are still disconnected. Retrying...')
|
||||
this.paymentEvents()
|
||||
}
|
||||
}, 60000)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.silentConnectionCleanup = true
|
||||
// Prevent background timers when component is destroyed
|
||||
if (this.connectionRetryTimer) {
|
||||
clearInterval(this.connectionRetryTimer)
|
||||
this.connectionRetryTimer = null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'g.connectionWarning': {
|
||||
handler(newVal, oldVal) {
|
||||
if (this.silentConnectionCleanup) return
|
||||
|
||||
if (newVal && !g.wsDisconnectionNotification) {
|
||||
this.$q.notify({
|
||||
message: 'Some wallet connections are lost. Retrying...',
|
||||
color: 'negative',
|
||||
icon: null,
|
||||
position: 'top',
|
||||
timeout: 5000
|
||||
})
|
||||
g.wsDisconnectionNotification = true
|
||||
g.wsReconnectedNotification = false
|
||||
} else if (
|
||||
oldVal === true &&
|
||||
newVal === false &&
|
||||
!g.wsReconnectedNotification
|
||||
) {
|
||||
this.$q.notify({
|
||||
message: 'All wallet connections restored.',
|
||||
color: 'positive',
|
||||
icon: null,
|
||||
position: 'top',
|
||||
timeout: 3000
|
||||
})
|
||||
g.wsReconnectedNotification = true
|
||||
g.wsDisconnectionNotification = false
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +100,7 @@ window.app.component('lnbits-funding-sources', {
|
||||
lnd_grpc_endpoint: 'Endpoint',
|
||||
lnd_grpc_cert: 'Certificate',
|
||||
lnd_grpc_port: 'Port',
|
||||
lnd_grpc_macaroon: 'GRPC Macaroon',
|
||||
lnd_grpc_invoice_macaroon: 'GRPC Invoice Macaroon',
|
||||
lnd_grpc_admin_macaroon: 'GRPC Admin Macaroon',
|
||||
lnd_grpc_admin_macaroon: 'Admin Macaroon',
|
||||
lnd_grpc_macaroon_encrypted: 'Encrypted Macaroon'
|
||||
}
|
||||
],
|
||||
|
||||
@@ -154,7 +154,7 @@ function confettiStars() {
|
||||
setTimeout(shoot, 200)
|
||||
}
|
||||
!(function (t, e) {
|
||||
;(!(function t(e, n, a, i) {
|
||||
!(function t(e, n, a, i) {
|
||||
var o = !!(
|
||||
e.Worker &&
|
||||
e.Blob &&
|
||||
@@ -248,12 +248,12 @@ function confettiStars() {
|
||||
function e(e, n) {
|
||||
t.postMessage({options: e || {}, callback: n})
|
||||
}
|
||||
;((t.init = function (e) {
|
||||
;(t.init = function (e) {
|
||||
var n = e.transferControlToOffscreen()
|
||||
t.postMessage({canvas: n}, [n])
|
||||
}),
|
||||
(t.fire = function (n, a, i) {
|
||||
if (g) return (e(n, null), g)
|
||||
if (g) return e(n, null), g
|
||||
var o = Math.random().toString(36).slice(2)
|
||||
return (g = l(function (a) {
|
||||
function r(e) {
|
||||
@@ -264,15 +264,15 @@ function confettiStars() {
|
||||
i(),
|
||||
a())
|
||||
}
|
||||
;(t.addEventListener('message', r),
|
||||
t.addEventListener('message', r),
|
||||
e(n, o),
|
||||
(m[o] = r.bind(null, {data: {callback: o}})))
|
||||
(m[o] = r.bind(null, {data: {callback: o}}))
|
||||
}))
|
||||
}),
|
||||
(t.reset = function () {
|
||||
for (var e in (t.postMessage({reset: !0}), m))
|
||||
(m[e](), delete m[e])
|
||||
}))
|
||||
m[e](), delete m[e]
|
||||
})
|
||||
})(h)
|
||||
}
|
||||
return h
|
||||
@@ -328,12 +328,12 @@ function confettiStars() {
|
||||
)
|
||||
}
|
||||
function k(t) {
|
||||
;((t.width = document.documentElement.clientWidth),
|
||||
(t.height = document.documentElement.clientHeight))
|
||||
;(t.width = document.documentElement.clientWidth),
|
||||
(t.height = document.documentElement.clientHeight)
|
||||
}
|
||||
function I(t) {
|
||||
var e = t.getBoundingClientRect()
|
||||
;((t.width = e.width), (t.height = e.height))
|
||||
;(t.width = e.width), (t.height = e.height)
|
||||
}
|
||||
function T(t, e, n, o, r) {
|
||||
var c,
|
||||
@@ -342,10 +342,10 @@ function confettiStars() {
|
||||
d = t.getContext('2d'),
|
||||
f = l(function (e) {
|
||||
function l() {
|
||||
;((c = s = null), d.clearRect(0, 0, o.width, o.height), r(), e())
|
||||
;(c = s = null), d.clearRect(0, 0, o.width, o.height), r(), e()
|
||||
}
|
||||
;((c = b.frame(function e() {
|
||||
;(!a ||
|
||||
;(c = b.frame(function e() {
|
||||
!a ||
|
||||
(o.width === i.width && o.height === i.height) ||
|
||||
((o.width = t.width = i.width), (o.height = t.height = i.height)),
|
||||
o.width ||
|
||||
@@ -354,7 +354,7 @@ function confettiStars() {
|
||||
d.clearRect(0, 0, o.width, o.height),
|
||||
(u = u.filter(function (t) {
|
||||
return (function (t, e) {
|
||||
;((e.x += Math.cos(e.angle2D) * e.velocity + e.drift),
|
||||
;(e.x += Math.cos(e.angle2D) * e.velocity + e.drift),
|
||||
(e.y += Math.sin(e.angle2D) * e.velocity + e.gravity),
|
||||
(e.wobble += 0.1),
|
||||
(e.velocity *= e.decay),
|
||||
@@ -363,7 +363,7 @@ function confettiStars() {
|
||||
(e.tiltCos = Math.cos(e.tiltAngle)),
|
||||
(e.random = Math.random() + 5),
|
||||
(e.wobbleX = e.x + 10 * e.scalar * Math.cos(e.wobble)),
|
||||
(e.wobbleY = e.y + 10 * e.scalar * Math.sin(e.wobble)))
|
||||
(e.wobbleY = e.y + 10 * e.scalar * Math.sin(e.wobble))
|
||||
var n = e.tick++ / e.totalTicks,
|
||||
a = e.x + e.random * e.tiltCos,
|
||||
i = e.y + e.random * e.tiltSin,
|
||||
@@ -393,12 +393,12 @@ function confettiStars() {
|
||||
2 * Math.PI
|
||||
)
|
||||
: (function (t, e, n, a, i, o, r, l, c) {
|
||||
;(t.save(),
|
||||
t.save(),
|
||||
t.translate(e, n),
|
||||
t.rotate(o),
|
||||
t.scale(a, i),
|
||||
t.arc(0, 0, 1, r, l, c),
|
||||
t.restore())
|
||||
t.restore()
|
||||
})(
|
||||
t,
|
||||
e.x,
|
||||
@@ -420,18 +420,18 @@ function confettiStars() {
|
||||
})(d, t)
|
||||
})).length
|
||||
? (c = b.frame(e))
|
||||
: l())
|
||||
: l()
|
||||
})),
|
||||
(s = l))
|
||||
(s = l)
|
||||
})
|
||||
return {
|
||||
addFettis: function (t) {
|
||||
return ((u = u.concat(t)), f)
|
||||
return (u = u.concat(t)), f
|
||||
},
|
||||
canvas: t,
|
||||
promise: f,
|
||||
reset: function () {
|
||||
;(c && b.cancel(c), s && s())
|
||||
c && b.cancel(c), s && s()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,7 +466,7 @@ function confettiStars() {
|
||||
k = p(e, 'scalar'),
|
||||
I = (function (t) {
|
||||
var e = p(t, 'origin', Object)
|
||||
return ((e.x = p(e, 'x', Number)), (e.y = p(e, 'y', Number)), e)
|
||||
return (e.x = p(e, 'x', Number)), (e.y = p(e, 'y', Number)), e
|
||||
})(e),
|
||||
E = d,
|
||||
S = [],
|
||||
@@ -531,7 +531,7 @@ function confettiStars() {
|
||||
return l(function (t) {
|
||||
t()
|
||||
})
|
||||
;(i && a
|
||||
i && a
|
||||
? (t = a.canvas)
|
||||
: i &&
|
||||
!t &&
|
||||
@@ -547,7 +547,7 @@ function confettiStars() {
|
||||
)
|
||||
})(g)),
|
||||
document.body.appendChild(t)),
|
||||
r && !d && u(t))
|
||||
r && !d && u(t)
|
||||
var m = {width: t.width, height: t.height}
|
||||
function b() {
|
||||
if (s) {
|
||||
@@ -564,9 +564,9 @@ function confettiStars() {
|
||||
m.width = m.height = null
|
||||
}
|
||||
function v() {
|
||||
;((a = null),
|
||||
;(a = null),
|
||||
r && e.removeEventListener('resize', b),
|
||||
i && t && (document.body.removeChild(t), (t = null), (d = !1)))
|
||||
i && t && (document.body.removeChild(t), (t = null), (d = !1))
|
||||
}
|
||||
return (
|
||||
s && !d && s.init(t),
|
||||
@@ -578,13 +578,12 @@ function confettiStars() {
|
||||
}
|
||||
return (
|
||||
(g.reset = function () {
|
||||
;(s && s.reset(), a && a.reset())
|
||||
s && s.reset(), a && a.reset()
|
||||
}),
|
||||
g
|
||||
)
|
||||
}
|
||||
;((n.exports = E(null, {useWorker: !0, resize: !0})),
|
||||
(n.exports.create = E))
|
||||
;(n.exports = E(null, {useWorker: !0, resize: !0})), (n.exports.create = E)
|
||||
})(
|
||||
(function () {
|
||||
return void 0 !== t ? t : 'undefined' != typeof self ? self : this || {}
|
||||
@@ -592,5 +591,5 @@ function confettiStars() {
|
||||
e,
|
||||
!1
|
||||
),
|
||||
(t.confetti = e.exports))
|
||||
(t.confetti = e.exports)
|
||||
})(window, {})
|
||||
|
||||
@@ -600,7 +600,7 @@ window.app.component('lnbits-date', {
|
||||
return LNbits.utils.formatDate(this.ts)
|
||||
},
|
||||
dateFrom() {
|
||||
return moment.utc(this.date).local().fromNow()
|
||||
return moment.utc(this.date).fromNow()
|
||||
}
|
||||
},
|
||||
template: `
|
||||
|
||||
@@ -164,7 +164,7 @@ window.PaymentsPageLogic = {
|
||||
if (p.extra && p.extra.tag) {
|
||||
p.tag = p.extra.tag
|
||||
}
|
||||
p.timeFrom = moment.utc(p.created_at).local().fromNow()
|
||||
p.timeFrom = moment.utc(p.created_at).fromNow()
|
||||
p.outgoing = p.amount < 0
|
||||
p.amount =
|
||||
new Intl.NumberFormat(window.LOCALE).format(p.amount / 1000) +
|
||||
|
||||
@@ -333,8 +333,8 @@ window.UsersPageLogic = {
|
||||
const url = `${window.location.origin}/wallet?usr=${this.activeWallet.userId}&wal=${walletId}`
|
||||
this.copyText(url)
|
||||
},
|
||||
|
||||
fetchUsers(props) {
|
||||
this.relaxFilterForFields(['username', 'email'])
|
||||
const params = LNbits.utils.prepareFilterQuery(this.usersTable, props)
|
||||
LNbits.api
|
||||
.request('GET', `/users/api/v1/user?${params}`)
|
||||
@@ -355,34 +355,6 @@ window.UsersPageLogic = {
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
},
|
||||
relaxFilterForFields(fieldNames = []) {
|
||||
fieldNames.forEach(fieldName => {
|
||||
const fieldValue = this.usersTable?.filter?.[fieldName]
|
||||
if (fieldValue) {
|
||||
if (this.usersTable.filter[fieldName]) {
|
||||
this.usersTable.filter[`${fieldName}[like]`] = fieldValue
|
||||
delete this.usersTable.filter[fieldName]
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateWallet(userWallet) {
|
||||
LNbits.api
|
||||
.request('PATCH', '/api/v1/wallet', userWallet.adminkey, {
|
||||
name: userWallet.name
|
||||
})
|
||||
.then(() => {
|
||||
userWallet.editable = false
|
||||
Quasar.Notify.create({
|
||||
message: 'Wallet name updated.',
|
||||
type: 'positive',
|
||||
timeout: 3500
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
})
|
||||
},
|
||||
|
||||
toggleAdmin(userId) {
|
||||
LNbits.api
|
||||
|
||||
@@ -179,7 +179,7 @@ window.WalletPageLogic = {
|
||||
methods: {
|
||||
dateFromNow(unix) {
|
||||
const date = new Date(unix * 1000)
|
||||
return moment.utc(date).local().fromNow()
|
||||
return moment.utc(date).fromNow()
|
||||
},
|
||||
formatFiatAmount(amount, currency) {
|
||||
this.update.currency = currency
|
||||
@@ -476,14 +476,8 @@ window.WalletPageLogic = {
|
||||
createdDate,
|
||||
'YYYY-MM-DDTHH:mm:ss.SSSZ'
|
||||
)
|
||||
cleanInvoice.expireDateFrom = moment
|
||||
.utc(expireDate)
|
||||
.local()
|
||||
.fromNow()
|
||||
cleanInvoice.createdDateFrom = moment
|
||||
.utc(createdDate)
|
||||
.local()
|
||||
.fromNow()
|
||||
cleanInvoice.expireDateFrom = moment.utc(expireDate).fromNow()
|
||||
cleanInvoice.createdDateFrom = moment.utc(createdDate).fromNow()
|
||||
|
||||
cleanInvoice.expired = false // TODO
|
||||
}
|
||||
|
||||
@@ -23,57 +23,6 @@
|
||||
.wallet-list-card:first-child {
|
||||
margin-left: 1px;
|
||||
}
|
||||
/* Force chip content to wrap on mobile */
|
||||
@media (max-width: 1023px) {
|
||||
.q-chip {
|
||||
height: auto !important;
|
||||
min-height: 2rem !important;
|
||||
}
|
||||
.q-chip .q-chip__content {
|
||||
white-space: normal !important;
|
||||
word-break: break-all !important;
|
||||
min-height: 1.5rem !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.q-chip .ellipsis {
|
||||
overflow: visible !important;
|
||||
text-overflow: clip !important;
|
||||
white-space: normal !important;
|
||||
word-break: break-all !important;
|
||||
}
|
||||
/* Fix q-input hint text wrapping and container growth */
|
||||
.q-field {
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
min-height: 56px !important;
|
||||
}
|
||||
.q-field__inner {
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.q-field__control {
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.q-field__bottom {
|
||||
position: relative !important;
|
||||
transform: none !important;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
.q-field__messages {
|
||||
min-height: auto !important;
|
||||
}
|
||||
.q-field__messages,
|
||||
.q-field__messages > div {
|
||||
white-space: normal !important;
|
||||
word-wrap: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
word-break: break-word !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<title>{% block title %}{{ SITE_TITLE }}{% endblock %}</title>
|
||||
<meta charset="utf-8" />
|
||||
@@ -92,22 +41,6 @@
|
||||
<div id="vue">
|
||||
<q-layout view="hHh lpR lfr" v-cloak>
|
||||
<q-header bordered class="bg-marginal-bg">
|
||||
<!-- VoidWallet Warning Banner -->
|
||||
{% if VOIDWALLET %}
|
||||
<q-banner v-if="g.user" class="bg-warning text-white" dense>
|
||||
<template v-slot:avatar>
|
||||
<q-icon name="warning" color="white" />
|
||||
</template>
|
||||
<a
|
||||
v-if="g.user && (g.user.super_user || g.user.admin)"
|
||||
href="/admin"
|
||||
style="color: white; text-decoration: underline; cursor: pointer"
|
||||
v-text="$t('voidwallet_active_admin')"
|
||||
></a>
|
||||
<span v-else v-text="$t('voidwallet_active_user')"></span>
|
||||
</q-banner>
|
||||
{% endif %}
|
||||
|
||||
<q-toolbar>
|
||||
{% block drawer_toggle %}
|
||||
<q-btn
|
||||
@@ -129,23 +62,16 @@
|
||||
>{% endblock %} {% block toolbar_subtitle %}
|
||||
<q-badge v-if="g.user && g.user.super_user">Super User</q-badge>
|
||||
<q-badge v-else-if="g.user && g.user.admin">Admin User</q-badge>
|
||||
<q-badge
|
||||
v-if="g.connectionWarning"
|
||||
color="red"
|
||||
class="inline-block q-ml-sm"
|
||||
>
|
||||
<q-icon
|
||||
name="wifi_tethering_off"
|
||||
color="white"
|
||||
size="12px"
|
||||
></q-icon>
|
||||
<q-tooltip
|
||||
><span v-text="$t('ws_connection_warning')"></span
|
||||
></q-tooltip>
|
||||
</q-badge>
|
||||
{% endblock %}
|
||||
</q-toolbar-title>
|
||||
{% block beta %} {% if LNBITS_CUSTOM_BADGE is not none and
|
||||
{% block beta %} {% if VOIDWALLET %}
|
||||
<q-badge
|
||||
v-text="$t('voidwallet_active')"
|
||||
color="red"
|
||||
class="q-mr-md gt-md"
|
||||
>
|
||||
</q-badge>
|
||||
{%endif%} {% if LNBITS_CUSTOM_BADGE is not none and
|
||||
LNBITS_CUSTOM_BADGE != "" %}
|
||||
<q-badge
|
||||
v-show="$q.screen.gt.sm"
|
||||
|
||||
@@ -649,7 +649,8 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="showButtons"
|
||||
class="qrcode__buttons row q-gutter-x-sm items-center justify-end no-wrap full-width"
|
||||
class="qrcode__buttons row q-gutter-x-sm"
|
||||
style="justify-content: flex-end"
|
||||
>
|
||||
<q-btn
|
||||
v-if="nfc && nfcSupported"
|
||||
|
||||
+20
-16
@@ -4,7 +4,7 @@ import json
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import coincurve
|
||||
import secp256k1
|
||||
from bech32 import bech32_decode, bech32_encode, convertbits
|
||||
from Cryptodome import Random
|
||||
from Cryptodome.Cipher import AES
|
||||
@@ -19,22 +19,22 @@ def generate_keypair() -> tuple[str, str]:
|
||||
|
||||
|
||||
def encrypt_content(
|
||||
content: str, service_pubkey: coincurve.PublicKey, account_private_key_hex: str
|
||||
content: str, service_pubkey: secp256k1.PublicKey, account_private_key_hex: str
|
||||
) -> str:
|
||||
"""
|
||||
Encrypts the content to be sent to the service.
|
||||
|
||||
Args:
|
||||
content (str): The content to be encrypted.
|
||||
service_pubkey (coincurve.PublicKey): The service provider's public key.
|
||||
service_pubkey (secp256k1.PublicKey): The service provider's public key.
|
||||
account_private_key_hex (str): The account private key in hex format.
|
||||
|
||||
Returns:
|
||||
str: The encrypted content.
|
||||
"""
|
||||
shared = service_pubkey.multiply(bytes.fromhex(account_private_key_hex)).format()[
|
||||
1:
|
||||
]
|
||||
shared = service_pubkey.tweak_mul(
|
||||
bytes.fromhex(account_private_key_hex)
|
||||
).serialize()[1:]
|
||||
# random iv (16B)
|
||||
iv = Random.new().read(AES.block_size)
|
||||
aes = AES.new(shared, AES.MODE_CBC, iv)
|
||||
@@ -52,22 +52,22 @@ def encrypt_content(
|
||||
|
||||
|
||||
def decrypt_content(
|
||||
content: str, service_pubkey: coincurve.PublicKey, account_private_key_hex: str
|
||||
content: str, service_pubkey: secp256k1.PublicKey, account_private_key_hex: str
|
||||
) -> str:
|
||||
"""
|
||||
Decrypts the content coming from the service.
|
||||
|
||||
Args:
|
||||
content (str): The encrypted content.
|
||||
service_pubkey (coincurve.PublicKey): The service provider's public key.
|
||||
service_pubkey (secp256k1.PublicKey): The service provider's public key.
|
||||
account_private_key_hex (str): The account private key in hex format.
|
||||
|
||||
Returns:
|
||||
str: The decrypted content.
|
||||
"""
|
||||
shared = service_pubkey.multiply(bytes.fromhex(account_private_key_hex)).format()[
|
||||
1:
|
||||
]
|
||||
shared = service_pubkey.tweak_mul(
|
||||
bytes.fromhex(account_private_key_hex)
|
||||
).serialize()[1:]
|
||||
# extract iv and content
|
||||
(encrypted_content_b64, iv_b64) = content.split("?iv=")
|
||||
encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii"))
|
||||
@@ -105,14 +105,16 @@ def verify_event(event: dict) -> bool:
|
||||
if event_id != event["id"]:
|
||||
return False
|
||||
pubkey_hex = event["pubkey"]
|
||||
pubkey = coincurve.PublicKeyXOnly(bytes.fromhex(pubkey_hex))
|
||||
if not pubkey.verify(bytes.fromhex(event["sig"]), bytes.fromhex(event_id)):
|
||||
pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey_hex), True)
|
||||
if not pubkey.schnorr_verify(
|
||||
bytes.fromhex(event_id), bytes.fromhex(event["sig"]), None, raw=True
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def sign_event(
|
||||
event: dict, account_public_key_hex: str, account_private_key: coincurve.PrivateKey
|
||||
event: dict, account_public_key_hex: str, account_private_key: secp256k1.PrivateKey
|
||||
) -> dict:
|
||||
"""
|
||||
Signs the event (in place) with the service secret
|
||||
@@ -120,7 +122,7 @@ def sign_event(
|
||||
Args:
|
||||
event (Dict): The event to be signed.
|
||||
account_public_key_hex (str): The account public key in hex format.
|
||||
account_private_key (coincurve.PrivateKey): The account private key.
|
||||
account_private_key (secp256k1.PrivateKey): The account private key.
|
||||
|
||||
Returns:
|
||||
Dict: The input event with the signature added.
|
||||
@@ -139,7 +141,9 @@ def sign_event(
|
||||
event["id"] = event_id
|
||||
event["pubkey"] = account_public_key_hex
|
||||
|
||||
signature = account_private_key.sign_schnorr(bytes.fromhex(event_id)).hex()
|
||||
signature = (
|
||||
account_private_key.schnorr_sign(bytes.fromhex(event_id), None, raw=True)
|
||||
).hex()
|
||||
event["sig"] = signature
|
||||
return event
|
||||
|
||||
|
||||
@@ -84,7 +84,9 @@ class LndWallet(Wallet):
|
||||
macaroon = (
|
||||
settings.lnd_grpc_macaroon
|
||||
or settings.lnd_grpc_admin_macaroon
|
||||
or settings.lnd_admin_macaroon
|
||||
or settings.lnd_grpc_invoice_macaroon
|
||||
or settings.lnd_invoice_macaroon
|
||||
)
|
||||
encrypted_macaroon = settings.lnd_grpc_macaroon_encrypted
|
||||
try:
|
||||
|
||||
@@ -7,8 +7,8 @@ from collections.abc import AsyncGenerator
|
||||
from typing import cast
|
||||
from urllib.parse import parse_qs, unquote, urlparse
|
||||
|
||||
import secp256k1
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from coincurve import PrivateKey, PublicKey
|
||||
from loguru import logger
|
||||
from websockets import connect as ws_connect
|
||||
|
||||
@@ -306,15 +306,15 @@ class NWCConnection:
|
||||
# Parse pairing url (if invalid an exception is raised)
|
||||
|
||||
# Extract keys (used to sign nwc events+identify NWC user)
|
||||
self.account_private_key = PrivateKey(bytes.fromhex(secret))
|
||||
self.account_private_key = secp256k1.PrivateKey(bytes.fromhex(secret))
|
||||
self.account_private_key_hex = secret
|
||||
self.account_public_key = self.account_private_key.public_key
|
||||
self.account_public_key = self.account_private_key.pubkey
|
||||
if not self.account_public_key:
|
||||
raise ValueError("Missing account public key")
|
||||
self.account_public_key_hex = self.account_public_key.format().hex()[2:]
|
||||
self.account_public_key_hex = self.account_public_key.serialize().hex()[2:]
|
||||
|
||||
# Extract service key (used for encryption to identify the nwc service provider)
|
||||
self.service_pubkey = PublicKey(bytes.fromhex("02" + pubkey))
|
||||
self.service_pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey), True)
|
||||
self.service_pubkey_hex = pubkey
|
||||
|
||||
# Extract relay url
|
||||
|
||||
+15
-54
@@ -1,6 +1,4 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from decimal import Decimal
|
||||
@@ -112,17 +110,8 @@ class StrikeWallet(Wallet):
|
||||
),
|
||||
)
|
||||
|
||||
# runtime state
|
||||
self.pending_invoices: list[str] = [] # Keep it as a list
|
||||
# path for persisting pending invoices
|
||||
self.state_path = os.path.join(
|
||||
settings.lnbits_data_folder, "strike_pending_invoices.json"
|
||||
)
|
||||
# load persisted pending invoices
|
||||
try:
|
||||
with open(self.state_path) as f:
|
||||
self.pending_invoices = json.load(f)
|
||||
except Exception:
|
||||
self.pending_invoices = []
|
||||
self.pending_payments: dict[str, str] = {}
|
||||
self.failed_payments: dict[str, str] = {}
|
||||
|
||||
@@ -131,13 +120,6 @@ class StrikeWallet(Wallet):
|
||||
self._cached_balance_ts: float = 0.0
|
||||
self._cache_ttl = 30 # seconds
|
||||
|
||||
def _persist_pending(self):
|
||||
try:
|
||||
with open(self.state_path, "w") as f:
|
||||
json.dump(self.pending_invoices, f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not persist pending invoices: {e}")
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
try:
|
||||
await self.client.aclose()
|
||||
@@ -253,7 +235,6 @@ class StrikeWallet(Wallet):
|
||||
)
|
||||
|
||||
self.pending_invoices.append(invoice_id)
|
||||
self._persist_pending()
|
||||
return InvoiceResponse(
|
||||
ok=True, checking_id=invoice_id, payment_request=bolt11
|
||||
)
|
||||
@@ -390,36 +371,15 @@ class StrikeWallet(Wallet):
|
||||
logger.debug(f"Error while fetching payment {checking_id}.")
|
||||
return PaymentPendingStatus()
|
||||
|
||||
async def _get_invoices_status_batch(
|
||||
self, invoice_ids: list[str]
|
||||
) -> dict[str, PaymentStatus]:
|
||||
out: dict[str, PaymentStatus] = {}
|
||||
if not invoice_ids:
|
||||
return out
|
||||
ids_list = ",".join(f"'{i}'" for i in invoice_ids)
|
||||
filter_expr = f"receiveRequestId in ({ids_list})"
|
||||
params = {"$filter": filter_expr, "$top": len(invoice_ids)}
|
||||
r = await self._get("/receive-requests/receives", params=params)
|
||||
r.raise_for_status()
|
||||
items = r.json().get("items") or r.json().get("value") or []
|
||||
completed = {item.get("receiveRequestId") for item in items}
|
||||
for inv in invoice_ids:
|
||||
out[inv] = (
|
||||
PaymentSuccessStatus(fee_msat=0)
|
||||
if inv in completed
|
||||
else PaymentPendingStatus()
|
||||
)
|
||||
return out
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
"""
|
||||
Poll Strike for invoice settlement while respecting the documented API limits.
|
||||
|
||||
Uses dynamic adjustment of polling frequency based on activity.
|
||||
"""
|
||||
min_poll, max_poll = 0.2, 3 # Increase polling frequency (was 1, 15)
|
||||
min_poll, max_poll = 1, 15
|
||||
# 1,000 requests / 10 minutes = ~100 requests/minute.
|
||||
rate_limit = 250
|
||||
rate_limit = 100
|
||||
sleep_s = min_poll
|
||||
# Main loop for polling invoices.
|
||||
self._running = True
|
||||
@@ -431,18 +391,19 @@ class StrikeWallet(Wallet):
|
||||
req_budget = max(
|
||||
1, rate_limit * sleep_s // 60
|
||||
) # Calculate request budget based on sleep time.
|
||||
batch = list(self.pending_invoices)[: int(req_budget)]
|
||||
processed = 0
|
||||
if batch:
|
||||
statuses = await self._get_invoices_status_batch(batch)
|
||||
processed = 1
|
||||
for inv, status in statuses.items():
|
||||
if status.success or status.failed:
|
||||
self.pending_invoices.remove(inv)
|
||||
self._persist_pending()
|
||||
if status.success:
|
||||
had_activity = True
|
||||
yield inv
|
||||
|
||||
for inv in list(self.pending_invoices):
|
||||
if processed >= req_budget: # If request budget is exhausted.
|
||||
break
|
||||
status = await self.get_invoice_status(inv)
|
||||
processed += 1
|
||||
|
||||
if status.success or status.failed:
|
||||
self.pending_invoices.remove(inv)
|
||||
if status.success:
|
||||
had_activity = True
|
||||
yield inv
|
||||
|
||||
# Dynamic adjustment of polling frequency based on activity.
|
||||
sleep_s = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ pkgs, flake }:
|
||||
{ pkgs, makeTest, inputs }:
|
||||
{
|
||||
vmTest = import ./nixos-module { inherit pkgs flake; };
|
||||
vmTest = import ./nixos-module { inherit pkgs makeTest inputs; };
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
{ pkgs, flake }:
|
||||
pkgs.nixosTest {
|
||||
{ pkgs, makeTest, inputs }:
|
||||
makeTest {
|
||||
name = "lnbits-nixos-module";
|
||||
nodes = {
|
||||
client = { config, pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.curl ];
|
||||
};
|
||||
lnbits = { ... }: {
|
||||
imports = [ flake.nixosModules.${pkgs.system}.default ];
|
||||
imports = [ inputs.self.nixosModules.default ];
|
||||
services.lnbits = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
host = "0.0.0.0";
|
||||
env = {
|
||||
LNBITS_ADMIN_UI = "false";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -22,10 +19,8 @@ pkgs.nixosTest {
|
||||
lnbits.wait_for_open_port(${toString nodes.lnbits.config.services.lnbits.port})
|
||||
client.wait_for_unit("multi-user.target")
|
||||
with subtest("Check that the lnbits webserver can be reached."):
|
||||
output = client.succeed(
|
||||
"curl -sSf http://lnbits:8231/ | grep title | head -n1"
|
||||
assert "<title>LNbits</title>" in client.succeed(
|
||||
"curl -sSf http:/lnbits:8231/ | grep title"
|
||||
)
|
||||
|
||||
assert "<title>LNbits</title>" in output;
|
||||
'';
|
||||
}
|
||||
|
||||
+5
-6
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "lnbits"
|
||||
version = "1.3.1"
|
||||
version = "1.3.0-rc8"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||
@@ -64,10 +64,10 @@ breez = ["breez-sdk==0.8.0", "breez-sdk-liquid==0.9.1"]
|
||||
liquid = ["wallycore==1.4.0"]
|
||||
migration = ["psycopg2-binary==2.9.10"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"black>=25.1.0,<26.0.0",
|
||||
"mypy==1.17.1",
|
||||
"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",
|
||||
@@ -221,8 +221,7 @@ extend-exclude = [
|
||||
select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B", "S"]
|
||||
# UP007: pyupgrade: use X | Y instead of Optional. (python3.10)
|
||||
# RUF012: mutable-class-default
|
||||
# RUF043: pytest-raises-ambiguous-pattern (RUF043)
|
||||
ignore = ["RUF012", "RUF043"]
|
||||
ignore = ["RUF012"]
|
||||
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
|
||||
@@ -370,7 +370,7 @@ async def test_pay_invoice_adminkey(client, invoice, adminkey_headers_from):
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_payments(client, inkey_fresh_headers_to, fake_payments):
|
||||
_, filters = fake_payments
|
||||
fake_data, filters = fake_payments
|
||||
|
||||
async def get_payments(params: dict):
|
||||
response = await client.get(
|
||||
|
||||
+13
-13
@@ -6,8 +6,8 @@ from uuid import uuid4
|
||||
|
||||
import jwt
|
||||
import pytest
|
||||
import secp256k1
|
||||
import shortuuid
|
||||
from coincurve import PrivateKey
|
||||
from httpx import AsyncClient
|
||||
|
||||
from lnbits.core.crud.users import (
|
||||
@@ -42,11 +42,11 @@ nostr_event = {
|
||||
"sig": "fb7eb47fa8355747f6837e55620103d73ba47b2c3164ab8319d2f164022a9f25"
|
||||
"6e00ecda7d3c8945f07b7d6ecc18cfff34c07bc99677309e2b9310d9fc1bb138",
|
||||
}
|
||||
private_key = PrivateKey(
|
||||
private_key = secp256k1.PrivateKey(
|
||||
bytes.fromhex("6e00ecda7d3c8945f07b7d6ecc18cfff34c07bc99677309e2b9310d9fc1bb138")
|
||||
)
|
||||
assert private_key.public_key, "Pubkey not created."
|
||||
pubkey_hex = private_key.public_key.format().hex()[2:]
|
||||
assert private_key.pubkey, "Pubkey not created."
|
||||
pubkey_hex = private_key.pubkey.serialize().hex()[2:]
|
||||
|
||||
|
||||
################################ LOGIN ################################
|
||||
@@ -552,9 +552,9 @@ async def test_register_nostr_ok(http_client: AsyncClient, settings: Settings):
|
||||
event = {**nostr_event}
|
||||
event["created_at"] = int(time.time())
|
||||
|
||||
private_key = PrivateKey(bytes.fromhex(os.urandom(32).hex()))
|
||||
assert private_key.public_key, "Pubkey not created."
|
||||
pubkey_hex = private_key.public_key.format().hex()[2:]
|
||||
private_key = secp256k1.PrivateKey(bytes.fromhex(os.urandom(32).hex()))
|
||||
assert private_key.pubkey, "Pubkey not created."
|
||||
pubkey_hex = private_key.pubkey.serialize().hex()[2:]
|
||||
event_signed = sign_event(event, pubkey_hex, private_key)
|
||||
base64_event = base64.b64encode(json.dumps(event_signed).encode()).decode("ascii")
|
||||
response = await http_client.post(
|
||||
@@ -759,9 +759,9 @@ async def test_change_pubkey_npub_ok(http_client: AsyncClient, settings: Setting
|
||||
payload: dict = jwt.decode(access_token, settings.auth_secret_key, ["HS256"])
|
||||
access_token_payload = AccessTokenPayload(**payload)
|
||||
|
||||
private_key = PrivateKey(bytes.fromhex(os.urandom(32).hex()))
|
||||
assert private_key.public_key, "Pubkey not created."
|
||||
pubkey_hex = private_key.public_key.format().hex()[2:]
|
||||
private_key = secp256k1.PrivateKey(bytes.fromhex(os.urandom(32).hex()))
|
||||
assert private_key.pubkey, "Pubkey not created."
|
||||
pubkey_hex = private_key.pubkey.serialize().hex()[2:]
|
||||
npub = hex_to_npub(pubkey_hex)
|
||||
|
||||
response = await http_client.put(
|
||||
@@ -802,9 +802,9 @@ async def test_change_pubkey_ok(
|
||||
payload: dict = jwt.decode(access_token, settings.auth_secret_key, ["HS256"])
|
||||
access_token_payload = AccessTokenPayload(**payload)
|
||||
|
||||
private_key = PrivateKey(bytes.fromhex(os.urandom(32).hex()))
|
||||
assert private_key.public_key, "Pubkey not created."
|
||||
pubkey_hex = private_key.public_key.format().hex()[2:]
|
||||
private_key = secp256k1.PrivateKey(bytes.fromhex(os.urandom(32).hex()))
|
||||
assert private_key.pubkey, "Pubkey not created."
|
||||
pubkey_hex = private_key.pubkey.serialize().hex()[2:]
|
||||
|
||||
response = await http_client.put(
|
||||
"/api/v1/auth/pubkey",
|
||||
|
||||
@@ -30,17 +30,6 @@ async def test_create_wallet_and_delete_wallet(app, to_user):
|
||||
|
||||
# check if wallet is deleted
|
||||
del_wallet = await get_wallet(wallet.id)
|
||||
assert del_wallet is None
|
||||
|
||||
# check if wallet is deleted
|
||||
del_wallet = await get_wallet(wallet.id, False)
|
||||
assert del_wallet is None
|
||||
|
||||
del_wallet = await get_wallet(wallet.id, None)
|
||||
assert del_wallet is not None
|
||||
assert del_wallet.deleted is True
|
||||
|
||||
del_wallet = await get_wallet(wallet.id, True)
|
||||
assert del_wallet is not None
|
||||
assert del_wallet.deleted is True
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import time
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from coincurve import PrivateKey, PublicKey
|
||||
import secp256k1
|
||||
from Cryptodome import Random
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import pad, unpad
|
||||
@@ -23,8 +23,8 @@ from tests.wallets.helpers import (
|
||||
|
||||
|
||||
def encrypt_content(priv_key, dest_pub_key, content):
|
||||
p = PublicKey(bytes.fromhex("02" + dest_pub_key))
|
||||
shared = p.multiply(bytes.fromhex(priv_key)).format()[1:]
|
||||
p = secp256k1.PublicKey(bytes.fromhex("02" + dest_pub_key), True)
|
||||
shared = p.tweak_mul(bytes.fromhex(priv_key)).serialize()[1:]
|
||||
iv = Random.new().read(AES.block_size)
|
||||
aes = AES.new(shared, AES.MODE_CBC, iv)
|
||||
|
||||
@@ -38,8 +38,8 @@ def encrypt_content(priv_key, dest_pub_key, content):
|
||||
|
||||
|
||||
def decrypt_content(priv_key, source_pub_key, content):
|
||||
p = PublicKey(bytes.fromhex("02" + source_pub_key))
|
||||
shared = p.multiply(bytes.fromhex(priv_key)).format()[1:]
|
||||
p = secp256k1.PublicKey(bytes.fromhex("02" + source_pub_key), True)
|
||||
shared = p.tweak_mul(bytes.fromhex(priv_key)).serialize()[1:]
|
||||
(encrypted_content_b64, iv_b64) = content.split("?iv=")
|
||||
encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii"))
|
||||
iv = base64.b64decode(iv_b64.encode("ascii"))
|
||||
@@ -69,8 +69,8 @@ def sign_event(pub_key, priv_key, event):
|
||||
event_id = hashlib.sha256(signature_data.encode()).hexdigest()
|
||||
event["id"] = event_id
|
||||
event["pubkey"] = pub_key
|
||||
s = PrivateKey(bytes.fromhex(priv_key))
|
||||
signature = s.sign_schnorr(bytes.fromhex(event_id)).hex()
|
||||
s = secp256k1.PrivateKey(bytes.fromhex(priv_key))
|
||||
signature = (s.schnorr_sign(bytes.fromhex(event_id), None, raw=True)).hex()
|
||||
event["sig"] = signature
|
||||
return event
|
||||
|
||||
|
||||
@@ -372,7 +372,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "25.9.0"
|
||||
version = "25.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
@@ -380,25 +380,24 @@ dependencies = [
|
||||
{ name = "packaging" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "pytokens" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/40/dbe31fc56b218a858c8fc6f5d8d3ba61c1fa7e989d43d4a4574b8b992840/black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7", size = 1715605, upload-time = "2025-09-19T00:36:13.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b2/f46800621200eab6479b1f4c0e3ede5b4c06b768e79ee228bc80270bcc74/black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92", size = 1571829, upload-time = "2025-09-19T00:32:42.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/64/5c7f66bd65af5c19b4ea86062bb585adc28d51d37babf70969e804dbd5c2/black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713", size = 1631888, upload-time = "2025-09-19T00:30:54.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/64/0b9e5bfcf67db25a6eef6d9be6726499a8a72ebab3888c2de135190853d3/black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1", size = 1327056, upload-time = "2025-09-19T00:31:08.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/f4/7531d4a336d2d4ac6cc101662184c8e7d068b548d35d874415ed9f4116ef/black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa", size = 1698727, upload-time = "2025-09-19T00:31:14.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/f9/66f26bfbbf84b949cc77a41a43e138d83b109502cd9c52dfc94070ca51f2/black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d", size = 1555679, upload-time = "2025-09-19T00:31:29.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/59/61475115906052f415f518a648a9ac679d7afbc8da1c16f8fdf68a8cebed/black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608", size = 1617453, upload-time = "2025-09-19T00:30:42.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/5b/20fd5c884d14550c911e4fb1b0dae00d4abb60a4f3876b449c4d3a9141d5/black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f", size = 1333655, upload-time = "2025-09-19T00:30:56.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012, upload-time = "2025-09-19T00:33:08.678Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421, upload-time = "2025-09-19T00:35:25.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619, upload-time = "2025-09-19T00:30:49.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481, upload-time = "2025-09-19T00:31:29.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -632,49 +631,43 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.11.0"
|
||||
version = "7.10.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/4e/08b493f1f1d8a5182df0044acc970799b58a8d289608e0d891a03e9d269a/coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27", size = 823798, upload-time = "2025-08-17T00:26:43.314Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/95/c49df0aceb5507a80b9fe5172d3d39bf23f05be40c23c8d77d556df96cec/coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31", size = 215800, upload-time = "2025-10-15T15:12:19.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/c6/7bb46ce01ed634fff1d7bb53a54049f539971862cc388b304ff3c51b4f66/coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075", size = 216198, upload-time = "2025-10-15T15:12:22.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/b2/75d9d8fbf2900268aca5de29cd0a0fe671b0f69ef88be16767cc3c828b85/coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab", size = 242953, upload-time = "2025-10-15T15:12:24.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/ac/acaa984c18f440170525a8743eb4b6c960ace2dbad80dc22056a437fc3c6/coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0", size = 244766, upload-time = "2025-10-15T15:12:25.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/0d/938d0bff76dfa4a6b228c3fc4b3e1c0e2ad4aa6200c141fcda2bd1170227/coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785", size = 246625, upload-time = "2025-10-15T15:12:27.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/54/8f5f5e84bfa268df98f46b2cb396b1009734cfb1e5d6adb663d284893b32/coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591", size = 243568, upload-time = "2025-10-15T15:12:28.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/30/8ba337c2877fe3f2e1af0ed7ff4be0c0c4aca44d6f4007040f3ca2255e99/coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088", size = 244665, upload-time = "2025-10-15T15:12:30.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/fb/c6f1d6d9a665536b7dde2333346f0cc41dc6a60bd1ffc10cd5c33e7eb000/coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f", size = 242681, upload-time = "2025-10-15T15:12:32.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/38/1b532319af5f991fa153c20373291dc65c2bf532af7dbcffdeef745c8f79/coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866", size = 242912, upload-time = "2025-10-15T15:12:34.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/3d/f39331c60ef6050d2a861dc1b514fa78f85f792820b68e8c04196ad733d6/coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841", size = 243559, upload-time = "2025-10-15T15:12:35.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/55/cb7c9df9d0495036ce582a8a2958d50c23cd73f84a23284bc23bd4711a6f/coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf", size = 218266, upload-time = "2025-10-15T15:12:37.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/a8/b79cb275fa7bd0208767f89d57a1b5f6ba830813875738599741b97c2e04/coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969", size = 219169, upload-time = "2025-10-15T15:12:39.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", size = 215912, upload-time = "2025-10-15T15:12:40.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", size = 216310, upload-time = "2025-10-15T15:12:42.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", size = 246706, upload-time = "2025-10-15T15:12:44.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", size = 248634, upload-time = "2025-10-15T15:12:45.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", size = 250741, upload-time = "2025-10-15T15:12:47.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", size = 246837, upload-time = "2025-10-15T15:12:48.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", size = 248429, upload-time = "2025-10-15T15:12:50.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", size = 246490, upload-time = "2025-10-15T15:12:52.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", size = 246208, upload-time = "2025-10-15T15:12:54.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", size = 247126, upload-time = "2025-10-15T15:12:56.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c", size = 218314, upload-time = "2025-10-15T15:12:58.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", size = 219203, upload-time = "2025-10-15T15:12:59.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", size = 217879, upload-time = "2025-10-15T15:13:01.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/f4/350759710db50362685f922259c140592dba15eb4e2325656a98413864d9/coverage-7.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d92d6edb0ccafd20c6fbf9891ca720b39c2a6a4b4a6f9cf323ca2c986f33e475", size = 216403, upload-time = "2025-08-17T00:24:19.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/7e/e467c2bb4d5ecfd166bfd22c405cce4c50de2763ba1d78e2729c59539a42/coverage-7.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7202da14dc0236884fcc45665ffb2d79d4991a53fbdf152ab22f69f70923cc22", size = 216802, upload-time = "2025-08-17T00:24:21.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/ab/2accdd1ccfe63b890e5eb39118f63c155202df287798364868a2884a50af/coverage-7.10.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ada418633ae24ec8d0fcad5efe6fc7aa3c62497c6ed86589e57844ad04365674", size = 243558, upload-time = "2025-08-17T00:24:23.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/04/c14c33d0cfc0f4db6b3504d01a47f4c798563d932a836fd5f2dbc0521d3d/coverage-7.10.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b828e33eca6c3322adda3b5884456f98c435182a44917ded05005adfa1415500", size = 245370, upload-time = "2025-08-17T00:24:24.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/71/147053061f1f51c1d3b3d040c3cb26876964a3a0dca0765d2441411ca568/coverage-7.10.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:802793ba397afcfdbe9f91f89d65ae88b958d95edc8caf948e1f47d8b6b2b606", size = 247228, upload-time = "2025-08-17T00:24:26.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/92/7ef882205d4d4eb502e6154ee7122c1a1b1ce3f29d0166921e0fb550a5d3/coverage-7.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d0b23512338c54101d3bf7a1ab107d9d75abda1d5f69bc0887fd079253e4c27e", size = 245270, upload-time = "2025-08-17T00:24:27.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/3d/297a20603abcc6c7d89d801286eb477b0b861f3c5a4222730f1c9837be3e/coverage-7.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f36b7dcf72d06a8c5e2dd3aca02be2b1b5db5f86404627dff834396efce958f2", size = 243287, upload-time = "2025-08-17T00:24:28.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/f9/b04111438f41f1ddd5dc88706d5f8064ae5bb962203c49fe417fa23a362d/coverage-7.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fce316c367a1dc2c411821365592eeb335ff1781956d87a0410eae248188ba51", size = 244164, upload-time = "2025-08-17T00:24:30.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/e5/c7d9eb7a9ea66cf92d069077719fb2b07782dcd7050b01a9b88766b52154/coverage-7.10.4-cp310-cp310-win32.whl", hash = "sha256:8c5dab29fc8070b3766b5fc85f8d89b19634584429a2da6d42da5edfadaf32ae", size = 218917, upload-time = "2025-08-17T00:24:31.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/30/4d9d3b81f5a836b31a7428b8a25e6d490d4dca5ff2952492af130153c35c/coverage-7.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:4b0d114616f0fccb529a1817457d5fb52a10e106f86c5fb3b0bd0d45d0d69b93", size = 219822, upload-time = "2025-08-17T00:24:32.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/ba/2c9817e62018e7d480d14f684c160b3038df9ff69c5af7d80e97d143e4d1/coverage-7.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:05d5f98ec893d4a2abc8bc5f046f2f4367404e7e5d5d18b83de8fde1093ebc4f", size = 216514, upload-time = "2025-08-17T00:24:34.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/5a/093412a959a6b6261446221ba9fb23bb63f661a5de70b5d130763c87f916/coverage-7.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9267efd28f8994b750d171e58e481e3bbd69e44baed540e4c789f8e368b24b88", size = 216914, upload-time = "2025-08-17T00:24:35.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/1f/2fdf4a71cfe93b07eae845ebf763267539a7d8b7e16b062f959d56d7e433/coverage-7.10.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4456a039fdc1a89ea60823d0330f1ac6f97b0dbe9e2b6fb4873e889584b085fb", size = 247308, upload-time = "2025-08-17T00:24:37.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/16/33f6cded458e84f008b9f6bc379609a6a1eda7bffe349153b9960803fc11/coverage-7.10.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c2bfbd2a9f7e68a21c5bd191be94bfdb2691ac40d325bac9ef3ae45ff5c753d9", size = 249241, upload-time = "2025-08-17T00:24:38.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/98/9c18e47c889be58339ff2157c63b91a219272503ee32b49d926eea2337f2/coverage-7.10.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab7765f10ae1df7e7fe37de9e64b5a269b812ee22e2da3f84f97b1c7732a0d8", size = 251346, upload-time = "2025-08-17T00:24:40.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/07/00a6c0d53e9a22d36d8e95ddd049b860eef8f4b9fd299f7ce34d8e323356/coverage-7.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a09b13695166236e171ec1627ff8434b9a9bae47528d0ba9d944c912d33b3d2", size = 249037, upload-time = "2025-08-17T00:24:41.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/0e/1e1b944d6a6483d07bab5ef6ce063fcf3d0cc555a16a8c05ebaab11f5607/coverage-7.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5c9e75dfdc0167d5675e9804f04a56b2cf47fb83a524654297000b578b8adcb7", size = 247090, upload-time = "2025-08-17T00:24:43.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/43/2ce5ab8a728b8e25ced077111581290ffaef9efaf860a28e25435ab925cf/coverage-7.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c751261bfe6481caba15ec005a194cb60aad06f29235a74c24f18546d8377df0", size = 247732, upload-time = "2025-08-17T00:24:44.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/f3/706c4a24f42c1c5f3a2ca56637ab1270f84d9e75355160dc34d5e39bb5b7/coverage-7.10.4-cp311-cp311-win32.whl", hash = "sha256:051c7c9e765f003c2ff6e8c81ccea28a70fb5b0142671e4e3ede7cebd45c80af", size = 218961, upload-time = "2025-08-17T00:24:46.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/aa/6b9ea06e0290bf1cf2a2765bba89d561c5c563b4e9db8298bf83699c8b67/coverage-7.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a647b152f10be08fb771ae4a1421dbff66141e3d8ab27d543b5eb9ea5af8e52", size = 219851, upload-time = "2025-08-17T00:24:48.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/be/f0dc9ad50ee183369e643cd7ed8f2ef5c491bc20b4c3387cbed97dd6e0d1/coverage-7.10.4-cp311-cp311-win_arm64.whl", hash = "sha256:b09b9e4e1de0d406ca9f19a371c2beefe3193b542f64a6dd40cfcf435b7d6aa0", size = 218530, upload-time = "2025-08-17T00:24:50.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/4a/781c9e4dd57cabda2a28e2ce5b00b6be416015265851060945a5ed4bd85e/coverage-7.10.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1f0264abcabd4853d4cb9b3d164adbf1565da7dab1da1669e93f3ea60162d79", size = 216706, upload-time = "2025-08-17T00:24:51.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/8c/51255202ca03d2e7b664770289f80db6f47b05138e06cce112b3957d5dfd/coverage-7.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:536cbe6b118a4df231b11af3e0f974a72a095182ff8ec5f4868c931e8043ef3e", size = 216939, upload-time = "2025-08-17T00:24:53.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/7f/df11131483698660f94d3c847dc76461369782d7a7644fcd72ac90da8fd0/coverage-7.10.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9a4c0d84134797b7bf3f080599d0cd501471f6c98b715405166860d79cfaa97e", size = 248429, upload-time = "2025-08-17T00:24:54.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/fa/13ac5eda7300e160bf98f082e75f5c5b4189bf3a883dd1ee42dbedfdc617/coverage-7.10.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c155fc0f9cee8c9803ea0ad153ab6a3b956baa5d4cd993405dc0b45b2a0b9e0", size = 251178, upload-time = "2025-08-17T00:24:56.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/bc/f63b56a58ad0bec68a840e7be6b7ed9d6f6288d790760647bb88f5fea41e/coverage-7.10.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5f2ab6e451d4b07855d8bcf063adf11e199bff421a4ba57f5bb95b7444ca62", size = 252313, upload-time = "2025-08-17T00:24:57.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b6/79338f1ea27b01266f845afb4485976211264ab92407d1c307babe3592a7/coverage-7.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:685b67d99b945b0c221be0780c336b303a7753b3e0ec0d618c795aada25d5e7a", size = 250230, upload-time = "2025-08-17T00:24:59.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/93/3b24f1da3e0286a4dc5832427e1d448d5296f8287464b1ff4a222abeeeb5/coverage-7.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c079027e50c2ae44da51c2e294596cbc9dbb58f7ca45b30651c7e411060fc23", size = 248351, upload-time = "2025-08-17T00:25:00.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/5f/d59412f869e49dcc5b89398ef3146c8bfaec870b179cc344d27932e0554b/coverage-7.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3749aa72b93ce516f77cf5034d8e3c0dfd45c6e8a163a602ede2dc5f9a0bb927", size = 249788, upload-time = "2025-08-17T00:25:02.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/52/04a3b733f40a0cc7c4a5b9b010844111dbf906df3e868b13e1ce7b39ac31/coverage-7.10.4-cp312-cp312-win32.whl", hash = "sha256:fecb97b3a52fa9bcd5a7375e72fae209088faf671d39fae67261f37772d5559a", size = 219131, upload-time = "2025-08-17T00:25:03.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/dd/12909fc0b83888197b3ec43a4ac7753589591c08d00d9deda4158df2734e/coverage-7.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:26de58f355626628a21fe6a70e1e1fad95702dafebfb0685280962ae1449f17b", size = 219939, upload-time = "2025-08-17T00:25:05.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/c7/058bb3220fdd6821bada9685eadac2940429ab3c97025ce53549ff423cc1/coverage-7.10.4-cp312-cp312-win_arm64.whl", hash = "sha256:67e8885408f8325198862bc487038a4980c9277d753cb8812510927f2176437a", size = 218572, upload-time = "2025-08-17T00:25:06.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/78/983efd23200921d9edb6bd40512e1aa04af553d7d5a171e50f9b2b45d109/coverage-7.10.4-py3-none-any.whl", hash = "sha256:065d75447228d05121e5c938ca8f0e91eed60a1eb2d1258d42d5084fecfc3302", size = 208365, upload-time = "2025-08-17T00:26:41.479Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -848,11 +841,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.20.0"
|
||||
version = "3.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1083,11 +1076,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.15"
|
||||
version = "2.6.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243, upload-time = "2025-08-09T19:35:00.6Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153, upload-time = "2025-08-09T19:34:59.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1101,11 +1094,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1131,56 +1124,46 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "jiter"
|
||||
version = "0.11.1"
|
||||
version = "0.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/68/0357982493a7b20925aece061f7fb7a2678e3b232f8d73a6edb7e5304443/jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc", size = 168385, upload-time = "2025-10-17T11:31:15.186Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/10/d099def5716452c8d5ffa527405373a44ddaf8e3c9d4f6de1e1344cffd90/jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df", size = 310078, upload-time = "2025-10-17T11:28:36.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/56/b81d010b0031ffa96dfb590628562ac5f513ce56aa2ab451d29fb3fedeb9/jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4", size = 317138, upload-time = "2025-10-17T11:28:38.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/12/31ea12af9d79671cc7bd893bf0ccaf3467624c0fc7146a0cbfe7b549bcfa/jiter-0.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87b2821795e28cc990939b68ce7a038edea680a24910bd68a79d54ff3f03c02", size = 348964, upload-time = "2025-10-17T11:28:40.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/d2/95cb6dc5ff962410667a29708c7a6c0691cc3c4866a0bfa79d085b56ebd6/jiter-0.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83f6fa494d8bba14ab100417c80e70d32d737e805cb85be2052d771c76fcd1f8", size = 363289, upload-time = "2025-10-17T11:28:41.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/3e/37006ad5843a0bc3a3ec3a6c44710d7a154113befaf5f26d2fe190668b63/jiter-0.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fbc6aea1daa2ec6f5ed465f0c5e7b0607175062ceebbea5ca70dd5ddab58083", size = 487243, upload-time = "2025-10-17T11:28:43.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/5c/d38c8c801a322a0c0de47b9618c16fd766366f087ce37c4e55ae8e3c8b03/jiter-0.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:302288e2edc43174bb2db838e94688d724f9aad26c5fb9a74f7a5fb427452a6a", size = 376139, upload-time = "2025-10-17T11:28:44.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/cd/442ad2389a5570b0ee673f93e14bbe8cdecd3e08a9ba7756081d84065e4c/jiter-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85db563fe3b367bb568af5d29dea4d4066d923b8e01f3417d25ebecd958de815", size = 359279, upload-time = "2025-10-17T11:28:46.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/35/8f5810d0e7d00bc395889085dbc1ccc36d454b56f28b2a5359dfd1bab48d/jiter-0.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1c1ba2b6b22f775444ef53bc2d5778396d3520abc7b2e1da8eb0c27cb3ffb10", size = 384911, upload-time = "2025-10-17T11:28:48.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/bd/8c069ceb0bafcf6b4aa5de0c27f02faf50468df39564a02e1a12389ad6c2/jiter-0.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:523be464b14f8fd0cc78da6964b87b5515a056427a2579f9085ce30197a1b54a", size = 517879, upload-time = "2025-10-17T11:28:49.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/3c/9163efcf762f79f47433078b4f0a1bddc56096082c02c6cae2f47f07f56f/jiter-0.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25b99b3f04cd2a38fefb22e822e35eb203a2cd37d680dbbc0c0ba966918af336", size = 508739, upload-time = "2025-10-17T11:28:51.785Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/07/50690f257935845d3114b95b5dd03749eeaab5e395cbb522f9e957da4551/jiter-0.11.1-cp310-cp310-win32.whl", hash = "sha256:47a79e90545a596bb9104109777894033347b11180d4751a216afef14072dbe7", size = 203948, upload-time = "2025-10-17T11:28:54.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/3a/5964a944bf2e98ffd566153fdc2a6a368fcb11b58cc46832ca8c75808dba/jiter-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:cace75621ae9bd66878bf69fbd4dfc1a28ef8661e0c2d0eb72d3d6f1268eddf5", size = 207522, upload-time = "2025-10-17T11:28:56.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/34/c9e6cfe876f9a24f43ed53fe29f052ce02bd8d5f5a387dbf46ad3764bef0/jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2", size = 310160, upload-time = "2025-10-17T11:28:59.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/9f/b06ec8181d7165858faf2ac5287c54fe52b2287760b7fe1ba9c06890255f/jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661", size = 316573, upload-time = "2025-10-17T11:29:00.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/49/3179d93090f2ed0c6b091a9c210f266d2d020d82c96f753260af536371d0/jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8", size = 348998, upload-time = "2025-10-17T11:29:02.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/9d/63db2c8eabda7a9cad65a2e808ca34aaa8689d98d498f5a2357d7a2e2cec/jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb", size = 363413, upload-time = "2025-10-17T11:29:03.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/ff/3e6b3170c5053053c7baddb8d44e2bf11ff44cd71024a280a8438ae6ba32/jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1", size = 487144, upload-time = "2025-10-17T11:29:05.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/50/b63fcadf699893269b997f4c2e88400bc68f085c6db698c6e5e69d63b2c1/jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be", size = 376215, upload-time = "2025-10-17T11:29:07.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/8c/57a8a89401134167e87e73471b9cca321cf651c1fd78c45f3a0f16932213/jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4", size = 359163, upload-time = "2025-10-17T11:29:09.047Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/96/30b0cdbffbb6f753e25339d3dbbe26890c9ef119928314578201c758aace/jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29", size = 385344, upload-time = "2025-10-17T11:29:10.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/d5/31dae27c1cc9410ad52bb514f11bfa4f286f7d6ef9d287b98b8831e156ec/jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9", size = 517972, upload-time = "2025-10-17T11:29:12.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/1e/5905a7a3aceab80de13ab226fd690471a5e1ee7e554dc1015e55f1a6b896/jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5", size = 508408, upload-time = "2025-10-17T11:29:13.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/12/1c49b97aa49077e136e8591cef7162f0d3e2860ae457a2d35868fd1521ef/jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a", size = 203937, upload-time = "2025-10-17T11:29:14.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/9d/2255f7c17134ee9892c7e013c32d5bcf4bce64eb115402c9fe5e727a67eb/jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19", size = 207589, upload-time = "2025-10-17T11:29:16.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/28/6307fc8f95afef84cae6caf5429fee58ef16a582c2ff4db317ceb3e352fa/jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869", size = 188391, upload-time = "2025-10-17T11:29:17.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/8b/318e8af2c904a9d29af91f78c1e18f0592e189bbdb8a462902d31fe20682/jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c", size = 305655, upload-time = "2025-10-17T11:29:18.859Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/29/6c7de6b5d6e511d9e736312c0c9bfcee8f9b6bef68182a08b1d78767e627/jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d", size = 315645, upload-time = "2025-10-17T11:29:20.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/5f/ef9e5675511ee0eb7f98dd8c90509e1f7743dbb7c350071acae87b0145f3/jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b", size = 348003, upload-time = "2025-10-17T11:29:22.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/1b/abe8c4021010b0a320d3c62682769b700fb66f92c6db02d1a1381b3db025/jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4", size = 365122, upload-time = "2025-10-17T11:29:24.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/2d/4a18013939a4f24432f805fbd5a19893e64650b933edb057cd405275a538/jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239", size = 488360, upload-time = "2025-10-17T11:29:25.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/77/38124f5d02ac4131f0dfbcfd1a19a0fac305fa2c005bc4f9f0736914a1a4/jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711", size = 376884, upload-time = "2025-10-17T11:29:27.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/43/59fdc2f6267959b71dd23ce0bd8d4aeaf55566aa435a5d00f53d53c7eb24/jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939", size = 358827, upload-time = "2025-10-17T11:29:28.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/d0/b3cc20ff5340775ea3bbaa0d665518eddecd4266ba7244c9cb480c0c82ec/jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54", size = 385171, upload-time = "2025-10-17T11:29:30.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/bc/94dd1f3a61f4dc236f787a097360ec061ceeebebf4ea120b924d91391b10/jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d", size = 518359, upload-time = "2025-10-17T11:29:31.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/8c/12ee132bd67e25c75f542c227f5762491b9a316b0dad8e929c95076f773c/jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250", size = 509205, upload-time = "2025-10-17T11:29:32.895Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/d5/9de848928ce341d463c7e7273fce90ea6d0ea4343cd761f451860fa16b59/jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e", size = 205448, upload-time = "2025-10-17T11:29:34.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/b0/8002d78637e05009f5e3fb5288f9d57d65715c33b5d6aa20fd57670feef5/jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87", size = 204285, upload-time = "2025-10-17T11:29:35.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/a2/bb24d5587e4dff17ff796716542f663deee337358006a80c8af43ddc11e5/jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c", size = 188712, upload-time = "2025-10-17T11:29:37.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/51/bd41562dd284e2a18b6dc0a99d195fd4a3560d52ab192c42e56fe0316643/jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6", size = 306871, upload-time = "2025-10-17T11:31:03.616Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/cb/64e7f21dd357e8cd6b3c919c26fac7fc198385bbd1d85bb3b5355600d787/jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad", size = 301454, upload-time = "2025-10-17T11:31:05.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/b0/54bdc00da4ef39801b1419a01035bd8857983de984fd3776b0be6b94add7/jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f", size = 336801, upload-time = "2025-10-17T11:31:06.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/8f/87176ed071d42e9db415ed8be787ef4ef31a4fa27f52e6a4fbf34387bd28/jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa", size = 343452, upload-time = "2025-10-17T11:31:08.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/bc/950dd7f170c6394b6fdd73f989d9e729bd98907bcc4430ef080a72d06b77/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d", size = 302626, upload-time = "2025-10-17T11:31:09.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/65/43d7971ca82ee100b7b9b520573eeef7eabc0a45d490168ebb9a9b5bb8b2/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838", size = 297034, upload-time = "2025-10-17T11:31:10.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/4c/000e1e0c0c67e96557a279f8969487ea2732d6c7311698819f977abae837/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f", size = 337328, upload-time = "2025-10-17T11:31:12.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/7e/4011b5c77bec97cb2b572f566220364e3e21b51c48c5bd9c4a9c26b41b67/jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303", size = 317215, upload-time = "2025-05-18T19:03:04.303Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/4f/144c1b57c39692efc7ea7d8e247acf28e47d0912800b34d0ad815f6b2824/jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e", size = 322814, upload-time = "2025-05-18T19:03:06.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/1f/db977336d332a9406c0b1f0b82be6f71f72526a806cbb2281baf201d38e3/jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f", size = 345237, upload-time = "2025-05-18T19:03:07.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/1c/aa30a4a775e8a672ad7f21532bdbfb269f0706b39c6ff14e1f86bdd9e5ff/jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224", size = 370999, upload-time = "2025-05-18T19:03:09.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/df/f8257abc4207830cb18880781b5f5b716bad5b2a22fb4330cfd357407c5b/jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7", size = 491109, upload-time = "2025-05-18T19:03:11.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/76/9e1516fd7b4278aa13a2cc7f159e56befbea9aa65c71586305e7afa8b0b3/jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6", size = 388608, upload-time = "2025-05-18T19:03:12.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/64/67750672b4354ca20ca18d3d1ccf2c62a072e8a2d452ac3cf8ced73571ef/jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf", size = 352454, upload-time = "2025-05-18T19:03:14.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/4d/5c4e36d48f169a54b53a305114be3efa2bbffd33b648cd1478a688f639c1/jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90", size = 391833, upload-time = "2025-05-18T19:03:16.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/de/ce4a6166a78810bd83763d2fa13f85f73cbd3743a325469a4a9289af6dae/jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0", size = 523646, upload-time = "2025-05-18T19:03:17.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/a6/3bc9acce53466972964cf4ad85efecb94f9244539ab6da1107f7aed82934/jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee", size = 514735, upload-time = "2025-05-18T19:03:19.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/d8/243c2ab8426a2a4dea85ba2a2ba43df379ccece2145320dfd4799b9633c5/jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4", size = 210747, upload-time = "2025-05-18T19:03:21.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/7a/8021bd615ef7788b98fc76ff533eaac846322c170e93cbffa01979197a45/jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5", size = 207484, upload-time = "2025-05-18T19:03:23.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473, upload-time = "2025-05-18T19:03:25.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869, upload-time = "2025-05-18T19:03:36.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765, upload-time = "2025-05-18T19:03:41.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234, upload-time = "2025-05-18T19:03:42.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1236,41 +1219,29 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema-specifications"
|
||||
version = "2025.9.1"
|
||||
version = "2025.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "referencing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-object-proxy"
|
||||
version = "1.12.0"
|
||||
version = "1.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/f9/1f56571ed82fb324f293661690635cf42c41deb8a70a6c9e6edc3e9bb3c8/lazy_object_proxy-1.11.0.tar.gz", hash = "sha256:18874411864c9fbbbaa47f9fc1dd7aea754c86cfde21278ef427639d1dd78e9c", size = 44736, upload-time = "2025-04-16T16:53:48.482Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/2b/d5e8915038acbd6c6a9fcb8aaf923dc184222405d3710285a1fec6e262bc/lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519", size = 26658, upload-time = "2025-08-22T13:42:23.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/8f/91fc00eeea46ee88b9df67f7c5388e60993341d2a406243d620b2fdfde57/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6", size = 68412, upload-time = "2025-08-22T13:42:24.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d2/b7189a0e095caedfea4d42e6b6949d2685c354263bdf18e19b21ca9b3cd6/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b", size = 67559, upload-time = "2025-08-22T13:42:25.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/ad/b013840cc43971582ff1ceaf784d35d3a579650eb6cc348e5e6ed7e34d28/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8", size = 66651, upload-time = "2025-08-22T13:42:27.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/6f/b7368d301c15612fcc4cd00412b5d6ba55548bde09bdae71930e1a81f2ab/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8", size = 66901, upload-time = "2025-08-22T13:42:28.585Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/1b/c6b1865445576b2fc5fa0fbcfce1c05fee77d8979fd1aa653dd0f179aefc/lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab", size = 26536, upload-time = "2025-08-22T13:42:29.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/b3/4684b1e128a87821e485f5a901b179790e6b5bc02f89b7ee19c23be36ef3/lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff", size = 26656, upload-time = "2025-08-22T13:42:30.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/03/1bdc21d9a6df9ff72d70b2ff17d8609321bea4b0d3cffd2cea92fb2ef738/lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad", size = 68832, upload-time = "2025-08-22T13:42:31.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/4b/5788e5e8bd01d19af71e50077ab020bc5cce67e935066cd65e1215a09ff9/lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00", size = 69148, upload-time = "2025-08-22T13:42:32.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/0e/090bf070f7a0de44c61659cb7f74c2fe02309a77ca8c4b43adfe0b695f66/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508", size = 67800, upload-time = "2025-08-22T13:42:34.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/d2/b320325adbb2d119156f7c506a5fbfa37fcab15c26d13cf789a90a6de04e/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa", size = 68085, upload-time = "2025-08-22T13:42:35.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/48/4b718c937004bf71cd82af3713874656bcb8d0cc78600bf33bb9619adc6c/lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370", size = 26535, upload-time = "2025-08-22T13:42:36.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/c8/457f1555f066f5bacc44337141294153dc993b5e9132272ab54a64ee98a2/lazy_object_proxy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:132bc8a34f2f2d662a851acfd1b93df769992ed1b81e2b1fda7db3e73b0d5a18", size = 28045, upload-time = "2025-04-16T16:53:32.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/33/3260b4f8de6f0942008479fee6950b2b40af11fc37dba23aa3672b0ce8a6/lazy_object_proxy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:01261a3afd8621a1accb5682df2593dc7ec7d21d38f411011a5712dcd418fbed", size = 28441, upload-time = "2025-04-16T16:53:33.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/f6/eb645ca1ff7408bb69e9b1fe692cce1d74394efdbb40d6207096c0cd8381/lazy_object_proxy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:090935756cc041e191f22f4f9c7fd4fe9a454717067adf5b1bbd2ce3046b556e", size = 28047, upload-time = "2025-04-16T16:53:34.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/9c/aabbe1e8b99b8b0edb846b49a517edd636355ac97364419d9ba05b8fa19f/lazy_object_proxy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:76ec715017f06410f57df442c1a8d66e6b5f7035077785b129817f5ae58810a4", size = 28440, upload-time = "2025-04-16T16:53:36.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/24/dae4759469e9cd318fef145f7cfac7318261b47b23a4701aa477b0c3b42c/lazy_object_proxy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a9f39098e93a63618a79eef2889ae3cf0605f676cd4797fdfd49fcd7ddc318b", size = 28142, upload-time = "2025-04-16T16:53:37.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/0c/645a881f5f27952a02f24584d96f9f326748be06ded2cee25f8f8d1cd196/lazy_object_proxy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee13f67f4fcd044ef27bfccb1c93d39c100046fec1fad6e9a1fcdfd17492aeb3", size = 28380, upload-time = "2025-04-16T16:53:39.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/1e/fb441c07b6662ec1fc92b249225ba6e6e5221b05623cb0131d082f782edc/lazy_object_proxy-1.11.0-py3-none-any.whl", hash = "sha256:a56a5093d433341ff7da0e89f9b486031ccd222ec8e52ec84d0ec1cdc819674b", size = 16635, upload-time = "2025-04-16T16:53:47.198Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1289,7 +1260,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lnbits"
|
||||
version = "1.3.1"
|
||||
version = "1.3.0rc8"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiosqlite" },
|
||||
@@ -1430,7 +1401,7 @@ dev = [
|
||||
{ name = "grpcio-tools", specifier = ">=1.69.0,<2.0.0" },
|
||||
{ name = "json5", specifier = ">=0.12.0,<1.0.0" },
|
||||
{ name = "mock", specifier = ">=5.1.0,<6.0.0" },
|
||||
{ name = "mypy", specifier = "==1.17.1" },
|
||||
{ name = "mypy", specifier = ">=1.11.2,<2.0.0" },
|
||||
{ name = "openai", specifier = ">=1.39.0,<2.0.0" },
|
||||
{ name = "openapi-spec-validator", specifier = ">=0.7.1,<1.0.0" },
|
||||
{ name = "pre-commit", specifier = ">=4.2.0,<5.0.0" },
|
||||
@@ -1705,7 +1676,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.109.1"
|
||||
version = "1.100.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -1717,9 +1688,9 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/36/e2e24d419438a5e66aa6445ec663194395226293d214bfe615df562b2253/openai-1.100.2.tar.gz", hash = "sha256:787b4c3c8a65895182c58c424f790c25c790cc9a0330e34f73d55b6ee5a00e32", size = 507954, upload-time = "2025-08-19T15:32:47.854Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/8d/9ab1599c7942b3d04784ac5473905dc543aeb30a1acce3591d0b425682db/openai-1.100.2-py3-none-any.whl", hash = "sha256:54d3457b2c8d7303a1bc002a058de46bdd8f37a8117751c7cf4ed4438051f151", size = 787755, upload-time = "2025-08-19T15:32:46.252Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1780,11 +1751,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.5.0"
|
||||
version = "4.3.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2110,7 +2081,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.2"
|
||||
version = "8.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -2121,23 +2092,23 @@ dependencies = [
|
||||
{ name = "pygments" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "6.3.0"
|
||||
version = "6.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage", extra = ["toml"] },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/4c/f883ab8f0daad69f47efdf95f55a66b51a8b939c430dadce0611508d9e99/pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2", size = 70398, upload-time = "2025-09-06T15:40:14.361Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/80/b4/bb7263e12aade3842b938bc5c6958cae79c5ee18992f9b9349019579da0f/pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749", size = 25115, upload-time = "2025-09-06T15:40:12.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2166,14 +2137,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest-mock"
|
||||
version = "3.15.1"
|
||||
version = "3.14.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2218,15 +2189,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytokens"
|
||||
version = "0.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/c2/dbadcdddb412a267585459142bfd7cc241e6276db69339353ae6e241ab2b/pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43", size = 15368, upload-time = "2025-10-15T08:02:42.738Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/5a/c269ea6b348b6f2c32686635df89f32dbe05df1088dd4579302a6f8f99af/pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8", size = 12038, upload-time = "2025-10-15T08:02:41.694Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywebpush"
|
||||
version = "2.0.3"
|
||||
@@ -2246,38 +2208,37 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.3"
|
||||
version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2336,92 +2297,105 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "rpds-py"
|
||||
version = "0.28.0"
|
||||
version = "0.27.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/dc/95f074d43452b3ef5d06276696ece4b3b5d696e7c9ad7173c54b1390cd70/rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea", size = 27419, upload-time = "2025-10-22T22:24:29.327Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/d9/991a0dee12d9fc53ed027e26a26a64b151d77252ac477e22666b9688bc16/rpds_py-0.27.0.tar.gz", hash = "sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f", size = 27420, upload-time = "2025-08-07T08:26:39.624Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/f8/13bb772dc7cbf2c3c5b816febc34fa0cb2c64a08e0569869585684ce6631/rpds_py-0.28.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7b6013db815417eeb56b2d9d7324e64fcd4fa289caeee6e7a78b2e11fc9b438a", size = 362820, upload-time = "2025-10-22T22:21:15.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/91/6acce964aab32469c3dbe792cb041a752d64739c534e9c493c701ef0c032/rpds_py-0.28.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a4c6b05c685c0c03f80dabaeb73e74218c49deea965ca63f76a752807397207", size = 348499, upload-time = "2025-10-22T22:21:17.658Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/93/c05bb1f4f5e0234db7c4917cb8dd5e2e0a9a7b26dc74b1b7bee3c9cfd477/rpds_py-0.28.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4794c6c3fbe8f9ac87699b131a1f26e7b4abcf6d828da46a3a52648c7930eba", size = 379356, upload-time = "2025-10-22T22:21:19.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/37/e292da436f0773e319753c567263427cdf6c645d30b44f09463ff8216cda/rpds_py-0.28.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e8456b6ee5527112ff2354dd9087b030e3429e43a74f480d4a5ca79d269fd85", size = 390151, upload-time = "2025-10-22T22:21:21.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/87/a4e3267131616e8faf10486dc00eaedf09bd61c87f01e5ef98e782ee06c9/rpds_py-0.28.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:beb880a9ca0a117415f241f66d56025c02037f7c4efc6fe59b5b8454f1eaa50d", size = 524831, upload-time = "2025-10-22T22:21:23.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/c8/4a4ca76f0befae9515da3fad11038f0fce44f6bb60b21fe9d9364dd51fb0/rpds_py-0.28.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6897bebb118c44b38c9cb62a178e09f1593c949391b9a1a6fe777ccab5934ee7", size = 404687, upload-time = "2025-10-22T22:21:25.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/65/118afe854424456beafbbebc6b34dcf6d72eae3a08b4632bc4220f8240d9/rpds_py-0.28.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b553dd06e875249fd43efd727785efb57a53180e0fde321468222eabbeaafa", size = 382683, upload-time = "2025-10-22T22:21:26.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/bc/0625064041fb3a0c77ecc8878c0e8341b0ae27ad0f00cf8f2b57337a1e63/rpds_py-0.28.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:f0b2044fdddeea5b05df832e50d2a06fe61023acb44d76978e1b060206a8a476", size = 398927, upload-time = "2025-10-22T22:21:27.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/1a/fed7cf2f1ee8a5e4778f2054153f2cfcf517748875e2f5b21cf8907cd77d/rpds_py-0.28.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05cf1e74900e8da73fa08cc76c74a03345e5a3e37691d07cfe2092d7d8e27b04", size = 411590, upload-time = "2025-10-22T22:21:29.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/64/a8e0f67fa374a6c472dbb0afdaf1ef744724f165abb6899f20e2f1563137/rpds_py-0.28.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:efd489fec7c311dae25e94fe7eeda4b3d06be71c68f2cf2e8ef990ffcd2cd7e8", size = 559843, upload-time = "2025-10-22T22:21:30.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/ea/e10353f6d7c105be09b8135b72787a65919971ae0330ad97d87e4e199880/rpds_py-0.28.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ada7754a10faacd4f26067e62de52d6af93b6d9542f0df73c57b9771eb3ba9c4", size = 584188, upload-time = "2025-10-22T22:21:32.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/b0/a19743e0763caf0c89f6fc6ba6fbd9a353b24ffb4256a492420c5517da5a/rpds_py-0.28.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c2a34fd26588949e1e7977cfcbb17a9a42c948c100cab890c6d8d823f0586457", size = 550052, upload-time = "2025-10-22T22:21:34.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/bc/ec2c004f6c7d6ab1e25dae875cdb1aee087c3ebed5b73712ed3000e3851a/rpds_py-0.28.0-cp310-cp310-win32.whl", hash = "sha256:f9174471d6920cbc5e82a7822de8dfd4dcea86eb828b04fc8c6519a77b0ee51e", size = 215110, upload-time = "2025-10-22T22:21:36.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/de/4ce8abf59674e17187023933547d2018363e8fc76ada4f1d4d22871ccb6e/rpds_py-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:6e32dd207e2c4f8475257a3540ab8a93eff997abfa0a3fdb287cae0d6cd874b8", size = 223850, upload-time = "2025-10-22T22:21:38.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/34/058d0db5471c6be7bef82487ad5021ff8d1d1d27794be8730aad938649cf/rpds_py-0.28.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:03065002fd2e287725d95fbc69688e0c6daf6c6314ba38bdbaa3895418e09296", size = 362344, upload-time = "2025-10-22T22:21:39.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/67/9503f0ec8c055a0782880f300c50a2b8e5e72eb1f94dfc2053da527444dd/rpds_py-0.28.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28ea02215f262b6d078daec0b45344c89e161eab9526b0d898221d96fdda5f27", size = 348440, upload-time = "2025-10-22T22:21:41.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/2e/94223ee9b32332a41d75b6f94b37b4ce3e93878a556fc5f152cbd856a81f/rpds_py-0.28.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25dbade8fbf30bcc551cb352376c0ad64b067e4fc56f90e22ba70c3ce205988c", size = 379068, upload-time = "2025-10-22T22:21:42.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/25/54fd48f9f680cfc44e6a7f39a5fadf1d4a4a1fd0848076af4a43e79f998c/rpds_py-0.28.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c03002f54cc855860bfdc3442928ffdca9081e73b5b382ed0b9e8efe6e5e205", size = 390518, upload-time = "2025-10-22T22:21:43.998Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/85/ac258c9c27f2ccb1bd5d0697e53a82ebcf8088e3186d5d2bf8498ee7ed44/rpds_py-0.28.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9699fa7990368b22032baf2b2dce1f634388e4ffc03dfefaaac79f4695edc95", size = 525319, upload-time = "2025-10-22T22:21:45.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/cb/c6734774789566d46775f193964b76627cd5f42ecf246d257ce84d1912ed/rpds_py-0.28.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9b06fe1a75e05e0713f06ea0c89ecb6452210fd60e2f1b6ddc1067b990e08d9", size = 404896, upload-time = "2025-10-22T22:21:47.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/53/14e37ce83202c632c89b0691185dca9532288ff9d390eacae3d2ff771bae/rpds_py-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9f83e7b326a3f9ec3ef84cda98fb0a74c7159f33e692032233046e7fd15da2", size = 382862, upload-time = "2025-10-22T22:21:49.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/83/f3642483ca971a54d60caa4449f9d6d4dbb56a53e0072d0deff51b38af74/rpds_py-0.28.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:0d3259ea9ad8743a75a43eb7819324cdab393263c91be86e2d1901ee65c314e0", size = 398848, upload-time = "2025-10-22T22:21:51.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/09/2d9c8b2f88e399b4cfe86efdf2935feaf0394e4f14ab30c6c5945d60af7d/rpds_py-0.28.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a7548b345f66f6695943b4ef6afe33ccd3f1b638bd9afd0f730dd255c249c9e", size = 412030, upload-time = "2025-10-22T22:21:52.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/f5/e1cec473d4bde6df1fd3738be8e82d64dd0600868e76e92dfeaebbc2d18f/rpds_py-0.28.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9a40040aa388b037eb39416710fbcce9443498d2eaab0b9b45ae988b53f5c67", size = 559700, upload-time = "2025-10-22T22:21:54.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/be/73bb241c1649edbf14e98e9e78899c2c5e52bbe47cb64811f44d2cc11808/rpds_py-0.28.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f60c7ea34e78c199acd0d3cda37a99be2c861dd2b8cf67399784f70c9f8e57d", size = 584581, upload-time = "2025-10-22T22:21:56.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/9c/ffc6e9218cd1eb5c2c7dbd276c87cd10e8c2232c456b554169eb363381df/rpds_py-0.28.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1571ae4292649100d743b26d5f9c63503bb1fedf538a8f29a98dce2d5ba6b4e6", size = 549981, upload-time = "2025-10-22T22:21:58.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/50/da8b6d33803a94df0149345ee33e5d91ed4d25fc6517de6a25587eae4133/rpds_py-0.28.0-cp311-cp311-win32.whl", hash = "sha256:5cfa9af45e7c1140af7321fa0bef25b386ee9faa8928c80dc3a5360971a29e8c", size = 214729, upload-time = "2025-10-22T22:21:59.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/fd/b0f48c4c320ee24c8c20df8b44acffb7353991ddf688af01eef5f93d7018/rpds_py-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd8d86b5d29d1b74100982424ba53e56033dc47720a6de9ba0259cf81d7cecaa", size = 223977, upload-time = "2025-10-22T22:22:01.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/21/c8e77a2ac66e2ec4e21f18a04b4e9a0417ecf8e61b5eaeaa9360a91713b4/rpds_py-0.28.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e27d3a5709cc2b3e013bf93679a849213c79ae0573f9b894b284b55e729e120", size = 217326, upload-time = "2025-10-22T22:22:02.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/5c/6c3936495003875fe7b14f90ea812841a08fca50ab26bd840e924097d9c8/rpds_py-0.28.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6b4f28583a4f247ff60cd7bdda83db8c3f5b05a7a82ff20dd4b078571747708f", size = 366439, upload-time = "2025-10-22T22:22:04.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/f9/a0f1ca194c50aa29895b442771f036a25b6c41a35e4f35b1a0ea713bedae/rpds_py-0.28.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d678e91b610c29c4b3d52a2c148b641df2b4676ffe47c59f6388d58b99cdc424", size = 348170, upload-time = "2025-10-22T22:22:06.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/ea/42d243d3a586beb72c77fa5def0487daf827210069a95f36328e869599ea/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e819e0e37a44a78e1383bf1970076e2ccc4dc8c2bbaa2f9bd1dc987e9afff628", size = 378838, upload-time = "2025-10-22T22:22:07.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/78/3de32e18a94791af8f33601402d9d4f39613136398658412a4e0b3047327/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5ee514e0f0523db5d3fb171f397c54875dbbd69760a414dccf9d4d7ad628b5bd", size = 393299, upload-time = "2025-10-22T22:22:09.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/7e/4bdb435afb18acea2eb8a25ad56b956f28de7c59f8a1d32827effa0d4514/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3fa06d27fdcee47f07a39e02862da0100cb4982508f5ead53ec533cd5fe55e", size = 518000, upload-time = "2025-10-22T22:22:11.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/d0/5f52a656875cdc60498ab035a7a0ac8f399890cc1ee73ebd567bac4e39ae/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46959ef2e64f9e4a41fc89aa20dbca2b85531f9a72c21099a3360f35d10b0d5a", size = 408746, upload-time = "2025-10-22T22:22:13.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/cd/49ce51767b879cde77e7ad9fae164ea15dce3616fe591d9ea1df51152706/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8455933b4bcd6e83fde3fefc987a023389c4b13f9a58c8d23e4b3f6d13f78c84", size = 386379, upload-time = "2025-10-22T22:22:14.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/99/e4e1e1ee93a98f72fc450e36c0e4d99c35370220e815288e3ecd2ec36a2a/rpds_py-0.28.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ad50614a02c8c2962feebe6012b52f9802deec4263946cddea37aaf28dd25a66", size = 401280, upload-time = "2025-10-22T22:22:16.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/35/e0c6a57488392a8b319d2200d03dad2b29c0db9996f5662c3b02d0b86c02/rpds_py-0.28.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5deca01b271492553fdb6c7fd974659dce736a15bae5dad7ab8b93555bceb28", size = 412365, upload-time = "2025-10-22T22:22:17.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6a/841337980ea253ec797eb084665436007a1aad0faac1ba097fb906c5f69c/rpds_py-0.28.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:735f8495a13159ce6a0d533f01e8674cec0c57038c920495f87dcb20b3ddb48a", size = 559573, upload-time = "2025-10-22T22:22:19.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/5e/64826ec58afd4c489731f8b00729c5f6afdb86f1df1df60bfede55d650bb/rpds_py-0.28.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:961ca621ff10d198bbe6ba4957decca61aa2a0c56695384c1d6b79bf61436df5", size = 583973, upload-time = "2025-10-22T22:22:20.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/ee/44d024b4843f8386a4eeaa4c171b3d31d55f7177c415545fd1a24c249b5d/rpds_py-0.28.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2374e16cc9131022e7d9a8f8d65d261d9ba55048c78f3b6e017971a4f5e6353c", size = 553800, upload-time = "2025-10-22T22:22:22.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/89/33e675dccff11a06d4d85dbb4d1865f878d5020cbb69b2c1e7b2d3f82562/rpds_py-0.28.0-cp312-cp312-win32.whl", hash = "sha256:d15431e334fba488b081d47f30f091e5d03c18527c325386091f31718952fe08", size = 216954, upload-time = "2025-10-22T22:22:24.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/36/45f6ebb3210887e8ee6dbf1bc710ae8400bb417ce165aaf3024b8360d999/rpds_py-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:a410542d61fc54710f750d3764380b53bf09e8c4edbf2f9141a82aa774a04f7c", size = 227844, upload-time = "2025-10-22T22:22:25.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/91/f3fb250d7e73de71080f9a221d19bd6a1c1eb0d12a1ea26513f6c1052ad6/rpds_py-0.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:1f0cfd1c69e2d14f8c892b893997fa9a60d890a0c8a603e88dca4955f26d1edd", size = 217624, upload-time = "2025-10-22T22:22:26.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/bc/b43f2ea505f28119bd551ae75f70be0c803d2dbcd37c1b3734909e40620b/rpds_py-0.28.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f5e7101145427087e493b9c9b959da68d357c28c562792300dd21a095118ed16", size = 363913, upload-time = "2025-10-22T22:24:07.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/f2/db318195d324c89a2c57dc5195058cbadd71b20d220685c5bd1da79ee7fe/rpds_py-0.28.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:31eb671150b9c62409a888850aaa8e6533635704fe2b78335f9aaf7ff81eec4d", size = 350452, upload-time = "2025-10-22T22:24:08.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/f2/1391c819b8573a4898cedd6b6c5ec5bc370ce59e5d6bdcebe3c9c1db4588/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b55c1f64482f7d8bd39942f376bfdf2f6aec637ee8c805b5041e14eeb771db", size = 380957, upload-time = "2025-10-22T22:24:10.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/5c/e5de68ee7eb7248fce93269833d1b329a196d736aefb1a7481d1e99d1222/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24743a7b372e9a76171f6b69c01aedf927e8ac3e16c474d9fe20d552a8cb45c7", size = 391919, upload-time = "2025-10-22T22:24:12.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/4f/2376336112cbfeb122fd435d608ad8d5041b3aed176f85a3cb32c262eb80/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:389c29045ee8bbb1627ea190b4976a310a295559eaf9f1464a1a6f2bf84dde78", size = 528541, upload-time = "2025-10-22T22:24:14.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/53/5ae232e795853dd20da7225c5dd13a09c0a905b1a655e92bdf8d78a99fd9/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23690b5827e643150cf7b49569679ec13fe9a610a15949ed48b85eb7f98f34ec", size = 405629, upload-time = "2025-10-22T22:24:16.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2d/351a3b852b683ca9b6b8b38ed9efb2347596973849ba6c3a0e99877c10aa/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f0c9266c26580e7243ad0d72fc3e01d6b33866cfab5084a6da7576bcf1c4f72", size = 384123, upload-time = "2025-10-22T22:24:17.585Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/15/870804daa00202728cc91cb8e2385fa9f1f4eb49857c49cfce89e304eae6/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4c6c4db5d73d179746951486df97fd25e92396be07fc29ee8ff9a8f5afbdfb27", size = 400923, upload-time = "2025-10-22T22:24:19.512Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/25/3706b83c125fa2a0bccceac951de3f76631f6bd0ee4d02a0ed780712ef1b/rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3b695a8fa799dd2cfdb4804b37096c5f6dba1ac7f48a7fbf6d0485bcd060316", size = 413767, upload-time = "2025-10-22T22:24:21.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/f9/ce43dbe62767432273ed2584cef71fef8411bddfb64125d4c19128015018/rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6aa1bfce3f83baf00d9c5fcdbba93a3ab79958b4c7d7d1f55e7fe68c20e63912", size = 561530, upload-time = "2025-10-22T22:24:22.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/c9/ffe77999ed8f81e30713dd38fd9ecaa161f28ec48bb80fa1cd9118399c27/rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b0f9dceb221792b3ee6acb5438eb1f02b0cb2c247796a72b016dcc92c6de829", size = 585453, upload-time = "2025-10-22T22:24:24.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/d2/4a73b18821fd4669762c855fd1f4e80ceb66fb72d71162d14da58444a763/rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5d0145edba8abd3db0ab22b5300c99dc152f5c9021fab861be0f0544dc3cbc5f", size = 552199, upload-time = "2025-10-22T22:24:26.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/2d/ad2e37dee3f45580f7fa0066c412a521f9bee53d2718b0e9436d308a1ecd/rpds_py-0.27.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:130c1ffa5039a333f5926b09e346ab335f0d4ec393b030a18549a7c7e7c2cea4", size = 371511, upload-time = "2025-08-07T08:23:06.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/67/57b4b2479193fde9dd6983a13c2550b5f9c3bcdf8912dffac2068945eb14/rpds_py-0.27.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a4cf32a26fa744101b67bfd28c55d992cd19438aff611a46cac7f066afca8fd4", size = 354718, upload-time = "2025-08-07T08:23:08.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/be/c2b95ec4b813eb11f3a3c3d22f22bda8d3a48a074a0519cde968c4d102cf/rpds_py-0.27.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64a0fe3f334a40b989812de70160de6b0ec7e3c9e4a04c0bbc48d97c5d3600ae", size = 381518, upload-time = "2025-08-07T08:23:09.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/d2/5a7279bc2b93b20bd50865a2269016238cee45f7dc3cc33402a7f41bd447/rpds_py-0.27.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a0ff7ee28583ab30a52f371b40f54e7138c52ca67f8ca17ccb7ccf0b383cb5f", size = 396694, upload-time = "2025-08-07T08:23:11.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/e9/bac8b3714bd853c5bcb466e04acfb9a5da030d77e0ddf1dfad9afb791c31/rpds_py-0.27.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15ea4d2e182345dd1b4286593601d766411b43f868924afe297570658c31a62b", size = 514813, upload-time = "2025-08-07T08:23:12.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/aa/293115e956d7d13b7d2a9e9a4121f74989a427aa125f00ce4426ca8b7b28/rpds_py-0.27.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36184b44bf60a480863e51021c26aca3dfe8dd2f5eeabb33622b132b9d8b8b54", size = 402246, upload-time = "2025-08-07T08:23:13.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/59/2d6789bb898fb3e2f0f7b82b7bcf27f579ebcb6cc36c24f4e208f7f58a5b/rpds_py-0.27.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b78430703cfcf5f5e86eb74027a1ed03a93509273d7c705babb547f03e60016", size = 383661, upload-time = "2025-08-07T08:23:15.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/55/add13a593a7a81243a9eed56d618d3d427be5dc1214931676e3f695dfdc1/rpds_py-0.27.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:dbd749cff1defbde270ca346b69b3baf5f1297213ef322254bf2a28537f0b046", size = 401691, upload-time = "2025-08-07T08:23:16.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/09/3e8b2aad494ffaca571e4e19611a12cc18fcfd756d9274f3871a2d822445/rpds_py-0.27.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bde37765564cd22a676dd8101b657839a1854cfaa9c382c5abf6ff7accfd4ae", size = 416529, upload-time = "2025-08-07T08:23:17.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/6d/bd899234728f1d8f72c9610f50fdf1c140ecd0a141320e1f1d0f6b20595d/rpds_py-0.27.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1d66f45b9399036e890fb9c04e9f70c33857fd8f58ac8db9f3278cfa835440c3", size = 558673, upload-time = "2025-08-07T08:23:18.99Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/f4/f3e02def5193fb899d797c232f90d6f8f0f2b9eca2faef6f0d34cbc89b2e/rpds_py-0.27.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d85d784c619370d9329bbd670f41ff5f2ae62ea4519761b679d0f57f0f0ee267", size = 588426, upload-time = "2025-08-07T08:23:20.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/0c/88e716cd8fd760e5308835fe298255830de4a1c905fd51760b9bb40aa965/rpds_py-0.27.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5df559e9e7644d9042f626f2c3997b555f347d7a855a15f170b253f6c5bfe358", size = 554552, upload-time = "2025-08-07T08:23:21.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a9/0a8243c182e7ac59b901083dff7e671feba6676a131bfff3f8d301cd2b36/rpds_py-0.27.0-cp310-cp310-win32.whl", hash = "sha256:b8a4131698b6992b2a56015f51646711ec5d893a0b314a4b985477868e240c87", size = 218081, upload-time = "2025-08-07T08:23:23.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/e7/202ff35852312760148be9e08fe2ba6900aa28e7a46940a313eae473c10c/rpds_py-0.27.0-cp310-cp310-win_amd64.whl", hash = "sha256:cbc619e84a5e3ab2d452de831c88bdcad824414e9c2d28cd101f94dbdf26329c", size = 230077, upload-time = "2025-08-07T08:23:24.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/c1/49d515434c1752e40f5e35b985260cf27af052593378580a2f139a5be6b8/rpds_py-0.27.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dbc2ab5d10544eb485baa76c63c501303b716a5c405ff2469a1d8ceffaabf622", size = 371577, upload-time = "2025-08-07T08:23:25.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/6d/bf2715b2fee5087fa13b752b5fd573f1a93e4134c74d275f709e38e54fe7/rpds_py-0.27.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ec85994f96a58cf7ed288caa344b7fe31fd1d503bdf13d7331ead5f70ab60d5", size = 354959, upload-time = "2025-08-07T08:23:26.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/5c/e7762808c746dd19733a81373c10da43926f6a6adcf4920a21119697a60a/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:190d7285cd3bb6d31d37a0534d7359c1ee191eb194c511c301f32a4afa5a1dd4", size = 381485, upload-time = "2025-08-07T08:23:27.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/51/0d308eb0b558309ca0598bcba4243f52c4cd20e15fe991b5bd75824f2e61/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c10d92fb6d7fd827e44055fcd932ad93dac6a11e832d51534d77b97d1d85400f", size = 396816, upload-time = "2025-08-07T08:23:29.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/aa/2d585ec911d78f66458b2c91252134ca0c7c70f687a72c87283173dc0c96/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd2c1d27ebfe6a015cfa2005b7fe8c52d5019f7bbdd801bc6f7499aab9ae739e", size = 514950, upload-time = "2025-08-07T08:23:30.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/ef/aced551cc1148179557aed84343073adadf252c91265263ee6203458a186/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4790c9d5dd565ddb3e9f656092f57268951398cef52e364c405ed3112dc7c7c1", size = 402132, upload-time = "2025-08-07T08:23:32.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/ac/cf644803d8d417653fe2b3604186861d62ea6afaef1b2284045741baef17/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4300e15e7d03660f04be84a125d1bdd0e6b2f674bc0723bc0fd0122f1a4585dc", size = 383660, upload-time = "2025-08-07T08:23:33.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ec/caf47c55ce02b76cbaeeb2d3b36a73da9ca2e14324e3d75cf72b59dcdac5/rpds_py-0.27.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:59195dc244fc183209cf8a93406889cadde47dfd2f0a6b137783aa9c56d67c85", size = 401730, upload-time = "2025-08-07T08:23:34.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/71/c1f355afdcd5b99ffc253422aa4bdcb04ccf1491dcd1bda3688a0c07fd61/rpds_py-0.27.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fae4a01ef8c4cb2bbe92ef2063149596907dc4a881a8d26743b3f6b304713171", size = 416122, upload-time = "2025-08-07T08:23:36.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0f/f4b5b1eda724ed0e04d2b26d8911cdc131451a7ee4c4c020a1387e5c6ded/rpds_py-0.27.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e3dc8d4ede2dbae6c0fc2b6c958bf51ce9fd7e9b40c0f5b8835c3fde44f5807d", size = 558771, upload-time = "2025-08-07T08:23:37.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/c0/5f8b834db2289ab48d5cffbecbb75e35410103a77ac0b8da36bf9544ec1c/rpds_py-0.27.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c3782fb753aa825b4ccabc04292e07897e2fd941448eabf666856c5530277626", size = 587876, upload-time = "2025-08-07T08:23:38.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/dd/1a1df02ab8eb970115cff2ae31a6f73916609b900dc86961dc382b8c2e5e/rpds_py-0.27.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:887ab1f12b0d227e9260558a4a2320024b20102207ada65c43e1ffc4546df72e", size = 554359, upload-time = "2025-08-07T08:23:39.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/e4/95a014ab0d51ab6e3bebbdb476a42d992d2bbf9c489d24cff9fda998e925/rpds_py-0.27.0-cp311-cp311-win32.whl", hash = "sha256:5d6790ff400254137b81b8053b34417e2c46921e302d655181d55ea46df58cf7", size = 218084, upload-time = "2025-08-07T08:23:41.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/78/f8d5b71ec65a0376b0de31efcbb5528ce17a9b7fdd19c3763303ccfdedec/rpds_py-0.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:e24d8031a2c62f34853756d9208eeafa6b940a1efcbfe36e8f57d99d52bb7261", size = 230085, upload-time = "2025-08-07T08:23:42.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/d3/84429745184091e06b4cc70f8597408e314c2d2f7f5e13249af9ffab9e3d/rpds_py-0.27.0-cp311-cp311-win_arm64.whl", hash = "sha256:08680820d23df1df0a0260f714d12966bc6c42d02e8055a91d61e03f0c47dda0", size = 222112, upload-time = "2025-08-07T08:23:43.233Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/17/e67309ca1ac993fa1888a0d9b2f5ccc1f67196ace32e76c9f8e1dbbbd50c/rpds_py-0.27.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4", size = 362611, upload-time = "2025-08-07T08:23:44.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/2e/28c2fb84aa7aa5d75933d1862d0f7de6198ea22dfd9a0cca06e8a4e7509e/rpds_py-0.27.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b", size = 347680, upload-time = "2025-08-07T08:23:46.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/3e/9834b4c8f4f5fe936b479e623832468aa4bd6beb8d014fecaee9eac6cdb1/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e", size = 384600, upload-time = "2025-08-07T08:23:48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/78/744123c7b38865a965cd9e6f691fde7ef989a00a256fa8bf15b75240d12f/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34", size = 400697, upload-time = "2025-08-07T08:23:49.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/97/3c3d32fe7daee0a1f1a678b6d4dfb8c4dcf88197fa2441f9da7cb54a8466/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8", size = 517781, upload-time = "2025-08-07T08:23:50.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/be/28f0e3e733680aa13ecec1212fc0f585928a206292f14f89c0b8a684cad1/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726", size = 406449, upload-time = "2025-08-07T08:23:51.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/ae/5d15c83e337c082d0367053baeb40bfba683f42459f6ebff63a2fd7e5518/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e", size = 386150, upload-time = "2025-08-07T08:23:52.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/65/944e95f95d5931112829e040912b25a77b2e7ed913ea5fe5746aa5c1ce75/rpds_py-0.27.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3", size = 406100, upload-time = "2025-08-07T08:23:54.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/a4/1664b83fae02894533cd11dc0b9f91d673797c2185b7be0f7496107ed6c5/rpds_py-0.27.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e", size = 421345, upload-time = "2025-08-07T08:23:55.832Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/26/b7303941c2b0823bfb34c71378249f8beedce57301f400acb04bb345d025/rpds_py-0.27.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f", size = 561891, upload-time = "2025-08-07T08:23:56.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/c8/48623d64d4a5a028fa99576c768a6159db49ab907230edddc0b8468b998b/rpds_py-0.27.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03", size = 591756, upload-time = "2025-08-07T08:23:58.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/51/18f62617e8e61cc66334c9fb44b1ad7baae3438662098efbc55fb3fda453/rpds_py-0.27.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374", size = 557088, upload-time = "2025-08-07T08:23:59.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/4c/e84c3a276e2496a93d245516be6b49e20499aa8ca1c94d59fada0d79addc/rpds_py-0.27.0-cp312-cp312-win32.whl", hash = "sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97", size = 221926, upload-time = "2025-08-07T08:24:00.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/89/9d0fbcef64340db0605eb0a0044f258076f3ae0a3b108983b2c614d96212/rpds_py-0.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5", size = 233235, upload-time = "2025-08-07T08:24:01.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/b0/e177aa9f39cbab060f96de4a09df77d494f0279604dc2f509263e21b05f9/rpds_py-0.27.0-cp312-cp312-win_arm64.whl", hash = "sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9", size = 223315, upload-time = "2025-08-07T08:24:03.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/55/287068956f9ba1cb40896d291213f09fdd4527630709058b45a592bc09dc/rpds_py-0.27.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:46f48482c1a4748ab2773f75fffbdd1951eb59794e32788834b945da857c47a8", size = 371566, upload-time = "2025-08-07T08:25:43.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/fb/443af59cbe552e89680bb0f1d1ba47f6387b92083e28a45b8c8863b86c5a/rpds_py-0.27.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:419dd9c98bcc9fb0242be89e0c6e922df333b975d4268faa90d58499fd9c9ebe", size = 355781, upload-time = "2025-08-07T08:25:45.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/f0/35f48bb073b5ca42b1dcc55cb148f4a3bd4411a3e584f6a18d26f0ea8832/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d42a0ef2bdf6bc81e1cc2d49d12460f63c6ae1423c4f4851b828e454ccf6f1", size = 382575, upload-time = "2025-08-07T08:25:46.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/e1/5f5296a21d1189f0f116a938af2e346d83172bf814d373695e54004a936f/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e39169ac6aae06dd79c07c8a69d9da867cef6a6d7883a0186b46bb46ccfb0c3", size = 397435, upload-time = "2025-08-07T08:25:48.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/79/3af99b7852b2b55cad8a08863725cbe9dc14781bcf7dc6ecead0c3e1dc54/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:935afcdea4751b0ac918047a2df3f720212892347767aea28f5b3bf7be4f27c0", size = 514861, upload-time = "2025-08-07T08:25:49.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/3e/11fd6033708ed3ae0e6947bb94f762f56bb46bf59a1b16eef6944e8a62ee/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8de567dec6d451649a781633d36f5c7501711adee329d76c095be2178855b042", size = 402776, upload-time = "2025-08-07T08:25:51.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/89/f9375ceaa996116de9cbc949874804c7874d42fb258c384c037a46d730b8/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:555ed147cbe8c8f76e72a4c6cd3b7b761cbf9987891b9448808148204aed74a5", size = 384665, upload-time = "2025-08-07T08:25:52.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/bf/0061e55c6f1f573a63c0f82306b8984ed3b394adafc66854a936d5db3522/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:d2cc2b34f9e1d31ce255174da82902ad75bd7c0d88a33df54a77a22f2ef421ee", size = 402518, upload-time = "2025-08-07T08:25:54.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/dc/8d506676bfe87b3b683332ec8e6ab2b0be118a3d3595ed021e3274a63191/rpds_py-0.27.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cb0702c12983be3b2fab98ead349ac63a98216d28dda6f518f52da5498a27a1b", size = 416247, upload-time = "2025-08-07T08:25:55.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/02/9a89eea1b75c69e81632de7963076e455b1e00e1cfb46dfdabb055fa03e3/rpds_py-0.27.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ba783541be46f27c8faea5a6645e193943c17ea2f0ffe593639d906a327a9bcc", size = 559456, upload-time = "2025-08-07T08:25:56.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/4a/0f3ac4351957847c0d322be6ec72f916e43804a2c1d04e9672ea4a67c315/rpds_py-0.27.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:2406d034635d1497c596c40c85f86ecf2bf9611c1df73d14078af8444fe48031", size = 587778, upload-time = "2025-08-07T08:25:58.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/8e/39d0d7401095bed5a5ad5ef304fae96383f9bef40ca3f3a0807ff5b68d9d/rpds_py-0.27.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dea0808153f1fbbad772669d906cddd92100277533a03845de6893cadeffc8be", size = 555247, upload-time = "2025-08-07T08:25:59.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/04/6b8311e811e620b9eaca67cd80a118ff9159558a719201052a7b2abb88bf/rpds_py-0.27.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2a81bdcfde4245468f7030a75a37d50400ac2455c3a4819d9d550c937f90ab5", size = 230256, upload-time = "2025-08-07T08:26:01.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/64/72ab5b911fdcc48058359b0e786e5363e3fde885156116026f1a2ba9a5b5/rpds_py-0.27.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e6491658dd2569f05860bad645569145c8626ac231877b0fb2d5f9bcb7054089", size = 371658, upload-time = "2025-08-07T08:26:02.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/4b/90ff04b4da055db53d8fea57640d8d5d55456343a1ec9a866c0ecfe10fd1/rpds_py-0.27.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec77545d188f8bdd29d42bccb9191682a46fb2e655e3d1fb446d47c55ac3b8d", size = 355529, upload-time = "2025-08-07T08:26:03.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/be/527491fb1afcd86fc5ce5812eb37bc70428ee017d77fee20de18155c3937/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a4aebf8ca02bbb90a9b3e7a463bbf3bee02ab1c446840ca07b1695a68ce424", size = 382822, upload-time = "2025-08-07T08:26:05.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/a5/dcdb8725ce11e6d0913e6fcf782a13f4b8a517e8acc70946031830b98441/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44524b96481a4c9b8e6c46d6afe43fa1fb485c261e359fbe32b63ff60e3884d8", size = 397233, upload-time = "2025-08-07T08:26:07.179Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/f9/0947920d1927e9f144660590cc38cadb0795d78fe0d9aae0ef71c1513b7c/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45d04a73c54b6a5fd2bab91a4b5bc8b426949586e61340e212a8484919183859", size = 514892, upload-time = "2025-08-07T08:26:08.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/ed/d1343398c1417c68f8daa1afce56ef6ce5cc587daaf98e29347b00a80ff2/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:343cf24de9ed6c728abefc5d5c851d5de06497caa7ac37e5e65dd572921ed1b5", size = 402733, upload-time = "2025-08-07T08:26:10.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/0b/646f55442cd14014fb64d143428f25667a100f82092c90087b9ea7101c74/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aed8118ae20515974650d08eb724150dc2e20c2814bcc307089569995e88a14", size = 384447, upload-time = "2025-08-07T08:26:11.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/15/0596ef7529828e33a6c81ecf5013d1dd33a511a3e0be0561f83079cda227/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:af9d4fd79ee1cc8e7caf693ee02737daabfc0fcf2773ca0a4735b356c8ad6f7c", size = 402502, upload-time = "2025-08-07T08:26:13.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/8d/986af3c42f8454a6cafff8729d99fb178ae9b08a9816325ac7a8fa57c0c0/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f0396e894bd1e66c74ecbc08b4f6a03dc331140942c4b1d345dd131b68574a60", size = 416651, upload-time = "2025-08-07T08:26:14.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/9a/b4ec3629b7b447e896eec574469159b5b60b7781d3711c914748bf32de05/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:59714ab0a5af25d723d8e9816638faf7f4254234decb7d212715c1aa71eee7be", size = 559460, upload-time = "2025-08-07T08:26:16.295Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/63/d1e127b40c3e4733b3a6f26ae7a063cdf2bc1caa5272c89075425c7d397a/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:88051c3b7d5325409f433c5a40328fcb0685fc04e5db49ff936e910901d10114", size = 588072, upload-time = "2025-08-07T08:26:17.776Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/7e/8ffc71a8f6833d9c9fb999f5b0ee736b8b159fd66968e05c7afc2dbcd57e/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466", size = 555083, upload-time = "2025-08-07T08:26:19.301Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.2"
|
||||
version = "0.12.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/34/8218a19b2055b80601e8fd201ec723c74c7fe1ca06d525a43ed07b6d8e85/ruff-0.14.2.tar.gz", hash = "sha256:98da787668f239313d9c902ca7c523fe11b8ec3f39345553a51b25abc4629c96", size = 5539663, upload-time = "2025-10-23T19:37:00.956Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4a/45/2e403fa7007816b5fbb324cb4f8ed3c7402a927a0a0cb2b6279879a8bfdc/ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", size = 5254702, upload-time = "2025-08-14T16:08:55.2Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/dd/23eb2db5ad9acae7c845700493b72d3ae214dce0b226f27df89216110f2b/ruff-0.14.2-py3-none-linux_armv6l.whl", hash = "sha256:7cbe4e593505bdec5884c2d0a4d791a90301bc23e49a6b1eb642dd85ef9c64f1", size = 12533390, upload-time = "2025-10-23T19:36:18.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/8c/5f9acff43ddcf3f85130d0146d0477e28ccecc495f9f684f8f7119b74c0d/ruff-0.14.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8d54b561729cee92f8d89c316ad7a3f9705533f5903b042399b6ae0ddfc62e11", size = 12887187, upload-time = "2025-10-23T19:36:22.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/fa/047646491479074029665022e9f3dc6f0515797f40a4b6014ea8474c539d/ruff-0.14.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c8753dfa44ebb2cde10ce5b4d2ef55a41fb9d9b16732a2c5df64620dbda44a3", size = 11925177, upload-time = "2025-10-23T19:36:24.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/8b/c44cf7fe6e59ab24a9d939493a11030b503bdc2a16622cede8b7b1df0114/ruff-0.14.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0bbeffb8d9f4fccf7b5198d566d0bad99a9cb622f1fc3467af96cb8773c9e3", size = 12358285, upload-time = "2025-10-23T19:36:26.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/01/47701b26254267ef40369aea3acb62a7b23e921c27372d127e0f3af48092/ruff-0.14.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7047f0c5a713a401e43a88d36843d9c83a19c584e63d664474675620aaa634a8", size = 12303832, upload-time = "2025-10-23T19:36:29.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/5c/ae7244ca4fbdf2bee9d6405dcd5bc6ae51ee1df66eb7a9884b77b8af856d/ruff-0.14.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bf8d2f9aa1602599217d82e8e0af7fd33e5878c4d98f37906b7c93f46f9a839", size = 13036995, upload-time = "2025-10-23T19:36:31.861Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/4c/0860a79ce6fd4c709ac01173f76f929d53f59748d0dcdd662519835dae43/ruff-0.14.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1c505b389e19c57a317cf4b42db824e2fca96ffb3d86766c1c9f8b96d32048a7", size = 14512649, upload-time = "2025-10-23T19:36:33.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/7f/d365de998069720a3abfc250ddd876fc4b81a403a766c74ff9bde15b5378/ruff-0.14.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a307fc45ebd887b3f26b36d9326bb70bf69b01561950cdcc6c0bdf7bb8e0f7cc", size = 14088182, upload-time = "2025-10-23T19:36:36.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/ea/d8e3e6b209162000a7be1faa41b0a0c16a133010311edc3329753cc6596a/ruff-0.14.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61ae91a32c853172f832c2f40bd05fd69f491db7289fb85a9b941ebdd549781a", size = 13599516, upload-time = "2025-10-23T19:36:39.208Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/ea/c7810322086db68989fb20a8d5221dd3b79e49e396b01badca07b433ab45/ruff-0.14.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1967e40286f63ee23c615e8e7e98098dedc7301568bd88991f6e544d8ae096", size = 13272690, upload-time = "2025-10-23T19:36:41.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/39/10b05acf8c45786ef501d454e00937e1b97964f846bf28883d1f9619928a/ruff-0.14.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2877f02119cdebf52a632d743a2e302dea422bfae152ebe2f193d3285a3a65df", size = 13496497, upload-time = "2025-10-23T19:36:43.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/a1/1f25f8301e13751c30895092485fada29076e5e14264bdacc37202e85d24/ruff-0.14.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e681c5bc777de5af898decdcb6ba3321d0d466f4cb43c3e7cc2c3b4e7b843a05", size = 12266116, upload-time = "2025-10-23T19:36:45.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/fa/0029bfc9ce16ae78164e6923ef392e5f173b793b26cc39aa1d8b366cf9dc/ruff-0.14.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e21be42d72e224736f0c992cdb9959a2fa53c7e943b97ef5d081e13170e3ffc5", size = 12281345, upload-time = "2025-10-23T19:36:47.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ab/ece7baa3c0f29b7683be868c024f0838770c16607bea6852e46b202f1ff6/ruff-0.14.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8264016f6f209fac16262882dbebf3f8be1629777cf0f37e7aff071b3e9b92e", size = 12629296, upload-time = "2025-10-23T19:36:49.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/7f/638f54b43f3d4e48c6a68062794e5b367ddac778051806b9e235dfb7aa81/ruff-0.14.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ca36b4cb4db3067a3b24444463ceea5565ea78b95fe9a07ca7cb7fd16948770", size = 13371610, upload-time = "2025-10-23T19:36:51.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/35/3654a973ebe5b32e1fd4a08ed2d46755af7267da7ac710d97420d7b8657d/ruff-0.14.2-py3-none-win32.whl", hash = "sha256:41775927d287685e08f48d8eb3f765625ab0b7042cc9377e20e64f4eb0056ee9", size = 12415318, upload-time = "2025-10-23T19:36:53.961Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/30/3758bcf9e0b6a4193a6f51abf84254aba00887dfa8c20aba18aa366c5f57/ruff-0.14.2-py3-none-win_amd64.whl", hash = "sha256:0df3424aa5c3c08b34ed8ce099df1021e3adaca6e90229273496b839e5a7e1af", size = 13565279, upload-time = "2025-10-23T19:36:56.578Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/5d/aa883766f8ef9ffbe6aa24f7192fb71632f31a30e77eb39aa2b0dc4290ac/ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a", size = 12554956, upload-time = "2025-10-23T19:36:58.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/20/53bf098537adb7b6a97d98fcdebf6e916fcd11b2e21d15f8c171507909cc/ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e", size = 11759705, upload-time = "2025-08-14T16:08:12.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/4d/c764ee423002aac1ec66b9d541285dd29d2c0640a8086c87de59ebbe80d5/ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", size = 12527042, upload-time = "2025-08-14T16:08:16.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/45/cfcdf6d3eb5fc78a5b419e7e616d6ccba0013dc5b180522920af2897e1be/ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", size = 11724457, upload-time = "2025-08-14T16:08:18.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/e6/44615c754b55662200c48bebb02196dbb14111b6e266ab071b7e7297b4ec/ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", size = 11949446, upload-time = "2025-08-14T16:08:21.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/d1/9b7d46625d617c7df520d40d5ac6cdcdf20cbccb88fad4b5ecd476a6bb8d/ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", size = 11566350, upload-time = "2025-08-14T16:08:23.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/20/b73132f66f2856bc29d2d263c6ca457f8476b0bbbe064dac3ac3337a270f/ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", size = 13270430, upload-time = "2025-08-14T16:08:25.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/21/eaf3806f0a3d4c6be0a69d435646fba775b65f3f2097d54898b0fd4bb12e/ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", size = 14264717, upload-time = "2025-08-14T16:08:27.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/82/1d0c53bd37dcb582b2c521d352fbf4876b1e28bc0d8894344198f6c9950d/ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", size = 13684331, upload-time = "2025-08-14T16:08:30.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2f/1c5cf6d8f656306d42a686f1e207f71d7cebdcbe7b2aa18e4e8a0cb74da3/ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", size = 12739151, upload-time = "2025-08-14T16:08:32.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/09/25033198bff89b24d734e6479e39b1968e4c992e82262d61cdccaf11afb9/ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", size = 12954992, upload-time = "2025-08-14T16:08:34.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/8e/d0dbf2f9dca66c2d7131feefc386523404014968cd6d22f057763935ab32/ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", size = 12899569, upload-time = "2025-08-14T16:08:36.852Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/bd/b614d7c08515b1428ed4d3f1d4e3d687deffb2479703b90237682586fa66/ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", size = 11751983, upload-time = "2025-08-14T16:08:39.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/d6/383e9f818a2441b1a0ed898d7875f11273f10882f997388b2b51cb2ae8b5/ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", size = 11538635, upload-time = "2025-08-14T16:08:41.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/9c/56f869d314edaa9fc1f491706d1d8a47747b9d714130368fbd69ce9024e9/ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", size = 12534346, upload-time = "2025-08-14T16:08:43.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/4b/d8b95c6795a6c93b439bc913ee7a94fda42bb30a79285d47b80074003ee7/ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", size = 13017021, upload-time = "2025-08-14T16:08:45.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/c1/5f9a839a697ce1acd7af44836f7c2181cdae5accd17a5cb85fcbd694075e/ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", size = 11734785, upload-time = "2025-08-14T16:08:48.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/66/cdddc2d1d9a9f677520b7cfc490d234336f523d4b429c1298de359a3be08/ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", size = 12840654, upload-time = "2025-08-14T16:08:50.158Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/fd/669816bc6b5b93b9586f3c1d87cd6bc05028470b3ecfebb5938252c47a35/ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", size = 11949623, upload-time = "2025-08-14T16:08:52.233Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2557,27 +2531,31 @@ sdist = { url = "https://files.pythonhosted.org/packages/bb/89/6df40b0c5fd9a1c30
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.3.0"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2628,11 +2606,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "types-mock"
|
||||
version = "5.2.0.20250924"
|
||||
version = "5.2.0.20250809"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/c3/00cf1e62c27fd195aaf22b249884f82643141b73f151ff019aa24c99bd17/types_mock-5.2.0.20250924.tar.gz", hash = "sha256:953197543b4183f00363e8e626f6c7abea1a3f7a4dd69d199addb70b01b6bb35", size = 11319, upload-time = "2025-09-24T02:53:33.093Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/99/c7299239f1421387c3cede84ea0ce773442bcdaa1fb8e02424087ebcfbb9/types_mock-5.2.0.20250809.tar.gz", hash = "sha256:9548ebc2fbb3859f0765c8215f0b063897a58a58930f95467a814a9c9de76b16", size = 11222, upload-time = "2025-08-09T03:13:56.803Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/85/52004fb81add2b05494cbd1c0dab71f3706f19935cabb4ad220643884382/types_mock-5.2.0.20250924-py3-none-any.whl", hash = "sha256:23617ffb4cf948c085db69ec90bd474afbce634ef74995045ae0a5748afbe57d", size = 10499, upload-time = "2025-09-24T02:53:32.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/82/0a2b1e779c0cb91312aaa5c61beada024a45392f6960f19da68c9c0040f2/types_mock-5.2.0.20250809-py3-none-any.whl", hash = "sha256:e81a5f5ae222b0a0e92401cb429ed44e5dc238f900e876dc6183cc5e649f336d", size = 10475, upload-time = "2025-08-09T03:13:55.764Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2646,11 +2624,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "types-protobuf"
|
||||
version = "6.32.1.20250918"
|
||||
version = "6.30.2.20250809"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/5a/bd06c2dbb77ebd4ea764473c9c4c014c7ba94432192cb965a274f8544b9d/types_protobuf-6.32.1.20250918.tar.gz", hash = "sha256:44ce0ae98475909ca72379946ab61a4435eec2a41090821e713c17e8faf5b88f", size = 63780, upload-time = "2025-09-18T02:50:39.391Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/9e/8777c578b5b66f6ef99ce9dac4865b51016a52b1d681942fbf75ac35d60f/types_protobuf-6.30.2.20250809.tar.gz", hash = "sha256:b04f2998edf0d81bd8600bbd5db0b2adf547837eef6362ba364925cee21a33b4", size = 62204, upload-time = "2025-08-09T03:14:07.547Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/37/5a/8d93d4f4af5dc3dd62aa4f020deae746b34b1d94fb5bee1f776c6b7e9d6c/types_protobuf-6.32.1.20250918-py3-none-any.whl", hash = "sha256:22ba6133d142d11cc34d3788ad6dead2732368ebb0406eaa7790ea6ae46c8d0b", size = 77885, upload-time = "2025-09-18T02:50:38.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/9a/43daca708592570539888d80d6b708dff0b1795218aaf6b13057cc2e2c18/types_protobuf-6.30.2.20250809-py3-none-any.whl", hash = "sha256:7afc2d3f569d281dd22f339179577243be60bf7d1dfb4bc13d0109859fb1f1be", size = 76389, upload-time = "2025-08-09T03:14:06.531Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2713,7 +2691,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.35.4"
|
||||
version = "20.34.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
@@ -2721,9 +2699,9 @@ dependencies = [
|
||||
{ name = "platformdirs" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user