diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 30fa722b..8a6bd676 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -226,7 +226,7 @@ Problems installing? These commands have helped us install LNbits. sudo apt install pkg-config libffi-dev libpq-dev # build essentials for debian/ubuntu -sudo apt install python3.9-dev gcc build-essential +sudo apt install python3.10-dev gcc build-essential # if the secp256k1 build fails: # if you used poetry diff --git a/lnbits/core/models/audit.py b/lnbits/core/models/audit.py index 8e251a3b..1cfcc733 100644 --- a/lnbits/core/models/audit.py +++ b/lnbits/core/models/audit.py @@ -1,7 +1,6 @@ from __future__ import annotations from datetime import datetime, timedelta, timezone -from typing import Optional from pydantic import BaseModel, Field @@ -10,16 +9,16 @@ from lnbits.settings import settings class AuditEntry(BaseModel): - component: Optional[str] = None - ip_address: Optional[str] = None - user_id: Optional[str] = None - path: Optional[str] = None - request_type: Optional[str] = None - request_method: Optional[str] = None - request_details: Optional[str] = None - response_code: Optional[str] = None + component: str | None = None + ip_address: str | None = None + user_id: str | None = None + path: str | None = None + request_type: str | None = None + request_method: str | None = None + request_details: str | None = None + response_code: str | None = None duration: float - delete_at: Optional[datetime] = None + delete_at: datetime | None = None created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) def __init__(self, **data): @@ -42,12 +41,12 @@ class AuditFilters(FilterModel): "duration", ] - ip_address: Optional[str] = None - user_id: Optional[str] = None - path: Optional[str] = None - request_method: Optional[str] = None - response_code: Optional[str] = None - component: Optional[str] = None + ip_address: str | None = None + user_id: str | None = None + path: str | None = None + request_method: str | None = None + response_code: str | None = None + component: str | None = None class AuditCountStat(BaseModel): diff --git a/lnbits/core/models/extensions.py b/lnbits/core/models/extensions.py index 223b2184..f57e8ae1 100644 --- a/lnbits/core/models/extensions.py +++ b/lnbits/core/models/extensions.py @@ -7,7 +7,7 @@ import os import shutil import zipfile from pathlib import Path -from typing import Any, Optional +from typing import Any import httpx from loguru import logger @@ -29,17 +29,17 @@ class ExplicitRelease(BaseModel): archive: str hash: str dependencies: list[str] = [] - repo: Optional[str] - icon: Optional[str] - short_description: Optional[str] - min_lnbits_version: Optional[str] - max_lnbits_version: Optional[str] - html_url: Optional[str] # todo: release_url - warning: Optional[str] - info_notification: Optional[str] - critical_notification: Optional[str] - details_link: Optional[str] - pay_link: Optional[str] + repo: str | None + icon: str | None + short_description: str | None + min_lnbits_version: str | None + max_lnbits_version: str | None + html_url: str | None # todo: release_url + warning: str | None + info_notification: str | None + critical_notification: str | None + details_link: str | None + pay_link: str | None def is_version_compatible(self): return is_lnbits_version_ok(self.min_lnbits_version, self.max_lnbits_version) @@ -77,9 +77,9 @@ class ExtensionConfig(BaseModel): name: str short_description: str tile: str = "" - warning: Optional[str] = "" - min_lnbits_version: Optional[str] - max_lnbits_version: Optional[str] + warning: str | None = "" + min_lnbits_version: str | None + max_lnbits_version: str | None def is_version_compatible(self) -> bool: return is_lnbits_version_ok(self.min_lnbits_version, self.max_lnbits_version) @@ -87,7 +87,7 @@ class ExtensionConfig(BaseModel): @classmethod async def fetch_github_release_config( cls, org: str, repo: str, tag_name: str - ) -> Optional[ExtensionConfig]: + ) -> ExtensionConfig | None: config_url = ( f"https://raw.githubusercontent.com/{org}/{repo}/{tag_name}/config.json" ) @@ -97,28 +97,28 @@ class ExtensionConfig(BaseModel): class ReleasePaymentInfo(BaseModel): - amount: Optional[int] = None - pay_link: Optional[str] = None - payment_hash: Optional[str] = None - payment_request: Optional[str] = None + amount: int | None = None + pay_link: str | None = None + payment_hash: str | None = None + payment_request: str | None = None class PayToEnableInfo(BaseModel): amount: int = 0 required: bool = False - wallet: Optional[str] = None + wallet: str | None = None class UserExtensionInfo(BaseModel): - paid_to_enable: Optional[bool] = False - payment_hash_to_enable: Optional[str] = None + paid_to_enable: bool | None = False + payment_hash_to_enable: str | None = None class UserExtension(BaseModel): user: str extension: str active: bool - extra: Optional[UserExtensionInfo] = None + extra: UserExtensionInfo | None = None @property def is_paid(self) -> bool: @@ -140,10 +140,10 @@ class UserExtension(BaseModel): class Extension(BaseModel): code: str is_valid: bool - name: Optional[str] = None - short_description: Optional[str] = None - tile: Optional[str] = None - upgrade_hash: Optional[str] = "" + name: str | None = None + short_description: str | None = None + tile: str | None = None + upgrade_hash: str | None = "" @property def module_name(self) -> str: @@ -176,21 +176,21 @@ class ExtensionRelease(BaseModel): archive: str source_repo: str is_github_release: bool = False - hash: Optional[str] = None - min_lnbits_version: Optional[str] = None - max_lnbits_version: Optional[str] = None - is_version_compatible: Optional[bool] = True - html_url: Optional[str] = None - description: Optional[str] = None - warning: Optional[str] = None - repo: Optional[str] = None - icon: Optional[str] = None - details_link: Optional[str] = None + hash: str | None = None + min_lnbits_version: str | None = None + max_lnbits_version: str | None = None + is_version_compatible: bool | None = True + html_url: str | None = None + description: str | None = None + warning: str | None = None + repo: str | None = None + icon: str | None = None + details_link: str | None = None - pay_link: Optional[str] = None - cost_sats: Optional[int] = None - paid_sats: Optional[int] = 0 - payment_hash: Optional[str] = None + pay_link: str | None = None + cost_sats: int | None = None + paid_sats: int | None = 0 + payment_hash: str | None = None @property def archive_url(self) -> str: @@ -208,8 +208,8 @@ class ExtensionRelease(BaseModel): self.cost_sats = payment_info.amount if payment_info else None async def fetch_release_payment_info( - self, amount: Optional[int] = None - ) -> Optional[ReleasePaymentInfo]: + self, amount: int | None = None + ) -> ReleasePaymentInfo | None: url = f"{self.pay_link}?amount={amount}" if amount else self.pay_link assert url, "Missing URL for payment info." try: @@ -281,7 +281,7 @@ class ExtensionRelease(BaseModel): return [GitHubRepoRelease.parse_obj(r) for r in releases] @classmethod - async def fetch_release_details(cls, details_link: str) -> Optional[dict]: + async def fetch_release_details(cls, details_link: str) -> dict | None: try: async with httpx.AsyncClient() as client: @@ -300,12 +300,12 @@ class ExtensionRelease(BaseModel): class ExtensionMeta(BaseModel): - installed_release: Optional[ExtensionRelease] = None - latest_release: Optional[ExtensionRelease] = None - pay_to_enable: Optional[PayToEnableInfo] = None + installed_release: ExtensionRelease | None = None + latest_release: ExtensionRelease | None = None + pay_to_enable: PayToEnableInfo | None = None payments: list[ReleasePaymentInfo] = [] dependencies: list[str] = [] - archive: Optional[str] = None + archive: str | None = None featured: bool = False @@ -313,11 +313,11 @@ class InstallableExtension(BaseModel): id: str name: str version: str - active: Optional[bool] = False - short_description: Optional[str] = None - icon: Optional[str] = None + active: bool | None = False + short_description: str | None = None + icon: str | None = None stars: int = 0 - meta: Optional[ExtensionMeta] = None + meta: ExtensionMeta | None = None @property def hash(self) -> str: @@ -452,7 +452,7 @@ class InstallableExtension(BaseModel): shutil.rmtree(self.ext_upgrade_dir, True) - def check_latest_version(self, release: Optional[ExtensionRelease]): + def check_latest_version(self, release: ExtensionRelease | None): if not release: return if not self.meta or not self.meta.latest_release: @@ -465,9 +465,7 @@ class InstallableExtension(BaseModel): ): self.meta.latest_release = release - def find_existing_payment( - self, pay_link: Optional[str] - ) -> Optional[ReleasePaymentInfo]: + 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( @@ -507,7 +505,7 @@ class InstallableExtension(BaseModel): @classmethod async def from_github_release( cls, github_release: GitHubRelease - ) -> Optional[InstallableExtension]: + ) -> InstallableExtension | None: try: repo, latest_release, config = await cls.fetch_github_repo_info( github_release.organisation, github_release.repository @@ -546,7 +544,7 @@ class InstallableExtension(BaseModel): ) @classmethod - def from_ext_dir(cls, ext_id: str) -> Optional[InstallableExtension]: + def from_ext_dir(cls, ext_id: str) -> InstallableExtension | None: try: conf_path = Path( settings.lnbits_extensions_path, "extensions", ext_id, "config.json" @@ -657,7 +655,7 @@ class InstallableExtension(BaseModel): @classmethod async def get_extension_release( cls, ext_id: str, source_repo: str, archive: str, version: str - ) -> Optional[ExtensionRelease]: + ) -> ExtensionRelease | None: all_releases: list[ExtensionRelease] = ( await InstallableExtension.get_extension_releases(ext_id) ) @@ -708,8 +706,8 @@ class CreateExtension(BaseModel): archive: str source_repo: str version: str - cost_sats: Optional[int] = 0 - payment_hash: Optional[str] = None + cost_sats: int | None = 0 + payment_hash: str | None = None class ExtensionDetailsRequest(BaseModel): @@ -718,7 +716,7 @@ class ExtensionDetailsRequest(BaseModel): version: str -async def github_api_get(url: str, error_msg: Optional[str]) -> Any: +async def github_api_get(url: str, error_msg: str | None) -> Any: headers = {"User-Agent": settings.user_agent} if settings.lnbits_ext_github_token: headers["Authorization"] = f"Bearer {settings.lnbits_ext_github_token}" @@ -730,7 +728,7 @@ async def github_api_get(url: str, error_msg: Optional[str]) -> Any: return resp.json() -def icon_to_github_url(source_repo: str, path: Optional[str]) -> str: +def icon_to_github_url(source_repo: str, path: str | None) -> str: if not path: return "" _, _, *rest = path.split("/") diff --git a/lnbits/core/models/payments.py b/lnbits/core/models/payments.py index a8e006c6..7ed99d71 100644 --- a/lnbits/core/models/payments.py +++ b/lnbits/core/models/payments.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import datetime, timezone from enum import Enum -from typing import Literal, Optional +from typing import Literal from fastapi import Query from pydantic import BaseModel, Field, validator @@ -28,16 +28,16 @@ class PaymentState(str, Enum): class PaymentExtra(BaseModel): - comment: Optional[str] = None - success_action: Optional[str] = None - lnurl_response: Optional[str] = None + comment: str | None = None + success_action: str | None = None + lnurl_response: str | None = None class PayInvoice(BaseModel): payment_request: str - description: Optional[str] = None - max_sat: Optional[int] = None - extra: Optional[dict] = {} + description: str | None = None + max_sat: int | None = None + extra: dict | None = {} class CreatePayment(BaseModel): @@ -46,10 +46,10 @@ class CreatePayment(BaseModel): bolt11: str amount_msat: int memo: str - extra: Optional[dict] = {} - preimage: Optional[str] = None - expiry: Optional[datetime] = None - webhook: Optional[str] = None + extra: dict | None = {} + preimage: str | None = None + expiry: datetime | None = None + webhook: str | None = None fee: int = 0 @@ -61,13 +61,13 @@ class Payment(BaseModel): fee: int bolt11: str status: str = PaymentState.PENDING - memo: Optional[str] = None - expiry: Optional[datetime] = None - webhook: Optional[str] = None - webhook_status: Optional[int] = None - preimage: Optional[str] = None - tag: Optional[str] = None - extension: Optional[str] = None + memo: str | None = None + expiry: datetime | None = None + webhook: str | None = None + webhook_status: int | None = None + preimage: str | None = None + tag: str | None = None + extension: str | None = None time: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) @@ -129,16 +129,16 @@ class PaymentFilters(FilterModel): __sort_fields__ = ["created_at", "amount", "fee", "memo", "time", "tag"] - status: Optional[str] - tag: Optional[str] - checking_id: Optional[str] + status: str | None + tag: str | None + checking_id: str | None amount: int fee: int - memo: Optional[str] + memo: str | None time: datetime - preimage: Optional[str] - payment_hash: Optional[str] - wallet_id: Optional[str] + preimage: str | None + payment_hash: str | None + wallet_id: str | None class PaymentDataPoint(BaseModel): @@ -173,11 +173,11 @@ class PaymentWalletStats(BaseModel): class PaymentDailyStats(BaseModel): date: datetime balance: float = 0 - balance_in: Optional[float] = 0 - balance_out: Optional[float] = 0 + balance_in: float | None = 0 + balance_out: float | None = 0 payments_count: int = 0 - count_in: Optional[int] = 0 - count_out: Optional[int] = 0 + count_in: int | None = 0 + count_out: int | None = 0 fee: float = 0 @@ -190,7 +190,7 @@ class PaymentHistoryPoint(BaseModel): class DecodePayment(BaseModel): data: str - filter_fields: Optional[list[str]] = [] + filter_fields: list[str] | None = [] class CreateInvoice(BaseModel): @@ -198,14 +198,14 @@ class CreateInvoice(BaseModel): internal: bool = False out: bool = True amount: float = Query(None, ge=0) - memo: Optional[str] = None - description_hash: Optional[str] = None - unhashed_description: Optional[str] = None - expiry: Optional[int] = None - extra: Optional[dict] = None - webhook: Optional[str] = None - bolt11: Optional[str] = None - lnurl_callback: Optional[str] = None + memo: str | None = None + description_hash: str | None = None + unhashed_description: str | None = None + expiry: int | None = None + extra: dict | None = None + webhook: str | None = None + bolt11: str | None = None + lnurl_callback: str | None = None @validator("unit") @classmethod diff --git a/lnbits/core/models/users.py b/lnbits/core/models/users.py index 58984d11..ff384a2b 100644 --- a/lnbits/core/models/users.py +++ b/lnbits/core/models/users.py @@ -1,7 +1,6 @@ from __future__ import annotations from datetime import datetime, timezone -from typing import Optional from uuid import UUID from fastapi import Query @@ -17,16 +16,16 @@ from .wallets import Wallet class UserExtra(BaseModel): - email_verified: Optional[bool] = False - first_name: Optional[str] = None - last_name: Optional[str] = None - display_name: Optional[str] = None - picture: Optional[str] = None + email_verified: bool | None = False + first_name: str | None = None + last_name: str | None = None + display_name: str | None = None + picture: str | None = None # Auth provider, possible values: # - "env": the user was created automatically by the system # - "lnbits": the user was created via register form (username/pass or user_id only) # - "google | github | ...": the user was created using an SSO provider - provider: Optional[str] = "lnbits" # auth provider + provider: str | None = "lnbits" # auth provider class EndpointAccess(BaseModel): @@ -50,13 +49,13 @@ class AccessControlList(BaseModel): endpoints: list[EndpointAccess] = [] token_id_list: list[SimpleItem] = [] - def get_endpoint(self, path: str) -> Optional[EndpointAccess]: + def get_endpoint(self, path: str) -> EndpointAccess | None: for e in self.endpoints: if e.path == path: return e return None - def get_token_by_id(self, token_id: str) -> Optional[SimpleItem]: + def get_token_by_id(self, token_id: str) -> SimpleItem | None: for t in self.token_id_list: if t.id == token_id: return t @@ -71,7 +70,7 @@ class UserAcls(BaseModel): access_control_list: list[AccessControlList] = [] updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) - def get_acl_by_id(self, acl_id: str) -> Optional[AccessControlList]: + def get_acl_by_id(self, acl_id: str) -> AccessControlList | None: for acl in self.access_control_list: if acl.id == acl_id: return acl @@ -82,7 +81,7 @@ class UserAcls(BaseModel): acl for acl in self.access_control_list if acl.id != acl_id ] - def get_acl_by_token_id(self, token_id: str) -> Optional[AccessControlList]: + def get_acl_by_token_id(self, token_id: str) -> AccessControlList | None: for acl in self.access_control_list: if acl.get_token_by_id(token_id): return acl @@ -91,10 +90,10 @@ class UserAcls(BaseModel): class Account(BaseModel): id: str - username: Optional[str] = None - password_hash: Optional[str] = None - pubkey: Optional[str] = None - email: Optional[str] = None + username: str | None = None + password_hash: str | None = None + pubkey: str | None = None + email: str | None = None extra: UserExtra = UserExtra() created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) @@ -134,10 +133,10 @@ class Account(BaseModel): class AccountOverview(Account): - transaction_count: Optional[int] = 0 - wallet_count: Optional[int] = 0 - balance_msat: Optional[int] = 0 - last_payment: Optional[datetime] = None + transaction_count: int | None = 0 + wallet_count: int | None = 0 + balance_msat: int | None = 0 + last_payment: datetime | None = None class AccountFilters(FilterModel): @@ -151,20 +150,20 @@ class AccountFilters(FilterModel): "last_payment", ] - email: Optional[str] = None - user: Optional[str] = None - username: Optional[str] = None - pubkey: Optional[str] = None - wallet_id: Optional[str] = None + email: str | None = None + user: str | None = None + username: str | None = None + pubkey: str | None = None + wallet_id: str | None = None class User(BaseModel): id: str created_at: datetime updated_at: datetime - email: Optional[str] = None - username: Optional[str] = None - pubkey: Optional[str] = None + email: str | None = None + username: str | None = None + pubkey: str | None = None extensions: list[str] = [] wallets: list[Wallet] = [] admin: bool = False @@ -176,7 +175,7 @@ class User(BaseModel): def wallet_ids(self) -> list[str]: return [wallet.id for wallet in self.wallets] - def get_wallet(self, wallet_id: str) -> Optional[Wallet]: + def get_wallet(self, wallet_id: str) -> Wallet | None: w = [wallet for wallet in self.wallets if wallet.id == wallet_id] return w[0] if w else None @@ -192,33 +191,33 @@ class User(BaseModel): class RegisterUser(BaseModel): - email: Optional[str] = Query(default=None) + email: str | None = Query(default=None) username: str = Query(default=..., min_length=2, max_length=20) password: str = Query(default=..., min_length=8, max_length=50) password_repeat: str = Query(default=..., min_length=8, max_length=50) class CreateUser(BaseModel): - id: Optional[str] = Query(default=None) - email: Optional[str] = Query(default=None) - username: Optional[str] = Query(default=None, min_length=2, max_length=20) - password: Optional[str] = Query(default=None, min_length=8, max_length=50) - password_repeat: Optional[str] = Query(default=None, min_length=8, max_length=50) + id: str | None = Query(default=None) + email: str | None = Query(default=None) + username: str | None = Query(default=None, min_length=2, max_length=20) + password: str | None = Query(default=None, min_length=8, max_length=50) + password_repeat: str | None = Query(default=None, min_length=8, max_length=50) pubkey: str = Query(default=None, max_length=64) - extensions: Optional[list[str]] = None - extra: Optional[UserExtra] = None + extensions: list[str] | None = None + extra: UserExtra | None = None class UpdateUser(BaseModel): user_id: str - email: Optional[str] = Query(default=None) - username: Optional[str] = Query(default=..., min_length=2, max_length=20) - extra: Optional[UserExtra] = None + email: str | None = Query(default=None) + username: str | None = Query(default=..., min_length=2, max_length=20) + extra: UserExtra | None = None class UpdateUserPassword(BaseModel): user_id: str - password_old: Optional[str] = None + password_old: str | None = None password: str = Query(default=..., min_length=8, max_length=50) password_repeat: str = Query(default=..., min_length=8, max_length=50) username: str = Query(default=..., min_length=2, max_length=20) @@ -252,10 +251,10 @@ class LoginUsernamePassword(BaseModel): class AccessTokenPayload(BaseModel): sub: str - usr: Optional[str] = None - email: Optional[str] = None - auth_time: Optional[int] = 0 - api_token_id: Optional[str] = None + usr: str | None = None + email: str | None = None + auth_time: int | None = 0 + api_token_id: str | None = None class UpdateBalance(BaseModel): diff --git a/lnbits/core/models/wallets.py b/lnbits/core/models/wallets.py index 4b96546e..702d1ee2 100644 --- a/lnbits/core/models/wallets.py +++ b/lnbits/core/models/wallets.py @@ -5,7 +5,6 @@ import hmac from dataclasses import dataclass from datetime import datetime, timezone from enum import Enum -from typing import Optional from ecdsa import SECP256k1, SigningKey from pydantic import BaseModel, Field @@ -37,7 +36,7 @@ class Wallet(BaseModel): deleted: bool = False created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) - currency: Optional[str] = None + currency: str | None = None balance_msat: int = Field(default=0, no_database=True) extra: WalletExtra = WalletExtra() @@ -67,7 +66,7 @@ class Wallet(BaseModel): class CreateWallet(BaseModel): - name: Optional[str] = None + name: str | None = None class KeyType(Enum): diff --git a/lnbits/db.py b/lnbits/db.py index eafda464..91cfb56f 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -8,7 +8,7 @@ import time from contextlib import asynccontextmanager from datetime import datetime, timezone from enum import Enum -from typing import Any, Generic, Literal, Optional, TypeVar, Union, get_origin +from typing import Any, Generic, Literal, TypeVar, get_origin from loguru import logger from pydantic import BaseModel, ValidationError, root_validator @@ -65,8 +65,8 @@ def get_placeholder(model: Any, field: str) -> str: class Compat: - type: Optional[str] = "" - schema: Optional[str] = "" + type: str | None = "" + schema: str | None = "" def interval_seconds(self, seconds: int) -> str: if self.type in {POSTGRES, COCKROACH}: @@ -167,8 +167,8 @@ class Connection(Compat): async def fetchall( self, query: str, - values: Optional[dict] = None, - model: Optional[type[TModel]] = None, + values: dict | None = None, + model: type[TModel] | None = None, ) -> list[TModel]: params = self.rewrite_values(values) if values else {} result = await self.conn.execute(text(self.rewrite_query(query)), params) @@ -183,8 +183,8 @@ class Connection(Compat): async def fetchone( self, query: str, - values: Optional[dict] = None, - model: Optional[type[TModel]] = None, + values: dict | None = None, + model: type[TModel] | None = None, ) -> TModel: params = self.rewrite_values(values) if values else {} result = await self.conn.execute(text(self.rewrite_query(query)), params) @@ -211,11 +211,11 @@ class Connection(Compat): async def fetch_page( self, query: str, - where: Optional[list[str]] = None, - values: Optional[dict] = None, - filters: Optional[Filters] = None, - model: Optional[type[TModel]] = None, - group_by: Optional[list[str]] = None, + where: list[str] | None = None, + values: dict | None = None, + filters: Filters | None = None, + model: type[TModel] | None = None, + group_by: list[str] | None = None, ) -> Page[TModel]: if not filters: filters = Filters() @@ -268,7 +268,7 @@ class Connection(Compat): total=count, ) - async def execute(self, query: str, values: Optional[dict] = None): + async def execute(self, query: str, values: dict | None = None): params = self.rewrite_values(values) if values else {} result = await self.conn.execute(text(self.rewrite_query(query)), params) await self.conn.commit() @@ -350,8 +350,8 @@ class Database(Compat): async def fetchall( self, query: str, - values: Optional[dict] = None, - model: Optional[type[TModel]] = None, + values: dict | None = None, + model: type[TModel] | None = None, ) -> list[TModel]: async with self.connect() as conn: return await conn.fetchall(query, values, model) @@ -359,8 +359,8 @@ class Database(Compat): async def fetchone( self, query: str, - values: Optional[dict] = None, - model: Optional[type[TModel]] = None, + values: dict | None = None, + model: type[TModel] | None = None, ) -> TModel: async with self.connect() as conn: return await conn.fetchone(query, values, model) @@ -378,16 +378,16 @@ class Database(Compat): async def fetch_page( self, query: str, - where: Optional[list[str]] = None, - values: Optional[dict] = None, - filters: Optional[Filters] = None, - model: Optional[type[TModel]] = None, - group_by: Optional[list[str]] = None, + where: list[str] | None = None, + values: dict | None = None, + filters: Filters | None = None, + model: type[TModel] | None = None, + group_by: list[str] | None = None, ) -> Page[TModel]: async with self.connect() as conn: return await conn.fetch_page(query, where, values, filters, model, group_by) - async def execute(self, query: str, values: Optional[dict] = None): + async def execute(self, query: str, values: dict | None = None): async with self.connect() as conn: return await conn.execute(query, values) @@ -445,7 +445,7 @@ class Operator(Enum): class FilterModel(BaseModel): __search_fields__: list[str] = [] - __sort_fields__: Optional[list[str]] = None + __sort_fields__: list[str] | None = None T = TypeVar("T") @@ -461,8 +461,8 @@ class Page(BaseModel, Generic[T]): class Filter(BaseModel, Generic[TFilterModel]): field: str op: Operator = Operator.EQ - model: Optional[type[TFilterModel]] - values: Optional[dict] = None + model: type[TFilterModel] | None + values: dict | None = None @classmethod def parse_query( @@ -517,15 +517,15 @@ class Filters(BaseModel, Generic[TFilterModel]): """ filters: list[Filter[TFilterModel]] = [] - search: Optional[str] = None + search: str | None = None - offset: Optional[int] = None - limit: Optional[int] = None + offset: int | None = None + limit: int | None = None - sortby: Optional[str] = None - direction: Optional[Literal["asc", "desc"]] = None + sortby: str | None = None + direction: Literal["asc", "desc"] | None = None - model: Optional[type[TFilterModel]] = None + model: type[TFilterModel] | None = None @root_validator(pre=True) def validate_sortby(cls, values): @@ -547,7 +547,7 @@ class Filters(BaseModel, Generic[TFilterModel]): stmt += f"OFFSET {self.offset}" return stmt - def where(self, where_stmts: Optional[list[str]] = None) -> str: + def where(self, where_stmts: list[str] | None = None) -> str: if not where_stmts: where_stmts = [] if self.filters: @@ -567,7 +567,7 @@ class Filters(BaseModel, Generic[TFilterModel]): return f"ORDER BY {self.sortby} {self.direction or 'asc'}" return "" - def values(self, values: Optional[dict] = None) -> dict: + def values(self, values: dict | None = None) -> dict: if not values: values = {} if self.filters: @@ -641,7 +641,7 @@ def model_to_dict(model: BaseModel) -> dict: return _dict -def dict_to_submodel(model: type[TModel], value: Union[dict, str]) -> Optional[TModel]: +def dict_to_submodel(model: type[TModel], value: dict | str) -> TModel | None: """convert a dictionary or JSON string to a Pydantic model""" if isinstance(value, str): if value == "null": diff --git a/lnbits/nodes/base.py b/lnbits/nodes/base.py index b436eaad..a75f822f 100644 --- a/lnbits/nodes/base.py +++ b/lnbits/nodes/base.py @@ -2,7 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod from enum import Enum -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from pydantic import BaseModel @@ -15,10 +15,10 @@ if TYPE_CHECKING: class NodePeerInfo(BaseModel): id: str - alias: Optional[str] = None - color: Optional[str] = None - last_timestamp: Optional[int] = None - addresses: Optional[list[str]] = None + alias: str | None = None + color: str | None = None + last_timestamp: int | None = None + addresses: list[str] | None = None class ChannelState(Enum): @@ -47,20 +47,20 @@ class NodeChannel(BaseModel): balance: ChannelBalance state: ChannelState # could be optional for closing/pending channels on lndrest - id: Optional[str] = None - short_id: Optional[str] = None - point: Optional[ChannelPoint] = None - name: Optional[str] = None - color: Optional[str] = None - fee_ppm: Optional[int] = None - fee_base_msat: Optional[int] = None + id: str | None = None + short_id: str | None = None + point: ChannelPoint | None = None + name: str | None = None + color: str | None = None + fee_ppm: int | None = None + fee_base_msat: int | None = None class ChannelStats(BaseModel): counts: dict[ChannelState, int] avg_size: int - biggest_size: Optional[int] - smallest_size: Optional[int] + biggest_size: int | None + smallest_size: int | None total_capacity: int @classmethod @@ -95,9 +95,9 @@ class ChannelStats(BaseModel): class NodeFees(BaseModel): total_msat: int - daily_msat: Optional[int] = None - weekly_msat: Optional[int] = None - monthly_msat: Optional[int] = None + daily_msat: int | None = None + weekly_msat: int | None = None + monthly_msat: int | None = None class PublicNodeInfo(BaseModel): @@ -121,25 +121,25 @@ class NodeInfoResponse(PublicNodeInfo): class NodePayment(BaseModel): pending: bool amount: int - fee: Optional[int] = None - memo: Optional[str] = None + fee: int | None = None + memo: str | None = None time: int - bolt11: Optional[str] = None - preimage: Optional[str] + bolt11: str | None = None + preimage: str | None payment_hash: str - expiry: Optional[float] = None - destination: Optional[NodePeerInfo] = None + expiry: float | None = None + destination: NodePeerInfo | None = None class NodeInvoice(BaseModel): pending: bool amount: int - memo: Optional[str] + memo: str | None bolt11: str - preimage: Optional[str] + preimage: str | None payment_hash: str - paid_at: Optional[int] = None - expiry: Optional[int] = None + paid_at: int | None = None + expiry: int | None = None class NodeInvoiceFilters(FilterModel): @@ -154,7 +154,7 @@ class Node(ABC): def __init__(self, wallet: Wallet): self.wallet = wallet - self.id: Optional[str] = None + self.id: str | None = None @property def name(self): @@ -203,22 +203,22 @@ class Node(ABC): self, peer_id: str, local_amount: int, - push_amount: Optional[int] = None, - fee_rate: Optional[int] = None, + push_amount: int | None = None, + fee_rate: int | None = None, ) -> ChannelPoint: raise NotImplementedError @abstractmethod async def close_channel( self, - short_id: Optional[str] = None, - point: Optional[ChannelPoint] = None, + short_id: str | None = None, + point: ChannelPoint | None = None, force: bool = False, ): raise NotImplementedError @abstractmethod - async def get_channel(self, channel_id: str) -> Optional[NodeChannel]: + async def get_channel(self, channel_id: str) -> NodeChannel | None: raise NotImplementedError @abstractmethod diff --git a/lnbits/nodes/cln.py b/lnbits/nodes/cln.py index 2a49cdf6..be6c274f 100644 --- a/lnbits/nodes/cln.py +++ b/lnbits/nodes/cln.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio from http import HTTPStatus -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from fastapi import HTTPException @@ -119,8 +119,8 @@ class CoreLightningNode(Node): self, peer_id: str, local_amount: int, - push_amount: Optional[int] = None, - fee_rate: Optional[int] = None, + push_amount: int | None = None, + fee_rate: int | None = None, ) -> ChannelPoint: try: result = await self.ln_rpc( @@ -173,8 +173,8 @@ class CoreLightningNode(Node): @catch_rpc_errors async def close_channel( self, - short_id: Optional[str] = None, - point: Optional[ChannelPoint] = None, + short_id: str | None = None, + point: ChannelPoint | None = None, force: bool = False, ): if not short_id: @@ -229,7 +229,7 @@ class CoreLightningNode(Node): await self.ln_rpc("setchannel", channel_id, feebase=base_msat, feeppm=ppm) @catch_rpc_errors - async def get_channel(self, channel_id: str) -> Optional[NodeChannel]: + async def get_channel(self, channel_id: str) -> NodeChannel | None: channels = await self.get_channels() for channel in channels: if channel.id == channel_id: diff --git a/lnbits/nodes/lndrest.py b/lnbits/nodes/lndrest.py index 18a58ad6..61861b5f 100644 --- a/lnbits/nodes/lndrest.py +++ b/lnbits/nodes/lndrest.py @@ -4,7 +4,7 @@ import asyncio import base64 import json from http import HTTPStatus -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from fastapi import HTTPException from httpx import HTTPStatusError @@ -60,9 +60,7 @@ def _parse_channel_point(raw: str) -> ChannelPoint: class LndRestNode(Node): wallet: LndRestWallet - async def request( - self, method: str, path: str, json: Optional[dict] = None, **kwargs - ): + async def request(self, method: str, path: str, json: dict | None = None, **kwargs): response = await self.wallet.client.request( method, f"{self.wallet.endpoint}{path}", json=json, **kwargs ) @@ -131,8 +129,8 @@ class LndRestNode(Node): self, peer_id: str, local_amount: int, - push_amount: Optional[int] = None, - fee_rate: Optional[int] = None, + push_amount: int | None = None, + fee_rate: int | None = None, ) -> ChannelPoint: response = await self.request( "POST", @@ -176,8 +174,8 @@ class LndRestNode(Node): async def close_channel( self, - short_id: Optional[str] = None, - point: Optional[ChannelPoint] = None, + short_id: str | None = None, + point: ChannelPoint | None = None, force: bool = False, ): if short_id: @@ -218,7 +216,7 @@ class LndRestNode(Node): }, ) - async def get_channel(self, channel_id: str) -> Optional[NodeChannel]: + async def get_channel(self, channel_id: str) -> NodeChannel | None: channel_info = await self.get(f"/v1/graph/edge/{channel_id}") info = await self.get("/v1/getinfo") if info["identity_pubkey"] == channel_info["node1_pub"]: diff --git a/lnbits/settings.py b/lnbits/settings.py index 344e2ea6..85f431e3 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -11,7 +11,7 @@ from hashlib import sha256 from os import path from pathlib import Path from time import gmtime, strftime, time -from typing import Any, Optional +from typing import Any import httpx from loguru import logger @@ -77,7 +77,7 @@ class RedirectPath(BaseModel): other.from_path, list(other.header_filters.items()) ) or other.redirect_matches(self.from_path, list(self.header_filters.items())) - def find_in_conflict(self, others: list[RedirectPath]) -> Optional[RedirectPath]: + def find_in_conflict(self, others: list[RedirectPath]) -> RedirectPath | None: for other in others: if self.in_conflict(other): return other @@ -153,7 +153,7 @@ class InstalledExtensionsSettings(LNbitsSettings): def find_extension_redirect( self, path: str, req_headers: list[tuple[bytes, bytes]] - ) -> Optional[RedirectPath]: + ) -> RedirectPath | None: headers = [(k.decode(), v.decode()) for k, v in req_headers] return next( ( @@ -167,8 +167,8 @@ class InstalledExtensionsSettings(LNbitsSettings): def activate_extension_paths( self, ext_id: str, - upgrade_hash: Optional[str] = None, - ext_redirects: Optional[list[dict]] = None, + upgrade_hash: str | None = None, + ext_redirects: list[dict] | None = None, ): self.lnbits_deactivated_extensions.discard(ext_id) @@ -231,12 +231,12 @@ class ExchangeHistorySettings(LNbitsSettings): class ThemesSettings(LNbitsSettings): lnbits_site_title: str = Field(default="LNbits") lnbits_site_tagline: str = Field(default="free and open-source lightning wallet") - lnbits_site_description: Optional[str] = Field( + lnbits_site_description: str | None = Field( default="The world's most powerful suite of bitcoin tools." ) lnbits_show_home_page_elements: bool = Field(default=True) lnbits_default_wallet_name: str = Field(default="LNbits wallet") - lnbits_custom_badge: Optional[str] = Field(default=None) + lnbits_custom_badge: str | None = Field(default=None) lnbits_custom_badge_color: str = Field(default="warning") lnbits_theme_options: list[str] = Field( default=[ @@ -251,17 +251,15 @@ class ThemesSettings(LNbitsSettings): "bitcoin", ] ) - lnbits_custom_logo: Optional[str] = Field(default=None) - lnbits_custom_image: Optional[str] = Field( - default="/static/images/logos/lnbits.svg" - ) + lnbits_custom_logo: str | None = Field(default=None) + lnbits_custom_image: str | None = Field(default="/static/images/logos/lnbits.svg") lnbits_ad_space_title: str = Field(default="Supported by") lnbits_ad_space: str = Field( default="https://shop.lnbits.com/;/static/images/bitcoin-shop-banner.png;/static/images/bitcoin-shop-banner.png,https://affil.trezor.io/aff_c?offer_id=169&aff_id=33845;/static/images/bitcoin-hardware-wallet.png;/static/images/bitcoin-hardware-wallet.png,https://opensats.org/;/static/images/open-sats.png;/static/images/open-sats.png" ) # sneaky sneaky lnbits_ad_space_enabled: bool = Field(default=False) lnbits_allowed_currencies: list[str] = Field(default=[]) - lnbits_default_accounting_currency: Optional[str] = Field(default=None) + lnbits_default_accounting_currency: str | None = Field(default=None) lnbits_qr_logo: str = Field(default="/static/images/logos/lnbits.png") lnbits_default_reaction: str = Field(default="confettiBothSides") lnbits_default_theme: str = Field(default="salvador") @@ -282,7 +280,7 @@ class FeeSettings(LNbitsSettings): lnbits_service_fee: float = Field(default=0) lnbits_service_fee_ignore_internal: bool = Field(default=True) lnbits_service_fee_max: int = Field(default=0) - lnbits_service_fee_wallet: Optional[str] = Field(default=None) + lnbits_service_fee_wallet: str | None = Field(default=None) # WARN: this same value must be used for balance check and passed to # funding_source.pay_invoice(), it may cause a vulnerability if the values differ @@ -413,121 +411,121 @@ class FakeWalletFundingSource(LNbitsSettings): class LNbitsFundingSource(LNbitsSettings): lnbits_endpoint: str = Field(default="https://demo.lnbits.com") - lnbits_key: Optional[str] = Field(default=None) - lnbits_admin_key: Optional[str] = Field(default=None) - lnbits_invoice_key: Optional[str] = Field(default=None) + lnbits_key: str | None = Field(default=None) + lnbits_admin_key: str | None = Field(default=None) + lnbits_invoice_key: str | None = Field(default=None) class ClicheFundingSource(LNbitsSettings): - cliche_endpoint: Optional[str] = Field(default=None) + cliche_endpoint: str | None = Field(default=None) class CoreLightningFundingSource(LNbitsSettings): - corelightning_rpc: Optional[str] = Field(default=None) + corelightning_rpc: str | None = Field(default=None) corelightning_pay_command: str = Field(default="pay") - clightning_rpc: Optional[str] = Field(default=None) + clightning_rpc: str | None = Field(default=None) class CoreLightningRestFundingSource(LNbitsSettings): - corelightning_rest_url: Optional[str] = Field(default=None) - corelightning_rest_macaroon: Optional[str] = Field(default=None) - corelightning_rest_cert: Optional[str] = Field(default=None) + corelightning_rest_url: str | None = Field(default=None) + corelightning_rest_macaroon: str | None = Field(default=None) + corelightning_rest_cert: str | None = Field(default=None) class EclairFundingSource(LNbitsSettings): - eclair_url: Optional[str] = Field(default=None) - eclair_pass: Optional[str] = Field(default=None) + eclair_url: str | None = Field(default=None) + eclair_pass: str | None = Field(default=None) class LndRestFundingSource(LNbitsSettings): - lnd_rest_endpoint: Optional[str] = Field(default=None) - lnd_rest_cert: Optional[str] = Field(default=None) - lnd_rest_macaroon: Optional[str] = Field(default=None) - lnd_rest_macaroon_encrypted: Optional[str] = Field(default=None) + lnd_rest_endpoint: str | None = Field(default=None) + lnd_rest_cert: str | None = Field(default=None) + lnd_rest_macaroon: str | None = Field(default=None) + lnd_rest_macaroon_encrypted: str | None = Field(default=None) lnd_rest_route_hints: bool = Field(default=True) lnd_rest_allow_self_payment: bool = Field(default=False) - lnd_cert: Optional[str] = Field(default=None) - lnd_admin_macaroon: Optional[str] = Field(default=None) - lnd_invoice_macaroon: Optional[str] = Field(default=None) - lnd_rest_admin_macaroon: Optional[str] = Field(default=None) - lnd_rest_invoice_macaroon: Optional[str] = Field(default=None) + lnd_cert: str | None = Field(default=None) + lnd_admin_macaroon: str | None = Field(default=None) + lnd_invoice_macaroon: str | None = Field(default=None) + lnd_rest_admin_macaroon: str | None = Field(default=None) + lnd_rest_invoice_macaroon: str | None = Field(default=None) class LndGrpcFundingSource(LNbitsSettings): - lnd_grpc_endpoint: Optional[str] = Field(default=None) - lnd_grpc_cert: Optional[str] = Field(default=None) - lnd_grpc_port: Optional[int] = Field(default=None) - lnd_grpc_admin_macaroon: Optional[str] = Field(default=None) - lnd_grpc_invoice_macaroon: Optional[str] = Field(default=None) - lnd_grpc_macaroon: Optional[str] = Field(default=None) - lnd_grpc_macaroon_encrypted: Optional[str] = Field(default=None) + lnd_grpc_endpoint: str | None = Field(default=None) + lnd_grpc_cert: str | None = Field(default=None) + lnd_grpc_port: int | None = Field(default=None) + lnd_grpc_admin_macaroon: str | None = Field(default=None) + lnd_grpc_invoice_macaroon: str | None = Field(default=None) + lnd_grpc_macaroon: str | None = Field(default=None) + lnd_grpc_macaroon_encrypted: str | None = Field(default=None) class LnPayFundingSource(LNbitsSettings): - lnpay_api_endpoint: Optional[str] = Field(default=None) - lnpay_api_key: Optional[str] = Field(default=None) - lnpay_wallet_key: Optional[str] = Field(default=None) - lnpay_admin_key: Optional[str] = Field(default=None) + lnpay_api_endpoint: str | None = Field(default=None) + lnpay_api_key: str | None = Field(default=None) + lnpay_wallet_key: str | None = Field(default=None) + lnpay_admin_key: str | None = Field(default=None) class BlinkFundingSource(LNbitsSettings): - blink_api_endpoint: Optional[str] = Field(default="https://api.blink.sv/graphql") - blink_ws_endpoint: Optional[str] = Field(default="wss://ws.blink.sv/graphql") - blink_token: Optional[str] = Field(default=None) + blink_api_endpoint: str | None = Field(default="https://api.blink.sv/graphql") + blink_ws_endpoint: str | None = Field(default="wss://ws.blink.sv/graphql") + blink_token: str | None = Field(default=None) class ZBDFundingSource(LNbitsSettings): - zbd_api_endpoint: Optional[str] = Field(default="https://api.zebedee.io/v0/") - zbd_api_key: Optional[str] = Field(default=None) + zbd_api_endpoint: str | None = Field(default="https://api.zebedee.io/v0/") + zbd_api_key: str | None = Field(default=None) class PhoenixdFundingSource(LNbitsSettings): - phoenixd_api_endpoint: Optional[str] = Field(default="http://localhost:9740/") - phoenixd_api_password: Optional[str] = Field(default=None) + phoenixd_api_endpoint: str | None = Field(default="http://localhost:9740/") + phoenixd_api_password: str | None = Field(default=None) class AlbyFundingSource(LNbitsSettings): - alby_api_endpoint: Optional[str] = Field(default="https://api.getalby.com/") - alby_access_token: Optional[str] = Field(default=None) + alby_api_endpoint: str | None = Field(default="https://api.getalby.com/") + alby_access_token: str | None = Field(default=None) class OpenNodeFundingSource(LNbitsSettings): - opennode_api_endpoint: Optional[str] = Field(default=None) - opennode_key: Optional[str] = Field(default=None) - opennode_admin_key: Optional[str] = Field(default=None) - opennode_invoice_key: Optional[str] = Field(default=None) + opennode_api_endpoint: str | None = Field(default=None) + opennode_key: str | None = Field(default=None) + opennode_admin_key: str | None = Field(default=None) + opennode_invoice_key: str | None = Field(default=None) class SparkFundingSource(LNbitsSettings): - spark_url: Optional[str] = Field(default=None) - spark_token: Optional[str] = Field(default=None) + spark_url: str | None = Field(default=None) + spark_token: str | None = Field(default=None) class LnTipsFundingSource(LNbitsSettings): - lntips_api_endpoint: Optional[str] = Field(default=None) - lntips_api_key: Optional[str] = Field(default=None) - lntips_admin_key: Optional[str] = Field(default=None) - lntips_invoice_key: Optional[str] = Field(default=None) + lntips_api_endpoint: str | None = Field(default=None) + lntips_api_key: str | None = Field(default=None) + lntips_admin_key: str | None = Field(default=None) + lntips_invoice_key: str | None = Field(default=None) class NWCFundingSource(LNbitsSettings): - nwc_pairing_url: Optional[str] = Field(default=None) + nwc_pairing_url: str | None = Field(default=None) class BreezSdkFundingSource(LNbitsSettings): - breez_api_key: Optional[str] = Field(default=None) - breez_greenlight_seed: Optional[str] = Field(default=None) - breez_greenlight_invite_code: Optional[str] = Field(default=None) - breez_greenlight_device_key: Optional[str] = Field(default=None) - breez_greenlight_device_cert: Optional[str] = Field(default=None) + breez_api_key: str | None = Field(default=None) + breez_greenlight_seed: str | None = Field(default=None) + breez_greenlight_invite_code: str | None = Field(default=None) + breez_greenlight_device_key: str | None = Field(default=None) + breez_greenlight_device_cert: str | None = Field(default=None) breez_use_trampoline: bool = Field(default=True) class BoltzFundingSource(LNbitsSettings): - boltz_client_endpoint: Optional[str] = Field(default="127.0.0.1:9002") - boltz_client_macaroon: Optional[str] = Field(default=None) - boltz_client_wallet: Optional[str] = Field(default="lnbits") - boltz_client_cert: Optional[str] = Field(default=None) + boltz_client_endpoint: str | None = Field(default="127.0.0.1:9002") + boltz_client_macaroon: str | None = Field(default=None) + boltz_client_wallet: str | None = Field(default="lnbits") + boltz_client_cert: str | None = Field(default=None) class LightningSettings(LNbitsSettings): @@ -562,8 +560,8 @@ class FundingSourcesSettings( class WebPushSettings(LNbitsSettings): - lnbits_webpush_pubkey: Optional[str] = Field(default=None) - lnbits_webpush_privkey: Optional[str] = Field(default=None) + lnbits_webpush_pubkey: str | None = Field(default=None) + lnbits_webpush_privkey: str | None = Field(default=None) class NodeUISettings(LNbitsSettings): @@ -669,9 +667,9 @@ class AuditSettings(LNbitsSettings): def audit_http_request( self, - http_method: Optional[str] = None, - path: Optional[str] = None, - http_response_code: Optional[str] = None, + http_method: str | None = None, + path: str | None = None, + http_response_code: str | None = None, ) -> bool: if not self.lnbits_audit_enabled: return False @@ -689,7 +687,7 @@ class AuditSettings(LNbitsSettings): return True - def _is_http_request_path_auditable(self, path: Optional[str]): + def _is_http_request_path_auditable(self, path: str | None): if len(self.lnbits_audit_exclude_paths) != 0 and path: for exclude_path in self.lnbits_audit_exclude_paths: if _re_fullmatch_safe(exclude_path, path): @@ -706,9 +704,7 @@ class AuditSettings(LNbitsSettings): return False - def _is_http_response_code_auditable( - self, http_response_code: Optional[str] - ) -> bool: + def _is_http_response_code_auditable(self, http_response_code: str | None) -> bool: if not http_response_code: # No response code means only request filters should apply return True @@ -805,9 +801,9 @@ class EnvSettings(LNbitsSettings): class SaaSSettings(LNbitsSettings): - lnbits_saas_callback: Optional[str] = Field(default=None) - lnbits_saas_secret: Optional[str] = Field(default=None) - lnbits_saas_instance_id: Optional[str] = Field(default=None) + lnbits_saas_callback: str | None = Field(default=None) + lnbits_saas_secret: str | None = Field(default=None) + lnbits_saas_instance_id: str | None = Field(default=None) class PersistenceSettings(LNbitsSettings): @@ -905,7 +901,7 @@ class Settings(EditableSettings, ReadOnlySettings, TransientSettings, BaseSettin or user_id == self.super_user ) - def is_super_user(self, user_id: Optional[str] = None) -> bool: + def is_super_user(self, user_id: str | None = None) -> bool: return user_id == self.super_user def is_admin_user(self, user_id: str) -> bool: @@ -924,12 +920,12 @@ class SuperSettings(EditableSettings): class AdminSettings(EditableSettings): is_super_user: bool - lnbits_allowed_funding_sources: Optional[list[str]] + lnbits_allowed_funding_sources: list[str] | None class SettingsField(BaseModel): id: str - value: Optional[Any] + value: Any | None tag: str = "core" diff --git a/lnbits/utils/cache.py b/lnbits/utils/cache.py index 8abcfbbe..aa22c4e7 100644 --- a/lnbits/utils/cache.py +++ b/lnbits/utils/cache.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio from time import time -from typing import Any, NamedTuple, Optional +from typing import Any, NamedTuple from loguru import logger @@ -23,7 +23,7 @@ class Cache: self.interval = interval self._values: dict[Any, Cached] = {} - def get(self, key: str, default=None) -> Optional[Any]: + def get(self, key: str, default=None) -> Any | None: cached = self._values.get(key) if cached is not None: if cached.expiry > time(): @@ -35,7 +35,7 @@ class Cache: def set(self, key: str, value: Any, expiry: float = 10): self._values[key] = Cached(value, time() + expiry) - def pop(self, key: str, default=None) -> Optional[Any]: + def pop(self, key: str, default=None) -> Any | None: cached = self._values.pop(key, None) if cached and cached.expiry > time(): return cached.value diff --git a/lnbits/wallets/__init__.py b/lnbits/wallets/__init__.py index a197d6e9..5bf0095d 100644 --- a/lnbits/wallets/__init__.py +++ b/lnbits/wallets/__init__.py @@ -1,7 +1,6 @@ from __future__ import annotations import importlib -from typing import Optional from lnbits.nodes import set_node_class from lnbits.settings import settings @@ -33,7 +32,7 @@ from .void import VoidWallet from .zbd import ZBDWallet -def set_funding_source(class_name: Optional[str] = None): +def set_funding_source(class_name: str | None = None): backend_wallet_class = class_name or settings.lnbits_backend_wallet_class funding_source_constructor = getattr(wallets_module, backend_wallet_class) global funding_source diff --git a/lnbits/wallets/base.py b/lnbits/wallets/base.py index c416224f..ff024a07 100644 --- a/lnbits/wallets/base.py +++ b/lnbits/wallets/base.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, AsyncGenerator, Coroutine, NamedTuple, Optional +from typing import TYPE_CHECKING, AsyncGenerator, Coroutine, NamedTuple from loguru import logger @@ -11,15 +11,15 @@ if TYPE_CHECKING: class StatusResponse(NamedTuple): - error_message: Optional[str] + error_message: str | None balance_msat: int class InvoiceResponse(NamedTuple): ok: bool - checking_id: Optional[str] = None # payment_hash, rpc_id - payment_request: Optional[str] = None - error_message: Optional[str] = None + checking_id: str | None = None # payment_hash, rpc_id + payment_request: str | None = None + error_message: str | None = None @property def success(self) -> bool: @@ -36,11 +36,11 @@ class InvoiceResponse(NamedTuple): class PaymentResponse(NamedTuple): # when ok is None it means we don't know if this succeeded - ok: Optional[bool] = None - checking_id: Optional[str] = None # payment_hash, rcp_id - fee_msat: Optional[int] = None - preimage: Optional[str] = None - error_message: Optional[str] = None + ok: bool | None = None + checking_id: str | None = None # payment_hash, rcp_id + fee_msat: int | None = None + preimage: str | None = None + error_message: str | None = None @property def success(self) -> bool: @@ -56,9 +56,9 @@ class PaymentResponse(NamedTuple): class PaymentStatus(NamedTuple): - paid: Optional[bool] = None - fee_msat: Optional[int] = None - preimage: Optional[str] = None + paid: bool | None = None + fee_msat: int | None = None + preimage: str | None = None @property def success(self) -> bool: @@ -94,7 +94,7 @@ class PaymentPendingStatus(PaymentStatus): class Wallet(ABC): - __node_cls__: Optional[type[Node]] = None + __node_cls__: type[Node] | None = None def __init__(self) -> None: self.pending_invoices: list[str] = [] @@ -111,9 +111,9 @@ class Wallet(ABC): def create_invoice( self, amount: int, - memo: Optional[str] = None, - description_hash: Optional[bytes] = None, - unhashed_description: Optional[bytes] = None, + memo: str | None = None, + description_hash: bytes | None = None, + unhashed_description: bytes | None = None, **kwargs, ) -> Coroutine[None, None, InvoiceResponse]: pass diff --git a/poetry.lock b/poetry.lock index 6f62a622..453668bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1395,9 +1395,6 @@ files = [ {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, ] -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] @@ -2916,7 +2913,6 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] @@ -3400,26 +3396,11 @@ files = [ {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] -[[package]] -name = "zipp" -version = "3.19.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [extras] breez = ["breez-sdk"] liquid = ["wallycore"] [metadata] lock-version = "2.0" -python-versions = "^3.13 | ^3.12 | ^3.11 | ^3.10 | ^3.9" -content-hash = "e263865649975ea7e977b3cbf6cb453c3653de115523d026e3863605ab48a463" +python-versions = "~3.12 | ~3.11 | ~3.10" +content-hash = "96dd180aaa4fbfeb34fa6f9647c8684fce183a72b1b41d22101a9dd4b962fa2e" diff --git a/pyproject.toml b/pyproject.toml index e280a5cf..dc9a5104 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.13 | ^3.12 | ^3.11 | ^3.10 | ^3.9" +python = "~3.12 | ~3.11 | ~3.10" bech32 = "1.2.0" click = "8.1.7" ecdsa = "0.19.0" @@ -192,7 +192,7 @@ extend-exclude = [ select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"] # UP007: pyupgrade: use X | Y instead of Optional. (python3.10) # RUF012: mutable-class-default -ignore = ["UP007", "RUF012"] +ignore = ["RUF012"] # Allow autofix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"]