async postgres

add timestamp conversation

pending fixed now

statement

repair conv

remove cleandbsetting
This commit is contained in:
dni ⚡
2024-09-05 12:38:50 +02:00
parent 08ea83c28e
commit 835f56cee6
10 changed files with 156 additions and 102 deletions
+2 -2
View File
@@ -226,12 +226,12 @@ LNBITS_HIDE_API=false
LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos" LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos"
# Database: to use SQLite, specify LNBITS_DATA_FOLDER # Database: to use SQLite, specify LNBITS_DATA_FOLDER
# to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... # to use PostgreSQL, specify LNBITS_DATABASE_URL=postgresql+asyncpg://...
# to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... # to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://...
# for both PostgreSQL and CockroachDB, you'll need to install # for both PostgreSQL and CockroachDB, you'll need to install
# psycopg2 as an additional dependency # psycopg2 as an additional dependency
LNBITS_DATA_FOLDER="./data" LNBITS_DATA_FOLDER="./data"
# LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" # LNBITS_DATABASE_URL="postgresql+asyncpg://user:password@host:port/databasename"
# the service fee (in percent) # the service fee (in percent)
LNBITS_SERVICE_FEE=0.0 LNBITS_SERVICE_FEE=0.0
+2 -1
View File
@@ -62,6 +62,7 @@ from .middleware import (
) )
from .requestvars import g from .requestvars import g
from .tasks import ( from .tasks import (
check_pending_payments,
create_task, create_task,
internal_invoice_listener, internal_invoice_listener,
invoice_listener, invoice_listener,
@@ -409,7 +410,7 @@ def register_async_tasks(app: FastAPI):
if not settings.lnbits_extensions_deactivate_all: if not settings.lnbits_extensions_deactivate_all:
create_task(check_and_register_extensions(app)) create_task(check_and_register_extensions(app))
# create_permanent_task(check_pending_payments) create_permanent_task(check_pending_payments)
create_permanent_task(invoice_listener) create_permanent_task(invoice_listener)
create_permanent_task(internal_invoice_listener) create_permanent_task(internal_invoice_listener)
create_permanent_task(cache.invalidate_forever) create_permanent_task(cache.invalidate_forever)
+8 -8
View File
@@ -199,10 +199,7 @@ async def get_user_password(user_id: str) -> Optional[str]:
"SELECT pass FROM accounts WHERE id = :user", "SELECT pass FROM accounts WHERE id = :user",
{"user": user_id}, {"user": user_id},
) )
if not row: return row.get("pass")
return None
return row[0]
# TODO: refactor not a crud function # TODO: refactor not a crud function
@@ -488,7 +485,7 @@ async def get_user_active_extensions_ids(
""", """,
{"user": user_id}, {"user": user_id},
) )
return [e[0] for e in rows] return [e.get("extension", "") for e in rows]
async def update_user_extension_extra( async def update_user_extension_extra(
@@ -682,7 +679,7 @@ async def get_wallet_for_key(
async def get_total_balance(conn: Optional[Connection] = None): async def get_total_balance(conn: Optional[Connection] = None):
row = await (conn or db).fetchone("SELECT SUM(balance) FROM balances") row = await (conn or db).fetchone("SELECT SUM(balance) FROM balances")
return 0 if row[0] is None else row[0] return row.get("balance", 0)
# wallet payments # wallet payments
@@ -1066,10 +1063,13 @@ async def get_payments_history(
results.insert( results.insert(
0, 0,
PaymentHistoryPoint( PaymentHistoryPoint(
balance=balance, date=row[0], income=row[1], spending=row[2] balance=balance,
date=row.get("date", 0),
income=row.get("income", 0),
spending=row.get("spending", 0),
), ),
) )
balance -= row.income - row.spending balance -= row.get("income", 0) - row.get("spending", 0)
return results return results
+2 -3
View File
@@ -7,7 +7,6 @@ import json
import time import time
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from sqlite3 import Row
from typing import Callable, Optional from typing import Callable, Optional
from ecdsa import SECP256k1, SigningKey from ecdsa import SECP256k1, SigningKey
@@ -238,7 +237,7 @@ class Payment(FromRowModel):
return self.status == PaymentState.FAILED.value return self.status == PaymentState.FAILED.value
@classmethod @classmethod
def from_row(cls, row: Row): def from_row(cls, row: dict):
return cls( return cls(
checking_id=row["checking_id"], checking_id=row["checking_id"],
payment_hash=row["hash"] or "0" * 64, payment_hash=row["hash"] or "0" * 64,
@@ -341,7 +340,7 @@ class TinyURL(BaseModel):
time: float time: float
@classmethod @classmethod
def from_row(cls, row: Row): def from_row(cls, row: dict):
return cls(**dict(row)) return cls(**dict(row))
+60 -55
View File
@@ -7,11 +7,11 @@ import re
import time import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from enum import Enum from enum import Enum
from sqlite3 import Row
from typing import Any, Generic, Literal, Optional, TypeVar from typing import Any, Generic, Literal, Optional, TypeVar
from loguru import logger from loguru import logger
from pydantic import BaseModel, ValidationError, root_validator from pydantic import BaseModel, ValidationError, root_validator
from sqlalchemy import event
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine
from sqlalchemy.sql import text from sqlalchemy.sql import text
@@ -23,31 +23,16 @@ SQLITE = "SQLITE"
if settings.lnbits_database_url: if settings.lnbits_database_url:
database_uri = settings.lnbits_database_url database_uri = settings.lnbits_database_url
if database_uri.startswith("cockroachdb://"): if database_uri.startswith("cockroachdb://"):
DB_TYPE = COCKROACH DB_TYPE = COCKROACH
else: else:
if not database_uri.startswith("postgresql+asyncpg://"):
raise ValueError(
"Please use the 'postgresql+asyncpg://...' "
"format for the database URL."
)
DB_TYPE = POSTGRES DB_TYPE = POSTGRES
from psycopg2.extensions import DECIMAL, new_type, register_type
def _parse_timestamp(value, _):
if value is None:
return None
f = "%Y-%m-%d %H:%M:%S.%f"
if "." not in value:
f = "%Y-%m-%d %H:%M:%S"
return time.mktime(datetime.datetime.strptime(value, f).timetuple())
register_type(
new_type(
DECIMAL.values,
"DEC2FLOAT",
lambda value, curs: float(value) if value is not None else None,
)
)
register_type(new_type((1184, 1114), "TIMESTAMP2INT", _parse_timestamp))
else: else:
if not os.path.isdir(settings.lnbits_data_folder): if not os.path.isdir(settings.lnbits_data_folder):
os.mkdir(settings.lnbits_data_folder) os.mkdir(settings.lnbits_data_folder)
@@ -161,15 +146,17 @@ class Connection(Compat):
clean_values[key] = raw_value clean_values[key] = raw_value
return clean_values return clean_values
async def fetchall(self, query: str, values: Optional[dict] = None) -> list: async def fetchall(self, query: str, values: Optional[dict] = None) -> list[dict]:
params = self.rewrite_values(values) if values else {} params = self.rewrite_values(values) if values else {}
result = await self.conn.execute(text(self.rewrite_query(query)), params) result = await self.conn.execute(text(self.rewrite_query(query)), params)
return result.fetchall() row = result.mappings().all()
result.close()
return row
async def fetchone(self, query: str, values: Optional[dict] = None): async def fetchone(self, query: str, values: Optional[dict] = None) -> dict:
params = self.rewrite_values(values) if values else {} params = self.rewrite_values(values) if values else {}
result = await self.conn.execute(text(self.rewrite_query(query)), params) result = await self.conn.execute(text(self.rewrite_query(query)), params)
row = result.fetchone() row = result.mappings().first()
result.close() result.close()
return row return row
@@ -209,9 +196,9 @@ class Connection(Compat):
if rows: if rows:
# no need for extra query if no pagination is specified # no need for extra query if no pagination is specified
if filters.offset or filters.limit: if filters.offset or filters.limit:
count = await self.fetchone( result = await self.fetchone(
f""" f"""
SELECT COUNT(*) FROM ( SELECT COUNT(*) as count FROM (
{query} {query}
{clause} {clause}
{group_by_string} {group_by_string}
@@ -219,20 +206,22 @@ class Connection(Compat):
""", """,
parsed_values, parsed_values,
) )
count = int(count[0]) count = int(result.get("count", 0))
else: else:
count = len(rows) count = len(rows)
else: else:
count = 0 count = 0
return Page( return Page(
data=[model.from_row(row) for row in rows] if model else rows, data=[model.from_row(row) for row in rows] if model else [],
total=count, total=count,
) )
async def execute(self, query: str, values: Optional[dict] = None): async def execute(self, query: str, values: Optional[dict] = None):
params = self.rewrite_values(values) if values else {} params = self.rewrite_values(values) if values else {}
return await self.conn.execute(text(self.rewrite_query(query)), params) result = await self.conn.execute(text(self.rewrite_query(query)), params)
await self.conn.commit()
return result
class Database(Compat): class Database(Compat):
@@ -257,6 +246,28 @@ class Database(Compat):
self.engine: AsyncEngine = create_async_engine( self.engine: AsyncEngine = create_async_engine(
database_uri, echo=settings.debug_database database_uri, echo=settings.debug_database
) )
if self.type in {POSTGRES, COCKROACH}:
@event.listens_for(self.engine.sync_engine, "connect")
def register_custom_types(dbapi_connection, *_):
def _parse_timestamp(value):
if value is None:
return None
f = "%Y-%m-%d %H:%M:%S.%f"
if "." not in value:
f = "%Y-%m-%d %H:%M:%S"
return time.mktime(datetime.datetime.strptime(value, f).timetuple())
dbapi_connection.run_async(
lambda connection: connection.set_type_codec(
"TIMESTAMP",
encoder=datetime.datetime.timestamp,
decoder=_parse_timestamp,
schema="pg_catalog",
)
)
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
logger.trace(f"database {self.type} added for {self.name}") logger.trace(f"database {self.type} added for {self.name}")
@@ -274,28 +285,22 @@ class Database(Compat):
if self.schema: if self.schema:
if self.type in {POSTGRES, COCKROACH}: if self.type in {POSTGRES, COCKROACH}:
await wconn.execute( await wconn.execute(
f"CREATE SCHEMA IF NOT EXISTS {self.schema}", {} f"CREATE SCHEMA IF NOT EXISTS {self.schema}"
) )
elif self.type == SQLITE: elif self.type == SQLITE:
await wconn.execute( await wconn.execute(f"ATTACH '{self.path}' AS {self.schema}")
f"ATTACH '{self.path}' AS {self.schema}", {}
)
yield wconn yield wconn
finally: finally:
self.lock.release() self.lock.release()
async def fetchall(self, query: str, values: Optional[dict] = None) -> list: async def fetchall(self, query: str, values: Optional[dict] = None) -> list[dict]:
async with self.connect() as conn: async with self.connect() as conn:
result = await conn.execute(query, values) return await conn.fetchall(query, values)
return result.fetchall()
async def fetchone(self, query: str, values: Optional[dict] = None): async def fetchone(self, query: str, values: Optional[dict] = None) -> dict:
async with self.connect() as conn: async with self.connect() as conn:
result = await conn.execute(query, values) return await conn.fetchone(query, values)
row = result.fetchone()
result.close()
return row
async def fetch_page( async def fetch_page(
self, self,
@@ -367,8 +372,8 @@ class Operator(Enum):
class FromRowModel(BaseModel): class FromRowModel(BaseModel):
@classmethod @classmethod
def from_row(cls, row: Row): def from_row(cls, row: dict):
return cls(**dict(row)) return cls(**row)
class FilterModel(BaseModel): class FilterModel(BaseModel):
@@ -421,14 +426,14 @@ class Filter(BaseModel, Generic[TFilterModel]):
return cls(field=field, op=op, values=values, model=model) return cls(field=field, op=op, values=values, model=model)
# @property @property
# def statement(self): def statement(self):
# if self.op in (Operator.INCLUDE, Operator.EXCLUDE): if self.op in (Operator.INCLUDE, Operator.EXCLUDE):
# placeholders = ", ".join([placeholder] * len(self.values)) placeholders = ", ".join([])
# stmt = [f"{self.field} {self.op.as_sql} ({placeholders})"] stmt = [f"{self.field} {self.op.as_sql} ({placeholders})"]
# else: else:
# stmt = [f"{self.field} {self.op.as_sql} {placeholder}"] * len(self.values) stmt = [f"{self.field} {self.op.as_sql} :{self.field}"]
# return " OR ".join(stmt) return " OR ".join(stmt)
class Filters(BaseModel, Generic[TFilterModel]): class Filters(BaseModel, Generic[TFilterModel]):
@@ -474,9 +479,9 @@ class Filters(BaseModel, Generic[TFilterModel]):
def where(self, where_stmts: Optional[list[str]] = None) -> str: def where(self, where_stmts: Optional[list[str]] = None) -> str:
if not where_stmts: if not where_stmts:
where_stmts = [] where_stmts = []
# if self.filters: if self.filters:
# for page_filter in self.filters: for page_filter in self.filters:
# where_stmts.append(page_filter.statement) where_stmts.append(page_filter.statement)
if self.search and self.model: if self.search and self.model:
fields = self.model.__search_fields__ fields = self.model.__search_fields__
if DB_TYPE == POSTGRES: if DB_TYPE == POSTGRES:
Generated
+69 -1
View File
@@ -65,6 +65,74 @@ files = [
{file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
] ]
[[package]]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
name = "asyncpg"
version = "0.29.0"
description = "An asyncio PostgreSQL driver"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"},
{file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"},
{file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"},
{file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"},
{file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"},
{file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"},
{file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"},
{file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"},
{file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"},
{file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"},
{file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"},
{file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"},
{file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"},
{file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"},
{file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"},
{file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"},
{file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"},
{file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"},
{file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"},
{file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"},
{file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"},
{file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"},
{file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"},
{file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"},
{file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"},
{file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"},
{file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"},
{file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"},
{file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"},
{file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"},
{file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"},
{file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"},
{file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"},
{file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"},
{file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"},
{file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"},
{file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"},
{file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"},
{file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"},
{file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"},
{file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"},
]
[package.dependencies]
async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""}
[package.extras]
docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "23.2.0" version = "23.2.0"
@@ -3183,4 +3251,4 @@ liquid = ["wallycore"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10 | ^3.9" python-versions = "^3.10 | ^3.9"
content-hash = "56cbae093e02e5165df2c73d62b054e71f9d431fb704726b625473720496d473" content-hash = "e722e73e2efaad876ac742628e068b5da1596f76a83c2188d39bf9719d08a28b"
+3 -2
View File
@@ -20,7 +20,6 @@ fastapi = "0.112.0"
httpx = "0.27.0" httpx = "0.27.0"
jinja2 = "3.1.4" jinja2 = "3.1.4"
lnurl = "0.5.3" lnurl = "0.5.3"
psycopg2-binary = "2.9.9"
pydantic = "1.10.17" pydantic = "1.10.17"
pyqrcode = "1.2.1" pyqrcode = "1.2.1"
shortuuid = "1.0.13" shortuuid = "1.0.13"
@@ -28,6 +27,9 @@ sse-starlette = "1.8.2"
typing-extensions = "4.12.2" typing-extensions = "4.12.2"
uvicorn = "0.30.5" uvicorn = "0.30.5"
sqlalchemy = "1.4.52" sqlalchemy = "1.4.52"
aiosqlite = "^0.20.0"
asyncpg = "^0.29.0"
sse-starlette = "1.6.5"
uvloop = "0.19.0" uvloop = "0.19.0"
websockets = "11.0.3" websockets = "11.0.3"
loguru = "0.7.2" loguru = "0.7.2"
@@ -59,7 +61,6 @@ wallycore = {version = "1.3.0", optional = true}
# needed for breez funding source # needed for breez funding source
breez-sdk = {version = "0.5.2", optional = true} breez-sdk = {version = "0.5.2", optional = true}
aiosqlite = "^0.20.0"
[tool.poetry.extras] [tool.poetry.extras]
breez = ["breez-sdk"] breez = ["breez-sdk"]
liquid = ["wallycore"] liquid = ["wallycore"]
-2
View File
@@ -25,7 +25,6 @@ from lnbits.core.views.payment_api import api_payments_create_invoice
from lnbits.db import DB_TYPE, SQLITE, Database from lnbits.db import DB_TYPE, SQLITE, Database
from lnbits.settings import settings from lnbits.settings import settings
from tests.helpers import ( from tests.helpers import (
clean_database,
get_random_invoice_data, get_random_invoice_data,
) )
@@ -47,7 +46,6 @@ def event_loop():
# use session scope to run once before and once after all tests # use session scope to run once before and once after all tests
@pytest_asyncio.fixture(scope="session") @pytest_asyncio.fixture(scope="session")
async def app(): async def app():
clean_database(settings)
app = create_app() app = create_app()
async with LifespanManager(app) as manager: async with LifespanManager(app) as manager:
settings.first_install = False settings.first_install = False
+1 -23
View File
@@ -2,11 +2,7 @@ import random
import string import string
from typing import Optional from typing import Optional
from psycopg2 import connect from lnbits.db import FromRowModel
from psycopg2.errors import InvalidCatalogName
from lnbits import core
from lnbits.db import DB_TYPE, POSTGRES, FromRowModel
from lnbits.wallets import get_funding_source, set_funding_source from lnbits.wallets import get_funding_source, set_funding_source
@@ -35,21 +31,3 @@ set_funding_source()
funding_source = get_funding_source() funding_source = get_funding_source()
is_fake: bool = funding_source.__class__.__name__ == "FakeWallet" is_fake: bool = funding_source.__class__.__name__ == "FakeWallet"
is_regtest: bool = not is_fake is_regtest: bool = not is_fake
def clean_database(settings):
if DB_TYPE == POSTGRES:
conn = connect(settings.lnbits_database_url)
conn.autocommit = True
with conn.cursor() as cur:
try:
cur.execute("DROP DATABASE lnbits_test")
except InvalidCatalogName:
pass
cur.execute("CREATE DATABASE lnbits_test")
core.db.__init__("database")
conn.close()
else:
# TODO: do this once mock data is removed from test data folder
# os.remove(settings.lnbits_data_folder + "/database.sqlite3")
pass
+9 -5
View File
@@ -1,5 +1,5 @@
# Python script to migrate an LNbits SQLite DB to Postgres # Python script to migrate an LNbits SQLite DB to Postgres
# All credits to @Fritz446 for the awesome work # credits to @Fritz446 for the awesome work
# pip install psycopg2 OR psycopg2-binary # pip install psycopg2 OR psycopg2-binary
@@ -9,10 +9,14 @@ import sqlite3
import sys import sys
from typing import List, Optional from typing import List, Optional
import psycopg2
from lnbits.settings import settings from lnbits.settings import settings
try:
import psycopg2
except ImportError:
print("Please install psycopg2")
sys.exit(1)
sqfolder = settings.lnbits_data_folder sqfolder = settings.lnbits_data_folder
db_url = settings.lnbits_database_url db_url = settings.lnbits_database_url
@@ -55,8 +59,8 @@ def check_db_versions(sqdb):
version = dbpost[key] version = dbpost[key]
if value != version: if value != version:
raise Exception( raise Exception(
f"sqlite database version ({value}) of {key} doesn't match postgres" f"sqlite database version ({value}) of {key} doesn't match "
f" database version {version}" f"postgres database version {version}"
) )
connection = postgres.connection connection = postgres.connection