chore: update black, new formatting + pre-commit (#3885)

This commit is contained in:
dni ⚡
2026-03-23 10:04:00 +01:00
committed by GitHub
parent 7a393b11fd
commit efc0547271
9 changed files with 119 additions and 178 deletions
+2 -2
View File
@@ -14,11 +14,11 @@ repos:
- id: mixed-line-ending - id: mixed-line-ending
- id: check-case-conflict - id: check-case-conflict
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 25.1.0 rev: 26.3.1
hooks: hooks:
- id: black - id: black
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.10 rev: v0.14.10
hooks: hooks:
- id: ruff - id: ruff
args: [ --fix, --exit-non-zero-on-fix ] args: [ --fix, --exit-non-zero-on-fix ]
+4 -8
View File
@@ -149,14 +149,12 @@ async def get_payments_paginated( # noqa: C901
f"(status = '{PaymentState.SUCCESS}' OR status = '{PaymentState.PENDING}')" f"(status = '{PaymentState.SUCCESS}' OR status = '{PaymentState.PENDING}')"
) )
elif complete: elif complete:
clause.append( clause.append(f"""
f"""
( (
status = '{PaymentState.SUCCESS}' status = '{PaymentState.SUCCESS}'
OR (amount < 0 AND status = '{PaymentState.PENDING}') OR (amount < 0 AND status = '{PaymentState.PENDING}')
) )
""" """)
)
elif pending: elif pending:
clause.append(f"status = '{PaymentState.PENDING}'") clause.append(f"status = '{PaymentState.PENDING}'")
elif failed: elif failed:
@@ -346,14 +344,12 @@ async def get_payments_history(
"wallet_id": wallet_id, "wallet_id": wallet_id,
} }
# count outgoing payments if they are still pending # count outgoing payments if they are still pending
where = [ where = [f"""
f"""
wallet_id = :wallet_id AND ( wallet_id = :wallet_id AND (
status = '{PaymentState.SUCCESS}' status = '{PaymentState.SUCCESS}'
OR (amount < 0 AND status = '{PaymentState.PENDING}') OR (amount < 0 AND status = '{PaymentState.PENDING}')
) )
""" """]
]
clause = filters.where(where) clause = filters.where(where)
transactions: list[dict] = await db.fetchall( transactions: list[dict] = await db.fetchall(
# This query is safe from SQL injection: # This query is safe from SQL injection:
+62 -124
View File
@@ -10,31 +10,26 @@ from lnbits.db import Connection
async def m000_create_migrations_table(db: Connection): async def m000_create_migrations_table(db: Connection):
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS dbversions ( CREATE TABLE IF NOT EXISTS dbversions (
db TEXT PRIMARY KEY, db TEXT PRIMARY KEY,
version INT NOT NULL version INT NOT NULL
) )
""" """)
)
async def m001_initial(db: Connection): async def m001_initial(db: Connection):
""" """
Initial LNbits tables. Initial LNbits tables.
""" """
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS accounts ( CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
email TEXT, email TEXT,
pass TEXT pass TEXT
); );
""" """)
) await db.execute("""
await db.execute(
"""
CREATE TABLE IF NOT EXISTS extensions ( CREATE TABLE IF NOT EXISTS extensions (
"user" TEXT NOT NULL, "user" TEXT NOT NULL,
extension TEXT NOT NULL, extension TEXT NOT NULL,
@@ -42,10 +37,8 @@ async def m001_initial(db: Connection):
UNIQUE ("user", extension) UNIQUE ("user", extension)
); );
""" """)
) await db.execute("""
await db.execute(
"""
CREATE TABLE IF NOT EXISTS wallets ( CREATE TABLE IF NOT EXISTS wallets (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
@@ -53,10 +46,8 @@ async def m001_initial(db: Connection):
adminkey TEXT NOT NULL, adminkey TEXT NOT NULL,
inkey TEXT inkey TEXT
); );
""" """)
) await db.execute(f"""
await db.execute(
f"""
CREATE TABLE IF NOT EXISTS apipayments ( CREATE TABLE IF NOT EXISTS apipayments (
payhash TEXT NOT NULL, payhash TEXT NOT NULL,
amount {db.big_int} NOT NULL, amount {db.big_int} NOT NULL,
@@ -67,11 +58,9 @@ async def m001_initial(db: Connection):
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
UNIQUE (wallet, payhash) UNIQUE (wallet, payhash)
); );
""" """)
)
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM ( SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM (
SELECT wallet, SUM(amount) AS s -- incoming SELECT wallet, SUM(amount) AS s -- incoming
@@ -85,8 +74,7 @@ async def m001_initial(db: Connection):
GROUP BY wallet GROUP BY wallet
)x )x
GROUP BY wallet; GROUP BY wallet;
""" """)
)
async def m002_add_fields_to_apipayments(db: Connection): async def m002_add_fields_to_apipayments(db: Connection):
@@ -149,8 +137,7 @@ async def m004_ensure_fees_are_always_negative(db: Connection):
""" """
await db.execute("DROP VIEW balances") await db.execute("DROP VIEW balances")
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM ( SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM (
SELECT wallet, SUM(amount) AS s -- incoming SELECT wallet, SUM(amount) AS s -- incoming
@@ -164,8 +151,7 @@ async def m004_ensure_fees_are_always_negative(db: Connection):
GROUP BY wallet GROUP BY wallet
)x )x
GROUP BY wallet; GROUP BY wallet;
""" """)
)
async def m005_balance_check_balance_notify(db: Connection): async def m005_balance_check_balance_notify(db: Connection):
@@ -174,8 +160,7 @@ async def m005_balance_check_balance_notify(db: Connection):
LNbits wallet and of balanceNotify URLs supplied by users to empty their wallets. LNbits wallet and of balanceNotify URLs supplied by users to empty their wallets.
""" """
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS balance_check ( CREATE TABLE IF NOT EXISTS balance_check (
wallet TEXT NOT NULL REFERENCES wallets (id), wallet TEXT NOT NULL REFERENCES wallets (id),
service TEXT NOT NULL, service TEXT NOT NULL,
@@ -183,19 +168,16 @@ async def m005_balance_check_balance_notify(db: Connection):
UNIQUE(wallet, service) UNIQUE(wallet, service)
); );
""" """)
)
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS balance_notify ( CREATE TABLE IF NOT EXISTS balance_notify (
wallet TEXT NOT NULL REFERENCES wallets (id), wallet TEXT NOT NULL REFERENCES wallets (id),
url TEXT NOT NULL, url TEXT NOT NULL,
UNIQUE(wallet, url) UNIQUE(wallet, url)
); );
""" """)
)
async def m006_add_invoice_expiry_to_apipayments(db: Connection): async def m006_add_invoice_expiry_to_apipayments(db: Connection):
@@ -262,19 +244,16 @@ async def m007_set_invoice_expiries(db: Connection):
async def m008_create_admin_settings_table(db: Connection): async def m008_create_admin_settings_table(db: Connection):
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS settings ( CREATE TABLE IF NOT EXISTS settings (
super_user TEXT, super_user TEXT,
editable_settings TEXT NOT NULL DEFAULT '{}' editable_settings TEXT NOT NULL DEFAULT '{}'
); );
""" """)
)
async def m009_create_tinyurl_table(db: Connection): async def m009_create_tinyurl_table(db: Connection):
await db.execute( await db.execute(f"""
f"""
CREATE TABLE IF NOT EXISTS tiny_url ( CREATE TABLE IF NOT EXISTS tiny_url (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
url TEXT, url TEXT,
@@ -282,13 +261,11 @@ async def m009_create_tinyurl_table(db: Connection):
wallet TEXT, wallet TEXT,
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
); );
""" """)
)
async def m010_create_installed_extensions_table(db: Connection): async def m010_create_installed_extensions_table(db: Connection):
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS installed_extensions ( CREATE TABLE IF NOT EXISTS installed_extensions (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
version TEXT NOT NULL, version TEXT NOT NULL,
@@ -299,8 +276,7 @@ async def m010_create_installed_extensions_table(db: Connection):
active BOOLEAN DEFAULT false, active BOOLEAN DEFAULT false,
meta TEXT NOT NULL DEFAULT '{}' meta TEXT NOT NULL DEFAULT '{}'
); );
""" """)
)
async def m011_optimize_balances_view(db: Connection): async def m011_optimize_balances_view(db: Connection):
@@ -309,23 +285,19 @@ async def m011_optimize_balances_view(db: Connection):
over the payments table instead of 2. over the payments table instead of 2.
""" """
await db.execute("DROP VIEW balances") await db.execute("DROP VIEW balances")
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT wallet, SUM(amount - abs(fee)) AS balance SELECT wallet, SUM(amount - abs(fee)) AS balance
FROM apipayments FROM apipayments
WHERE (pending = false AND amount > 0) OR amount < 0 WHERE (pending = false AND amount > 0) OR amount < 0
GROUP BY wallet GROUP BY wallet
""" """)
)
async def m012_add_currency_to_wallet(db: Connection): async def m012_add_currency_to_wallet(db: Connection):
await db.execute( await db.execute("""
"""
ALTER TABLE wallets ADD COLUMN currency TEXT ALTER TABLE wallets ADD COLUMN currency TEXT
""" """)
)
async def m013_add_deleted_to_wallets(db: Connection): async def m013_add_deleted_to_wallets(db: Connection):
@@ -345,15 +317,13 @@ async def m014_set_deleted_wallets(db: Connection):
Sets deleted column to wallets. Sets deleted column to wallets.
""" """
try: try:
result = await db.execute( result = await db.execute("""
"""
SELECT * SELECT *
FROM wallets FROM wallets
WHERE user LIKE 'del:%' WHERE user LIKE 'del:%'
AND adminkey LIKE 'del:%' AND adminkey LIKE 'del:%'
AND inkey LIKE 'del:%' AND inkey LIKE 'del:%'
""" """)
)
rows = result.mappings().all() rows = result.mappings().all()
for row in rows: for row in rows:
@@ -386,8 +356,7 @@ async def m014_set_deleted_wallets(db: Connection):
async def m015_create_push_notification_subscriptions_table(db: Connection): async def m015_create_push_notification_subscriptions_table(db: Connection):
await db.execute( await db.execute(f"""
f"""
CREATE TABLE IF NOT EXISTS webpush_subscriptions ( CREATE TABLE IF NOT EXISTS webpush_subscriptions (
endpoint TEXT NOT NULL, endpoint TEXT NOT NULL,
"user" TEXT NOT NULL, "user" TEXT NOT NULL,
@@ -396,8 +365,7 @@ async def m015_create_push_notification_subscriptions_table(db: Connection):
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
PRIMARY KEY (endpoint, "user") PRIMARY KEY (endpoint, "user")
); );
""" """)
)
async def m016_add_username_column_to_accounts(db: Connection): async def m016_add_username_column_to_accounts(db: Connection):
@@ -484,8 +452,7 @@ async def m018_balances_view_exclude_deleted(db: Connection):
Make deleted wallets not show up in the balances view. Make deleted wallets not show up in the balances view.
""" """
await db.execute("DROP VIEW balances") await db.execute("DROP VIEW balances")
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT apipayments.wallet, SELECT apipayments.wallet,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
@@ -495,8 +462,7 @@ async def m018_balances_view_exclude_deleted(db: Connection):
AND ((apipayments.pending = false AND apipayments.amount > 0) AND ((apipayments.pending = false AND apipayments.amount > 0)
OR apipayments.amount < 0) OR apipayments.amount < 0)
GROUP BY wallet GROUP BY wallet
""" """)
)
async def m019_balances_view_based_on_wallets(db: Connection): async def m019_balances_view_based_on_wallets(db: Connection):
@@ -505,8 +471,7 @@ async def m019_balances_view_based_on_wallets(db: Connection):
Important for querying whole lnbits balances. Important for querying whole lnbits balances.
""" """
await db.execute("DROP VIEW balances") await db.execute("DROP VIEW balances")
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT apipayments.wallet, SELECT apipayments.wallet,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
@@ -516,8 +481,7 @@ async def m019_balances_view_based_on_wallets(db: Connection):
AND ((apipayments.pending = false AND apipayments.amount > 0) AND ((apipayments.pending = false AND apipayments.amount > 0)
OR apipayments.amount < 0) OR apipayments.amount < 0)
GROUP BY apipayments.wallet GROUP BY apipayments.wallet
""" """)
)
async def m020_add_column_column_to_user_extensions(db: Connection): async def m020_add_column_column_to_user_extensions(db: Connection):
@@ -536,8 +500,7 @@ async def m021_add_success_failed_to_apipayments(db: Connection):
await db.execute("UPDATE apipayments SET status = 'success' WHERE NOT pending") await db.execute("UPDATE apipayments SET status = 'success' WHERE NOT pending")
await db.execute("DROP VIEW balances") await db.execute("DROP VIEW balances")
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT apipayments.wallet, SELECT apipayments.wallet,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
@@ -549,8 +512,7 @@ async def m021_add_success_failed_to_apipayments(db: Connection):
OR (apipayments.status IN ('success', 'pending') AND apipayments.amount < 0) OR (apipayments.status IN ('success', 'pending') AND apipayments.amount < 0)
) )
GROUP BY apipayments.wallet GROUP BY apipayments.wallet
""" """)
)
async def m022_add_pubkey_to_accounts(db: Connection): async def m022_add_pubkey_to_accounts(db: Connection):
@@ -581,8 +543,7 @@ async def m024_drop_pending(db: Connection):
async def m025_refresh_view(db: Connection): async def m025_refresh_view(db: Connection):
await db.execute("DROP VIEW balances") await db.execute("DROP VIEW balances")
await db.execute( await db.execute("""
"""
CREATE VIEW balances AS CREATE VIEW balances AS
SELECT apipayments.wallet_id, SELECT apipayments.wallet_id,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
@@ -594,8 +555,7 @@ async def m025_refresh_view(db: Connection):
OR (apipayments.status IN ('success', 'pending') AND apipayments.amount < 0) OR (apipayments.status IN ('success', 'pending') AND apipayments.amount < 0)
) )
GROUP BY apipayments.wallet_id GROUP BY apipayments.wallet_id
""" """)
)
async def m026_update_payment_table(db: Connection): async def m026_update_payment_table(db: Connection):
@@ -658,8 +618,7 @@ async def m027_update_apipayments_data(db: Connection):
async def m028_update_settings(db: Connection): async def m028_update_settings(db: Connection):
await db.execute( await db.execute("""
"""
CREATE TABLE IF NOT EXISTS system_settings ( CREATE TABLE IF NOT EXISTS system_settings (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
value TEXT, value TEXT,
@@ -667,8 +626,7 @@ async def m028_update_settings(db: Connection):
UNIQUE (id, tag) UNIQUE (id, tag)
); );
""" """)
)
async def _insert_key_value(id_: str, value: Any): async def _insert_key_value(id_: str, value: Any):
await db.execute( await db.execute(
@@ -691,8 +649,7 @@ async def m028_update_settings(db: Connection):
async def m029_create_audit_table(db: Connection): async def m029_create_audit_table(db: Connection):
await db.execute( await db.execute(f"""
f"""
CREATE TABLE IF NOT EXISTS audit ( CREATE TABLE IF NOT EXISTS audit (
component TEXT, component TEXT,
ip_address TEXT, ip_address TEXT,
@@ -706,16 +663,13 @@ async def m029_create_audit_table(db: Connection):
delete_at TIMESTAMP, delete_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
); );
""" """)
)
async def m030_add_user_api_tokens_column(db: Connection): async def m030_add_user_api_tokens_column(db: Connection):
await db.execute( await db.execute("""
"""
ALTER TABLE accounts ADD COLUMN access_control_list TEXT ALTER TABLE accounts ADD COLUMN access_control_list TEXT
""" """)
)
async def m031_add_color_and_icon_to_wallets(db: Connection): async def m031_add_color_and_icon_to_wallets(db: Connection):
@@ -738,32 +692,25 @@ async def m033_update_payment_table(db: Connection):
async def m034_add_stored_paylinks_to_wallet(db: Connection): async def m034_add_stored_paylinks_to_wallet(db: Connection):
await db.execute( await db.execute("""
"""
ALTER TABLE wallets ADD COLUMN stored_paylinks TEXT ALTER TABLE wallets ADD COLUMN stored_paylinks TEXT
""" """)
)
async def m035_add_wallet_type_column(db: Connection): async def m035_add_wallet_type_column(db: Connection):
await db.execute( await db.execute("""
"""
ALTER TABLE wallets ADD COLUMN wallet_type TEXT DEFAULT 'lightning' ALTER TABLE wallets ADD COLUMN wallet_type TEXT DEFAULT 'lightning'
""" """)
)
async def m036_add_shared_wallet_column(db: Connection): async def m036_add_shared_wallet_column(db: Connection):
await db.execute( await db.execute("""
"""
ALTER TABLE wallets ADD COLUMN shared_wallet_id TEXT ALTER TABLE wallets ADD COLUMN shared_wallet_id TEXT
""" """)
)
async def m037_create_assets_table(db: Connection): async def m037_create_assets_table(db: Connection):
await db.execute( await db.execute(f"""
f"""
CREATE TABLE IF NOT EXISTS assets ( CREATE TABLE IF NOT EXISTS assets (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
user_id TEXT NOT NULL, user_id TEXT NOT NULL,
@@ -776,16 +723,13 @@ async def m037_create_assets_table(db: Connection):
data {db.blob} NOT NULL, data {db.blob} NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
); );
""" """)
)
async def m038_add_labels_for_payments(db: Connection): async def m038_add_labels_for_payments(db: Connection):
await db.execute( await db.execute("""
"""
ALTER TABLE apipayments ADD COLUMN labels TEXT ALTER TABLE apipayments ADD COLUMN labels TEXT
""" """)
)
async def m039_index_payments(db: Connection): async def m039_index_payments(db: Connection):
@@ -804,11 +748,9 @@ async def m039_index_payments(db: Connection):
] ]
for index in indexes: for index in indexes:
logger.debug(f"Creating index idx_payments_{index}...") logger.debug(f"Creating index idx_payments_{index}...")
await db.execute( await db.execute(f"""
f"""
CREATE INDEX IF NOT EXISTS idx_payments_{index} ON apipayments ({index}); CREATE INDEX IF NOT EXISTS idx_payments_{index} ON apipayments ({index});
""" """)
)
async def m040_index_wallets(db: Connection): async def m040_index_wallets(db: Connection):
@@ -825,11 +767,9 @@ async def m040_index_wallets(db: Connection):
for index in indexes: for index in indexes:
logger.debug(f"Creating index idx_wallets_{index}...") logger.debug(f"Creating index idx_wallets_{index}...")
await db.execute( await db.execute(f"""
f"""
CREATE INDEX IF NOT EXISTS idx_wallets_{index} ON wallets ("{index}"); CREATE INDEX IF NOT EXISTS idx_wallets_{index} ON wallets ("{index}");
""" """)
)
async def m042_index_accounts(db: Connection): async def m042_index_accounts(db: Connection):
@@ -843,11 +783,9 @@ async def m042_index_accounts(db: Connection):
for index in indexes: for index in indexes:
logger.debug(f"Creating index idx_wallets_{index}...") logger.debug(f"Creating index idx_wallets_{index}...")
await db.execute( await db.execute(f"""
f"""
CREATE INDEX IF NOT EXISTS idx_accounts_{index} ON accounts ("{index}"); CREATE INDEX IF NOT EXISTS idx_accounts_{index} ON accounts ("{index}");
""" """)
)
async def m043_add_ui_customization_to_accounts(db: Connection): async def m043_add_ui_customization_to_accounts(db: Connection):
+1 -1
View File
@@ -69,7 +69,7 @@ def decrypt_content(
1: 1:
] ]
# extract iv and content # extract iv and content
(encrypted_content_b64, iv_b64) = content.split("?iv=") encrypted_content_b64, iv_b64 = content.split("?iv=")
encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii")) encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii"))
iv = base64.b64decode(iv_b64.encode("ascii")) iv = base64.b64decode(iv_b64.encode("ascii"))
# Decrypt # Decrypt
+1 -1
View File
@@ -64,7 +64,7 @@ migration = ["psycopg2-binary==2.9.11"]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"black>=25.12.0,<26.0.0", "black~=26.3.1",
"mypy==1.17.1", "mypy==1.17.1",
"types-protobuf>=6.32.1.20251210,<7.0.0", "types-protobuf>=6.32.1.20251210,<7.0.0",
"pre-commit>=4.5.1,<5.0.0", "pre-commit>=4.5.1,<5.0.0",
+4 -8
View File
@@ -6,25 +6,21 @@ from tests.helpers import DbTestModel
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
async def fetch_page(db): async def fetch_page(db):
await db.execute("DROP TABLE IF EXISTS test_db_fetch_page") await db.execute("DROP TABLE IF EXISTS test_db_fetch_page")
await db.execute( await db.execute("""
"""
CREATE TABLE test_db_fetch_page ( CREATE TABLE test_db_fetch_page (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
value TEXT NOT NULL, value TEXT NOT NULL,
name TEXT NOT NULL name TEXT NOT NULL
) )
""" """)
) await db.execute("""
await db.execute(
"""
INSERT INTO test_db_fetch_page (id, name, value) VALUES INSERT INTO test_db_fetch_page (id, name, value) VALUES
('1', 'Alice', 'foo'), ('1', 'Alice', 'foo'),
('2', 'Bob', 'bar'), ('2', 'Bob', 'bar'),
('3', 'Carol', 'bar'), ('3', 'Carol', 'bar'),
('4', 'Dave', 'bar'), ('4', 'Dave', 'bar'),
('5', 'Dave', 'foo') ('5', 'Dave', 'foo')
""" """)
)
yield yield
await db.execute("DROP TABLE test_db_fetch_page") await db.execute("DROP TABLE test_db_fetch_page")
+1 -1
View File
@@ -40,7 +40,7 @@ def encrypt_content(priv_key, dest_pub_key, content):
def decrypt_content(priv_key, source_pub_key, content): def decrypt_content(priv_key, source_pub_key, content):
p = PublicKey(bytes.fromhex("02" + source_pub_key)) p = PublicKey(bytes.fromhex("02" + source_pub_key))
shared = p.multiply(bytes.fromhex(priv_key)).format()[1:] shared = p.multiply(bytes.fromhex(priv_key)).format()[1:]
(encrypted_content_b64, iv_b64) = content.split("?iv=") encrypted_content_b64, iv_b64 = content.split("?iv=")
encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii")) encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii"))
iv = base64.b64decode(iv_b64.encode("ascii")) iv = base64.b64decode(iv_b64.encode("ascii"))
aes = AES.new(shared, AES.MODE_CBC, iv) aes = AES.new(shared, AES.MODE_CBC, iv)
+4 -8
View File
@@ -136,12 +136,10 @@ def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None):
assert os.path.isfile(file), f"{file} does not exist!" assert os.path.isfile(file), f"{file} does not exist!"
sqlite_cursor = get_sqlite_cursor(file) sqlite_cursor = get_sqlite_cursor(file)
tables = sqlite_cursor.execute( tables = sqlite_cursor.execute("""
"""
SELECT name FROM sqlite_master SELECT name FROM sqlite_master
WHERE type='table' AND name not like 'sqlite?_%' escape '?' WHERE type='table' AND name not like 'sqlite?_%' escape '?'
""" """).fetchall()
).fetchall()
for table in tables: for table in tables:
table_name = table[0] table_name = table[0]
@@ -184,11 +182,9 @@ def build_table_columns(file: str, schema: str, table_name: str):
sqlite_columns = sqlite_cursor.execute( sqlite_columns = sqlite_cursor.execute(
f"PRAGMA table_info({table_name})" f"PRAGMA table_info({table_name})"
).fetchall() ).fetchall()
pg_cursor.execute( pg_cursor.execute(f"""
f"""
SELECT table_name, column_name, udt_name FROM information_schema.columns SELECT table_name, column_name, udt_name FROM information_schema.columns
WHERE table_schema = '{schema}'AND table_name = '{table_name}';""" WHERE table_schema = '{schema}'AND table_name = '{table_name}';""")
)
pg_columns = pg_cursor.fetchall() pg_columns = pg_cursor.fetchall()
columns = [] columns = []
Generated
+40 -25
View File
@@ -332,7 +332,7 @@ wheels = [
[[package]] [[package]]
name = "black" name = "black"
version = "25.12.0" version = "26.3.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
@@ -344,24 +344,24 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" }, { name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/37/d5/8d3145999d380e5d09bb00b0f7024bf0a8ccb5c07b5648e9295f02ec1d98/black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8", size = 1895720, upload-time = "2025-12-08T01:46:58.197Z" }, { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" },
{ url = "https://files.pythonhosted.org/packages/06/97/7acc85c4add41098f4f076b21e3e4e383ad6ed0a3da26b2c89627241fc11/black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a", size = 1727193, upload-time = "2025-12-08T01:52:26.674Z" }, { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" },
{ url = "https://files.pythonhosted.org/packages/24/f0/fdf0eb8ba907ddeb62255227d29d349e8256ef03558fbcadfbc26ecfe3b2/black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea", size = 1774506, upload-time = "2025-12-08T01:46:25.721Z" }, { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" },
{ url = "https://files.pythonhosted.org/packages/e4/f5/9203a78efe00d13336786b133c6180a9303d46908a9aa72d1104ca214222/black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f", size = 1416085, upload-time = "2025-12-08T01:46:06.073Z" }, { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" },
{ url = "https://files.pythonhosted.org/packages/ba/cc/7a6090e6b081c3316282c05c546e76affdce7bf7a3b7d2c3a2a69438bd01/black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da", size = 1226038, upload-time = "2025-12-08T01:45:29.388Z" }, { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" },
{ url = "https://files.pythonhosted.org/packages/60/ad/7ac0d0e1e0612788dbc48e62aef8a8e8feffac7eb3d787db4e43b8462fa8/black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a", size = 1877003, upload-time = "2025-12-08T01:43:29.967Z" }, { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" },
{ url = "https://files.pythonhosted.org/packages/e8/dd/a237e9f565f3617a88b49284b59cbca2a4f56ebe68676c1aad0ce36a54a7/black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be", size = 1712639, upload-time = "2025-12-08T01:52:46.756Z" }, { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" },
{ url = "https://files.pythonhosted.org/packages/12/80/e187079df1ea4c12a0c63282ddd8b81d5107db6d642f7d7b75a6bcd6fc21/black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b", size = 1758143, upload-time = "2025-12-08T01:45:29.137Z" }, { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" },
{ url = "https://files.pythonhosted.org/packages/93/b5/3096ccee4f29dc2c3aac57274326c4d2d929a77e629f695f544e159bfae4/black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5", size = 1420698, upload-time = "2025-12-08T01:45:53.379Z" }, { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" },
{ url = "https://files.pythonhosted.org/packages/7e/39/f81c0ffbc25ffbe61c7d0385bf277e62ffc3e52f5ee668d7369d9854fadf/black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655", size = 1229317, upload-time = "2025-12-08T01:46:35.606Z" }, { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" },
{ url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" }, { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" },
{ url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" }, { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" }, { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" },
{ url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" }, { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" },
{ url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" }, { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" },
{ url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" },
] ]
[[package]] [[package]]
@@ -1384,7 +1384,7 @@ provides-extras = ["breez", "liquid", "migration"]
dev = [ dev = [
{ name = "anyio", specifier = ">=4.12.1" }, { name = "anyio", specifier = ">=4.12.1" },
{ name = "asgi-lifespan", specifier = ">=2.1.0,<3.0.0" }, { name = "asgi-lifespan", specifier = ">=2.1.0,<3.0.0" },
{ name = "black", specifier = ">=25.12.0,<26.0.0" }, { name = "black", specifier = "~=26.3.1" },
{ name = "grpcio-tools", specifier = ">=1.76.0,<2.0.0" }, { name = "grpcio-tools", specifier = ">=1.76.0,<2.0.0" },
{ name = "json5", specifier = ">=0.13.0,<1.0.0" }, { name = "json5", specifier = ">=0.13.0,<1.0.0" },
{ name = "mock", specifier = ">=5.2.0,<6.0.0" }, { name = "mock", specifier = ">=5.2.0,<6.0.0" },
@@ -1716,11 +1716,11 @@ wheels = [
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.12.1" version = "1.0.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
] ]
[[package]] [[package]]
@@ -2190,11 +2190,26 @@ wheels = [
[[package]] [[package]]
name = "pytokens" name = "pytokens"
version = "0.3.0" version = "0.4.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" },
{ url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" },
{ url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" },
{ url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" },
{ url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" },
{ url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" },
{ url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" },
{ url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" },
{ url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" },
{ url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" },
{ url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" },
{ url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" },
{ url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" },
{ url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" },
{ url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
] ]
[[package]] [[package]]