Merge branch 'main' into fix/mypy

This commit is contained in:
dni 2022-07-25 13:46:22 +02:00
commit e67b2c0529
21 changed files with 338 additions and 81 deletions

View File

@ -38,12 +38,14 @@ jobs:
./venv/bin/python -m pip install --upgrade pip
./venv/bin/pip install -r requirements.txt
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
sudo apt install unzip
- name: Run migrations
run: |
rm -rf ./data
mkdir -p ./data
export LNBITS_DATA_FOLDER="./data"
unzip tests/data/mock_data.zip -d ./data
timeout 5s ./venv/bin/uvicorn lnbits.__main__:app --host 0.0.0.0 --port 5001 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
export LNBITS_DATABASE_URL="postgres://postgres:postgres@0.0.0.0:5432/postgres"
timeout 5s ./venv/bin/uvicorn lnbits.__main__:app --host 0.0.0.0 --port 5001 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
./venv/bin/python tools/conv.py --dont-ignore-missing
./venv/bin/python tools/conv.py

View File

@ -19,17 +19,11 @@ jobs:
docker build -t lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
source docker-scripts.sh
lnbits-regtest-start
echo "sleeping 60 seconds"
sleep 60
echo "continue"
lnbits-regtest-init
bitcoin-cli-sim -generate 1
lncli-sim 1 listpeers
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
- name: Install dependencies
env:
env:
VIRTUAL_ENV: ./venv
PATH: ${{ env.VIRTUAL_ENV }}/bin:${{ env.PATH }}
run: |
@ -37,7 +31,7 @@ jobs:
./venv/bin/python -m pip install --upgrade pip
./venv/bin/pip install -r requirements.txt
./venv/bin/pip install pylightning
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
- name: Run tests
env:
PYTHONUNBUFFERED: 1
@ -66,17 +60,11 @@ jobs:
docker build -t lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
source docker-scripts.sh
lnbits-regtest-start
echo "sleeping 60 seconds"
sleep 60
echo "continue"
lnbits-regtest-init
bitcoin-cli-sim -generate 1
lncli-sim 1 listpeers
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
- name: Install dependencies
env:
env:
VIRTUAL_ENV: ./venv
PATH: ${{ env.VIRTUAL_ENV }}/bin:${{ env.PATH }}
run: |
@ -84,7 +72,7 @@ jobs:
./venv/bin/python -m pip install --upgrade pip
./venv/bin/pip install -r requirements.txt
./venv/bin/pip install pylightning
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
- name: Run tests
env:
PYTHONUNBUFFERED: 1
@ -94,4 +82,4 @@ jobs:
CLIGHTNING_RPC: docker/data/clightning-1/regtest/lightning-rpc
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
make test-real-wallet

2
.gitignore vendored
View File

