async postgres
add timestamp conversation pending fixed now statement repair conv remove cleandbsetting
This commit is contained in:
+2
-2
@@ -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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user