refactor: better namespace
This commit is contained in:
+48
-372
@@ -7,7 +7,6 @@ import secrets
|
||||
import time
|
||||
from collections.abc import Awaitable, Callable, Iterable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from typing import Any, TypeVar, cast, get_type_hints
|
||||
|
||||
@@ -16,37 +15,19 @@ from pydantic import BaseModel
|
||||
from lnbits.helpers import sha256s
|
||||
|
||||
from .models import (
|
||||
Bolt11Request,
|
||||
CreateInvoicePublicRequest,
|
||||
CreateInvoiceRequest,
|
||||
CreateInvoiceResponse,
|
||||
CurrencyConvertRequest,
|
||||
CurrencyConvertResponse,
|
||||
CurrencyListResponse,
|
||||
CurrencyRateRequest,
|
||||
CurrencyRateResponse,
|
||||
DecodeInvoiceResponse,
|
||||
EmptyRequest,
|
||||
ExtensionApiRequest,
|
||||
FiatToSatsRequest,
|
||||
FiatToSatsResponse,
|
||||
HttpRequest,
|
||||
HttpResponse,
|
||||
InvoiceAmountMsatResponse,
|
||||
InvoiceExpiryResponse,
|
||||
InvoiceMemoResponse,
|
||||
InvoicePaymentHashResponse,
|
||||
ListUserWalletsResponse,
|
||||
LogRequest,
|
||||
LogResponse,
|
||||
NowResponse,
|
||||
RandomIdRequest,
|
||||
RandomIdResponse,
|
||||
RandomSecretAndHashRequest,
|
||||
RandomSecretAndHashResponse,
|
||||
SatsToFiatRequest,
|
||||
SatsToFiatResponse,
|
||||
ServerHealthResponse,
|
||||
StorageDeleteRequest,
|
||||
StorageDeleteResponse,
|
||||
StorageGetRequest,
|
||||
@@ -56,9 +37,6 @@ from .models import (
|
||||
StorageSetRequest,
|
||||
StorageSetResponse,
|
||||
UserWalletSummary,
|
||||
ValidateInvoiceResponse,
|
||||
VerifyPreimageRequest,
|
||||
VerifyPreimageResponse,
|
||||
)
|
||||
from .storage import (
|
||||
storage_delete_row,
|
||||
@@ -80,6 +58,7 @@ class ExtensionAPIMethodExport:
|
||||
method_id: str
|
||||
namespace: str
|
||||
name: str
|
||||
host_interface: str
|
||||
host_name: str
|
||||
sdk_name: str
|
||||
description: str
|
||||
@@ -93,6 +72,7 @@ class ExtensionAPIMethod:
|
||||
namespace: str
|
||||
name: str
|
||||
python_name: str
|
||||
host_interface: str
|
||||
host_name: str
|
||||
sdk_name: str
|
||||
description: str
|
||||
@@ -114,16 +94,18 @@ def extension_api_method(
|
||||
host_name: str,
|
||||
sdk_name: str,
|
||||
description: str,
|
||||
host_interface: str = "host",
|
||||
required_permission: str | None = None,
|
||||
require_auth: bool = True,
|
||||
) -> Callable[
|
||||
[Callable[[ExtensionAPI, _RequestModel], Awaitable[_ResponseModel]]],
|
||||
Callable[[ExtensionAPI, _RequestModel], Awaitable[_ResponseModel]],
|
||||
[Callable[[Any, _RequestModel], Awaitable[_ResponseModel]]],
|
||||
Callable[[Any, _RequestModel], Awaitable[_ResponseModel]],
|
||||
]:
|
||||
export = ExtensionAPIMethodExport(
|
||||
method_id=method_id,
|
||||
namespace=namespace,
|
||||
name=name,
|
||||
host_interface=host_interface,
|
||||
host_name=host_name,
|
||||
sdk_name=sdk_name,
|
||||
description=description,
|
||||
@@ -132,15 +114,16 @@ def extension_api_method(
|
||||
)
|
||||
|
||||
def decorator(
|
||||
function: Callable[[ExtensionAPI, _RequestModel], Awaitable[_ResponseModel]],
|
||||
) -> Callable[[ExtensionAPI, _RequestModel], Awaitable[_ResponseModel]]:
|
||||
function: Callable[[Any, _RequestModel], Awaitable[_ResponseModel]],
|
||||
) -> Callable[[Any, _RequestModel], Awaitable[_ResponseModel]]:
|
||||
@wraps(function)
|
||||
async def wrapper(self: ExtensionAPI, request: _RequestModel) -> _ResponseModel:
|
||||
if require_auth and not self.has_authenticated_context():
|
||||
async def wrapper(self: Any, request: _RequestModel) -> _ResponseModel:
|
||||
api = getattr(self, "api", self)
|
||||
if require_auth and not api.has_authenticated_context():
|
||||
raise PermissionError(
|
||||
f"Extension API method '{method_id}' requires authentication."
|
||||
)
|
||||
self.require_permission(required_permission)
|
||||
api.require_permission(required_permission)
|
||||
return await function(self, request)
|
||||
|
||||
setattr(wrapper, _EXTENSION_API_METHOD_ATTR, export)
|
||||
@@ -167,6 +150,9 @@ class ExtensionAPI:
|
||||
self.context = context
|
||||
self.owner_id = sha256s(user_id) if user_id else owner_id
|
||||
self._uuid = secrets.token_urlsafe(12).replace("-", "_")
|
||||
from .api_utils import ExtensionAPIUtils
|
||||
|
||||
self.utils = ExtensionAPIUtils(self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -502,281 +488,6 @@ class ExtensionAPI:
|
||||
log("extension:%s %s", self.extension_id, request.message)
|
||||
return LogResponse()
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.list",
|
||||
namespace="utils",
|
||||
name="List currencies",
|
||||
host_name="utils_currencies_list",
|
||||
sdk_name="currenciesList",
|
||||
description="List currencies supported by LNbits exchange-rate conversion.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_currencies_list(
|
||||
self, request: EmptyRequest
|
||||
) -> CurrencyListResponse:
|
||||
from lnbits.utils.exchange_rates import allowed_currencies
|
||||
|
||||
return CurrencyListResponse(currencies=allowed_currencies())
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.rate",
|
||||
namespace="utils",
|
||||
name="Get currency rate",
|
||||
host_name="utils_currencies_rate",
|
||||
sdk_name="currenciesRate",
|
||||
description="Get sats-per-fiat and BTC price for a currency.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_currencies_rate(
|
||||
self, request: CurrencyRateRequest
|
||||
) -> CurrencyRateResponse:
|
||||
from lnbits.utils.exchange_rates import get_fiat_rate_and_price_satoshis
|
||||
|
||||
rate, price = await get_fiat_rate_and_price_satoshis(request.currency)
|
||||
return CurrencyRateResponse(rate=rate, price=price)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.convert",
|
||||
namespace="utils",
|
||||
name="Convert currency amount",
|
||||
host_name="utils_currencies_convert",
|
||||
sdk_name="currenciesConvert",
|
||||
description="Convert between sats, BTC, and supported fiat currencies.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_currencies_convert(
|
||||
self, request: CurrencyConvertRequest
|
||||
) -> CurrencyConvertResponse:
|
||||
from lnbits.utils.exchange_rates import (
|
||||
fiat_amount_as_satoshis,
|
||||
satoshis_amount_as_fiat,
|
||||
)
|
||||
|
||||
from_currency = request.from_currency
|
||||
if from_currency == "sats":
|
||||
from_currency = "sat"
|
||||
|
||||
amounts: list[tuple[str, float]] = []
|
||||
if from_currency == "sat":
|
||||
sats = int(request.amount)
|
||||
amounts.append(("BTC", sats / 100_000_000))
|
||||
amounts.append(("sats", sats))
|
||||
for currency in request.to.split(","):
|
||||
currency = currency.strip()
|
||||
if currency:
|
||||
amounts.append(
|
||||
(
|
||||
currency.upper(),
|
||||
await satoshis_amount_as_fiat(sats, currency),
|
||||
)
|
||||
)
|
||||
else:
|
||||
sats = await fiat_amount_as_satoshis(request.amount, from_currency)
|
||||
amounts.append((from_currency.upper(), request.amount))
|
||||
amounts.append(("sats", sats))
|
||||
amounts.append(("BTC", sats / 100_000_000))
|
||||
return CurrencyConvertResponse(amounts=amounts)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.fiat_to_sats",
|
||||
namespace="utils",
|
||||
name="Convert fiat to sats",
|
||||
host_name="utils_currencies_fiat_to_sats",
|
||||
sdk_name="currenciesFiatToSats",
|
||||
description="Convert a fiat amount to sats.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_currencies_fiat_to_sats(
|
||||
self, request: FiatToSatsRequest
|
||||
) -> FiatToSatsResponse:
|
||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
||||
|
||||
return FiatToSatsResponse(
|
||||
amount_sat=await fiat_amount_as_satoshis(
|
||||
request.amount,
|
||||
request.currency,
|
||||
)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.sats_to_fiat",
|
||||
namespace="utils",
|
||||
name="Convert sats to fiat",
|
||||
host_name="utils_currencies_sats_to_fiat",
|
||||
sdk_name="currenciesSatsToFiat",
|
||||
description="Convert a sats amount to fiat.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_currencies_sats_to_fiat(
|
||||
self, request: SatsToFiatRequest
|
||||
) -> SatsToFiatResponse:
|
||||
from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
|
||||
|
||||
return SatsToFiatResponse(
|
||||
amount=await satoshis_amount_as_fiat(request.amount, request.currency)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.server.health",
|
||||
namespace="utils",
|
||||
name="Server health",
|
||||
host_name="utils_server_health",
|
||||
sdk_name="serverHealth",
|
||||
description="Return basic public LNbits server health data.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_server_health(self, request: EmptyRequest) -> ServerHealthResponse:
|
||||
from lnbits.settings import settings
|
||||
|
||||
return ServerHealthResponse(
|
||||
server_time=int(time.time()),
|
||||
up_time=settings.lnbits_server_up_time,
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.decode_invoice",
|
||||
namespace="utils",
|
||||
name="Decode Lightning invoice",
|
||||
host_name="utils_lightning_decode_invoice",
|
||||
sdk_name="lightningDecodeInvoice",
|
||||
description="Decode a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_decode_invoice(
|
||||
self, request: Bolt11Request
|
||||
) -> DecodeInvoiceResponse:
|
||||
invoice = _decode_bolt11(request.bolt11)
|
||||
return _decoded_invoice_response(invoice)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.validate_invoice",
|
||||
namespace="utils",
|
||||
name="Validate Lightning invoice",
|
||||
host_name="utils_lightning_validate_invoice",
|
||||
sdk_name="lightningValidateInvoice",
|
||||
description="Validate whether a string is a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_validate_invoice(
|
||||
self, request: Bolt11Request
|
||||
) -> ValidateInvoiceResponse:
|
||||
try:
|
||||
_decode_bolt11(request.bolt11)
|
||||
return ValidateInvoiceResponse(valid=True)
|
||||
except Exception as exc:
|
||||
return ValidateInvoiceResponse(valid=False, error=str(exc))
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_payment_hash",
|
||||
namespace="utils",
|
||||
name="Get Lightning invoice payment hash",
|
||||
host_name="utils_lightning_invoice_payment_hash",
|
||||
sdk_name="lightningInvoicePaymentHash",
|
||||
description="Get the payment hash from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_invoice_payment_hash(
|
||||
self, request: Bolt11Request
|
||||
) -> InvoicePaymentHashResponse:
|
||||
return InvoicePaymentHashResponse(
|
||||
payment_hash=str(_decode_bolt11(request.bolt11).payment_hash)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_amount_msat",
|
||||
namespace="utils",
|
||||
name="Get Lightning invoice amount",
|
||||
host_name="utils_lightning_invoice_amount_msat",
|
||||
sdk_name="lightningInvoiceAmountMsat",
|
||||
description="Get the amount in msat from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_invoice_amount_msat(
|
||||
self, request: Bolt11Request
|
||||
) -> InvoiceAmountMsatResponse:
|
||||
return InvoiceAmountMsatResponse(
|
||||
amount_msat=_invoice_amount_msat(_decode_bolt11(request.bolt11))
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_expiry",
|
||||
namespace="utils",
|
||||
name="Get Lightning invoice expiry",
|
||||
host_name="utils_lightning_invoice_expiry",
|
||||
sdk_name="lightningInvoiceExpiry",
|
||||
description="Get the expiry timestamp from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_invoice_expiry(
|
||||
self, request: Bolt11Request
|
||||
) -> InvoiceExpiryResponse:
|
||||
return InvoiceExpiryResponse(
|
||||
expires_at=_invoice_expires_at(_decode_bolt11(request.bolt11))
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_memo",
|
||||
namespace="utils",
|
||||
name="Get Lightning invoice memo",
|
||||
host_name="utils_lightning_invoice_memo",
|
||||
sdk_name="lightningInvoiceMemo",
|
||||
description="Get the memo from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_invoice_memo(
|
||||
self, request: Bolt11Request
|
||||
) -> InvoiceMemoResponse:
|
||||
return InvoiceMemoResponse(memo=_invoice_memo(_decode_bolt11(request.bolt11)))
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.verify_preimage",
|
||||
namespace="utils",
|
||||
name="Verify Lightning preimage",
|
||||
host_name="utils_lightning_verify_preimage",
|
||||
sdk_name="lightningVerifyPreimage",
|
||||
description="Verify that a preimage matches a payment hash.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_verify_preimage(
|
||||
self, request: VerifyPreimageRequest
|
||||
) -> VerifyPreimageResponse:
|
||||
from lnbits.utils.crypto import verify_preimage
|
||||
|
||||
return VerifyPreimageResponse(
|
||||
valid=verify_preimage(request.preimage, request.payment_hash)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.random_secret_and_hash",
|
||||
namespace="utils",
|
||||
name="Random Lightning secret and hash",
|
||||
host_name="utils_lightning_random_secret_and_hash",
|
||||
sdk_name="lightningRandomSecretAndHash",
|
||||
description="Create a random secret and matching SHA256 hash.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def utils_lightning_random_secret_and_hash(
|
||||
self, request: RandomSecretAndHashRequest
|
||||
) -> RandomSecretAndHashResponse:
|
||||
from lnbits.utils.crypto import random_secret_and_hash
|
||||
|
||||
secret, payment_hash = random_secret_and_hash(request.length)
|
||||
return RandomSecretAndHashResponse(secret=secret, hash=payment_hash)
|
||||
|
||||
@staticmethod
|
||||
def _permission_data(
|
||||
permissions: Iterable[Any],
|
||||
@@ -846,85 +557,49 @@ class ExtensionAPI:
|
||||
return table, wallet_field
|
||||
|
||||
|
||||
def _decode_bolt11(payment_request: str) -> Any:
|
||||
from lnbits import bolt11
|
||||
|
||||
return bolt11.decode(payment_request)
|
||||
|
||||
|
||||
def _decoded_invoice_response(invoice: Any) -> DecodeInvoiceResponse:
|
||||
return DecodeInvoiceResponse(
|
||||
payment_hash=str(getattr(invoice, "payment_hash", "")) or None,
|
||||
amount_msat=_invoice_amount_msat(invoice),
|
||||
expiry=_invoice_expiry(invoice),
|
||||
expires_at=_invoice_expires_at(invoice),
|
||||
memo=_invoice_memo(invoice),
|
||||
)
|
||||
|
||||
|
||||
def _invoice_amount_msat(invoice: Any) -> int | None:
|
||||
amount_msat = getattr(invoice, "amount_msat", None)
|
||||
if amount_msat is None:
|
||||
return None
|
||||
return int(amount_msat)
|
||||
|
||||
|
||||
def _invoice_expiry(invoice: Any) -> int | None:
|
||||
expiry = getattr(invoice, "expiry", None)
|
||||
if expiry is None:
|
||||
return None
|
||||
return int(expiry)
|
||||
|
||||
|
||||
def _invoice_expires_at(invoice: Any) -> int | None:
|
||||
expiry_date = getattr(invoice, "expiry_date", None)
|
||||
if isinstance(expiry_date, datetime):
|
||||
return int(expiry_date.timestamp())
|
||||
|
||||
date = getattr(invoice, "date", None)
|
||||
expiry = getattr(invoice, "expiry", None)
|
||||
if isinstance(date, datetime) and expiry is not None:
|
||||
return int(date.timestamp() + int(expiry))
|
||||
if isinstance(date, (int, float)) and expiry is not None:
|
||||
return int(date + int(expiry))
|
||||
return None
|
||||
|
||||
|
||||
def _invoice_memo(invoice: Any) -> str | None:
|
||||
memo = getattr(invoice, "description", None)
|
||||
return str(memo) if memo is not None else None
|
||||
|
||||
|
||||
def list_extension_api_methods(
|
||||
api_cls: type[ExtensionAPI] = ExtensionAPI,
|
||||
) -> list[ExtensionAPIMethod]:
|
||||
methods: list[ExtensionAPIMethod] = []
|
||||
|
||||
for python_name, function in inspect.getmembers(api_cls, inspect.isfunction):
|
||||
export = getattr(function, _EXTENSION_API_METHOD_ATTR, None)
|
||||
if not export:
|
||||
continue
|
||||
for prefix, method_cls in _extension_api_method_sources(api_cls):
|
||||
for python_name, function in inspect.getmembers(method_cls, inspect.isfunction):
|
||||
export = getattr(function, _EXTENSION_API_METHOD_ATTR, None)
|
||||
if not export:
|
||||
continue
|
||||
|
||||
request_model, response_model = _get_method_models(function)
|
||||
methods.append(
|
||||
ExtensionAPIMethod(
|
||||
method_id=export.method_id,
|
||||
namespace=export.namespace,
|
||||
name=export.name,
|
||||
python_name=python_name,
|
||||
host_name=export.host_name,
|
||||
sdk_name=export.sdk_name,
|
||||
description=export.description,
|
||||
request_model=request_model,
|
||||
response_model=response_model,
|
||||
required_permission=export.required_permission,
|
||||
require_auth=export.require_auth,
|
||||
request_model, response_model = _get_method_models(function)
|
||||
methods.append(
|
||||
ExtensionAPIMethod(
|
||||
method_id=export.method_id,
|
||||
namespace=export.namespace,
|
||||
name=export.name,
|
||||
python_name=f"{prefix}.{python_name}" if prefix else python_name,
|
||||
host_interface=export.host_interface,
|
||||
host_name=export.host_name,
|
||||
sdk_name=export.sdk_name,
|
||||
description=export.description,
|
||||
request_model=request_model,
|
||||
response_model=response_model,
|
||||
required_permission=export.required_permission,
|
||||
require_auth=export.require_auth,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return sorted(methods, key=lambda method: method.method_id)
|
||||
|
||||
|
||||
def _extension_api_method_sources(
|
||||
api_cls: type[ExtensionAPI],
|
||||
) -> list[tuple[str, type[Any]]]:
|
||||
sources: list[tuple[str, type[Any]]] = [("", api_cls)]
|
||||
if issubclass(api_cls, ExtensionAPI):
|
||||
from .api_utils import extension_api_utils_method_classes
|
||||
|
||||
sources.extend(extension_api_utils_method_classes().items())
|
||||
return sources
|
||||
|
||||
|
||||
def extension_api_permission_ids(
|
||||
api_cls: type[ExtensionAPI] = ExtensionAPI,
|
||||
) -> set[str]:
|
||||
@@ -956,6 +631,7 @@ def extension_api_contract(
|
||||
"namespace": method.namespace,
|
||||
"name": method.name,
|
||||
"python_name": method.python_name,
|
||||
"host_interface": method.host_interface,
|
||||
"host_name": method.host_name,
|
||||
"sdk_name": method.sdk_name,
|
||||
"sdk_qualified_name": method.sdk_qualified_name,
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .api import extension_api_method
|
||||
from .models import (
|
||||
Bolt11Request,
|
||||
CurrencyConvertRequest,
|
||||
CurrencyConvertResponse,
|
||||
CurrencyListResponse,
|
||||
CurrencyRateRequest,
|
||||
CurrencyRateResponse,
|
||||
DecodeInvoiceResponse,
|
||||
EmptyRequest,
|
||||
FiatToSatsRequest,
|
||||
FiatToSatsResponse,
|
||||
InvoiceAmountMsatResponse,
|
||||
InvoiceExpiryResponse,
|
||||
InvoiceMemoResponse,
|
||||
InvoicePaymentHashResponse,
|
||||
RandomSecretAndHashRequest,
|
||||
RandomSecretAndHashResponse,
|
||||
SatsToFiatRequest,
|
||||
SatsToFiatResponse,
|
||||
ServerHealthResponse,
|
||||
ValidateInvoiceResponse,
|
||||
VerifyPreimageRequest,
|
||||
VerifyPreimageResponse,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .api import ExtensionAPI
|
||||
|
||||
|
||||
class ExtensionAPIUtils:
|
||||
def __init__(self, api: ExtensionAPI) -> None:
|
||||
self.api = api
|
||||
self.currencies = ExtensionCurrencyUtils(api)
|
||||
self.server = ExtensionServerUtils(api)
|
||||
self.lightning = ExtensionLightningUtils(api)
|
||||
|
||||
|
||||
class _ExtensionAPIUtilsGroup:
|
||||
def __init__(self, api: ExtensionAPI) -> None:
|
||||
self.api = api
|
||||
|
||||
|
||||
class ExtensionCurrencyUtils(_ExtensionAPIUtilsGroup):
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.list",
|
||||
namespace="utils.currencies",
|
||||
name="List currencies",
|
||||
host_interface="utils-currencies",
|
||||
host_name="list_currencies",
|
||||
sdk_name="list",
|
||||
description="List currencies supported by LNbits exchange-rate conversion.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def list(self, request: EmptyRequest) -> CurrencyListResponse:
|
||||
from lnbits.utils.exchange_rates import allowed_currencies
|
||||
|
||||
return CurrencyListResponse(currencies=allowed_currencies())
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.rate",
|
||||
namespace="utils.currencies",
|
||||
name="Get currency rate",
|
||||
host_interface="utils-currencies",
|
||||
host_name="rate",
|
||||
sdk_name="rate",
|
||||
description="Get sats-per-fiat and BTC price for a currency.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def rate(self, request: CurrencyRateRequest) -> CurrencyRateResponse:
|
||||
from lnbits.utils.exchange_rates import get_fiat_rate_and_price_satoshis
|
||||
|
||||
rate, price = await get_fiat_rate_and_price_satoshis(request.currency)
|
||||
return CurrencyRateResponse(rate=rate, price=price)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.convert",
|
||||
namespace="utils.currencies",
|
||||
name="Convert currency amount",
|
||||
host_interface="utils-currencies",
|
||||
host_name="convert",
|
||||
sdk_name="convert",
|
||||
description="Convert between sats, BTC, and supported fiat currencies.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def convert(self, request: CurrencyConvertRequest) -> CurrencyConvertResponse:
|
||||
from lnbits.utils.exchange_rates import (
|
||||
fiat_amount_as_satoshis,
|
||||
satoshis_amount_as_fiat,
|
||||
)
|
||||
|
||||
from_currency = request.from_currency
|
||||
if from_currency == "sats":
|
||||
from_currency = "sat"
|
||||
|
||||
amounts: list[tuple[str, float]] = []
|
||||
if from_currency == "sat":
|
||||
sats = int(request.amount)
|
||||
amounts.append(("BTC", sats / 100_000_000))
|
||||
amounts.append(("sats", sats))
|
||||
for currency in request.to.split(","):
|
||||
currency = currency.strip()
|
||||
if currency:
|
||||
amounts.append(
|
||||
(
|
||||
currency.upper(),
|
||||
await satoshis_amount_as_fiat(sats, currency),
|
||||
)
|
||||
)
|
||||
else:
|
||||
sats = await fiat_amount_as_satoshis(request.amount, from_currency)
|
||||
amounts.append((from_currency.upper(), request.amount))
|
||||
amounts.append(("sats", sats))
|
||||
amounts.append(("BTC", sats / 100_000_000))
|
||||
return CurrencyConvertResponse(amounts=amounts)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.fiat_to_sats",
|
||||
namespace="utils.currencies",
|
||||
name="Convert fiat to sats",
|
||||
host_interface="utils-currencies",
|
||||
host_name="fiat_to_sats",
|
||||
sdk_name="fiatToSats",
|
||||
description="Convert a fiat amount to sats.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def fiat_to_sats(self, request: FiatToSatsRequest) -> FiatToSatsResponse:
|
||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
||||
|
||||
return FiatToSatsResponse(
|
||||
amount_sat=await fiat_amount_as_satoshis(
|
||||
request.amount,
|
||||
request.currency,
|
||||
)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.currencies.sats_to_fiat",
|
||||
namespace="utils.currencies",
|
||||
name="Convert sats to fiat",
|
||||
host_interface="utils-currencies",
|
||||
host_name="sats_to_fiat",
|
||||
sdk_name="satsToFiat",
|
||||
description="Convert a sats amount to fiat.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def sats_to_fiat(self, request: SatsToFiatRequest) -> SatsToFiatResponse:
|
||||
from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
|
||||
|
||||
return SatsToFiatResponse(
|
||||
amount=await satoshis_amount_as_fiat(request.amount, request.currency)
|
||||
)
|
||||
|
||||
|
||||
class ExtensionServerUtils(_ExtensionAPIUtilsGroup):
|
||||
@extension_api_method(
|
||||
method_id="utils.server.health",
|
||||
namespace="utils.server",
|
||||
name="Server health",
|
||||
host_interface="utils-server",
|
||||
host_name="health",
|
||||
sdk_name="health",
|
||||
description="Return basic public LNbits server health data.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def health(self, request: EmptyRequest) -> ServerHealthResponse:
|
||||
from lnbits.settings import settings
|
||||
|
||||
return ServerHealthResponse(
|
||||
server_time=int(time.time()),
|
||||
up_time=settings.lnbits_server_up_time,
|
||||
)
|
||||
|
||||
|
||||
class ExtensionLightningUtils(_ExtensionAPIUtilsGroup):
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.decode_invoice",
|
||||
namespace="utils.lightning",
|
||||
name="Decode Lightning invoice",
|
||||
host_interface="utils-lightning",
|
||||
host_name="decode_invoice",
|
||||
sdk_name="decodeInvoice",
|
||||
description="Decode a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def decode_invoice(self, request: Bolt11Request) -> DecodeInvoiceResponse:
|
||||
invoice = _decode_bolt11(request.bolt11)
|
||||
return _decoded_invoice_response(invoice)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.validate_invoice",
|
||||
namespace="utils.lightning",
|
||||
name="Validate Lightning invoice",
|
||||
host_interface="utils-lightning",
|
||||
host_name="validate_invoice",
|
||||
sdk_name="validateInvoice",
|
||||
description="Validate whether a string is a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def validate_invoice(self, request: Bolt11Request) -> ValidateInvoiceResponse:
|
||||
try:
|
||||
_decode_bolt11(request.bolt11)
|
||||
return ValidateInvoiceResponse(valid=True)
|
||||
except Exception as exc:
|
||||
return ValidateInvoiceResponse(valid=False, error=str(exc))
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_payment_hash",
|
||||
namespace="utils.lightning",
|
||||
name="Get Lightning invoice payment hash",
|
||||
host_interface="utils-lightning",
|
||||
host_name="invoice_payment_hash",
|
||||
sdk_name="invoicePaymentHash",
|
||||
description="Get the payment hash from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def invoice_payment_hash(
|
||||
self, request: Bolt11Request
|
||||
) -> InvoicePaymentHashResponse:
|
||||
return InvoicePaymentHashResponse(
|
||||
payment_hash=str(_decode_bolt11(request.bolt11).payment_hash)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_amount_msat",
|
||||
namespace="utils.lightning",
|
||||
name="Get Lightning invoice amount",
|
||||
host_interface="utils-lightning",
|
||||
host_name="invoice_amount_msat",
|
||||
sdk_name="invoiceAmountMsat",
|
||||
description="Get the amount in msat from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def invoice_amount_msat(
|
||||
self, request: Bolt11Request
|
||||
) -> InvoiceAmountMsatResponse:
|
||||
return InvoiceAmountMsatResponse(
|
||||
amount_msat=_invoice_amount_msat(_decode_bolt11(request.bolt11))
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_expiry",
|
||||
namespace="utils.lightning",
|
||||
name="Get Lightning invoice expiry",
|
||||
host_interface="utils-lightning",
|
||||
host_name="invoice_expiry",
|
||||
sdk_name="invoiceExpiry",
|
||||
description="Get the expiry timestamp from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def invoice_expiry(self, request: Bolt11Request) -> InvoiceExpiryResponse:
|
||||
return InvoiceExpiryResponse(
|
||||
expires_at=_invoice_expires_at(_decode_bolt11(request.bolt11))
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.invoice_memo",
|
||||
namespace="utils.lightning",
|
||||
name="Get Lightning invoice memo",
|
||||
host_interface="utils-lightning",
|
||||
host_name="invoice_memo",
|
||||
sdk_name="invoiceMemo",
|
||||
description="Get the memo from a BOLT11 Lightning invoice.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def invoice_memo(self, request: Bolt11Request) -> InvoiceMemoResponse:
|
||||
return InvoiceMemoResponse(memo=_invoice_memo(_decode_bolt11(request.bolt11)))
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.verify_preimage",
|
||||
namespace="utils.lightning",
|
||||
name="Verify Lightning preimage",
|
||||
host_interface="utils-lightning",
|
||||
host_name="verify_preimage",
|
||||
sdk_name="verifyPreimage",
|
||||
description="Verify that a preimage matches a payment hash.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def verify_preimage(
|
||||
self, request: VerifyPreimageRequest
|
||||
) -> VerifyPreimageResponse:
|
||||
from lnbits.utils.crypto import verify_preimage
|
||||
|
||||
return VerifyPreimageResponse(
|
||||
valid=verify_preimage(request.preimage, request.payment_hash)
|
||||
)
|
||||
|
||||
@extension_api_method(
|
||||
method_id="utils.lightning.random_secret_and_hash",
|
||||
namespace="utils.lightning",
|
||||
name="Random Lightning secret and hash",
|
||||
host_interface="utils-lightning",
|
||||
host_name="random_secret_and_hash",
|
||||
sdk_name="randomSecretAndHash",
|
||||
description="Create a random secret and matching SHA256 hash.",
|
||||
required_permission="utils.basic",
|
||||
require_auth=False,
|
||||
)
|
||||
async def random_secret_and_hash(
|
||||
self, request: RandomSecretAndHashRequest
|
||||
) -> RandomSecretAndHashResponse:
|
||||
from lnbits.utils.crypto import random_secret_and_hash
|
||||
|
||||
secret, payment_hash = random_secret_and_hash(request.length)
|
||||
return RandomSecretAndHashResponse(secret=secret, hash=payment_hash)
|
||||
|
||||
|
||||
def extension_api_utils_method_classes() -> dict[str, type[_ExtensionAPIUtilsGroup]]:
|
||||
return {
|
||||
"utils.currencies": ExtensionCurrencyUtils,
|
||||
"utils.server": ExtensionServerUtils,
|
||||
"utils.lightning": ExtensionLightningUtils,
|
||||
}
|
||||
|
||||
|
||||
def _decode_bolt11(payment_request: str) -> Any:
|
||||
from lnbits import bolt11
|
||||
|
||||
return bolt11.decode(payment_request)
|
||||
|
||||
|
||||
def _decoded_invoice_response(invoice: Any) -> DecodeInvoiceResponse:
|
||||
return DecodeInvoiceResponse(
|
||||
payment_hash=str(getattr(invoice, "payment_hash", "")) or None,
|
||||
amount_msat=_invoice_amount_msat(invoice),
|
||||
expiry=_invoice_expiry(invoice),
|
||||
expires_at=_invoice_expires_at(invoice),
|
||||
memo=_invoice_memo(invoice),
|
||||
)
|
||||
|
||||
|
||||
def _invoice_amount_msat(invoice: Any) -> int | None:
|
||||
amount_msat = getattr(invoice, "amount_msat", None)
|
||||
if amount_msat is None:
|
||||
return None
|
||||
return int(amount_msat)
|
||||
|
||||
|
||||
def _invoice_expiry(invoice: Any) -> int | None:
|
||||
expiry = getattr(invoice, "expiry", None)
|
||||
if expiry is None:
|
||||
return None
|
||||
return int(expiry)
|
||||
|
||||
|
||||
def _invoice_expires_at(invoice: Any) -> int | None:
|
||||
expiry_date = getattr(invoice, "expiry_date", None)
|
||||
if isinstance(expiry_date, datetime):
|
||||
return int(expiry_date.timestamp())
|
||||
|
||||
date = getattr(invoice, "date", None)
|
||||
expiry = getattr(invoice, "expiry", None)
|
||||
if isinstance(date, datetime) and expiry is not None:
|
||||
return int(date.timestamp() + int(expiry))
|
||||
if isinstance(date, (int, float)) and expiry is not None:
|
||||
return int(date + int(expiry))
|
||||
return None
|
||||
|
||||
|
||||
def _invoice_memo(invoice: Any) -> str | None:
|
||||
memo = getattr(invoice, "description", None)
|
||||
return str(memo) if memo is not None else None
|
||||
@@ -30,26 +30,34 @@ class ExtensionAPIHost:
|
||||
) -> dict[str, Any]:
|
||||
method = self._require_method(host_name)
|
||||
request = self._request_model(method, payload)
|
||||
handler = getattr(self.api, method.python_name)
|
||||
handler = _resolve_attr_path(self.api, method.python_name)
|
||||
response = handler(request)
|
||||
if inspect.isawaitable(response):
|
||||
response = await response
|
||||
return self._response_payload(method, response)
|
||||
|
||||
def imports(self) -> dict[str, HostImport]:
|
||||
return self.imports_for_interface("host")
|
||||
|
||||
def import_object(self) -> dict[str, dict[str, HostImport]]:
|
||||
interfaces = sorted({method.host_interface for method in self.methods})
|
||||
return {
|
||||
f"lnbits:extension/{interface}": self.imports_for_interface(interface)
|
||||
for interface in interfaces
|
||||
}
|
||||
|
||||
def imports_for_interface(self, host_interface: str) -> dict[str, HostImport]:
|
||||
return {
|
||||
_snake_to_camel(method.host_name): self._make_import(method)
|
||||
for method in self.methods
|
||||
if method.host_interface == host_interface
|
||||
}
|
||||
|
||||
def import_object(self) -> dict[str, dict[str, HostImport]]:
|
||||
return {"lnbits:extension/host": self.imports()}
|
||||
|
||||
def _make_import(self, method: ExtensionAPIMethod) -> HostImport:
|
||||
async def host_import(
|
||||
payload: Mapping[str, Any] | BaseModel | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.invoke(method.host_name, payload)
|
||||
return await self.invoke(method.method_id, payload)
|
||||
|
||||
return host_import
|
||||
|
||||
@@ -66,6 +74,8 @@ class ExtensionAPIHost:
|
||||
index: dict[str, ExtensionAPIMethod] = {}
|
||||
for method in methods:
|
||||
for host_name in {
|
||||
method.method_id,
|
||||
f"{method.host_interface}:{method.host_name}",
|
||||
method.host_name,
|
||||
_snake_to_camel(method.host_name),
|
||||
method.host_name.replace("_", "-"),
|
||||
@@ -118,3 +128,9 @@ def _snake_to_camel(value: str) -> str:
|
||||
def _to_snake(value: str) -> str:
|
||||
value = value.replace("-", "_")
|
||||
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value).lower()
|
||||
|
||||
|
||||
def _resolve_attr_path(value: Any, path: str) -> Any:
|
||||
for part in path.split("."):
|
||||
value = getattr(value, part)
|
||||
return value
|
||||
|
||||
@@ -128,12 +128,17 @@ def _add_extension_host_imports(
|
||||
event_loop: asyncio.AbstractEventLoop,
|
||||
) -> None:
|
||||
with linker.root() as root:
|
||||
with root.add_instance("lnbits:extension/host") as host:
|
||||
for method in list_extension_api_methods():
|
||||
host.add_func(
|
||||
method.host_name.replace("_", "-"),
|
||||
_make_host_import(api_host, method.host_name, event_loop),
|
||||
)
|
||||
methods_by_interface: dict[str, list[Any]] = {}
|
||||
for method in list_extension_api_methods():
|
||||
methods_by_interface.setdefault(method.host_interface, []).append(method)
|
||||
|
||||
for host_interface, methods in methods_by_interface.items():
|
||||
with root.add_instance(f"lnbits:extension/{host_interface}") as host:
|
||||
for method in methods:
|
||||
host.add_func(
|
||||
method.host_name.replace("_", "-"),
|
||||
_make_host_import(api_host, method.method_id, event_loop),
|
||||
)
|
||||
|
||||
|
||||
def _make_host_import(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
@@ -185,6 +186,7 @@ def _render_method_metadata(
|
||||
f' namespace: "{method.namespace}",',
|
||||
f' sdkName: "{method.sdk_name}",',
|
||||
f' pythonName: "{method.python_name}",',
|
||||
f' hostInterface: "{method.host_interface}",',
|
||||
f' hostName: "{method.host_name}",',
|
||||
f' hostJsName: "{_camel(method.host_name)}",',
|
||||
f" requiredPermission: {permission},",
|
||||
@@ -197,76 +199,120 @@ def _render_method_metadata(
|
||||
|
||||
def _render_host_type(methods: Sequence[ExtensionAPIMethod]) -> list[str]:
|
||||
lines = ["export type ExtensionHost = {"]
|
||||
for method in sorted(methods, key=lambda item: item.host_name):
|
||||
request = _model_name(method.request_model)
|
||||
response = _model_name(method.response_model)
|
||||
if _is_empty_model(method.request_model):
|
||||
lines.append(f" {_camel(method.host_name)}(): MaybePromise<{response}>")
|
||||
else:
|
||||
lines.append(
|
||||
f" {_camel(method.host_name)}"
|
||||
f"(input: {request}): MaybePromise<{response}>"
|
||||
)
|
||||
lines.append("}")
|
||||
return lines
|
||||
|
||||
|
||||
def _render_sdk_type(methods: Sequence[ExtensionAPIMethod]) -> list[str]:
|
||||
namespaces = _methods_by_namespace(methods)
|
||||
lines = ["export type ExtensionSdk = {"]
|
||||
for namespace, namespace_methods in namespaces.items():
|
||||
lines.append(f" {namespace}: {{")
|
||||
for method in namespace_methods:
|
||||
for host_interface, interface_methods in _methods_by_host_interface(
|
||||
methods
|
||||
).items():
|
||||
lines.append(f" {_ts_property(host_interface)}: {{")
|
||||
for method in sorted(interface_methods, key=lambda item: item.host_name):
|
||||
request = _model_name(method.request_model)
|
||||
response = _model_name(method.response_model)
|
||||
if _is_empty_model(method.request_model):
|
||||
lines.append(f" {method.sdk_name}(): Promise<{response}>")
|
||||
lines.append(
|
||||
f" {_camel(method.host_name)}(): MaybePromise<{response}>"
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
f" {method.sdk_name}(input: {request}): Promise<{response}>"
|
||||
f" {_camel(method.host_name)}"
|
||||
f"(input: {request}): MaybePromise<{response}>"
|
||||
)
|
||||
lines.append(" }")
|
||||
lines.append("}")
|
||||
return lines
|
||||
|
||||
|
||||
def _render_sdk_type(methods: Sequence[ExtensionAPIMethod]) -> list[str]:
|
||||
lines = ["export type ExtensionSdk = {"]
|
||||
_render_sdk_type_node(lines, _namespace_tree(methods), 1)
|
||||
lines.append("}")
|
||||
return lines
|
||||
|
||||
|
||||
def _render_create_sdk(methods: Sequence[ExtensionAPIMethod]) -> list[str]:
|
||||
namespaces = _methods_by_namespace(methods)
|
||||
lines = [
|
||||
"export function createExtensionSdk(",
|
||||
" host: ExtensionHost",
|
||||
"): ExtensionSdk {",
|
||||
" return {",
|
||||
]
|
||||
for namespace, namespace_methods in namespaces.items():
|
||||
lines.append(f" {namespace}: {{")
|
||||
for method in namespace_methods:
|
||||
host_name = _camel(method.host_name)
|
||||
if _is_empty_model(method.request_model):
|
||||
signature = f"{method.sdk_name}()"
|
||||
host_call = f"host.{host_name}()"
|
||||
else:
|
||||
signature = f"{method.sdk_name}(input)"
|
||||
host_call = f"host.{host_name}(input)"
|
||||
lines.extend(
|
||||
[
|
||||
f" async {signature} {{",
|
||||
f" return {host_call}",
|
||||
" },",
|
||||
]
|
||||
)
|
||||
lines.append(" },")
|
||||
_render_create_sdk_node(lines, _namespace_tree(methods), 2)
|
||||
lines.extend([" }", "}"])
|
||||
return lines
|
||||
|
||||
|
||||
def _methods_by_namespace(
|
||||
def _render_sdk_type_node(lines: list[str], node: dict[str, Any], level: int) -> None:
|
||||
indent = " " * level
|
||||
for namespace, child in _iter_child_namespaces(node):
|
||||
lines.append(f"{indent}{namespace}: {{")
|
||||
_render_sdk_type_node(lines, child, level + 1)
|
||||
lines.append(f"{indent}}}")
|
||||
|
||||
for method in node.get("__methods__", []):
|
||||
request = _model_name(method.request_model)
|
||||
response = _model_name(method.response_model)
|
||||
if _is_empty_model(method.request_model):
|
||||
lines.append(f"{indent}{method.sdk_name}(): Promise<{response}>")
|
||||
else:
|
||||
lines.append(
|
||||
f"{indent}{method.sdk_name}(input: {request}): Promise<{response}>"
|
||||
)
|
||||
|
||||
|
||||
def _render_create_sdk_node(lines: list[str], node: dict[str, Any], level: int) -> None:
|
||||
indent = " " * level
|
||||
for namespace, child in _iter_child_namespaces(node):
|
||||
lines.append(f"{indent}{namespace}: {{")
|
||||
_render_create_sdk_node(lines, child, level + 1)
|
||||
lines.append(f"{indent}}},")
|
||||
|
||||
for method in node.get("__methods__", []):
|
||||
host_call_target = (
|
||||
f"host{_ts_access(method.host_interface)}"
|
||||
f"{_ts_access(_camel(method.host_name))}"
|
||||
)
|
||||
if _is_empty_model(method.request_model):
|
||||
signature = f"{method.sdk_name}()"
|
||||
host_call = f"{host_call_target}()"
|
||||
else:
|
||||
signature = f"{method.sdk_name}(input)"
|
||||
host_call = f"{host_call_target}(input)"
|
||||
lines.extend(
|
||||
[
|
||||
f"{indent}async {signature} {{",
|
||||
f"{indent} return {host_call}",
|
||||
f"{indent}}},",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _methods_by_host_interface(
|
||||
methods: Sequence[ExtensionAPIMethod],
|
||||
) -> dict[str, list[ExtensionAPIMethod]]:
|
||||
namespaces: dict[str, list[ExtensionAPIMethod]] = defaultdict(list)
|
||||
interfaces: dict[str, list[ExtensionAPIMethod]] = defaultdict(list)
|
||||
for method in sorted(
|
||||
methods, key=lambda item: (item.host_interface, item.host_name)
|
||||
):
|
||||
interfaces[method.host_interface].append(method)
|
||||
return dict(sorted(interfaces.items()))
|
||||
|
||||
|
||||
def _namespace_tree(methods: Sequence[ExtensionAPIMethod]) -> dict[str, Any]:
|
||||
tree: dict[str, Any] = {}
|
||||
for method in sorted(methods, key=lambda item: (item.namespace, item.sdk_name)):
|
||||
namespaces[method.namespace].append(method)
|
||||
return dict(sorted(namespaces.items()))
|
||||
node = tree
|
||||
for part in method.namespace.split("."):
|
||||
node = node.setdefault(part, {})
|
||||
node.setdefault("__methods__", []).append(method)
|
||||
return tree
|
||||
|
||||
|
||||
def _iter_child_namespaces(
|
||||
node: dict[str, Any],
|
||||
) -> list[tuple[str, dict[str, Any]]]:
|
||||
return [
|
||||
(key, value)
|
||||
for key, value in sorted(node.items())
|
||||
if key != "__methods__" and isinstance(value, dict)
|
||||
]
|
||||
|
||||
|
||||
def _model_name(model: type[BaseModel]) -> str:
|
||||
@@ -278,6 +324,18 @@ def _camel(value: str) -> str:
|
||||
return head + "".join(part.capitalize() for part in tail)
|
||||
|
||||
|
||||
def _ts_property(value: str) -> str:
|
||||
if re.match(r"^[A-Za-z_$][A-Za-z0-9_$]*$", value):
|
||||
return value
|
||||
return f'"{value}"'
|
||||
|
||||
|
||||
def _ts_access(value: str) -> str:
|
||||
if re.match(r"^[A-Za-z_$][A-Za-z0-9_$]*$", value):
|
||||
return f".{value}"
|
||||
return f'["{value}"]'
|
||||
|
||||
|
||||
def _is_empty_model(model: type[BaseModel]) -> bool:
|
||||
return not model.__fields__
|
||||
|
||||
|
||||
Reference in New Issue
Block a user