@ -15,7 +15,7 @@ __pycache__
.webassets-cache
htmlcov
test-reports
tests/data
tests/data/*.sqlite3
*.swo
*.swp

View File

@ -30,7 +30,7 @@ async def create_tpos(wallet_id: str, data: CreateTposData) -> TPoS:
async def get_tpos(tpos_id: str) -> Optional[TPoS]:
row = await db.fetchone("SELECT * FROM tpos.tposs WHERE id = ?", (tpos_id,))
return TPoS.from_row(row) if row else None
return TPoS(**row) if row else None
async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]:
@ -42,7 +42,7 @@ async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]:
f"SELECT * FROM tpos.tposs WHERE wallet IN ({q})", (*wallet_ids,)
)
return [TPoS.from_row(row) for row in rows]
return [TPoS(**row) for row in rows]
async def delete_tpos(tpos_id: str) -> None:

View File

@ -1,13 +1,15 @@
from sqlite3 import Row
from typing import Optional
from fastapi import Query
from pydantic import BaseModel
class CreateTposData(BaseModel):
name: str
currency: str
tip_options: str
tip_wallet: str
tip_options: str = Query(None)
tip_wallet: str = Query(None)
class TPoS(BaseModel):
@ -15,8 +17,8 @@ class TPoS(BaseModel):
wallet: str
name: str
currency: str
tip_options: str
tip_wallet: str
tip_options: Optional[str]
tip_wallet: Optional[str]
@classmethod
def from_row(cls, row: Row) -> "TPoS":

View File

@ -26,7 +26,6 @@ async def on_invoice_paid(payment: Payment) -> None:
# now we make some special internal transfers (from no one to the receiver)
tpos = await get_tpos(payment.extra.get("tposId"))
tipAmount = payment.extra.get("tipAmount")
if tipAmount is None:
@ -34,6 +33,7 @@ async def on_invoice_paid(payment: Payment) -> None:
return
tipAmount = tipAmount * 1000
amount = payment.amount - tipAmount
# mark the original payment with one extra key, "splitted"
# (this prevents us from doing this process again and it's informative)
@ -41,13 +41,13 @@ async def on_invoice_paid(payment: Payment) -> None:
await core_db.execute(
"""
UPDATE apipayments
SET extra = ?, amount = amount - ?
SET extra = ?, amount = ?
WHERE hash = ?
AND checking_id NOT LIKE 'internal_%'
""",
(
json.dumps(dict(**payment.extra, tipSplitted=True)),
tipAmount,
amount,
payment.payment_hash,
),
)
@ -60,7 +60,7 @@ async def on_invoice_paid(payment: Payment) -> None:
payment_request="",
payment_hash=payment.payment_hash,
amount=tipAmount,
memo=payment.memo,
memo=f"Tip for {payment.memo}",
pending=False,
extra={"tipSplitted": True},
)

View File

@ -54,8 +54,8 @@
></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ (col.name == 'tip_options' ? JSON.parse(col.value).join(", ")
: col.value) }}
{{ (col.name == 'tip_options' && col.value ?
JSON.parse(col.value).join(", ") : col.value) }}
</q-td>
<q-td auto-width>
<q-btn

View File

@ -167,7 +167,12 @@
<div class="text-center">
<h3 class="q-my-md">{% raw %}{{ famount }}{% endraw %}</h3>
<h5 class="q-mt-none">
{% raw %}{{ fsat }}{% endraw %} <small>sat</small>
{% raw %}{{ fsat }}
<small>sat</small>
<span v-show="tip_options" style="font-size: 0.75rem"
>( + {{ tipAmountSat }} tip)</span
>
{% endraw %}
</h5>
</div>
<div class="row q-mt-lg">
@ -272,7 +277,7 @@
return {
tposId: '{{ tpos.id }}',
currency: '{{ tpos.currency }}',
tip_options: JSON.parse('{{ tpos.tip_options }}'),
tip_options: null,
exchangeRate: null,
stack: [],
tipAmount: 0.0,
@ -310,7 +315,6 @@
return Math.ceil((this.tipAmount / this.exchangeRate) * 100000000)
},
fsat: function () {
console.log('sat', this.sat, LNbits.utils.formatSat(this.sat))
return LNbits.utils.formatSat(this.sat)
}
},
@ -350,7 +354,7 @@
this.showInvoice()
},
submitForm: function () {
if (this.tip_options.length) {
if (this.tip_options) {
this.showTipModal()
} else {
this.showInvoice()
@ -362,7 +366,6 @@
showInvoice: function () {
var self = this
var dialog = this.invoiceDialog
console.log(this.sat, this.tposId)
axios
.post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, {
params: {
@ -416,6 +419,11 @@
created: function () {
var getRates = this.getRates
getRates()
this.tip_options =
'{{ tpos.tip_options | tojson }}' == 'null'
? null
: JSON.parse('{{ tpos.tip_options }}')
console.log(typeof this.tip_options, this.tip_options)
setInterval(function () {
getRates()
}, 20000)

View File

@ -17,7 +17,7 @@ from .models import CreateTposData
@tpos_ext.get("/api/v1/tposs", status_code=HTTPStatus.OK)
async def api_tposs(
all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
@ -63,6 +63,9 @@ async def api_tpos_create_invoice(
status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
)
if tipAmount:
amount += tipAmount
try:
payment_hash, payment_request = await create_invoice(
wallet_id=tpos.wallet,

View File

@ -26,6 +26,8 @@ LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes t
- on details you can print the vouchers\
![printable vouchers](https://i.imgur.com/2xLHbob.jpg)
- every printed LNURLw QR code is unique, it can only be used once
3. Bonus: you can use an LNbits themed voucher, or use a custom one. There's a _template.svg_ file in `static/images` folder if you want to create your own.\
![voucher](https://i.imgur.com/qyQoHi3.jpg)
#### Advanced

View File

@ -26,9 +26,10 @@ async def create_withdraw_link(
k1,
open_time,
usescsv,
webhook_url
webhook_url,
custom_url
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
link_id,
@ -44,6 +45,7 @@ async def create_withdraw_link(
int(datetime.now().timestamp()) + data.wait_time,
usescsv,
data.webhook_url,
data.custom_url,
),
)
link = await get_withdraw_link(link_id, 0)

View File

@ -115,3 +115,10 @@ async def m004_webhook_url(db):
Adds webhook_url
"""
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_url TEXT;")
async def m005_add_custom_print_design(db):
"""
Adds custom print design
"""
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;")

View File

@ -16,6 +16,7 @@ class CreateWithdrawData(BaseModel):
wait_time: int = Query(..., ge=1)
is_unique: bool
webhook_url: str = Query(None)
custom_url: str = Query(None)
class WithdrawLink(BaseModel):
@ -34,6 +35,7 @@ class WithdrawLink(BaseModel):
usescsv: str = Query(None)
number: int = Query(0)
webhook_url: str = Query(None)
custom_url: str = Query(None)
@property
def is_spent(self) -> bool:

View File

@ -20,9 +20,12 @@ var mapWithdrawLink = function (obj) {
obj.uses_left = obj.uses - obj.used
obj.print_url = [locationPath, 'print/', obj.id].join('')
obj.withdraw_url = [locationPath, obj.id].join('')
obj._data.use_custom = Boolean(obj.custom_url)
return obj
}
const CUSTOM_URL = '/static/images/default_voucher.png'
new Vue({
el: '#vue',
mixins: [windowMixin],
@ -59,13 +62,15 @@ new Vue({
secondMultiplier: 'seconds',
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
data: {
is_unique: false
is_unique: false,
use_custom: false
}
},
simpleformDialog: {
show: false,
data: {
is_unique: true,
use_custom: true,
title: 'Vouchers',
min_withdrawable: 0,
wait_time: 1
@ -106,12 +111,14 @@ new Vue({
},
closeFormDialog: function () {
this.formDialog.data = {
is_unique: false
is_unique: false,
use_custom: false
}
},
simplecloseFormDialog: function () {
this.simpleformDialog.data = {
is_unique: false
is_unique: false,
use_custom: false
}
},
openQrCodeDialog: function (linkId) {
@ -133,6 +140,9 @@ new Vue({
id: this.formDialog.data.wallet
})
var data = _.omit(this.formDialog.data, 'wallet')
if (data.use_custom && !data?.custom_url) {
data.custom_url = CUSTOM_URL
}
data.wait_time =
data.wait_time *
@ -141,7 +151,6 @@ new Vue({
minutes: 60,
hours: 3600
}[this.formDialog.secondMultiplier]
if (data.id) {
this.updateWithdrawLink(wallet, data)
} else {
@ -159,6 +168,10 @@ new Vue({
data.title = 'vouchers'
data.is_unique = true
if (data.use_custom && !data?.custom_url) {
data.custom_url = '/static/images/default_voucher.png'
}
if (data.id) {
this.updateWithdrawLink(wallet, data)
} else {
@ -181,7 +194,8 @@ new Vue({
'uses',
'wait_time',
'is_unique',
'webhook_url'
'webhook_url',
'custom_url'
)
)
.then(function (response) {

View File

@ -217,6 +217,32 @@
label="Webhook URL (optional)"
hint="A URL to be called whenever this link gets used."
></q-input>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
v-model="formDialog.data.use_custom"
color="primary"
></q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label>Use a custom voucher design </q-item-label>
<q-item-label caption
>You can use an LNbits voucher design or a custom
one</q-item-label
>
</q-item-section>
</q-item>
</q-list>
<q-input
v-if="formDialog.data.use_custom"
filled
dense
v-model="formDialog.data.custom_url"
type="text"
label="Custom design .png (optional)"
hint="Enter a URL if you want to use a custom design or leave blank for showing only the QR"
></q-input>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
@ -303,6 +329,32 @@
:default="1"
label="Number of vouchers"
></q-input>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
v-model="simpleformDialog.data.use_custom"
color="primary"
></q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label>Use a custom voucher design </q-item-label>
<q-item-label caption
>You can use an LNbits voucher design or a custom
one</q-item-label
>
</q-item-section>
</q-item>
</q-list>
<q-input
v-if="simpleformDialog.data.use_custom"
filled
dense
v-model="simpleformDialog.data.custom_url"
type="text"
label="Custom design .png (optional)"
hint="Enter a URL if you want to use a custom design or leave blank for showing only the QR"
></q-input>
<div class="row q-mt-lg">
<q-btn

View File

@ -0,0 +1,110 @@
{% extends "print.html" %} {% block page %}
<div class="row">
<div class="" id="vue">
{% for page in link %}
<page size="A4" id="pdfprint">
{% for one in page %}
<div class="wrapper">
<img src="{{custom_url}}" alt="..." />
<span>{{ amt }} sats</span>
<div class="lnurlw">
<qrcode :value="'{{one}}'" :options="{width: 95, margin: 1}"></qrcode>
</div>
</div>
{% endfor %}
</page>
{% endfor %}
</div>
</div>
{% endblock %} {% block styles %}
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400');
body {
background: rgb(204, 204, 204);
}
page {
background: white;
display: block;
margin: 0 auto;
margin-bottom: 0.5cm;
box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
}
page[size='A4'] {
width: 21cm;
height: 29.7cm;
}
.wrapper {
position: relative;
margin-bottom: 1rem;
padding: 1rem;
width: fit-content;
}
.wrapper span {
display: block;
position: absolute;
font-family: 'Inter';
font-size: 0.75rem;
color: #fff;
top: calc(3.2mm + 1rem);
right: calc(4mm + 1rem);
}
.wrapper img {
display: block;
width: 187mm;
height: auto;
}
.wrapper .lnurlw {
display: block;
position: absolute;
top: calc(7.3mm + 1rem);
left: calc(7.5mm + 1rem);
transform: rotate(45deg);
}
@media print {
body,
page {
margin: 0px !important;
box-shadow: none !important;
}
.q-page,
.wrapper {
padding: 0px !important;
}
.wrapper span {
top: 3mm;
right: 4mm;
}
.wrapper .lnurlw {
display: block;
position: absolute;
top: 7.3mm;
left: 7.5mm;
transform: rotate(45deg);
}
}
</style>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
data: function () {
return {
theurl: location.protocol + '//' + location.host,
printDialog: {
show: true,
data: null
},
links: []
}
},
created() {
this.links = '{{ link | tojson }}'
}
})
</script>
{% endblock %}

View File

@ -99,6 +99,18 @@ async def print_qr(request: Request, link_id):
page_link = list(chunks(links, 2))
linked = list(chunks(page_link, 5))
if link.custom_url:
return withdraw_renderer().TemplateResponse(
"withdraw/print_qr_custom.html",
{
"request": request,
"link": page_link,
"unique": True,
"custom_url": link.custom_url,
"amt": link.max_withdrawable,
},
)
return withdraw_renderer().TemplateResponse(
"withdraw/print_qr.html", {"request": request, "link": linked, "unique": True}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -0,0 +1,16 @@
<svg width="2000" height="1422" viewBox="0 0 2000 1422" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2_2)">
<rect width="2000" height="1422" fill="#F0F0F0"/>
<line x1="-0.707107" y1="710.293" x2="710.293" y2="-0.707106" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
<line x1="0.707107" y1="710.293" x2="711.707" y2="1421.29" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
<line x1="710" y1="-0.00140647" x2="712" y2="1422" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
<line y1="710" x2="2000" y2="710" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
<line x1="709.707" y1="-0.707107" x2="1420.71" y2="710.293" stroke="#696969" stroke-opacity="0.5" stroke-width="2"/>
<rect x="26" y="216.454" width="275" height="275" transform="rotate(-45 26 216.454)" fill="white" fill-opacity="0.5"/>
</g>
<defs>
<clipPath id="clip0_2_2">
<rect width="2000" height="1422" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 985 B

BIN
tests/data/mock_data.zip Normal file

Binary file not shown.

View File

@ -38,8 +38,6 @@ else:
pgport = LNBITS_DATABASE_URL.split("@")[1].split(":")[1].split("/")[0]
pgschema = ""
print(pgdb, pguser, pgpswd, pghost, pgport, pgschema)
def get_sqlite_cursor(sqdb) -> sqlite3:
consq = sqlite3.connect(sqdb)
@ -99,8 +97,12 @@ def insert_to_pg(query, data):
for d in data:
try:
cursor.execute(query, d)
except:
raise ValueError(f"Failed to insert {d}")
except Exception as e:
if args.ignore_errors:
print(e)
print(f"Failed to insert {d}")
else:
raise ValueError(f"Failed to insert {d}")
connection.commit()
cursor.close()
@ -256,9 +258,10 @@ def migrate_ext(sqlite_db_file, schema, ignore_missing=True):
k1,
open_time,
used,
usescsv
usescsv,
webhook_url
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
"""
insert_to_pg(q, res.fetchall())
# WITHDRAW HASH CHECK
@ -316,8 +319,8 @@ def migrate_ext(sqlite_db_file, schema, ignore_missing=True):
# TPOSS
res = sq.execute("SELECT * FROM tposs;")
q = f"""
INSERT INTO tpos.tposs (id, wallet, name, currency)
VALUES (%s, %s, %s, %s);
INSERT INTO tpos.tposs (id, wallet, name, currency, tip_wallet, tip_options)
VALUES (%s, %s, %s, %s, %s, %s);
"""
insert_to_pg(q, res.fetchall())
elif schema == "tipjar":
@ -512,12 +515,13 @@ def migrate_ext(sqlite_db_file, schema, ignore_missing=True):
wallet,
url,
memo,
description,
amount,
time,
remembers,
extra
extras
)
VALUES (%s, %s, %s, %s, %s, to_timestamp(%s), %s, %s);
VALUES (%s, %s, %s, %s, %s, %s, to_timestamp(%s), %s, %s);
"""
insert_to_pg(q, res.fetchall())
elif schema == "offlineshop":
@ -543,15 +547,15 @@ def migrate_ext(sqlite_db_file, schema, ignore_missing=True):
# lnurldevice
res = sq.execute("SELECT * FROM lnurldevices;")
q = f"""
INSERT INTO lnurldevice.lnurldevices (id, key, title, wallet, currency, device, profit)
VALUES (%s, %s, %s, %s, %s, %s, %s);
INSERT INTO lnurldevice.lnurldevices (id, key, title, wallet, currency, device, profit, timestamp)
VALUES (%s, %s, %s, %s, %s, %s, %s, to_timestamp(%s));
"""
insert_to_pg(q, res.fetchall())
# lnurldevice PAYMENT
res = sq.execute("SELECT * FROM lnurldevicepayment;")
q = f"""
INSERT INTO lnurldevice.lnurldevicepayment (id, deviceid, payhash, payload, pin, sats)
VALUES (%s, %s, %s, %s, %s, %s);
INSERT INTO lnurldevice.lnurldevicepayment (id, deviceid, payhash, payload, pin, sats, timestamp)
VALUES (%s, %s, %s, %s, %s, %s, to_timestamp(%s));
"""
insert_to_pg(q, res.fetchall())
elif schema == "lnurlp":
@ -710,36 +714,69 @@ def migrate_ext(sqlite_db_file, schema, ignore_missing=True):
sq.close()
parser = argparse.ArgumentParser(description="Migrate data from SQLite to PostgreSQL")
parser = argparse.ArgumentParser(
description="LNbits migration tool for migrating data from SQLite to PostgreSQL"
)
parser.add_argument(
dest="sqlite_file",
dest="sqlite_path",
const=True,
nargs="?",
help="SQLite DB to migrate from",
default="data/database.sqlite3",
help=f"SQLite DB folder *or* single extension db file to migrate. Default: {sqfolder}",
default=sqfolder,
type=str,
)
parser.add_argument(
"-i",
"--dont-ignore-missing",
help="Error if migration is missing for an extension.",
"-e",
"--extensions-only",
help="Migrate only extensions",
required=False,
default=False,
const=True,
nargs="?",
type=bool,
action="store_true",
)
parser.add_argument(
"-s",
"--skip-missing",
help="Error if migration is missing for an extension",
required=False,
default=False,
action="store_true",
)
parser.add_argument(
"-i",
"--ignore-errors",
help="Don't error if migration fails",
required=False,
default=False,
action="store_true",
)
args = parser.parse_args()
print(args)
print("Selected path: ", args.sqlite_path)
check_db_versions(args.sqlite_file)
migrate_core(args.sqlite_file)
if os.path.isdir(args.sqlite_path):
file = os.path.join(args.sqlite_path, "database.sqlite3")
check_db_versions(file)
if not args.extensions_only:
print(f"Migrating: {file}")
migrate_core(file)
if os.path.isdir(args.sqlite_path):
files = [
os.path.join(args.sqlite_path, file) for file in os.listdir(args.sqlite_path)
]
else:
files = [args.sqlite_path]
files = os.listdir(sqfolder)
for file in files:
path = f"data/{file}"
if file.startswith("ext_"):
schema = file.replace("ext_", "").split(".")[0]
print(f"Migrating: {schema}")
migrate_ext(path, schema, ignore_missing=not args.dont_ignore_missing)
filename = os.path.basename(file)
if filename.startswith("ext_"):
schema = filename.replace("ext_", "").split(".")[0]
print(f"Migrating: {file}")
migrate_ext(
file,
schema,
ignore_missing=args.skip_missing,
)