mirror of
https://github.com/fusion44/blitz_api.git
synced 2026-08-13 11:52:45 +02:00
fix: adapt code to recent dependeny upgrades
This commit is contained in:
parent
67b72dc755
commit
b7e9ae29fb
6 changed files with 92 additions and 66 deletions
|
|
@ -140,10 +140,10 @@ class NetworkInfo(BaseModel):
|
|||
connections_out: int = Query(..., description="The number of outbound connections")
|
||||
network_active: bool = Query(..., description="Whether p2p networking is enabled")
|
||||
networks: List[BtcNetwork] = Query(..., description="Information per network")
|
||||
relay_fee: int = Query(
|
||||
relay_fee: float = Query(
|
||||
..., description="Minimum relay fee for transactions in BTC/kB"
|
||||
)
|
||||
incremental_fee: int = Query(
|
||||
incremental_fee: float = Query(
|
||||
...,
|
||||
description=(
|
||||
"Minimum fee increment for mempool limiting or BIP 125 "
|
||||
|
|
@ -228,7 +228,7 @@ class Bip9Data(BaseModel):
|
|||
...,
|
||||
description='One of "defined", "started", "locked_in", "active", "failed"',
|
||||
)
|
||||
bit: int = Query(
|
||||
bit: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"the bit(0-28) in the block version field used to signal this "
|
||||
|
|
@ -255,21 +255,21 @@ class Bip9Data(BaseModel):
|
|||
min_activation_height: int = Query(
|
||||
..., description="Minimum height of blocks for which the rules may be enforced"
|
||||
)
|
||||
statistics: Bip9Statistics = Query(
|
||||
statistics: Bip9Statistics | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"numeric statistics about BIP9 signalling for a "
|
||||
"softfork(only for `started` status)"
|
||||
),
|
||||
)
|
||||
height: int = Query(
|
||||
height: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Height of the first block which the rules are or will be "
|
||||
"enforced(only for `buried` type, or `bip9` type with `active` status)"
|
||||
),
|
||||
)
|
||||
active: bool = Query(
|
||||
active: bool | None = Query(
|
||||
None,
|
||||
description="True if the rules are enforced for the mempool and the next block",
|
||||
)
|
||||
|
|
@ -300,10 +300,10 @@ class SoftFork(BaseModel):
|
|||
"True **if** the rules are enforced for the mempool and the next block"
|
||||
),
|
||||
)
|
||||
bip9: Bip9Data = Query(
|
||||
bip9: Bip9Data | None = Query(
|
||||
None, description='Status of bip9 softforks(only for "bip9" type)'
|
||||
)
|
||||
height: int = Query(
|
||||
height: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Height of the first block which the rules are or will be enforced "
|
||||
|
|
@ -337,7 +337,7 @@ class BlockchainInfo(BaseModel):
|
|||
best_block_hash: str = Query(
|
||||
..., description="The hash of the currently best block"
|
||||
)
|
||||
difficulty: int = Query(..., description="The current difficulty")
|
||||
difficulty: float = Query(..., description="The current difficulty")
|
||||
mediantime: int = Query(..., description="Median time for the current best block")
|
||||
verification_progress: float = Query(
|
||||
..., description="Estimate of verification progress[0..1]"
|
||||
|
|
@ -353,19 +353,19 @@ class BlockchainInfo(BaseModel):
|
|||
..., description="The estimated size of the block and undo files on disk"
|
||||
)
|
||||
pruned: bool = Query(..., description="If the blocks are subject to pruning")
|
||||
prune_height: int = Query(
|
||||
prune_height: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Lowest-height complete block stored(only present if pruning is enabled)"
|
||||
),
|
||||
)
|
||||
automatic_pruning: bool = Query(
|
||||
automatic_pruning: bool | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Whether automatic pruning is enabled(only present if pruning is enabled)"
|
||||
),
|
||||
)
|
||||
prune_target_size: int = Query(
|
||||
prune_target_size: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"The target size used by pruning(only present if automatic pruning is "
|
||||
|
|
@ -395,12 +395,12 @@ class BlockchainInfo(BaseModel):
|
|||
chainwork=r["chainwork"],
|
||||
size_on_disk=r["size_on_disk"],
|
||||
pruned=r["pruned"],
|
||||
pruned_height=None if "pruneheight" not in r else r["pruneheight"],
|
||||
prune_height=None if "pruneheight" not in r else r["pruneheight"],
|
||||
automatic_pruning=(
|
||||
None if "automatic_pruning" not in r else r["automatic_pruning"]
|
||||
None if "automatic_pruning" not in r else bool(r["automatic_pruning"])
|
||||
),
|
||||
prune_target_size=(
|
||||
None if "prune_target_size" not in r else r["prune_target_size"]
|
||||
None if "prune_target_size" not in r else int(r["prune_target_size"])
|
||||
),
|
||||
warnings=r["warnings"],
|
||||
softforks=softforks,
|
||||
|
|
@ -422,7 +422,7 @@ class BtcInfo(BaseModel):
|
|||
verification_progress: float = Query(
|
||||
..., description="Estimate of verification progress[0..1]"
|
||||
)
|
||||
difficulty: int = Query(..., description="The current difficulty")
|
||||
difficulty: float = Query(..., description="The current difficulty")
|
||||
size_on_disk: int = Query(
|
||||
..., description="The estimated size of the block and undo files on disk"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
lastpay_index = 0
|
||||
_current_id: int = _WAIT_ANY_INVOICE_ID + 1
|
||||
_futures: dict[int, asyncio.Future] = {}
|
||||
_socket_path: str = None
|
||||
_reader: asyncio.StreamReader = None
|
||||
_writer: asyncio.StreamWriter = None
|
||||
_loop: asyncio.AbstractEventLoop = None
|
||||
_socket_path: str | None = None
|
||||
_reader: asyncio.StreamReader | None = None
|
||||
_writer: asyncio.StreamWriter | None = None
|
||||
_loop: asyncio.AbstractEventLoop | None = None
|
||||
_initialized: bool = False
|
||||
_invoice_queue = asyncio.Queue()
|
||||
|
||||
|
|
@ -78,7 +78,8 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
yield InitLnRepoUpdate(state=LnInitState.BOOTSTRAPPING)
|
||||
|
||||
try:
|
||||
self._socket_path = decouple.config("cln_jrpc_path")
|
||||
self._socket_path = str(decouple.config("cln_jrpc_path"))
|
||||
print(decouple.config("cln_jrpc_path"))
|
||||
except decouple.UndefinedValueError as e:
|
||||
logger.debug(e)
|
||||
logger.error(
|
||||
|
|
@ -102,6 +103,13 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
|
||||
while True:
|
||||
try:
|
||||
if not isinstance(self._socket_path, str):
|
||||
logger.trace(
|
||||
f"self._socket_path is None, retrying {type(self._socket_path)}"
|
||||
)
|
||||
await asyncio.sleep(10)
|
||||
continue
|
||||
|
||||
self._reader, self._writer = await asyncio.open_unix_connection(
|
||||
path=self._socket_path,
|
||||
limit=_SOCKET_BUFFER_SIZE_LIMIT,
|
||||
|
|
@ -145,7 +153,7 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
chan_local = chan_remote = chan_pending_local = chan_pending_remote = 0
|
||||
|
||||
for o in res["outputs"]:
|
||||
sat = parse_cln_msat(o["amount_msat"]) / 1000
|
||||
sat = int(parse_cln_msat(o["amount_msat"]) / 1000)
|
||||
|
||||
if o["status"] == "unconfirmed":
|
||||
onchain_unconfirmed += sat
|
||||
|
|
@ -227,7 +235,7 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
tx = []
|
||||
for invoice in res[0]:
|
||||
i = GenericTx.from_invoice(invoice)
|
||||
if successful_only and i["status"] == "succeeded":
|
||||
if successful_only and i.status == "succeeded":
|
||||
tx.append(i)
|
||||
continue
|
||||
tx.append(i)
|
||||
|
|
@ -240,7 +248,11 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
|
||||
tx.append(t)
|
||||
|
||||
for pay in res[2]: # type: Payment
|
||||
for pay in res[2]:
|
||||
if pay is not Payment:
|
||||
logger.error("Payment is not a payment class.")
|
||||
continue
|
||||
|
||||
comment = ""
|
||||
|
||||
if pay.payment_request is not None and len(pay.payment_request) > 0:
|
||||
|
|
@ -249,7 +261,7 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
|
||||
p = GenericTx.from_payment(pay, comment)
|
||||
|
||||
if successful_only and p["status"] == "succeeded":
|
||||
if successful_only and p.status == "succeeded":
|
||||
tx.append(p)
|
||||
continue
|
||||
|
||||
|
|
@ -524,7 +536,7 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
if "error" not in res:
|
||||
res = res["result"]
|
||||
r = SendCoinsResponse.from_cln_json(res, input)
|
||||
await broadcast_sse_msg(SSE.LN_ONCHAIN_PAYMENT_STATUS, r.dict())
|
||||
await broadcast_sse_msg(SSE.LN_ONCHAIN_PAYMENT_STATUS, r.model_dump())
|
||||
|
||||
return r
|
||||
|
||||
|
|
@ -681,7 +693,7 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
await self._refresh_invoice_sub(True)
|
||||
|
||||
@logger.catch(exclude=(HTTPException,))
|
||||
async def listen_forward_events(self) -> ForwardSuccessEvent:
|
||||
async def listen_forward_events(self) -> AsyncGenerator[ForwardSuccessEvent, None]:
|
||||
logger.trace("listen_forward_events()")
|
||||
|
||||
# CLN has no subscription to forwarded events.
|
||||
|
|
@ -731,10 +743,18 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
|
||||
if "error" in res:
|
||||
self._handle_open_channel_error(res["error"])
|
||||
res = res["result"]
|
||||
|
||||
if "txid" in res and "channel_id" in res:
|
||||
return res["txid"]
|
||||
res = res["result"]
|
||||
if "txid" not in res and "channel_id" not in res:
|
||||
self._handle_open_channel_error(
|
||||
{
|
||||
"message": (
|
||||
"Unable to find txid and channel_id in connect peer result"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return res["txid"]
|
||||
|
||||
def _handle_open_channel_error(self, error):
|
||||
logger.trace(f"_handle_open_channel_error({error})")
|
||||
|
|
@ -903,6 +923,10 @@ class LnNodeCLNjRPC(LightningNodeBase):
|
|||
|
||||
async def _read_loop(self):
|
||||
logger.trace("_read_loop()")
|
||||
if self._writer is None or self._reader is None:
|
||||
message = "Not initialized - _writer or _reader not yet available"
|
||||
logger.error(message)
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=message)
|
||||
|
||||
while not self._writer.is_closing():
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -479,7 +479,7 @@ This will show more debug information.
|
|||
expiry=expiry,
|
||||
r_hash=response.r_hash.hex(),
|
||||
payment_request=response.payment_request,
|
||||
add_index=response.add_index,
|
||||
add_index=str(response.add_index),
|
||||
payment_addr=response.payment_addr.hex(),
|
||||
state=InvoiceState.OPEN,
|
||||
is_keysend=is_keysend,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Union
|
||||
|
||||
|
|
@ -491,7 +492,7 @@ class Channel(BaseModel):
|
|||
|
||||
|
||||
class Invoice(BaseModel):
|
||||
memo: str = Query(
|
||||
memo: str | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Optional memo to attach along with the invoice. "
|
||||
|
|
@ -501,7 +502,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
r_preimage: str = Query(
|
||||
r_preimage: str | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"The hex-encoded preimage(32 byte) which will allow settling "
|
||||
|
|
@ -509,7 +510,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
r_hash: str = Query(None, description="The hash of the preimage.")
|
||||
r_hash: str | None = Query(None, description="The hash of the preimage.")
|
||||
|
||||
value_msat: int = Query(
|
||||
..., description="The value of this invoice in milli satoshis."
|
||||
|
|
@ -517,21 +518,23 @@ class Invoice(BaseModel):
|
|||
|
||||
settled: bool = Query(False, description="Whether this invoice has been fulfilled")
|
||||
|
||||
creation_date: int = Query(
|
||||
creation_date: int | None = Query(
|
||||
None,
|
||||
description="When this invoice was created. Not available with CLN.",
|
||||
)
|
||||
|
||||
settle_date: int = Query(
|
||||
settle_date: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"When this invoice was settled. " "Not available with pending invoices."
|
||||
),
|
||||
)
|
||||
|
||||
expiry_date: int = Query(None, description="The time at which this invoice expires")
|
||||
expiry_date: int | None = Query(
|
||||
None, description="The time at which this invoice expires"
|
||||
)
|
||||
|
||||
payment_request: str = Query(
|
||||
payment_request: str | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"A bare-bones invoice for a payment within the "
|
||||
|
|
@ -540,7 +543,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
description_hash: str = Query(
|
||||
description_hash: str | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Hash(SHA-256) of a description of the payment. Used if the description of "
|
||||
|
|
@ -549,21 +552,21 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
expiry: int = Query(
|
||||
expiry: int | None = Query(
|
||||
None,
|
||||
description="Payment request expiry time in seconds. Default is 3600 (1 hour).",
|
||||
)
|
||||
|
||||
fallback_addr: str = Query(None, description="Fallback on-chain address.")
|
||||
fallback_addr: str | None = Query(None, description="Fallback on-chain address.")
|
||||
|
||||
cltv_expiry: int = Query(
|
||||
cltv_expiry: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Delta to use for the time-lock of the CLTV extended to the final hop."
|
||||
),
|
||||
)
|
||||
|
||||
route_hints: List[RouteHint] = Query(
|
||||
route_hints: List[RouteHint] | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Route hints that can each be individually used to assist "
|
||||
|
|
@ -571,7 +574,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
private: bool = Query(
|
||||
private: bool | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Whether this invoice should include routing hints for private channels."
|
||||
|
|
@ -598,7 +601,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
settle_index: int = Query(
|
||||
settle_index: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"The `settle` index of this invoice. Each newly settled invoice will "
|
||||
|
|
@ -606,7 +609,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
amt_paid_sat: int = Query(
|
||||
amt_paid_sat: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"The amount that was accepted for this invoice, in satoshis. This "
|
||||
|
|
@ -618,7 +621,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
amt_paid_msat: int = Query(
|
||||
amt_paid_msat: int | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"The amount that was accepted for this invoice, in millisatoshis. "
|
||||
|
|
@ -632,15 +635,15 @@ class Invoice(BaseModel):
|
|||
|
||||
state: InvoiceState = Query(..., description="The state the invoice is in.")
|
||||
|
||||
htlcs: List[InvoiceHTLC] = Query(
|
||||
htlcs: List[InvoiceHTLC] | None = Query(
|
||||
None, description="List of HTLCs paying to this invoice[EXPERIMENTAL]."
|
||||
)
|
||||
|
||||
features: List[FeaturesEntry] = Query(
|
||||
features: List[FeaturesEntry] | None = Query(
|
||||
None, description="List of features advertised on the invoice."
|
||||
)
|
||||
|
||||
is_keysend: bool = Query(
|
||||
is_keysend: bool | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"[LND only] Indicates if this invoice was a spontaneous payment "
|
||||
|
|
@ -648,7 +651,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
payment_addr: str = Query(
|
||||
payment_addr: str | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"The payment address of this invoice. This value will be used "
|
||||
|
|
@ -657,7 +660,7 @@ class Invoice(BaseModel):
|
|||
),
|
||||
)
|
||||
|
||||
is_amp: bool = Query(
|
||||
is_amp: bool | None = Query(
|
||||
None, description="Signals whether or not this is an AMP invoice."
|
||||
)
|
||||
|
||||
|
|
@ -676,7 +679,6 @@ class Invoice(BaseModel):
|
|||
memo=i.memo,
|
||||
r_preimage=i.r_preimage.hex(),
|
||||
r_hash=i.r_hash.hex(),
|
||||
value=i.value,
|
||||
value_msat=i.value_msat,
|
||||
settled=i.settled,
|
||||
creation_date=i.creation_date,
|
||||
|
|
@ -689,7 +691,7 @@ class Invoice(BaseModel):
|
|||
cltv_expiry=i.cltv_expiry,
|
||||
route_hints=_route_hints(i.route_hints),
|
||||
private=i.private,
|
||||
add_index=i.add_index,
|
||||
add_index=str(i.add_index),
|
||||
settle_index=i.settle_index,
|
||||
amt_paid_sat=i.amt_paid_sat,
|
||||
amt_paid_msat=i.amt_paid_msat,
|
||||
|
|
@ -705,11 +707,10 @@ class Invoice(BaseModel):
|
|||
def from_cln_json(cls, i) -> "Invoice":
|
||||
amt = parse_cln_msat(i["amount_msat"])
|
||||
return cls(
|
||||
add_index=i["label"],
|
||||
add_index=str(i["label"]),
|
||||
memo=i["description"],
|
||||
r_preimage=i["payment_preimage"] if "payment_preimage" in i else None,
|
||||
r_hash=i["payment_hash"],
|
||||
value=amt / 1000,
|
||||
value_msat=amt,
|
||||
settled=True if i["status"] == "paid" else False,
|
||||
expiry_date=i["expires_at"],
|
||||
|
|
@ -717,7 +718,7 @@ class Invoice(BaseModel):
|
|||
payment_request=i["bolt11"],
|
||||
settle_index=i["pay_index"] if "pay_index" in i else None,
|
||||
amt_paid_sat=(
|
||||
parse_cln_msat(i["amount_received_msat"]) / 1000
|
||||
round(parse_cln_msat(i["amount_received_msat"]) / 1000)
|
||||
if "amount_received_msat" in i
|
||||
else None
|
||||
),
|
||||
|
|
@ -737,7 +738,6 @@ class Invoice(BaseModel):
|
|||
memo=i.description,
|
||||
r_preimage=i.payment_preimage.hex(),
|
||||
r_hash=i.payment_hash.hex(),
|
||||
value=i.amount_msat.msat / 1000,
|
||||
value_msat=i.amount_msat.msat,
|
||||
settled=True if state == InvoiceState.SETTLED else False,
|
||||
expiry_date=i.expires_at,
|
||||
|
|
@ -1208,6 +1208,7 @@ class Payment(BaseModel):
|
|||
def from_cln_jrpc(cls, p) -> "Payment":
|
||||
value = parse_cln_msat(p["amount_msat"])
|
||||
total_sent = parse_cln_msat(p["amount_sent_msat"])
|
||||
ts = time.localtime(p["created_at"])
|
||||
|
||||
return cls(
|
||||
payment_hash=p["payment_hash"],
|
||||
|
|
@ -1216,7 +1217,7 @@ class Payment(BaseModel):
|
|||
payment_request=p["bolt11"] if "bolt11" in p else "",
|
||||
status=PaymentStatus.from_cln_jrpc(p["status"]),
|
||||
fee_msat=total_sent - value,
|
||||
creation_time_ns=p["created_at"],
|
||||
creation_time_ns=ts.tm_sec,
|
||||
label=p["label"] if "label" in p else "",
|
||||
failure_reason=PaymentFailureReason.from_cln_jrpc(p),
|
||||
)
|
||||
|
|
@ -1623,10 +1624,10 @@ class LightningInfoLite(BaseModel):
|
|||
block_height: int = Query(
|
||||
..., description="The node's current view of the height of the best block"
|
||||
)
|
||||
synced_to_chain: bool = Query(
|
||||
synced_to_chain: bool | None = Query(
|
||||
None, description="Whether the wallet's view is synced to the main chain"
|
||||
)
|
||||
synced_to_graph: bool = Query(
|
||||
synced_to_graph: bool | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Whether we consider ourselves synced with " "the public channel graph."
|
||||
|
|
@ -1906,7 +1907,8 @@ class GenericTx(BaseModel):
|
|||
"category **onchain**."
|
||||
),
|
||||
)
|
||||
num_confs: Union[int, None] = Query(
|
||||
num_confs: int | None = Query(
|
||||
None,
|
||||
ge=0,
|
||||
description=(
|
||||
"Number of confirmations. Only applicable for category **onchain**."
|
||||
|
|
@ -1931,7 +1933,7 @@ class GenericTx(BaseModel):
|
|||
amount = i.value_msat
|
||||
|
||||
return cls(
|
||||
id=i.payment_request,
|
||||
id=i.payment_request or "",
|
||||
category=TxCategory.LIGHTNING,
|
||||
type=TxType.RECEIVE,
|
||||
amount=amount,
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ async def lifespan(app: FastAPI):
|
|||
remove_local_cookie()
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
app.include_router(app_router)
|
||||
app.include_router(bitcoin_router)
|
||||
if node_type != "none":
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from app.system.docs import get_debug_data_sample_str
|
|||
class LoginInput(BaseModel):
|
||||
password: constr(min_length=8)
|
||||
one_time_password: Optional[
|
||||
constr(min_length=6, max_length=6, regex="^[0-9]+$")
|
||||
constr(min_length=6, max_length=6, pattern="^[0-9]+$")
|
||||
] = None
|
||||
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class SystemInfo(BaseModel):
|
|||
"",
|
||||
description="The version of this platform",
|
||||
)
|
||||
code_version = Query(
|
||||
code_version: str = Query(
|
||||
"",
|
||||
description="[RaspiBlitz only] The code version.",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue